Files
Aislo/B05_Profile/B05_Profile_Engine_RouteInitial_CrossEarth.py
T
eomsangdonandClaude Opus 5.5 eee1da09fa feat(B05 추천 횡단 토공): CrossEarth 새 파일 · 표준 횡단면 = 저장 패널값 · 판 8 + 소스 지문 · [재계산] 서버 · with_profile 저장 (420)
- 새 B05_Profile_Engine_RouteInitial_CrossEarth.py — cross_earthwork(line, chainages, design_z, terrain, standard) · cross_earthwork_delta(before, after, changed_chainages, …) · attach_cross_earth(결과 표 절 · 성 = 횡단)
- 균형 종단 · 횡단 토공 표준 횡단면 = 프로젝트 저장 패널값(추천 · [적용] 둘 다) · generate_initial_route standard 인자
- 저장본 ALGO_VERSION 8 · 엔진 소스 지문 열쇠 · 표준 횡단면 열쇠 · 옛 판 · 옛 지문 미리 셈 때 지움
- 요청 recompute — 묶음 잠금 안에서 이 묶음 저장본 · _POOLS 버리고 새로 셈 · 저장본 request 엔 안 적힘
- initial_choice.json with_profile([적용] 「종단 반영」) · 미리 셈 다섯 갈래 종단 켬
- 시험 420_1 새로 · 224_3 · 364_4 · route_initial 고쳐 씀

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy
2026-10-18 12:43:20 +09:00

178 lines
7.4 KiB
Python

