feat(공용 지도 겹): 임도 짐작 걷음 · 그 자리 = 임도망도(전국 국가임도 · route 아이콘 · 범례 「임도망도 간선 · 작업 · 산불」 · 줌 14 부터 연도 · 종류 글) · 올린 임도망도 = 임도(업로드)(위치 칸 단추 · 목록 · 알림 · 서버 글) (PLAN 630 · 요구 532 · 534)

- 열쇠 그대로(nationalRoad · forestRoadFile) — 옛 켬끔 취향 이어짐 · forestRoad 열쇠 · 도로 소로 거름은 B05 자료 셈 이음으로 남김(겹 목록 · 꼴 · 범례만 걷음)
- 기존 도로(road) 겹 · 소스 · 범례 그대로
- 시험 553_4 겹 차례 · 584_3 범례 기대값을 새 이름으로 고침 · 622_2 에 630 · 534 지킴 덧

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 22:10:58 +09:00
co-authored by Claude Opus 5.5
parent a80367a2c2
commit a6ef53e8a3
14 changed files with 102 additions and 73 deletions
+40 -4
View File
@@ -1,7 +1,7 @@
/* =============================================================================
* map_forest_layers.ts
* 공용 지도 — 받은 산림 · 환경 자료 겹(PLAN 622-2 · 624-2 · 요구 529 · 530) — 서버 줌별 조각(622-1)을 GL 고정 소스로(위성 · 등고선 615 꼴).
* 벡터 = 국유림 임도(forest_road · 간선 · 작업 · 산불 색 · 줌 따라 굵기) · 산림입지도(forest_soil · 토양 무리 색 ·
* 벡터 = 임도망도(forest_road · 전국 국가임도 · 간선 · 작업 · 산불 색 · 줌 따라 굵기 · 줌 14 부터 연도 · 종류 글) · 산림입지도(forest_soil · 토양 무리 색 ·
* 반투명 · z10 아래 숨김) · 경제림(eco_forest · 국유 · 사유 테두리) /
* 그림 = 생태자연도(ecology) · 산사태위험지도(landslide · 옛 캔버스는 받은 범위 한 장 그대로).
* 조각만 쓰는 겹(`TILE_ONLY`) = 받기 없음 · 옛 캔버스(그래픽 카드 끔)엔 안 보임.
@@ -11,7 +11,7 @@
import type { ExpressionSpecification, SourceSpecification } from "maplibre-gl";
import type { LegendItem } from "@ui/ui_template_flow_legend";
import { tileSource, type GlSpec } from "@ui/ui_template_gl_map";
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";
@@ -147,7 +147,7 @@ export function forestAreaSpecs(): Spec[] {
];
}
/** 국유림 임도 선 — 검은 테 + 사업 구분 색(올린 임도망도 위). */
/** 임도망도(전국 국가임도) 선 — 검은 테 + 사업 구분 색(도로 위 · 임도(업로드) 아래). */
export function forestRoadSpecs(): Spec[] {
const color = [
"match",
@@ -171,6 +171,42 @@ export function forestRoadSpecs(): Spec[] {
];
}
/** 임도망도 글(요구 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 => ({
@@ -180,7 +216,7 @@ export function forestLegendRows(on: Record<MapLayerKey, boolean>): LegendItem[]
});
const rows: LegendItem[][] = [];
if (on.nationalRoad)
rows.push(ROAD_KINDS.map(({ label, color }) => item(`국유림 임도 ${label}`, color, "line")));
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)
+2 -2
View File
@@ -1,13 +1,13 @@
/* =============================================================================
* map_layer_data.ts
* 공용 지도 겹 자료 도움(PLAN 569-2 · 581 · 586-2 · 624-3) — D03 예정노선 · B05 평면이 같이 씀(D03 `UI_LayerDefs` 에서 옮김).
* 임도 짐작 구분 · 물 면 도엽 이름 · 넓이 글 · 그림 읽기 · GL 자료(지물 색 붙이기 · 그림 귀퉁이 · 음영 그늘 그림).
* 도로 소로 · 미분류 구분 · 물 면 도엽 이름 · 넓이 글 · 그림 읽기 · GL 자료(지물 색 붙이기 · 그림 귀퉁이 · 음영 그늘 그림).
* ========================================================================== */
import type { GlImage } from "@ui/ui_template_gl_map";
import type { GeoJsonCollection, PlanRaster } from "./map_layer_api";
/** 도로 중 기존 임도로 짐작하는 구분. */
/** 도로 중 소로 · 미분류 구분(옛 임도 짐작 · 겹 걷음 · 페이지 자료 셈만 씀). */
export const FOREST_ROAD_KINDS = new Set(["소로", "미분류"]);
/** 물 면 = 도엽 수계(E) 면 셋. */
export const WATER_KEYS = ["실폭하천", "수계면E003", "수계면E005"];
+7 -13
View File
@@ -19,7 +19,7 @@ export type MapLayerKey =
| "stream"
| "water"
| "road"
| "forestRoad"
| "forestRoad" // 걷은 겹(임도 짐작 · 요구 532) — 목록 · 꼴 없음 · 페이지 자료 셈 이음만 남음
| "forestRoadFile"
| "nationalRoad"
| "ecoForest"
@@ -36,7 +36,7 @@ export type MapLayerKey =
| "building"
| "place";
/** 올린 임도망 고름(605-2) — 묶음 하나 또는 그 안의 선 하나(지도 강조). */
/** 임도(업로드) 고름(605-2) — 묶음 하나 또는 그 안의 선 하나(지도 강조). */
export interface RoadPick {
bundle: string;
line?: number;
@@ -63,23 +63,17 @@ export const GROUPS: readonly (readonly LayerDef[])[] = [
{ key: "water", icon: "ripple", title: "물 면(도엽 실폭하천 · 저수지 등)", on: false },
{ key: "road", icon: "road", title: "기존 도로(도엽 도로중심선)", on: false },
{
key: "forestRoad",
key: "nationalRoad",
icon: "route",
title: "기존 임도 짐작(도엽 도로 소로 · 미분류 = 주황 점선 · 정식 임도망도 아님)",
title:
"임도망도(산림청 전국 국가임도 · 간선 · 작업 · 산불 색 · 지방 · 민간임도는 「임도(업로드)」로)",
on: false,
},
{
key: "forestRoadFile",
icon: "map",
title:
"올린 임도망도(산림청 시군구 SHP = 밝은 분홍 실선 · 왼쪽 「위치」 칸에서 올리고 묶음 고름 · 지움)",
on: false,
},
{
key: "nationalRoad",
icon: "flag",
title:
"국유림 임도(산림청 전국 국가임도 · 간선 · 작업 · 산불 색 · 지방 · 민간임도 없음 · 참고용)",
"임도(업로드)(산림청 시군구 SHP 등 사용자가 올린 지방 · 민간임도 = 밝은 분홍 실선 · 왼쪽 「위치」 칸에서 올리고 묶음 고름 · 지움)",
on: false,
},
],
@@ -155,7 +149,7 @@ export const MAP_LAYER_PAGES: Record<
{ hide: readonly MapLayerKey[]; on?: readonly MapLayerKey[]; pref: string }
> = {
d03: { hide: [], pref: "aislo:pref:d03.layers" },
// A② 처음 = 지금 그대로(등고선 · 하천) · A⑨ 띠(타당성) · 올린 임도망도 숨김
// A② 처음 = 지금 그대로(등고선 · 하천) · A⑨ 띠(타당성) · 임도(업로드) 숨김
b05: {
hide: ["bands", "forestRoadFile"],
on: ["contour", "stream"],
+11 -18
View File
@@ -2,8 +2,8 @@
* map_layer_specs.ts
* 공용 지도 GL 겹 꼴(PLAN 586-2 · 624-2 · 요구 530) — 바탕 겹 스물의 MapLibre 꼴 · 차례(아래 → 위):
* 위성 · 그림 겹(음영 · 급경사 · 산사태위험 · 생태자연도) · 면 겹(산림입지 · 경제림 · 보호구역 · 띠 · 물 면 · 건물) ·
* 선 겹(지적 · 등고선 · 도로 · 임도 짐작 · 올린 임도망도 · 국유림 임도 · 세류 · 단층 · 지질구조선 · 0차곡 · 산사태취약 필지) ·
* 점 · 글(산사태취약 · 지명 · 등고 수치).
* 선 겹(지적 · 등고선 · 도로 · 임도망도 · 임도(업로드) · 세류 · 단층 · 지질구조선 · 0차곡 · 산사태취약 필지) ·
* 점 · 글(산사태취약 · 임도망도 연도 · 종류 · 지명 · 등고 수치).
* 자료 = 받는 쪽이 지물에 붙인 `_color` · 고정 소스(위성 · 등고 · 산림 조각) 이름 = `map_forest_layers` · `ui_template_gl_map`.
* 카메라 · 지도 세우기는 페이지 몫(D03 `UI_MapGl.createGlBase` · B05 `createTmGl`) — 여기는 꼴만.
* ========================================================================== */
@@ -11,7 +11,12 @@
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, forestRoadSpecs } from "./map_forest_layers";
import {
forestAreaSpecs,
forestRasterSpecs,
forestRoadLabelSpec,
forestRoadSpecs,
} from "./map_forest_layers";
import {
LINE_KEYS,
LINE_LAYERS,
@@ -20,7 +25,7 @@ import {
type RoadPick,
} from "./map_layer_list";
/** 자료 소스(임도 짐작 = 도로 소스를 거름 · 위성 = 조각). */
/** 자료 소스(위성 = 조각 · 걷은 임도 짐작 = 소스 없음). */
export type GlSource = Exclude<MapLayerKey, "satellite" | "forestRoad">;
const GL_FONT_BOLD = [`${GL_FONT} SemiBold`, GL_FONT];
@@ -196,19 +201,7 @@ export function mapLayerSpecs(light: number, pick: RoadPick | null): GlSpec[] {
color: HALO,
width: 3.2,
}),
...haloLine(
"forestRoad",
"road",
themeColor("--draw-map-forest-road", "#f59e0b"),
2,
[7, 4],
{ color: HALO, width: 3.6 },
[
"in",
["get", "도로구분"],
["literal", ["소로", "미분류"]],
] as unknown as FilterSpecification,
),
...forestRoadSpecs(), // 임도망도(전국 국가임도) = 벡터 조각 · 임도(업로드) 아래
...haloLine(
"forestRoadFile",
"forestRoadFile",
@@ -217,7 +210,6 @@ export function mapLayerSpecs(light: number, pick: RoadPick | null): GlSpec[] {
[],
{ color: HALO, width: 4 },
),
...forestRoadSpecs(), // 국유림 임도 = 벡터 조각
// 고른 묶음 · 선 강조(605-2) — 같은 자료에서 걸러 한 겹 더 굵게
...["-pick-halo", "-pick"].map(
(tail, i) =>
@@ -269,6 +261,7 @@ export function mapLayerSpecs(light: number, pick: RoadPick | null): GlSpec[] {
"circle-stroke-width": 1.5,
},
} as GlSpec,
forestRoadLabelSpec(), // 임도망도 글 — 지명 아래(겹치면 지명이 앞섬)
{
key: "place",
id: "place-dot",
+2 -8
View File
@@ -1,7 +1,7 @@
/* =============================================================================
* map_legend.ts
* 공용 지도 범례(PLAN 569-2 · 622-2 · 624-2 · 요구 530) — 켠 겹 범례 칸 하나(줄마다 `createLegend`) + 페이지가 붙이는 덧 줄.
* 조각 겹 범례(산사태위험 · 국유림 임도 · 경제림 · 생태자연도) = 받을 자료 없음 → 켬끔만 보고 여기서 만듦(`tileLegendRows`).
* 조각 겹 범례(산사태위험 · 임도망도 · 경제림 · 생태자연도) = 받을 자료 없음 → 켬끔만 보고 여기서 만듦(`tileLegendRows`).
* 받은 자료 범례 줄(수 · 넓이 · 급경사 범위 · 624-3 D03 에서 옮김) = `legendRows(on, data)` — 받은 자료 꼴은 `LegendData`
* (D03 옛 캔버스 자료 · B05 GL 자료 둘 다 맞음 · 받는 일은 페이지 몫).
* 쓰는 법 = `const legend = createMapLegend("d03-plan__legend")` → `legend.show(rows, [덧 줄])` · 줄 · 덧 없으면 숨김.
@@ -189,15 +189,9 @@ export function legendRows(
label: `건물 ${(data.building.features?.length ?? 0).toLocaleString()}동`,
color: "var(--draw-map-building)",
});
if (on.forestRoad && data.road)
tail.push({
label: `임도 짐작 ${data.road.small.features?.length ?? 0}가닥`,
color: "var(--draw-map-forest-road)",
kind: "dash",
});
if (on.forestRoadFile && data.forestRoadFile)
tail.push({
label: `올린 임도망도 ${data.forestRoadFile.info.count}가닥 ${km(data.forestRoadFile.info.length_m)}`,
label: `임도(업로드) ${data.forestRoadFile.info.count}가닥 ${km(data.forestRoadFile.info.length_m)}`,
color: "var(--draw-map-forest-road-file)",
kind: "line",
});
@@ -217,7 +217,7 @@ def bundle_layer(
path, meta = _paths(project_root, bundle)
stamp = file_stamp(path)
if stamp is None:
raise FileNotFoundError(f"올린 임도망도 묶음 {bundle} 이(가) 없습니다.")
raise FileNotFoundError(f"임도(업로드) 묶음 {bundle} 이(가) 없습니다.")
at = [round(v, 1) for v in origin] if origin else None
key = derived_key(
ROAD_KIND, ROAD_VERSION, bundle, stamp, int(epsg), [round(v, 1) for v in box], at
@@ -238,7 +238,7 @@ def road_layer(project_root: Path, box: Box, epsg: int, origin: XY | None = None
ids = bundle_ids(project_root)
if not ids:
raise FileNotFoundError(
"올린 임도망도가 없습니다 — [임도망 올리기]로 시군구 SHP 를 올리세요."
"임도(업로드)가 없습니다 — 「위치」 칸 [임도(업로드)]로 시군구 SHP 를 올리세요."
)
rows, features, crs, edges, gaps = [], [], set(), [], []
for bundle in ids:
@@ -335,9 +335,9 @@ def save_roads(
def delete_bundle(project_root: Path, bundle: str) -> None:
"""묶음 하나 지움(저장본은 열쇠가 달라져 저절로 안 쓰임) — 없으면 `FileNotFoundError`."""
if bundle != LEGACY and not re.fullmatch(r"[0-9a-f]{12}", bundle):
raise FileNotFoundError(f"올린 임도망도 묶음 {bundle} 이(가) 없습니다.")
raise FileNotFoundError(f"임도(업로드) 묶음 {bundle} 이(가) 없습니다.")
zip_path, meta_path = _paths(project_root, bundle)
if not zip_path.is_file():
raise FileNotFoundError(f"올린 임도망도 묶음 {bundle} 이(가) 없습니다.")
raise FileNotFoundError(f"임도(업로드) 묶음 {bundle} 이(가) 없습니다.")
zip_path.unlink()
meta_path.unlink(missing_ok=True)
@@ -34,9 +34,9 @@ export type Prepared = Record<LineLayerKey, PreparedLines> & {
/** 세류 · 하천(소하천 · 지방하천 · 연결선). */
stream: [PreparedLayer, PreparedLayer];
cadastral: PreparedLayer;
/** 전체 · 그중 임도 짐작(소로 · 미분류) — `road` · `forestRoad` 가 함께 씀. */
/** 전체 · 그중 소로 · 미분류(옛 임도 짐작 · 겹 걷음 · 수만 셈). */
road: { all: PreparedLayer; small: PreparedLayer };
/** 올린 임도망도(584-3). */
/** 임도(업로드)(584-3). */
forestRoadFile: { layer: PreparedLayer; info: PlanForestRoads };
/** 급경사(603) — 값 그림 칸 · 고른 범위 · 칠한 그림(blob 주소) · 칠하기 ms. */
slope: {
+10 -10
View File
@@ -4,8 +4,8 @@
* (공용 겹 줄 `A00_Common/map/map_layer_bar` · 넘치면 가로 밀기 571-1 · 오른쪽 평면 도구줄과 따로).
*
* 겹 = 바탕(위성 · 음영기복 · 도엽 등고선(계곡선 25 m 굵게 · 수치) · 급경사 색) ·
* 물 · 길(세류선 · 하천 · 물 면(실폭하천 · 저수지) · 기존 도로 · 기존 임도 짐작(소로 · 미분류) ·
* 올린 임도망도(시군구 SHP · 올리기 = 왼쪽 「위치」 칸 `uploadRoads`)) ·
* 물 · 길(세류선 · 하천 · 물 면(실폭하천 · 저수지) · 기존 도로 · 임도망도(전국 국가임도 · 조각) ·
* 임도(업로드)(시군구 SHP · 올리기 = 왼쪽 「위치」 칸 `uploadRoads`)) ·
* 구역(지적도 · 보호 · 규제 구역 여덟 종 · 산림입지도(토양형) · 산사태위험지도 · 산사태취약지역(시도 표 점 · 지번 필지 테두리) ·
* 선 겹(단층 · 지질구조선 · 0차곡) ·
* 계류 100 m · 도로 300 m 띠 · 건물 · 지명).
@@ -14,14 +14,14 @@
* 옛 차례 답은 버림(475) · 도엽 선 겹(등고 · 세류 · 물 면 · 도로 · 건물 · 지명) = 보이는 도엽 조각(606 · `UI_SheetTiles`) · 지형을 안 받았으면 단추를 막음 ·
* 받기 실패 = 그 겹만 꺼짐 + 알림 · 받는 일은 페이지 서클에 셈(`track`).
* 그리기 차례(아래 → 위) = 위성(GL 조각만 · 옛 캔버스 = 위성 없음 · 590-2) → 음영기복 → 급경사 → 산사태위험지도 → 산림입지도 → 보호구역 → 띠 → 지적 → 물 면 →
* 건물 → 등고선 → 도로 → 임도 짐작 → 올린 임도망도 → 세류 → 선 겹 → 산사태취약지역 → 지명 → 그은 선.
* 건물 → 등고선 → 도로 → 임도(업로드) → 세류 → 선 겹 → 산사태취약지역 → 지명 → 그은 선.
* 겹 모두 = 바탕 캔버스(`base` · 582) · 등고선 = 줌 단계 색인 · 글 폭 기억.
* 범례 = 켠 겹의 받은 범위 안 수 · 넓이(왼쪽 아래 · 칸 = 공용 `map_legend` · 줄 = `D03_PlanAnalysis_UI_LayerLegend`).
* 급경사 = 서버 값 그림을 고른 범위대로 칠함 · 범례 ⚙ → 범위 모달(603 · `D03_PlanAnalysis_UI_SlopeBands`).
* GL 바탕(586-2 · 처음 = 켬 · 끝 단추 끔 = 옛 캔버스 · `aislo:pref:d03.canvas`) — 겹 스물 · 등고 글 · 지명을
* MapLibre 가 그림(`D03_PlanAnalysis_UI_MapGl`) · 캔버스 바탕 겹은 쉼(등고 간격 셈만) · 받기 · 범례는 같음 ·
* WebGL 못 씀 · 잃음 = 저절로 옛 캔버스 + 알림(취향은 안 바꿈).
* 받은 산림 · 환경 자료(622-2) = 국유림 임도 · 경제림 · 생태자연도(조각만 · 받기 없음) · 산림입지도 · 산사태위험(GL = 조각 ·
* 받은 산림 · 환경 자료(622-2) = 임도망도 · 경제림 · 생태자연도(조각만 · 받기 없음) · 산림입지도 · 산사태위험(GL = 조각 ·
* 옛 캔버스 = 지금 받기 그대로) — 꼴 · 범례 = 공용 `A00_Common/map/map_forest_layers`.
* ========================================================================== */
@@ -102,7 +102,7 @@ export interface PlanLayers {
layers: PlanMapLayer[];
/** 지형(위성 틀)을 다시 읽음 — 처음 · 지형 받은 뒤 · 받은 범위(`have` · 지형면을 다 읽은 뒤 값)가 바뀌면 켠 겹 모두 새로. */
load: (have: [number, number, number, number] | null) => Promise<void>;
/** 임도망도 올리기(왼쪽 「위치」 칸) — 받으면 그 겹을 켬 · 못 받으면 알림 + null. */
/** 임도(업로드) 올리기(왼쪽 「위치」 칸) — 받으면 그 겹을 켬 · 못 받으면 알림 + null. */
uploadRoads: (files: File[]) => Promise<PlanForestRoads | null>;
/** 올린 묶음 하나 지움(605-1) — 지도에서 바로 빠짐 · 못 지우면 알림 + 거짓. */
deleteRoad: (bundle: string) => Promise<boolean>;
@@ -285,7 +285,7 @@ export function createPlanLayers(opts: {
if (meta !== at) return info;
applyRoads(info);
showToast(
`임도망도 받음 — 범위 안 ${info.count}가닥 ${(info.length_m / 1000).toFixed(2)} km (파일 ${info.total}가닥)`,
`임도(업로드) 받음 — 범위 안 ${info.count}가닥 ${(info.length_m / 1000).toFixed(2)} km (파일 ${info.total}가닥)`,
"success",
);
return info;
@@ -302,7 +302,7 @@ export function createPlanLayers(opts: {
return await job;
} catch (error) {
showToast(
`임도망도 올리지 못함 — ${error instanceof Error ? error.message : String(error)}`,
`임도(업로드) 올리지 못함 — ${error instanceof Error ? error.message : String(error)}`,
"error",
);
return null;
@@ -339,14 +339,14 @@ export function createPlanLayers(opts: {
applyRoads(info);
showToast(
info
? `임도망도 묶음 지움 — 남은 ${info.bundles.length}묶음`
: "임도망도 묶음 지움 — 남은 것 없음",
? `임도(업로드) 묶음 지움 — 남은 ${info.bundles.length}묶음`
: "임도(업로드) 묶음 지움 — 남은 것 없음",
"success",
);
return true;
} catch (error) {
showToast(
`임도망도 묶음을 지우지 못함 — ${error instanceof Error ? error.message : String(error)}`,
`임도(업로드) 묶음을 지우지 못함 — ${error instanceof Error ? error.message : String(error)}`,
"error",
);
return false;
+1 -1
View File
@@ -463,7 +463,7 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
fitTo(around(site));
}
/** 임도망 올리기 — 지도 맞춤 = 주소지 둘레 ∪ 잘린 임도 상자(없으면 주소지). */
/** 임도(업로드) 올리기 — 지도 맞춤 = 주소지 둘레 ∪ 잘린 임도 상자(없으면 주소지). */
async function uploadRoads(files: File[]): Promise<void> {
const info = await backdrop.uploadRoads(files);
if (info) fitTo([...around(site), ...boxOfLonLat(epsg, info.bounds)]);
@@ -1,6 +1,6 @@
/* =============================================================================
* D03_PlanAnalysis_UI_RoadList.ts
* D03 「위치」 칸 올린 임도망 목록(PLAN 605-2 · 요구 476-1) — 묶음 줄(파일 이름 · 가닥 수 · 길이 · 지움) ·
* D03 「위치」 칸 임도(업로드) 묶음 목록(PLAN 605-2 · 요구 476-1) — 묶음 줄(파일 이름 · 가닥 수 · 길이 · 지움) ·
* 펼치면 선 줄 · 줄 고름 = 지도 강조 + 맞춤(페이지가 함) · 지움 = 물음 뒤 그 묶음만 · 자료는 서버 한 번 읽음.
* 부르는 곳 = `D03_PlanAnalysis_UI_Site.ts`.
* ========================================================================== */
@@ -111,7 +111,7 @@ export function createRoadList(params: RoadListParams): RoadList {
const bundles = info?.bundles ?? [];
root.hidden = !bundles.length;
if (!bundles.length) return void root.replaceChildren();
const rows: HTMLElement[] = [createText(`올린 임도망 ${bundles.length}묶음`, "muted", 12)];
const rows: HTMLElement[] = [createText(`임도(업로드) ${bundles.length}묶음`, "muted", 12)];
for (const bundle of bundles) {
const shown = open.has(bundle.id);
const fold = createIconButton({
@@ -130,7 +130,7 @@ export function createRoadList(params: RoadListParams): RoadList {
size: 24,
onClick: async () => {
const ok = await askConfirm({
message: `올린 임도망 「${bundle.name}」 묶음을 지울까요? 지도에서도 빠집니다.`,
message: `임도(업로드) 「${bundle.name}」 묶음을 지울까요? 지도에서도 빠집니다.`,
okLabel: "지움",
danger: true,
});
+5 -4
View File
@@ -2,7 +2,7 @@
* D03_PlanAnalysis_UI_Site.ts
* D03 「위치」 칸(PLAN 596-2 · 요구 463-3~6 · 19) — 지금 기준점 주소 한 줄 · 주소 · 장소 찾기
* (서버 VWorld `searchPlaces` · 부품 `createFindList` · Enter = 찾기 · ↑ ↓ Enter = 고름) ·
* 알림 한 줄(받기 실패 · 끊김 · 기준점 없음 · 못 불러옴만) + [다시 받기] · [임도망 올리기] + 올린 임도망 목록
* 알림 한 줄(받기 실패 · 끊김 · 기준점 없음 · 못 불러옴만) + [다시 받기] · [임도(업로드)] + 올린 묶음 목록
* (`D03_PlanAnalysis_UI_RoadList.ts` · 605-2).
* 고름 = 페이지가 받기 물음(멂 · 다른 TM 띠) → 기준점 캐시 → 지도 이동 → 둘레 받기 · [저장] 때 정본(등록과 한 칸).
* ========================================================================== */
@@ -24,7 +24,7 @@ export interface PlanSiteParams {
onRetry: () => void;
/** 올림(끝나면 목록을 다시 읽음). */
onUpload: (files: File[]) => Promise<unknown> | void;
/** 올린 임도망 목록(605-2) — 고름 · 지움은 페이지. */
/** 임도(업로드) 묶음 목록(605-2) — 고름 · 지움은 페이지. */
roads: RoadListParams;
}
@@ -111,7 +111,7 @@ export function createPlanSite(params: PlanSiteParams): PlanSite {
});
const roads = createRoadList(params.roads);
const upload = createButton({
label: "임도망 올리기",
label: "임도(업로드)",
variant: "ghost",
size: 28,
onClick: () =>
@@ -119,7 +119,8 @@ export function createPlanSite(params: PlanSiteParams): PlanSite {
Promise.resolve(params.onUpload(files)).then(() => roads.reload()),
),
});
upload.title = "임도망도 SHP(시군구 zip 하나 · 또는 shp · shx · dbf · prj 함께 · 100 MB 까지)";
upload.title =
"임도(업로드) — 산림청 시군구 SHP 등 지방 · 민간임도(zip 하나 · 또는 shp · shx · dbf · prj 함께 · 100 MB 까지)";
return {
parts: [address, find.root, note, retry, upload, roads.root],
render(state) {
+5 -3
View File
@@ -79,9 +79,8 @@ def test_d03_parts():
("stream", "true"),
("water", "false"),
("road", "false"),
("forestRoad", "false"),
("forestRoadFile", "false"),
("nationalRoad", "false"),
("forestRoadFile", "false"),
("cadastral", "false"),
("zones", "false"),
("soils", "false"),
@@ -103,7 +102,10 @@ def test_d03_parts():
kind_css = _src("A00_Common/map/map_draw_kind.css")
assert "createSegment({" in kind and kind.count("createInputField(") == 2
assert "columns: 2" in kind and "export function createDrawKindFields(" in kind
assert "createDrawKindFields," in draw_panel and 'from "../A00_Common/map/map_draw_kind"' in draw_panel
assert (
"createDrawKindFields," in draw_panel
and 'from "../A00_Common/map/map_draw_kind"' in draw_panel
)
assert ".map-draw-kind__fields" in kind_css and "createDrawKindFields(" in draw
assert '"길이·기울기 고정"' in kind and '"둘 다"' not in kind
assert "d03-plan__hint" not in draw # 칸 설명 글 걷음(요구 463-6)
@@ -360,7 +360,8 @@ def test_screen_defs() -> None:
)
assert "createRoadList(" in site and "roads.reload()" in site
assert len((folder / "D03_PlanAnalysis_UI_RoadList.ts").read_text("utf-8").splitlines()) <= 700
assert "임도 짐작" in legend and "올린 임도망도" in legend # 범례 구분
shared = (folder.parent / "A00_Common" / "map" / "map_legend.ts").read_text("utf-8")
assert "임도 짐작" not in shared and "임도(업로드) ${" in shared # 짐작 걷음(요구 532)
assert "이 시도 자료 없음" in legend
for name in (
"D03_PlanAnalysis_UI_Layers.ts",
@@ -40,6 +40,14 @@ def test_layer_list_five_off_by_default() -> None:
entry = re.search(rf'key: "{key}",\s*icon: "(\w+)",[^}}]*on: (\w+)', defs)
assert entry and entry.group(2) == "false", key
assert re.search(rf"^\s+{entry.group(1)}:", icons, re.M), entry.group(1)
# 630 — 임도망도 = 짐작 자리 · route 아이콘 · 짐작 겹 걷음
assert re.search(r'key: "nationalRoad",\s*icon: "route",\s*title:\s*"임도망도\(', defs)
assert 'key: "forestRoad",' not in defs
assert "item(`임도망도 ${label}`" in _read(MAP / "map_forest_layers.ts")
forest = _read(MAP / "map_forest_layers.ts")
assert '"nationalRoad-label"' in forest and "minzoom: 14" in forest # 534 줌인 글
assert '["get", "year"]' in forest and '"symbol-placement": "line"' in forest
assert "forestRoadLabelSpec()," in _read(MAP / "map_layer_specs.ts")
def test_colors_are_draw_map_tokens() -> None: