diff --git a/B05_Profile/logic/B05_Profile_Pipeline_RouteGround.ts b/B05_Profile/logic/B05_Profile_Pipeline_RouteGround.ts index 47ec5b627..b9e3d0d88 100644 --- a/B05_Profile/logic/B05_Profile_Pipeline_RouteGround.ts +++ b/B05_Profile/logic/B05_Profile_Pipeline_RouteGround.ts @@ -13,6 +13,7 @@ * ========================================================================== */ import type { + DesignCurve, GroundOutput, GroundSample, PipelineStation, @@ -38,35 +39,43 @@ import { export interface RouteStageDeps { /** 곡선 기본 반지름(m) — 편집기 `minRadiusM`(계획 기준 · 0 = 없음). */ minRadiusM(): number; - /** 초안이 없을 때(= 저장분 그대로) 그릴 노선 — 없으면 null. */ - savedLine(): Vertex[] | null; + /** 초안이 없을 때(= 저장분 그대로) 그릴 노선 · 곡선표 — 없으면 null. */ + savedLine(): { line: Vertex[]; curves: DesignCurve[] } | null; } /** 정점마다 누가거리 — 첫 점 0. */ -export function routeLineOf(line: Vertex[]): RouteLine { +export function routeLineOf(line: Vertex[], curves: DesignCurve[] = []): RouteLine { const chainage = [0]; for (let i = 1; i < line.length; i += 1) { chainage.push( chainage[i - 1] + Math.hypot(line[i][0] - line[i - 1][0], line[i][1] - line[i - 1][1]), ); } - return { line, chainage, lengthM: chainage[chainage.length - 1] }; + return { line, curves, chainage, lengthM: chainage[chainage.length - 1] }; } -/** 초안 → 그린 노선. 길이를 붙든 곡선은 교각에 맞춰 R 을 다시 잡음(편집기 `markEdited` 와 같은 차례). */ -export function draftLine(draft: RouteDraft, minRadiusM: number): Vertex[] { +/** 초안 → 그린 노선 · 곡선표(계약 꼴). 길이를 붙든 곡선은 교각에 맞춰 R 을 다시 잡음(편집기 + * `markEdited` 와 같은 차례). */ +export function draftLine( + draft: RouteDraft, + minRadiusM: number, +): { line: Vertex[]; curves: DesignCurve[] } { const nodes = draft.nodes.map((node): Vertex => [node[0], node[1]]); const radius = [...draft.curveRadius]; applyArcLocks(nodes, [...draft.curveLock], [...draft.curveArc], radius); - return buildEditedPolyline(nodes, draft.curveOn, radius, minRadiusM).vertices; + const built = buildEditedPolyline(nodes, draft.curveOn, radius, minRadiusM); + return { + line: built.vertices, + curves: built.curves.map(({ start, apex, end, radius_m }) => ({ start, apex, end, radius_m })), + }; } export function createRouteStage(deps: RouteStageDeps): StageFn<"route"> { return (ctx) => { const draft = ctx.inputs.route; - const line = draft ? draftLine(draft, deps.minRadiusM()) : deps.savedLine(); - if (!line || line.length < 2) throw new Error("그릴 노선이 없습니다."); - return routeLineOf(line); + const drawn = draft ? draftLine(draft, deps.minRadiusM()) : deps.savedLine(); + if (!drawn || drawn.line.length < 2) throw new Error("그릴 노선이 없습니다."); + return routeLineOf(drawn.line, drawn.curves); }; } diff --git a/resources/tester/test_346_1_route_ground_stage.py b/resources/tester/test_346_1_route_ground_stage.py index 9f25a8298..0c2c3f8e2 100644 --- a/resources/tester/test_346_1_route_ground_stage.py +++ b/resources/tester/test_346_1_route_ground_stage.py @@ -49,7 +49,9 @@ const draft = { rev: 1, nodes: job.nodes, curveOn: Array(n).fill(true), curveRadius: Array(n).fill(null), curveLock: Array(n).fill(null), curveArc: Array(n).fill(null), stationSnap: false, terrain: null, }; -const routeStage = createRouteStage({ minRadiusM: () => job.minRadius, savedLine: () => job.line }); +const routeStage = createRouteStage({ + minRadiusM: () => job.minRadius, savedLine: () => ({ line: job.line, curves: [] }), +}); const ctxOf = (extra) => ({ generation: 1, range: null, signal: new AbortController().signal, inputs: { route: null, alignment: null, cross: null, pipes: null, structures: null }, @@ -242,7 +244,14 @@ def test_route_ground_단계가_서버와_같고_범위_밖을_재사용한다(t steps = np.hypot(*np.diff(np.asarray(built.vertices), axis=0).T) assert abs(route["lengthM"] - float(steps.sum())) < 1e-6 assert route["chainage"][0] == 0 and len(route["chainage"]) == len(route["line"]) + assert len(route["curves"]) == len(built.curves) > 0 + for ts, py in zip(route["curves"], built.curves, strict=True): + assert set(ts) == {"start", "apex", "end", "radius_m"} + assert abs(ts["radius_m"] - py.radius_m) < 1e-9 + for key in ("start", "apex", "end"): + assert max(abs(a - b) for a, b in zip(ts[key], getattr(py, key))) < 1e-9 assert out["saved"]["line"] == line + assert out["saved"]["curves"] == [] assert out["noRoute"] # ground 브라우저 길 = 서버 generate_sections(측점 · 횡단 · 종단)