Files
Aislo/resources/tester/test_route_polyline_curve_fit.py
T
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

91 lines
4.3 KiB
Python

"""계획노선이 「직선 > 곡선 > 직선」으로 서고, 반지름이 예정노선에 맞는지.
사용자 확정(2026-09-07) — 「계획노선은 직선>곡선>직선 형태의 폴리라인임. 반지름은 법정
최소 값을 지키되 **기존 예정노선에 가까운 폴리라인을 찾는 게 키**임.」
옛 방식은 꺾임점마다 법정 하한(R 12m) 원호를 하나씩 끼웠다. 그러면 완만한 긴 곡선이
작은 원호 여러 개로 쪼개져 원본과 벌어졌다. 이제 이어진 꺾임을 한 곡선으로 묶고 그 안에서
반지름을 골라 맞춘다.
"""
import math
from common_util.common_util_route_polyline import (
_point_to_segment_m,
build_planned_polyline,
)
MIN_RADIUS = 12.0
def _arc_route(radius_m: float, *, straight_m: int = 150, step_deg: int = 3) -> list:
"""직선 → 반지름 `radius_m` 원호(90°) → 직선 을 3m 간격 점으로 흉내 낸다."""
points = [(float(x), 0.0) for x in range(-straight_m, 0, 3)]
for degree in range(0, 91, step_deg):
angle = math.radians(degree)
points.append((radius_m * math.sin(angle), radius_m - radius_m * math.cos(angle)))
points += [(radius_m, radius_m + y) for y in range(3, straight_m, 3)]
return points
def _deviation(points: list, line: list) -> tuple[float, float]:
"""예정노선 점들이 폴리라인에서 벗어난 (최대, 평균) 거리(m)."""
gaps = [
min(_point_to_segment_m(point, a, b) for a, b in zip(line, line[1:])) for point in points
]
return max(gaps), sum(gaps) / len(gaps)
def test_매끄러운_곡선은_한_곡선으로_서고_반지름을_되찾는다():
for radius in (25.0, 40.0, 80.0):
points = _arc_route(radius)
result = build_planned_polyline(points, min_radius_m=MIN_RADIUS)
fitted = [node.radius_m for node in result.nodes if node.radius_m]
assert result.curve_count == 1, f"R {radius}m 이 곡선 {result.curve_count}개로 쪼개짐"
# 원본 반지름을 10% 안으로 되찾아야 한다 — 하한(12m)에 눌러앉으면 실패.
assert abs(fitted[0] - radius) / radius < 0.10, f"R {radius}m → {fitted[0]:.1f}m"
worst, _mean = _deviation(points, result.vertices)
assert worst < 1.0, f"R {radius}m 에서 최대 {worst:.2f}m 벗어남"
def test_법정_하한_아래로는_고르지_않는다():
"""원본이 하한보다 급하게 돌면 하한을 지키고, 자리가 모자랄 때만 줄여 위반으로 남긴다."""
points = _arc_route(6.0, straight_m=60)
result = build_planned_polyline(points, min_radius_m=MIN_RADIUS)
for node in result.nodes:
if node.radius_m is None:
continue
if node.radius_m < MIN_RADIUS:
assert node.violations, "하한 미달인데 표시가 없음"
def test_급한_꺾임은_하한을_그대로_쓴다():
"""직각으로 꺾이면 원본에 가장 가까운 값이 곧 하한이다 — 키우면 원본에서 멀어진다."""
points = [(float(x), 0.0) for x in range(-120, 1, 3)]
points += [(0.0, float(y)) for y in range(3, 121, 3)]
result = build_planned_polyline(points, min_radius_m=MIN_RADIUS)
fitted = [node.radius_m for node in result.nodes if node.radius_m]
assert fitted, "직각 꺾임에 곡선이 없음"
assert abs(fitted[0] - MIN_RADIUS) < 0.5, f"직각인데 R {fitted[0]:.1f}m"
def test_묶는_것이_손해면_안_묶는다():
"""성격이 다른 굴곡이 이어 붙으면 한 곡선으로 펴지 않는다 — 크게 벌어지기 때문."""
# 오른쪽으로 완만히 돌다가 같은 방향으로 급히 꺾이는 자리
points = [(float(x), 0.0) for x in range(-90, 0, 3)]
for degree in range(0, 31, 3):
angle = math.radians(degree)
points.append((60 * math.sin(angle), 60 - 60 * math.cos(angle)))
last = points[-1]
for step in range(1, 30):
points.append(
(
last[0] + step * 3 * math.cos(math.radians(75)),
last[1] + step * 3 * math.sin(math.radians(75)),
)
)
result = build_planned_polyline(points, min_radius_m=MIN_RADIUS)
worst, _mean = _deviation(points, result.vertices)
# 묶어서 크게 벌어지느니 쪼개는 편을 골라야 한다.
assert worst < 8.0, f"최대 {worst:.2f}m 벌어짐 — 묶지 말았어야 함"