Files
Aislo/B04_wf1_Surface/B04_wf1_Surface_Engine_Extent.py
T
eomsangdonandClaude Opus 5 a9caf872b2 perf(B04/B05): 도엽 표시본 도입 + 배경 범위·도엽 기준을 계획노선으로 변경
- 도엽 병합본의 화면 표시용 사본 생성(B04_wf1_Surface_Engine_SheetView):
  프로젝트 + 500m로 잘라내고 좌표를 소수 7자리로 줄여 등고선 37.3MB -> 0.97MB.
  분석용 원본은 그대로 둔다(배수유역은 상류까지 넓은 범위가 필요).
- /geojson의 도엽 레이어를 파일 그대로 + ETag로 전송 — 매 요청 2.0초 재직렬화 제거(0.008초).
- /vworld-map도 ETag 전송으로 바꾸고, 프론트가 붙이던 `&_t=` 시간꼬리표 제거.
- 도엽 레이어를 브라우저 보관함 경유로 조회하고 준비화면에서 미리 담는다.
- 배경 지도 범위 = 라이다 범위 + 계획노선 범위 + 여유 300m(SURFACE_MAP_MARGIN_M).
- 도엽 기준 좌표 = 계획노선 시점·종점(없으면 라이다 중심). 같은 도엽이면 9매,
  이웃 도엽에 걸치면 12매. 표본 프로젝트는 기존 9매와 동일(회귀 없음).
- 선정에서 빠진 도엽 zip 정리(prune_sheets) — 표본에서 30매 중 21매가 다른 지역 잔재.

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

114 lines
4.5 KiB
Python

# B04_wf1_Surface_Engine_Extent.py
# 전처리에서 내려받을 범위와 기준 좌표를 정한다.
#
# 배경(2026-08-01 사용자 지시): 배경 지도·수치지형도 도엽의 기준을 라이다 범위 한가운데로
# 잡으면, 계획노선이 라이다 범위를 벗어날 때 배경과 도엽이 노선을 덮지 못한다.
# 계획노선(B03 CSV)의 시점·종점을 기준으로 삼고, 없을 때만 라이다 범위로 되돌린다.
from __future__ import annotations
import logging
from pathlib import Path
from typing import Any
from config.config_system import SURFACE_MAP_MARGIN_M
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,
margin_m: float = SURFACE_MAP_MARGIN_M,
) -> dict[str, list[float]]:
"""배경 지도를 내려받을 범위. 라이다 범위와 계획노선을 합친 뒤 여유폭만큼 넓힌다.
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 - margin_m, x_max + margin_m],
"y": [y_min - margin_m, y_max + margin_m],
"z": list(las_bounds.get("z", [0.0, 0.0])),
}
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)]