"""측점 열쇠 한 규칙(PLAN 344-3) — 노선이 바뀌어도 같은 측점을 같은 이름으로 부름. 일반 = ``grid:k``(누가거리 ÷ 측점 간격 반올림) · 관 = ``pipe:`` · 구조물 = ``structure:`` · 시종점 = ``end:BP`` · ``end:EP`` · id 를 모르는 구조물 측점 = ``at:<누가거리 0.01 m>``. ⚠ 파이썬·TS 짝 — ``common_util_station_key.ts`` ``stationKey`` ↔ ``station_key``. 한쪽을 고치면 다른 쪽도 고치고 거울 시험 ``resources/tester/test_344_3_station_key_mirror.py``. """ from __future__ import annotations import math import re _LOOSE = re.compile(r"^-?\d+(\.\d+)?$") def _half_up(value: float) -> int: """JS ``Math.round`` 와 같은 반올림(0.5 는 위로).""" return math.floor(value + 0.5) def chainage_key(chainage_m: float) -> str: """누가거리 0.01 m — 옛 초안 열쇠(``toFixed(2)``)와 같은 자릿수.""" return f"{_half_up(chainage_m * 100) / 100:.2f}" def station_key( kind: str, chainage_m: float, interval_m: float | None, station_id: str | None = None, id_kind: str = "structure", ) -> str: """측점 열쇠 — irregular 는 id 가 있으면 pipe: · structure:(``id_kind``) · 없으면 at:.""" if kind == "bp": return "end:BP" if kind == "ep": return "end:EP" if kind in ("pipe", "structure"): return f"{kind}:{station_id}" if station_id else f"at:{chainage_key(chainage_m)}" if kind == "irregular": return f"{id_kind}:{station_id}" if station_id else f"at:{chainage_key(chainage_m)}" if interval_m and interval_m > 0: return f"grid:{_half_up(chainage_m / interval_m)}" return f"at:{chainage_key(chainage_m)}" def loose_chainage(key: str) -> float | None: """옛 초안 열쇠(누가거리 글) · ``at:`` 열쇠의 누가거리 — 측점 열쇠면 None.""" text = key[3:] if key.startswith("at:") else key return float(text) if _LOOSE.match(text) else None