"""노선 추천 횡단 토공(PLAN 420 · 요구 254).
측점마다 공식 횡단 엔진(B06 `compute_cross_design` — [저장] 정본과 같은 파이썬)으로 절 · 성
단면적을 재고 평균단면법으로 토량을 낸다. 띠 가늠(폭 4 m · 비탈 뺌 · `..._Metrics`)을 갈음하는
추천 토공 — 후보 줄 세움 · 합치기 판정 · 결과 표 절 · 성.
계산 자리 = 서버 단독(③). 측점 횡단 뜨기 · 단면적은 균형 종단(`..._Profile`)과 같은 함수 —
종단 반영 끔 = 계획고 = 지반고([적용] 뒤 1차 종단과 같음).
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
import numpy as np
from B05_Profile.B05_Profile_Engine_RouteInitial_Profile import (
STATION_M,
_station_areas,
route_sections,
)
if TYPE_CHECKING:
from B05_Profile.B05_Profile_Engine_RouteInitial_Search import Terrain
from common_util.common_util_surface_sampler import SurfaceElevationSampler
def _end_area_weights(at: np.ndarray) -> np.ndarray:
"""평균단면법 무게 — 측점마다 앞 · 뒤 구간 반씩(Σ (A_i + A_i+1) / 2 · L = Σ A_i · w_i)."""
spans = np.diff(at)
weight = np.zeros(len(at))
weight[:-1] += spans / 2.0
weight[1:] += spans / 2.0
return weight
def _areas(
line: list[tuple[float, float]],
chainages: Any,
design_z: Any,
terrain: Terrain | SurfaceElevationSampler,
standard: dict[str, Any] | None,
design_curves: list[dict[str, Any]] | None,
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
"""(누가거리, 지반고, 계획고, [절 자연 · 절 다짐 · 성] ㎡ 행렬)."""
at, ground, sections, modes, widen = route_sections(line, terrain, design_curves, at=chainages)
plan = ground if design_z is None else np.asarray(design_z, dtype=np.float64)
if plan.shape != at.shape:
raise ValueError("계획고 수가 측점 수와 다릅니다.")
rows = np.asarray(
[
_station_areas(sections[i], modes[i], float(plan[i]), widen[i], standard)
for i in range(len(at))
]
).reshape(-1, 3)
return at, ground, plan, rows
def cross_earthwork(
line: list[tuple[float, float]],
chainages: Any,
design_z: Any,
terrain: Terrain | SurfaceElevationSampler,
standard: dict[str, Any] | None = None,
*,
design_curves: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
"""노선 하나의 횡단 토공 — `{cut, cut_compacted, fill, per_station}`(㎥ · 절 = 자연 상태).
`chainages` = 잴 누가거리(None = 20 m 측점 + 종점 · 균형 종단과 같은 틀) · `design_z` =
그 측점 계획고(None = 지반고 · 종단 반영 끔) · `standard` = 프로젝트 저장 표준 횡단면 설정값
(None = 설정 기본값) · `design_curves` = 곡선표(확폭 · 없으면 측점별 실측 반경)."""
at, ground, plan, rows = _areas(line, chainages, design_z, terrain, standard, design_curves)
weight = _end_area_weights(at)
cut, cut_c, fill = (float((weight * rows[:, k]).sum()) for k in range(3))
return {
"cut": round(cut, 1),
"cut_compacted": round(cut_c, 1),
"fill": round(fill, 1),
"per_station": [
{
"chainage_m": round(float(s), 2),
"ground_m": round(float(g), 3),
"plan_m": round(float(p), 3),
"cut_m2": round(float(r[0]), 4),
"cut_compacted_m2": round(float(r[1]), 4),
"fill_m2": round(float(r[2]), 4),
}
for s, g, p, r in zip(at, ground, plan, rows)
],
}
def cross_earthwork_delta(
before: list[tuple[float, float]] | dict[str, Any],
after: list[tuple[float, float]],
changed_chainages: Any,
terrain: Terrain | SurfaceElevationSampler,
standard: dict[str, Any] | None = None,
*,
before_chainages: Any = None,
z_before: Any = None,
z_after: Any = None,
design_curves_before: list[dict[str, Any]] | None = None,
design_curves_after: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
"""바뀐 측점만 횡단으로 잰 토공 차(새 − 옛 · ㎥) — 합치기 판정(3~6 측점).
측점 하나 = `STATION_M` 몫(가운데 측점 평균단면법 무게). `before` = 옛 노선 정점 또는 그
노선의 `cross_earthwork` 결과(그 측점 단면적을 다시 안 셈 · 누가거리 가장 가까운 것) ·
`before_chainages` = 옛 노선에서 잴 누가거리(없으면 `changed_chainages`) · `z_*` = 그 측점
계획고(None = 지반고). 결과 `cut` · `fill` · `earth` = 차 · `before` 가 결과면 `*_total` =
고친 뒤 노선 전체 어림(옛 합 + 차)."""
at_after = np.asarray(changed_chainages, dtype=np.float64).reshape(-1)
at_before = at_after if before_chainages is None else np.asarray(before_chainages, float)
if at_after.size == 0:
new = old = np.zeros((0, 3))
else:
new = _areas(after, at_after, z_after, terrain, standard, design_curves_after)[3]
if isinstance(before, dict):
rows = before["per_station"]
held = np.asarray([row["chainage_m"] for row in rows])
pick = [int(np.argmin(np.abs(held - s))) for s in at_before]
old = np.asarray(
[[rows[i]["cut_m2"], rows[i]["cut_compacted_m2"], rows[i]["fill_m2"]] for i in pick]
).reshape(-1, 3)
else:
old = _areas(before, at_before, z_before, terrain, standard, design_curves_before)[3]
cut, cut_c, fill = (float(STATION_M * (new[:, k].sum() - old[:, k].sum())) for k in range(3))
out = {
"cut": round(cut, 1),
"cut_compacted": round(cut_c, 1),
"fill": round(fill, 1),
"earth": round(cut + fill, 1),
"stations": int(at_after.size),
}
if isinstance(before, dict):
out["cut_total"] = round(before["cut"] + cut, 1)
out["fill_total"] = round(before["fill"] + fill, 1)
return out
def attach_cross_earth(
result: dict[str, Any],
terrain: Terrain | SurfaceElevationSampler,
standard: dict[str, Any] | None = None,
) -> None:
"""추천 결과 표 절 · 성 = 횡단 값(420) — 종단 켬 = 균형 종단 횡단 ㎥ · 끔 = 계획고 지반고.
띠 가늠은 `band_cut_m3` · `band_fill_m3` 로 남긴다. 맞추기 전 선(`unsnapped`)도 같이."""
for item in (result, result.get("unsnapped")):
if not item or not item.get("planned"):
continue
metrics = item["metrics"]
if "band_cut_m3" in metrics:
continue
profile = metrics.get("profile")
if profile and "error" not in profile:
cut, fill = float(profile["cut_m3"]), float(profile["fill_m3"])
else:
try:
earth = cross_earthwork(
[tuple(p) for p in item["planned"]],
None,
None,
terrain,
standard,
design_curves=item.get("curves") or None,
)
except ValueError: # 지반을 못 뜬 노선 — 띠 가늠 그대로
continue
cut, fill = earth["cut"], earth["fill"]
metrics["band_cut_m3"] = metrics.get("cut_m3")
metrics["band_fill_m3"] = metrics.get("fill_m3")
metrics["cut_m3"] = round(cut, 1)
metrics["fill_m3"] = round(fill, 1)
metrics["surplus_m3"] = round(cut - fill, 1)