perf(배수유역): 347-1 흐름 방향 · 도엽 좌표 저장 — 격자 원점 좌표 정수 m · 노선이 범위 안이면 읽기만

- 격자 원점 = 좌표 정수 m(GRID_ORIGIN_XY · 옛 「도로 시작점」 지시를 바꿈 · 사용자 확정)
- 흐름 방향 저장본 drainage/descent_<입력 지문>_<범위>.npz — 입력 지문 = 도엽 등고선 지문 + 남긴 등고 단 + 셀 + 계산 규칙 · 덮으면 읽어 잘라 씀 · 최근 2벌
- 도엽(하천 · 등고선) 사업지 좌표 저장본 drainage/00_sheet_metric.pkl — 원본 자국 · 좌표계가 같으면 재투영 안 함
- 검증_sub3 실측: 저장본 읽기 = 지은 것과 칸마다 같음(적색 · 해석 영역 · 화살표 · 귀속 차이 0) · 108 → 10.8 s(붐빔 · 340 기준 약 6 s) · 원점 바뀜으로 적색 460,035 → 459,792칸(−0.05 %) · 관 자리 같음
- 시험 test_347_1_descent_store · 실측 bench 저장본 갈래

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWh5wNNycMkbwMpACz1rte
This commit is contained in:
2026-10-16 11:21:13 +09:00
co-authored by Claude Opus 5.5
parent fba1eb8a5d
commit 0e0c1be1c0
8 changed files with 334 additions and 31 deletions
@@ -8,9 +8,11 @@
"""
import asyncio
import hashlib
import json
import logging
import math
import pickle
import time
from pathlib import Path
from typing import Any
@@ -149,6 +151,42 @@ def _map_coordinates(coordinates: Any, transformer: Transformer) -> Any:
return [item for item in mapped if item is not None]
# 도엽을 사업지 좌표로 바꾼 저장본(347-1) — 원본 파일 자국 · 좌표계가 같으면 읽기만.
SHEET_METRIC_FILENAME = "00_sheet_metric.pkl"
def _metric_sheets(
directory: Path, cache_dir: Path, source_crs: str, transformer: Transformer
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], str]:
"""(하천, 등고선, 도엽 등고선 지문). 피처 4,200개 재투영(3.8 s)을 한 번만 한다."""
marks = []
for name in (STREAM_FILE, CONTOUR_FILE):
try:
info = (directory / name).stat()
marks.append(f"{name}:{info.st_mtime_ns}:{info.st_size}")
except OSError:
marks.append(f"{name}:-")
text = "|".join([*marks, str(source_crs)])
key = hashlib.sha1(text.encode(), usedforsecurity=False).hexdigest()[:16]
path = cache_dir / SHEET_METRIC_FILENAME
try:
with path.open("rb") as file:
saved = pickle.load(file)
if saved.get("key") == key:
return saved["streams"], saved["contours"], key
except (OSError, pickle.PickleError, EOFError, AttributeError, KeyError, TypeError):
pass
streams = _reproject_features(_load_features(directory, STREAM_FILE), transformer)
contours = _reproject_features(_load_features(directory, CONTOUR_FILE), transformer)
try:
cache_dir.mkdir(parents=True, exist_ok=True)
with path.open("wb") as file:
pickle.dump({"key": key, "streams": streams, "contours": contours}, file)
except OSError:
logger.warning("배수유역: 도엽 좌표 저장본을 남기지 못했습니다 (%s).", path)
return streams, contours, key
def _candidate_payload(
candidate: StructureCandidate,
to_lonlat: Any,
@@ -204,10 +242,8 @@ async def _prepare(project_id: UUID) -> dict[str, Any] | JSONResponse:
to_lonlat_transformer = Transformer.from_crs(source_crs, "EPSG:4326", always_xy=True)
to_metric_transformer = Transformer.from_crs("EPSG:4326", source_crs, always_xy=True)
directory = _sheet_dir(stored_path)
streams = _reproject_features(_load_features(directory, STREAM_FILE), to_metric_transformer)
contour_features = _reproject_features(
_load_features(directory, CONTOUR_FILE), to_metric_transformer
streams, contour_features, contour_key = _metric_sheets(
_sheet_dir(stored_path), drainage_dir(stored_path), source_crs, to_metric_transformer
)
return {
"route_source": route_file.name,
@@ -215,6 +251,7 @@ async def _prepare(project_id: UUID) -> dict[str, Any] | JSONResponse:
"route_line": planned.line,
"streams": streams,
"contours": contour_features,
"contour_key": contour_key,
"stored_path": stored_path,
"to_lonlat": lambda x, y: to_lonlat_transformer.transform(x, y),
}
@@ -403,6 +440,8 @@ async def get_primary_region(
prepared["vertices"],
prepared["contours"],
prepared["streams"],
# 흐름 방향 저장본(347-1) — 노선이 저장 범위 안이면 읽기만
(drainage_dir(prepared["stored_path"]), prepared["contour_key"]),
)
if preview is None:
return JSONResponse(