침입수 분석결과 실측하수량 관로 우선 줌인 및 지역 선이동 제거
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"use client";
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState, type MutableRefObject } from "react";
|
||||
import GeoJSON from "ol/format/GeoJSON.js";
|
||||
@@ -161,6 +161,33 @@ const getFirstFeatureId = (payload: any) => {
|
||||
return String(firstFeature?.id ?? "").trim();
|
||||
};
|
||||
|
||||
const getOilqPipeExtent = (features: any[]) => {
|
||||
const oilqFeatures = features.filter((feature) => Boolean(feature?.get?.("hasOilq")));
|
||||
if (oilqFeatures.length === 0) return null;
|
||||
|
||||
let nextExtent: [number, number, number, number] | null = null;
|
||||
oilqFeatures.forEach((feature) => {
|
||||
const geometryExtent = feature?.getGeometry?.()?.getExtent?.();
|
||||
if (!Array.isArray(geometryExtent) || geometryExtent.some((value: number) => !Number.isFinite(value))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!nextExtent) {
|
||||
nextExtent = [...geometryExtent] as [number, number, number, number];
|
||||
return;
|
||||
}
|
||||
|
||||
nextExtent = [
|
||||
Math.min(nextExtent[0], geometryExtent[0]),
|
||||
Math.min(nextExtent[1], geometryExtent[1]),
|
||||
Math.max(nextExtent[2], geometryExtent[2]),
|
||||
Math.max(nextExtent[3], geometryExtent[3]),
|
||||
];
|
||||
});
|
||||
|
||||
return nextExtent;
|
||||
};
|
||||
|
||||
export const useInftwtResultMap = ({
|
||||
gisMaps,
|
||||
setFeatureData,
|
||||
@@ -198,7 +225,7 @@ export const useInftwtResultMap = ({
|
||||
});
|
||||
|
||||
layer.set("id", INFTWT_ZONE_LAYER_ID);
|
||||
layer.set("lyrNm", "침입수분석-처리구역");
|
||||
layer.set("lyrNm", "移⑥엯?섎텇??泥섎━援ъ뿭");
|
||||
layer.set("lyrTyNm", "tb_ms_swage_prcs_zone");
|
||||
layer.set("clickPsblYn", "N");
|
||||
layer.setVisible?.(true);
|
||||
@@ -224,7 +251,7 @@ export const useInftwtResultMap = ({
|
||||
});
|
||||
|
||||
layer.set("id", INFTWT_PIPE_LAYER_ID);
|
||||
layer.set("lyrNm", "침입수분석-결과관로");
|
||||
layer.set("lyrNm", "침입수분석 결과관로");
|
||||
layer.set("lyrTyNm", "tb_ms_swage_ppl");
|
||||
layer.set("clickPsblYn", "Y");
|
||||
layer.setVisible?.(true);
|
||||
@@ -241,6 +268,7 @@ export const useInftwtResultMap = ({
|
||||
const zoneExtent = zoneSourceRef.current?.getExtent?.();
|
||||
const pipeExtent = pipeSourceRef.current?.getExtent?.();
|
||||
const pipeFeatures = pipeSourceRef.current?.getFeatures?.() ?? [];
|
||||
const oilqPipeExtent = getOilqPipeExtent(pipeFeatures);
|
||||
const hasPipeFeatures = pipeFeatures.length > 0;
|
||||
const hasZoneFeatures = Boolean(zoneSourceRef.current?.getFeatures?.().length);
|
||||
const preferZone = mapView?.focusTargetType === "ZONE" && hasZoneFeatures;
|
||||
@@ -257,13 +285,15 @@ export const useInftwtResultMap = ({
|
||||
: false;
|
||||
|
||||
const shouldUseZoneExtent = preferZone && (hasPipeInsideZone || !hasPipeFeatures);
|
||||
const extent = shouldUseZoneExtent
|
||||
? zoneExtent
|
||||
: hasPipeFeatures
|
||||
? pipeExtent
|
||||
: hasZoneFeatures
|
||||
? zoneExtent
|
||||
: null;
|
||||
const extent = oilqPipeExtent
|
||||
? oilqPipeExtent
|
||||
: shouldUseZoneExtent
|
||||
? zoneExtent
|
||||
: hasPipeFeatures
|
||||
? pipeExtent
|
||||
: hasZoneFeatures
|
||||
? zoneExtent
|
||||
: null;
|
||||
|
||||
if (!map || !extent || extent.some((value: number) => !Number.isFinite(value))) {
|
||||
return;
|
||||
@@ -272,7 +302,7 @@ export const useInftwtResultMap = ({
|
||||
map.getView?.().fit?.(extent, {
|
||||
duration: 350,
|
||||
padding: DEFAULT_RESULT_FIT_PADDING,
|
||||
maxZoom: shouldUseZoneExtent ? 15 : 16,
|
||||
maxZoom: oilqPipeExtent ? 17 : shouldUseZoneExtent ? 15 : 16,
|
||||
constrainResolution: true,
|
||||
nearest: true,
|
||||
});
|
||||
@@ -307,6 +337,11 @@ export const useInftwtResultMap = ({
|
||||
|
||||
const moveToStableFocus = useCallback(
|
||||
async (mapView: InftwtResultMapViewResponse) => {
|
||||
if ((mapView.oilqPipeCount ?? 0) > 0) {
|
||||
fitToRenderedFeatures(mapView);
|
||||
return;
|
||||
}
|
||||
|
||||
const activeRow = activeResultRowRef.current;
|
||||
const movedByRegion = moveByRegionCodes(
|
||||
activeRow?.ctpvCd ?? mapView.analysis?.ctpvCd,
|
||||
@@ -353,7 +388,7 @@ export const useInftwtResultMap = ({
|
||||
pipeLayerRef.current?.changed?.();
|
||||
gisMaps.current?.renderSync?.();
|
||||
|
||||
console.info("침입수 FE overlay 렌더 결과", {
|
||||
console.info("移⑥엯??FE overlay ?뚮뜑 寃곌낵", {
|
||||
focusTargetType: mapView.focusTargetType,
|
||||
zoneFeatureCount: zoneFeatures.length,
|
||||
pipeFeatureCount: pipeFeatures.length,
|
||||
@@ -372,6 +407,15 @@ export const useInftwtResultMap = ({
|
||||
setIsOpen(false);
|
||||
setIsSearchModalOpen(false);
|
||||
|
||||
const cachedMapView = mapViewCacheRef.current.get(row.inftwtAnlsSn);
|
||||
let mapView = cachedMapView;
|
||||
|
||||
if (!mapView) {
|
||||
const payload = await getRawJson(`/sewerage-service/api/v1/inftwtAnlsRslt/mapView/${row.inftwtAnlsSn}`);
|
||||
mapView = (payload?.data ?? payload) as InftwtResultMapViewResponse;
|
||||
mapViewCacheRef.current.set(row.inftwtAnlsSn, mapView);
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent(INFTWT_PROC_SEARCH_EVENT, {
|
||||
@@ -379,26 +423,17 @@ export const useInftwtResultMap = ({
|
||||
ctpvCd: row.ctpvCd ?? "0",
|
||||
sggCd: row.sggCd ?? "0",
|
||||
swtrmtFcltNm: row.swtrmtFcltNm ?? "",
|
||||
suppressMapMove: (mapView.oilqPipeCount ?? 0) > 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const cachedMapView = mapViewCacheRef.current.get(row.inftwtAnlsSn);
|
||||
if (cachedMapView) {
|
||||
setActiveMapView(cachedMapView);
|
||||
void renderMapView(cachedMapView);
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = await getRawJson(`/sewerage-service/api/v1/inftwtAnlsRslt/mapView/${row.inftwtAnlsSn}`);
|
||||
const mapView = (payload?.data ?? payload) as InftwtResultMapViewResponse;
|
||||
mapViewCacheRef.current.set(row.inftwtAnlsSn, mapView);
|
||||
setActiveMapView(mapView);
|
||||
void renderMapView(mapView);
|
||||
} catch (error) {
|
||||
console.error("침입수 분석결과 지도 연계 중 오류:", error);
|
||||
alert("침입수 분석결과를 지도에 연계하지 못했습니다.");
|
||||
console.error("移⑥엯??遺꾩꽍寃곌낵 吏???곌퀎 以??ㅻ쪟:", error);
|
||||
alert("移⑥엯??遺꾩꽍寃곌낵瑜?吏?꾩뿉 ?곌퀎?섏? 紐삵뻽?듬땲??");
|
||||
}
|
||||
},
|
||||
[renderMapView, setFeatureData, setIsOpen, setIsSearchModalOpen],
|
||||
@@ -419,7 +454,7 @@ export const useInftwtResultMap = ({
|
||||
if (!payload?.row) return;
|
||||
void handleRowSelect(payload.row);
|
||||
} catch (error) {
|
||||
console.warn("침입수 분석결과 storage 동기화 값을 해석하지 못했습니다.", error);
|
||||
console.warn("移⑥엯??遺꾩꽍寃곌낵 storage ?숆린??媛믪쓣 ?댁꽍?섏? 紐삵뻽?듬땲??", error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -459,7 +494,7 @@ export const useInftwtResultMap = ({
|
||||
});
|
||||
|
||||
if (!popupWindow) {
|
||||
alert("팝업이 차단되었습니다. 브라우저 팝업 허용 설정을 확인해주세요.");
|
||||
alert("?앹뾽??李⑤떒?섏뿀?듬땲?? 釉뚮씪?곗? ?앹뾽 ?덉슜 ?ㅼ젙???뺤씤?댁<?몄슂.");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -112,3 +112,15 @@
|
||||
## 16. 2026-07-09 버튼 순서 미세조정
|
||||
|
||||
- 사용자 요청에 따라 침입수 분석 화면 액션 버튼 순서를 `양식 받기 -> 침입수분석 실행 -> 결과보기`로 조정했다.
|
||||
- 변경은 공통 `ii/BaseLayout.tsx` 렌더 순서만 수정해 RDII 분석 화면에도 같은 순서가 적용되도록 맞췄다.
|
||||
|
||||
## 17. 2026-07-09 실측하수량 관로 우선 줌인 보정
|
||||
|
||||
- 침입수 분석결과 popup에서 row click 시 `oilqPipeCount > 0`이면 시도/시군구 중심 이동보다 `hasOilq` 관로 extent fit을 우선 적용하도록 `useInftwtResultMap`을 보정했다.
|
||||
- 실측하수량 관로가 없는 row만 기존 지역 중심 이동과 일반 결과 관로 fallback을 유지한다.
|
||||
- 이 보정으로 주황색으로 표시되는 실측하수량 관로가 있는 케이스는 지도 진입 직후 해당 관로 주변으로 바로 확대된다.
|
||||
|
||||
## 18. 2026-07-09 지역 선이동 제거 보정
|
||||
|
||||
- `useInftwtResultMap`은 row click 직후 mapView를 먼저 확보한 뒤, `oilqPipeCount > 0`이면 검색바 동기화 이벤트에 `suppressMapMove`를 내려 지역 중심 이동을 건너뛰도록 보정했다.
|
||||
- `procSearchBar`는 `suppressMapMove`가 내려오면 시도/시군구/시설 상태만 갱신하고 `moveMapToCenter`는 호출하지 않는다.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"use client";
|
||||
"use client";
|
||||
import { SelectBox, type SelectDataType } from "../../../ui";
|
||||
import { Button } from "@common/components/ui";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
@@ -29,7 +29,7 @@ interface SearchBarProps {
|
||||
}
|
||||
|
||||
interface CenterSelectDataType extends SelectDataType {
|
||||
center?: [number, number]; // 좌표 중심 위치
|
||||
center?: [number, number]; // 醫뚰몴 以묒떖 ?꾩튂
|
||||
}
|
||||
|
||||
export const ProcSearchBar = ({
|
||||
@@ -74,7 +74,7 @@ export const ProcSearchBar = ({
|
||||
center: sidoCenter[String(f.properties.mctpv_cd ?? "").trim()] //getGeometryCenter(f.geometry) ?? undefined,
|
||||
}))
|
||||
.sort((a: any, b: any) => {
|
||||
// 커스텀 순서 정의
|
||||
// 而ㅼ뒪? ?쒖꽌 ?뺤쓽
|
||||
const customOrder = [
|
||||
"서울특별시",
|
||||
"부산광역시",
|
||||
@@ -98,19 +98,19 @@ export const ProcSearchBar = ({
|
||||
const indexA = customOrder.indexOf(a.text);
|
||||
const indexB = customOrder.indexOf(b.text);
|
||||
|
||||
// 둘 다 커스텀 순서에 있으면 해당 순서로
|
||||
// ????而ㅼ뒪? ?쒖꽌???덉쑝硫??대떦 ?쒖꽌濡?
|
||||
if (indexA !== -1 && indexB !== -1) {
|
||||
return indexA - indexB;
|
||||
}
|
||||
|
||||
// 하나만 커스텀 순서에 있으면 그것을 앞으로
|
||||
// ?섎굹留?而ㅼ뒪? ?쒖꽌???덉쑝硫?洹멸쾬???욎쑝濡?
|
||||
if (indexA !== -1) return -1;
|
||||
if (indexB !== -1) return 1;
|
||||
|
||||
// 둘 다 커스텀 순서에 없으면 기본 알파벳 순서
|
||||
// ????而ㅼ뒪? ?쒖꽌???놁쑝硫?湲곕낯 ?뚰뙆踰??쒖꽌
|
||||
return a.text.localeCompare(b.text, 'ko');
|
||||
});
|
||||
setSidoData([{ text: "시도", val: "0" }, ...list]);
|
||||
setSidoData([{ text: "?쒕룄", val: "0" }, ...list]);
|
||||
};
|
||||
|
||||
const fetchSigungu = async () => {
|
||||
@@ -174,7 +174,7 @@ export const ProcSearchBar = ({
|
||||
const json = await res.json();
|
||||
list = (json.features || [])
|
||||
.map((f: any) => ({
|
||||
text: f.properties.swtrmt_fclt_nm ?? "(이름없음)",
|
||||
text: f.properties.swtrmt_fclt_nm ?? "(?대쫫?놁쓬)",
|
||||
val: f.id,
|
||||
}))
|
||||
.sort((a: any, b: any) => a.text.localeCompare(b.text, "ko"));
|
||||
@@ -193,28 +193,28 @@ export const ProcSearchBar = ({
|
||||
});
|
||||
const json = await res.json();
|
||||
list = (json.data || []).map((item: any) => ({
|
||||
text: item.ruseUtztnFcltNm ?? "(이름없음)",
|
||||
text: item.ruseUtztnFcltNm ?? "(?대쫫?놁쓬)",
|
||||
val: item.pbnstRuseFcltId,
|
||||
}));
|
||||
} else if (type === "tb_wr_ruse_fclt_dmd_co") {
|
||||
const res = await fetch(url);
|
||||
const json = await res.json();
|
||||
list = (json.data || []).map((item: any) => ({
|
||||
text: item.dmdCoNm ?? "(이름없음)",
|
||||
text: item.dmdCoNm ?? "(?대쫫?놁쓬)",
|
||||
val: item.dmdInfoId,
|
||||
}));
|
||||
} else if (type === "tb_wr_tmpr_dmd_co") {
|
||||
const res = await fetch(url);
|
||||
const json = await res.json();
|
||||
list = (json.data || []).map((item: any) => ({
|
||||
text: item.dmdCoNm ?? "(이름없음)",
|
||||
text: item.dmdCoNm ?? "(?대쫫?놁쓬)",
|
||||
val: item.fctryMngSn,
|
||||
}));
|
||||
} else {
|
||||
const res = await fetch(url);
|
||||
const json = await res.json();
|
||||
list = (json.data || []).map((item: any) => ({
|
||||
text: item.bdstNm ?? "(이름없음)",
|
||||
text: item.bdstNm ?? "(?대쫫?놁쓬)",
|
||||
val: item.bdstInfoSn,
|
||||
}));
|
||||
}
|
||||
@@ -264,7 +264,7 @@ export const ProcSearchBar = ({
|
||||
|
||||
const prefix = nextSidoCd.substring(0, 2);
|
||||
const filtered = sigunData.filter((item) => item.val.startsWith(prefix));
|
||||
setFilteredSigunData([{ text: "시군구", val: "0" }, ...filtered]);
|
||||
setFilteredSigunData([{ text: "시군구", val: "0" }, ...filtered]);
|
||||
setSigunValue("0");
|
||||
},
|
||||
[sigunData],
|
||||
@@ -315,7 +315,7 @@ export const ProcSearchBar = ({
|
||||
|
||||
const handleSearch = useCallback(() => {
|
||||
if (sidoValue === "0") {
|
||||
alert("시도를 선택해 주세요");
|
||||
alert("시도를 선택해 주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -344,10 +344,12 @@ export const ProcSearchBar = ({
|
||||
ctpvCd?: string;
|
||||
sggCd?: string;
|
||||
swtrmtFcltNm?: string;
|
||||
suppressMapMove?: boolean;
|
||||
}>;
|
||||
const nextCtpvCd = String(event.detail?.ctpvCd ?? "0").trim() || "0";
|
||||
const nextSggCd = String(event.detail?.sggCd ?? "0").trim() || "0";
|
||||
const nextSwtrmtFcltNm = String(event.detail?.swtrmtFcltNm ?? "").trim();
|
||||
const suppressMapMove = Boolean(event.detail?.suppressMapMove);
|
||||
const nextSidoCenter = sidoCenter[nextCtpvCd.slice(0, 2)];
|
||||
const nextSigunguCenter = getSigunguCenter(nextSggCd);
|
||||
|
||||
@@ -364,6 +366,10 @@ export const ProcSearchBar = ({
|
||||
setFacilityValue("0");
|
||||
setPendingExternalFacilityName(nextSwtrmtFcltNm);
|
||||
|
||||
if (suppressMapMove) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextSggCd !== "0" && nextSigunguCenter) {
|
||||
moveMapToCenter(gisMaps, nextSigunguCenter, 12);
|
||||
return;
|
||||
@@ -430,7 +436,7 @@ export const ProcSearchBar = ({
|
||||
/>
|
||||
<div className="flex flex-row space-x-2">
|
||||
<Button color="primary" className="mx-2 mt-1" size="sm" onClick={handleSearch}>
|
||||
조회
|
||||
議고쉶
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user