/* ============================================================================= * map_layer_list.ts * 공용 지도 겹 목록(PLAN 624-2 · 요구 530 · 결정 A② · A⑧ · A⑨) — 예정노선(D03) · B05 평면이 같이 씀. * 겹 = 요구 546 그룹1 표 한 줄 — 열쇠 · 아이콘 · 호버제목 · 아이콘 묶음(`LAYER_GROUPS` · 없음 = 단독) · 보일 페이지 · 처음 값(`on` = 예정노선). * 페이지 표(`MAP_LAYER_PAGES`) = 처음 켬(없으면 겹의 `on`) · 켬끔 취향 열쇠. * 선 겹 꼴 · 보호구역 색 · 산사태위험 범례 = GL 꼴(`map_layer_specs`) · 범례(`map_legend`) · 옛 캔버스가 같이 읽음. * 쓰는 법 = 겹 줄 `createMapLayerBar({ page })`(`map_layer_bar`) · 새 겹 = `LAYERS` 한 줄 + 꼴 한 벌. * ========================================================================== */ import type { IconName } from "@ui/ui_template_icons"; import type { LegendItem } from "@ui/ui_template_flow_legend"; import { readByKey } from "../b_page_state"; export type MapLayerKey = | "satellite" | "hillshade" | "contour" | "slope" | "stream" | "water" | "road" | "forestRoad" // 걷은 겹(임도 짐작 · 요구 532) — 목록 · 꼴 없음 · 페이지 자료 셈 이음만 남음 | "forestRoadFile" | "nationalRoad" | "ecoForest" | "ecology" | "cadastral" | "zones" | "soils" | "landslide" | "landslideZones" | "faults" | "lineaments" | "zeroOrder" | "bands" // 계류 100 m 띠(자료 = 띠 둘 한 벌 · `_band` 로 가름) | "roadBands" // 도로 300 m 띠(같은 자료 · 「도로」 묶음) | "building" | "place"; /** 임도(업로드) 고름(605-2) — 묶음 하나 또는 그 안의 선 하나(지도 강조). */ export interface RoadPick { bundle: string; line?: number; } /** 지도가 있는 페이지. */ export type MapPage = "d03" | "b05"; /** 아이콘 묶음(요구 546 표 「아이콘 그룹명」) — 묶음 단추 = 아이콘 + ▾ + 켠 수 · 여럿 고르기 메뉴. */ export type LayerGroup = "water" | "road" | "land" | "limit"; export const LAYER_GROUPS: Record = { water: { name: "수계망도", icon: "streams" }, road: { name: "도로", icon: "signpost" }, land: { name: "토지·건물", icon: "parcelHouse" }, limit: { name: "제한·재해", icon: "ban" }, }; export interface LayerDef { key: MapLayerKey; icon: IconName; /** 호버제목(요구 546 표) — 마우스 글 · 메뉴 줄 글. */ title: string; /** 아이콘 묶음 — null = 단독 단추. */ group: LayerGroup | null; /** 보일 페이지(표 「사용」). */ pages: readonly MapPage[]; /** 예정노선 처음 값 — 다른 페이지는 `MAP_LAYER_PAGES[page].on`. */ on: boolean; } const BOTH: readonly MapPage[] = ["d03", "b05"]; const row = ( key: MapLayerKey, icon: IconName, title: string, group: LayerGroup | null, on = false, ): LayerDef => ({ key, icon, title, group, pages: BOTH, on }); /** 그룹1 표(요구 546 · 차례 = 단추 차례) — 도로 300 m 띠는 표 밖 한 줄(「도로」 묶음 · 기능 남김). */ export const LAYERS: readonly LayerDef[] = [ row("satellite", "satellite", "위성사진", null, true), row("hillshade", "sun", "음영기복도", null), row("contour", "mountain", "등고선", null, true), row("slope", "angle", "급경사", null), row("stream", "droplet", "세류", "water", true), row("water", "ripple", "하천", "water"), row("zeroOrder", "arrowsJoin", "0차곡", "water"), row("bands", "borderOuter", "계류100m", "water"), row("road", "road", "도로", "road"), row("nationalRoad", "route", "임도망도", "road"), row("forestRoadFile", "map", "임도(업로드)", "road"), row("roadBands", "roadBand", "도로300m", "road"), row("cadastral", "borderAll", "지적도", "land"), row("building", "building", "건물", "land"), row("place", "mapPin", "지명", "land"), row("zones", "shield", "보호규제구역", "limit"), row("soils", "leaf", "산림입지도", "limit"), row("ecoForest", "trees", "국유림·사유림", "limit"), row("ecology", "plant", "생태자연도", "limit"), row("landslide", "alert", "산사태위험지도", "limit"), row("landslideZones", "alertOctagon", "산사태취약지역", "limit"), row("faults", "bolt", "단층", "limit"), row("lineaments", "lineament", "지질구조선", "limit"), ]; /** 페이지 표 — 처음 켬(없음 = 겹의 `on`) · 켬끔 취향 열쇠. */ export const MAP_LAYER_PAGES: Record = { d03: { pref: "aislo:pref:d03.layers" }, // A② 처음 = 지금 그대로(등고선 · 하천) b05: { on: ["contour", "stream"], pref: "aislo:pref:b05.layers" }, }; /** 그 페이지에 보일 겹(표 차례). */ export const pageLayers = (page: MapPage): LayerDef[] => LAYERS.filter((def) => def.pages.includes(page)); /** 켬끔 취향 — 없거나 깨지면 페이지 처음 값 · 안 보일 겹 = 끔. */ export function readLayerPref(page: MapPage): Record { const { on: first, pref } = MAP_LAYER_PAGES[page]; const on = Object.fromEntries( LAYERS.map((def) => [def.key, first ? first.includes(def.key) : def.on]), ) as Record; try { const saved = JSON.parse(readByKey(pref) ?? "{}") as Partial>; for (const def of LAYERS) if (typeof saved[def.key] === "boolean") on[def.key] = saved[def.key] as boolean; } catch { // 깨진 값 = 처음 값 } for (const def of LAYERS) if (!def.pages.includes(page)) on[def.key] = false; return on; } /** 보호구역 종류 → 그림 색 값(서버 `ZONE_KINDS` 열쇠). */ export const ZONE_COLORS: Record = { forest: "--draw-map-zone-forest", baekdu: "--draw-map-zone-baekdu", water: "--draw-map-zone-water", park: "--draw-map-zone-park", heritage: "--draw-map-zone-heritage", wetland: "--draw-map-zone-wetland", wildlife: "--draw-map-zone-wildlife", greenbelt: "--draw-map-zone-greenbelt", }; /** 산사태위험지도 범례(1 등급 = 가장 위험 · 색 = WMS 그림 색). */ export const LANDSLIDE_LEGEND: LegendItem[] = [1, 2, 3, 4, 5].map((grade) => ({ label: `산사태 ${grade}등급`, color: `var(--draw-map-landslide-grade-${grade})`, })); /** 선 겹 — 이름 · 그림 꼴(서버 `LINE_KINDS` 열쇠 = `kind` · 없으면 0차곡). 새 선 겹 = 여기 한 줄. */ export type LineLayerKey = "faults" | "lineaments" | "zeroOrder"; export const LINE_LAYERS: Record< LineLayerKey, { label: string; kind?: string; color: string; width: number; dash: number[] } > = { faults: { label: "단층", kind: "faults", color: "--draw-map-fault", width: 2.4, dash: [] }, lineaments: { label: "지질구조선", kind: "lineaments", color: "--draw-map-lineament", width: 1.8, dash: [8, 4], }, zeroOrder: { label: "0차곡", color: "--draw-map-zero-order", width: 1.8, dash: [4, 3] }, }; export const LINE_KEYS = Object.keys(LINE_LAYERS) as LineLayerKey[];