feat(509-5 · 509-2 B05 추천 지형): 분석 지형 고르기 = 추천 노선 창 안 · 지형마다 추천 미리 셈 · [적용] = 창 지형이 설계 지형 · 평면은 지형 이름만 · 설계 지형 격자 하나(수치지형도 적용이면 그 격자)

- 전처리 미리 셈이 고를 수 있는 지형마다(설계 지형 먼저 · 수치지형도는 DB 모델 있을 때만) 다섯 갈래 + 초기 종단
- 창 지형 바꿈 = 그 지형 저장본만 · 없으면 409 안내(+[재계산]) · 기본값 판정에서 지형 뺌
- POST /route/initial terrain = 창 지형 · [적용] draft.terrain = 창 지형 · 손 [저장]은 지형 안 바꿈(서버도 as_initial 일 때만)
- 평면 분석 지형 고르기 · 화면 취향(routeedit-terrain) 걷음 · 지반고 · 묶음 초안 = 설계 지형(null) · 적용 뒤 받아 둔 격자 버림
- 시험 test_509_5 · test_509_2 · 걷은 고르기를 보던 옛 시험 새 동작으로(352_3 건너뜀)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 20:41:20 +09:00
co-authored by Claude Opus 5.5
parent 23fd2f6f8d
commit 4eec3a4b37
30 changed files with 441 additions and 169 deletions
+12 -37
View File
@@ -31,11 +31,8 @@ import {
import { createContourLock } from "./B05_Profile_UI_RouteEdit_ContourLock";
import { fetchDrainageLayers } from "./B05_Profile_UI_Drainage_Parts";
import {
analysisTerrain,
fetchRoutePlan,
fetchTerrainSources,
setAnalysisTerrain,
setTerrainContext,
type AnalysisTerrain,
} from "./B05_Profile_Api_Replan";
import { bindRouteApply } from "./B05_Profile_UI_RouteEdit_Apply";
@@ -116,7 +113,7 @@ export async function mountRouteEditor(
const stationIntervalM = options.stationIntervalM ?? 20;
const chrome = createRouteEditChrome(parent, options.tools);
const { overlay, canvas, status, measureBox, measureText, measureButton } = chrome;
const { minorContourButton, terrainSelect, stationSnapButton } = chrome;
const { minorContourButton, terrainName, stationSnapButton } = chrome;
const context = canvas.getContext("2d")!;
let expected: Vertex[] = [];
@@ -161,11 +158,10 @@ export async function mountRouteEditor(
let meta: VWorldMeta | null = null;
/** 바탕 등고선 한 벌 — LAS 것이거나 도엽 것. 고르기는 `_Contour` 몫. */
let contours: RouteEditContours | null = null;
/** 지금 설계 지형(PLAN 133-2) — 고른 분석 지형이 이와 다르면 [확인]이 설계 지형을 바꾼다. */
/** 지금 설계 지형 — 편집 분석이 쓰는 지형 · 바꾸기는 추천 창 [적용](509-5). */
let designTerrain: AnalysisTerrain = "las";
/** [확인]에 실을 분석 지형 — 고르기가 안 보이면 null(설계 지형 그대로). */
const pickedTerrain = (): AnalysisTerrain | null =>
terrainSelect.closest("label")!.hidden ? null : analysisTerrain(projectId);
/** 추천 창이 고를 지형 — `/route/terrain-sources` 를 읽은 뒤. */
let terrainSources: { sources: AnalysisTerrain[]; design: AnalysisTerrain } | null = null;
/** 등고선 말고 함께 깔 도엽 레이어(하천중심선). */
let otherSheets: PreparedLayer[] = [];
/** 고른 등고선 가닥 — 없으면 -1(계획서 0-9 ⑦). */
@@ -267,6 +263,7 @@ export async function mountRouteEditor(
planned: () => planned,
planMinRadiusM: () => minRadiusM,
apply: (snapshot, message) => restoreSnapshot(snapshot, message, true),
terrains: () => terrainSources,
});
function resize(): void {
@@ -447,8 +444,8 @@ export async function mountRouteEditor(
historyControls.sync();
}
/** 계산 묶음에 알림 — 노선 · 분석 지형 · 스냅이 저장분과 다르면 초안 · 같으면 null(352-3 · 430 · 433-14 ②). */
const otherChanged = (): boolean => pickedTerrain() !== designTerrain || snap.changed();
/** 계산 묶음에 알림 — 노선 · 스냅이 저장분과 다르면 초안 · 같으면 null(430 · 433-14 ②). */
const otherChanged = (): boolean => snap.changed();
function tellDraft(): void {
if (!history) return;
const dirty = lineChanged(history) || otherChanged();
@@ -587,9 +584,7 @@ export async function mountRouteEditor(
const unbindApply = bindRouteApply({
projectId,
nodes: () => ({ planned, curveOn, curveRadius, curveLock, curveArc }),
// 분석 지형만 바꿔도 [확인]이 설계 지형을 바꿔 다시 셈한다(PLAN 133-2).
changed: () => (!!history && lineChanged(history)) || otherChanged(),
terrain: pickedTerrain,
snap: snap.draft,
initial,
onApplied,
@@ -633,21 +628,16 @@ export async function mountRouteEditor(
syncCurveBar();
if (!planned.length) planned = plannedLine.map((vertex) => [vertex[0], vertex[1]]);
meta = drainage.meta;
// 분석 지형 고르기(PLAN 133-1) — 라이다 · 수치지형도 둘 다 있을 때만 보임 · 열 때 안 씀(352-3).
// 고른 적 없으면 데이터대로(설계 지형 · 라이다가 있으면 라이다) · 사용자가 바꿀 때만 기억.
// 설계 지형 이름만(509-5) — 고르기는 추천 창 안 · 둘 다 있을 때만 이름을 보임.
designTerrain = sources?.design ?? "las";
const choosable = !!sources?.sources.includes("las") && !!sources?.sources.includes("sheet");
terrainSelect.closest("label")!.hidden = !choosable;
setTerrainContext(projectId, choosable, designTerrain);
terrainSelect.value = analysisTerrain(projectId);
terrainSources = sources;
terrainName.hidden = !(sources?.sources.includes("las") && sources.sources.includes("sheet"));
terrainName.textContent = `지형 ${designTerrain === "sheet" ? "수치지형도" : "라이다"}`;
// 등고선은 따로 고른다(LAS 우선 · 수치지형도를 고르면 도엽). 나머지 도엽 레이어(하천중심선)만 배경으로 깐다.
// 확정 모델 등고선은 설계 지형이 라이다일 때만 라이다 것 — 수치지형도 설계면 도엽 선(글도 도엽).
const loadBackground = () =>
loadRouteEditBackground(projectId, drainage, {
surfaceModelId:
analysisTerrain(projectId) === "las" && designTerrain === "las"
? (options.surfaceModelId ?? null)
: null,
surfaceModelId: designTerrain === "las" ? (options.surfaceModelId ?? null) : null,
intervalM: options.contourIntervalM ?? 1,
smooth: options.smooth ?? false,
});
@@ -670,21 +660,6 @@ export async function mountRouteEditor(
};
useBackground(await loadBackground());
if (closed) return;
// 고르면 모달 안 분석을 고른 지형으로 다시 그림 — 등고선 · 기울기 · 종단 띠 · 횡단 · 잰 값.
let terrainTicket = 0;
terrainSelect.addEventListener("change", () => {
setAnalysisTerrain(projectId, terrainSelect.value === "sheet" ? "sheet" : "las");
tellDraft(); // 노선이 그대로여도 지형이 설계와 다르면 초안으로 다시 셈(352-3).
const mine = ++terrainTicket;
void loadBackground().then((background) => {
if (closed || mine !== terrainTicket) return;
useBackground(background);
measure.clear();
slope.refresh();
showStatus();
draw();
});
});
viewWanted = true;
resize(); // 칸 크기가 서 있으면 여기서 보던 화면을 연다(아니면 처음 보일 때).
showStatus();