Files
Aislo/B05_Profile/B05_Profile_Route_Polyline_Write.py
T

185 lines
7.2 KiB
Python

"""계획노선 폴리라인 파일 쓰기(`B05_Profile_Router_Replan` 에서 나눔 · PLAN 511-B B-5).
노드 · 곡선 켬끔 · R(편집 갈래) → 선 · 노드 · 곡선표(+ 편집표) = 늘 떠 있는 Node `route.polyline`
(화면 `buildEditedPolyline` · `nodeEdits` 와 같은 TS) · 파이썬은 파일 꼴(csv 4자리 · 곡선 as_dict
반올림)만. 단순화 갈래(예상노선 첫 폴리라인) · Node 못 돌림 = 파이썬 `build_planned_polyline` ·
`node_edits`(짝 · 로그 남김).
"""
from __future__ import annotations
import json
import logging
from pathlib import Path
from typing import Any
from B05_Profile.B05_Profile_Route_NodeEdits import node_edits
from common_util.common_util_route_geometry import write_route_csv
from common_util.common_util_route_polyline import (
PlannedPolyline,
RouteCurve,
RouteNode,
build_planned_polyline,
)
logger = logging.getLogger(__name__)
def _nodes_path(path: Path) -> Path:
"""그 폴리라인을 낳은 **노드** 파일 자리 — `planned_route.csv` → `planned_route_nodes.csv`."""
return path.with_name(f"{path.stem}_nodes.csv")
def _curves_path(path: Path) -> Path:
"""그 폴리라인의 **곡선 성분** 자리 — `planned_route.csv` → `planned_route_curves.json`.
왜 따로 남기나(2026-09-07 사용자 확정) — 계획노선은 「직선 > 곡선 > 직선」이고 사용자가
잡는 것은 **곡선 시작·끝점**이며 반지름도 직접 바꾼다. 정점 목록만으로는 어디부터
어디까지가 한 곡선인지, 그 반지름이 얼마인지 알 수 없어 편집·도면이 같은 값을 못 본다.
"""
return path.with_name(f"{path.stem}_curves.json")
def _read_curves(path: Path, key: str = "curves") -> list[dict]:
"""저장해 둔 곡선 성분(`key` = "edits" 면 넣은 편집값 · 430). 없거나 못 읽으면 빈 목록."""
target = _curves_path(path)
if not target.is_file():
return []
try:
data = json.loads(target.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
logger.warning("계획노선 곡선 성분을 읽지 못했습니다: %s", target)
return []
curves = data.get(key) if isinstance(data, dict) else None
return curves if isinstance(curves, list) else []
def node_polyline(
points: list[tuple[float, float]],
radius_m: float,
curve_flags: list[bool] | None,
radii: list[float | None] | None,
locks: list[str | None] | None,
arcs: list[float | None] | None,
) -> tuple[PlannedPolyline, list[dict[str, Any]]] | None:
"""편집 갈래 폴리라인 + 편집표 = Node `route.polyline` — 못 돌림 None."""
from common_util.common_util_calc_runner import run_calc
route = {
"points": [[float(x), float(y)] for x, y in points],
"curve_flags": curve_flags,
"radii": radii,
"locks": locks,
"arcs": arcs,
"min_radius_m": float(radius_m),
}
try:
out = (run_calc(["route.polyline"], {"route_polyline": route}) or {}).get("route.polyline")
except Exception: # noqa: BLE001 — 노선 저장은 파이썬 짝으로 이어 감
logger.exception("Node 노선 폴리라인 실패 — 파이썬 길")
return None
if not out:
logger.warning("Node 노선 폴리라인 못 돌림 — 파이썬 길")
return None
# JSON 정수(100) → float — 파이썬 길과 같은 글(`100.0`)로 적힘
nodes = [
RouteNode(
x=float(x),
y=float(y),
inner_angle_deg=_num(info["inner_angle_deg"]),
radius_m=_num(info["radius_m"]),
tangent_m=_num(info["tangent_m"]),
violations=list(info["violations"]),
)
for (x, y), info in zip(out["cleaned"], out["nodes"])
]
curves = [
RouteCurve(
apex=_xy(curve["apex"]),
radius_m=float(curve["radius_m"]),
tangent_m=float(curve["tangent_m"]),
inner_angle_deg=float(curve["inner_angle_deg"]),
start=_xy(curve["start"]),
end=_xy(curve["end"]),
node_first=int(curve["node_first"]),
node_last=int(curve["node_last"]),
violations=list(curve["violations"]),
)
for curve in out["curves"]
]
vertices = [_xy(vertex) for vertex in out["vertices"]]
edits = [
{**edit, "radius_m": _num(edit["radius_m"]), "arc_m": _num(edit["arc_m"])}
for edit in out["edits"]
]
return PlannedPolyline(nodes=nodes, vertices=vertices, curves=curves), edits
def _num(value: Any) -> float | None:
return None if value is None else float(value)
def _xy(pair: list[float]) -> tuple[float, float]:
return float(pair[0]), float(pair[1])
def _write_planned_polyline(
path: Path,
points: list[tuple[float, float]],
radius_m: float,
*,
simplify: bool = True,
curve_flags: list[bool] | None = None,
radii: list[float | None] | None = None,
station_snap: bool | None = None,
station_snap_step: float | None = None,
locks: list[str | None] | None = None,
arcs: list[float | None] | None = None,
) -> dict:
"""점 묶음을 폴리라인으로 바꿔 CSV 로 쓴다. 노드 요약을 돌려준다.
`station_snap` — [확인] 때만 곡선표에 적음(요구 224-2). None 이면 칸을 안 둠(끔) ·
`station_snap_step` — 켬 + 간격이 있을 때만 곡선표에 적음(390-5 · 없으면 측점 No.).
`locks` · `arcs` — 편집값이 오면 넣은 값을 곡선표 `edits` 에(430 · `_Route_NodeEdits`).
**노드도 함께 남긴다** — 노드는 폴리라인에서 되뽑을 수 없다. 폴리라인에는 원호 위 점이
섞여 있어 다시 단순화하면 꺾임점이 조금씩 지워지고, 그것을 반복하면 [확인]을 누를
때마다 선이 깎인다(2026-09-07 실측: 정점 136 → 134 → 119). 낳은 값을 그대로 보관한다.
"""
edited = curve_flags is not None or radii is not None
made = (
node_polyline(points, radius_m, curve_flags, radii, locks, arcs)
if edited and not simplify
else None
)
if made is not None:
result, edits = made
else:
result = build_planned_polyline(
points,
min_radius_m=radius_m,
simplify=simplify,
curve_flags=curve_flags,
radii=radii,
)
edits = node_edits(points, curve_flags, radii, locks, arcs) if edited else None
write_route_csv(path, [{"x": x, "y": y} for x, y in result.vertices])
write_route_csv(_nodes_path(path), [{"x": node.x, "y": node.y} for node in result.nodes])
table: dict[str, Any] = {
"min_radius_m": round(radius_m, 3),
"curves": [c.as_dict() for c in result.curves],
}
if station_snap is not None:
table["station_snap"] = bool(station_snap)
if station_snap and station_snap_step is not None:
table["station_snap_step"] = float(station_snap_step)
if edits is not None:
table["edits"] = edits
_curves_path(path).write_text(json.dumps(table, ensure_ascii=False), encoding="utf-8")
return {
"nodes": len(result.nodes),
"curves": result.curve_count,
"violations": result.violation_count,
"vertices": len(result.vertices),
}