⚠ **뿌리** — `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>
129 lines
6.1 KiB
Python
129 lines
6.1 KiB
Python
"""계획노선 폴리라인화 — 원호가 접선과 실제로 맞물리는지 수치로 본다.
|
|
|
|
왜 (2026-09-06 사용자 지시) — 예상노선은 폴리라인이 아니라 점 묶음이라, 계획노선은 그것을
|
|
복사해 **폴리라인으로 바꾼 것**이어야 한다. 곡선 기준은 지식DB(별표2 Ⅰ.2.다) 값을 쓴다.
|
|
"""
|
|
|
|
import math
|
|
|
|
from common_util.common_util_route_polyline import (
|
|
build_planned_polyline,
|
|
dedupe_points,
|
|
)
|
|
|
|
|
|
def _dist(a, b):
|
|
return math.hypot(b[0] - a[0], b[1] - a[1])
|
|
|
|
|
|
def test_붙어_있는_점은_하나로():
|
|
points = [(0, 0), (0.1, 0), (0.2, 0), (10, 0)]
|
|
assert dedupe_points(points) == [(0, 0), (10, 0)]
|
|
|
|
|
|
def test_펴진_자리에도_곡선을_둔다():
|
|
"""내각 175° — **곡선 생략을 안 한다**(2026-09-07 사용자 지시).
|
|
|
|
별표2 는 「내각 155° 이상이면 곡선을 두지 않을 수 있다」고 하지만, 그러면 조금 폈다는
|
|
이유로 곡선이 사라져 **사용자가 평면 노선을 고칠 때** 잡을 손잡이가 없어진다.
|
|
그래서 꺾이는 자리에는 늘 곡선을 둔다. 아주 완만하므로 원본과 거의 겹친다.
|
|
"""
|
|
at = (100.0, 100.0 * math.tan(math.radians(5)))
|
|
result = build_planned_polyline([(0.0, 0.0), at, (200.0, 0.0)], min_radius_m=15.0)
|
|
assert result.curve_count == 1
|
|
# 꺾임점은 선 위에 없다 — 아주 완만한 원호가 그 자리를 대신한다.
|
|
assert all(_dist(v, at) > 1e-9 for v in result.vertices)
|
|
# 그래도 원본에서 크게 벗어나지 않는다.
|
|
assert max(abs(v[1] - at[1] * min(v[0], 200 - v[0]) / 100.0) for v in result.vertices) < 3.0
|
|
|
|
|
|
def test_직각_꺾임에_원호가_들어간다():
|
|
"""내각 90° — 곡선 대상. R 15m 이면 접선 15m, 양쪽 구간 100m 라 자리는 넉넉하다."""
|
|
result = build_planned_polyline([(0.0, 0.0), (100.0, 0.0), (100.0, 100.0)], min_radius_m=15.0)
|
|
node = result.nodes[1]
|
|
assert result.curve_count == 1
|
|
assert node.radius_m == 15.0
|
|
assert math.isclose(node.tangent_m, 15.0, rel_tol=1e-6) # R·tan(45°) = R
|
|
assert not node.violations
|
|
|
|
# 접선점이 꺾임점에서 tangent 만큼 떨어져 있어야 한다.
|
|
start = (100.0 - 15.0, 0.0)
|
|
end = (100.0, 15.0)
|
|
assert any(_dist(v, start) < 1e-6 for v in result.vertices)
|
|
assert any(_dist(v, end) < 1e-6 for v in result.vertices)
|
|
# 꺾임점 자체는 선 위에 없다 — 원호가 대신 지나간다.
|
|
assert all(_dist(v, (100.0, 100.0 - 100.0)) > 1e-6 for v in result.vertices[:1])
|
|
assert all(_dist(v, (100.0, 0.0)) > 1e-9 for v in result.vertices)
|
|
|
|
# 원호 위 점은 전부 중심에서 R 만큼 떨어져 있어야 한다(중심 = (85, 15)).
|
|
center = (85.0, 15.0)
|
|
arc = [v for v in result.vertices if 1e-6 < _dist(v, start) and 1e-6 < _dist(v, end)]
|
|
arc = [v for v in arc if abs(_dist(v, center) - 15.0) < 1.0]
|
|
assert arc, "원호 점이 없다"
|
|
for point in arc:
|
|
assert math.isclose(_dist(point, center), 15.0, abs_tol=1e-6)
|
|
|
|
|
|
def test_자리가_모자라면_반지름을_줄이고_위반으로_표시():
|
|
"""구간이 10m 뿐이라 R 15m 접선(15m)이 안 들어간다 — 줄이되 막지 않는다."""
|
|
result = build_planned_polyline([(0.0, 0.0), (10.0, 0.0), (10.0, 10.0)], min_radius_m=15.0)
|
|
node = result.nodes[1]
|
|
assert node.radius_m is not None
|
|
assert node.radius_m < 15.0
|
|
assert math.isclose(node.tangent_m, 5.0, rel_tol=1e-6) # 구간 10m 의 절반
|
|
assert any("최소곡선반지름 미달" in text for text in node.violations)
|
|
assert result.violation_count == 1
|
|
|
|
|
|
def test_배향곡선_하한도_표시된다():
|
|
result = build_planned_polyline([(0.0, 0.0), (6.0, 0.0), (6.0, 6.0)], min_radius_m=15.0)
|
|
node = result.nodes[1]
|
|
assert any("배향곡선 하한 미달" in text for text in node.violations)
|
|
|
|
|
|
def test_노드는_원본_점을_그대로_들고_있다():
|
|
"""폴리라인화는 **모양 정리**일 뿐 — 제어점(IP)은 원본 자리에 그대로 있어야 한다."""
|
|
points = [(0.0, 0.0), (100.0, 0.0), (100.0, 100.0), (200.0, 100.0)]
|
|
result = build_planned_polyline(points, min_radius_m=15.0)
|
|
assert [(node.x, node.y) for node in result.nodes] == points
|
|
|
|
|
|
def test_점이_둘_이하면_그대로_돌려준다():
|
|
assert build_planned_polyline([(0.0, 0.0), (10.0, 0.0)], min_radius_m=15.0).vertices == [
|
|
(0.0, 0.0),
|
|
(10.0, 0.0),
|
|
]
|
|
assert build_planned_polyline([], min_radius_m=15.0).vertices == []
|
|
|
|
|
|
def test_완만한_긴_굴곡이_삼켜지지_않는다():
|
|
"""허용오차보다 얕은 S 자 굴곡 — 노드 간격 상한이 없으면 통째로 지워진다.
|
|
|
|
2026-09-06 실측 재현: 4.5km S자 노선이 노드 20개·곡선 2곳으로 뭉개졌다.
|
|
편차 3m(허용오차 4m 아래)짜리 완만한 굴곡을 400m 에 걸쳐 만든다.
|
|
"""
|
|
from common_util.common_util_route_polyline import simplify_to_nodes
|
|
|
|
points = [(float(x), 3.0 * math.sin(math.pi * x / 200.0)) for x in range(0, 801, 4)]
|
|
# 상한이 없으면(되뽑기 끔) 굴곡이 지워져 양 끝만 남는다.
|
|
flat = simplify_to_nodes(points, 4.0, max_spacing_m=0.0)
|
|
assert len(flat) <= 3, flat
|
|
|
|
# 상한 100m 를 두면 그 구간을 다시 뽑아 굴곡이 살아난다.
|
|
kept = simplify_to_nodes(points, 4.0, max_spacing_m=100.0)
|
|
assert len(kept) > len(flat)
|
|
spans = [math.hypot(b[0] - a[0], b[1] - a[1]) for a, b in zip(kept, kept[1:])]
|
|
# 상한은 **되뽑기를 거는 방아쇠**이지 딱 잘라 지키는 값이 아니다 — 겹 수(MAX_REFINE_DEPTH)
|
|
# 안에서 허용오차를 반씩 낮춰 뽑으므로 조금 넘길 수 있다. 확인할 것은 「지워지지 않았다」.
|
|
assert max(spans) <= 100.0 * 1.2, spans
|
|
assert len(kept) >= 8, kept
|
|
|
|
|
|
def test_되뽑기는_원본_점만_남긴다():
|
|
"""되뽑아도 새 점을 만들지 않는다 — 노드는 언제나 원본 자리다."""
|
|
from common_util.common_util_route_polyline import simplify_to_nodes
|
|
|
|
points = [(float(x), 3.0 * math.sin(math.pi * x / 200.0)) for x in range(0, 801, 4)]
|
|
kept = simplify_to_nodes(points, 4.0, max_spacing_m=100.0)
|
|
assert all(point in points for point in kept)
|