# -*- coding: utf-8 -*- """349-0v 손으로 고친 노선 [저장] — 미리보기 계획고 = 저장 뒤 계획고(바뀐 구간 거울). 화면 planline 단계: 옛 = 저장 계획 폴리라인(`read_route_plan` planned) · 새 = `buildEditedPolyline`(노드 초안) → `changedSpan` → `carryProfileEdits`. 서버 노선 저장: 옛 = 같은 계획 폴리라인 CSV · 새 = `_write_planned_polyline` 이 쓴 CSV → `changed_span` → `carry_profile_edits`. 두 길이 같은 구간 · 같은 계획고를 내야 함(풀린 노선 geojson 으로 가르면 점 간격이 달라 앞머리가 일찍 갈렸음). """ from __future__ import annotations import json import math import subprocess import sys from pathlib import Path 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_Router_Replan as replan # noqa: E402 from B05_Profile.B05_Profile_Engine_Grade_Carry import ( # noqa: E402 carry_profile_edits, changed_span, ) pytestmark = pytest.mark.skipif( not (ROOT / "config" / "node_modules" / "typescript").is_dir(), reason="프론트엔드 의존성 미설치", ) RADIUS_M = 30.0 OLD_NODES = [(0.0, 0.0), (180.0, 20.0), (320.0, 140.0), (480.0, 130.0), (620.0, 260.0)] OLD_NODES += [(800.0, 250.0), (950.0, 380.0)] MOVED = 4 # 가운데 뒤쪽 꺾임점 하나를 끎(검증_sub2c 끌기와 같은 꼴) NEW_NODES = [(x + 25.0, y - 30.0) if i == MOVED else (x, y) for i, (x, y) in enumerate(OLD_NODES)] CURVE_ON = [True] * len(OLD_NODES) RADII: list[float | None] = [None] * len(OLD_NODES) def _csv_line(path: Path, nodes) -> list[list[float]]: replan._write_planned_polyline( path, list(nodes), RADIUS_M, simplify=False, curve_flags=CURVE_ON, radii=RADII ) return replan._vertices_of(path) def _length(line) -> float: return sum(math.dist(a, b) for a, b in zip(line, line[1:])) @pytest.fixture(scope="module") def world(tmp_path_factory): tmp = tmp_path_factory.mktemp("span") old_line = _csv_line(tmp / "old.csv", OLD_NODES) # 저장 노선(화면 · 서버 같은 파일) new_line = _csv_line(tmp / "new.csv", NEW_NODES) # 서버가 [저장] 때 쓰는 새 계획 폴리라인 total_old, total_new = _length(old_line), _length(new_line) base_pvi = [ {"chainage_m": round(s, 6), "elevation_m": 100.0 + 0.04 * s} for s in [0.0, 150.0, 300.0, 450.0, 600.0, 750.0, 900.0, total_old] ] ground_pvi = [ {"chainage_m": round(s, 6), "elevation_m": 101.0 + 0.03 * s} for s in [x * 40.0 for x in range(int(total_new // 40) + 1)] + [total_new] ] edits = { "station_offsets": {"150.000": 0.8, "600.000": -0.5, "900.000": 0.4}, "curve_radii": {"300.000": 400.0}, } payload = { "nodes": [list(p) for p in NEW_NODES], "curveOn": CURVE_ON, "curveRadius": RADII, "minRadiusM": RADIUS_M, "old_line": old_line, "base_pvi": base_pvi, "edits": edits, "ground_pvi": ground_pvi, } source = tmp / "input.json" source.write_text(json.dumps(payload), encoding="utf-8") proc = subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", str(HERE / "helper_349_0v_span.cjs"), str(source)], capture_output=True, text=True, encoding="utf-8", check=True, ) return {**payload, "new_line_py": new_line, "ts": json.loads(proc.stdout)} def test_새_계획_폴리라인_화면_서버_같음(world): ts_line = world["ts"]["new_line"] assert len(ts_line) == len(world["new_line_py"]) assert max(math.dist(a, b) for a, b in zip(ts_line, world["new_line_py"])) < 1e-3 def test_바뀐_구간_화면_서버_같음(world): span = changed_span( [tuple(p) for p in world["old_line"]], [tuple(p) for p in world["new_line_py"]] ) ts_span = world["ts"]["span"] assert span is not None and ts_span is not None for key in ("start_m", "old_end_m", "new_end_m", "delta_m"): assert span[key] == pytest.approx(ts_span[key], abs=1e-3), key assert (span["start_fixed"], span["end_fixed"]) == ( ts_span["start_fixed"], ts_span["end_fixed"], ) # 끈 꺾임점 앞앞 꺾임점의 곡선 끝(마지막 같은 점)부터 — 노선 앞머리부터 다시 풀지 않음 assert _length(OLD_NODES[: MOVED - 1]) < span["start_m"] < _length(OLD_NODES[:MOVED]) def test_옮긴_계획고_화면_서버_같음(world): span = changed_span( [tuple(p) for p in world["old_line"]], [tuple(p) for p in world["new_line_py"]] ) base, edits = carry_profile_edits(world["base_pvi"], world["edits"], span, world["ground_pvi"]) ts = world["ts"]["result"] flat = [v for n in base for v in (n["chainage_m"], n["elevation_m"])] assert flat == pytest.approx( [v for n in ts["basePvi"] for v in (n["chainage_m"], n["elevation_m"])], abs=1e-3 ) # 서버 새 선 = CSV 0.1 mm 반올림 · 화면 = 원값 assert edits == ts["edits"] assert edits["station_offsets"]["150.000"] == 0.8 # 바뀐 구간 앞 사용자 계획고 그대로