Files
Aislo/D03_PlanAnalysis/D03_PlanAnalysis_Lidar.py
T

116 lines
4.8 KiB
Python

"""D 예정노선 라이다 — LAS 올리기 → B04 엔진 그대로 지형(요구 530 · 531 · PLAN 624-4).
- `POST /api/projects/{id}/plan/lidar` — LAS(+ .prj) 올리기 · 뒤에서 지형 짓기
(진행 = `/plan/progress` 의 `lidar`) · 다시 올리면 옛 LAS 를 갈음
- `GET …/plan/lidar` — 지은 라이다 지형(없으면 `ready: false`)
자리 = `B04_PreProcess/lidar/`(D 몫 · 안은 B 와 같은 꼴 — `B03_FileInput/input/las/` ·
`B04_PreProcess/models/`).
LAS 좌표계(.prj · LAS 머리 · 없으면 프로젝트 좌표계)가 프로젝트와 다르면 옮겨 적음(높이 그대로).
지도 · GIS 받기 · 도엽 서피스는 안 함(`maps=False`) · 지은 DTM = `surface_models` 행
(높이 표본 격자).
라이다 범위 밖 = 빈칸(격자 `valid_mask`) · 수치지형도로 메우지 않음(결정 A⑥).
이름 검사 · 좌표계 · 옮겨 놓기 · 받기 · 짓기 = 공용 `common_util_lidar_upload`(B05 와 같이 · 637-1).
계산 자리 = ③ 서버 단독.
"""
from __future__ import annotations
import asyncio
import json
from pathlib import Path
from typing import Any, Callable
from uuid import UUID
from fastapi import APIRouter, File, HTTPException, UploadFile
from common_util import common_util_lidar_upload as lidar_upload
from common_util.common_util_json import atomic_write_json
from common_util.common_util_lidar_upload import ( # noqa: F401 — 옛 이름(시험 · 부르는 쪽)
LAS_SUFFIXES,
check_names,
las_epsg,
place_las,
)
router = APIRouter(prefix="/api/projects", tags=["D03 Plan Lidar"])
LIDAR = Path("B04_PreProcess") / "lidar" # 표면 모델 행 경로 = B04_PreProcess 아래
UPLOAD = "upload"
LAS_DIR = Path("B03_FileInput") / "input" / "las"
MODELS = Path("B04_PreProcess") / "models"
META = "lidar_meta.json"
def lidar_root(root: Path) -> Path:
return Path(root) / LIDAR
def read_meta(root: Path) -> dict[str, Any]:
try:
return json.loads((lidar_root(root) / META).read_text(encoding="utf-8"))
except (OSError, ValueError):
return {}
def lidar_dem(root: Path) -> Path | None:
"""지은 라이다 DTM(평활 안 함) — 없으면 None."""
name = read_meta(root).get("model_file")
path = Path(root) / name if name else None
return path if path is not None and path.is_file() else None
def build_lidar(root: Path, epsg: int, report: Callable[[int, str], None]) -> dict[str, Any]:
"""올린 자리(`upload/`)의 LAS → 입력 자리 갈음 → B04 엔진(지면 필터 · DTM) → 메타."""
base = lidar_root(root)
las_paths = lidar_upload.stage_las(base / UPLOAD, base / LAS_DIR, epsg, report)
result = lidar_upload.build_surface(base, las_paths, report)
prefix = LIDAR.as_posix()
models = [
{**model, "model_file_path": f"{prefix}/{model['model_file_path']}"}
for model in result["models"]
if model.get("model_file_path")
]
picked = result["source_filters"][0]
meta = {
"epsg": int(epsg),
"files": [p.name for p in las_paths],
"source_filter": picked,
"model_file": f"{prefix}/{(MODELS / f'dtm_{picked}.npz').as_posix()}",
"point_count": result["processed"]["point_count"],
"bounds": result["processed"]["bounds"],
}
atomic_write_json(base / META, meta)
return {"meta": meta, "models": models}
@router.post("/{project_id}/plan/lidar", response_model=None)
async def upload_plan_lidar(project_id: UUID, files: list[UploadFile] = File(...)) -> dict:
"""D 에 LAS 올리기 → 뒤에서 라이다 지형(진행 = `/plan/progress` 의 `lidar`)."""
from D03_PlanAnalysis import D03_PlanAnalysis_Router as base
check_names([upload.filename or "" for upload in files])
root, epsg, _, _ = await base._plan_ends(project_id) # 예정노선 아님 400
if (base._JOBS.get((str(project_id), "lidar")) or {}).get("running"):
raise HTTPException(status_code=409, detail="이미 돌고 있습니다.")
await lidar_upload.receive_uploads(files, lidar_root(root) / UPLOAD)
async def work(report: Any) -> None:
got = await asyncio.to_thread(build_lidar, root, epsg, report)
await base._save_models(project_id, got["models"], epsg)
report(100, f"라이다 지형 끝 — 점 {got['meta']['point_count']:,}개")
return base._start_job(project_id, "lidar", work, root)
@router.get("/{project_id}/plan/lidar", response_model=None)
async def plan_lidar(project_id: UUID) -> dict[str, Any]:
"""지은 라이다 지형 — `ready` · 메타(좌표계 · 파일 · 필터 · 점 수 · 범위)."""
from D03_PlanAnalysis import D03_PlanAnalysis_Store as store
root, _ = await store.load_plan(project_id)
meta = await asyncio.to_thread(read_meta, root)
ready = await asyncio.to_thread(lidar_dem, root) is not None
return {"status": "success", "ready": ready, **meta}