Files
Aislo/resources/tester/test_407_curve_station.py
T
eomsangdonandClaude Opus 5.5 fe893cfb8e feat(B05 측점): 곡선 측점 — MC 에 측점이 없으면 곡선 측점 하나 더함 (407-1 · 요구 252-2)
- 서버 generate_sections ↔ TS buildStations 짝 · curve_stations ↔ curveStations 짝 · 종류 curve · 열쇠 curve:<누가거리> · 이름 뒤 (MC)
- 값 = 스냅 간격 자릿수(0.1 = 소수 한 자리 · 그 밖 · 스냅 끔 = 정수) · 0.005 m 안 측점(일반 · 옮긴 · 관 · 구조물) 있으면 안 더함 · BC · EC 안 더함 · 224-2 옮김 그대로
- 격자 번호 안 가져감 · 노선 저장 이월 열쇠 · B06 종류(curve) · 종단 선 색 · 카드 · 종단 이름 (MC) · 평면 눈금 (MC) · 오른쪽 횡단 일반측점 갈래
- 시험: test_407_curve_station 새로 · 344_3 둘 · 224_2 · 346_1 헬퍼 저장 노선 곡선표

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG
2026-10-18 09:13:52 +09:00

174 lines
6.6 KiB
Python

"""PLAN 407 · 요구 252-2 — 곡선 측점(MC 에 측점이 없으면 하나 더함).
짝: 파이썬 `B05_Profile_Engine_Sections_Core.curve_stations` · `generate_sections` ↔ TS
`logic/B05_Profile_UI_RouteEdit_Frame.ts` `curveStations` · `logic/B05_Profile_Sections_Stations.ts`
`buildStations`(헬퍼 `helper_344_3_pipeline_stations.cjs`). 열쇠 `curve:<누가거리>` ·
이름 뒤 「 (MC)」 ·
값 = 스냅 간격 자릿수(0.1 = 소수 한 자리 · 그 밖 · 스냅 끔 = 정수) · 일반측점 값과 같으면 안 더함 ·
스냅 켬 · 호 안 일반측점 = 224-2 대로 옮김(곡선 측점 없음) · BC · EC 안 더함.
"""
from __future__ import annotations
import json
import subprocess
import sys
import tempfile
from pathlib import Path
import numpy as np
import pytest
HERE = Path(__file__).resolve().parent
ROOT = HERE.parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from B05_Profile import B05_Profile_Engine_Sections_Core as core # noqa: E402
from common_util.common_util_station_key import station_key # noqa: E402
LINE = [[0.0, 0.0], [1000.3, 0.0]]
LONG = {"start": [616.1, 0.0], "apex": [631.3, 3.0], "end": [646.5, 0.0], "radius_m": 40.0}
SHORT = {"start": [101.0, 0.0], "apex": [107.3, 2.0], "end": [113.6, 0.0], "radius_m": 30.0}
ON_GRID = {"start": [195.0, 0.0], "apex": [200.1, 2.0], "end": [205.2, 0.0], "radius_m": 30.0}
CURVES = [LONG, SHORT, ON_GRID]
GRID = [float(value) for value in range(0, 1001, 20)]
class _Flat:
def sample_xy(self, xy):
count = len(np.asarray(xy, dtype=np.float64))
return np.full(count, 100.0), np.ones(count, dtype=bool)
def _server(snap: bool, step: float | None = None, extras: tuple = ()) -> list[dict]:
data = core.generate_sections(
LINE,
_Flat(),
core.SectionGenerationOptions(extra_stations=extras),
design_curves=CURVES,
station_snap=snap,
station_snap_step=step,
)
return data["longitudinal"]["stations"]
def _curves(stations: list[dict]) -> list[float]:
return [s["chainage_m"] for s in stations if s["kind"] == "curve"]
# ── 파이썬 규칙 ────────────────────────────────────────────────────────────────
def test_정수_MC_하나() -> None:
assert core.curve_stations(GRID, [(101.0, 113.6)], 1000.3) == [107.0]
def test_스냅_간격_0_1_이면_소수_한_자리() -> None:
assert core.curve_stations(GRID, [(101.0, 113.6)], 1000.3, 0.1) == [107.3]
assert core.curve_stations(GRID, [(101.0, 113.6)], 1000.3, 10.0) == [107.0]
def test_일반측점_값이면_안_더함() -> None:
assert core.curve_stations(GRID, [(195.0, 205.2)], 1000.3) == []
def test_MC_옆_관_측점이면_안_더함() -> None:
assert core.curve_stations([*GRID, 107.003], [(101.0, 113.6)], 1000.3) == []
def test_노선_밖_MC_는_뺌() -> None:
assert core.curve_stations([0.0], [(995.0, 1010.0)], 1000.3) == [] # MC 1003
def test_스냅_끔_MC_마다_곡선_측점() -> None:
stations = _server(False)
assert _curves(stations) == [107.0, 631.0] # 200 = 일반측점 위 → 안 더함
by = {s["chainage_m"]: s for s in stations}
assert by[640.0]["kind"] == "regular" and by[200.0]["kind"] == "regular"
assert by[107.0]["label"] == "STA.0+107.000 (MC)"
assert len(stations) == len(_server(False, extras=())) == 51 + 1 + 2 # 격자 · EP · MC 둘
assert 101.0 not in by and 113.6 not in by # BC · EC 안 더함
def test_스냅_켬_짧은_호만_곡선_측점() -> None:
stations = _server(True)
by = {s["chainage_m"]: s for s in stations}
assert _curves(stations) == [107.0]
assert by[631.0]["kind"] == "regular" and 640.0 not in by # 224-2 옮김 그대로
def test_스냅_켬_0_1_간격() -> None:
stations = _server(True, 0.1)
by = {s["chainage_m"]: s for s in stations}
assert _curves(stations) == [107.3]
assert 631.3 in by and by[200.1]["kind"] == "regular" # 옮긴 일반측점
def test_MC_에_구조물이면_구조물_측점_하나() -> None:
stations = _server(False, extras=((107.0, "관"),))
by = {s["chainage_m"]: s for s in stations}
assert by[107.0]["kind"] == "irregular" and _curves(stations) == [631.0]
def test_열쇠() -> None:
assert station_key("curve", 107.0, 20.0) == "curve:107.00"
assert station_key("curve", 107.3, 20.0) == "curve:107.30"
# ── 파이썬 ↔ TS 같은 목록 ─────────────────────────────────────────────────────
CASES = [
{"name": "끔", "snap": False, "step": None},
{"name": "켬", "snap": True, "step": None},
{"name": "켬0.1", "snap": True, "step": 0.1},
{"name": "끔0.1", "snap": False, "step": 0.1}, # 끔이면 간격 무시 = 정수
]
@pytest.fixture(scope="module")
def ts_rows() -> list[list[dict]]:
job = {
"cases": [{"line": LINE, "curves": CURVES, **case} for case in CASES],
"pipes": [],
"structures": [],
"types": [],
"move": {
"line": LINE,
"curves": CURVES,
"movedLine": LINE,
"movedCurves": CURVES,
"fromM": 0.0,
},
}
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / "job.json"
path.write_text(json.dumps(job), encoding="utf-8")
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
["node", str(HERE / "helper_344_3_pipeline_stations.cjs"), str(path)],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)["cases"]
@pytest.mark.parametrize("index", range(len(CASES)), ids=[c["name"] for c in CASES])
def test_파이썬과_TS_같은_목록(ts_rows, index) -> None:
case = CASES[index]
server = _server(case["snap"], case["step"])
ours = ts_rows[index]
assert [s["chainage_m"] for s in server] == pytest.approx(
[s["chainageM"] for s in ours], abs=1e-6
)
for py, ts in zip(server, ours, strict=True):
assert (py["kind"] == "curve") == (ts["kind"] == "curve"), py["chainage_m"]
if ts["kind"] == "curve":
assert ts["key"] == station_key("curve", py["chainage_m"], 20.0)
assert any(s["kind"] == "curve" for s in ours)
# 곡선 측점은 격자 번호를 안 가져감 — 격자 열쇠는 그대로 1 ~ 50 하나씩(0 = BP)
grid = sorted(int(s["key"][5:]) for s in ours if s["kind"] == "grid")
assert grid == list(range(1, 51))