Merge remote-tracking branch 'origin/feat/b03-route-shapefile' into feat/b07-cover-template

# Conflicts:
#	B03_FileInput/B03_FileInput_Service_Chain.py
This commit is contained in:
2026-09-01 15:34:07 +09:00
25 changed files with 841 additions and 109 deletions
+9 -4
View File
@@ -301,11 +301,16 @@ def download_geodata(
실패해도 예외를 밖으로 던지지 않는다 — 분석 본체를 막지 않는다.
"""
try:
# 입력 파일과 같은 폴더의 .prj를 우선 사용 (PLAN E-1: 전체 재귀 glob 제거)
prj_candidates = sorted(prj_search_dir.glob("*.prj")) or sorted(
project_root.glob("B03_FileInput/**/*.prj")
# 입력 파일과 같은 폴더의 .prj를 우선 사용 (PLAN E-1: 전체 재귀 glob 제거).
# 없으면 지형 PRJ — 노선 shapefile 세트의 PRJ가 섞이지 않도록 고른다(2026-08-31).
from common_util.common_util_crs import find_project_prj
prj_candidates = sorted(prj_search_dir.glob("*.prj"))
prj_path = (
prj_candidates[0]
if prj_candidates
else (find_project_prj(project_root) or project_root / "result.prj")
)
prj_path = prj_candidates[0] if prj_candidates else project_root / "result.prj"
from B04_PreProcess.B04_PreProcess_Engine_Extent import (
download_extent,
@@ -144,10 +144,16 @@ def planned_route_bounds(project_root: Path, target_epsg: str) -> dict[str, floa
def project_epsg_from_prj(project_root: Path) -> str:
"""프로젝트 PRJ에서 좌표계를 읽는다. 없으면 중부원점(EPSG:5186)."""
"""프로젝트 PRJ에서 좌표계를 읽는다. 없으면 중부원점(EPSG:5186).
PRJ가 둘 이상 올라오므로(노선 세트 + 지형) 지형 PRJ를 고른다 — `find_project_prj`.
"""
from common_util.common_util_crs import find_project_prj
from .B04_PreProcess_Engine_VWorld import get_epsg_from_prj
for prj_path in sorted(project_root.glob("B03_FileInput/**/*.prj")):
prj_path = find_project_prj(project_root)
if prj_path is not None:
return get_epsg_from_prj(prj_path.read_text(encoding="utf-8", errors="ignore"))
return "EPSG:5186"
@@ -451,14 +451,14 @@ def build_sheet_surface_from_route(
"""B03 업로드 계획노선 CSV를 찾아 방식별 도엽 서피스를 만든다. 없으면 빈 목록."""
from common_util.common_util_route_geometry import (
find_planned_route_file,
read_planned_route_csv,
read_planned_route,
)
route_file = find_planned_route_file(project_root / "B03_FileInput" / "input")
if route_file is None:
logger.warning("도엽 서피스: 계획 노선 파일이 없습니다.")
return []
planned = read_planned_route_csv(route_file)
planned = read_planned_route(route_file)
if planned is None or len(planned.vertices) < 2:
logger.warning("도엽 서피스: 계획 노선 파일을 읽지 못했습니다: %s", route_file.name)
return []
@@ -478,7 +478,7 @@ def run_sheet_surface_analysis(
반환 형식은 `run_surface_analysis()`와 같다(save_surface_analysis_to_db 호환).
"""
from common_util.common_util_route_geometry import read_planned_route_csv
from common_util.common_util_route_geometry import read_planned_route
def _report(percent: int, stage: str, message: str) -> None:
if on_progress is not None:
@@ -490,7 +490,7 @@ def run_sheet_surface_analysis(
processed_dir.mkdir(parents=True, exist_ok=True)
models_dir.mkdir(parents=True, exist_ok=True)
planned = read_planned_route_csv(route_csv_path)
planned = read_planned_route(route_csv_path)
if planned is None or len(planned.vertices) < 2:
raise ValueError(f"계획 노선 파일을 읽지 못했습니다: {route_csv_path.name}")
epsg = planned.epsg or 5186
+3 -3
View File
@@ -250,7 +250,7 @@ async def get_planned_route(project_id: UUID) -> dict[str, Any] | JSONResponse:
from B04_PreProcess.B04_PreProcess_Engine_Extent import project_epsg_from_prj
from common_util.common_util_route_geometry import (
find_planned_route_file,
read_planned_route_csv,
read_planned_route,
)
pool = get_db_pool()
@@ -259,7 +259,7 @@ async def get_planned_route(project_id: UUID) -> dict[str, Any] | JSONResponse:
stored_path = await get_project_storage_relative_path(connection, project_id)
project_root = Path(resolve_stored_project_path(stored_path))
route_file = find_planned_route_file(project_root / "B03_FileInput" / "input")
planned = read_planned_route_csv(route_file) if route_file else None
planned = read_planned_route(route_file) if route_file else None
if planned is None or len(planned.vertices) < 2:
return {"status": "success", "points": []}
@@ -267,7 +267,7 @@ async def get_planned_route(project_id: UUID) -> dict[str, Any] | JSONResponse:
# 그것이 프로젝트 좌표계와 다르면 여기서 한 번 옮긴다.
target_epsg = project_epsg_from_prj(project_root)
points = [(float(vertex.x), float(vertex.y)) for vertex in planned.vertices]
source_epsg = f"EPSG:{planned.epsg}" if planned.epsg else target_epsg
source_epsg = planned.crs_input or target_epsg
if source_epsg != target_epsg:
from pyproj import Transformer
@@ -27,7 +27,7 @@ from B04_PreProcess.B04_PreProcess_Engine_Watershed_Grid import GridSpec
from B05_Profile.B05_Profile_Repository import get_surface_crs_epsg
from common_util.common_util_route_geometry import (
find_planned_route_file,
read_planned_route_csv,
read_planned_route,
)
from common_util.common_util_storage import resolve_stored_project_path
from config.config_db import get_db_pool
@@ -80,7 +80,7 @@ async def _resolve_epsg(project_id: UUID, stored_path: str) -> str:
epsg = await get_surface_crs_epsg(connection, project_id, 0)
project_root = Path(resolve_stored_project_path(stored_path))
route_file = find_planned_route_file(project_root / "B03_FileInput" / "input")
planned = read_planned_route_csv(route_file) if route_file else None
planned = read_planned_route(route_file) if route_file else None
source = (planned.epsg if planned else None) or epsg or 5186
return f"EPSG:{source}"
@@ -39,7 +39,7 @@ from B05_Profile.B05_Profile_Repository import get_surface_crs_epsg
from common_util.common_util_route_geometry import (
StructureCandidate,
find_planned_route_file,
read_planned_route_csv,
read_planned_route,
)
from common_util.common_util_storage import resolve_stored_project_path
from common_util.common_util_wamis_rainfall import (
@@ -172,7 +172,7 @@ async def _prepare(project_id: UUID) -> dict[str, Any] | JSONResponse:
status_code=404,
content={"status": "error", "message": "계획 노선 파일이 업로드되지 않았습니다."},
)
planned = read_planned_route_csv(route_file)
planned = read_planned_route(route_file)
if planned is None or len(planned.vertices) < 2:
return JSONResponse(
status_code=400,
@@ -246,7 +246,7 @@ def _route_center_lonlat(stored_path: str, fallback_epsg: int | None) -> tuple[f
route_file = find_planned_route_file(_route_input_dir(stored_path))
if route_file is None:
return None
planned = read_planned_route_csv(route_file)
planned = read_planned_route(route_file)
if planned is None or not planned.vertices:
return None
middle = planned.vertices[len(planned.vertices) // 2]