위성지도 기능 개선 및 침수 레이어 범례 보완
This commit is contained in:
@@ -121,7 +121,7 @@ import {
|
||||
} from "./components/floodHistory/floodHistoryTypes";
|
||||
import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup";
|
||||
import SearchModal from "./searchModal/searchModal";
|
||||
import { WATER_DEPTH_LAYER_TYPES, WATER_DEPTH_LEGEND_ITEMS } from "./waterDepth/waterDepth";
|
||||
import { WATER_DEPTH_LAYER_TYPES, extractWaterDepthLegendItems } from "./waterDepth/waterDepth";
|
||||
import {
|
||||
buildWaterRiskStatusLabel,
|
||||
EMPTY_WATER_RISK_SESSION_SNAPSHOT,
|
||||
@@ -2289,6 +2289,7 @@ const Map = () => {
|
||||
const [isWaterDepthLayerActive, setIsWaterDepthLayerActive] = useState(false); // 침수심 레이어 활성 여부
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [mapReady, setMapReady] = useState(false);
|
||||
const [checkedLayerLegendVersion, setCheckedLayerLegendVersion] = useState(0);
|
||||
const [isAgedPipeChecked, setIsAgedPipeChecked] = useState(false);
|
||||
const [isSinkholeChecked, setIsSinkholeChecked] = useState(false);
|
||||
const [isFloodHistoryChecked, setIsFloodHistoryChecked] = useState(false);
|
||||
@@ -4524,6 +4525,7 @@ const Map = () => {
|
||||
// 체크된 레이어 변경 처리 함수
|
||||
const handleCheckedLayersChange = useCallback((checkedIds: string[], checkedNodesData: any[]) => {
|
||||
checkedNodesDataRef.current = checkedNodesData;
|
||||
setCheckedLayerLegendVersion((version) => version + 1);
|
||||
|
||||
// 유하방향은 일반 setLayerOnMap 대상이 아니므로 체크 노드 여부를 별도로 추적한다.
|
||||
const flowDirectionNode = checkedNodesData.find((node) =>
|
||||
@@ -4576,6 +4578,11 @@ const Map = () => {
|
||||
}
|
||||
}, [clearFloodHistorySelection, closeSinkholeDetail, handleWaterDepthVisibility, removeFlowDirectionLayer, removeSinkholeLayer, retainFlowDirectionUsageCodes]);
|
||||
|
||||
const waterDepthLegendItems = useMemo(
|
||||
() => extractWaterDepthLegendItems(checkedNodesDataRef.current ?? []),
|
||||
[checkedLayerLegendVersion],
|
||||
);
|
||||
|
||||
const handleOpenLayerPopup = useCallback(
|
||||
(_layerNm: string, node?: any) => {
|
||||
if (isAgedPipeLayerNode(node)) {
|
||||
@@ -4657,11 +4664,14 @@ const Map = () => {
|
||||
침수심 범례
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{WATER_DEPTH_LEGEND_ITEMS.map((item, index) => (
|
||||
<div key={`${item.label}-${index}`} className="flex items-center gap-2">
|
||||
{waterDepthLegendItems.map((item) => (
|
||||
<div key={item.key} className="flex items-center gap-2">
|
||||
<span
|
||||
className="inline-block h-4 w-10 shrink-0 border border-slate-300"
|
||||
style={{ backgroundColor: item.color }}
|
||||
className="inline-block h-4 w-10 shrink-0"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
border: `1px solid ${item.borderColor}`,
|
||||
}}
|
||||
/>
|
||||
<span className="text-[1rem] font-medium text-slate-700">{item.label}</span>
|
||||
</div>
|
||||
@@ -4687,6 +4697,7 @@ const Map = () => {
|
||||
lastSinkholeSyncSummary,
|
||||
sinkholeRadiusMeters,
|
||||
userInfo?.adminRoleYn,
|
||||
waterDepthLegendItems,
|
||||
]);
|
||||
|
||||
// 선택된 설정이 있을 때만 하단 전용 섹션을 노출하고, 세로 공간이 부족하면 내부 스크롤로 처리한다.
|
||||
|
||||
@@ -20,13 +20,101 @@ export const WATER_DEPTH_LAYER_TYPES: string[] = [
|
||||
'tb_ms_sbmrg_dngr_map' // 침수위험지도
|
||||
];
|
||||
|
||||
export const WATER_DEPTH_LEGEND_ITEMS = [
|
||||
{ color: "rgb(255, 255, 127)", label: "0.0~0.5m 미만" },
|
||||
{ color: "rgb(191, 255, 0)", label: "0.5~1.0m 미만" },
|
||||
{ color: "rgb(0, 255, 255)", label: "1.0~2.0m 미만" },
|
||||
{ color: "rgb(191, 127, 255)", label: "2.0~5.0m 미만" },
|
||||
{ color: "rgb(255, 0, 127)", label: "5.0m 이상" },
|
||||
] as const;
|
||||
export type WaterDepthLegendItem = {
|
||||
key: string;
|
||||
label: string;
|
||||
color: string;
|
||||
borderColor: string;
|
||||
sortSeq: number;
|
||||
};
|
||||
|
||||
const getLayerTypeName = (layerInfo: any) =>
|
||||
String(layerInfo?.lyrTyNm ?? layerInfo?.lyrTypeNm ?? layerInfo?.lyr_type_nm ?? "").trim();
|
||||
|
||||
const getFiniteSortOrder = (value: unknown) => {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : Number.MAX_SAFE_INTEGER;
|
||||
};
|
||||
|
||||
const getLegendNumericOrder = (label: string) => {
|
||||
const normalized = String(label).trim();
|
||||
const rangeMatch = normalized.match(/^(\d+(?:\.\d+)?)~(\d+(?:\.\d+)?)m/);
|
||||
if (rangeMatch) {
|
||||
return {
|
||||
start: Number(rangeMatch[1]),
|
||||
end: Number(rangeMatch[2]),
|
||||
priority: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const overMatch = normalized.match(/^(\d+(?:\.\d+)?)m\s*이상/);
|
||||
if (overMatch) {
|
||||
const value = Number(overMatch[1]);
|
||||
return {
|
||||
start: value,
|
||||
end: value,
|
||||
priority: 1,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
start: Number.MAX_SAFE_INTEGER,
|
||||
end: Number.MAX_SAFE_INTEGER,
|
||||
priority: 2,
|
||||
};
|
||||
};
|
||||
|
||||
const getLegendColor = (layerInfo: any) =>
|
||||
String(layerInfo?.polgnColor ?? layerInfo?.spacColor ?? layerInfo?.lnColor ?? layerInfo?.lineColor ?? "").trim();
|
||||
|
||||
const getLegendBorderColor = (layerInfo: any) =>
|
||||
String(layerInfo?.lnColor ?? layerInfo?.lineColor ?? layerInfo?.polgnColor ?? layerInfo?.spacColor ?? "").trim();
|
||||
|
||||
const hasLegendStyle = (layerInfo: any) => getLegendColor(layerInfo) !== "" || getLegendBorderColor(layerInfo) !== "";
|
||||
|
||||
const normalizeLegendLabel = (label: string) => {
|
||||
const normalized = String(label).trim();
|
||||
return normalized.endsWith("미만") ? normalized : `${normalized} 미만`;
|
||||
};
|
||||
|
||||
export const extractWaterDepthLegendItems = (checkedNodesData: any[]): WaterDepthLegendItem[] => {
|
||||
if (!Array.isArray(checkedNodesData) || checkedNodesData.length === 0) return [];
|
||||
|
||||
const items = new Map<string, WaterDepthLegendItem>();
|
||||
|
||||
checkedNodesData.forEach((node) => {
|
||||
const layerInfo = node?.data ?? {};
|
||||
const layerTypeName = getLayerTypeName(layerInfo);
|
||||
if (!WATER_DEPTH_LAYER_TYPES.includes(layerTypeName)) return;
|
||||
if (!hasLegendStyle(layerInfo)) return;
|
||||
|
||||
const itemKey = String(node?.text ?? layerInfo?.lyrNm ?? "").trim();
|
||||
const itemColor = getLegendColor(layerInfo);
|
||||
const itemBorderColor = getLegendBorderColor(layerInfo);
|
||||
|
||||
if (!itemKey || !itemColor) return;
|
||||
if (!items.has(itemKey)) {
|
||||
items.set(itemKey, {
|
||||
key: itemKey,
|
||||
label: normalizeLegendLabel(itemKey),
|
||||
color: itemColor,
|
||||
borderColor: itemBorderColor || itemColor,
|
||||
sortSeq: getFiniteSortOrder(layerInfo?.sortSeq),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(items.values()).sort((left, right) => {
|
||||
const leftOrder = getLegendNumericOrder(left.key);
|
||||
const rightOrder = getLegendNumericOrder(right.key);
|
||||
|
||||
if (leftOrder.start !== rightOrder.start) return leftOrder.start - rightOrder.start;
|
||||
if (leftOrder.priority !== rightOrder.priority) return leftOrder.priority - rightOrder.priority;
|
||||
if (leftOrder.end !== rightOrder.end) return leftOrder.end - rightOrder.end;
|
||||
if (left.sortSeq !== right.sortSeq) return left.sortSeq - right.sortSeq;
|
||||
return left.label.localeCompare(right.label, "ko");
|
||||
});
|
||||
};
|
||||
|
||||
interface WaterDepthProps {
|
||||
onClose?: () => void;
|
||||
@@ -34,6 +122,7 @@ interface WaterDepthProps {
|
||||
setIsPlaying: (isPlaying: boolean) => void;
|
||||
totalSteps?: number;
|
||||
currentStep?: number;
|
||||
legendItems?: WaterDepthLegendItem[];
|
||||
}
|
||||
|
||||
export function WaterDepth({
|
||||
@@ -42,6 +131,7 @@ export function WaterDepth({
|
||||
setIsPlaying,
|
||||
totalSteps = 0,
|
||||
currentStep = 0,
|
||||
legendItems = [],
|
||||
}: WaterDepthProps) {
|
||||
|
||||
useEffect(() => {
|
||||
@@ -80,10 +170,11 @@ export function WaterDepth({
|
||||
<div className="w-[140px] mx-auto flex flex-col items-center">
|
||||
<div className="bg-blue-900 text-white text-2xl text-center py-1 mb-2 rounded w-full">침수심 범례</div>
|
||||
<div className="space-y-1 w-full">
|
||||
{WATER_DEPTH_LEGEND_ITEMS.map((item, index) => (
|
||||
{legendItems.map((item) => (
|
||||
<LegendItem
|
||||
key={`${item.label}-${index}`}
|
||||
key={item.key}
|
||||
color={item.color}
|
||||
borderColor={item.borderColor}
|
||||
label={item.label}
|
||||
/>
|
||||
))}
|
||||
@@ -95,12 +186,12 @@ export function WaterDepth({
|
||||
);
|
||||
}
|
||||
|
||||
function LegendItem({ color, label }: { color: string; label: string }) {
|
||||
function LegendItem({ color, borderColor, label }: { color: string; borderColor: string; label: string }) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<span
|
||||
className="inline-block w-16 h-6"
|
||||
style={{ backgroundColor: color, border: "1px solid #ccc" }}
|
||||
style={{ backgroundColor: color, border: `1px solid ${borderColor}` }}
|
||||
/>
|
||||
<span className="text-xl text-gray-900 font-bold">{label}</span>
|
||||
</div>
|
||||
|
||||
@@ -45,7 +45,7 @@ GisApp.GOOGLE_3D_URL = `https://tile.googleapis.com/v1/3dtiles/root.json?key=${G
|
||||
|
||||
// NGII 정보(배경지도)
|
||||
//GisApp.NGII_WMTS_URL = process.env.NEXT_PUBLIC_NGII_URL;
|
||||
GisApp.NGII_WMTS_URL = `/ngii-server/openapi/Gettile.do?apikey=${process.env.NEXT_PUBLIC_NGII_PARAM}`;
|
||||
GisApp.NGII_WMTS_PARAM = process.env.NEXT_PUBLIC_NGII_PARAM ?? '';
|
||||
|
||||
GisApp.EMAP_URL = '/emap/';
|
||||
GisApp.EMAP_SATELLITE_URL = '/emap/satellite/';
|
||||
@@ -79,6 +79,17 @@ GisApp.setLayerInfos = function (layerInfos) {
|
||||
GisApp.layerInfos = layerInfos;
|
||||
};
|
||||
|
||||
GisApp.getNgiiWmtsUrl = function (layerName, matrix, x, y) {
|
||||
const layerAwareParams = /(^|&)layer=[^&]*/i.test(GisApp.NGII_WMTS_PARAM)
|
||||
? GisApp.NGII_WMTS_PARAM.replace(/(^|&)layer=[^&]*/i, `$1layer=${layerName}`)
|
||||
: `${GisApp.NGII_WMTS_PARAM}&layer=${layerName}`;
|
||||
|
||||
return `/ngii-server/openapi/Gettile.do?apikey=${layerAwareParams}`
|
||||
.replace("{z}", matrix)
|
||||
.replace("{x}", x)
|
||||
.replace("{y}", y);
|
||||
};
|
||||
|
||||
/**
|
||||
* GisApp core
|
||||
* @requires gis.layer.js
|
||||
@@ -161,7 +172,7 @@ GisApp.core.prototype = {
|
||||
});
|
||||
|
||||
const wmtsSource = new TileWMTS({
|
||||
//url: GisApp.NGII_WMTS_URL,
|
||||
// url은 setTileUrlFunction에서 layer별로 동적으로 만든다.
|
||||
projection: projection,
|
||||
tileGrid: tileGrid,
|
||||
crossOrigin: "anonymous",
|
||||
@@ -175,11 +186,7 @@ GisApp.core.prototype = {
|
||||
wmtsSource.setTileUrlFunction((tileCoord) => {
|
||||
const [z, x, y] = tileCoord;
|
||||
const matrix = matrixIds[z];
|
||||
// console.log(GisApp.NGII_WMTS_URL.replace("{z}", matrix).replace("{x}", x).replace("{y}", y));
|
||||
return GisApp.NGII_WMTS_URL
|
||||
.replace("{z}", matrix)
|
||||
.replace("{x}", x)
|
||||
.replace("{y}", y);
|
||||
return GisApp.getNgiiWmtsUrl(_layer, matrix, x, y);
|
||||
});
|
||||
|
||||
return new TileLayer({
|
||||
|
||||
Reference in New Issue
Block a user