"""미교차 측점이 **얼마나 더 넓히면 닫히는지** — 저장분만으로 잰다(읽기 전용). 재는 법 — 저장된 `design_line` 의 바깥 끝(사면이 잘린 자리)에서 시작해, 지반이 **바깥 5m 평균 기울기**로 이어진다고 보고 설계 사면선과 만나는 거리를 푼다. 지형이 그대로 이어진다는 가정이라 **하한 추정**이며, 지형이 설계 사면보다 가파르면 「아무리 넓혀도 안 닫힘」으로 잡힌다. """ import asyncio import json import sys from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[2] if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) import config.config_db as db # noqa: E402 from config.config_system import STORAGE_BASE_DIR # noqa: E402 ROUTE_ID = 169 PROJECT = "5601e828-feea-487a-9b25-415e5199f2f5" SAMPLES = Path(STORAGE_BASE_DIR) / "1" / "3" / PROJECT / "B06_Section" / "cross_sections" def ground_of(chainage: float): path = SAMPLES / f"cross_{int(round(chainage)):05d}m.json" if not path.exists(): return None doc = json.loads(path.read_text(encoding="utf-8")) pts = [ (float(s["offset_m"]), float(s["elevation_m"])) for s in doc.get("samples") or [] if s.get("valid") is not False and s.get("elevation_m") is not None ] return sorted(pts) or None def need_width(design_line, ground, ratio_fill, ratio_cut): """양 끝에서 필요한 추가 폭(m). 못 닫히면 None.""" out = {} for side, pick in (("left", min), ("right", max)): edge = pick(design_line, key=lambda p: p["offset_m"]) tip_x, tip_z = float(edge["offset_m"]), float(edge["elevation_m"]) near = [p for p in ground if abs(p[0] - tip_x) <= 5.0] if len(near) < 2: continue run = near[-1][0] - near[0][0] if abs(run) < 1e-6: continue terrain = (near[-1][1] - near[0][1]) / run # 바깥으로 갈수록 (+)면 오름 gap = tip_z - (near[-1][1] if side == "right" else near[0][1]) # 절토(사면이 올라감)면 1/cut, 성토(내려감)면 -1/fill. 바깥 방향 부호를 맞춘다. rising = gap < 0 # 설계선이 지반보다 낮다 = 절토측 design = (1.0 / ratio_cut) if rising else (-1.0 / ratio_fill) if side == "left": design, terrain = -design, -terrain closing = design - terrain if abs(closing) < 1e-9 or (gap < 0) != (closing > 0): out[side] = None # 벌어지기만 함 — 아무리 넓혀도 안 닫힘 continue out[side] = abs(gap / closing) return out async def main() -> None: await db.init_db_pool() pool = db.get_db_pool() rows = [] async with pool.acquire() as conn, conn.cursor() as cur: await cur.execute("SELECT chainage_m, data FROM cross_sections WHERE route_id=%s", (ROUTE_ID,)) for chainage, data in await cur.fetchall(): doc = (json.loads(data) if isinstance(data, str) else data) or {} design = doc.get("design") or {} if not design.get("slope_unclosed"): continue ground = ground_of(float(chainage)) line = design.get("design_line") or [] if not ground or len(line) < 2: continue need = need_width( line, ground, float(design.get("fill_slope_ratio") or 1.2), float(design.get("cut_slope_ratio") or 1.0), ) rows.append((float(chainage), need)) await db.close_db_pool() never = [(c, s) for c, n in rows for s, v in n.items() if v is None] finite = [(c, s, v) for c, n in rows for s, v in n.items() if v is not None] print(f"미교차 측점 {len(rows)}곳") print(f"· 아무리 넓혀도 안 닫히는 측·조합: {len(never)}건") if finite: finite.sort(key=lambda r: -r[2]) print(f"· 넓히면 닫히는 조합: {len(finite)}건 — 필요한 추가 폭 최대 {finite[0][2]:.1f}m, " f"중앙값 {sorted(v for _, _, v in finite)[len(finite)//2]:.1f}m") for row in finite[:6]: print(f" {row[0]:8.1f}m {row[1]:5s} +{row[2]:.1f}m") asyncio.run(main())