fix(b06): 미교차 측점 지반 샘플을 지표면 끝까지 넓힘(㉳ (가) · 브레인 판정 「+5m 씩 · 상한 = 지표면 끝」 · 716 「+5m 한 번」 갈음)
- 서버 재계산([저장]·[확정]·자동설계 체인) 맨 앞에서 미교차 측점만 열린 쪽을 +5m 씩 확정 지표면에서 다시 떠 계산 · 닫히면 멈춤 · 지표면 밖 샘플이 섞이면 그 걸음은 안 붙이고 미교차로 남김 - 넓힌 샘플은 횡단 파일에 남겨 화면·Node·B08 이 같은 지반을 봄 · 계산식은 안 바뀜(입력만 넓어짐) · 뒤 보정(포장·세월교·사토장)은 넓힌 지반 위에서 - 종전엔 반폭 20m 끝에서 잘려 성토가 작게 섰음 - 936be972 서버 재계산 전→후: 미교차 12 → 1(840 지표면 끝) · 성토 단면 460 19.87→35.11 · 480 48.04→84.35 · 500 37.15→52.47 · 700 24.74→54.84 · 1000 56.30→80.40 · 840 41.60→110.35 · 258.12 25.11→31.44 · 유토곡선 성토 13,350.64 → 17,248.52㎥ · 토취 7,259.84 → 11,100.86㎥ · 내역 본체 129,498,518 → 154,857,626원 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
"""미교차 측점의 지반 샘플을 **지표면 끝까지** 넓힘 — ㉳ (가) (2026-09-14 브레인 판정).
|
||||
|
||||
반폭(기본 20m) 샘플 끝에서 사면이 원지반과 안 만나면 면적이 거기서 잘려 성토가 **작게** 섰다
|
||||
(936be972 미교차 12곳 중 11곳이 넓히면 닫힘 · 성토 +2,559.9㎥). ⇒ **열린 쪽만** +5m 씩 확정 지표면에서
|
||||
다시 떠 계산하고, 닫히면 멈춘다. 새 걸음에 지표면 밖(무효) 샘플이 섞이면 그 걸음은 안 붙이고 멈춘다 —
|
||||
그 측점은 「지표면 끝까지 넓혀도 안 만남」으로 미교차가 남는다.
|
||||
⚠ 상한 숫자를 두지 않는다 — 지표면이 실제로 있는 끝이라 근거가 필요 없음(인위 기본값 금지).
|
||||
⚠ 716 판정 「미교차 측점만 반폭 +5m 한 번」을 갈음(그것으론 4곳만 닫혔음).
|
||||
⚠ 계산식은 안 바뀐다 — 샘플(입력)만 넓어진다. 넓힌 샘플은 횡단 파일에 남겨 화면(TS)·Node·B08 이
|
||||
같은 지반을 본다(한 벌).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
|
||||
import numpy as np
|
||||
|
||||
from B05_Profile.B05_Profile_Engine_Sections import cross_filename
|
||||
from B06_Section.B06_Section_Engine_Design import (
|
||||
_SLOPE_CLOSE_TOLERANCE_M,
|
||||
compute_cross_design,
|
||||
curve_widening_args,
|
||||
)
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
#: 한 걸음 — 브레인 판정 「+5m 씩」.
|
||||
EXTEND_STEP_M = 5.0
|
||||
#: 지표면 끝까지 넓혀도 안 닫힌 측점의 사유(경고가 그대로 씀).
|
||||
SURFACE_END_REASON = "지표면 끝까지 넓혀도 성토 비탈이 원지반과 안 만남"
|
||||
|
||||
|
||||
def _valid_sorted(samples: list[dict[str, Any]]) -> list[tuple[float, float]]:
|
||||
return sorted(
|
||||
(float(s["offset_m"]), float(s["elevation_m"]))
|
||||
for s in samples
|
||||
if s.get("valid") is not False and s.get("elevation_m") is not None
|
||||
)
|
||||
|
||||
|
||||
def _open_ends(samples: list[dict[str, Any]], design: dict[str, Any]) -> tuple[bool, bool]:
|
||||
"""(우 끝 열림, 좌 끝 열림) — 샘플 끝의 지반과 설계선 높이차가 허용오차를 넘나."""
|
||||
ground = _valid_sorted(samples)
|
||||
line = sorted(
|
||||
(float(p["offset_m"]), float(p["elevation_m"])) for p in design.get("design_line") or []
|
||||
)
|
||||
if not ground or not line:
|
||||
return False, False
|
||||
right = abs(ground[0][1] - line[0][1]) > _SLOPE_CLOSE_TOLERANCE_M
|
||||
left = abs(ground[-1][1] - line[-1][1]) > _SLOPE_CLOSE_TOLERANCE_M
|
||||
return right, left
|
||||
|
||||
|
||||
def extend_unclosed(
|
||||
samples: list[dict[str, Any]],
|
||||
frame: dict[str, Any],
|
||||
compute: Callable[[list[dict[str, Any]]], dict[str, Any]],
|
||||
sampler: Any,
|
||||
step_m: float = EXTEND_STEP_M,
|
||||
) -> tuple[list[dict[str, Any]], dict[str, Any]] | None:
|
||||
"""넓힌 샘플과 `{left_m, right_m, surface_end}` — 이미 닫혔으면 `None`."""
|
||||
design = compute(samples)
|
||||
if not design.get("slope_unclosed"):
|
||||
return None
|
||||
offsets = [offset for offset, _z in _valid_sorted(samples)]
|
||||
spacing = min(b - a for a, b in zip(offsets, offsets[1:]) if b - a > 1e-9)
|
||||
count = max(int(round(step_m / spacing)), 1)
|
||||
origin = np.array([float(frame["origin"]["x"]), float(frame["origin"]["y"])])
|
||||
left_axis = np.array([float(v) for v in frame["left_xy"]])
|
||||
info: dict[str, Any] = {"left_m": 0.0, "right_m": 0.0, "surface_end": False}
|
||||
samples = list(samples)
|
||||
while design.get("slope_unclosed"):
|
||||
right_open, left_open = _open_ends(samples, design)
|
||||
added: list[dict[str, Any]] = []
|
||||
for side, is_open, sign in (("right", right_open, -1.0), ("left", left_open, 1.0)):
|
||||
if not is_open:
|
||||
continue
|
||||
edge = min(offsets) if sign < 0 else max(offsets)
|
||||
new_offsets = np.array([edge + sign * spacing * k for k in range(1, count + 1)])
|
||||
xy = origin[None, :] + left_axis[None, :] * new_offsets[:, None]
|
||||
z, valid = sampler.sample_xy(xy)
|
||||
if not np.all(valid):
|
||||
info["surface_end"] = True
|
||||
continue
|
||||
added.extend(
|
||||
{
|
||||
"offset_m": round(float(o), 6),
|
||||
"x": round(float(p[0]), 6),
|
||||
"y": round(float(p[1]), 6),
|
||||
"z": round(float(e), 6),
|
||||
"elevation_m": round(float(e), 6),
|
||||
"valid": True,
|
||||
}
|
||||
for o, p, e in zip(new_offsets, xy, z)
|
||||
)
|
||||
info[f"{side}_m"] += step_m
|
||||
if not added:
|
||||
break
|
||||
samples = sorted(samples + added, key=lambda s: float(s["offset_m"]))
|
||||
offsets = [offset for offset, _z in _valid_sorted(samples)]
|
||||
design = compute(samples)
|
||||
return samples, info
|
||||
|
||||
|
||||
def extend_unclosed_sections(
|
||||
longitudinal: dict[str, Any],
|
||||
cross_sections: list[dict[str, Any]],
|
||||
project_root: Path,
|
||||
standard: dict[str, Any] | None,
|
||||
sampler: Any,
|
||||
) -> int:
|
||||
"""저장 설계가 미교차인 측점을 넓혀 샘플·설계를 자리에서 갈고 횡단 파일에 남긴다. 넓힌 수."""
|
||||
from B06_Section.B06_Section_Router_Design import (
|
||||
USER_TOUCHED_KEYS,
|
||||
ford_drop_at,
|
||||
ford_surface_drops,
|
||||
stored_berm,
|
||||
stored_cut_slope,
|
||||
)
|
||||
from common_util.common_util_route_profile import design_elevation_from_longitudinal
|
||||
from config.config_system import STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M
|
||||
|
||||
if sampler is None:
|
||||
return 0
|
||||
drops = ford_surface_drops(project_root)
|
||||
cross_dir = project_root / "B06_Section" / "cross_sections"
|
||||
changed = 0
|
||||
for section in cross_sections:
|
||||
design = section.get("design")
|
||||
if not isinstance(design, dict) or not design.get("slope_unclosed"):
|
||||
continue
|
||||
chainage = float(section.get("chainage_m", 0.0))
|
||||
|
||||
def compute(samples: list[dict[str, Any]], design=design, chainage=chainage) -> dict:
|
||||
return compute_cross_design(
|
||||
samples,
|
||||
design_elevation_from_longitudinal(longitudinal, chainage),
|
||||
ground_type=str(design.get("ground_type") or "ripping_rock"),
|
||||
section_mode=str(design.get("section_mode") or "left_cut"),
|
||||
ditch_side=design.get("ditch_side"),
|
||||
ditch_type=str(design.get("ditch_type") or "standard"),
|
||||
paved=bool(design.get("paved", False)),
|
||||
standard=standard,
|
||||
rock_boundary_offset_m=design.get(
|
||||
"rock_boundary_offset_m", STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M
|
||||
),
|
||||
two_stage_slope=bool(design.get("two_stage_slope", True)),
|
||||
cut_slope_ratio=stored_cut_slope(design),
|
||||
ditch_enabled=design.get("ditch_enabled"),
|
||||
ditch_choice=design.get("ditch_choice"),
|
||||
surface_drop_m=ford_drop_at(chainage, drops),
|
||||
berm=stored_berm(design),
|
||||
**curve_widening_args(section),
|
||||
)
|
||||
|
||||
try:
|
||||
result = extend_unclosed(
|
||||
section.get("samples") or [], section["frame"], compute, sampler
|
||||
)
|
||||
except (ValueError, KeyError):
|
||||
continue
|
||||
if result is None:
|
||||
continue
|
||||
samples, info = result
|
||||
if not info["left_m"] and not info["right_m"]:
|
||||
continue # 첫 걸음부터 지표면 밖 — 샘플이 그대로라 갈 것이 없음
|
||||
recomputed = compute(samples)
|
||||
for key in ("status", "pavement_suggested", *USER_TOUCHED_KEYS):
|
||||
if design.get(key) is not None:
|
||||
recomputed[key] = design[key]
|
||||
section["samples"] = samples
|
||||
section["design"] = recomputed
|
||||
cross_path = cross_dir / cross_filename(chainage)
|
||||
if cross_path.is_file():
|
||||
stored = json.loads(cross_path.read_text(encoding="utf-8"))
|
||||
atomic_write_json(cross_path, {**stored, "samples": samples})
|
||||
logger.info(
|
||||
"미교차 샘플 넓힘: 측점 %.3f 좌 +%sm 우 +%sm 지표면 끝 %s",
|
||||
chainage,
|
||||
info["left_m"],
|
||||
info["right_m"],
|
||||
info["surface_end"],
|
||||
)
|
||||
changed += 1
|
||||
return changed
|
||||
Reference in New Issue
Block a user