Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
|
||||
from collections import Counter
|
||||
from collections.abc import Callable
|
||||
from dataclasses import replace
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
@@ -367,11 +368,13 @@ def design_pipe_anchored_profile(
|
||||
# 높이로, 정본 산식은 `common_util_drainage_pipes.facility_clearance_m`이다.
|
||||
# 시·종점만 오프셋을 얹는다.
|
||||
target = np.interp(base_s, chainage, ground)
|
||||
lifted = np.zeros(len(base_s), dtype=bool)
|
||||
if pipe_clearances:
|
||||
for index, value in enumerate(base_s):
|
||||
clearance = _clearance_at(pipe_clearances, float(value))
|
||||
if clearance > 0:
|
||||
target[index] += clearance
|
||||
lifted[index] = True
|
||||
target[0] = fixed[0]
|
||||
target[-1] = fixed[1]
|
||||
|
||||
@@ -380,12 +383,15 @@ def design_pipe_anchored_profile(
|
||||
edits = dict(edits or {})
|
||||
user_radii = dict(edits.get("curve_radii") or {})
|
||||
|
||||
def radii_for(nodes_z: np.ndarray) -> dict[str, float]:
|
||||
"""현 표고에서의 대수차로 R = L / A 를 역산한다(사용자 지정 R이 있으면 그쪽이 이긴다)."""
|
||||
def radii_for(nodes_z: np.ndarray, held: np.ndarray) -> dict[str, float]:
|
||||
"""현 표고에서의 대수차로 R = L / A 를 역산한다(사용자 지정 R이 있으면 그쪽이 이긴다) ·
|
||||
직선 위로 옮긴(`held`) 꼭짓점은 R 없음."""
|
||||
spans = base_s[1:] - base_s[:-1]
|
||||
grades = (nodes_z[1:] - nodes_z[:-1]) / np.where(spans > 0, spans, 1.0)
|
||||
resolved: dict[str, float] = {}
|
||||
for index in range(1, len(base_s) - 1):
|
||||
if held[index]:
|
||||
continue
|
||||
delta = abs(float(grades[index] - grades[index - 1]))
|
||||
radius = target_length / delta if delta > 1e-9 else min_radius
|
||||
resolved[chainage_key(base_s[index])] = max(radius, min_radius)
|
||||
@@ -394,19 +400,28 @@ def design_pipe_anchored_profile(
|
||||
# 대칭 종단곡선은 꼭짓점(변화점)을 지나지 않는다 — 곡선이 배관 지반고를 통과하도록
|
||||
# 변화점 표고를 반복 보정한다(호가 안쪽으로 파고드는 중앙종거만큼 꼭짓점을 밀어낸다).
|
||||
# 표고가 움직이면 대수차도 변하므로 R도 매 회 다시 역산한다.
|
||||
base_z = target.copy()
|
||||
radii = radii_for(base_z)
|
||||
for _ in range(12):
|
||||
radii = radii_for(base_z)
|
||||
curves, _curve_warnings = build_curves(base_s, base_z, policy, radii)
|
||||
plan_at = evaluate(base_s, base_z, curves, base_s)
|
||||
error = target - plan_at
|
||||
error[0] = 0.0
|
||||
error[-1] = 0.0
|
||||
if float(np.max(np.abs(error))) < 1e-4:
|
||||
# 초기값 직선화(438 · 433-13②) — 기본 L 기준 R > 500 m 관 꼭짓점 묶음은 앞뒤 꺾임을 잇는
|
||||
# 직선 위(높이 한도 없음 · 요구 269-2) · 경계가 새로 R > 500 m 가 되면 묶음을 늘려 다시 ·
|
||||
# 시설 여유로 들린 관 꼭짓점은 그대로(직선화 경계).
|
||||
held = np.zeros(len(base_s), dtype=bool)
|
||||
for _ in range(len(base_s)):
|
||||
base_z = straighten_held(base_s, target, held)
|
||||
for _ in range(12):
|
||||
radii = radii_for(base_z, held)
|
||||
curves, _curve_warnings = build_curves(base_s, base_z, policy, radii)
|
||||
plan_at = evaluate(base_s, base_z, curves, base_s)
|
||||
error = target - plan_at
|
||||
error[0] = 0.0
|
||||
error[-1] = 0.0
|
||||
error[held] = 0.0
|
||||
if float(np.max(np.abs(error))) < 1e-4:
|
||||
break
|
||||
base_z = straighten_held(base_s, base_z + error, held)
|
||||
flat = flat_bends(base_s, base_z, policy.default_curve_length_m) & ~held & ~lifted
|
||||
if not flat.any():
|
||||
break
|
||||
base_z = base_z + error
|
||||
edits["curve_radii"] = radii
|
||||
held = held | flat
|
||||
edits["curve_radii"] = radii_for(base_z, held)
|
||||
|
||||
alignment = build_alignment(
|
||||
base_s=base_s,
|
||||
@@ -498,6 +513,14 @@ def design_alignment_profile(
|
||||
down_limit,
|
||||
balance_tolerance_percent=policy.balance_tolerance_percent,
|
||||
)
|
||||
# 초기값 직선화(438 · 433-13②) — 기본 L 기준 R > 500 m 꼭짓점 묶음은 앞뒤 꺾임을 잇는 직선 위
|
||||
held = np.zeros(len(base_s), dtype=bool)
|
||||
flat = flat_bends(base_s, base_z, policy.default_curve_length_m)
|
||||
while flat.any():
|
||||
held = held | flat
|
||||
straight = straighten_held(base_s, base_z, held)
|
||||
flat = flat_bends(base_s, straight, policy.default_curve_length_m) & ~held
|
||||
base_z = straighten_held(base_s, base_z, held)
|
||||
alignment = build_alignment(
|
||||
base_s=base_s,
|
||||
base_z=base_z,
|
||||
@@ -531,6 +554,16 @@ def rebuild_alignment_profile(
|
||||
policy = AlignmentPolicy.from_dict(stored.get("policy") or {})
|
||||
|
||||
previous = (longitudinal.get("design_profiles") or [{}])[0]
|
||||
criteria = previous.get("criteria") or {}
|
||||
# 옛 정책엔 법정 최소 R · L 이 없음 — 첫 계획선 설계 기준(criteria = 설계속도 법정값)으로
|
||||
# 채움(433-13 · TS `readAlignment` 짝)
|
||||
policy = replace(
|
||||
policy,
|
||||
min_vertical_radius_m=policy.min_vertical_radius_m
|
||||
or float(criteria.get("min_vertical_radius_m") or 0.0),
|
||||
min_curve_length_m=policy.min_curve_length_m
|
||||
or float(criteria.get("min_curve_length_m") or 0.0),
|
||||
)
|
||||
alignment = build_alignment(
|
||||
base_s=base_s,
|
||||
base_z=base_z,
|
||||
@@ -540,7 +573,6 @@ def rebuild_alignment_profile(
|
||||
policy=policy,
|
||||
edits=edits,
|
||||
)
|
||||
criteria = previous.get("criteria") or {}
|
||||
options = GradeDesignOptions(
|
||||
max_grade_pct=float(criteria.get("max_grade_pct") or policy.max_grade_pct),
|
||||
max_reverse_grade_pct=float(criteria.get("max_reverse_grade_pct") or 5.0),
|
||||
|
||||
@@ -213,14 +213,21 @@ async def sync_uphill_overrides_into_designs(
|
||||
_SAME_STATION_M = 0.006
|
||||
#: 지반 종단으로 볼 꼭짓점 자리 계획선 · 지반 어긋남 가운데값(m) — 새 꼭짓점을 지반에 세울지(438).
|
||||
_SEAT_GROUND_M = 0.01
|
||||
#: 새 꼭짓점 이웃이 옮기기 전 계획고에서 뜨면 맞출 한도(m · 433-5).
|
||||
_SEAT_KEEP_M = 0.001
|
||||
|
||||
|
||||
def _seat_born_nodes(
|
||||
stored: dict[str, Any], base: list[dict[str, Any]], born: list[float]
|
||||
stored: dict[str, Any],
|
||||
base: list[dict[str, Any]],
|
||||
born: list[float],
|
||||
held: list[float] = (),
|
||||
) -> list[dict[str, Any]]:
|
||||
"""새 측점(`born`)에 자동 계획선 규칙(원지반 지남 · `pass_through`)으로 꼭짓점을 세움(427).
|
||||
|
||||
다른 꼭짓점 높이는 그대로 · 양 끝 밖 · 이미 꼭짓점 있는(0.006m 안) 자리는 건너뜀. 지반 = 저장
|
||||
이웃 꼭짓점은 곡선 모양이 바뀌어 옮기기 전 계획고에서 1mm 넘게 뜨면 그 높이로 맞춤(433-5 ·
|
||||
바로 앞뒤부터 번지는 만큼) · 높이 고친 꼭짓점(`held` = 옮긴 뒤 `station_offsets` 측점)과 그
|
||||
밖 꼭짓점은 그대로 · 양 끝 밖 · 이미 꼭짓점 있는(0.006m 안) 자리는 건너뜀. 지반 = 저장
|
||||
선형 표본(`samples`) · 곡선 = 편집 없이(`pass_through` 와 같음). 옮기기 전 계획선이 꼭짓점
|
||||
대부분에서 지반을 안 지나면(균형 종단) 지반 대신 옮기기 전 계획선 높이(438). TS 짝 =
|
||||
`seatBornNodes`(B05_Profile/logic/B05_Profile_Grade_Seat.ts) · 거울 시험 =
|
||||
@@ -258,20 +265,41 @@ def _seat_born_nodes(
|
||||
key=lambda row: row[0],
|
||||
)
|
||||
base_s = np.array([row[0] for row in rows], dtype=np.float64)
|
||||
target = np.array([row[1] for row in rows], dtype=np.float64)
|
||||
free = np.array([row[2] is None for row in rows])
|
||||
base_z = target.copy()
|
||||
for _ in range(20):
|
||||
born_rows = np.array([row[2] is None for row in rows])
|
||||
# 다른 꼭짓점 목표 = 옮기기 전 계획선 그 자리 높이(433-5) · 맞출 수 있는 = 높이 안 고친 것
|
||||
target = np.where(
|
||||
born_rows,
|
||||
np.array([row[1] for row in rows], dtype=np.float64),
|
||||
evaluate(pre_s, pre_z, pre_curves, base_s),
|
||||
)
|
||||
can = np.array(
|
||||
[
|
||||
row[2] is not None and all(abs(row[0] - value) >= _SAME_STATION_M for value in held)
|
||||
for row in rows
|
||||
]
|
||||
)
|
||||
free = born_rows.copy()
|
||||
base_z = np.array([row[1] for row in rows], dtype=np.float64)
|
||||
for _ in range(len(rows)):
|
||||
for _ in range(20):
|
||||
curves, _warnings = build_curves(base_s, base_z, policy)
|
||||
error = np.where(free, target - evaluate(base_s, base_z, curves, base_s), 0.0)
|
||||
if float(np.max(np.abs(error))) < 1e-6:
|
||||
break
|
||||
base_z = base_z + error
|
||||
# 이웃 곡선 모양이 바뀌어 뜬 꼭짓점 — 그 꼭짓점도 맞춤(번지는 만큼)
|
||||
curves, _warnings = build_curves(base_s, base_z, policy)
|
||||
error = np.where(free, target - evaluate(base_s, base_z, curves, base_s), 0.0)
|
||||
if float(np.max(np.abs(error))) < 1e-6:
|
||||
drift = (
|
||||
can & ~free & (np.abs(target - evaluate(base_s, base_z, curves, base_s)) > _SEAT_KEEP_M)
|
||||
)
|
||||
if not drift.any():
|
||||
break
|
||||
base_z = base_z + error
|
||||
free |= drift
|
||||
return [
|
||||
node
|
||||
if node is not None
|
||||
else {"chainage_m": round(float(s), 6), "elevation_m": round(float(z), 6)}
|
||||
for (s, _target, node), z in zip(rows, base_z.tolist())
|
||||
if node is not None and not keep
|
||||
else {**(node or {}), "chainage_m": round(float(s), 6), "elevation_m": round(float(z), 6)}
|
||||
for (s, _z, node), z, keep in zip(rows, base_z.tolist(), free.tolist())
|
||||
]
|
||||
|
||||
|
||||
@@ -367,7 +395,8 @@ def _follow_moved_stations(
|
||||
changed = True
|
||||
if new_key is not None and new_key not in reseat:
|
||||
base.append({**node, "chainage_m": float(new_key)})
|
||||
seated = _seat_born_nodes(stored, base, seat)
|
||||
held = [float(key) for key in next_edits["station_offsets"]]
|
||||
seated = _seat_born_nodes(stored, base, seat, held)
|
||||
changed = changed or len(seated) != len(base)
|
||||
base = seated
|
||||
# 측점 목록만 바뀌어도 다시 셈 — 계획선 측점 줄(표 · ▲▼)이 옛 측점을 들고 남지 않게(272) ·
|
||||
|
||||
@@ -70,6 +70,8 @@ function createHoldRepeater(node: Node): {
|
||||
|
||||
export interface ProfileEditStore {
|
||||
edits(): AlignmentEdits;
|
||||
/** 서버 저장분 곡선반경 — ▲▼ 가 자동 R 로 보고 지울 기준(433-13). */
|
||||
savedRadii(): Readonly<Record<string, number>>;
|
||||
replace(next: AlignmentEdits): void;
|
||||
/** 서버 저장이 끝났음을 표시한다. 편집 델타는 그대로 두고 초안만 지운다.
|
||||
* 이때의 값이 [초기화]의 기준점이 된다. */
|
||||
@@ -209,6 +211,7 @@ function handleOf(
|
||||
|
||||
return {
|
||||
edits: () => state.current,
|
||||
savedRadii: () => state.saved.curve_radii ?? {},
|
||||
replace: commit,
|
||||
markSaved() {
|
||||
state.unsaved = false;
|
||||
|
||||
@@ -47,6 +47,8 @@ export interface PanelToolsContext {
|
||||
base: () => AlignmentBase | null;
|
||||
alignment: () => ProfileAlignment | null;
|
||||
edits: () => AlignmentEdits;
|
||||
/** 서버 저장분 곡선반경 — ▲▼ 자동 R 기준(433-13). */
|
||||
savedRadii: () => Readonly<Record<string, number>>;
|
||||
/** false = 막힘(기울기 · 흙덮기). */
|
||||
applyEdits: (next: AlignmentEdits) => boolean;
|
||||
/** 고른 측점 id(없으면 null). */
|
||||
@@ -219,7 +221,7 @@ export function createPanelTools(ctx: PanelToolsContext): PanelTools {
|
||||
if (tools.mode() === "shift") return tools.nudge(delta);
|
||||
const chainage = selectedChainage();
|
||||
if (chainage === null) return false;
|
||||
ctx.applyEdits(adjustStation(base, ctx.edits(), chainage, delta));
|
||||
ctx.applyEdits(adjustStation(base, ctx.edits(), chainage, delta, ctx.savedRadii()));
|
||||
return true;
|
||||
}
|
||||
// 좌우 이동 = 마지막 누름이 종단도 안일 때만 — 횡단 카드 · 3D 를 누른 뒤 방향키가 관을 몰래 옮겼음(219-1)
|
||||
|
||||
@@ -79,6 +79,7 @@ export interface ProfileRenderContext {
|
||||
};
|
||||
store: {
|
||||
edits: () => AlignmentEdits;
|
||||
savedRadii: () => Readonly<Record<string, number>>;
|
||||
resetStation: (chainageM: number) => void;
|
||||
};
|
||||
detail: () => SectionDetailResponse | null;
|
||||
@@ -203,7 +204,8 @@ export function renderProfile(ctx: ProfileRenderContext): void {
|
||||
ctx.applyEdits(setCurveRadius(store.edits(), curve, radius ?? 0)),
|
||||
// 값 열 계획고 직접 입력 → 규칙 측점과 동일한 station_offset 파이프라인.
|
||||
onAdjustStation: (chainage, delta) =>
|
||||
base && ctx.applyEdits(adjustStation(base, store.edits(), chainage, delta)),
|
||||
base &&
|
||||
ctx.applyEdits(adjustStation(base, store.edits(), chainage, delta, store.savedRadii())),
|
||||
})
|
||||
: null;
|
||||
|
||||
@@ -411,7 +413,7 @@ export function renderProfile(ctx: ProfileRenderContext): void {
|
||||
// 다른 편집 경로(직선화·쉬프트·틸팅·방향키)와 규칙이 갈린다(2026-09-02).
|
||||
onStation: (chainage, delta) => {
|
||||
if (!base) return;
|
||||
ctx.applyEdits(adjustStation(base, store.edits(), chainage, delta));
|
||||
ctx.applyEdits(adjustStation(base, store.edits(), chainage, delta, store.savedRadii()));
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* =============================================================================
|
||||
* B05_Profile/logic/B05_Profile_Grade_Seat.ts
|
||||
* 옮긴 관 · 구조물의 새 측점에 계획선 꼭짓점 세움(427) — 원지반 지남(`passThrough` 규칙) · 다른 꼭짓점 그대로.
|
||||
* 옮긴 관 · 구조물의 새 측점에 계획선 꼭짓점 세움(427) — 원지반 지남(`passThrough` 규칙) · 이웃 꼭짓점은 곡선 모양이
|
||||
* 바뀌어 옮기기 전 계획고에서 1mm 넘게 뜨면 그 높이로 맞춤(433-5 · 바로 앞뒤부터 번지는 만큼) · 높이 고친 꼭짓점은 그대로.
|
||||
* 옮기기 전 계획선이 꼭짓점 대부분에서 지반을 안 지나면(균형 종단) 지반 대신 옮기기 전 계획선 높이(438).
|
||||
*
|
||||
* 짝 = 파이썬 `B05_Profile_Router_Confirm._seat_born_nodes` · 세울 측점은 공용 `followMovedNodes`
|
||||
@@ -16,6 +17,8 @@ import {
|
||||
|
||||
/** 지반 종단으로 볼 꼭짓점 자리 계획선 · 지반 어긋남 가운데값(m) — 파이썬 `_SEAT_GROUND_M`. */
|
||||
const SEAT_GROUND_M = 0.01;
|
||||
/** 새 꼭짓점 이웃이 옮기기 전 계획고에서 뜨면 맞출 한도(m · 433-5) — 파이썬 `_SEAT_KEEP_M`. */
|
||||
const SEAT_KEEP_M = 0.001;
|
||||
|
||||
/** `np.median` 짝. */
|
||||
function median(values: number[]): number {
|
||||
@@ -24,11 +27,13 @@ function median(values: number[]): number {
|
||||
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
||||
}
|
||||
|
||||
/** 새 측점(`born`)에 원지반을 지나는 꼭짓점 — 양 끝 밖 · 이미 꼭짓점 있는(0.006m 안) 자리는 건너뜀. */
|
||||
/** 새 측점(`born`)에 원지반을 지나는 꼭짓점 — 양 끝 밖 · 이미 꼭짓점 있는(0.006m 안) 자리는 건너뜀 ·
|
||||
* `held` = 옮긴 뒤 높이 고친 꼭짓점 측점(`followMovedNodes` 의 `held`). */
|
||||
export function seatBornNodes(
|
||||
base: Pick<AlignmentBase, "chainage" | "ground" | "policy" | "basePvi">,
|
||||
nodes: AlignmentNode[],
|
||||
born: ReadonlyArray<number>,
|
||||
held: ReadonlyArray<number> = [],
|
||||
): AlignmentNode[] {
|
||||
if (!nodes.length || base.chainage.length < 2) return nodes;
|
||||
const first = nodes[0].chainage_m;
|
||||
@@ -59,22 +64,40 @@ export function seatBornNodes(
|
||||
...fresh.map((s) => ({ s, z: seatZ(s), node: null })),
|
||||
].sort((a, b) => a.s - b.s);
|
||||
const baseS = rows.map((row) => row.s);
|
||||
const target = rows.map((row) => row.z);
|
||||
let baseZ = [...target];
|
||||
for (let round = 0; round < 20; round += 1) {
|
||||
// 다른 꼭짓점 목표 = 옮기기 전 계획선 그 자리 높이(433-5) · 맞출 수 있는 = 높이 안 고친 것
|
||||
const target = rows.map((row) => (row.node ? evaluateAt(preS, preZ, preCurves, row.s) : row.z));
|
||||
const can = rows.map(
|
||||
(row) => row.node !== null && held.every((value) => Math.abs(row.s - value) >= 0.006),
|
||||
);
|
||||
const free = rows.map((row) => row.node === null);
|
||||
let baseZ = rows.map((row) => row.z);
|
||||
const errorOf = (): number[] => {
|
||||
const pvi = baseS.map((s, index) => ({ chainage_m: s, elevation_m: baseZ[index] }));
|
||||
const curves = buildCurves(pvi, base.policy, {}, []);
|
||||
const error = rows.map((row, index) =>
|
||||
row.node ? 0 : target[index] - evaluateAt(baseS, baseZ, curves, baseS[index]),
|
||||
return baseS.map((s, index) => target[index] - evaluateAt(baseS, baseZ, curves, s));
|
||||
};
|
||||
for (let grow = 0; grow < rows.length; grow += 1) {
|
||||
for (let round = 0; round < 20; round += 1) {
|
||||
const error = errorOf().map((value, index) => (free[index] ? value : 0));
|
||||
if (Math.max(...error.map(Math.abs)) < 1e-6) break;
|
||||
baseZ = baseZ.map((z, index) => z + error[index]);
|
||||
}
|
||||
// 이웃 곡선 모양이 바뀌어 뜬 꼭짓점 — 그 꼭짓점도 맞춤(번지는 만큼)
|
||||
const drift = errorOf().map(
|
||||
(value, index) => can[index] && !free[index] && Math.abs(value) > SEAT_KEEP_M,
|
||||
);
|
||||
if (Math.max(...error.map(Math.abs)) < 1e-6) break;
|
||||
baseZ = baseZ.map((z, index) => z + error[index]);
|
||||
if (!drift.includes(true)) break;
|
||||
drift.forEach((hit, index) => {
|
||||
if (hit) free[index] = true;
|
||||
});
|
||||
}
|
||||
return rows.map(
|
||||
(row, index) =>
|
||||
row.node ?? {
|
||||
chainage_m: Number(row.s.toFixed(6)),
|
||||
elevation_m: Number(baseZ[index].toFixed(6)),
|
||||
},
|
||||
return rows.map((row, index) =>
|
||||
row.node && !free[index]
|
||||
? row.node
|
||||
: {
|
||||
...row.node,
|
||||
chainage_m: Number(row.s.toFixed(6)),
|
||||
elevation_m: Number(baseZ[index].toFixed(6)),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -561,12 +561,14 @@ const FEEDBACK_TOLERANCE_M = 1e-4;
|
||||
* 버튼 1클릭이 정확히 0.1m가 되지 않는다. **화면에 보이는 계획고**가 정확히 delta만큼
|
||||
* 움직이도록 중앙종거 변화분을 몇 번 되먹여 보정한다.
|
||||
* 누른 점 좌우 다음 꺾임(`pivotAxes`)을 축으로 사이 측점이 새 직선 위로 따라 돈다(355-1) · 직선 안 점도 그 점이 새 꺾임(368-1).
|
||||
* 누른 점 R 이 자동 R(`autoRadii` = 저장분 · 관 정착 등 엔진이 역산한 값)과 같으면 지워 기본 L(433-13) · 사용자 R 은 둠.
|
||||
*/
|
||||
export function adjustStation(
|
||||
base: AlignmentBase,
|
||||
edits: AlignmentEdits,
|
||||
chainageM: number,
|
||||
delta: number,
|
||||
autoRadii: Readonly<Record<string, number>> = {},
|
||||
): AlignmentEdits {
|
||||
const key = chainageKey(chainageM);
|
||||
const current = buildAlignment(base, edits);
|
||||
@@ -580,6 +582,7 @@ export function adjustStation(
|
||||
const withOffset = (value: number): AlignmentEdits => {
|
||||
const offsets = { ...edits.station_offsets, [key]: Number(value.toFixed(6)) };
|
||||
const radii = { ...edits.curve_radii };
|
||||
if (radii[key] !== undefined && radii[key] === autoRadii[key]) delete radii[key];
|
||||
axes?.inner.forEach((chainage) => {
|
||||
const [axisM, axisZ] = chainage < chainageM ? [axes.fromM, axes.fromZ] : [axes.toM, axes.toZ];
|
||||
const z =
|
||||
|
||||
@@ -17,6 +17,16 @@ export function readAlignment(data: LongitudinalSection): ProfileAlignment | nul
|
||||
station_offsets: candidate.edits?.station_offsets ?? {},
|
||||
curve_radii: candidate.edits?.curve_radii ?? {},
|
||||
};
|
||||
// 옛 정책엔 법정 최소 R · L 이 없음 — 첫 계획선 설계 기준(criteria = 설계속도 법정값)으로 채움
|
||||
// (433-13 · 서버 `rebuild_alignment_profile` 짝)
|
||||
const criteria = data.design_profiles?.[0]?.criteria;
|
||||
const policy = candidate.policy;
|
||||
if (criteria && (!policy.min_vertical_radius_m || !policy.min_curve_length_m))
|
||||
candidate.policy = {
|
||||
...policy,
|
||||
min_vertical_radius_m: policy.min_vertical_radius_m || criteria.min_vertical_radius_m || 0,
|
||||
min_curve_length_m: policy.min_curve_length_m || criteria.min_curve_length_m || 0,
|
||||
};
|
||||
return candidate;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user