- 열쇠 그대로(nationalRoad · forestRoadFile) — 옛 켬끔 취향 이어짐 · forestRoad 열쇠 · 도로 소로 거름은 B05 자료 셈 이음으로 남김(겹 목록 · 꼴 · 범례만 걷음) - 기존 도로(road) 겹 · 소스 · 범례 그대로 - 시험 553_4 겹 차례 · 584_3 범례 기대값을 새 이름으로 고침 · 622_2 에 630 · 534 지킴 덧 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
226 lines
9.2 KiB
TypeScript
226 lines
9.2 KiB
TypeScript
/* =============================================================================
|
|
* map_forest_layers.ts
|
|
* 공용 지도 — 받은 산림 · 환경 자료 겹(PLAN 622-2 · 624-2 · 요구 529 · 530) — 서버 줌별 조각(622-1)을 GL 고정 소스로(위성 · 등고선 615 꼴).
|
|
* 벡터 = 임도망도(forest_road · 전국 국가임도 · 간선 · 작업 · 산불 색 · 줌 따라 굵기 · 줌 14 부터 연도 · 종류 글) · 산림입지도(forest_soil · 토양 무리 색 ·
|
|
* 반투명 · z10 아래 숨김) · 경제림(eco_forest · 국유 · 사유 테두리) /
|
|
* 그림 = 생태자연도(ecology) · 산사태위험지도(landslide · 옛 캔버스는 받은 범위 한 장 그대로).
|
|
* 조각만 쓰는 겹(`TILE_ONLY`) = 받기 없음 · 옛 캔버스(그래픽 카드 끔)엔 안 보임.
|
|
* 토양 무리 색(`soilColor`) = 조각(`code` = SLTP_CD 두 자리) · 브이월드 요약(기호 「B₁」) 같은 색 — 범례 · 옛 캔버스도 이것.
|
|
* 부르는 곳 = `map_layer_specs`(꼴) · D03 `UI_MapGl`(소스) · `UI_Layers`(받기 거름) · `map_legend`(범례).
|
|
* ========================================================================== */
|
|
|
|
import type { ExpressionSpecification, SourceSpecification } from "maplibre-gl";
|
|
import type { LegendItem } from "@ui/ui_template_flow_legend";
|
|
import { GL_FONT, tileSource, type GlSpec } from "@ui/ui_template_gl_map";
|
|
import { themeColor } from "@ui/ui_template_palette";
|
|
import type { MapLayerKey } from "./map_layer_list";
|
|
|
|
/** 조각만 쓰는 겹 — 받을 자료 없음(위성 포함). */
|
|
const TILE_ONLY = ["satellite", "nationalRoad", "ecoForest", "ecology"] as const;
|
|
export type TileOnlyKey = (typeof TILE_ONLY)[number];
|
|
export const tileOnly = (key: MapLayerKey): key is TileOnlyKey =>
|
|
(TILE_ONLY as readonly string[]).includes(key);
|
|
|
|
/** 받는 동안 서클에 세는 조각 소스(등고선 포함). */
|
|
export const TRACKED_SOURCES = [
|
|
"contour",
|
|
"forest_road",
|
|
"forest_soil",
|
|
"eco_forest",
|
|
"ecology",
|
|
"landslide",
|
|
];
|
|
|
|
/** 고정 소스 — 벡터 z8~15(서버 줌별 단순화) · 그림 z6~19. */
|
|
export function forestSources(projectId: string): Record<string, SourceSpecification> {
|
|
const vt = (name: string) => tileSource(projectId, `vt/${name}`, "pbf", 8, 15);
|
|
const wmt = (name: string) => tileSource(projectId, `wmt/${name}`, "png", 6, 19);
|
|
return {
|
|
forest_road: vt("forest_road"),
|
|
forest_soil: vt("forest_soil"),
|
|
eco_forest: vt("eco_forest"),
|
|
ecology: wmt("ecology"),
|
|
landslide: wmt("landslide"),
|
|
};
|
|
}
|
|
|
|
/** 토양형 코드(산림입지토양도 SLTP_CD) → 기호. 무리 = 갈색 · 적황색 · 암적색 · 회갈색 · 화산회 · 침식 · 미숙 · 그 밖(비산림). */
|
|
const SOIL_SYMBOLS: Record<string, string> = {
|
|
"01": "B1", "02": "B2", "03": "B3", "04": "B4",
|
|
"05": "rB1", "06": "rB2", "07": "R1", "08": "R2", "09": "Y",
|
|
"10": "DR1", "11": "DR2", "12": "DR3", "13": "DRb1", "14": "DRb2",
|
|
"15": "GrB1", "16": "GrB2", "17": "Va1", "18": "Va2", "19": "Va3", "20": "Va4",
|
|
"21": "Va-R1", "22": "Va-R2", "23": "Va-gr",
|
|
"24": "Er1", "25": "Er2", "26": "Er-c", "27": "Im", "28": "Li", "29": "Bs",
|
|
}; // prettier-ignore
|
|
/** 무리 첫 코드(차례 = `--draw-map-soil-1..6`). */
|
|
const SOIL_GROUP_START = [1, 5, 10, 15, 24, 30];
|
|
const soilToken = (group: number): string => `--draw-map-soil-${group + 1}`;
|
|
const SUB = /[₀-₉]/g;
|
|
|
|
/** 토양형(조각 두 자리 코드 · 브이월드 기호 「B₁」) → 무리 색 값 · 모름 = 「그 밖」. */
|
|
export function soilColor(code: string): string {
|
|
const plain = code.trim().replace(SUB, (c) => String(c.charCodeAt(0) - 0x2080));
|
|
const found = /^\d+$/.test(plain)
|
|
? Number(plain)
|
|
: Number(Object.keys(SOIL_SYMBOLS).find((key) => SOIL_SYMBOLS[key] === plain) ?? 99);
|
|
return soilToken(Math.max(SOIL_GROUP_START.filter((start) => found >= start).length - 1, 0));
|
|
}
|
|
|
|
/** 임도 사업 구분(`kind` 1 간선 · 2 작업 · 3 산불) 색 값. */
|
|
const ROAD_KINDS = [
|
|
{ kind: 1, label: "간선", color: "--draw-map-national-road-main" },
|
|
{ kind: 2, label: "작업", color: "--draw-map-national-road-work" },
|
|
{ kind: 3, label: "산불", color: "--draw-map-national-road-fire" },
|
|
];
|
|
const ECO_KINDS = [
|
|
{ own: "국유", color: "--draw-map-eco-national" },
|
|
{ own: "사유", color: "--draw-map-eco-private" },
|
|
];
|
|
/** 생태자연도 범례 = 환경부 WMS 그림 색(3등급 = 칠 없음). */
|
|
const ECOLOGY_LEGEND = [
|
|
{ label: "1등급", color: "--draw-map-ecology-1" },
|
|
{ label: "2등급", color: "--draw-map-ecology-2" },
|
|
{ label: "별도관리지역", color: "--draw-map-ecology-x" },
|
|
];
|
|
|
|
const HALO = "rgba(0, 0, 0, 0.55)";
|
|
const ROUND = { "line-join": "round", "line-cap": "round" } as const;
|
|
const zoomWidth = (z8: number, z12: number, z15: number): ExpressionSpecification =>
|
|
["interpolate", ["linear"], ["zoom"], 8, z8, 12, z12, 15, z15] as ExpressionSpecification;
|
|
|
|
type Spec = GlSpec<MapLayerKey>;
|
|
const raster = (key: "landslide" | "ecology", opacity: number): Spec =>
|
|
({
|
|
key,
|
|
id: key,
|
|
type: "raster",
|
|
source: key,
|
|
paint: { "raster-opacity": opacity, "raster-fade-duration": 0 },
|
|
}) as Spec;
|
|
|
|
/** 그림 겹(산사태위험 · 생태자연도) — 아래 차례(급경사 위). */
|
|
export const forestRasterSpecs = (): Spec[] => [raster("landslide", 0.6), raster("ecology", 0.55)];
|
|
|
|
/** 면 겹(산림입지도 · 경제림) — 보호구역 아래. */
|
|
export function forestAreaSpecs(): Spec[] {
|
|
const soilAt = (group: number): string => themeColor(soilToken(group), "#e879f9");
|
|
const soil = [
|
|
"step",
|
|
["to-number", ["get", "code"], 99],
|
|
soilAt(0),
|
|
...SOIL_GROUP_START.slice(1).flatMap((start, i) => [start, soilAt(i + 1)]),
|
|
] as ExpressionSpecification;
|
|
const eco = [
|
|
"match",
|
|
["get", "own"],
|
|
...ECO_KINDS.flatMap(({ own, color }) => [own, themeColor(color, "#34d399")]),
|
|
themeColor(ECO_KINDS[1].color, "#a78bfa"),
|
|
] as unknown as ExpressionSpecification;
|
|
const vector = (source: string) => ({ source, "source-layer": source });
|
|
return [
|
|
{
|
|
key: "soils",
|
|
id: "soils",
|
|
type: "fill",
|
|
...vector("forest_soil"),
|
|
minzoom: 10,
|
|
paint: { "fill-color": soil, "fill-opacity": 0.35 },
|
|
} as Spec,
|
|
{
|
|
key: "soils",
|
|
id: "soils-edge",
|
|
type: "line",
|
|
...vector("forest_soil"),
|
|
minzoom: 10,
|
|
layout: ROUND,
|
|
paint: { "line-color": soil, "line-width": 0.8, "line-opacity": 0.9 },
|
|
} as Spec,
|
|
{
|
|
key: "ecoForest",
|
|
id: "ecoForest",
|
|
type: "line",
|
|
...vector("eco_forest"),
|
|
layout: ROUND,
|
|
paint: { "line-color": eco, "line-width": zoomWidth(1, 1.8, 2.4) },
|
|
} as Spec,
|
|
];
|
|
}
|
|
|
|
/** 임도망도(전국 국가임도) 선 — 검은 테 + 사업 구분 색(도로 위 · 임도(업로드) 아래). */
|
|
export function forestRoadSpecs(): Spec[] {
|
|
const color = [
|
|
"match",
|
|
["get", "kind"],
|
|
...ROAD_KINDS.flatMap(({ kind, color }) => [kind, themeColor(color, "#ff6b00")]),
|
|
themeColor(ROAD_KINDS[0].color, "#ff6b00"),
|
|
] as unknown as ExpressionSpecification;
|
|
const line = (id: string, paint: Record<string, unknown>): Spec =>
|
|
({
|
|
key: "nationalRoad",
|
|
id,
|
|
type: "line",
|
|
source: "forest_road",
|
|
"source-layer": "forest_road",
|
|
layout: ROUND,
|
|
paint,
|
|
}) as Spec;
|
|
return [
|
|
line("nationalRoad-halo", { "line-color": HALO, "line-width": zoomWidth(2.2, 3.6, 5.6) }),
|
|
line("nationalRoad", { "line-color": color, "line-width": zoomWidth(1, 2, 3.6) }),
|
|
];
|
|
}
|
|
|
|
/** 임도망도 글(요구 534) — 줌 14 부터 선 따라 「개설 연도 · 종류」(연도 0 = 종류만) · 겹치면 뺌 · 꼴 = 지명 글. */
|
|
export function forestRoadLabelSpec(): Spec {
|
|
const kind = [
|
|
"match",
|
|
["get", "kind"],
|
|
...ROAD_KINDS.flatMap(({ kind, label }) => [kind, label]),
|
|
"",
|
|
] as unknown as ExpressionSpecification;
|
|
return {
|
|
key: "nationalRoad",
|
|
id: "nationalRoad-label",
|
|
type: "symbol",
|
|
source: "forest_road",
|
|
"source-layer": "forest_road",
|
|
minzoom: 14,
|
|
layout: {
|
|
"symbol-placement": "line",
|
|
"symbol-spacing": 300,
|
|
"text-field": [
|
|
"concat",
|
|
["case", [">", ["get", "year"], 0], ["concat", ["to-string", ["get", "year"]], " · "], ""],
|
|
kind,
|
|
],
|
|
"text-font": [GL_FONT],
|
|
"text-size": 11,
|
|
"text-max-angle": 30,
|
|
"text-padding": 2,
|
|
},
|
|
paint: {
|
|
"text-color": themeColor("--draw-map-place", "#f8fafc"),
|
|
"text-halo-color": "rgba(0, 0, 0, 0.7)",
|
|
"text-halo-width": 1.5,
|
|
},
|
|
} as unknown as Spec;
|
|
}
|
|
|
|
/** 범례 — 켠 조각 겹(산사태위험 = 범례 자리 `LANDSLIDE_LEGEND` 따로). */
|
|
export function forestLegendRows(on: Record<MapLayerKey, boolean>): LegendItem[][] {
|
|
const item = (label: string, color: string, kind?: LegendItem["kind"]): LegendItem => ({
|
|
label,
|
|
color: `var(${color})`,
|
|
...(kind ? { kind } : {}),
|
|
});
|
|
const rows: LegendItem[][] = [];
|
|
if (on.nationalRoad)
|
|
rows.push(ROAD_KINDS.map(({ label, color }) => item(`임도망도 ${label}`, color, "line")));
|
|
if (on.ecoForest)
|
|
rows.push(ECO_KINDS.map(({ own, color }) => item(`경제림 ${own}`, color, "line")));
|
|
if (on.ecology)
|
|
rows.push(ECOLOGY_LEGEND.map(({ label, color }) => item(`생태자연도 ${label}`, color)));
|
|
return rows;
|
|
}
|