- 측점 계획 짝 planStations(plus) ↔ plan_stations(plus=) — 주인 plus-{m} · 이름 "" · 같은 m 은 관 · 구조물이 이김
- 정본 B05_Profile/route/plus_stations.json(x · y) · GET/PUT /section/plus-stations · PUT 뒤 측점 맞춤
- 초안 plus-stations(설계 가게 plusStations) · [저장] 차례 구조물 뒤 · 계획고 앞
- 노선 저장 = 좌표로 따라감 · 되살림 대상 · B05 종단 · B06 목록 바뀜 신호
- 종단 세로선 plus 표시(점선 · 끌기 없음 자리)
- 거울 시험 272_1 · 322_3 · 344_3 에 추가 측점 경우 · 322_3 관 id 고정(347) 맞춤
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG
94 lines
3.7 KiB
Python
94 lines
3.7 KiB
Python
"""추가 측점 정본(`B05_Profile/route/plus_stations.json`) — 구조물 정본 곁(368-2 · 요구 240-21).
|
|
|
|
사용자가 종단 원지반선에서 넣은 일반 측점(정수 m) · 이름 없음. 자리 정본 = 좌표(x · y) — 노선이
|
|
바뀌면 관 · 구조물처럼 새 노선에 투영해 누가거리를 다시 매김(`follow_structures` 재사용).
|
|
측점 계획에는 `resolve_extra_stations` → `plan_stations(plus=)` 로 들어감(TS `planStations` 짝).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from typing import Any, Sequence
|
|
|
|
from common_util.common_util_json import atomic_write_json
|
|
from common_util.common_util_route_follow import follow_structures
|
|
|
|
PLUS_STATIONS_FILE_NAME = "plus_stations.json"
|
|
|
|
|
|
def plus_stations_path(project_root: str | Path) -> Path:
|
|
return Path(project_root) / "B05_Profile" / "route" / PLUS_STATIONS_FILE_NAME
|
|
|
|
|
|
def load_plus_stations(project_root: str | Path) -> list[dict[str, Any]]:
|
|
"""[{chainage_m, x, y}] — 없거나 깨졌으면 빈 목록."""
|
|
path = plus_stations_path(project_root)
|
|
if not path.is_file():
|
|
return []
|
|
try:
|
|
rows = json.loads(path.read_text(encoding="utf-8")).get("stations") or []
|
|
except (OSError, ValueError, AttributeError):
|
|
return []
|
|
return [
|
|
row
|
|
for row in rows
|
|
if isinstance(row, dict) and isinstance(row.get("chainage_m"), (int, float))
|
|
]
|
|
|
|
|
|
def plus_chainages(project_root: str | Path) -> list[float]:
|
|
return sorted({float(row["chainage_m"]) for row in load_plus_stations(project_root)})
|
|
|
|
|
|
def save_plus_stations(
|
|
project_root: str | Path, chainages: Sequence[float], xy: Sequence[Sequence[float]] | None
|
|
) -> list[float]:
|
|
"""정수 m 로 맞춰(0 ~ 노선 끝 · 같은 m 하나) 좌표와 함께 씀 → 쓴 누가거리."""
|
|
from shapely.geometry import LineString
|
|
|
|
line = LineString([(float(p[0]), float(p[1])) for p in xy]) if xy and len(xy) > 1 else None
|
|
end = float(line.length) if line is not None else float("inf")
|
|
values = sorted({float(round(c)) for c in chainages if 0 <= round(c) <= end})
|
|
rows = []
|
|
for value in values:
|
|
point = line.interpolate(value) if line is not None else None
|
|
rows.append(
|
|
{
|
|
"chainage_m": value,
|
|
"x": round(float(point.x), 4) if point is not None else None,
|
|
"y": round(float(point.y), 4) if point is not None else None,
|
|
}
|
|
)
|
|
path = plus_stations_path(project_root)
|
|
if not rows and not path.exists():
|
|
return []
|
|
os.makedirs(path.parent, exist_ok=True)
|
|
atomic_write_json(str(path), {"stations": rows})
|
|
return values
|
|
|
|
|
|
def follow_plus_stations(project_root: str | Path, new_xy: Sequence[Sequence[float]]) -> int:
|
|
"""노선이 바뀔 때 좌표를 새 노선에 투영해 누가거리를 다시 매김 → 옮긴 수(같은 m 는 하나)."""
|
|
rows = load_plus_stations(project_root)
|
|
items = [
|
|
SimpleNamespace(chainage_m=float(r["chainage_m"]), x=r.get("x"), y=r.get("y")) for r in rows
|
|
]
|
|
if not items:
|
|
return 0
|
|
before = [item.chainage_m for item in items]
|
|
follow_structures(items, new_xy)
|
|
moved = sum(a != item.chainage_m for a, item in zip(before, items))
|
|
if moved:
|
|
seen: set[float] = set()
|
|
kept = []
|
|
for item in sorted(items, key=lambda i: i.chainage_m):
|
|
if item.chainage_m in seen:
|
|
continue
|
|
seen.add(item.chainage_m)
|
|
kept.append({"chainage_m": item.chainage_m, "x": item.x, "y": item.y})
|
|
atomic_write_json(str(plus_stations_path(project_root)), {"stations": kept})
|
|
return moved
|