- 서버 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
174 lines
6.6 KiB
Python
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))
|