/* ============================================================================= * B04_PreProcess_UI_MapViewer_Gl.ts * B04 하단 지도 GL 바탕(PLAN 589-2) — 백지도 · 혼합(한 장 그림) · 위성(조각 · 줌인 선명) · 도엽 · 지적 · 행정 · * 등고선 겹을 MapLibre 로. 등고 글 · 배수유역 · 계획선 · 측점 · 흐름 강도 · 세부유역 · 관 = 위 캔버스 그대로. * 지도 본체(`_MapViewer`)는 켬끔 · 자료 · 따라가기만 부름(700 줄). * ========================================================================== */ import { getVWorldMapUrl, type VWorldMeta } from "./B04_PreProcess_Api_Fetch"; import { BACKGROUND_LAYERS, GIS_LAYERS, gisLayerColor, type BackgroundLayer, type GisLayer, } from "./B04_PreProcess_UI_MapLayers"; import { createLayerToggle } from "./B04_PreProcess_UI_LayerToggle"; import { createTmGl, GL_TOGGLE_TITLE, lineSpecs, metaCorners, rasterSpec, satelliteData, } from "./B04_PreProcess_UI_MapGl"; import { normalizedToScreen, type GeoJsonCollection, type Normalizer, type ViewState, } from "./B04_PreProcess_UI_MapRender"; type Key = BackgroundLayer | GisLayer; const isContour = (layer: GisLayer): boolean => layer === "등고선" || layer === "도엽_등고선"; /** 캔버스와 같은 차례 — 등고선이 맨 밑. */ const ORDER = [...GIS_LAYERS].sort((a, b) => (isContour(a) ? -1 : isContour(b) ? 1 : 0)); export interface SurfaceMapGl { /** 지금 GL 로 그리나 — 참이면 캔버스는 바탕 겹을 쉼 · 그림(img)은 숨김. */ on: () => boolean; /** 켠 겹. */ sync: (backgrounds: ReadonlySet, layers: ReadonlySet) => void; /** 받은 겹(원본 경위도 GeoJSON) — null = 비움. */ setLayer: (layer: GisLayer, data: GeoJsonCollection | null) => void; /** 프로젝트 · 메타 — 그림 · 조각 주소(null = 다 비움). */ setMeta: (projectId: string | null, meta: VWorldMeta | null) => void; follow: (meta: VWorldMeta | null, normalizer: Normalizer | null, view: ViewState) => void; dispose: () => void; } /** `buttons` = 켬끔 칩 자리 · `onChange` = 켬끔이 바뀜(그림 숨김 · 다시 그림). */ export function createSurfaceMapGl( viewport: HTMLElement, buttons: HTMLElement, onChange: () => void, ): SurfaceMapGl { const toggle = createLayerToggle({ label: "GPU", title: GL_TOGGLE_TITLE, onChange: () => tm.toggle(), }); toggle.root.dataset.layer = "gl"; buttons.append(toggle.root); const tm = createTmGl({ parent: viewport, specs: () => [ ...BACKGROUND_LAYERS.map((layer) => rasterSpec(layer)), ...ORDER.flatMap((layer) => lineSpecs(layer, gisLayerColor(layer), isContour(layer) ? 0.7 : 1.5, { marker: layer === "도엽_표고점" ? "x" : "dot", }), ), ], themeNames: ["--color-surface", "--map-cadastral", "--map-sheet-contour", "--map-sheet-stream"], onChange: (on) => { toggle.set(on); onChange(); }, }); toggle.set(tm.on()); return { on: tm.on, sync(backgrounds, layers) { for (const layer of BACKGROUND_LAYERS) tm.gl.setVisible(layer, backgrounds.has(layer)); for (const layer of GIS_LAYERS) tm.gl.setVisible(layer, layers.has(layer)); }, setLayer: (layer, data) => tm.gl.setData(layer, data), setMeta(projectId, meta) { tm.gl.setData("satellite", projectId ? satelliteData(projectId) : null); for (const layer of ["white", "hybrid"] as const) tm.gl.setData( layer, projectId && meta ? { url: location.origin + getVWorldMapUrl(projectId, layer), corners: metaCorners(meta), } : null, ); if (!meta) for (const layer of GIS_LAYERS) tm.gl.setData(layer, null); }, follow: (meta, normalizer, view) => tm.follow( meta, normalizer, (nx, ny) => normalizedToScreen(view, nx, ny), view.width, view.height, ), dispose: tm.dispose, }; }