Files

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];
};