유하방향 기능 구현
This commit is contained in:
@@ -17,10 +17,9 @@
|
||||
|
||||
"use client";
|
||||
|
||||
import Gis from "@common/business_components/ui/map/Gis";
|
||||
import { getData, getRawJson } from "../../components/lib/api";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
import GeoJSON from "ol/format/GeoJSON.js";
|
||||
import Point from "ol/geom/Point.js";
|
||||
import VectorLayer from "ol/layer/Vector.js";
|
||||
@@ -28,32 +27,39 @@ import { unByKey } from "ol/Observable.js";
|
||||
import VectorSource from "ol/source/Vector.js";
|
||||
import Stroke from "ol/style/Stroke.js";
|
||||
import Style from "ol/style/Style.js";
|
||||
import SearchModal from "./searchModal/searchModal";
|
||||
import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup";
|
||||
import { WaterDepth, WATER_DEPTH_LAYER_TYPES } from "./waterDepth/waterDepth";
|
||||
|
||||
import { openWindowPopup } from "@/features/windowPopup/lib/openWindowPopup";
|
||||
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/windowPopup/model/types";
|
||||
import { downloadExcelWithKoreanHeaders } from "@common/business_components/ui";
|
||||
import Gis from "@common/business_components/ui/map/Gis";
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
import { ProcSearchBar } from "@common/business_components/ui/map/searchBar";
|
||||
import { Button } from "@common/components";
|
||||
import useSearchModalStore from "@/store/useSearchModalStore";
|
||||
import useStore from "@/store/useStore";
|
||||
import { downloadExcelWithKoreanHeaders } from "@common/business_components/ui";
|
||||
|
||||
import { getData, getRawJson } from "../../components/lib/api";
|
||||
|
||||
import AgedPipeLayerSettingPanel from "./components/agedPipe/AgedPipeLayerSettingPanel";
|
||||
import {
|
||||
AGED_PIPE_DEFAULT_FILTERS,
|
||||
AGED_PIPE_EXCEL_HEADER,
|
||||
AGED_PIPE_MIN_ZOOM,
|
||||
AGED_PIPE_LAYER_TYPE,
|
||||
AGED_PIPE_MIN_ZOOM,
|
||||
AGED_PIPE_PAGE_SIZE,
|
||||
type AgedPipeFilters,
|
||||
type AgedPipeRow,
|
||||
buildAgedPipeCqlFilters,
|
||||
isAgedPipeLayerNode,
|
||||
matchesAgedPipeFilters,
|
||||
normalizeAgedPipeFilters,
|
||||
toDisplayPipeRow,
|
||||
validateElapsedYears,
|
||||
type AgedPipeFilters,
|
||||
type AgedPipeRow,
|
||||
AGED_PIPE_DEFAULT_FILTERS,
|
||||
} from "./components/agedPipe/agedPipeUtils";
|
||||
import { openWindowPopup } from "@/features/windowPopup/lib/openWindowPopup";
|
||||
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/windowPopup/model/types";
|
||||
import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup";
|
||||
import SearchModal from "./searchModal/searchModal";
|
||||
import { WATER_DEPTH_LAYER_TYPES,WaterDepth } from "./waterDepth/waterDepth";
|
||||
|
||||
import useSearchModalStore from "@/store/useSearchModalStore";
|
||||
import useStore from "@/store/useStore";
|
||||
|
||||
const AGED_PIPE_CONTROL_NODE_ID = "SN_AGED_PIPE";
|
||||
const AGED_PIPE_HIGHLIGHT_LAYER_ID = "SN_AGED_PIPE_HIGHLIGHT";
|
||||
@@ -70,18 +76,27 @@ const SOUTH_KOREA_VIEW_EXTENT: [number, number, number, number] = [
|
||||
// 유하방향은 GeoServer WMS/WFS 레이어가 아니라 BE 보정 GeoJSON API로 그리는 가상 레이어이다.
|
||||
const FLOW_DIRECTION_LAYER_TYPE = "tb_sn_swage_ppl_flow_direction";
|
||||
const FLOW_DIRECTION_LAYER_NAME = "유하방향";
|
||||
const FLOW_DIRECTION_PIPE_LAYER_TYPE = "tb_ms_swage_ppl";
|
||||
const FLOW_DIRECTION_SRS = "EPSG:5179";
|
||||
const FLOW_DIRECTION_DEFAULT_MIN_ZOOM = 13;
|
||||
const FLOW_DIRECTION_DEFAULT_LIMIT = 5000;
|
||||
const FLOW_DIRECTION_DEFAULT_COLOR = "rgba(40, 90, 180, 0.84)";
|
||||
const FLOW_DIRECTION_NETWORK_CORRECTION_COLOR = "rgba(0, 168, 132, 0.95)";
|
||||
const FLOW_DIRECTION_BBOX_BUFFER_RATIO = 0.35;
|
||||
const FLOW_DIRECTION_CACHE_MAX_FEATURES = 10000;
|
||||
|
||||
type FlowDirectionUsageStyle = {
|
||||
color: string;
|
||||
lineColor: string;
|
||||
};
|
||||
|
||||
type FlowDirectionMode = "PIPE_START_END" | "PIPE_DEPTH";
|
||||
|
||||
// /map은 관로 깊이 기준, /map2는 관로 원천 geometry 시점->종점 기준만 유지한다. 두 화면 모두 BBOX 조회만 사용한다.
|
||||
const getFlowDirectionModeByPathname = (pathname: string | null): FlowDirectionMode => {
|
||||
const normalizedPathname = String(pathname ?? "").replace(/\/$/, "");
|
||||
if (normalizedPathname.endsWith("/map2")) return "PIPE_START_END";
|
||||
return "PIPE_DEPTH";
|
||||
};
|
||||
|
||||
const AGED_PIPE_FOCUS_PADDING: [number, number, number, number] = [120, 120, 120, 120];
|
||||
const DEFAULT_FEATURE_FIT_PADDING: [number, number, number, number] = [50, 50, 50, 50];
|
||||
|
||||
@@ -132,15 +147,19 @@ const normalizeFlowDirectionLayerInfos = (data: any[]): any[] => {
|
||||
};
|
||||
|
||||
// 레이어 설정의 최소 표출 줌을 읽되, 값이 없으면 유하방향 기본 줌 기준을 적용한다.
|
||||
const getFiniteNumber = (value: unknown) => {
|
||||
if (value === null || value === undefined || String(value).trim() === "") return null;
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
};
|
||||
|
||||
const getFlowDirectionMinZoom = (layerInfo: any) => {
|
||||
const minZoom = Number(layerInfo?.minViewSize ?? layerInfo?.minZoom);
|
||||
return Number.isFinite(minZoom) ? minZoom : FLOW_DIRECTION_DEFAULT_MIN_ZOOM;
|
||||
return getFiniteNumber(layerInfo?.minViewSize ?? layerInfo?.minZoom) ?? FLOW_DIRECTION_DEFAULT_MIN_ZOOM;
|
||||
};
|
||||
|
||||
// 유하방향 화살표는 관로 위에 얹는 보조 레이어라 레이어 설정의 drawOrdr를 우선 사용하고 없으면 높은 zIndex를 준다.
|
||||
const getFlowDirectionZIndex = (layerInfo: any) => {
|
||||
const drawOrder = Number(layerInfo?.drawOrdr);
|
||||
return Number.isFinite(drawOrder) ? drawOrder : 9998;
|
||||
return getFiniteNumber(layerInfo?.drawOrdr) ?? 9998;
|
||||
};
|
||||
|
||||
// BE 응답이 FeatureCollection 그대로 오거나 공통 응답 wrapper의 data 안에 들어오는 두 계약을 모두 처리한다.
|
||||
@@ -357,8 +376,19 @@ const createFlowDirectionStyle = (
|
||||
const usageCode = String(feature.get("usgCd") ?? feature.get("usg_cd") ?? "").trim();
|
||||
const usageStyle = usageStyleMap[usageCode];
|
||||
const baseColor = usageStyle?.color ?? FLOW_DIRECTION_DEFAULT_COLOR;
|
||||
const isNetworkCorrected = String(
|
||||
feature.get("networkCorrectionYn") ??
|
||||
feature.get("network_correction_yn") ??
|
||||
"",
|
||||
).toUpperCase() === "Y";
|
||||
|
||||
const styleByConfidence: Record<string, { color: string; lineColor: string; scale: number; width: number }> = {
|
||||
const styleByConfidence: Record<string, { color: string; lineColor: string; scale: number; width: number; lineDash?: number[] }> = {
|
||||
HIGH: {
|
||||
color: baseColor,
|
||||
lineColor: usageStyle?.lineColor ?? withAlpha(baseColor, 0.42),
|
||||
scale: 1,
|
||||
width: 1.3,
|
||||
},
|
||||
MEDIUM: {
|
||||
color: baseColor,
|
||||
lineColor: usageStyle?.lineColor ?? withAlpha(baseColor, 0.42),
|
||||
@@ -372,12 +402,21 @@ const createFlowDirectionStyle = (
|
||||
width: 1.1,
|
||||
},
|
||||
};
|
||||
const style = styleByConfidence[confidence] ?? styleByConfidence.LOW!;
|
||||
const style = isNetworkCorrected
|
||||
? {
|
||||
color: FLOW_DIRECTION_NETWORK_CORRECTION_COLOR,
|
||||
lineColor: withAlpha(FLOW_DIRECTION_NETWORK_CORRECTION_COLOR, 0.5),
|
||||
scale: 1.08,
|
||||
width: 2,
|
||||
lineDash: [8, 4],
|
||||
}
|
||||
: styleByConfidence[confidence] ?? styleByConfidence.LOW!;
|
||||
const styles = [
|
||||
new Style({
|
||||
stroke: new Stroke({
|
||||
color: style.lineColor,
|
||||
width: style.width,
|
||||
lineDash: style.lineDash,
|
||||
lineCap: "round",
|
||||
lineJoin: "round",
|
||||
}),
|
||||
@@ -426,8 +465,8 @@ const extractUsageCodesFromLayerInfo = (layerInfo: any) => {
|
||||
|
||||
// 현재 줌에서 실제 표출 가능한 관로 레이어인지 확인해 숨겨진 관종의 유하방향 요청을 막는다.
|
||||
const isLayerVisibleAtZoom = (layerInfo: any, zoom: number) => {
|
||||
const minZoom = Number(layerInfo?.minViewSize ?? layerInfo?.minZoom);
|
||||
return !Number.isFinite(minZoom) || zoom >= minZoom;
|
||||
const minZoom = getFiniteNumber(layerInfo?.minViewSize ?? layerInfo?.minZoom);
|
||||
return minZoom === null || zoom >= minZoom;
|
||||
};
|
||||
|
||||
// 현재 체크되어 화면에 보이는 관로 레이어의 관종 코드와 선 색상을 유하방향 조회/스타일 기준으로 변환한다.
|
||||
@@ -437,6 +476,7 @@ const extractCheckedPipeUsageStyles = (checkedNodesData: any[], zoom: number) =>
|
||||
checkedNodesData.forEach((node) => {
|
||||
const data = node?.data ?? node;
|
||||
if (!data || isFlowDirectionLayerInfo(data)) return;
|
||||
if (getLayerTypeName(data) !== FLOW_DIRECTION_PIPE_LAYER_TYPE) return;
|
||||
if (!isLayerVisibleAtZoom(data, zoom)) return;
|
||||
|
||||
const usageCodes = extractUsageCodesFromLayerInfo(data);
|
||||
@@ -498,6 +538,8 @@ const appendAgedPipeControlNode = (data: any[]) => {
|
||||
};
|
||||
|
||||
const Map = () => {
|
||||
const pathname = usePathname();
|
||||
const flowDirectionMode = useMemo(() => getFlowDirectionModeByPathname(pathname), [pathname]);
|
||||
const { isSearchModalOpen, setIsSearchModalOpen } = useSearchModalStore();
|
||||
|
||||
const [treeData, setTreeData] = useState<any[]>([]); // 초기값을 객체 또는 null로 설정
|
||||
@@ -541,9 +583,13 @@ const Map = () => {
|
||||
// 같은 관로가 겹치는 BBOX에서 반복 조회될 수 있으므로 고유키와 삽입 순서를 캐시로 관리한다.
|
||||
const flowDirectionFeatureKeySetRef = useRef<Set<string>>(new Set());
|
||||
const flowDirectionFeatureKeyQueueRef = useRef<string[]>([]);
|
||||
// 관종 체크 변경 시 다른 관종 유하방향을 유지하기 위해 현재 source에 남겨둘 관종 코드를 별도로 추적한다.
|
||||
const flowDirectionVisibleUsageCodesRef = useRef<Set<string>>(new Set());
|
||||
// 관종 체크 변경으로 새로 켜진 usgCd만 다음 조회 대상으로 넘겨 기존 관종 재요청을 막는다.
|
||||
const flowDirectionSelectionRequestUsageCodesRef = useRef<string[] | null>(null);
|
||||
// 체크 해제나 재조회 중 늦게 도착한 이전 API 응답이 레이어를 다시 그리지 못하도록 요청 순번을 관리한다.
|
||||
const flowDirectionRequestSeqRef = useRef(0);
|
||||
// 지도 이동이 연속으로 발생하면 이전 bbox 요청을 중단해 늦은 응답과 불필요한 네트워크 대기를 줄인다.
|
||||
// 지도 이동이 연속으로 발생하면 이전 유하방향 요청을 중단해 늦은 응답과 불필요한 네트워크 대기를 줄인다.
|
||||
const flowDirectionAbortControllerRef = useRef<AbortController | null>(null);
|
||||
// 같은 장애 상황에서 alert가 반복 노출되지 않도록 실패 안내 여부를 기억한다.
|
||||
const flowDirectionFailureAlertedRef = useRef(false);
|
||||
@@ -621,6 +667,7 @@ const Map = () => {
|
||||
flowDirectionSourceRef.current = null;
|
||||
flowDirectionFeatureKeySetRef.current.clear();
|
||||
flowDirectionFeatureKeyQueueRef.current = [];
|
||||
flowDirectionVisibleUsageCodesRef.current.clear();
|
||||
}, []);
|
||||
|
||||
// 유하방향 cache/source만 비워 관종 변경처럼 조건이 달라진 경우 이전 조건의 피처가 남지 않게 한다.
|
||||
@@ -628,8 +675,46 @@ const Map = () => {
|
||||
flowDirectionSourceRef.current?.clear();
|
||||
flowDirectionFeatureKeySetRef.current.clear();
|
||||
flowDirectionFeatureKeyQueueRef.current = [];
|
||||
flowDirectionVisibleUsageCodesRef.current.clear();
|
||||
}, []);
|
||||
|
||||
// 체크 해제된 관종의 유하방향 feature/cache만 제거하고, 새로 체크된 관종 코드만 다음 요청 대상으로 돌려준다.
|
||||
const retainFlowDirectionUsageCodes = useCallback((usageCodes: string[]) => {
|
||||
const nextUsageCodes = new Set(usageCodes);
|
||||
const addedUsageCodes = usageCodes.filter((usageCode) =>
|
||||
!flowDirectionVisibleUsageCodesRef.current.has(usageCode),
|
||||
);
|
||||
const source = flowDirectionSourceRef.current;
|
||||
if (source) {
|
||||
source.getFeatures().forEach((feature: any) => {
|
||||
const usageCode = String(feature.get("usgCd") ?? feature.get("usg_cd") ?? "").trim();
|
||||
if (nextUsageCodes.has(usageCode)) return;
|
||||
|
||||
const featureKey = feature.get("flowDirectionCacheKey");
|
||||
if (featureKey) {
|
||||
flowDirectionFeatureKeySetRef.current.delete(String(featureKey));
|
||||
}
|
||||
source.removeFeature(feature);
|
||||
});
|
||||
flowDirectionFeatureKeyQueueRef.current = flowDirectionFeatureKeyQueueRef.current.filter((featureKey) =>
|
||||
flowDirectionFeatureKeySetRef.current.has(featureKey),
|
||||
);
|
||||
}
|
||||
flowDirectionVisibleUsageCodesRef.current = new Set(
|
||||
Array.from(flowDirectionVisibleUsageCodesRef.current).filter((usageCode) =>
|
||||
nextUsageCodes.has(usageCode),
|
||||
),
|
||||
);
|
||||
return addedUsageCodes;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!flowDirectionCheckedRef.current) return;
|
||||
clearFlowDirectionFeatureCache();
|
||||
flowDirectionSelectionRequestUsageCodesRef.current = null;
|
||||
setFlowDirectionSelectionVersion((version) => version + 1);
|
||||
}, [clearFlowDirectionFeatureCache, flowDirectionMode]);
|
||||
|
||||
// 유하방향 레이어는 하나만 유지하고 source에 피처를 누적한다.
|
||||
const ensureFlowDirectionLayer = useCallback((layerInfo: any) => {
|
||||
const map = gisMaps.current;
|
||||
@@ -695,23 +780,11 @@ const Map = () => {
|
||||
source.addFeatures(nextFeatures);
|
||||
}
|
||||
|
||||
while (flowDirectionFeatureKeyQueueRef.current.length > FLOW_DIRECTION_CACHE_MAX_FEATURES) {
|
||||
const staleKey = flowDirectionFeatureKeyQueueRef.current.shift();
|
||||
if (!staleKey) break;
|
||||
|
||||
const staleFeature = source.getFeatures().find((feature: any) =>
|
||||
feature.get("flowDirectionCacheKey") === staleKey,
|
||||
);
|
||||
if (staleFeature) {
|
||||
source.removeFeature(staleFeature);
|
||||
}
|
||||
flowDirectionFeatureKeySetRef.current.delete(staleKey);
|
||||
}
|
||||
}, [ensureFlowDirectionLayer]);
|
||||
|
||||
// 현재 지도 BBOX와 체크된 관종 필터로 유하방향 GeoJSON을 조회하고, 최신 요청만 지도에 반영한다.
|
||||
// 현재 지도 BBOX와 체크된 관종별 필터로 유하방향 GeoJSON을 각각 조회하고, 최신 요청만 지도에 반영한다.
|
||||
const loadFlowDirectionLayer = useCallback(
|
||||
async (layerInfo: any) => {
|
||||
async (layerInfo: any, requestOnlyUsageCodes?: string[]) => {
|
||||
const map = gisMaps.current;
|
||||
if (!map) return;
|
||||
|
||||
@@ -739,45 +812,113 @@ const Map = () => {
|
||||
removeFlowDirectionLayer();
|
||||
return;
|
||||
}
|
||||
retainFlowDirectionUsageCodes(usageCodes);
|
||||
|
||||
const usageCodeSet = new Set(usageCodes);
|
||||
const requestUsageCodes = requestOnlyUsageCodes
|
||||
? requestOnlyUsageCodes.filter((usageCode) => usageCodeSet.has(usageCode))
|
||||
: usageCodes;
|
||||
const isPartialUsageRequest = Array.isArray(requestOnlyUsageCodes);
|
||||
if (requestUsageCodes.length === 0) {
|
||||
if (flowDirectionAbortControllerRef.current === abortController) {
|
||||
flowDirectionAbortControllerRef.current = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const screenExtent = view.calculateExtent(size);
|
||||
const requestExtent = expandFlowDirectionExtent(screenExtent);
|
||||
const screenBbox = formatFlowDirectionBbox(screenExtent);
|
||||
const requestBbox = formatFlowDirectionBbox(requestExtent);
|
||||
const params = new URLSearchParams({
|
||||
bbox: requestBbox,
|
||||
const baseParams = new URLSearchParams({
|
||||
srs: FLOW_DIRECTION_SRS,
|
||||
limit: String(FLOW_DIRECTION_DEFAULT_LIMIT),
|
||||
usgCd: usageCodes.join(","),
|
||||
directionMode: flowDirectionMode,
|
||||
searchStrategy: "BBOX",
|
||||
bbox: requestBbox,
|
||||
});
|
||||
|
||||
try {
|
||||
const payload = await getRawJson(
|
||||
`/sewerage-service/api/v1/map/flowDirectionGeometry?${params.toString()}`,
|
||||
{ signal: abortController.signal },
|
||||
);
|
||||
const settledResponses = await Promise.allSettled(requestUsageCodes.map(async (usageCode) => {
|
||||
const params = new URLSearchParams(baseParams);
|
||||
params.set("usgCd", usageCode);
|
||||
const payload = await getRawJson(
|
||||
`/sewerage-service/api/v1/map/flowDirectionGeometry?${params.toString()}`,
|
||||
{ signal: abortController.signal },
|
||||
);
|
||||
return { usageCode, featureCollection: getFeatureCollection(payload) };
|
||||
}));
|
||||
if (requestSeq !== flowDirectionRequestSeqRef.current) return;
|
||||
if (abortController.signal.aborted) return;
|
||||
|
||||
const featureCollection = getFeatureCollection(payload);
|
||||
renderFlowDirectionLayer(featureCollection, layerInfo, usageStyleMap);
|
||||
const responses = settledResponses
|
||||
.filter((result): result is PromiseFulfilledResult<{ usageCode: string; featureCollection: any }> =>
|
||||
result.status === "fulfilled",
|
||||
)
|
||||
.map((result) => result.value);
|
||||
const failedResponses = settledResponses.filter((result) => result.status === "rejected");
|
||||
|
||||
if (responses.length === 0) {
|
||||
if (!isPartialUsageRequest) {
|
||||
removeFlowDirectionLayer();
|
||||
}
|
||||
throw failedResponses[0]?.reason ?? new Error("유하방향 관종별 조회 결과가 없습니다.");
|
||||
}
|
||||
|
||||
if (failedResponses.length > 0) {
|
||||
console.error("유하방향 일부 관종 조회 실패:", failedResponses.map((result) => result.reason));
|
||||
if (!flowDirectionFailureAlertedRef.current) {
|
||||
flowDirectionFailureAlertedRef.current = true;
|
||||
}
|
||||
}
|
||||
|
||||
responses.forEach(({ usageCode, featureCollection }) => {
|
||||
renderFlowDirectionLayer(featureCollection, layerInfo, usageStyleMap);
|
||||
flowDirectionVisibleUsageCodesRef.current.add(usageCode);
|
||||
});
|
||||
const returnedFeaturesByUsage = responses.reduce<Record<string, number>>(
|
||||
(acc, { usageCode, featureCollection }) => {
|
||||
acc[usageCode] = featureCollection.features?.length ?? 0;
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
const networkCorrectedFeatures = responses.reduce((count, { featureCollection }) => {
|
||||
const correctedCount = featureCollection.features?.filter((feature: any) =>
|
||||
String(
|
||||
feature?.properties?.networkCorrectionYn ??
|
||||
feature?.properties?.network_correction_yn ??
|
||||
"",
|
||||
).toUpperCase() === "Y",
|
||||
).length ?? 0;
|
||||
return count + correctedCount;
|
||||
}, 0);
|
||||
(window as any).__SN_FLOW_DIRECTION_DEBUG__ = {
|
||||
screenBbox,
|
||||
requestBbox,
|
||||
requestSeq,
|
||||
zoom,
|
||||
directionMode: flowDirectionMode,
|
||||
searchStrategy: "BBOX",
|
||||
usageCodes,
|
||||
returnedFeatures: featureCollection.features?.length ?? 0,
|
||||
requestUsageCodes,
|
||||
requestCount: responses.length,
|
||||
returnedFeaturesByUsage,
|
||||
returnedFeatures: Object.values(returnedFeaturesByUsage).reduce((sum, count) => sum + count, 0),
|
||||
networkCorrectedFeatures,
|
||||
cachedFeatures: flowDirectionSourceRef.current?.getFeatures?.().length ?? 0,
|
||||
};
|
||||
flowDirectionFailureAlertedRef.current = false;
|
||||
if (failedResponses.length === 0) {
|
||||
flowDirectionFailureAlertedRef.current = false;
|
||||
}
|
||||
} catch (error) {
|
||||
if (requestSeq !== flowDirectionRequestSeqRef.current) return;
|
||||
if (typeof error === "object" && error !== null && "name" in error && error.name === "AbortError") return;
|
||||
|
||||
console.error("유하방향 레이어 조회 실패:", error);
|
||||
removeFlowDirectionLayer();
|
||||
if (!isPartialUsageRequest) {
|
||||
removeFlowDirectionLayer();
|
||||
}
|
||||
if (!flowDirectionFailureAlertedRef.current) {
|
||||
alert("유하방향 정보를 불러오지 못했습니다.");
|
||||
flowDirectionFailureAlertedRef.current = true;
|
||||
}
|
||||
} finally {
|
||||
@@ -786,7 +927,7 @@ const Map = () => {
|
||||
}
|
||||
}
|
||||
},
|
||||
[removeFlowDirectionLayer, renderFlowDirectionLayer],
|
||||
[flowDirectionMode, removeFlowDirectionLayer, renderFlowDirectionLayer, retainFlowDirectionUsageCodes],
|
||||
);
|
||||
|
||||
// SearchModal, GisContent 등에서 호출
|
||||
@@ -1057,12 +1198,22 @@ const Map = () => {
|
||||
setIsFlowDirectionChecked(Boolean(nextFlowDirectionLayerInfo));
|
||||
setFlowDirectionLayerInfo(nextFlowDirectionLayerInfo);
|
||||
if (nextFlowDirectionLayerInfo) {
|
||||
clearFlowDirectionFeatureCache();
|
||||
const map = gisMaps.current;
|
||||
const zoom = map?.getView?.()?.getZoom?.() ?? 0;
|
||||
const nextUsageStyleMap = extractCheckedPipeUsageStyles(checkedNodesData, zoom);
|
||||
const requestUsageCodes = retainFlowDirectionUsageCodes(Object.keys(nextUsageStyleMap));
|
||||
flowDirectionSelectionRequestUsageCodesRef.current = requestUsageCodes;
|
||||
if (requestUsageCodes.length === 0) {
|
||||
flowDirectionRequestSeqRef.current += 1;
|
||||
flowDirectionAbortControllerRef.current?.abort();
|
||||
flowDirectionAbortControllerRef.current = null;
|
||||
}
|
||||
setFlowDirectionSelectionVersion((version) => version + 1);
|
||||
}
|
||||
|
||||
if (!nextFlowDirectionLayerInfo) {
|
||||
// 해제 직후 pending API 응답이 늦게 도착해도 renderFlowDirectionLayer가 실행되지 않도록 순번을 무효화한다.
|
||||
flowDirectionSelectionRequestUsageCodesRef.current = null;
|
||||
flowDirectionRequestSeqRef.current += 1;
|
||||
flowDirectionAbortControllerRef.current?.abort();
|
||||
flowDirectionAbortControllerRef.current = null;
|
||||
@@ -1072,7 +1223,7 @@ const Map = () => {
|
||||
|
||||
handleWaterDepthVisibility(checkedIds, checkedNodesData);
|
||||
setIsAgedPipeChecked(checkedNodesData.some((node) => isAgedPipeLayerNode(node)));
|
||||
}, [clearFlowDirectionFeatureCache, handleWaterDepthVisibility, removeFlowDirectionLayer]);
|
||||
}, [handleWaterDepthVisibility, removeFlowDirectionLayer, retainFlowDirectionUsageCodes]);
|
||||
|
||||
const handleOpenLayerPopup = useCallback(
|
||||
(_layerNm: string, node?: any) => {
|
||||
@@ -1149,7 +1300,10 @@ const Map = () => {
|
||||
|
||||
// 트리 체크 상태나 관종 레이어 선택이 바뀌면 유하방향 API vector layer를 추가/갱신/제거한다.
|
||||
if (isFlowDirectionChecked) {
|
||||
loadFlowDirectionLayer(flowDirectionLayerInfo);
|
||||
const requestUsageCodes = flowDirectionSelectionRequestUsageCodesRef.current;
|
||||
flowDirectionSelectionRequestUsageCodesRef.current = null;
|
||||
if (requestUsageCodes && requestUsageCodes.length === 0) return;
|
||||
loadFlowDirectionLayer(flowDirectionLayerInfo, requestUsageCodes ?? undefined);
|
||||
} else {
|
||||
removeFlowDirectionLayer();
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../map/page";
|
||||
Reference in New Issue
Block a user