Files
Aislo/B05_Profile/B05_Profile_Engine_Grade_Carry.py
T
eomsangdonandClaude Opus 5.5 bb63b30aae feat(344-2): 첫 계획선 · 관 자리 투영 · 구조물 좌표 따라감 · 계획고 옮김 짝
- 첫 계획선(계획고 = 지반고) base_pvi TS 짝 = B05_Profile/logic/B05_Profile_Grade_Ground.ts(1순위만 · 못 서는 땅은 null = 서버로 다시 셈)
- 관 자리 투영 TS 짝 · 구조물 따라감 py · ts 짝 = common_util_route_follow(정수 m · 구간 끝 밂 · 0 ~ 연장 자름 · 20 m 넘으면 멀어짐 표시 · 상수 STRUCTURE_ROUTE_DRIFT_MAX_M)
- 구조물 x · y 저장(저장 때 노선 위 자리 · 자리가 그대로면 옛 좌표 지킴 · 옛 자료는 읽을 때 채움) · 노선 [확인] 때 구조물 정본이 좌표를 따라감
- 노선 바뀜 때 계획고 옮김 = 바뀐 구간만 새 지반 첫 계획선 · 앞 그대로 · 뒤 길이 차만큼 밂(B05_Profile_Engine_Grade_Carry.py ↔ logic/B05_Profile_Grade_Carry.ts)
- 거울 시험 test_344_2_route_pairs(+ helper cjs) · buildCurves · evaluateAt 내보냄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
2026-10-16 11:19:54 +09:00

128 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""노선이 바뀔 때 계획고 옮김 — 바뀐 구간만 지반에 다시 맞추고 그 밖 편집은 길이 차만큼 민다(344-2).
사용자 확정(10-16 · 341 결정 1): 노선 끝쪽을 고쳤는데 앞쪽 계획고가 날아가지 않게 한다.
- 바뀐 구간 = 옛 · 새 노선 정점열의 같은 앞머리 · 같은 꼬리를 뺀 가운데(`changed_span`) — 바뀐
노드 앞 · 뒤 접선점 사이. 원호 정점까지 같은지 보므로 곡선이 바뀐 이웃 노드 범위도 들어간다.
- 구간 앞 = 같은 땅 · 같은 누가거리 → 그대로. 구간 뒤 = 같은 땅 · 누가거리만 길이 차만큼 밀림.
- 구간 안 = 새 지반 첫 계획선(`design_ground_following_profile` 의 base_pvi) 꼭짓점으로
갈아 끼움 · 그 안 편집(계획고 · 종단곡선 R)은 버림.
- 옮기는 것 = base_pvi · station_offsets · curve_radii(누가거리 글자 키).
짝 = TS `B05_Profile/logic/B05_Profile_Grade_Carry.ts`(같은 규칙) · 거울 시험 =
`resources/tester/test_344_2_route_pairs.py`. 서버 정본은 이쪽(346-3 [저장]이 부름).
"""
from __future__ import annotations
import math
from typing import Any
from B05_Profile.B05_Profile_Engine_Grade_Alignment import chainage_key
# 같은 정점으로 볼 거리(m) — 저장 좌표 0.0001 m 자리 · 원호 다시 뽑기 잡음보다 크게.
SAME_VERTEX_M = 1e-3
_EPS = 1e-6
def _cumulative(xy: list[tuple[float, float]]) -> list[float]:
total = [0.0]
for (x0, y0), (x1, y1) in zip(xy, xy[1:]):
total.append(total[-1] + math.hypot(x1 - x0, y1 - y0))
return total
def _same(a: tuple[float, float], b: tuple[float, float]) -> bool:
return abs(a[0] - b[0]) <= SAME_VERTEX_M and abs(a[1] - b[1]) <= SAME_VERTEX_M
def changed_span(
old_xy: list[tuple[float, float]], new_xy: list[tuple[float, float]]
) -> dict[str, Any] | None:
"""옛 · 새 노선 정점열 → 바뀐 누가거리 구간. 같으면 None.
`start_m` = 같은 앞머리 끝(옛 = 새) · `old_end_m` · `new_end_m` = 같은 꼬리 시작 ·
`delta_m` = 새 연장 − 옛 연장 · `start_fixed` · `end_fixed` = 앞머리 · 꼬리가 한 점이라도
같은가.
"""
old_xy = [(float(x), float(y)) for x, y in old_xy]
new_xy = [(float(x), float(y)) for x, y in new_xy]
limit = min(len(old_xy), len(new_xy))
prefix = 0
while prefix < limit and _same(old_xy[prefix], new_xy[prefix]):
prefix += 1
if prefix == len(old_xy) == len(new_xy):
return None
suffix = 0
while suffix < limit - prefix and _same(old_xy[-1 - suffix], new_xy[-1 - suffix]):
suffix += 1
old_cum, new_cum = _cumulative(old_xy), _cumulative(new_xy)
return {
"start_m": old_cum[prefix - 1] if prefix else 0.0,
"old_end_m": old_cum[len(old_xy) - suffix] if suffix else old_cum[-1],
"new_end_m": new_cum[len(new_xy) - suffix] if suffix else new_cum[-1],
"delta_m": new_cum[-1] - old_cum[-1],
"start_fixed": prefix > 0,
"end_fixed": suffix > 0,
}
def _before(span: dict[str, Any], s: float) -> bool:
"""구간 앞(그대로 둠) — 앞머리가 같으면 그 끝점까지."""
start = float(span["start_m"])
return s < start - _EPS or (bool(span["start_fixed"]) and s <= start + _EPS)
def _after(span: dict[str, Any], s: float, end: float) -> bool:
"""구간 뒤(밂) — 꼬리가 같으면 그 시작점부터."""
return s > end + _EPS or (bool(span["end_fixed"]) and s >= end - _EPS)
def carry_profile_edits(
base_pvi: list[dict[str, float]],
edits: dict[str, Any] | None,
span: dict[str, Any],
ground_pvi: list[dict[str, float]],
) -> tuple[list[dict[str, float]], dict[str, dict[str, float]]]:
"""옛 꼭짓점 · 편집을 새 노선으로 옮긴다 → (새 base_pvi, 새 edits).
`ground_pvi` = 새 노선 지반 첫 계획선 꼭짓점(전 구간 · `design_ground_following_profile`) —
바뀐 구간 안 몫만 쓴다.
"""
delta = float(span["delta_m"])
old_end, new_end = float(span["old_end_m"]), float(span["new_end_m"])
def moved(s: float) -> float | None:
if _before(span, s):
return s
if _after(span, s, old_end):
return s + delta
return None # 바뀐 구간 안 — 버림
nodes: dict[float, dict[str, float]] = {}
for node in base_pvi:
at = moved(float(node["chainage_m"]))
if at is not None:
key = round(at, 3)
nodes[key] = {"chainage_m": round(at, 6), "elevation_m": float(node["elevation_m"])}
for node in ground_pvi:
s = float(node["chainage_m"])
if _before(span, s) or _after(span, s, new_end):
continue
nodes.setdefault(
round(s, 3), {"chainage_m": round(s, 6), "elevation_m": float(node["elevation_m"])}
)
carried: dict[str, dict[str, float]] = {}
for name in ("station_offsets", "curve_radii"):
values: dict[str, float] = {}
for key, value in ((edits or {}).get(name) or {}).items():
try:
at = moved(float(key))
except (TypeError, ValueError):
continue
if at is not None and value is not None:
values[chainage_key(at)] = float(value)
carried[name] = values
return [nodes[key] for key in sorted(nodes)], carried