"""관 지점이 어느 선으로 읽든 그 선 위에 앉는지. B04가 관 자리를 정한 선(계획노선 CSV)과 B05 계획선은 같은 자리를 지나면서 연장이 다르다. 누가거리만 저장하면 읽는 쪽 선에 따라 관이 3~4m 미끄러진다(2026-08-30 사용자 지적). """ import sys from pathlib import Path import numpy as np from shapely.geometry import LineString, Point PROJECT_ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(PROJECT_ROOT)) from common_util.common_util_drainage_pipes import ( # noqa: E402 PIPE_SOURCE_STREAM, PipePoint, load_pipe_points_file, route_signature, save_pipe_points, ) from common_util.common_util_route_geometry import RouteVertex # noqa: E402 def _vertices(points) -> list[RouteVertex]: vertices, cumulative, previous = [], 0.0, None for x, y in points: if previous is not None: cumulative += float(np.hypot(x - previous[0], y - previous[1])) vertices.append(RouteVertex(x=float(x), y=float(y), z=0.0, chainage_m=cumulative)) previous = (x, y) return vertices def test_pipe_point_projects_onto_another_route(tmp_path, monkeypatch): # 같은 자리를 지나지만 정점을 더 촘촘히 깔아 연장이 조금 긴 선 — 실제 B05 계획선과 같은 관계 coarse = _vertices([(0.0, 0.0), (100.0, 0.0), (200.0, 40.0)]) # 경로 탐색이 만든 선 — 같은 자리를 지나지만 잔물결이 있어 연장이 1%쯤 길다 reference = LineString([(0.0, 0.0), (100.0, 0.0), (200.0, 40.0)]) fine_xy = [(0.0, 0.0)] for step in range(1, 40): along = reference.interpolate(reference.length * step / 40.0) fine_xy.append((along.x, along.y + (0.7 if step % 2 else -0.7))) fine_xy.append((200.0, 40.0)) fine = _vertices(fine_xy) coarse_line = LineString([(v.x, v.y) for v in coarse]) fine_line = LineString([(v.x, v.y) for v in fine]) assert abs(fine_line.length - coarse_line.length) > 0.3 # 연장이 실제로 다르다 import common_util.common_util_drainage_pipes as pipes_module monkeypatch.setattr(pipes_module, "pipe_points_path", lambda _p: tmp_path / "pipe_points.json") pipe = PipePoint(chainage_m=130.0, source=PIPE_SOURCE_STREAM, start_m=125.0, end_m=135.0) save_pipe_points("storage/x", route_signature(coarse), [pipe], coarse) stored = (tmp_path / "pipe_points.json").read_text(encoding="utf-8") assert '"x"' in stored and '"y"' in stored # 좌표가 실제로 남는다 # 다른 선으로 읽는다 — 지문이 다르지만 버리지 않고 투영해 온다 loaded = load_pipe_points_file(tmp_path / "pipe_points.json", route_signature(fine), fine) assert loaded is not None and len(loaded) == 1 moved = loaded[0] # ① 원래 자리를 유지한다 (좌표가 정본, 저장 정밀도 mm) original = coarse_line.interpolate(130.0) assert Point(moved.x, moved.y).distance(original) < 1e-3 # ② 새 선 위에서 그 자리에 **가장 가까운 점**에 앉는다 (더 가까운 자리는 없다) on_fine = fine_line.interpolate(moved.chainage_m) assert abs(on_fine.distance(original) - original.distance(fine_line)) < 1e-3 # ③ 누가거리는 선이 달라진 만큼 옮겨간다 assert abs(moved.chainage_m - 130.0) > 0.1 # ④ 구간 길이(시설 치수)는 그대로, 기준점과 함께 밀린다 assert abs((moved.end_m - moved.start_m) - 10.0) < 1e-6 assert abs((moved.chainage_m - moved.start_m) - 5.0) < 1e-6 def test_old_file_without_coordinates_is_discarded(tmp_path, monkeypatch): """좌표 없는 구 저장분은 이월하지 않는다 — 자리를 되짚을 근거가 없다.""" import json import common_util.common_util_drainage_pipes as pipes_module path = tmp_path / "pipe_points.json" path.write_text( json.dumps({"route_signature": "old", "points": [{"chainage_m": 10.0}]}), encoding="utf-8", ) monkeypatch.setattr(pipes_module, "pipe_points_path", lambda _p: path) fine = _vertices([(0.0, 0.0), (100.0, 0.0)]) assert load_pipe_points_file(path, route_signature(fine), fine) is None