노후관로 분석 - 시도 선택시 지도 줌인 기능 구현

This commit is contained in:
kiminchan
2026-05-13 11:11:54 +09:00
parent 31b885a497
commit 9604e4591b
3 changed files with 94 additions and 7 deletions
@@ -24,6 +24,7 @@ import { downloadAgedPipeCsv } from "./agedPipeExport";
const POPUP_ROW_SELECT_MESSAGE = "SN_AGED_PIPE_ROW_SELECT";
const POPUP_MINIMIZE_MESSAGE = "SN_AGED_PIPE_POPUP_MINIMIZE";
const POPUP_FILTER_SYNC_MESSAGE = "SN_AGED_PIPE_FILTER_SYNC";
const POPUP_CTPV_FOCUS_MESSAGE = "SN_AGED_PIPE_CTPV_FOCUS";
export default function AgedPipePopupPage() {
const searchParams = useSearchParams();
@@ -123,7 +124,7 @@ export default function AgedPipePopupPage() {
return () => window.removeEventListener("message", handleFilterSyncMessage);
}, [loadRows]);
const handleSearch = (page = 1) => {
const handleSearch = async (page = 1, shouldFocusCtpv = false) => {
if (!validateElapsedYears(Number(filters.elapsedYears))) {
alert("경과년도는 1년~100년 범위 내에서 입력해주세요.");
return;
@@ -131,7 +132,17 @@ export default function AgedPipePopupPage() {
const normalized = normalizeAgedPipeFilters(filters);
setFilters(normalized);
loadRows(page, normalized);
await loadRows(page, normalized);
if (shouldFocusCtpv && normalized.ctpvCd !== "0") {
window.opener?.postMessage(
{
type: POPUP_CTPV_FOCUS_MESSAGE,
ctpvCd: normalized.ctpvCd,
},
window.location.origin,
);
}
};
const handleRowClick = (index?: number) => {
@@ -239,7 +250,7 @@ export default function AgedPipePopupPage() {
});
}}
onKeyDown={(event) => {
if (event.key === "Enter") handleSearch(1);
if (event.key === "Enter") void handleSearch(1, true);
}}
/>
</label>
@@ -288,7 +299,7 @@ export default function AgedPipePopupPage() {
size="sm"
color="primary"
className="h-10 px-5 !text-[1.1rem]"
onClick={() => handleSearch(1)}
onClick={() => void handleSearch(1, true)}
>
조회
</Button>
@@ -311,7 +322,7 @@ export default function AgedPipePopupPage() {
pageSize={10}
total={total}
cellClick={(index) => handleRowClick(index)}
onPageChange={(page) => handleSearch(page)}
onPageChange={(page) => void handleSearch(page)}
curPage={currentPage}
index={selectedIndex}
widthFixed={true}
@@ -10,7 +10,7 @@ import {
export const AGED_PIPE_LAYER_TYPE = "tb_ms_swage_ppl";
export const AGED_PIPE_DEFAULT_ELAPSED_YEARS = 30;
export const AGED_PIPE_PAGE_SIZE = 10;
export const AGED_PIPE_MIN_ZOOM = 14;
export const AGED_PIPE_MIN_ZOOM = 13;
export const AGED_PIPE_INVALID_INSTALL_YMD_PREFIXES = ["19000101"];
export type AgedPipeFilters = {
+77 -1
View File
@@ -68,6 +68,10 @@ const AGED_PIPE_HIGHLIGHT_LAYER_ID = "SN_AGED_PIPE_HIGHLIGHT";
const AGED_PIPE_ROW_SELECT_MESSAGE = "SN_AGED_PIPE_ROW_SELECT";
const AGED_PIPE_POPUP_MINIMIZE_MESSAGE = "SN_AGED_PIPE_POPUP_MINIMIZE";
const AGED_PIPE_FILTER_SYNC_MESSAGE = "SN_AGED_PIPE_FILTER_SYNC";
// 노후관로 팝업에서 조회 버튼 클릭 시, 부모 지도를 선택 시도 범위로 이동시키는 메시지이다.
const AGED_PIPE_CTPV_FOCUS_MESSAGE = "SN_AGED_PIPE_CTPV_FOCUS";
// 시도 기준 자동 이동은 조회 직후 노후관로가 바로 보이는 수준까지 한 단계 더 당겨 고정한다.
const AGED_PIPE_CTPV_FOCUS_ZOOM = 14;
const SOUTH_KOREA_VIEW_EXTENT: [number, number, number, number] = [
720000,
1445000,
@@ -952,6 +956,72 @@ const Map = () => {
}
}, [draftAgedPipeFilters, isAgedPipeListOpen, loadAgedPipeList, syncAgedPipePopupFilters]);
// 노후관로 팝업에서 시도 기준 조회를 실행하면, 부모 지도창을 해당 시도 extent로 이동시킨다.
const focusMapToAgedPipeCtpv = useCallback(async (ctpvCd: string) => {
const normalizedCtpvCd = String(ctpvCd ?? "").replace(/[^0-9]/g, "").slice(0, 2);
if (!normalizedCtpvCd || normalizedCtpvCd === "0") return;
const map = gisMaps.current;
if (!map) return;
try {
const params = new URLSearchParams({
service: "WFS",
version: "1.0.0",
request: "GetFeature",
typeName: "hasudo:tb_ms_ctpv",
outputFormat: "application/json",
CQL_FILTER: `mctpv_cd='${normalizedCtpvCd}'`,
});
const payload = await getRawJson(`/geoserver/hasudo/ows?${params.toString()}`);
// 시도 WFS는 환경마다 3857/4326 등 다른 CRS로 내려올 수 있으므로 응답 crs를 우선 신뢰한다.
const responseCrsName = String(payload?.crs?.properties?.name ?? "");
const resolvedDataProjection = responseCrsName.includes("EPSG::")
? `EPSG:${responseCrsName.split("EPSG::")[1]}`
: responseCrsName.includes("EPSG:")
? responseCrsName.slice(responseCrsName.lastIndexOf("EPSG:"))
: GisApp.FROM_PROJ;
const features = new GeoJSON().readFeatures(payload, {
dataProjection: resolvedDataProjection,
featureProjection: map.getView().getProjection(),
});
let extent: [number, number, number, number] | null = null;
features.forEach((feature) => {
const geometry = feature.getGeometry();
if (!geometry) return;
const geometryExtent = geometry.getExtent();
if (!extent) {
extent = [...geometryExtent] as [number, number, number, number];
return;
}
extent = [
Math.min(extent[0], geometryExtent[0]),
Math.min(extent[1], geometryExtent[1]),
Math.max(extent[2], geometryExtent[2]),
Math.max(extent[3], geometryExtent[3]),
];
});
if (!extent) return;
const view = map.getView();
view.fit(extent, {
padding: AGED_PIPE_FOCUS_PADDING,
maxZoom: 18,
constrainResolution: true,
nearest: true,
});
const fittedZoom = view.getZoom() ?? 0;
if (fittedZoom < AGED_PIPE_CTPV_FOCUS_ZOOM) {
view.setZoom(AGED_PIPE_CTPV_FOCUS_ZOOM);
}
} catch (error) {
console.error("노후관로 시도 이동 중 오류:", error);
}
}, []);
const openAgedPipePopupWindow = useCallback(() => {
const popup = openWindowPopup("agedPipePopup", {
elapsedYears: String(appliedAgedPipeFilters.elapsedYears),
@@ -1067,6 +1137,12 @@ const Map = () => {
return;
}
if (event.data?.type === AGED_PIPE_CTPV_FOCUS_MESSAGE) {
if (!event.data?.ctpvCd) return;
void focusMapToAgedPipeCtpv(String(event.data.ctpvCd));
return;
}
if (event.data?.type === PIPE_ANALYSIS_DETAIL_ROW_SELECT_MESSAGE) {
if (!event.data?.row) return;
handlePipeAnalysisRowSelect(event.data.row as PipeAnalysisDetailRow);
@@ -1075,7 +1151,7 @@ const Map = () => {
window.addEventListener("message", handleAgedPipePopupMessage);
return () => window.removeEventListener("message", handleAgedPipePopupMessage);
}, [agedPipeTotalCount, handleAgedPipeRowDoubleClick, handlePipeAnalysisRowSelect]);
}, [agedPipeTotalCount, focusMapToAgedPipeCtpv, handleAgedPipeRowDoubleClick, handlePipeAnalysisRowSelect]);
// WaterDepth 표시 여부를 결정하는 함수
const handleWaterDepthVisibility = useCallback((checkedIds: string[], checkedNodesData: any[]) => {