- UI_Map: 바탕(위성 · 등고선) + 겹(그리기 · 거리재기) · 도구줄 부품(되돌리기 · 다시하기 · 3D · 좌 · 우 회전 · 거리재기) · B05 createMapRotation · 왼쪽 끌기 이동 끔(가운데 단추 · 휠 방향 = 설정) - 새 UI_Draw: 시점에서 점 찍어 직선 · 미리보기 길이 · 기울기 · 제약 칸(자유 · 길이 고정 · 기울기 고정 · 둘 다 · 20 m · 9 %) · 종점 붙기 · 갈래 · 구역 마크 · Enter · Esc · Backspace - 새 UI_Measure: 두 점 길이 · 기울기 · 높이 차(지형 heightAt) - logic/Plan: 선 · 갈래 · 조각 꼴 다루기로 다시 씀(옛 갈래 넷 · 이은 누가 걷음) - UI_Page · Api: 지형 범위 카카오 지도 미리보기 → [이 범위 받기] · [범위 밖 더 받기] · GET · PUT plan/network · [초기화] · 조각 연도 고름 · 캐시 판 2 - UI_3D: 지형 + 그은 선(선 고름) · 예상 코리도 · 체크 칸 걷음 - 지움: UI_Cross · UI_Edit · logic/Expect · logic/Edit · test_544_1(예상 횡단 · 예상 코리도 셈이 화면에서 걷혀 지킬 대상 없음) - 시험: 530_3 = 손 긋기 꼴 다루기 + 서버 derive 대조로 다시 씀 · 553_4 D03 부품 단언 새 화면으로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
106 lines
3.6 KiB
TypeScript
106 lines
3.6 KiB
TypeScript
/* =============================================================================
|
|
* D03_PlanAnalysis_UI_Measure.ts
|
|
* D03 거리재기 겹(PLAN 562-3) — 두 점을 누르면 그 사이 길이(m) · 원지반 기울기(%) · 높이 차(m).
|
|
* 셋째 누름 = 새로 잼 · Esc = 지움. 지반고 = 받은 지형 `heightAt` · 셈 = 562-1 짝(`logic/D03_PlanAnalysis_Draw`).
|
|
* ========================================================================== */
|
|
|
|
import { groundSlopePct, segmentLength } from "./logic/D03_PlanAnalysis_Draw";
|
|
import type { Vertex } from "./logic/D03_PlanAnalysis_Plan";
|
|
import { planColors, type PlanMapLayer, type ScreenMap } from "./D03_PlanAnalysis_UI_Map";
|
|
|
|
export interface PlanMeasure {
|
|
layer: PlanMapLayer;
|
|
/** 지움(Esc · 끔). */
|
|
clear: () => void;
|
|
/** 검증 창구 — 잰 값(두 점이 다 있으면). */
|
|
result: () => { lengthM: number; slopePct: number | null; riseM: number | null } | null;
|
|
}
|
|
|
|
const fmt = (value: number, digits = 1): string => value.toFixed(digits);
|
|
const signed = (value: number, digits = 1): string =>
|
|
`${value >= 0 ? "+" : ""}${fmt(value, digits)}`;
|
|
|
|
export function createPlanMeasure(heightAt: () => (x: number, y: number) => number): PlanMeasure {
|
|
let from: Vertex | null = null;
|
|
let to: Vertex | null = null;
|
|
let fixed = false;
|
|
|
|
const measure = (a: Vertex, b: Vertex) => {
|
|
const z = heightAt();
|
|
const rise = z(b[0], b[1]) - z(a[0], a[1]);
|
|
return {
|
|
lengthM: segmentLength(a, b),
|
|
slopePct: groundSlopePct(a, b, z),
|
|
riseM: Number.isFinite(rise) ? rise : null,
|
|
};
|
|
};
|
|
|
|
const layer: PlanMapLayer = {
|
|
draw(context, map: ScreenMap) {
|
|
if (!from) return;
|
|
const colors = planColors();
|
|
const [ax, ay] = map.toScreen(from);
|
|
context.lineWidth = 2;
|
|
context.strokeStyle = colors.active;
|
|
context.fillStyle = colors.active;
|
|
context.setLineDash([6, 4]);
|
|
if (to) {
|
|
const [bx, by] = map.toScreen(to);
|
|
context.beginPath();
|
|
context.moveTo(ax, ay);
|
|
context.lineTo(bx, by);
|
|
context.stroke();
|
|
context.setLineDash([]);
|
|
for (const [x, y] of [
|
|
[ax, ay],
|
|
[bx, by],
|
|
]) {
|
|
context.beginPath();
|
|
context.arc(x, y, 4, 0, Math.PI * 2);
|
|
context.fill();
|
|
}
|
|
const { lengthM, slopePct, riseM } = measure(from, to);
|
|
const text =
|
|
`${fmt(lengthM)} m · ` +
|
|
(slopePct === null ? "지형 밖" : `${signed(slopePct)} % · Δh ${signed(riseM ?? 0)} m`);
|
|
const [mx, my] = [(ax + bx) / 2, (ay + by) / 2 - 14];
|
|
map.upright(context, mx, my, () => {
|
|
context.font = "12px system-ui, sans-serif";
|
|
context.textAlign = "center";
|
|
context.textBaseline = "middle";
|
|
context.lineWidth = 3;
|
|
context.strokeStyle = colors.halo;
|
|
context.strokeText(text, mx, my);
|
|
context.fillStyle = "#ffffff";
|
|
context.fillText(text, mx, my);
|
|
});
|
|
} else {
|
|
context.beginPath();
|
|
context.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
context.fill();
|
|
}
|
|
},
|
|
press(px, py, map, button) {
|
|
if (button !== 0) return false;
|
|
const point = map.toMetric(px, py);
|
|
if (!from || fixed) {
|
|
[from, to, fixed] = [point, null, false];
|
|
} else {
|
|
[to, fixed] = [point, true];
|
|
}
|
|
return true;
|
|
},
|
|
hover(px, py, map) {
|
|
if (from && !fixed) to = map.toMetric(px, py);
|
|
},
|
|
};
|
|
|
|
return {
|
|
layer,
|
|
clear: () => {
|
|
[from, to, fixed] = [null, null, false];
|
|
},
|
|
result: () => (from && to && fixed ? measure(from, to) : null),
|
|
};
|
|
}
|