feat(B03/B04): LAS 없이 도엽등고선으로 3D 서피스를 만들어 설계한다
- B03: 'LAS 없이 설계' 토글 — LAS 필수카드 비활성화, las_free 플래그로
업로드·완료 검증 면제, 계획노선 CSV를 WF1 분석 입력으로 사용
- B04: 신규 Engine_SheetSurface — 도엽_등고선.geojson을 노선 bbox+300m
직사각형으로 절취, 배수유역 엔진의 등고선 정점구름·Delaunay TIN 보간을
재사용해 dtm_sheet.npz(1m 격자, LAS DTM과 동일 형식) + 프리뷰 glb 생성.
build_surface_sampler('sheet','dtm')로 종·횡단·배수 하류 계산 무수정 동작
- WF1: las_free면 run_sheet_surface_analysis로 분기, sheet/dtm 자동 확정.
VWorld·도엽 확보 블록을 download_geodata()로 추출해 두 경로가 공유
- LAS가 있어도 도엽 서피스를 함께 생성·등록(참고용), B04에
'도엽등고 3D 서피스' 별도 컨테이너로 표시
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -223,11 +223,64 @@ def run_surface_analysis(
|
||||
time.monotonic() - step_started,
|
||||
)
|
||||
|
||||
# 3-2. VWorld 지도 및 국가 GIS 벡터 다운로드 (기존 산출물이 있으면 스킵)
|
||||
# 3-2·3-3. VWorld 지도·국가 GIS 벡터·수치지형도 도엽 (공용 블록 — 도엽 서피스도 사용)
|
||||
_report(90, "download_maps", "VWorld 지도 및 GIS 벡터 데이터 다운로드 중")
|
||||
las_bounds_dict = {
|
||||
"x": [float(bounds[0, 0]), float(bounds[0, 1])],
|
||||
"y": [float(bounds[1, 0]), float(bounds[1, 1])],
|
||||
"z": [float(bounds[2, 0]), float(bounds[2, 1])],
|
||||
}
|
||||
download_geodata(
|
||||
project_root, processed_dir, las_bounds_dict, las_path.parent, rebuild, report=_report
|
||||
)
|
||||
|
||||
# 3-4. 도엽등고선 3D 서피스 — LAS가 있어도 참고용으로 같이 만들어 영구저장한다
|
||||
# (2026-08-30 사용자 확정). 실패해도 분석은 계속한다.
|
||||
_report(94, "surface_model", "도엽등고선 3D 서피스 생성 중")
|
||||
sheet_model: dict[str, Any] | None = None
|
||||
try:
|
||||
# 입력 LAS와 같은 폴더의 .prj를 우선 사용 (PLAN E-1: 전체 재귀 glob 제거)
|
||||
prj_candidates = sorted(las_path.parent.glob("*.prj")) or sorted(
|
||||
from B04_PreProcess.B04_PreProcess_Engine_SheetSurface import (
|
||||
build_sheet_surface_from_route,
|
||||
)
|
||||
|
||||
sheet_model = build_sheet_surface_from_route(project_root, processed_dir, models_dir)
|
||||
except Exception as exc:
|
||||
logger.warning("도엽등고선 서피스 생성 실패: %s", exc)
|
||||
|
||||
_report(95, "saving", "결과 저장 중")
|
||||
|
||||
return _collect_analysis_result(
|
||||
project_root,
|
||||
models_dir,
|
||||
structured_path,
|
||||
bounds_dict,
|
||||
stats,
|
||||
total_points,
|
||||
ground_summary,
|
||||
manifest,
|
||||
sheet_model,
|
||||
total_started,
|
||||
)
|
||||
|
||||
|
||||
def download_geodata(
|
||||
project_root: Path,
|
||||
processed_dir: Path,
|
||||
las_bounds_dict: dict[str, list[float]],
|
||||
prj_search_dir: Path,
|
||||
rebuild: bool,
|
||||
*,
|
||||
default_epsg: str = "EPSG:5186",
|
||||
report: Any = None,
|
||||
) -> None:
|
||||
"""VWorld 지도·국가 GIS 벡터·수치지형도 도엽 확보 (공용 블록).
|
||||
|
||||
LAS 분석(run_surface_analysis)과 LAS 없는 도엽 서피스 분석이 같이 쓴다.
|
||||
실패해도 예외를 밖으로 던지지 않는다 — 분석 본체를 막지 않는다.
|
||||
"""
|
||||
try:
|
||||
# 입력 파일과 같은 폴더의 .prj를 우선 사용 (PLAN E-1: 전체 재귀 glob 제거)
|
||||
prj_candidates = sorted(prj_search_dir.glob("*.prj")) or sorted(
|
||||
project_root.glob("B03_FileInput/**/*.prj")
|
||||
)
|
||||
prj_path = prj_candidates[0] if prj_candidates else project_root / "result.prj"
|
||||
@@ -243,12 +296,7 @@ def run_surface_analysis(
|
||||
get_epsg_from_prj,
|
||||
)
|
||||
|
||||
las_bounds_dict = {
|
||||
"x": [float(bounds[0, 0]), float(bounds[0, 1])],
|
||||
"y": [float(bounds[1, 0]), float(bounds[1, 1])],
|
||||
"z": [float(bounds[2, 0]), float(bounds[2, 1])],
|
||||
}
|
||||
project_epsg = "EPSG:5186"
|
||||
project_epsg = default_epsg
|
||||
if prj_path.exists():
|
||||
project_epsg = get_epsg_from_prj(prj_path.read_text(encoding="utf-8", errors="ignore"))
|
||||
# 국가 GIS 벡터는 라이다∪계획노선 범위로 받는다.
|
||||
@@ -305,7 +353,8 @@ def run_surface_analysis(
|
||||
# 3-3. 1:5,000 수치지형도 도엽 확보 → 프로젝트 영구저장소
|
||||
# 기준은 계획노선 시점·종점 (같은 도엽이면 9매, 이웃 도엽에 걸치면 12매).
|
||||
# (실패해도 분석은 계속 — 폴백은 수동 다운로드 + 인제스트)
|
||||
_report(92, "download_maps", "수치지형도 도엽 확보 중")
|
||||
if report is not None:
|
||||
report(92, "download_maps", "수치지형도 도엽 확보 중")
|
||||
try:
|
||||
from B04_PreProcess.B04_PreProcess_Engine_Extent import sheet_reference_points_wgs84
|
||||
from B04_PreProcess.B04_PreProcess_Engine_MapSheet import neighbors_for_points
|
||||
@@ -356,8 +405,20 @@ def run_surface_analysis(
|
||||
except Exception as exc:
|
||||
logger.warning("B04 지도·GIS 다운로드 단계 실패: %s", exc)
|
||||
|
||||
_report(95, "saving", "결과 저장 중")
|
||||
|
||||
def _collect_analysis_result(
|
||||
project_root: Path,
|
||||
models_dir: Path,
|
||||
structured_path: Path,
|
||||
bounds_dict: dict[str, float],
|
||||
stats: dict[str, Any],
|
||||
total_points: int,
|
||||
ground_summary: dict[str, Any],
|
||||
manifest: dict[str, Any],
|
||||
sheet_model: dict[str, Any] | None,
|
||||
total_started: float,
|
||||
) -> dict[str, Any]:
|
||||
"""manifest에서 모델 목록을 추려 분석 결과 dict를 조립한다."""
|
||||
processed = {
|
||||
"processed_file_path": _relative_to_project(project_root, structured_path),
|
||||
"converted_file_path": None,
|
||||
@@ -403,6 +464,8 @@ def run_surface_analysis(
|
||||
"layers": layers,
|
||||
}
|
||||
)
|
||||
if sheet_model is not None:
|
||||
models.append(sheet_model)
|
||||
|
||||
logger.info(
|
||||
"B04 WF1 분석 완료: 모델 %d개, 총 %.1fs", len(models), time.monotonic() - total_started
|
||||
|
||||
Reference in New Issue
Block a user