- 겹 표(map_layer_list) = 그룹1 22 줄 표 차례 · title = 호버제목 · group(수계망도 · 도로 · 토지·건물 · 제한·재해) · pages · 숨김 표 걷음 · 도로300m 한 줄(「도로」 묶음 · 기능 남김) - 겹 줄(map_layer_bar) = 단독 넷 | 묶음 넷(여럿 고르기 메뉴 · 켠 수 배지 · 안 닫힘) | 그래픽 카드 · sync 가 단추 · 메뉴 줄 막힘 · 글을 맞춤(why = 페이지 까닭) - 도구 표(map_toolbar) = 그룹2 17 줄 title · mapToolGroups 가 글을 표로 맞춤 · 기본 단추 생성 · 지형 칩 막힘 「수치지형도/라이다 — 까닭」 - 띠 = 한 자료 · 겹 둘(GL fillEdge 거름 · 옛 캔버스 · 범례 줄 따로) · B05 띠 받기(D03 과 같은 서버 길 · bandCollection 공용) - D03 = 겹 줄 · 도구줄 한 행(createMapToolRow) · 222px 기댐 · 줄바꿈 CSS 걷음 · 측점 · 기울기 호버 덮어쓰기 걷음 · LAS 올리기 호버 걷음 - B05 = 세로 두 줄 → 한 행 · 호버 덮어쓰기 걷음(도구줄 · 1m 등고선 · 3D · 추천) · 겹 막힘 querySelector 덮어쓰기 → 공용 sync · 임도(업로드) 보임 + 「받은 임도 자료 없음」 막힘 · 스냅 간격 날 select → 고르기 메뉴(하나) - 켬끔 기억 열쇠 · 측점 · 기울기 처음 값 · 그래픽 카드 취향 열쇠 둘 그대로 - 낡은 시험 고침(568_1 · 622_2 · 624_2 · 624_3 · 553_4 · 625_draw · b05_routeedit_initial · 626_3 · 390_4 · 346_2 · 353_1 · 581_2 · 584_1 · 584_3) — 표 꼴 row() · 호버 글 = 표 · 한 행 · 고르기 메뉴로 바뀐 글을 잡음 · 새 지킴 634_2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
316 lines
11 KiB
TypeScript
316 lines
11 KiB
TypeScript
/* =============================================================================
|
|
* map_layer_specs.ts
|
|
* 공용 지도 GL 겹 꼴(PLAN 586-2 · 624-2 · 요구 530) — 바탕 겹 스물의 MapLibre 꼴 · 차례(아래 → 위):
|
|
* 위성 · 그림 겹(음영 · 급경사 · 산사태위험 · 생태자연도) · 면 겹(산림입지 · 경제림 · 보호구역 · 띠 · 물 면 · 건물) ·
|
|
* 선 겹(지적 · 등고선 · 도로 · 임도망도 · 임도(업로드) · 세류 · 단층 · 지질구조선 · 0차곡 · 산사태취약 필지) ·
|
|
* 점 · 글(산사태취약 · 임도망도 연도 · 종류 · 지명 · 등고 수치).
|
|
* 자료 = 받는 쪽이 지물에 붙인 `_color` · 고정 소스(위성 · 등고 · 산림 조각) 이름 = `map_forest_layers` · `ui_template_gl_map`.
|
|
* 카메라 · 지도 세우기는 페이지 몫(D03 `UI_MapGl.createGlBase` · B05 `createTmGl`) — 여기는 꼴만.
|
|
* ========================================================================== */
|
|
|
|
import type { ExpressionSpecification, FilterSpecification } from "maplibre-gl";
|
|
import { GL_FONT, type GlSpec as GlMapSpec } from "@ui/ui_template_gl_map";
|
|
import { themeColor } from "@ui/ui_template_palette";
|
|
import {
|
|
forestAreaSpecs,
|
|
forestRasterSpecs,
|
|
forestRoadLabelSpec,
|
|
forestRoadSpecs,
|
|
} from "./map_forest_layers";
|
|
import {
|
|
LINE_KEYS,
|
|
LINE_LAYERS,
|
|
type LineLayerKey,
|
|
type MapLayerKey,
|
|
type RoadPick,
|
|
} from "./map_layer_list";
|
|
|
|
/** 자료 소스(위성 = 조각 · 걷은 임도 짐작 = 소스 없음). */
|
|
export type GlSource = Exclude<MapLayerKey, "satellite" | "forestRoad">;
|
|
|
|
const GL_FONT_BOLD = [`${GL_FONT} SemiBold`, GL_FONT];
|
|
|
|
type GlSpec = GlMapSpec<MapLayerKey>;
|
|
|
|
const HALO = "rgba(0, 0, 0, 0.55)";
|
|
const ROUND = { "line-join": "round", "line-cap": "round" } as const;
|
|
const COLOR: ExpressionSpecification = ["get", "_color"];
|
|
/** 등고 조각 굵은 선(서버가 줌마다 정함) — 가는 선은 모두라 굵은 선은 두 번 겹쳐 짙음(캔버스처럼). */
|
|
const BOLD = ["==", ["get", "k"], 1] as unknown as FilterSpecification;
|
|
/** 캔버스 점선(px) → MapLibre 점선(선 굵기 배수). */
|
|
const dash = (px: number[], width: number): number[] => px.map((v) => v / width);
|
|
|
|
/** 선 겹 — 검은 테두리 + 선(점선이면 둘 다 같은 px 무늬). */
|
|
function haloLine(
|
|
key: MapLayerKey,
|
|
source: GlSource,
|
|
color: string,
|
|
width: number,
|
|
pattern: number[] = [],
|
|
halo = { color: HALO, width: width + 1.6 },
|
|
filter?: FilterSpecification,
|
|
): GlSpec[] {
|
|
const line = (id: string, paint: Record<string, unknown>, w: number): GlSpec =>
|
|
({
|
|
key,
|
|
id,
|
|
type: "line",
|
|
source,
|
|
...(filter ? { filter } : {}),
|
|
layout: ROUND,
|
|
paint: {
|
|
...paint,
|
|
"line-width": w,
|
|
...(pattern.length ? { "line-dasharray": dash(pattern, w) } : {}),
|
|
},
|
|
}) as GlSpec;
|
|
return [
|
|
line(`${key}-halo`, { "line-color": halo.color }, halo.width),
|
|
line(key, { "line-color": color }, width),
|
|
];
|
|
}
|
|
|
|
/** 면 겹 — 채움 + 테두리(캔버스 `fillLayer` 처럼 테두리 투명도 0.9). */
|
|
/** 띠 테두리 — 계류 · 도로 같은 꼴. */
|
|
const BAND_EDGE = { color: COLOR, width: 1.2, dash: [6, 4] };
|
|
|
|
function fillEdge(
|
|
key: MapLayerKey,
|
|
source: GlSource,
|
|
fill: string | ExpressionSpecification,
|
|
opacity: number | ExpressionSpecification,
|
|
edge: { color: string | ExpressionSpecification; width: number; dash?: number[] },
|
|
filter?: ExpressionSpecification,
|
|
): GlSpec[] {
|
|
const only = filter ? { filter } : {};
|
|
return [
|
|
{
|
|
key,
|
|
id: key,
|
|
type: "fill",
|
|
source,
|
|
...only,
|
|
paint: { "fill-color": fill, "fill-opacity": opacity },
|
|
} as GlSpec,
|
|
{
|
|
key,
|
|
id: `${key}-edge`,
|
|
type: "line",
|
|
source,
|
|
...only,
|
|
layout: ROUND,
|
|
paint: {
|
|
"line-color": edge.color,
|
|
"line-width": edge.width,
|
|
"line-opacity": 0.9,
|
|
...(edge.dash ? { "line-dasharray": dash(edge.dash, edge.width) } : {}),
|
|
},
|
|
} as GlSpec,
|
|
];
|
|
}
|
|
|
|
/** 겹 차례(아래 → 위)와 꼴 — 옛 캔버스(D03 `UI_LayerPaint`)의 색 · 굵기 · 투명도 · 차례 그대로 · `light` = 위성 밝기 상한. */
|
|
export function mapLayerSpecs(light: number, pick: RoadPick | null): GlSpec[] {
|
|
const contour = themeColor("--draw-map-contour", "#ffd68c");
|
|
const place = themeColor("--draw-map-place", "#f8fafc");
|
|
const landslideZone = themeColor("--draw-map-landslide-zone", "#e11d48");
|
|
const raster = (
|
|
key: "satellite" | "hillshade" | "slope",
|
|
opacity: number,
|
|
extra: Record<string, number> = {},
|
|
): GlSpec =>
|
|
({
|
|
key,
|
|
id: key,
|
|
type: "raster",
|
|
source: key,
|
|
paint: { "raster-opacity": opacity, "raster-fade-duration": 0, ...extra },
|
|
}) as GlSpec;
|
|
const lineKind = (key: LineLayerKey): GlSpec[] => {
|
|
const { color, width, dash } = LINE_LAYERS[key];
|
|
return haloLine(key, key, themeColor(color, "#f43f5e"), width, dash);
|
|
};
|
|
return [
|
|
// 위성 = 조금 어둡고 연하게(요구 475 · 노선 선 대비) — 밝기 상한은 사용자 조절
|
|
raster("satellite", 1, { "raster-brightness-max": light, "raster-saturation": SAT_SATURATION }),
|
|
raster("hillshade", 1), // 위성 위 = 「곱하기」와 같은 검은 그늘 그림(받는 쪽이 바꿈)
|
|
raster("slope", 0.55),
|
|
...forestRasterSpecs(), // 산사태위험 · 생태자연도 = 조각 그림(622-2)
|
|
...forestAreaSpecs(), // 산림입지도 · 경제림 = 벡터 조각
|
|
...fillEdge("zones", "zones", COLOR, 0.22, { color: COLOR, width: 1.6 }),
|
|
// 띠 = 한 자료(`_band`) · 겹 둘(634-2 · 도로 300 m 아래 · 계류 100 m 위 — 옛 한 겹 차례 그대로)
|
|
...fillEdge("roadBands", "bands", COLOR, 0.16, BAND_EDGE, ["==", ["get", "_band"], "road"]),
|
|
...fillEdge("bands", "bands", COLOR, 0.18, BAND_EDGE, ["==", ["get", "_band"], "stream"]),
|
|
{
|
|
key: "cadastral",
|
|
id: "cadastral",
|
|
type: "line",
|
|
source: "cadastral",
|
|
layout: ROUND,
|
|
paint: {
|
|
"line-color": themeColor("--draw-map-parcel", "#f0abfc"),
|
|
"line-width": 0.8,
|
|
"line-opacity": 0.8,
|
|
},
|
|
} as GlSpec,
|
|
...((): GlSpec[] => {
|
|
const water = themeColor("--draw-map-water", "#0ea5e9");
|
|
return fillEdge("water", "water", water, 0.45, { color: water, width: 1 });
|
|
})(),
|
|
...fillEdge("building", "building", themeColor("--draw-map-building", "#fb7185"), 0.7, {
|
|
color: HALO,
|
|
width: 0.6,
|
|
}),
|
|
{
|
|
key: "contour",
|
|
id: "contour-minor",
|
|
type: "line",
|
|
source: "contour",
|
|
"source-layer": "contour",
|
|
layout: ROUND,
|
|
paint: { "line-color": contour, "line-width": 0.8, "line-opacity": 0.7 },
|
|
} as GlSpec,
|
|
{
|
|
key: "contour",
|
|
id: "contour-index",
|
|
type: "line",
|
|
source: "contour",
|
|
"source-layer": "contour",
|
|
filter: BOLD,
|
|
layout: ROUND,
|
|
paint: { "line-color": contour, "line-width": 1.6, "line-opacity": 0.7 },
|
|
} as GlSpec,
|
|
{
|
|
key: "contour",
|
|
id: "contour-label",
|
|
type: "symbol",
|
|
source: "contour",
|
|
"source-layer": "contour",
|
|
filter: BOLD,
|
|
layout: {
|
|
"symbol-placement": "line",
|
|
"symbol-spacing": 320,
|
|
"text-field": ["to-string", ["get", "h"]],
|
|
"text-font": [GL_FONT],
|
|
"text-size": 10,
|
|
"text-max-angle": 30,
|
|
"text-padding": 2,
|
|
},
|
|
paint: {
|
|
"text-color": contour,
|
|
"text-halo-color": "rgba(0, 0, 0, 0.6)",
|
|
"text-halo-width": 1.5,
|
|
},
|
|
} as GlSpec,
|
|
...haloLine("road", "road", themeColor("--draw-map-road", "#f8fafc"), 1.6, [], {
|
|
color: HALO,
|
|
width: 3.2,
|
|
}),
|
|
...forestRoadSpecs(), // 임도망도(전국 국가임도) = 벡터 조각 · 임도(업로드) 아래
|
|
...haloLine(
|
|
"forestRoadFile",
|
|
"forestRoadFile",
|
|
themeColor("--draw-map-forest-road-file", "#ff2fd0"),
|
|
2.4,
|
|
[],
|
|
{ color: HALO, width: 4 },
|
|
),
|
|
// 고른 묶음 · 선 강조(605-2) — 같은 자료에서 걸러 한 겹 더 굵게
|
|
...["-pick-halo", "-pick"].map(
|
|
(tail, i) =>
|
|
({
|
|
key: "forestRoadFile",
|
|
id: `forestRoadFile${tail}`,
|
|
type: "line",
|
|
source: "forestRoadFile",
|
|
filter: pickFilter(pick),
|
|
layout: ROUND,
|
|
paint: {
|
|
"line-color": i ? themeColor("--draw-map-sketch", "#ffe94d") : HALO,
|
|
"line-width": i ? 4.2 : 7.4,
|
|
},
|
|
}) as GlSpec,
|
|
),
|
|
{
|
|
key: "stream",
|
|
id: "stream",
|
|
type: "line",
|
|
source: "stream",
|
|
layout: ROUND,
|
|
paint: {
|
|
"line-color": themeColor("--draw-map-stream", "#38bdf8"),
|
|
"line-width": ["case", ["==", ["get", "구분"], "세류"], 1, 2.4],
|
|
},
|
|
} as GlSpec,
|
|
...LINE_KEYS.flatMap(lineKind),
|
|
// 산사태취약 — 필지 = 「이 필지 안 어딘가」 테두리만 · 점 = 지정 자리
|
|
...haloLine(
|
|
"landslideZones",
|
|
"landslideZones",
|
|
landslideZone,
|
|
1.8,
|
|
[6, 3],
|
|
{ color: "rgba(255, 255, 255, 0.7)", width: 3.2 },
|
|
["!=", ["geometry-type"], "Point"] as unknown as FilterSpecification,
|
|
),
|
|
{
|
|
key: "landslideZones",
|
|
id: "landslideZones-point",
|
|
type: "circle",
|
|
source: "landslideZones",
|
|
filter: ["==", ["geometry-type"], "Point"],
|
|
paint: {
|
|
"circle-radius": 5,
|
|
"circle-color": landslideZone,
|
|
"circle-stroke-color": "#ffffff",
|
|
"circle-stroke-width": 1.5,
|
|
},
|
|
} as GlSpec,
|
|
forestRoadLabelSpec(), // 임도망도 글 — 지명 아래(겹치면 지명이 앞섬)
|
|
{
|
|
key: "place",
|
|
id: "place-dot",
|
|
type: "circle",
|
|
source: "place",
|
|
paint: { "circle-radius": 2.5, "circle-color": place },
|
|
} as GlSpec,
|
|
{
|
|
key: "place",
|
|
id: "place",
|
|
type: "symbol",
|
|
source: "place",
|
|
layout: {
|
|
"text-field": ["get", "name"],
|
|
"text-font": GL_FONT_BOLD,
|
|
"text-size": 11,
|
|
"text-anchor": "bottom",
|
|
"text-offset": [0, -0.36],
|
|
"text-padding": 2,
|
|
},
|
|
paint: {
|
|
"text-color": place,
|
|
"text-halo-color": "rgba(0, 0, 0, 0.7)",
|
|
"text-halo-width": 1.5,
|
|
},
|
|
} as GlSpec,
|
|
];
|
|
}
|
|
|
|
/** 고른 묶음 · 선만 거름(고름 없음 = 아무것도 안 맞는 거름). */
|
|
export function pickFilter(pick: RoadPick | null): FilterSpecification {
|
|
const all: unknown[] = ["all", ["==", ["get", "__bundle"], pick?.bundle ?? ""]];
|
|
if (pick?.line !== undefined) all.push(["==", ["get", "__line"], pick.line]);
|
|
return all as unknown as FilterSpecification;
|
|
}
|
|
|
|
/** 겹 색 지문 — 밝음 · 어두움이 바뀌면 색을 다시. */
|
|
export const MAP_THEME_NAMES = [
|
|
"--draw-map-contour",
|
|
"--draw-map-road",
|
|
"--draw-map-stream",
|
|
"--draw-map-parcel",
|
|
] as const;
|
|
|
|
/** 위성 채도(−1~1) — 조금 연하게. */
|
|
const SAT_SATURATION = -0.3;
|