Files
Aislo/B04_wf1_Surface/B04_wf1_Surface_Engine_Extent.py
T
eomsangdonandClaude Opus 5 6af50811b6 fix(B04): 배경 지도 재다운로드 판정을 파일 존재에서 범위 포함 여부로 변경
기존에는 vworld_*_meta.json이 있으면 무조건 건너뛰었다. B03 업로드가 부르는 전처리
경로는 rebuild=False라, 계획노선이 바뀌거나 여유 셀 설정을 바꿔도 옛 사진을 계속 썼다
(2026-08-01 사용자 지적: 트리거 시점 문제).

map_meta_covers()로 저장된 사진이 필요한 범위(라이다 U 계획노선)를 실제로 덮는지
확인하고, 못 덮으면 다시 받는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 12:18:47 +09:00

135 lines
5.4 KiB
Python

# B04_wf1_Surface_Engine_Extent.py
# 전처리에서 내려받을 범위와 기준 좌표를 정한다.
#
# 배경(2026-08-01 사용자 지시): 배경 지도·수치지형도 도엽의 기준을 라이다 범위 한가운데로
# 잡으면, 계획노선이 라이다 범위를 벗어날 때 배경과 도엽이 노선을 덮지 못한다.
# 계획노선(B03 CSV)의 시점·종점을 기준으로 삼고, 없을 때만 라이다 범위로 되돌린다.
from __future__ import annotations
import json
import logging
from pathlib import Path
from typing import Any
logger = logging.getLogger(__name__)
_ROUTE_CSV_GLOB = "B03_FileInput/input/csv/*.csv"
def read_planned_route(project_root: Path) -> dict[str, Any] | None:
"""B03 계획노선 CSV를 읽어 좌표계·범위·시점·종점을 돌려준다. 없거나 형식이 어긋나면 None."""
from B03_FileInput.B03_FileInput_Engine_Analyze import analyze_planned_route_csv
for csv_path in sorted(project_root.glob(_ROUTE_CSV_GLOB)):
try:
return analyze_planned_route_csv(csv_path)
except (OSError, ValueError) as exc:
logger.warning("B04 계획노선 CSV 해석 실패: %s (%s)", csv_path.name, exc)
return None
def _to_target_crs(
points: list[tuple[float, float]], source_epsg: int | None, target_epsg: str
) -> list[tuple[float, float]]:
"""계획노선 좌표를 라이다 좌표계로 옮긴다. 좌표계가 같거나 알 수 없으면 그대로 쓴다."""
if source_epsg is None:
return points
source = f"EPSG:{source_epsg}"
if source.upper() == target_epsg.upper():
return points
from pyproj import Transformer
transformer = Transformer.from_crs(source, target_epsg, always_xy=True)
return [transformer.transform(x, y) for x, y in points]
def download_extent(
project_root: Path,
las_bounds: dict[str, list[float]],
target_epsg: str,
) -> dict[str, list[float]]:
"""배경 지도가 반드시 덮어야 할 범위 = 라이다 범위 ∪ 계획노선 범위.
여유폭은 여기서 미터로 더하지 않는다. 내려받기 쪽에서 이 범위를 덮는 타일을 정한 뒤
바깥으로 `SURFACE_MAP_MARGIN_TILES` 겹만큼 주변 셀을 더 받는다(2026-08-01 사용자 지시).
las_bounds/반환값 모두 {"x": [최소, 최대], "y": [...], "z": [...]} 꼴(라이다 좌표계).
"""
x_min, x_max = float(las_bounds["x"][0]), float(las_bounds["x"][1])
y_min, y_max = float(las_bounds["y"][0]), float(las_bounds["y"][1])
route = read_planned_route(project_root)
if route:
bounds = route["bounds"]
corners = [
(float(bounds["x_min"]), float(bounds["y_min"])),
(float(bounds["x_max"]), float(bounds["y_max"])),
]
try:
moved = _to_target_crs(corners, route.get("epsg"), target_epsg)
except Exception as exc:
logger.warning("B04 계획노선 좌표 변환 실패 — 라이다 범위만 사용 (%s)", exc)
moved = []
for x, y in moved:
x_min, x_max = min(x_min, x), max(x_max, x)
y_min, y_max = min(y_min, y), max(y_max, y)
return {
"x": [x_min, x_max],
"y": [y_min, y_max],
"z": list(las_bounds.get("z", [0.0, 0.0])),
}
def map_meta_covers(meta_path: Path, extent: dict[str, list[float]]) -> bool:
"""저장된 배경 지도가 필요한 범위를 이미 덮고 있는가.
파일 존재 여부만 보면 계획노선이 바뀌거나 여유 셀 설정을 바꿔도 옛 사진을 계속 쓴다
(B03 업로드 → 전처리 경로는 `rebuild=False`라 더더욱 다시 받지 않는다, 2026-08-01).
"""
if not meta_path.is_file():
return False
try:
meta = json.loads(meta_path.read_text(encoding="utf-8"))
return (
float(meta["x_min"]) <= extent["x"][0]
and float(meta["x_max"]) >= extent["x"][1]
and float(meta["y_min"]) <= extent["y"][0]
and float(meta["y_max"]) >= extent["y"][1]
)
except (OSError, ValueError, KeyError, TypeError):
return False
def sheet_reference_points_wgs84(
project_root: Path,
las_bounds: dict[str, list[float]],
target_epsg: str,
) -> list[tuple[float, float]]:
"""도엽 선정 기준 좌표(위도, 경도) 목록.
① 계획노선 시점·종점 → ② 라이다 범위 중심(폴백).
시점과 종점이 서로 다른 도엽에 걸치면 호출측이 두 도엽의 주변을 모두 확보한다.
"""
from pyproj import Transformer
to_wgs84 = Transformer.from_crs(target_epsg, "EPSG:4326", always_xy=True)
route = read_planned_route(project_root)
if route:
ends = [
(float(route["start_point"][0]), float(route["start_point"][1])),
(float(route["end_point"][0]), float(route["end_point"][1])),
]
try:
moved = _to_target_crs(ends, route.get("epsg"), target_epsg)
return [(lat, lon) for lon, lat in (to_wgs84.transform(x, y) for x, y in moved)]
except Exception as exc:
logger.warning("B04 계획노선 기준 좌표 산출 실패 — 라이다 중심 사용 (%s)", exc)
center_x = (float(las_bounds["x"][0]) + float(las_bounds["x"][1])) / 2.0
center_y = (float(las_bounds["y"][0]) + float(las_bounds["y"][1])) / 2.0
lon, lat = to_wgs84.transform(center_x, center_y)
return [(lat, lon)]