fix(B05): 352-3 분석 지형 기본값 = 데이터대로 · 열 때 pref 쓰기 삭제 · 바꾸면 계산 묶음에 알림
- 고른 적 없으면 설계 지형(라이다가 있으면 라이다) · 사용자가 바꿀 때만 기억 - 하나뿐인 프로젝트는 있는 지형만 · 옛 값 무시 - 바꾸면 노선 초안으로 계산 묶음에 알림 · 설계 지형이 수치지형도면 확정 모델 등고선을 LAS 로 쓰지 않음 - 시험 test_352_3 추가 · 133_1 · 133_2 고쳐 씀 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VmC34fsxxbweEG3RUjs3Mn
This commit is contained in:
@@ -23,16 +23,29 @@ import type { RouteDraft } from "../A00_Common/design/logic/design_pipeline_type
|
||||
/** 편집 창 분석 지형 — las = 확정 지표면(라이다) · sheet = 수치지형도 서피스. */
|
||||
export type AnalysisTerrain = "las" | "sheet";
|
||||
|
||||
/** 고른 분석 지형 — 화면 상태 보관소 pref 통(노선 편집 고정과 같은 자리). 없으면 `fallback`
|
||||
* (모달이 열 때 설계 지형으로 채움). [확인] 때 이 값이 설계 지형이 됨(PLAN 133-2). */
|
||||
/** 편집기가 열 때 알리는 지형 사정(352-3) — 저장하지 않음. 고를 수 있나(라이다 · 수치지형도 둘) · 설계 지형. */
|
||||
const terrainContext = new Map<string, { choosable: boolean; design: AnalysisTerrain }>();
|
||||
|
||||
export const setTerrainContext = (
|
||||
projectId: string,
|
||||
choosable: boolean,
|
||||
design: AnalysisTerrain,
|
||||
): void => void terrainContext.set(projectId, { choosable, design });
|
||||
|
||||
/** 지금 쓰는 분석 지형 — 하나뿐인 프로젝트는 그것 · 둘이면 사용자가 고른 값(화면 상태 보관소 pref 통 ·
|
||||
* 노선 편집 고정과 같은 자리), 고른 적 없으면 데이터대로(= 설계 지형 · 라이다가 있으면 라이다).
|
||||
* 고른 값은 [저장] 때 설계 지형이 됨(PLAN 133-2). */
|
||||
export const analysisTerrain = (
|
||||
projectId: string,
|
||||
fallback: AnalysisTerrain = "las",
|
||||
): AnalysisTerrain => {
|
||||
const context = terrainContext.get(projectId);
|
||||
if (context && !context.choosable) return context.design;
|
||||
const saved = readState<string>("routeedit-terrain", projectId);
|
||||
return saved === "sheet" || saved === "las" ? saved : fallback;
|
||||
return saved === "sheet" || saved === "las" ? saved : (context?.design ?? fallback);
|
||||
};
|
||||
|
||||
/** 사용자가 고를 때만 기억 — 열 때 기본값은 쓰지 않음. */
|
||||
export const setAnalysisTerrain = (projectId: string, terrain: AnalysisTerrain): void =>
|
||||
writeState("routeedit-terrain", terrain, projectId);
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
fetchRoutePlan,
|
||||
fetchTerrainSources,
|
||||
setAnalysisTerrain,
|
||||
setTerrainContext,
|
||||
type AnalysisTerrain,
|
||||
} from "./B05_Profile_Api_Replan";
|
||||
import { bindRouteApply } from "./B05_Profile_UI_RouteEdit_Apply";
|
||||
@@ -184,7 +185,7 @@ export async function mountRouteEditor(
|
||||
let designTerrain: AnalysisTerrain = "las";
|
||||
/** [확인]에 실을 분석 지형 — 고르기가 안 보이면 null(설계 지형 그대로). */
|
||||
const pickedTerrain = (): AnalysisTerrain | null =>
|
||||
terrainSelect.closest("label")!.hidden ? null : analysisTerrain(projectId, designTerrain);
|
||||
terrainSelect.closest("label")!.hidden ? null : analysisTerrain(projectId);
|
||||
/** 등고선 말고 함께 깔 도엽 레이어(하천중심선). */
|
||||
let otherSheets: PreparedLayer[] = [];
|
||||
/** 고른 등고선 가닥 — 없으면 -1(계획서 0-9 ⑦). */
|
||||
@@ -624,21 +625,21 @@ export async function mountRouteEditor(
|
||||
syncCurveBar();
|
||||
if (!planned.length) planned = plannedLine.map((vertex) => [vertex[0], vertex[1]]);
|
||||
meta = drainage.meta;
|
||||
// 분석 지형 고르기(PLAN 133-1) — 라이다 · 수치지형도 둘 다 있을 때만 보임 · 기본 라이다.
|
||||
// 고른 값이 없으면 지금 설계 지형(133-2) · 못 고르는 프로젝트는 설계 지형 그대로.
|
||||
// 분석 지형 고르기(PLAN 133-1) — 라이다 · 수치지형도 둘 다 있을 때만 보임 · 열 때 안 씀(352-3).
|
||||
// 고른 적 없으면 데이터대로(설계 지형 · 라이다가 있으면 라이다) · 사용자가 바꿀 때만 기억.
|
||||
designTerrain = sources?.design ?? "las";
|
||||
const choosable = !!sources?.sources.includes("las") && !!sources?.sources.includes("sheet");
|
||||
terrainSelect.closest("label")!.hidden = !choosable;
|
||||
setAnalysisTerrain(
|
||||
projectId,
|
||||
choosable ? analysisTerrain(projectId, designTerrain) : designTerrain,
|
||||
);
|
||||
setTerrainContext(projectId, choosable, designTerrain);
|
||||
terrainSelect.value = analysisTerrain(projectId);
|
||||
// 등고선은 따로 고른다(LAS 우선 · 수치지형도를 고르면 도엽). 나머지 도엽 레이어(하천중심선)만 배경으로 깐다.
|
||||
// 확정 모델 등고선은 설계 지형이 라이다일 때만 라이다 것 — 수치지형도 설계면 도엽 선(글도 도엽).
|
||||
const loadBackground = () =>
|
||||
loadRouteEditBackground(projectId, drainage, {
|
||||
surfaceModelId:
|
||||
analysisTerrain(projectId) === "sheet" ? null : (options.surfaceModelId ?? null),
|
||||
analysisTerrain(projectId) === "las" && designTerrain === "las"
|
||||
? (options.surfaceModelId ?? null)
|
||||
: null,
|
||||
intervalM: options.contourIntervalM ?? 1,
|
||||
smooth: options.smooth ?? false,
|
||||
});
|
||||
@@ -665,6 +666,9 @@ export async function mountRouteEditor(
|
||||
let terrainTicket = 0;
|
||||
terrainSelect.addEventListener("change", () => {
|
||||
setAnalysisTerrain(projectId, terrainSelect.value === "sheet" ? "sheet" : "las");
|
||||
// 계산 묶음에 알림 — 노선이 그대로여도 지형이 설계와 다르면 초안으로 다시 셈(352-3).
|
||||
const dirty = history?.isDirty() || pickedTerrain() !== designTerrain;
|
||||
options.draft?.edited(dirty ? draftLink.draftOfSnapshot(snapshotNow(), stationSnap) : null);
|
||||
const mine = ++terrainTicket;
|
||||
void loadBackground().then((background) => {
|
||||
if (closed || mine !== terrainTicket) return;
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/* PLAN 352-3 단위검증 헬퍼 — 분석 지형 기본값(고른 적 없으면 데이터대로) · 사용자가 바꿀 때만 기억.
|
||||
* 진짜 `B05_Profile_Api_Replan.ts` 를 트랜스파일해 Node 로 돌림. 화면 상태 보관소만 가짜(쓰기 횟수 셈).
|
||||
* 결과 = 표준출력 JSON 한 벌. 짝 = `test_352_3_analysis_terrain_default.py`. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const store = new Map();
|
||||
let writes = 0;
|
||||
const STUBS = {
|
||||
config_frontend: { API_BASE_URL: "" },
|
||||
b_page_state: {
|
||||
readState: (key, scope) => store.get(`${key}|${scope}`) ?? null,
|
||||
writeState: (key, value, scope) => (writes++, store.set(`${key}|${scope}`, value)),
|
||||
},
|
||||
common_util_surface_sampler: { heightAt: () => 0 },
|
||||
B05_Profile_Api_TerrainGrid: { loadDesignTerrainGrid: async () => null },
|
||||
};
|
||||
const cache = new Map();
|
||||
function loadTs(full) {
|
||||
if (cache.has(full)) return cache.get(full);
|
||||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) => {
|
||||
const key = Object.keys(STUBS).find((name) => request.endsWith(name));
|
||||
if (key) return STUBS[key];
|
||||
return require(request);
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
return box.exports;
|
||||
}
|
||||
const api = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_Api_Replan.ts"));
|
||||
|
||||
const out = {};
|
||||
// ① 사정을 모를 때 — 라이다
|
||||
out.unknown = api.analysisTerrain("p0");
|
||||
// ② 둘 다 있고 고른 적 없음 — 데이터대로(설계 라이다 → 라이다) · 쓰기 0
|
||||
api.setTerrainContext("p1", true, "las");
|
||||
out.bothNoPick = api.analysisTerrain("p1");
|
||||
out.writesAfterOpen = writes;
|
||||
// ③ 설계가 수치지형도인 프로젝트 · 고른 적 없음 — 설계대로(고르기 글 = 실제 쓰는 지형)
|
||||
api.setTerrainContext("p2", true, "sheet");
|
||||
out.sheetDesignNoPick = api.analysisTerrain("p2");
|
||||
// ④ 사용자가 바꿈 — 그때만 기억 · 다음 접근(사정 다시 알림)에도 남음
|
||||
api.setAnalysisTerrain("p1", "sheet");
|
||||
out.afterPick = api.analysisTerrain("p1");
|
||||
out.writesAfterPick = writes;
|
||||
api.setTerrainContext("p1", true, "las");
|
||||
out.nextAccess = api.analysisTerrain("p1");
|
||||
// ⑤ 하나뿐인 프로젝트 — 옛 값이 남아 있어도 있는 것만 씀
|
||||
store.set("routeedit-terrain|p3", "las");
|
||||
api.setTerrainContext("p3", false, "sheet");
|
||||
out.sheetOnlyStale = api.analysisTerrain("p3");
|
||||
store.set("routeedit-terrain|p4", "sheet");
|
||||
api.setTerrainContext("p4", false, "las");
|
||||
out.lasOnlyStale = api.analysisTerrain("p4");
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -166,7 +166,7 @@ def test_화면_고르기_배선():
|
||||
assert '<option value="las">라이다</option>' in chrome
|
||||
assert '<option value="sheet">수치지형도</option>' in chrome
|
||||
assert 'sources?.sources.includes("las") && !!sources?.sources.includes("sheet")' in main
|
||||
assert 'saved === "sheet" || saved === "las" ? saved : fallback' in api
|
||||
assert 'saved === "sheet" || saved === "las" ? saved : (context?.design ?? fallback)' in api
|
||||
# 모달 분석 셋(종단 띠는 345-5 에서 없앰)이 고른 지형을 실어 보냄 · [확인] 재계산은 안 실음
|
||||
assert api.count("terrain: analysisTerrain(projectId)") == 2
|
||||
assert "terrain: analysisTerrain(projectId)" in initial_api
|
||||
|
||||
@@ -294,4 +294,6 @@ def test_화면_확인이_고른_지형을_실음():
|
||||
# 346-3 — 노선 초안(draft.terrain)에 실림 · [저장]이 보냄
|
||||
assert "terrain: params.terrain() })" in apply and "terrain: settings.terrain," in apply
|
||||
assert "pickedTerrain() !== designTerrain" in main # 지형만 바꿔도 [확인]이 다시 셈
|
||||
assert "analysisTerrain(projectId, designTerrain)" in main # 고른 값 없으면 설계 지형
|
||||
assert (
|
||||
"setTerrainContext(projectId, choosable, designTerrain)" in main
|
||||
) # 고른 값 없으면 설계 지형
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"""352-3 분석 지형 기본값 — 고른 적 없으면 데이터대로 · 열 때 안 씀 · 사용자가 바꿀 때만 기억 · 바꾸면 묶음에 알림.
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_352_3_analysis_terrain_default.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def out() -> dict:
|
||||
run = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", str(HERE / "helper_352_3_terrain.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
assert run.returncode == 0, run.stderr
|
||||
return json.loads(run.stdout)
|
||||
|
||||
|
||||
def test_고른_적_없으면_데이터대로_열_때_안_씀(out) -> None:
|
||||
assert out["unknown"] == "las"
|
||||
assert out["bothNoPick"] == "las" # 라이다가 있는 프로젝트 = 라이다
|
||||
assert out["writesAfterOpen"] == 0 # 열 때 pref 에 쓰지 않음
|
||||
assert out["sheetDesignNoPick"] == "sheet" # 설계 지형대로 — 고르기 글 = 실제 쓰는 지형
|
||||
|
||||
|
||||
def test_사용자가_바꿀_때만_기억(out) -> None:
|
||||
assert out["afterPick"] == "sheet" and out["writesAfterPick"] == 1
|
||||
assert out["nextAccess"] == "sheet" # 다음 접근에도
|
||||
|
||||
|
||||
def test_하나뿐이면_있는_것만(out) -> None:
|
||||
assert out["sheetOnlyStale"] == "sheet"
|
||||
assert out["lasOnlyStale"] == "las"
|
||||
|
||||
|
||||
def test_편집기_배선() -> None:
|
||||
main = (ROOT / "B05_Profile/B05_Profile_UI_RouteEdit.ts").read_text(encoding="utf-8")
|
||||
assert main.count("setAnalysisTerrain(") == 1 # 바꿀 때 한 곳뿐 — 열 때 쓰기 없음
|
||||
handler = main.split('terrainSelect.addEventListener("change"', 1)[1][:900]
|
||||
assert "setAnalysisTerrain(projectId" in handler
|
||||
assert "options.draft?.edited(" in handler # 바꾸면 계산 묶음에 알림
|
||||
# 설계 지형이 수치지형도면 확정 모델(수치지형도 서피스) 등고선을 「LAS」로 쓰지 않음
|
||||
assert 'analysisTerrain(projectId) === "las" && designTerrain === "las"' in main
|
||||
Reference in New Issue
Block a user