/* ============================================================================= * map_geology.ts * 공용 지도 암상 지질도 겹(PLAN 760-1 · 요구 597-3 · 598-3) — D03 예정노선 · B05 평면이 같이 씀. * 그림 = KIGAM 5만 WMS 조각(`map_forest_layers` 「geology_map」) · 도형 = 서버 `/plan/layers/geology` * (기호별 요약 · 경위도 면 · 노선마다 암상 구간 · 키는 서버만). * 색 = 바위 갈래(서버 `ROCK_GROUPS` 차례) → `--draw-map-geology-1..6` · 면 위 글 = 「기호 · 첫 암상 낱말」. * 범례 줄(`geologyRow`) · 노선 구간 표(`geologyTail` · 참고 — 토사 · 암 판정 아님) = 범례 칸. * ========================================================================== */ import type { LegendItem } from "@ui/ui_template_flow_legend"; import { GL_FONT, type GlSpec } from "@ui/ui_template_gl_map"; import { themeColor } from "@ui/ui_template_palette"; import { createTable } from "@ui/ui_template_table"; import { el } from "@ui/ui_template_elements_base"; import { stationLabel } from "@util/common_util_station"; import { request, type GeoJsonCollection } from "./map_layer_api"; import { hectare } from "./map_layer_data"; import type { MapLayerKey } from "./map_layer_list"; /** 기호 하나(받은 범위 안). */ export interface GeologyUnit { symnum: string; symbol: string; litho: string; strata: string; age: string; sheet: string; group: number; count: number; area_m2: number; } /** 노선 구간 — `i` = 요약 차례 · -1 = 자료 없음. */ export interface GeologySegment { from_m: number; to_m: number; i: number; } export interface PlanGeology extends GeoJsonCollection { source: string; summary: GeologyUnit[]; groups: string[]; routes: Array<{ name: string; segments: GeologySegment[] }>; } const GROUP_FALLBACK = ["#f472b6", "#fb923c", "#fde047", "#818cf8", "#e5e7eb", "#5eead4"]; const groupToken = (group: number): string => `--draw-map-geology-${Math.min(group, 5) + 1}`; const firstWord = (text: string): string => text.split(/\s+/)[0] ?? ""; /** 긴 암상 글 줄임(범례 · 표 폭). */ const short = (text: string, max = 22): string => text.length > max ? `${text.slice(0, max)}…` : text; /** 받기 — 면 위 글 꼴을 먼저 읽어 둠(한글 글자). */ export async function fetchPlanGeology(projectId: string): Promise { const got = await request(`/projects/${projectId}/plan/layers/geology`); const text = got.summary.map((unit) => unit.symbol + unit.litho).join("") || "가"; await document.fonts.load(`600 24px "${GL_FONT}"`, text).catch(() => null); return got; } /** GL 자료 — 지물마다 색(`_color`) · 글(`_label`). */ export function geologyCollection(info: PlanGeology): GeoJsonCollection { return { features: (info.features ?? []).map((feature) => { const unit = info.summary[Number(feature.properties?.["i"])]; const group = unit?.group ?? 5; return { ...feature, properties: { ...feature.properties, _color: themeColor(groupToken(group), GROUP_FALLBACK[group] ?? GROUP_FALLBACK[5]), _label: unit ? `${unit.symbol} · ${firstWord(unit.litho)}` : "", }, }; }), }; } /** GL 꼴 — 연한 채움 · 갈래 색 테두리 · 면 위 글(겹치면 뺌). */ export function geologySpecs(): GlSpec[] { const color = ["get", "_color"]; return [ { key: "geology", id: "geology", type: "fill", source: "geology", paint: { "fill-color": color, "fill-opacity": 0.2 }, }, { key: "geology", id: "geology-edge", type: "line", source: "geology", layout: { "line-join": "round" }, paint: { "line-color": color, "line-width": 1.6, "line-opacity": 0.95 }, }, { key: "geology", id: "geology-label", type: "symbol", source: "geology", layout: { "text-field": ["get", "_label"], "text-font": [GL_FONT], "text-size": 12, "text-padding": 4, }, paint: { "text-color": color, "text-halo-color": "rgba(0, 0, 0, 0.75)", "text-halo-width": 1.5, }, }, ] as unknown as GlSpec[]; } const unitColor = (unit: GeologyUnit | undefined): string => unit ? `var(${groupToken(unit.group)})` : "transparent"; /** 범례 줄 — 기호마다 「기호 대표암상 넓이」(갈래 색) · 없으면 한 줄. */ export function geologyRow(info: PlanGeology): LegendItem[] { if (!info.summary.length) return [{ label: "암상 지질도 없음(받은 범위 안)", color: "transparent", off: true }]; return info.summary.map((unit) => ({ label: `${unit.symbol} ${short(unit.litho || unit.strata)} ${hectare(unit.area_m2)}`, color: unitColor(unit), })); } /** 노선 따라 바위 종류 구간 표(노선마다) + 참고 글 — 켬 · 받음 · 노선 있을 때만. */ export function geologyTail(on: Record, info?: PlanGeology): HTMLElement[] { if (!on.geology || !info?.routes.length) return []; const columns = [ { label: "측점" }, { label: "길이", align: "end" as const }, { label: "기호" }, { label: "대표암상" }, ]; const tables = info.routes.map((route) => { const rows = route.segments.map((seg) => { const unit = info.summary[seg.i]; return [ `${stationLabel(seg.from_m, 20)} ~ ${stationLabel(seg.to_m, 20)}`, `${(seg.to_m - seg.from_m).toFixed(0)} m`, unit?.symbol ?? "—", unit ? short(unit.litho || unit.strata, 30) : "자료 없음", ]; }); return el("div", { className: "ui-legend ui-legend--vertical", children: [`암상 구간 — ${route.name}`, createTable(columns, rows, { density: "data" })], }); }); const note = el("div", { className: "ui-legend", children: ["참고 — 토사 · 암 판정 아님(1:50,000 바위 종류 · 경계 수십 m 어긋날 수 있음)"], }); return [...tables, note]; }