"""곡선부 확폭 — 표·경계·편측 적용·상한을 확인한다(2026-09-06). 파이썬과 브라우저가 한 세트이므로 같은 기대값을 `test_b06_curve_widening.mjs` 도 쓴다. 값이 갈리면 두 화면이 다른 단면을 그린다. """ import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) import numpy as np # noqa: E402 from B05_Profile.B05_Profile_Engine_Sections_Core import _plan_radii # noqa: E402 from B06_Section.B06_Section_Engine_Design import compute_cross_design # noqa: E402 from config.config_system_design import curve_widening_m # noqa: E402 # (반경 m, 기대 확폭 m) — 별표2 Ⅰ.2.나.(4). 경계는 "이상 ~ 미만". TABLE_CASES = [ (9.9, 0.0), (10.0, 2.25), (12.999, 2.25), (13.0, 2.0), (14.0, 1.75), (15.0, 1.5), (18.0, 1.25), (20.0, 1.0), (25.0, 0.75), (30.0, 0.5), (40.0, 0.25), (45.0, 0.0), (200.0, 0.0), ] def _flat_samples() -> list[dict]: return [{"offset_m": float(o), "elevation_m": 100.0, "valid": True} for o in range(-25, 26)] def test_widening_table_boundaries() -> None: for radius, expected in TABLE_CASES: assert curve_widening_m(radius) == expected, radius assert curve_widening_m(None) == 0.0 def test_widening_applies_to_outer_side_only() -> None: samples = _flat_samples() base = compute_cross_design(samples, 100.0, ground_type="soil", section_mode="both_fill") right = compute_cross_design( samples, 100.0, ground_type="soil", section_mode="both_fill", plan_radius_m=16.0, curve_outer_side="right", ) left = compute_cross_design( samples, 100.0, ground_type="soil", section_mode="both_fill", plan_radius_m=16.0, curve_outer_side="left", ) assert base["widening_left_m"] == 0.0 and base["widening_right_m"] == 0.0 assert right["widening_right_m"] == 1.5 and right["widening_left_m"] == 0.0 assert left["widening_left_m"] == 1.5 and left["widening_right_m"] == 0.0 # 확폭은 붙은 쪽 차도 끝만 밖으로 민다. assert right["carriageway_edges"]["left"] == base["carriageway_edges"]["left"] assert right["carriageway_edges"]["right"]["offset_m"] < base["carriageway_edges"]["right"]["offset_m"] assert right["carriageway_width_m"] == base["carriageway_width_m"] + 1.5 def test_widening_capped_by_legal_max_width() -> None: """확폭을 더한 유효너비는 5m 를 넘지 않는다(별표2 — 최대 5미터까지).""" samples = _flat_samples() design = compute_cross_design( samples, 100.0, ground_type="soil", section_mode="both_fill", plan_radius_m=11.0, # 표값 2.25m curve_outer_side="right", ) assert design["carriageway_width_m"] <= 5.0 + 1e-9 # 규격 3.0m 이면 2.0m 까지만 붙는다. assert design["widening_right_m"] == 2.0 def test_plan_radius_and_outer_side_from_polyline() -> None: """반지름 50m 원호에서 반경 50m 가 나오고, 좌회전이면 바깥은 우측이다.""" angles = np.linspace(0.0, np.pi / 2, 400) radius = 50.0 left_turn = np.column_stack([radius * np.cos(angles), radius * np.sin(angles)]) chainage = np.r_[ 0.0, np.cumsum(np.hypot(np.diff(left_turn[:, 0]), np.diff(left_turn[:, 1]))) ] radii, sides = _plan_radii(left_turn, chainage, np.array([40.0]), float(chainage[-1])) assert abs(radii[0] - radius) < 0.1 assert sides[0] == "right" right_turn = np.column_stack([radius * np.cos(-angles), radius * np.sin(-angles)]) chainage2 = np.r_[ 0.0, np.cumsum(np.hypot(np.diff(right_turn[:, 0]), np.diff(right_turn[:, 1]))) ] _, sides2 = _plan_radii(right_turn, chainage2, np.array([40.0]), float(chainage2[-1])) assert sides2[0] == "left" def test_straight_route_has_no_radius() -> None: line = np.array([[0.0, 0.0], [300.0, 0.0]]) radii, sides = _plan_radii(line, np.array([0.0, 300.0]), np.array([50.0, 150.0]), 300.0) assert radii == [None, None] assert sides == [None, None] def test_widening_taper_runs_before_and_after_curve() -> None: """곡선 앞뒤 10m 안 측점은 확폭이 0 → W 로 이어진다(2026-09-06).""" from B05_Profile.B05_Profile_Engine_Sections_Core import _curve_widenings # 5m 간격 측점: 20~30m 구간만 곡선(R=16 → 1.5m), 나머지는 직선. chainage = np.arange(0.0, 55.0, 5.0) radii: list[float | None] = [None] * len(chainage) sides: list[str | None] = [None] * len(chainage) for index, value in enumerate(chainage): if 20.0 <= value <= 30.0: radii[index] = 16.0 sides[index] = "right" widenings, out_sides = _curve_widenings(chainage, radii, sides) by_chainage = dict(zip(chainage.tolist(), widenings, strict=True)) # 곡선 안은 표값 그대로. assert by_chainage[20.0] == 1.5 and by_chainage[25.0] == 1.5 and by_chainage[30.0] == 1.5 # 앞뒤 10m 는 선형으로 줄어든다 — 5m 지점에서 절반. assert by_chainage[15.0] == 0.75 and by_chainage[35.0] == 0.75 assert by_chainage[10.0] == 0.0 and by_chainage[40.0] == 0.0 # 테이퍼 측점도 확폭이 붙는 쪽을 물려받는다. assert out_sides[chainage.tolist().index(15.0)] == "right" # 테이퍼 밖은 방향이 없다. assert out_sides[chainage.tolist().index(5.0)] is None def test_widening_without_curve_stays_zero() -> None: from B05_Profile.B05_Profile_Engine_Sections_Core import _curve_widenings chainage = np.arange(0.0, 60.0, 20.0) widenings, sides = _curve_widenings(chainage, [None] * 3, [None] * 3) assert widenings == [0.0, 0.0, 0.0] assert sides == [None, None, None]