feat(B05): 노선 추천 정리 — 알약 다섯 한 줄 · 종단은 갈래가 정함 · 오른쪽 위 [적용]/[닫기] · 지도 안 옵션 겹창 · 추천 토량에 곡선부 확폭(77)
- 알약 = 예상노선 추적(종단 없음) · 영선 · 토공 최소 · 기울기 최소 · 비중 계산(넷은 종단 늘 반영) · 안전 우선 · 계류 횡단 최소 · 공사비 어림 최소 화면에서 뺌 - 「종단 반영」 켬/끔 단추 없앰(추천 창 · 종단 띠) — 띠 · [확인]은 지금 추천 갈래로 종단 여부 - ✕ 없애고 [적용] · [닫기]를 제목줄 오른쪽 위로 · 옵션 칸은 지도 왼쪽 위 작은 겹창 · 창 폭 넓힘 - 적용한 추천 갈래가 있으면 창을 다시 열 때 그 갈래가 골라진 채 - 도움말: 종단 반영 = 측점마다 횡단을 보고 정한 종단(적분값 아님) - 균형 종단 단면적에 곡선부 확폭(B05 측점 생성과 같은 셈) — 빼서 확정 뒤 성토가 1.7배 벌어졌음(노선 267 같은 계획고: 성 10,102 → 17,819 · 본 설계 16,140) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019EMfGERKUg5nMMNQsyVZ2j
This commit is contained in:
@@ -10,7 +10,8 @@
|
||||
1. 측점(20 m)마다 지반 횡단(±20 m · 0.5 m)을 DEM 에서 뜬다 — B05 측점 생성과 같은 꼴
|
||||
(좌 = +offset · 상단측 = 좌우 평균 표고가 높은 쪽 · 그쪽 절토).
|
||||
2. 측점마다 계획고 ±Δ 의 절 · 성 단면적 곡선 — B06 `compute_cross_design` 그대로(지반유형
|
||||
리핑암 = B06 기본값) · `AREA_STEP_M` 간격으로 재고 사이는 직선 보간.
|
||||
리핑암 = B06 기본값 · 곡선부 확폭 = B05 측점 생성과 같은 `_plan_radii` · `_curve_widenings`
|
||||
— 빼면 확정 뒤 성토가 1.7배로 벌어짐 · PLAN 77-7) · `AREA_STEP_M` 간격으로 재고 사이는 보간.
|
||||
3. 동적계획(앞 두 측점 상태)으로 측점 계획고를 고른다 — 목표 = 절토(다짐 환산) + 성토 ㎥ +
|
||||
λ × (성토 − 절토). λ 를 이분해 |절 − 성| / 큰 쪽이 허용(10 %) 안인 가장 작은 λ.
|
||||
· 종단기울기 상한 — 넘지 못함.
|
||||
@@ -57,8 +58,8 @@ GRADE_EPS = 1e-6
|
||||
|
||||
def route_sections(
|
||||
vertices: list[tuple[float, float]], terrain: Terrain
|
||||
) -> tuple[np.ndarray, np.ndarray, list[list[dict[str, Any]]], list[str]]:
|
||||
"""측점 누가거리 · 중심 지반고 · 측점별 지반 횡단 샘플 · 단면유형(상단측 절토)."""
|
||||
) -> tuple[np.ndarray, np.ndarray, list[list[dict[str, Any]]], list[str], list[dict[str, Any]]]:
|
||||
"""측점 누가거리 · 중심 지반고 · 측점별 지반 횡단 샘플 · 단면유형(상단측 절토) · 곡선부 확폭."""
|
||||
xy = np.asarray(vertices, dtype=np.float64)
|
||||
keep = np.r_[True, np.hypot(*np.diff(xy, axis=0).T) > 1e-9]
|
||||
xy = xy[keep]
|
||||
@@ -100,11 +101,27 @@ def route_sections(
|
||||
lefts, rights = row[(offsets > 0) & flags], row[(offsets < 0) & flags]
|
||||
up = "right" if lefts.size and rights.size and rights.mean() > lefts.mean() else "left"
|
||||
modes.append(f"{up}_cut")
|
||||
return at, ground, sections, modes
|
||||
return at, ground, sections, modes, _widenings(xy, chain, at)
|
||||
|
||||
|
||||
def _widenings(xy: np.ndarray, chain: np.ndarray, at: np.ndarray) -> list[dict[str, Any]]:
|
||||
"""측점마다 `compute_cross_design` 확폭 인자 — B05 측점 생성(곡선표 없을 때)과 같은 셈."""
|
||||
from B05_Profile.B05_Profile_Engine_Sections_Core import _curve_widenings, _plan_radii
|
||||
|
||||
radii, outer = _plan_radii(xy, chain, at, float(chain[-1]))
|
||||
widenings, sides = _curve_widenings(at, radii, outer)
|
||||
return [
|
||||
{"plan_radius_m": r, "curve_outer_side": s, "curve_widening_m": w}
|
||||
for r, s, w in zip(radii, sides, widenings)
|
||||
]
|
||||
|
||||
|
||||
def _area_curves(
|
||||
sections: list[list[dict[str, Any]]], modes: list[str], ground: np.ndarray, levels: np.ndarray
|
||||
sections: list[list[dict[str, Any]]],
|
||||
modes: list[str],
|
||||
ground: np.ndarray,
|
||||
levels: np.ndarray,
|
||||
curves: list[dict[str, Any]] | None = None,
|
||||
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
||||
"""측점 · 계획고 후보마다 (절토 자연 ㎡, 절토 다짐 환산 ㎡, 성토 ㎡)."""
|
||||
from B06_Section.B06_Section_Engine_Design import compute_cross_design
|
||||
@@ -113,6 +130,7 @@ def _area_curves(
|
||||
shape = levels.shape
|
||||
cut, cut_c, fill = np.zeros(shape), np.zeros(shape), np.zeros(shape)
|
||||
for i, (samples, mode) in enumerate(zip(sections, modes)):
|
||||
widen = curves[i] if curves else {}
|
||||
delta = levels[i] - ground[i]
|
||||
lo = np.floor(delta.min() / AREA_STEP_M) * AREA_STEP_M
|
||||
at = np.arange(lo, delta.max() + AREA_STEP_M, AREA_STEP_M)
|
||||
@@ -120,7 +138,11 @@ def _area_curves(
|
||||
for d in at:
|
||||
try:
|
||||
r = compute_cross_design(
|
||||
samples, float(ground[i] + d), ground_type=GROUND_TYPE, section_mode=mode
|
||||
samples,
|
||||
float(ground[i] + d),
|
||||
ground_type=GROUND_TYPE,
|
||||
section_mode=mode,
|
||||
**widen,
|
||||
)
|
||||
except ValueError: # 지반 샘플이 모자란 측점 — 토량 모름(0)
|
||||
rows.append((0.0, 0.0, 0.0))
|
||||
@@ -196,6 +218,7 @@ def balanced_profile(
|
||||
*,
|
||||
max_grade: float,
|
||||
design_speed: int = 20,
|
||||
curves: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""상한(종단기울기 · 종단곡선)을 지키며 절 · 성토를 횡단 ㎥ 로 맞춘 계획선과 토량.
|
||||
|
||||
@@ -208,7 +231,9 @@ def balanced_profile(
|
||||
weight[1:] += spans / 2.0
|
||||
offsets = np.arange(-LEVEL_RANGE_M, LEVEL_RANGE_M + 1e-9, LEVEL_STEP_M)
|
||||
levels = _reference(ground, spans, max_grade)[:, None] + offsets[None, :]
|
||||
cut, cut_c, fill = (weight[:, None] * a for a in _area_curves(sections, modes, ground, levels))
|
||||
cut, cut_c, fill = (
|
||||
weight[:, None] * a for a in _area_curves(sections, modes, ground, levels, curves)
|
||||
)
|
||||
criteria = (
|
||||
FOREST_ROAD_PROFILE_CRITERIA["design_speed"].get(design_speed)
|
||||
or (FOREST_ROAD_PROFILE_CRITERIA["design_speed"][20])
|
||||
@@ -293,11 +318,17 @@ def route_profile(
|
||||
vertices: list[tuple[float, float]], terrain: Terrain, *, max_grade: float, design_speed: int
|
||||
) -> dict[str, Any]:
|
||||
"""후보 노선 하나의 균형 종단 — 측점 횡단을 뜨고 `balanced_profile`."""
|
||||
chainage, ground, sections, modes = route_sections(vertices, terrain)
|
||||
chainage, ground, sections, modes, curves = route_sections(vertices, terrain)
|
||||
if len(chainage) < 2:
|
||||
raise ValueError("노선이 짧아 종단을 세울 수 없습니다.")
|
||||
return balanced_profile(
|
||||
chainage, ground, sections, modes, max_grade=max_grade, design_speed=design_speed
|
||||
chainage,
|
||||
ground,
|
||||
sections,
|
||||
modes,
|
||||
max_grade=max_grade,
|
||||
design_speed=design_speed,
|
||||
curves=curves,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user