feat(B04 배수): 하강 방향장 도엽별 공용 창고 — 도엽 + 여유 100 m 로 한 번 짓고 이어 붙여 읽기만 (PLAN 609-1 · 요구 486 · 493)

- 새 파일 B04_PreProcess_Engine_Watershed_DescentShared.py — storage/_shared/terrain/<도엽>/descent_<좌표계>_1m.npz(칸 코드 int8 · 밴드 표고) + 열쇠(판 · 여유 · 규칙 · 전국 파일 지문 · zip 해시) · 짓는 법은 build_contour_descent 그대로 · 원천 = 전국 등고선 · 빠진 곳 = 607 도엽 zip
- 배수 확장 source_for — 저장소 셋째 값(좌표계)이 있으면 공용 길 · 못 지으면 옛 프로젝트 길 · 2 값 저장소(시험)는 그대로
- 라우터 두 곳 — _prepare 가 좌표계를 함께 넘김
- 공용 지형 창고 — 좌표 변환 6 곳이 번호 없는 좌표계 글(PRJ WKT)도 받음 · 정수 입력 그대로
- 시험 test_609_1 — 도엽 넷 이어 붙임 = 통째 짓기 · 두 번째 짓기 0 · 원천 바뀌면 다시 · WKT · 확장 공용 길 = 프로젝트 길 · 실패 시 프로젝트 길

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-30 23:09:57 +09:00
co-authored by Claude Opus 5.5
parent 0a54d52fff
commit 59ae735eda
6 changed files with 501 additions and 20 deletions
@@ -215,12 +215,13 @@ async def _prepare(project_id: UUID) -> dict[str, Any] | JSONResponse:
)
# 설계 계통과 같은 프로젝트 좌표계로 맞춘다. 도엽 재투영도 이 좌표계로 간다.
def sheets() -> tuple[Transformer, list[dict[str, Any]], list[dict[str, Any]], str]:
def sheets() -> tuple[str, Transformer, list[dict[str, Any]], list[dict[str, Any]], str]:
source_crs = resolve_project_crs(
project_root, route_crs_input=planned.crs_input, db_epsg=epsg
)
to_metric_transformer = Transformer.from_crs("EPSG:4326", source_crs, always_xy=True)
return (
source_crs,
Transformer.from_crs(source_crs, "EPSG:4326", always_xy=True),
*_metric_sheets(
_sheet_dir(stored_path),
@@ -231,7 +232,9 @@ async def _prepare(project_id: UUID) -> dict[str, Any] | JSONResponse:
)
# 재투영(저장본 없으면 3.8 s) · 피클 읽기는 스레드로 — 이벤트 루프를 안 막음(505-1)
to_lonlat_transformer, streams, contour_features, contour_key = await asyncio.to_thread(sheets)
crs, to_lonlat_transformer, streams, contour_features, contour_key = await asyncio.to_thread(
sheets
)
return {
"route_source": route_file.name,
"vertices": planned.vertices,
@@ -239,6 +242,7 @@ async def _prepare(project_id: UUID) -> dict[str, Any] | JSONResponse:
"streams": streams,
"contours": contour_features,
"contour_key": contour_key,
"crs": crs, # 공용 하강장(609-1)
"stored_path": stored_path,
"to_lonlat": lambda x, y: to_lonlat_transformer.transform(x, y),
}
@@ -413,7 +417,7 @@ async def get_primary_region(
prepared["contours"],
prepared["streams"],
# 흐름 방향 저장본(347-1) — 노선이 저장 범위 안이면 읽기만
(drainage_dir(prepared["stored_path"]), prepared["contour_key"]),
(drainage_dir(prepared["stored_path"]), prepared["contour_key"], prepared["crs"]),
)
if preview is None:
return JSONResponse(