260725_1
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
경사비는 수평:수직 = ratio:1 (예: 1:1.2 → ratio=1.2).
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from config.config_system import (
|
||||
@@ -170,15 +171,28 @@ class _SectionGeometry:
|
||||
ditch_side: str,
|
||||
ditch_type: str,
|
||||
cross_slope_pct: float,
|
||||
ground_at: Callable[[float], float] | None = None,
|
||||
soil_cut_ratio: float | None = None,
|
||||
rock_boundary_offset_m: float | None = None,
|
||||
two_stage_slope: bool = False,
|
||||
) -> None:
|
||||
half_road = group["road_width_m"] / 2.0
|
||||
self.left_extent = half_road + group["shoulder_left_m"] # 좌(+) 노면 끝
|
||||
self.right_extent = half_road + group["shoulder_right_m"] # 우(-) 노면 끝
|
||||
self.z_center = design_elevation_m
|
||||
self.cut_ratio = max(group["cut_slope_ratio"], 1e-6)
|
||||
self.cut_ratio = max(group["cut_slope_ratio"], 1e-6) # 암 구간(하단) 절토 경사
|
||||
self.fill_ratio = max(group["fill_slope_ratio"], 1e-6)
|
||||
self.left_role, self.right_role = _side_role(section_mode)
|
||||
self.ditch_side = ditch_side
|
||||
# 2단계 절토: 암반 경계선(지반선 + rock_boundary_offset) 아래는 암 경사(cut_ratio),
|
||||
# 위는 토사 경사(soil_cut_ratio)를 쓴다. 경계 아래→위 전환점(무릎)을 측별로 미리 구한다.
|
||||
self.soil_cut_ratio = max(soil_cut_ratio or group["cut_slope_ratio"], 1e-6)
|
||||
self.two_stage = bool(
|
||||
two_stage_slope and ground_at is not None and rock_boundary_offset_m is not None
|
||||
)
|
||||
self._ground_at = ground_at
|
||||
self._rock_offset = rock_boundary_offset_m or 0.0
|
||||
self._rock_knee: dict[str, tuple[float, float] | None] = {}
|
||||
# 성토만(양성)이면 측구 없음(합의). 절토가 있는 단면만 측구를 판다.
|
||||
self.has_ditch = section_mode != "both_fill"
|
||||
self.ditch_type = ditch_type
|
||||
@@ -186,6 +200,19 @@ class _SectionGeometry:
|
||||
slope = cross_slope_pct / 100.0
|
||||
self.slope_per_offset = -slope if ditch_side == "left" else slope
|
||||
|
||||
# 편절편성에서 계획고가 지반보다 많이 낮으면 성토측 지반이 노면 끝보다 높아
|
||||
# 실제로는 양측 절토가 된다. 그 측 역할을 절토로 자동 전환한다(측구 위치는
|
||||
# ditch_side 그대로 유지, 전환된 측도 2단 경사 대상). 양절·양성(사용자 명시)은
|
||||
# 손대지 않는다.
|
||||
if section_mode in ("left_cut", "right_cut") and ground_at is not None:
|
||||
for side, edge in (("left", self.left_extent), ("right", -self.right_extent)):
|
||||
role = self.left_role if side == "left" else self.right_role
|
||||
if role == "fill" and ground_at(edge) > self.road_z(edge) + 1e-3:
|
||||
if side == "left":
|
||||
self.left_role = "cut"
|
||||
else:
|
||||
self.right_role = "cut"
|
||||
|
||||
# 측구 꼭짓점(측구측 노면 끝 기준, 바깥 방향 부호 적용).
|
||||
self.ditch_points: list[tuple[float, float]] = []
|
||||
edge_offset = self.left_extent if ditch_side == "left" else -self.right_extent
|
||||
@@ -228,6 +255,48 @@ class _SectionGeometry:
|
||||
return abs(outer[0]), outer[1]
|
||||
return edge_offset, edge_z
|
||||
|
||||
def _rock_boundary_z(self, side: str, dist: float) -> float:
|
||||
"""측·거리(절대 오프셋)에서 암반 경계선 표고 = 지반선 + 오프셋(음수=하향)."""
|
||||
signed = dist if side == "left" else -dist
|
||||
assert self._ground_at is not None # two_stage일 때만 호출
|
||||
return self._ground_at(signed) + self._rock_offset
|
||||
|
||||
def knee(self, side: str) -> tuple[float, float] | None:
|
||||
"""절토 사면이 암반 경계선을 지나는 전환점(무릎 거리, 표고)을 구한다(측별 캐시).
|
||||
|
||||
노면 끝(사면 시작)에서 암 경사(cut_ratio)로 올라가며 경계선을 만나면 그 지점부터
|
||||
토사 경사로 완만해진다. 시작부터 경계 위면 무릎=시작(전부 토사), 끝까지 못 만나면
|
||||
None(전부 암). 경계선은 지반을 따라 변하므로 세밀 행진으로 교차점을 찾는다.
|
||||
"""
|
||||
if not self.two_stage:
|
||||
return None
|
||||
if side in self._rock_knee:
|
||||
return self._rock_knee[side]
|
||||
start_dist, start_z = self._slope_start(side)
|
||||
diff_prev = start_z - self._rock_boundary_z(side, start_dist)
|
||||
result: tuple[float, float] | None
|
||||
if diff_prev >= 0:
|
||||
result = (start_dist, start_z) # 시작부터 토사(경계 위)
|
||||
else:
|
||||
result = None
|
||||
step = 0.05
|
||||
dist_prev = start_dist
|
||||
dist = start_dist + step
|
||||
while dist <= start_dist + 200.0:
|
||||
z_rock = start_z + (dist - start_dist) / self.cut_ratio
|
||||
diff = z_rock - self._rock_boundary_z(side, dist)
|
||||
if diff >= 0:
|
||||
span = diff - diff_prev
|
||||
ratio = (-diff_prev) / span if abs(span) > 1e-9 else 0.0
|
||||
knee_dist = dist_prev + (dist - dist_prev) * ratio
|
||||
knee_z = start_z + (knee_dist - start_dist) / self.cut_ratio
|
||||
result = (knee_dist, knee_z)
|
||||
break
|
||||
dist_prev, diff_prev = dist, diff
|
||||
dist += step
|
||||
self._rock_knee[side] = result
|
||||
return result
|
||||
|
||||
def design_z(self, offset_m: float, ground_m: float) -> float:
|
||||
"""offset 하나의 설계 표고(사면은 지반 교차점 이후 지반 추종)."""
|
||||
side = "left" if offset_m >= 0 else "right"
|
||||
@@ -253,17 +322,32 @@ class _SectionGeometry:
|
||||
return points[-1][1]
|
||||
role = self.left_role if side == "left" else self.right_role
|
||||
start_dist, start_z = self._slope_start(side)
|
||||
run = abs(offset_m) - start_dist
|
||||
dist = abs(offset_m)
|
||||
run = dist - start_dist
|
||||
if role == "cut":
|
||||
slope_line = start_z + run / self.cut_ratio
|
||||
knee = self.knee(side) if self.two_stage else None
|
||||
if knee is not None:
|
||||
knee_dist, knee_z = knee
|
||||
if dist <= knee_dist: # 암반 구간(경계 아래): 암 경사
|
||||
slope_line = start_z + (dist - start_dist) / self.cut_ratio
|
||||
else: # 토사 구간(경계 위): 무릎에서 토사 경사로 완만
|
||||
slope_line = knee_z + (dist - knee_dist) / self.soil_cut_ratio
|
||||
else:
|
||||
slope_line = start_z + run / self.cut_ratio
|
||||
return min(slope_line, ground_m)
|
||||
fill_line = start_z - run / self.fill_ratio
|
||||
return max(fill_line, ground_m)
|
||||
|
||||
def breakpoints(self) -> list[float]:
|
||||
"""적분·설계선에 반드시 포함할 설계 꼭짓점 오프셋 목록."""
|
||||
"""적분·설계선에 반드시 포함할 설계 꼭짓점 오프셋 목록(2단계 무릎 포함)."""
|
||||
points = [0.0, self.left_extent, -self.right_extent]
|
||||
points.extend(offset for offset, _z in self.ditch_points)
|
||||
if self.two_stage:
|
||||
for side in ("left", "right"):
|
||||
role = self.left_role if side == "left" else self.right_role
|
||||
knee = self.knee(side) if role == "cut" else None
|
||||
if knee is not None:
|
||||
points.append(knee[0] if side == "left" else -knee[0])
|
||||
return points
|
||||
|
||||
|
||||
@@ -277,6 +361,8 @@ def compute_cross_design(
|
||||
ditch_type: str = "standard",
|
||||
paved: bool = False,
|
||||
standard: dict[str, Any] | None = None,
|
||||
rock_boundary_offset_m: float | None = None,
|
||||
two_stage_slope: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
"""측점 하나의 표준횡단 설계선과 절·성토 단면적을 계산한다.
|
||||
|
||||
@@ -285,6 +371,8 @@ def compute_cross_design(
|
||||
ditch_type: 일반(standard)/L형(l_type, 암 구간 전용).
|
||||
paved: 포장 중첩 여부 — 횡단경사·포장층만 포장 그룹 값으로 바꾼다(기하는 지반유형).
|
||||
standard: B06 설정 패널 편집값(STANDARD_CROSS_SECTION 형태). 요청값 → config 순.
|
||||
rock_boundary_offset_m: 암반 경계선 오프셋(지반선 기준, 음수=하향). 암 지반 2단계 절토용.
|
||||
two_stage_slope: 암 지반에서 암반 경계 기준 2단계 경사 적용 여부(기본 True, 토글로 해제).
|
||||
"""
|
||||
if ground_type not in SECTION_GROUND_TYPE_PRESET:
|
||||
raise ValueError(f"지원하지 않는 지반유형입니다: {ground_type}")
|
||||
@@ -317,6 +405,12 @@ def compute_cross_design(
|
||||
if len(valid) < 2:
|
||||
raise ValueError("유효한 지반 샘플이 부족해 횡단 설계를 계산할 수 없습니다.")
|
||||
|
||||
ground_at = _ground_interpolator(valid)
|
||||
# 2단계 절토는 암 프리셋에서만, 암반 경계 오프셋이 있을 때만 켠다.
|
||||
enable_two_stage = (
|
||||
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"]
|
||||
geometry = _SectionGeometry(
|
||||
design_elevation_m=design_elevation_m,
|
||||
group=group,
|
||||
@@ -324,8 +418,11 @@ def compute_cross_design(
|
||||
ditch_side=resolved_ditch_side,
|
||||
ditch_type=ditch_type,
|
||||
cross_slope_pct=cross_slope_pct,
|
||||
ground_at=ground_at,
|
||||
soil_cut_ratio=soil_cut_ratio,
|
||||
rock_boundary_offset_m=rock_boundary_offset_m,
|
||||
two_stage_slope=enable_two_stage,
|
||||
)
|
||||
ground_at = _ground_interpolator(valid)
|
||||
|
||||
# 적분 오프셋 = 지반 샘플 ∪ 설계 꼭짓점(샘플 범위 안쪽만). 꼭짓점을 넣어야
|
||||
# 측구 모서리·노면 끝이 잘리지 않아 면적과 설계선이 정확해진다.
|
||||
@@ -378,6 +475,8 @@ def compute_cross_design(
|
||||
"ditch_side": resolved_ditch_side,
|
||||
"ditch_type": ditch_type if geometry.has_ditch else None,
|
||||
"cut_slope_ratio": round(geometry.cut_ratio, 4),
|
||||
"soil_cut_slope_ratio": round(geometry.soil_cut_ratio, 4),
|
||||
"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),
|
||||
@@ -403,6 +502,9 @@ def compute_cross_design(
|
||||
}
|
||||
if paved:
|
||||
result["pavement_thickness_m"] = round(paved_group["pavement_thickness_m"], 4)
|
||||
# 암 지반은 경계선 오프셋을 echo해 프론트가 세션값 없이도 오버레이·재계산에 쓰게 한다.
|
||||
if preset_key == "rock" and rock_boundary_offset_m is not None:
|
||||
result["rock_boundary_offset_m"] = round(float(rock_boundary_offset_m), 4)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user