feat(D03): 노선분석 지도 바탕 겹 켜고 끔 — 위성 · 등고선(계곡선 굵게) · 세류 · 지적 · 도로 · 급경사 (PLAN 569-2 · 요구 395-2)

- 새 D03_PlanAnalysis_UI_Layers.ts — 왼쪽 위 겹 단추 한 줄 · 처음 켬 = 위성 · 등고선 · 세류 · 고른 값 aislo:pref:d03.layers 기억 · 켤 때만 받음 · 급경사 범례
- UI_Map — 등고선 그리기를 겹으로 옮김 · ScreenMap.view 더함
- Api — 569-1 서버 겹 세 길(지적 · 도엽 겹 · 급경사 그림) 부름
- theme.css 그림 색 일곱(지도 겹 넷 · 급경사 셋) · test_551_2 그림 값 개수 17 → 24(새 값 일곱)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 07:01:36 +09:00
co-authored by Claude Opus 5.5
parent 7bc33ded6e
commit 6cec62c75b
7 changed files with 412 additions and 77 deletions
+6 -33
View File
@@ -1,10 +1,10 @@
/* =============================================================================
* D03_PlanAnalysis_UI_Map.ts
* D03 메인 평면(PLAN 530-3 · 562-3) — 위성 + 도엽 등고선 바탕 · 위에 겹(그리기 · 거리재기) · 도구줄 여섯
* D03 메인 평면(PLAN 530-3 · 562-3) — 위성 바탕 · 위에 겹(바탕 겹 569-2 · 그리기 · 거리재기) · 도구줄 여섯
* (되돌리기 · 다시하기 · 3D · 좌회전 · 우회전 · 거리재기).
*
* 셈 · 그리기는 B 부품 그대로(import 만) — 좌표 `createMetricProjector` · `screenToMetric` · 맞춤
* `computeRouteView` · 등고선 `drawPreparedLayer` · 휠 확대 · 가운데 단추 옮기기 `bindRouteEditNavigation` ·
* `computeRouteView` · 휠 확대 · 가운데 단추 옮기기 `bindRouteEditNavigation` ·
* 회전 `createMapRotation`(B05 그대로 · 단추 `[data-act="rotate-ccw|cw"]` · Ctrl+휠).
* 왼쪽 단추 = 겹 몫(점 찍기 · 마크 · 재기) — 왼쪽 끌기 옮기기 없음(설정 창 규칙 = 가운데 단추 옮김 · 휠 방향).
* 겹은 돌리기 전 그림 좌표로 그리고 받음 — 누른 자리는 `unrotate` 로 되돌려 넘김.
@@ -18,12 +18,8 @@ import {
computeMapRect,
computeRouteView,
createMetricProjector,
drawPreparedLabels,
drawPreparedLayer,
normalizedToScreen,
pickLevelStep,
routeLineColor,
type PreparedLayer,
type ViewState,
} from "../B04_PreProcess/B04_PreProcess_UI_MapRender";
import { bindRouteEditNavigation } from "../B05_Profile/B05_Profile_UI_RouteEdit_Input";
@@ -49,7 +45,6 @@ const FAKE_MARGIN_M = 600;
export interface PlanMapScene {
meta: VWorldMeta | null;
image: HTMLImageElement | null;
contours: PreparedLayer | null;
/** 처음 맞출 점(시점 · 종점 · 그은 선). */
fit: Vertex[];
}
@@ -59,6 +54,8 @@ export interface ScreenMap {
toScreen: (point: Vertex) => [number, number];
toMetric: (px: number, py: number) => Vertex;
pxPerMeter: number;
/** B04 그리기 틀(`drawPreparedLayer` 등이 받음). */
view: ViewState;
/** 글자를 세울 각(라디안 · 안 돌렸으면 0). */
uprightRad: number;
/** (x, y) 둘레로 글자를 눈높이로 세워 그림(돌린 지도). */
@@ -165,7 +162,7 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
toolbar.classList.add("d03-plan__tools");
root.append(canvas, toolbar);
const context = canvas.getContext("2d")!;
let scene: PlanMapScene = { meta: null, image: null, contours: null, fit: [] };
let scene: PlanMapScene = { meta: null, image: null, fit: [] };
let layers: PlanMapLayer[] = [];
let meta: VWorldMeta | null = null;
let fitted = false;
@@ -189,6 +186,7 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
toScreen: (point) => projector.toScreen(point[0], point[1]),
toMetric: (px, py) => screenToMetric(meta!, view, px, py),
pxPerMeter: projector.pxPerMeter,
view,
uprightRad: rotation.uprightRad(),
upright: (ctx, x, y, paint) => {
const angle = rotation.uprightRad();
@@ -234,31 +232,6 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
const [x1, y1] = normalizedToScreen(view, 1, 1);
context.drawImage(scene.image, x0, y0, x1 - x0, y1 - y0);
}
if (scene.contours) {
context.save();
context.strokeStyle = "rgba(255, 214, 140, 0.7)";
context.lineWidth = 0.8;
drawPreparedLayer(
context,
scene.contours,
view,
"dot",
pickLevelStep(scene.contours, view, 5),
);
context.font = "10px system-ui, sans-serif";
context.textAlign = "center";
context.textBaseline = "middle";
drawPreparedLabels(
context,
scene.contours,
view,
"#fde68a",
25,
rotation.uprightRad(),
"rgba(0, 0, 0, 0.6)",
);
context.restore();
}
const map = screenMap();
for (const layer of layers) {
context.save();