185 lines
7.2 KiB
Python
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),
|
|
}
|