feat(D03 겹): 노선분석 지도 산림입지도 겹 — 브이월드 WFS 토양형 · 묶음 셋째 · 처음 끔 (PLAN 581-2 · 요구 440-8)

- 서버: lt_c_fsdifrsts 받은 범위로 잘라 토양형 코드 · 이름 · 넓이 · 토양형별 합 · 덮으면 다시 안 받음 · 실패 = 502
- 화면: 단추 하나(잎 아이콘 · 부품 상자에 더함) · 토양형 색 --draw-map-soil-1~6 · 범례 = 토양형 이름 · ha · 바탕 캔버스
- 산사태위험지도(생활안전지도 = 공공누리 4유형 · 데이터 준비 중) · 단층 · 지질구조선(이용 조건 문서 없음)은 넣지 않음(브레인 결정 A)
- test_553_4: 겹 목록에 soils 더함(새 겹 · 기대 목록 갱신)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-29 15:15:10 +09:00
co-authored by Claude Opus 5.5
parent 63a9f16d32
commit f29fb01dd5
9 changed files with 272 additions and 16 deletions
+45 -3
View File
@@ -1,14 +1,14 @@
/* =============================================================================
* D03_PlanAnalysis_UI_Layers.ts
* D03 평면 바탕 겹 켜고 끔(PLAN 569-2 · 581-1 · 요구 395-2 · 440-8 · 442) — 지도 왼쪽 위 아이콘 한 줄
* D03 평면 바탕 겹 켜고 끔(PLAN 569-2 · 581-1 · 581-2 · 요구 395-2 · 440-8 · 442) — 지도 왼쪽 위 아이콘 한 줄
* (공용 지도 도구줄 `createMapTools` · 넘치면 가로 밀기 571-1 · 오른쪽 평면 도구줄과 따로).
*
* 겹 = 바탕(위성 · 음영기복 · 도엽 등고선(계곡선 25 m 굵게 · 수치) · 급경사 색) ·
* 물 · 길(세류선 · 하천 · 물 면(실폭하천 · 저수지) · 기존 도로 · 기존 임도 짐작(소로 · 미분류)) ·
* 구역(지적도 · 보호 · 규제 구역 여덟 종 · 계류 100 m · 도로 300 m 띠 · 건물 · 지명).
* 구역(지적도 · 보호 · 규제 구역 여덟 종 · 산림입지도(토양형) · 계류 100 m · 도로 300 m 띠 · 건물 · 지명).
* 처음 켬 = 위성 · 등고선 · 세류 · 고른 값 = 화면 취향(`aislo:pref:d03.layers`) 기억.
* 받기 = 켤 때만(한 번 받으면 다시 안 받음) · 지형을 안 받았으면 단추를 막음 · 받기 실패 = 그 겹만 꺼짐 + 알림.
* 그리기 차례(아래 → 위) = 위성(`scene.image`) → 음영기복 → 급경사 → 보호구역 → 띠 → 지적 → 물 면 →
* 그리기 차례(아래 → 위) = 위성(`scene.image`) → 음영기복 → 급경사 → 산림입지도 → 보호구역 → 띠 → 지적 → 물 면 →
* 건물 → 등고선 → 도로 → 임도 짐작 → 세류 → 지명 → 그은 선.
* 겹 모두 = 바탕 캔버스(`base` · 582) · 등고선 = 줌 단계 색인 · 글 폭 기억.
* 범례 = 켠 겹의 받은 범위 안 수 · 넓이(왼쪽 아래).
@@ -44,15 +44,18 @@ import {
fetchPlanCadastral,
fetchPlanRaster,
fetchPlanSheetLayer,
fetchPlanSoils,
fetchPlanZones,
type PlanBand,
type PlanRaster,
type PlanSoils,
type PlanZones,
} from "./D03_PlanAnalysis_Api";
import {
FOREST_ROAD_KINDS,
GROUPS,
LAYERS,
SOIL_COLORS,
WATER_KEYS,
ZONE_COLORS,
fillLayer,
@@ -76,6 +79,8 @@ interface Prepared {
hillshade: { raster: PlanRaster; image: HTMLImageElement };
water: PreparedLayer;
zones: { kinds: Map<string, PreparedLayer>; summary: PlanZones["summary"] };
/** 토양형(코드 차례) → 지물 · 색 값. */
soils: { kinds: Array<{ layer: PreparedLayer; color: string }>; summary: PlanSoils["summary"] };
bands: { stream: PreparedLayer; road: PreparedLayer; info: { stream: PlanBand; road: PlanBand } };
building: PreparedLayer;
place: Array<{ lon: number; lat: number; name: string }>;
@@ -191,6 +196,17 @@ export function createPlanLayers(opts: {
: [{ label: "보호 · 규제 구역 없음(받은 범위 안)", color: "transparent", off: true }],
);
}
if (on.soils && data.soils) {
const { kinds, summary } = data.soils;
rows.push(
summary.length
? summary.map((soil, i) => ({
label: `${soil.name} ${hectare(soil.area_m2)}`,
color: `var(${kinds[i].color})`,
}))
: [{ label: "산림입지도 없음(받은 범위 안)", color: "transparent", off: true }],
);
}
if (on.bands && data.bands) {
const { stream, road } = data.bands.info;
rows.push([
@@ -352,6 +368,15 @@ export function createPlanLayers(opts: {
`보호 · 규제 구역 일부 받지 못함 — ${failed.map((kind) => kind.label).join(" · ")}`,
"error",
);
} else if (key === "soils") {
const soils = await fetchPlanSoils(projectId);
data.soils = {
kinds: soils.summary.map((soil, i) => ({
layer: prepare(pick(soils, (props) => props["code"] === soil.code)),
color: SOIL_COLORS[i % SOIL_COLORS.length],
})),
summary: soils.summary,
};
} else if (key === "bands") {
const info = await fetchPlanBands(projectId);
data.bands = {
@@ -418,6 +443,16 @@ export function createPlanLayers(opts: {
drawRaster(context, map, data.slope);
},
},
{
base: true,
draw: (context, map) => {
if (!on.soils || !data.soils) return;
for (const { layer, color } of data.soils.kinds) {
const fill = themeColor(color, "#facc15");
fillLayer(context, layer, map.view, fill, 0.35, { color: fill, width: 1.4 });
}
},
},
{
base: true,
draw: (context, map) => {
@@ -582,6 +617,13 @@ export function createPlanLayers(opts: {
[`zone_${kind}_m2`, value.area_m2],
]),
)),
...(data.soils &&
Object.fromEntries(
data.soils.summary.flatMap((soil) => [
[`soil_${soil.code}`, soil.count],
[`soil_${soil.code}_m2`, soil.area_m2],
]),
)),
...(data.bands && {
band_stream_m2: data.bands.info.stream.area_m2,
band_road_m2: data.bands.info.road.area_m2,