291 lines
9.1 KiB
TypeScript
291 lines
9.1 KiB
TypeScript
"use client";
|
|
import type * as React from "react";
|
|
import { useCallback, useEffect, useRef } from "react";
|
|
import GeoJSON from "ol/format/GeoJSON.js";
|
|
import { unByKey } from "ol/Observable.js";
|
|
import VectorSource from "ol/source/Vector.js";
|
|
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
|
import { getRawJson } from "@/shared/api/sewerage-api";
|
|
import { useGisEdit } from "../components/gis-edit/use-gis-edit";
|
|
import { FLOW_DIRECTION_SRS } from "../model/map-constants";
|
|
import { getFeatureCollection } from "./map-values";
|
|
import {
|
|
expandGisEditPipeSnapExtent,
|
|
normalizeGisEditFid,
|
|
getGisEditFeatureFid,
|
|
} from "./gis-edit-features";
|
|
|
|
/** 편집 스냅용 관로의 영역 조회와 취소 수명을 소유한다. */
|
|
type Options = {
|
|
gisEdit: ReturnType<typeof useGisEdit>;
|
|
gisMaps: React.RefObject<any>;
|
|
mapReady: boolean;
|
|
};
|
|
|
|
/** 편집 스냅용 관로의 영역 조회와 취소 수명을 소유한다. */
|
|
export function useGisEditPipeSnap({ gisEdit, gisMaps, mapReady }: Options) {
|
|
// 편집 세션 전체 객체 대신 실제 사용하는 상태와 명령만 의존성으로 추적한다.
|
|
const {
|
|
changes: editChanges,
|
|
isEditMode: editIsEditMode,
|
|
isSnapEnabled: editIsSnapEnabled,
|
|
selectedLayer: editSelectedLayer,
|
|
pendingInteraction: editPendingInteraction,
|
|
} = gisEdit;
|
|
|
|
const gisEditPipeSnapSourceRef = useRef<VectorSource | null>(null);
|
|
|
|
const gisEditPipeSnapAbortControllerRef = useRef<AbortController | null>(
|
|
null,
|
|
);
|
|
|
|
const gisEditPipeSnapRequestSeqRef = useRef(0);
|
|
|
|
const ensureGisEditPipeSnapSource = useCallback(() => {
|
|
if (gisEditPipeSnapSourceRef.current) {
|
|
return gisEditPipeSnapSourceRef.current;
|
|
}
|
|
const source = new VectorSource();
|
|
gisEditPipeSnapSourceRef.current = source;
|
|
return source;
|
|
}, []);
|
|
|
|
const clearGisEditPipeSnapSource = useCallback(() => {
|
|
gisEditPipeSnapAbortControllerRef.current?.abort();
|
|
gisEditPipeSnapAbortControllerRef.current = null;
|
|
gisEditPipeSnapSourceRef.current?.clear();
|
|
}, []);
|
|
|
|
const buildGisEditPipeSnapPreviewFeatures = useCallback(
|
|
(excludedChangeId?: string | null) => {
|
|
const normalizedExcludedChangeId = String(excludedChangeId ?? "").trim();
|
|
const geoJson = new GeoJSON();
|
|
|
|
return editChanges
|
|
.filter((change) => change.layerId === "tb_ms_swage_ppl")
|
|
.filter(
|
|
(change) =>
|
|
!change.attributeOnly &&
|
|
change.editType !== "DELETE" &&
|
|
Boolean(change.afterGeometry),
|
|
)
|
|
.filter((change) => change.changeId !== normalizedExcludedChangeId)
|
|
.map((change) => {
|
|
const feature = geoJson.readFeature(
|
|
{
|
|
type: "Feature",
|
|
geometry: change.afterGeometry,
|
|
properties: {
|
|
fid:
|
|
change.fid ??
|
|
change.featureRef?.fid ??
|
|
change.objectId ??
|
|
change.changeId,
|
|
layerId: change.layerId,
|
|
changeId: change.changeId,
|
|
},
|
|
},
|
|
{
|
|
dataProjection: FLOW_DIRECTION_SRS,
|
|
featureProjection: GisApp.TO_PROJ,
|
|
},
|
|
);
|
|
feature.setId(`gis-edit-snap:${change.changeId}`);
|
|
return feature;
|
|
});
|
|
},
|
|
[editChanges],
|
|
);
|
|
|
|
const loadGisEditPipeSnapSource = useCallback(
|
|
async (options?: {
|
|
excludedFid?: string | null;
|
|
excludedChangeId?: string | null;
|
|
}) => {
|
|
const map = gisMaps.current;
|
|
const source = ensureGisEditPipeSnapSource();
|
|
if (!map || !source) return;
|
|
|
|
const size = map.getSize?.();
|
|
const view = map.getView?.();
|
|
if (!size || !view?.calculateExtent) return;
|
|
|
|
const normalizedExcludedFid = normalizeGisEditFid(
|
|
options?.excludedFid ?? "",
|
|
);
|
|
const normalizedExcludedChangeId = String(
|
|
options?.excludedChangeId ?? "",
|
|
).trim();
|
|
const hiddenPipeFidSet = new Set(
|
|
editChanges
|
|
.filter((change) => change.layerId === "tb_ms_swage_ppl")
|
|
.filter(
|
|
(change) => !change.attributeOnly && change.editType !== "DELETE",
|
|
)
|
|
.map((change) =>
|
|
normalizeGisEditFid(
|
|
change.fid ?? change.featureRef?.fid ?? change.objectId,
|
|
),
|
|
)
|
|
.filter((fid) => /^\d+$/.test(fid)),
|
|
);
|
|
if (/^\d+$/.test(normalizedExcludedFid)) {
|
|
hiddenPipeFidSet.add(normalizedExcludedFid);
|
|
}
|
|
|
|
const previewSnapFeatures = buildGisEditPipeSnapPreviewFeatures(
|
|
normalizedExcludedChangeId,
|
|
);
|
|
source.clear();
|
|
if (previewSnapFeatures.length > 0) {
|
|
source.addFeatures(previewSnapFeatures);
|
|
}
|
|
|
|
const screenExtent = view.calculateExtent(size);
|
|
const requestExtent = expandGisEditPipeSnapExtent(screenExtent);
|
|
const [minX, minY, maxX, maxY] = requestExtent;
|
|
const bboxFilter = `BBOX(geom, ${minX}, ${minY}, ${maxX}, ${maxY}, '${GisApp.TO_PROJ}')`;
|
|
const cqlFilter = bboxFilter;
|
|
|
|
const requestSeq = gisEditPipeSnapRequestSeqRef.current + 1;
|
|
gisEditPipeSnapRequestSeqRef.current = requestSeq;
|
|
gisEditPipeSnapAbortControllerRef.current?.abort();
|
|
const abortController = new AbortController();
|
|
gisEditPipeSnapAbortControllerRef.current = abortController;
|
|
|
|
try {
|
|
const layerModule = GisApp.Module.layer as any;
|
|
const storageName = String(layerModule?.storageNm ?? "hasudo:");
|
|
const params = new URLSearchParams({
|
|
SERVICE: "WFS",
|
|
VERSION: "1.1.0",
|
|
REQUEST: "GetFeature",
|
|
TYPENAME: `${storageName}tb_ms_swage_ppl`,
|
|
SRSNAME: GisApp.TO_PROJ,
|
|
OUTPUTFORMAT: "application/json",
|
|
CQL_FILTER: cqlFilter,
|
|
});
|
|
const response = await getRawJson(
|
|
`${GisApp.GEOSERVER_URL}?${params.toString()}`,
|
|
{
|
|
signal: abortController.signal,
|
|
},
|
|
);
|
|
if (requestSeq !== gisEditPipeSnapRequestSeqRef.current) return;
|
|
if (abortController.signal.aborted) return;
|
|
|
|
const featureCollection = getFeatureCollection(response);
|
|
const features = new GeoJSON()
|
|
.readFeatures(featureCollection, {
|
|
dataProjection: GisApp.TO_PROJ,
|
|
featureProjection: GisApp.TO_PROJ,
|
|
})
|
|
.filter(
|
|
(feature: any) =>
|
|
!hiddenPipeFidSet.has(getGisEditFeatureFid(feature)),
|
|
);
|
|
|
|
source.clear();
|
|
source.addFeatures(features);
|
|
if (previewSnapFeatures.length > 0) {
|
|
source.addFeatures(previewSnapFeatures);
|
|
}
|
|
} catch (error) {
|
|
if ((error as Error)?.name === "AbortError") return;
|
|
console.error("GIS 편집 스냅용 관로 조회 실패:", error);
|
|
source.clear();
|
|
if (previewSnapFeatures.length > 0) {
|
|
source.addFeatures(previewSnapFeatures);
|
|
}
|
|
} finally {
|
|
if (gisEditPipeSnapAbortControllerRef.current === abortController) {
|
|
gisEditPipeSnapAbortControllerRef.current = null;
|
|
}
|
|
}
|
|
},
|
|
[
|
|
gisMaps,
|
|
ensureGisEditPipeSnapSource,
|
|
editChanges,
|
|
buildGisEditPipeSnapPreviewFeatures,
|
|
],
|
|
);
|
|
|
|
const shouldUsePipeSnapSource =
|
|
editIsEditMode &&
|
|
editIsSnapEnabled &&
|
|
editSelectedLayer?.layerId === "tb_ms_swage_ppl";
|
|
|
|
useEffect(
|
|
() => () => {
|
|
clearGisEditPipeSnapSource();
|
|
},
|
|
[clearGisEditPipeSnapSource],
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (!mapReady || !gisMaps.current) return;
|
|
if (!shouldUsePipeSnapSource) {
|
|
clearGisEditPipeSnapSource();
|
|
return;
|
|
}
|
|
|
|
const excludedFid =
|
|
editPendingInteraction?.editType === "VERTEX_EDIT" ||
|
|
editPendingInteraction?.editType === "MOVE"
|
|
? normalizeGisEditFid(
|
|
editChanges.find(
|
|
(change) => change.changeId === editPendingInteraction?.changeId,
|
|
)?.fid ?? "",
|
|
)
|
|
: "";
|
|
|
|
void loadGisEditPipeSnapSource({
|
|
excludedFid,
|
|
excludedChangeId: editPendingInteraction?.changeId ?? null,
|
|
});
|
|
}, [
|
|
clearGisEditPipeSnapSource,
|
|
editChanges,
|
|
editPendingInteraction,
|
|
gisMaps,
|
|
loadGisEditPipeSnapSource,
|
|
mapReady,
|
|
shouldUsePipeSnapSource,
|
|
]);
|
|
|
|
useEffect(() => {
|
|
if (!mapReady || !gisMaps.current) return;
|
|
if (!shouldUsePipeSnapSource) return;
|
|
|
|
const key = gisMaps.current.on("moveend", () => {
|
|
const excludedFid =
|
|
editPendingInteraction?.editType === "VERTEX_EDIT" ||
|
|
editPendingInteraction?.editType === "MOVE"
|
|
? normalizeGisEditFid(
|
|
editChanges.find(
|
|
(change) =>
|
|
change.changeId === editPendingInteraction?.changeId,
|
|
)?.fid ?? "",
|
|
)
|
|
: "";
|
|
void loadGisEditPipeSnapSource({
|
|
excludedFid,
|
|
excludedChangeId: editPendingInteraction?.changeId ?? null,
|
|
});
|
|
});
|
|
|
|
return () => {
|
|
unByKey(key);
|
|
};
|
|
}, [
|
|
editChanges,
|
|
editPendingInteraction,
|
|
gisMaps,
|
|
loadGisEditPipeSnapSource,
|
|
mapReady,
|
|
shouldUsePipeSnapSource,
|
|
]);
|
|
return { ensureGisEditPipeSnapSource, shouldUsePipeSnapSource };
|
|
}
|