refactor(공통): 작업 좌표계 폴백을 창구 하나로 통합 + 트림 실패 진단

같은 폴백 사다리가 네 곳에 서로 다른 모양으로 흩어져 있었고, 그중 배수유역 라우터
`_route_center_lonlat` 은 노선 CSV 의 `crs_epsg` **라벨**을 그대로 변환에 썼다(라벨과
실좌표계가 다른 사례 실측 — 2026-09-01 용화 라벨 5179 / 실제 5176).

`common_util_crs.resolve_project_crs()` 신설 — 사다리는 ① 노선 crs_input ② 파일 라벨
(원본 좌표를 읽는 자리만) ③ 지형 PRJ(작업 좌표계 정본) ④ DB epsg ⑤ EPSG:5186.
작업 좌표계는 **지형 PRJ 우선으로 확정**(2026-09-03 사용자 결정 — 서피스 격자가 모델좌표의
주인이라 현행 유지). `project_epsg_from_prj()` 도 이 창구로 위임.

더해 지표면 트림이 노선을 통째로 지울 때 노선·지표면 bbox 를 함께 로그에 남긴다 —
"겹치지 않습니다" 만으로는 좌표계 문제인지 측량 범위 문제인지 갈리지 않았다.

검증 — `tmp/tests/test_project_crs_resolution.py` 5건 신설(사다리 4·진단 1),
전체 375 passed·17 skipped, ruff format 무변경.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-03 13:25:20 +09:00
co-authored by Claude Opus 5
parent f91d589ed4
commit e83171b2b3
6 changed files with 98 additions and 16 deletions
+33
View File
@@ -277,7 +277,10 @@ def load_design_route(
except (FileNotFoundError, KeyError, OSError) as exc:
logger.warning("설계 노선: 지표면을 열지 못해 트림을 건너뜁니다 — %s", exc)
else:
before = points
points = trim_route_to_surface(points, sampler)
if len(points) < 2:
_log_trim_wipeout(before, sampler, target_crs)
points = densify_route(
points,
ROUTE_DIRECT_LINK_CELL_FACTOR * ROUTE_GRID_RES_M * ROUTE_PLANNED_DENSIFY_SAFETY,
@@ -308,6 +311,36 @@ def replace_vertices(
)
def _log_trim_wipeout(points: list[tuple[float, float]], sampler: Any, target_crs: str) -> None:
"""트림이 노선을 통째로 지운 이유를 **수치로** 남긴다.
노선과 지표면의 좌표계가 어긋나면 트림 결과가 빈 목록이 되는데, 그때 로그가
"겹치지 않습니다" 뿐이라 좌표계 문제인지 범위 문제인지 갈리지 않았다(2026-09-03 실사고:
도엽 서피스가 노선 원본 좌표계로 만들어져 노선이 통째로 지워짐). 두 bbox 를 함께 찍어
한 줄로 판별되게 한다 — 겹침이 0이면 좌표계, 일부 겹치면 측량 범위 문제다.
"""
xs = [x for x, _ in points]
ys = [y for _, y in points]
grid_x = getattr(sampler, "x", None)
grid_y = getattr(sampler, "y", None)
surface = (
f"[{float(min(grid_x)):.0f}~{float(max(grid_x)):.0f}, "
f"{float(min(grid_y)):.0f}~{float(max(grid_y)):.0f}]"
if grid_x is not None and grid_y is not None and len(grid_x) and len(grid_y)
else "미상"
)
logger.warning(
"설계 노선: 지표면 트림 결과가 비었습니다 — 노선 bbox [%.0f~%.0f, %.0f~%.0f] / "
"지표면 bbox %s (작업 좌표계 %s). 두 범위가 전혀 안 겹치면 좌표계 불일치입니다.",
min(xs),
max(xs),
min(ys),
max(ys),
surface,
target_crs,
)
def trim_route_to_surface(
points: list[tuple[float, float]],
sampler: Any,