116 lines
4.8 KiB
Python
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}
|