feat(512-6 배수유역 옛 그림): 노선 [저장] 뒤 새로 연 B05 = 마지막 [저장] 세부 유역 저장본의 옛 유역 · 옛 노선선 + 관만 지금 노선 위 · 낡음 점 · [업데이트] 로만 바꿈 · 저장본 없음 · 깨짐 = 관 자리만(옛 꼴) · 요구 326-6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user