- 새 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
178 lines
7.4 KiB
Python
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)
|