313 lines
9.6 KiB
TypeScript
313 lines
9.6 KiB
TypeScript
/** 지반침하 선택 데이터와 도형·표시 스타일 변환. 기존 값과 처리 순서를 유지한다. */
|
|
import Fill from "ol/style/Fill.js";
|
|
import CircleStyle from "ol/style/Circle.js";
|
|
import Stroke from "ol/style/Stroke.js";
|
|
import Style from "ol/style/Style.js";
|
|
import Text from "ol/style/Text.js";
|
|
import {
|
|
SINKHOLE_CONTROL_NODE_ID,
|
|
SINKHOLE_LAYER_NAME,
|
|
SINKHOLE_LAYER_TYPE,
|
|
type SinkholeDetail,
|
|
type SinkholeListItem,
|
|
} from "@/app/(map-layout)/map/components/sinkhole/sinkhole-types";
|
|
import {
|
|
SINKHOLE_POINT_PALETTE,
|
|
SINKHOLE_TREE_ICON_NAME,
|
|
} from "../model/map-constants";
|
|
|
|
// 지반침하 목록 API가 정상일 때는 x/y를 바로 Point feature로 바꿔 geometry 전용 API 장애를 우회할 수 있다.
|
|
export const buildSinkholeFeatureCollectionFromItems = (
|
|
items: SinkholeListItem[],
|
|
) => ({
|
|
type: "FeatureCollection",
|
|
features: items
|
|
.map((item) => {
|
|
const x = Number(item?.x);
|
|
const y = Number(item?.y);
|
|
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
type: "Feature",
|
|
id: `${SINKHOLE_LAYER_TYPE}.${String(item?.acdntNo ?? "").trim()}`,
|
|
geometry: {
|
|
type: "Point",
|
|
coordinates: [x, y],
|
|
},
|
|
properties: {
|
|
...item,
|
|
lyrTypeNm: SINKHOLE_LAYER_TYPE,
|
|
x,
|
|
y,
|
|
},
|
|
};
|
|
})
|
|
.filter(Boolean),
|
|
});
|
|
|
|
export type SinkholePointVariant = keyof typeof SINKHOLE_POINT_PALETTE;
|
|
|
|
export const resolveSinkholePointVariant = (
|
|
_ocrnYmd?: string | null,
|
|
): SinkholePointVariant => "blue";
|
|
|
|
export const getSinkholeSelectionValue = (data: any, key: string) =>
|
|
data?.[key] ??
|
|
data?.values_?.[key] ??
|
|
data?.get?.(key) ??
|
|
data?.properties?.[key];
|
|
|
|
export const getSinkholeAccidentId = (data: any) =>
|
|
String(getSinkholeSelectionValue(data, "acdntNo") ?? "").trim();
|
|
|
|
export const getSinkholeFallbackDetailFromSelection = (
|
|
data: any,
|
|
): Partial<SinkholeDetail> | null => {
|
|
const acdntNo = getSinkholeAccidentId(data);
|
|
if (!acdntNo) return null;
|
|
|
|
const point = getSinkholePointFromSelection(data);
|
|
return {
|
|
acdntNo,
|
|
ctpvNm: String(getSinkholeSelectionValue(data, "ctpvNm") ?? ""),
|
|
sggNm: String(getSinkholeSelectionValue(data, "sggNm") ?? ""),
|
|
emdNm: String(getSinkholeSelectionValue(data, "emdNm") ?? ""),
|
|
daddr: String(getSinkholeSelectionValue(data, "daddr") ?? ""),
|
|
fullAddress: String(
|
|
getSinkholeSelectionValue(data, "fullAddress") ??
|
|
getSinkholeSelectionValue(data, "daddr") ??
|
|
"",
|
|
),
|
|
ocrnYmd: String(getSinkholeSelectionValue(data, "ocrnYmd") ?? ""),
|
|
restoreStatus: String(
|
|
getSinkholeSelectionValue(data, "restoreStatus") ?? "",
|
|
),
|
|
restoreCompletedYmd: String(
|
|
getSinkholeSelectionValue(data, "restoreCompletedYmd") ?? "",
|
|
),
|
|
coordStatus: String(
|
|
getSinkholeSelectionValue(data, "coordStatus") ??
|
|
getSinkholeSelectionValue(data, "geocodeStatus") ??
|
|
"",
|
|
),
|
|
geocodeStatus: String(
|
|
getSinkholeSelectionValue(data, "geocodeStatus") ??
|
|
getSinkholeSelectionValue(data, "coordStatus") ??
|
|
"",
|
|
),
|
|
geocodeSource: String(
|
|
getSinkholeSelectionValue(data, "geocodeSource") ?? "",
|
|
),
|
|
geocodeErrorMessage: String(
|
|
getSinkholeSelectionValue(data, "geocodeErrorMessage") ?? "",
|
|
),
|
|
latitude: getSinkholeSelectionValue(data, "latitude") ?? null,
|
|
longitude: getSinkholeSelectionValue(data, "longitude") ?? null,
|
|
x: point?.x ?? getSinkholeSelectionValue(data, "x") ?? null,
|
|
y: point?.y ?? getSinkholeSelectionValue(data, "y") ?? null,
|
|
sizeWidth: getSinkholeSelectionValue(data, "sizeWidth") ?? null,
|
|
sizeLength: getSinkholeSelectionValue(data, "sizeLength") ?? null,
|
|
sizeDepth: getSinkholeSelectionValue(data, "sizeDepth") ?? null,
|
|
detailCause: getSinkholeSelectionValue(data, "detailCause") ?? null,
|
|
deathCount: getSinkholeSelectionValue(data, "deathCount") ?? null,
|
|
injuryCount: getSinkholeSelectionValue(data, "injuryCount") ?? null,
|
|
vehicleDamageCount:
|
|
getSinkholeSelectionValue(data, "vehicleDamageCount") ?? null,
|
|
};
|
|
};
|
|
|
|
export const getSinkholePointFromSelection = (
|
|
data: any,
|
|
): { x: number; y: number } | null => {
|
|
const directX = Number(getSinkholeSelectionValue(data, "x"));
|
|
const directY = Number(getSinkholeSelectionValue(data, "y"));
|
|
if (Number.isFinite(directX) && Number.isFinite(directY)) {
|
|
return { x: directX, y: directY };
|
|
}
|
|
|
|
const geometry = data?.getGeometry?.();
|
|
if (geometry?.getType?.() === "Point") {
|
|
const coordinates = geometry.getCoordinates?.();
|
|
const x = Number(coordinates?.[0]);
|
|
const y = Number(coordinates?.[1]);
|
|
if (Number.isFinite(x) && Number.isFinite(y)) {
|
|
return { x, y };
|
|
}
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
export const createSinkholePointStyle = (
|
|
variant: SinkholePointVariant = "blue",
|
|
selected = false,
|
|
) =>
|
|
new Style({
|
|
image: new CircleStyle({
|
|
radius: selected ? 9 : 7,
|
|
fill: new Fill({
|
|
color: selected
|
|
? SINKHOLE_POINT_PALETTE[variant].fillSelected
|
|
: SINKHOLE_POINT_PALETTE[variant].fill,
|
|
}),
|
|
stroke: new Stroke({
|
|
color: selected
|
|
? SINKHOLE_POINT_PALETTE[variant].strokeSelected
|
|
: SINKHOLE_POINT_PALETTE[variant].stroke,
|
|
width: selected ? 2.6 : 2,
|
|
}),
|
|
}),
|
|
zIndex: selected ? 9997 : 9994,
|
|
});
|
|
|
|
export const createSinkholeRadiusStyle = (
|
|
variant: SinkholePointVariant = "blue",
|
|
) =>
|
|
new Style({
|
|
stroke: new Stroke({
|
|
color: SINKHOLE_POINT_PALETTE[variant].radiusStroke,
|
|
width: 2.2,
|
|
lineDash: [8, 6],
|
|
}),
|
|
fill: new Fill({
|
|
color: SINKHOLE_POINT_PALETTE[variant].radiusFill,
|
|
}),
|
|
zIndex: 9993,
|
|
});
|
|
|
|
// 반경 원 상단에 실제 적용 거리값을 표시하는 선택 전용 지도 라벨 스타일이다.
|
|
export const createSinkholeRadiusLabelStyle = (
|
|
radiusMeters: number,
|
|
variant: SinkholePointVariant = "blue",
|
|
) =>
|
|
new Style({
|
|
text: new Text({
|
|
text: `반경 ${radiusMeters.toLocaleString("ko-KR")}m`,
|
|
font: "600 13px Pretendard, sans-serif",
|
|
textAlign: "center",
|
|
offsetY: -14,
|
|
padding: [4, 7, 4, 7],
|
|
fill: new Fill({ color: SINKHOLE_POINT_PALETTE[variant].strokeSelected }),
|
|
stroke: new Stroke({ color: "rgba(255, 255, 255, 0.98)", width: 3 }),
|
|
backgroundFill: new Fill({ color: "rgba(255, 255, 255, 0.9)" }),
|
|
backgroundStroke: new Stroke({
|
|
color: SINKHOLE_POINT_PALETTE[variant].radiusStroke,
|
|
width: 1.5,
|
|
}),
|
|
}),
|
|
zIndex: 9998,
|
|
});
|
|
|
|
export const createSinkholeFocusedPipeStyle = () => [
|
|
new Style({
|
|
stroke: new Stroke({
|
|
color: "rgba(88, 28, 135, 0.22)",
|
|
width: 18,
|
|
lineCap: "round",
|
|
lineJoin: "round",
|
|
}),
|
|
zIndex: 9997,
|
|
}),
|
|
new Style({
|
|
stroke: new Stroke({
|
|
color: "rgba(255, 255, 255, 0.96)",
|
|
width: 12,
|
|
lineCap: "round",
|
|
lineJoin: "round",
|
|
}),
|
|
zIndex: 9998,
|
|
}),
|
|
new Style({
|
|
stroke: new Stroke({
|
|
color: "rgba(37, 99, 235, 0.98)",
|
|
width: 8,
|
|
lineCap: "round",
|
|
lineJoin: "round",
|
|
}),
|
|
zIndex: 9999,
|
|
}),
|
|
];
|
|
|
|
export const appendSinkholeControlNode = (data: any[]) => {
|
|
if (!Array.isArray(data)) return data;
|
|
const existingRoot = data.find((item) => {
|
|
const nodeId = String(item?.lyrSn ?? item?.id ?? "");
|
|
const layerName = String(item?.lyrNm ?? item?.name ?? "").trim();
|
|
const layerTypeName = String(
|
|
item?.lyrTyNm ?? item?.lyrTypeNm ?? item?.lyr_type_nm ?? "",
|
|
).trim();
|
|
return (
|
|
nodeId === SINKHOLE_CONTROL_NODE_ID ||
|
|
layerName === SINKHOLE_LAYER_NAME ||
|
|
layerTypeName === SINKHOLE_LAYER_TYPE
|
|
);
|
|
});
|
|
|
|
const existingRootId =
|
|
existingRoot?.lyrSn ?? existingRoot?.id ?? SINKHOLE_CONTROL_NODE_ID;
|
|
const filtered = data.filter((item) => {
|
|
const nodeId = String(item?.lyrSn ?? item?.id ?? "");
|
|
const upperId = String(item?.upperLyrSn ?? item?.upId ?? "");
|
|
const layerName = String(item?.lyrNm ?? item?.name ?? "").trim();
|
|
const layerTypeName = String(
|
|
item?.lyrTyNm ?? item?.lyrTypeNm ?? item?.lyr_type_nm ?? "",
|
|
).trim();
|
|
|
|
if (nodeId === String(existingRootId)) return false;
|
|
if (upperId === String(existingRootId)) return false;
|
|
if (!existingRoot && nodeId === SINKHOLE_CONTROL_NODE_ID) return false;
|
|
if (!existingRoot && upperId === SINKHOLE_CONTROL_NODE_ID) return false;
|
|
if (!existingRoot && layerName === SINKHOLE_LAYER_NAME) return false;
|
|
if (!existingRoot && layerTypeName === SINKHOLE_LAYER_TYPE) return false;
|
|
return true;
|
|
});
|
|
|
|
const rootNode = existingRoot
|
|
? {
|
|
...existingRoot,
|
|
lyrSn: existingRootId,
|
|
id: existingRootId,
|
|
gobjTy: "",
|
|
iconNm: existingRoot.iconNm || SINKHOLE_TREE_ICON_NAME,
|
|
clickPsblYn: "N",
|
|
actlLyrYn: "N",
|
|
children: null,
|
|
}
|
|
: {
|
|
lyrSn: SINKHOLE_CONTROL_NODE_ID,
|
|
id: SINKHOLE_CONTROL_NODE_ID,
|
|
lyrNm: SINKHOLE_LAYER_NAME,
|
|
upperLyrSn: null,
|
|
upId: null,
|
|
sortOrdr: "3",
|
|
drawOrdr: null,
|
|
flterSql: null,
|
|
gobjTy: "",
|
|
lyrTyNm: SINKHOLE_LAYER_TYPE,
|
|
useYn: "Y",
|
|
minViewSize: null,
|
|
iconNm: SINKHOLE_TREE_ICON_NAME,
|
|
iconSize: null,
|
|
lblAtrbNm: null,
|
|
fontNm: null,
|
|
fontIntnColor: null,
|
|
fontExtnColor: null,
|
|
fontExtnWdth: null,
|
|
lnWdth: null,
|
|
lnColor: null,
|
|
polgnColor: null,
|
|
initEprssYn: "Y",
|
|
clickPsblYn: "N",
|
|
exprsYn: "Y",
|
|
cmncNm: "all",
|
|
srvcNm: "api",
|
|
actlLyrYn: "N",
|
|
clckZoom: null,
|
|
children: null,
|
|
};
|
|
|
|
return [...filtered, rootNode];
|
|
};
|