Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1

This commit is contained in:
2026-10-22 20:32:34 +09:00
2 changed files with 111 additions and 11 deletions
+39 -11
View File
@@ -16,6 +16,7 @@ from __future__ import annotations
import asyncio
import copy
import dataclasses
import hashlib
import json
import logging
@@ -27,6 +28,7 @@ from uuid import UUID
from fastapi import APIRouter, Depends
from fastapi.responses import JSONResponse
from pyproj import Transformer
from B04_PreProcess.B04_PreProcess_Engine_Watershed_Export import STAGES, drainage_dir
from B05_Profile.B05_Profile_Structures_Schema import DEFINITION_PATH, structure_type_map
@@ -121,14 +123,18 @@ def _rebuilt(detail: DrainageDetail) -> list[PipePoint]:
def _build_stale(context: DrainageContext, points: list[PipePoint]) -> DrainageDetail:
"""낡은 분석 — 세부 유역 없이 관 자리만(지금 노선 위) · 시설 속성은 넘겨받은 그대로."""
"""낡은 분석 — 마지막 [저장]의 옛 유역 그림(저장본 파일 · 그 노선) + 관 자리만 지금 노선 위 ·
저장본이 없으면 관 자리만(512-6 · 새로 연 화면도 옛 그림 · [업데이트] 로만 바꿈)."""
pipes = pipes_from_chainages(context.vertices, [point.chainage_m for point in points])
_retag(pipes, points)
saved = _read_cache_file(context.stored_path)
if saved is not None:
return dataclasses.replace(saved["value"][0], pipes=pipes, pipe_candidates=[])
to_lonlat = context.to_lonlat
detail = DrainageDetail(
return DrainageDetail(
route_lonlat=[list(to_lonlat(vertex.x, vertex.y)) for vertex in context.vertices],
pipes=pipes_from_chainages(context.vertices, [point.chainage_m for point in points]),
pipes=pipes,
)
_retag(detail.pipes, points)
return detail
# 세부 유역 저장본(342-3 · 부록 4 R15) — 열쇠 = 관 목록 · 노선(평면 · 종단 Z) · 분석 산출물 ·
@@ -165,6 +171,26 @@ def _cache_path(stored_path: str):
return drainage_dir(stored_path) / DETAIL_CACHE_FILENAME
def _read_cache_file(stored_path: str) -> dict[str, Any] | None:
"""[저장] 때 남긴 세부 유역 저장본 `{key, value: (detail, 관 목록)}` — 없거나 깨지면 None."""
try:
with _cache_path(stored_path).open("rb") as file:
saved = pickle.load(file)
saved["value"][0].basins # 모양 확인
return saved
except (
OSError,
ValueError,
pickle.PickleError,
EOFError,
AttributeError,
KeyError,
TypeError,
IndexError,
):
return None
def _remember(key: str, value: tuple[DrainageDetail, list[PipePoint]]) -> None:
_DETAIL_CACHE.pop(key, None)
_DETAIL_CACHE[key] = value
@@ -179,12 +205,8 @@ def _build_cached(
key = _detail_key(context, points)
hit = _DETAIL_CACHE.get(key)
if hit is None:
try:
with _cache_path(context.stored_path).open("rb") as file:
saved = pickle.load(file)
hit = saved["value"] if saved.get("key") == key else None
except (OSError, pickle.PickleError, EOFError, AttributeError, KeyError, TypeError):
hit = None
saved = _read_cache_file(context.stored_path)
hit = saved["value"] if saved is not None and saved.get("key") == key else None
if hit is None:
detail, carried = _build(context.stored_path, context, points)
if detail is None:
@@ -312,6 +334,12 @@ def _payload(
payload["route_signature"] = route_signature(context.vertices)
payload["pipe_signature"] = pipe_signature([pipe.chainage_m for pipe in detail.pipes])
payload["stale"] = STALE_MESSAGE if stale else None
if stale: # 노선선 = 옛 그림의 노선(512-6 · 저장본 없으면 지금 노선 그대로)
back = Transformer.from_crs("EPSG:4326", context.crs, always_xy=True)
payload["route_xy"] = [
[round(x, 3), round(y, 3)]
for x, y in (back.transform(lon, lat) for lon, lat in detail.route_lonlat)
]
return round_floats(payload, LONLAT_DIGITS)
@@ -0,0 +1,72 @@
"""512-6(요구 326-6) 노선 [저장] 뒤 새로 연 B05 = 저장된 옛 유역 그림 + 낡음.
- 낡은 분석(노선 지문 다름)이면 마지막 [저장]의 세부 유역 저장본(`04_detail_cache.pkl`)으로
옛 유역 · 옛 노선선을 돌려줌 · 관 자리만 지금 노선 위 · 저장본 없으면 관 자리만(옛 꼴).
- 화면은 열 때 그림이 없으니 그 답을 그대로 그림 + 낡음 점([업데이트] 로만 바꿈 · test_512_2).
"""
import pickle
from pathlib import Path
from uuid import uuid4
from pyproj import Transformer
from B04_PreProcess import B04_PreProcess_Router_Basins as basins
from common_util.common_util_drainage_context import DrainageContext
from common_util.common_util_drainage_detail import DrainageDetail, WatershedBasin
from common_util.common_util_route_geometry import RouteVertex
CRS = "EPSG:5186"
TO = Transformer.from_crs(CRS, "EPSG:4326", always_xy=True)
def _context(tmp_path: Path, monkeypatch, shift: float) -> DrainageContext:
monkeypatch.setattr(basins, "drainage_dir", lambda _stored: tmp_path)
vertices = [
RouteVertex(200000.0 + shift, 500000.0, 100.0, 0.0),
RouteVertex(200300.0 + shift, 500000.0, 90.0, 300.0),
]
return DrainageContext(
stored_path="x",
project_root=tmp_path,
vertices=vertices,
crs=CRS,
to_lonlat=lambda x, y: TO.transform(x, y),
)
def test_저장본_있으면_옛_그림과_옛_노선(tmp_path, monkeypatch):
old = DrainageDetail(
route_lonlat=[
list(TO.transform(200000.0, 500000.0)),
list(TO.transform(200300.0, 500000.0)),
],
basins=[WatershedBasin(index=1, chainage_m=120.0, outlet_x=200120.0, outlet_y=500000.0)],
)
with (tmp_path / basins.DETAIL_CACHE_FILENAME).open("wb") as file:
pickle.dump({"key": "옛", "value": (old, [])}, file)
context = _context(tmp_path, monkeypatch, shift=17.0) # 노선 17 m 옮김
points = [basins.PipePoint(chainage_m=150.0, source="user")]
detail = basins._build_stale(context, points)
assert [basin.chainage_m for basin in detail.basins] == [120.0]
assert detail.pipe_candidates == []
assert abs(detail.pipes[0].x - 200167.0) < 1e-6 # 관 = 지금 노선 위
payload = basins._payload(uuid4(), context, detail, points, True, stale=True)
assert payload["stale"] and len(payload["basins"]) == 1
xy = payload["route_xy"]
assert abs(xy[0][0] - 200000.0) < 0.01 and abs(xy[1][0] - 200300.0) < 0.01 # 옛 노선
def test_저장본_없으면_관_자리만(tmp_path, monkeypatch):
context = _context(tmp_path, monkeypatch, shift=17.0)
points = [basins.PipePoint(chainage_m=150.0, source="user")]
detail = basins._build_stale(context, points)
assert detail.basins == []
payload = basins._payload(uuid4(), context, detail, points, True, stale=True)
assert abs(payload["route_xy"][0][0] - 200017.0) < 0.01 # 지금 노선
def test_깨진_저장본은_없음과_같음(tmp_path, monkeypatch):
(tmp_path / basins.DETAIL_CACHE_FILENAME).write_bytes(b"broken")
monkeypatch.setattr(basins, "drainage_dir", lambda _stored: tmp_path)
assert basins._read_cache_file("x") is None