feat(B06): 곡선부 노폭 자동 확폭 — 평면 R 기준, 곡선 바깥쪽 편측
2026-09-06 사용자 확정. 별표2 Ⅰ.2.나.(4) 확폭표(R 10~45m → 2.25~0.25m)를 측점별 평면 곡선반경에 물려 차도 폭을 넓힌다. - 확폭 방향은 **곡선 바깥쪽 편측** — 노선 폴리라인의 외적 부호로 회전 방향을 보고 바깥쪽을 정한다(좌회전이면 우측). 측점 기록에 `curve_outer_side` 로 실린다. - 차도 반폭을 좌·우로 나눠 들어 한쪽만 넓어지게 함. 확폭이 0이면 예전과 같은 대칭 단면이다. 노견·측구·사면은 그 바깥으로 그대로 밀린다. - 확폭을 더한 유효너비는 법정 상한 5m 에서 자른다(규격 3.0m 면 최대 2.0m 까지). - 계산 짝을 함께 고침 — 파이썬 `compute_cross_design` 과 브라우저 `computeCrossDesign`, 표는 양쪽에 두되 짝임을 주석으로 못 박음. 확폭 입력은 측점 기록에서 뽑는 헬퍼 하나로 9개 호출부(횡단·확정·B07 도면)에 같은 값이 가게 함. - 횡단도에 노폭 라벨 — 확폭이 걸리면 「노폭 4.5m (규격 3.0 + 확폭 1.5)」로 적는다. - 확인: 표 경계·편측 적용·5m 상한·회전 방향 판정 5건(pytest) + 브라우저 표 15건 일치. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -35,11 +35,13 @@ from B06_Section.B06_Section_Engine_Areas import (
|
||||
_trapezoid_areas,
|
||||
)
|
||||
from config.config_system import (
|
||||
CURVE_WIDENING_MAX_WIDTH_M,
|
||||
SECTION_DITCH_SIDES,
|
||||
SECTION_DITCH_TYPES,
|
||||
SECTION_GROUND_TYPE_PRESET,
|
||||
SECTION_MODES,
|
||||
STANDARD_CROSS_SECTION,
|
||||
curve_widening_m,
|
||||
)
|
||||
|
||||
|
||||
@@ -159,11 +161,17 @@ class _SectionGeometry:
|
||||
rock_boundary_offset_m: float | None = None,
|
||||
two_stage_slope: bool = False,
|
||||
ditch_enabled: bool | None = None,
|
||||
widening_left_m: float = 0.0,
|
||||
widening_right_m: float = 0.0,
|
||||
) -> None:
|
||||
half_road = group["road_width_m"] / 2.0
|
||||
self.half_road = half_road # 차도 반폭(노견 제외) — 포장 범위 기준
|
||||
self.left_extent = half_road + group["shoulder_left_m"] # 좌(+) 노면 끝
|
||||
self.right_extent = half_road + group["shoulder_right_m"] # 우(-) 노면 끝
|
||||
# 곡선부 확폭은 **한쪽으로만** 붙는다(2026-09-06 사용자 확정: 곡선 바깥쪽).
|
||||
# 그래서 반폭을 좌·우로 나눠 든다 — 확폭이 0이면 예전과 똑같은 대칭 단면이다.
|
||||
self.half_road_left = half_road + max(widening_left_m, 0.0)
|
||||
self.half_road_right = half_road + max(widening_right_m, 0.0)
|
||||
self.half_road = half_road # 규격 차도 반폭(확폭 전) — 수량·표기 기준
|
||||
self.left_extent = self.half_road_left + group["shoulder_left_m"] # 좌(+) 노면 끝
|
||||
self.right_extent = self.half_road_right + group["shoulder_right_m"] # 우(-) 노면 끝
|
||||
self.z_center = design_elevation_m
|
||||
self.cut_ratio = max(group["cut_slope_ratio"], 1e-6) # 암 구간(하단) 절토 경사
|
||||
self.fill_ratio = max(group["fill_slope_ratio"], 1e-6)
|
||||
@@ -448,6 +456,20 @@ class _SectionGeometry:
|
||||
return points
|
||||
|
||||
|
||||
def curve_widening_args(section: dict[str, Any] | None) -> dict[str, Any]:
|
||||
"""측점 기록에서 곡선부 확폭 입력을 뽑는다 — `compute_cross_design(**...)` 로 넘긴다.
|
||||
|
||||
측점마다 실려 오는 값이라 호출자마다 따로 꺼내 쓰면 빠뜨리기 쉽다(2026-09-06).
|
||||
옛 저장분에는 두 값이 없어 확폭 없이 예전과 같은 단면이 나온다.
|
||||
"""
|
||||
if not isinstance(section, dict):
|
||||
return {"plan_radius_m": None, "curve_outer_side": None}
|
||||
return {
|
||||
"plan_radius_m": section.get("plan_radius_m"),
|
||||
"curve_outer_side": section.get("curve_outer_side"),
|
||||
}
|
||||
|
||||
|
||||
def compute_cross_design(
|
||||
samples: list[dict[str, Any]],
|
||||
design_elevation_m: float | None,
|
||||
@@ -462,6 +484,8 @@ def compute_cross_design(
|
||||
two_stage_slope: bool = True,
|
||||
ditch_enabled: bool | None = None,
|
||||
surface_drop_m: float = 0.0,
|
||||
plan_radius_m: float | None = None,
|
||||
curve_outer_side: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""측점 하나의 표준횡단 설계선과 절·성토 단면적을 계산한다.
|
||||
|
||||
@@ -472,6 +496,10 @@ def compute_cross_design(
|
||||
standard: B06 설정 패널 편집값(STANDARD_CROSS_SECTION 형태). 요청값 → config 순.
|
||||
rock_boundary_offset_m: 암반 경계선 오프셋(지반선 기준, 음수=하향). 암 지반 2단계 절토용.
|
||||
two_stage_slope: 암 지반에서 암반 경계 기준 2단계 경사 적용 여부(기본 True, 토글로 해제).
|
||||
plan_radius_m: 이 측점의 평면 곡선반경(m). 곡선부 확폭(별표2 Ⅰ.2.나.(4))을 정하는
|
||||
입력이며, None·45m 이상이면 확폭이 없다.
|
||||
curve_outer_side: 곡선 **바깥쪽**("left"/"right"). 확폭은 그쪽으로만 붙는다
|
||||
(2026-09-06 사용자 확정). 값이 없으면 확폭을 넣지 않는다.
|
||||
surface_drop_m: 노면을 통째로 내리는 양(m) — 세월교 월류 높이. 구체 위 노면은 월류
|
||||
높이만큼 낮게 앉으므로 계획고를 그만큼 내려 잡는다. 단면 전체가 평행 이동하므로
|
||||
횡단경사·측구·사면 규칙은 그대로고 절·성토 면적만 따라 바뀐다(2026-08-30 사용자).
|
||||
@@ -515,6 +543,14 @@ def compute_cross_design(
|
||||
preset_key == "rock" and two_stage_slope and rock_boundary_offset_m is not None
|
||||
)
|
||||
soil_cut_ratio = _resolve_group("soil", standard)["cut_slope_ratio"]
|
||||
# 곡선부 확폭 — 표는 하한이고, 확폭을 더한 유효너비가 법정 상한(5m)을 넘지 않게 자른다.
|
||||
widening = curve_widening_m(plan_radius_m) if curve_outer_side in ("left", "right") else 0.0
|
||||
if widening > 0.0:
|
||||
room = max(CURVE_WIDENING_MAX_WIDTH_M - group["road_width_m"], 0.0)
|
||||
widening = min(widening, room)
|
||||
widening_left = widening if curve_outer_side == "left" else 0.0
|
||||
widening_right = widening if curve_outer_side == "right" else 0.0
|
||||
|
||||
geometry = _SectionGeometry(
|
||||
design_elevation_m=design_elevation_m,
|
||||
group=group,
|
||||
@@ -527,6 +563,8 @@ def compute_cross_design(
|
||||
rock_boundary_offset_m=rock_boundary_offset_m,
|
||||
two_stage_slope=enable_two_stage,
|
||||
ditch_enabled=ditch_enabled,
|
||||
widening_left_m=widening_left,
|
||||
widening_right_m=widening_right,
|
||||
)
|
||||
|
||||
# 적분 오프셋 = 지반 샘플 ∪ 설계 꼭짓점(샘플 범위 안쪽만). 꼭짓점을 넣어야
|
||||
@@ -617,7 +655,11 @@ def compute_cross_design(
|
||||
"two_stage_slope": bool(geometry.two_stage),
|
||||
"fill_slope_ratio": round(geometry.fill_ratio, 4),
|
||||
"roadbed_width_m": round(geometry.left_extent + geometry.right_extent, 4),
|
||||
"carriageway_width_m": round(group["road_width_m"], 4),
|
||||
"carriageway_width_m": round(geometry.half_road_left + geometry.half_road_right, 4),
|
||||
# 규격 폭과 확폭을 따로 남긴다 — 횡단도 라벨·수량 산출이 둘을 나눠 쓴다.
|
||||
"carriageway_standard_width_m": round(group["road_width_m"], 4),
|
||||
"widening_left_m": round(geometry.half_road_left - geometry.half_road, 4),
|
||||
"widening_right_m": round(geometry.half_road_right - geometry.half_road, 4),
|
||||
"cross_slope_pct": round(cross_slope_pct, 4),
|
||||
"ditch": ditch_spec,
|
||||
"ditch_enabled": bool(geometry.has_ditch),
|
||||
@@ -636,12 +678,12 @@ def compute_cross_design(
|
||||
# 차도(노견 제외) 양 끝점 — 포장 범위 기준(D-5).
|
||||
"carriageway_edges": {
|
||||
"left": {
|
||||
"offset_m": round(geometry.half_road, 4),
|
||||
"elevation_m": round(geometry.road_z(geometry.half_road), 4),
|
||||
"offset_m": round(geometry.half_road_left, 4),
|
||||
"elevation_m": round(geometry.road_z(geometry.half_road_left), 4),
|
||||
},
|
||||
"right": {
|
||||
"offset_m": round(-geometry.half_road, 4),
|
||||
"elevation_m": round(geometry.road_z(-geometry.half_road), 4),
|
||||
"offset_m": round(-geometry.half_road_right, 4),
|
||||
"elevation_m": round(geometry.road_z(-geometry.half_road_right), 4),
|
||||
},
|
||||
},
|
||||
"design_elevation_m": round(float(design_elevation_m), 4),
|
||||
|
||||
Reference in New Issue
Block a user