Files
Aislo/resources/tester/test_route_trim_to_surface.py
eomsangdonandClaude Opus 5 0ef32b5279 chore(tester): 시험을 resources/tester/ 로 옮김 — 창끼리 건너가게
⚠ **뿌리** — `tmp/` 는 창 사이에 안 건너감(실측 확인: 상대 창이 놓은 `tmp/_sync_probe.txt`
가 시간을 두고 두 번 봐도 안 보임). 그래서 **정본(등록부 스키마)만 건너가고 그것을 읽는
시험은 안 건너가** 오늘 두 번, 같은 시험이 **연 창은 통과·받은 창은 실패**가 됐음.

- `tmp/tests/*` 를 `resources/tester/` 로 **복사**(127 파일). 내용은 **한 줄도 안 고침**
- `tmp/tests` 는 **남겨 둠** — 되돌릴 자리(사용자 지시)
- 실행: `./venv/Scripts/python.exe -m pytest resources/tester/ -q`
  옮기기 전과 **같은 수**: 617 통과 / 22 건너뜀 / 실패 0

⇒ 이제 시험·예외·까닭이 **정본과 함께** 움직임. 오늘 세운 「예외는 정본 스키마에」와 짝임.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 17:12:30 +09:00

114 lines
4.5 KiB
Python

"""계획노선이 지표면 밖으로 나갈 때의 트림 규칙.
배경(2026-09-01): 실무 자료 `용화.las`가 계획노선 2,136m 중 일부만 덮는다.
자르지 않으면 자동 설계 체인이 서피스 밖 정점에서 끊긴다. 서피스 가장자리는
점 밀도가 떨어져 불규칙하므로, 잘라 낸 쪽 끝에서 30m를 더 깎는다.
"""
import math
import numpy as np
import pytest
from common_util.common_util_route_geometry import densify_route, trim_route_to_surface
class BoxSampler:
"""x가 [x_min, x_max] 안일 때만 유효한 가짜 지표면."""
def __init__(self, x_min: float, x_max: float) -> None:
self.x_min = x_min
self.x_max = x_max
def sample_xy(self, xy: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
xs = np.asarray(xy)[:, 0]
valid = (xs >= self.x_min) & (xs <= self.x_max)
return np.zeros(len(xs)), valid
def _line(length_m: float, step_m: float = 10.0) -> list[tuple[float, float]]:
count = int(length_m / step_m) + 1
return [(index * step_m, 0.0) for index in range(count)]
def _length(points: list[tuple[float, float]]) -> float:
return sum(math.dist(points[i], points[i + 1]) for i in range(len(points) - 1))
def test_route_fully_inside_is_untouched() -> None:
"""전 구간이 지표면 안이면 손대지 않는다 — 멀쩡한 노선을 깎을 이유가 없다."""
points = _line(500.0)
assert trim_route_to_surface(points, BoxSampler(-100.0, 1000.0)) == points
def test_tail_outside_is_cut_with_edge_margin() -> None:
"""뒤쪽이 잘리면 그 끝에서 30m를 더 깎는다. 시점은 원래 자리 그대로."""
points = _line(1000.0)
kept = trim_route_to_surface(points, BoxSampler(-100.0, 600.0), edge_trim_m=30.0)
assert kept[0] == points[0], "시점은 지표면 안이므로 깎으면 안 된다"
assert kept[-1][0] == pytest.approx(570.0, abs=1.0), "600m 경계에서 30m 더 깎는다"
assert _length(kept) == pytest.approx(570.0, abs=1.0)
def test_both_ends_outside_are_cut() -> None:
points = _line(1000.0)
kept = trim_route_to_surface(points, BoxSampler(200.0, 800.0), edge_trim_m=30.0)
assert kept[0][0] == pytest.approx(230.0, abs=1.0)
assert kept[-1][0] == pytest.approx(770.0, abs=1.0)
def test_longest_valid_run_wins() -> None:
"""가장자리에서 한두 점이 튀어도 본 구간을 잃지 않는다."""
class HoleSampler:
def sample_xy(self, xy):
xs = np.asarray(xy)[:, 0]
# 0~20m 짧은 조각, 200~800m 긴 조각
valid = ((xs <= 20.0) | ((xs >= 200.0) & (xs <= 800.0))) & (xs <= 800.0)
return np.zeros(len(xs)), valid
kept = trim_route_to_surface(_line(1000.0), HoleSampler(), edge_trim_m=30.0)
assert kept[0][0] == pytest.approx(230.0, abs=1.0), "긴 구간을 골라야 한다"
assert kept[-1][0] == pytest.approx(770.0, abs=1.0)
def test_route_entirely_outside_returns_empty() -> None:
assert trim_route_to_surface(_line(500.0), BoxSampler(9000.0, 9999.0)) == []
def test_edge_margin_eating_everything_returns_empty() -> None:
"""남는 구간이 여유보다 짧으면 빈 목록 — 못 미더운 구간으로 설계하지 않는다."""
kept = trim_route_to_surface(_line(1000.0), BoxSampler(500.0, 540.0), edge_trim_m=30.0)
assert kept == []
def test_sampler_failure_keeps_route() -> None:
"""지표면을 못 읽으면 노선을 자르지 않는다 — 트림 실패가 설계를 막으면 안 된다."""
class BrokenSampler:
def sample_xy(self, xy):
raise OSError("DTM 손상")
points = _line(300.0)
assert trim_route_to_surface(points, BrokenSampler()) == points
def test_densify_keeps_shape_and_original_vertices() -> None:
"""조밀화는 같은 직선 위에 점을 더 찍을 뿐 — 형상도 끝점도 바뀌지 않는다."""
points = [(0.0, 0.0), (100.0, 0.0), (100.0, 50.0)]
dense = densify_route(points, 4.0)
assert dense[0] == points[0] and dense[-1] == points[-1]
for original in points:
assert original in dense, "원래 정점은 모두 남아야 한다"
gaps = [math.dist(dense[i], dense[i + 1]) for i in range(len(dense) - 1)]
assert max(gaps) <= 4.0 + 1e-9
assert _length(dense) == pytest.approx(_length(points)), "연장이 바뀌면 형상이 바뀐 것"
def test_densify_leaves_close_vertices_alone() -> None:
"""이미 문턱 이하면 손대지 않는다 — 쓸데없이 정점을 불리지 않는다."""
points = [(0.0, 0.0), (2.0, 0.0), (4.0, 0.0)]
assert densify_route(points, 4.0) == points