- 624-3 도구줄 = 공용 표 「b05」 · 아래 줄 = 공용 겹 줄(띠 · 올린 임도망도 숨김 · 처음 = 등고선 · 하천) · 범례 = 공용 legendRows(상태 글판 위) - 노선 띠 300 m 자르기 = B05 자기 칸(등고선 · 하천 · 1 m)만 · 그 밖 겹 = 안 자르는 둘째 GL 칸(맨 밑 · 켠 겹 있을 때만 · GL 만) · 등고선 끔 = 등고 글도 숨김 - 626-1 추천 보기 = 도구줄 아이콘 + 아래 판(영선 · 토공 최소 · 기울기 최소 · 비중 계산 · 끔) — route_recommend 저장본(지금 설계 지형) 선만 · 다시 셈 · 쓰기 없음 - 지형 칩 → 확인창 → 새 POST /route/design-terrain(설계 지형만 바꿈 · 노선 저장 체인 다시 · 노선 · 초기값 · 추천 저장본 그대로 · 잠금 · 실패 되돌림) - 걷음 — 추천 모달(_Recommend · _Help · _Map · CSS) · 좌측 「노선 추천」 칸 · saveRecommendedRoute · UI_RouteReset(서버 as_initial 길 · 시험은 남김) - 부품 상자 — createPickMenu(아이콘 고르기 메뉴 · 판 = body fixed · ✓) - 낡은 시험 고침 · 지움 — 모달 · 좌측 칸 · [적용] 화면 배선을 보던 시험(283 · 346_3 · 349 · 364_4 · 383_1 · 390_5 · 418 · 421 · 421_2 · 476_2 · 509_2 · 509_5 · 551_2 · 600_3 · b05_routeedit_initial · design_view_parity · modal_top · 133_1 · 133_2) · test_508_2 · helper_283 지움(모달 전용) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
109 lines
4.7 KiB
Python
109 lines
4.7 KiB
Python
"""383-1 R = 일반측점 기본 제약 — 새 초기 노선 맞춤 + [측점 스냅] 켬 · 추천 후보 맞춤 · [적용] 켬.
|
|
|
|
./venv/Scripts/python.exe -m pytest resources/tester/test_383_1_route_station_default.py -q
|
|
|
|
새 사본(예상노선만) → 초기 노선 곡선 MC 가 일반측점 위(≈ 0 밖) · 곡선표 `station_snap` 켬 ·
|
|
추천 「예상노선 추적」(켬) 노드 = 초기 노선 노드 · 측점 밖 곡선 = 「못 지킨 구간」(station) ·
|
|
옛 사본(초기 노선 이미 있음)은 파일 · 깃발 그대로(말없이 측점 안 바뀜) · 추천 [적용] 은 스냅 켬.
|
|
합성 예상노선 · 지형으로 초기 노선과 추천 결과가 같은 기본 제약을 지키는지 확인한다.
|
|
"""
|
|
|
|
import json
|
|
import math
|
|
from pathlib import Path
|
|
|
|
from shapely.geometry import LineString, Point
|
|
|
|
from B05_Profile.B05_Profile_Engine_RouteInitial import generate_initial_route
|
|
from B05_Profile.B05_Profile_Engine_RouteInitial_Station import MC_TOL_M, STATION_M
|
|
from B05_Profile.B05_Profile_Engine_Sections import load_station_snap
|
|
from B05_Profile.B05_Profile_Router_Replan import (
|
|
_curves_path,
|
|
_ensure_planned_initial,
|
|
_nodes_path,
|
|
_write_planned_polyline,
|
|
)
|
|
from common_util.common_util_route_geometry import (
|
|
expected_route_csv_path,
|
|
planned_route_initial_path,
|
|
read_planned_route_csv,
|
|
write_route_csv,
|
|
)
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
RADIUS = 12.0
|
|
EXPECTED = [(float(x), 200.0 + 40.0 * math.sin(x / 60.0)) for x in range(50, 753, 20)]
|
|
|
|
|
|
def _copy_expected(tmp_path: Path) -> Path:
|
|
target = expected_route_csv_path(tmp_path)
|
|
write_route_csv(target, [{"x": x, "y": y} for x, y in EXPECTED])
|
|
return tmp_path
|
|
|
|
|
|
def _off_station(path: Path) -> tuple[int, int]:
|
|
"""(측점 밖 곡선 수, 곡선 수) — 저장 파일에서 다시 잼(MC = BC · EC 누가거리 가운데)."""
|
|
line = LineString([(v.x, v.y) for v in read_planned_route_csv(path).vertices])
|
|
curves = json.loads(_curves_path(path).read_text(encoding="utf-8"))["curves"]
|
|
taken, off = set(), 0
|
|
for curve in curves:
|
|
mc = (line.project(Point(curve["start"])) + line.project(Point(curve["end"]))) / 2
|
|
n = round(mc / STATION_M)
|
|
if n >= 1 and abs(mc - n * STATION_M) <= MC_TOL_M and n not in taken:
|
|
taken.add(n)
|
|
else:
|
|
off += 1
|
|
return off, len(curves)
|
|
|
|
|
|
def test_new_copy_initial_on_stations_and_flag_on(tmp_path: Path) -> None:
|
|
root = _copy_expected(tmp_path)
|
|
assert _ensure_planned_initial(root, RADIUS) is not None
|
|
initial = planned_route_initial_path(root)
|
|
off, total = _off_station(initial)
|
|
print(f"새 초기 노선 곡선 {total} · 측점 밖 {off}")
|
|
assert total and off <= 1
|
|
assert load_station_snap(root) is True
|
|
|
|
|
|
def test_follow_nodes_equal_initial(tmp_path: Path) -> None:
|
|
"""추천 「예상노선 추적」(측점 맞춤 켬) = 새 초기 노선 · 측점 밖 곡선 수 = station 목록 수."""
|
|
import test_351_2_curve_stations as t351
|
|
|
|
root = _copy_expected(tmp_path)
|
|
_ensure_planned_initial(root, RADIUS)
|
|
terrain, streams = t351._terrain(), []
|
|
expected = [(v.x, v.y) for v in read_planned_route_csv(expected_route_csv_path(root)).vertices]
|
|
result = generate_initial_route(
|
|
expected,
|
|
terrain,
|
|
streams,
|
|
mode="follow",
|
|
weights=None,
|
|
max_grade=0.14,
|
|
min_radius_m=RADIUS,
|
|
with_profile=False,
|
|
)
|
|
saved = [
|
|
(v.x, v.y)
|
|
for v in read_planned_route_csv(_nodes_path(planned_route_initial_path(root))).vertices
|
|
]
|
|
nodes = [(node["x"], node["y"]) for node in result["nodes"]]
|
|
assert len(nodes) == len(saved)
|
|
assert all(abs(a[0] - b[0]) <= 1e-3 and abs(a[1] - b[1]) <= 1e-3 for a, b in zip(nodes, saved))
|
|
misses = [v for v in result["metrics"]["violations"] if v["kind"] == "station"]
|
|
assert len(misses) == result["metrics"]["over_limit"]["station"] == result["off_station_curves"]
|
|
assert "unsnapped" not in result # 측점 맞춤 필수 — 맞추기 전 선 안 냄(419)
|
|
|
|
|
|
def test_old_copy_flag_and_line_unchanged(tmp_path: Path) -> None:
|
|
"""옛 사본 — 초기 노선(깃발 칸 없음)이 이미 있으면 다시 안 씀 · 스냅 끔 그대로."""
|
|
root = _copy_expected(tmp_path)
|
|
initial = planned_route_initial_path(root)
|
|
points = [(v.x, v.y) for v in read_planned_route_csv(expected_route_csv_path(root)).vertices]
|
|
_write_planned_polyline(initial, points, RADIUS) # 옛 규칙(맞춤 없음 · 칸 없음)
|
|
before = {p: p.read_bytes() for p in (initial, _curves_path(initial), _nodes_path(initial))}
|
|
assert _ensure_planned_initial(root, RADIUS) is None
|
|
assert {p: p.read_bytes() for p in before} == before
|
|
assert load_station_snap(root) is False
|