Files
Aislo/B04_wf1_Surface/B04_wf1_Surface_Router.py
T
2026-07-05 21:27:23 +09:00

150 lines
6.4 KiB
Python

"""B04 지표면 분석 FastAPI 라우터."""
import asyncio
import logging
from pathlib import Path
from uuid import UUID
from fastapi import APIRouter
from fastapi.responses import JSONResponse
from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path
from B04_wf1_Surface.B04_wf1_Surface_Engine import run_surface_analysis
from B04_wf1_Surface.B04_wf1_Surface_Repository import (
create_processed_point_cloud,
create_surface_model,
create_terrain_layer,
get_input_file,
list_surface_models,
)
from B04_wf1_Surface.B04_wf1_Surface_Schema import (
SurfaceAnalyzeRequest,
SurfaceAnalyzeResponse,
SurfaceModelListResponse,
SurfaceModelSummary,
)
from common_util.common_util_storage import resolve_stored_project_path
from config.config_db import get_db_pool
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/projects", tags=["B04 Surface Analysis"])
@router.post("/{project_id}/surface/analyze", response_model=SurfaceAnalyzeResponse)
async def analyze_surface(
project_id: UUID, request: SurfaceAnalyzeRequest
) -> SurfaceAnalyzeResponse | JSONResponse:
"""LAS 구조화·지면 필터·지표면 모델 생성을 실행하고 DB에 기록한다."""
try:
source_filters = request.resolved_filters()
methods = request.resolved_methods()
except ValueError as exc:
return JSONResponse(status_code=400, content={"status": "error", "message": str(exc)})
pool = get_db_pool()
try:
async with pool.acquire() as connection:
stored_path = await get_project_storage_relative_path(connection, project_id)
project_root = Path(resolve_stored_project_path(stored_path))
input_file = await get_input_file(connection, project_id, request.input_file_id)
las_path = project_root / Path(input_file["raw_file_path"])
if not las_path.is_file():
return JSONResponse(
status_code=404,
content={"status": "error", "message": "원본 LAS 파일을 찾을 수 없습니다."},
)
# 무거운 지형 연산은 이벤트 루프를 막지 않도록 별도 스레드에서 실행.
result = await asyncio.to_thread(
run_surface_analysis,
project_root,
las_path,
source_filters=source_filters,
methods=methods,
force=request.force,
)
# DB 기록 (트랜잭션)
await connection.begin()
try:
processed = result["processed"]
processed_cloud_id = await create_processed_point_cloud(
connection,
input_file_id=request.input_file_id,
project_id=project_id,
process_type="structured",
processed_file_path=processed["processed_file_path"],
converted_format=None,
converted_file_path=processed["converted_file_path"],
point_count=processed["point_count"],
bounds=processed["bounds"],
statistics=processed["statistics"],
classification_summary=None,
processing_params={"filters": source_filters},
)
surface_model_ids: list[int] = []
for model in result["models"]:
model_id = await create_surface_model(
connection,
project_id=project_id,
model_type=model["model_type"],
source_file_id=request.input_file_id,
processed_cloud_id=processed_cloud_id,
crs_epsg=input_file["crs_epsg"],
resolution_m=model["resolution_m"],
model_file_path=model["model_file_path"],
generation_params=model["generation_params"],
)
surface_model_ids.append(model_id)
for layer in model["layers"]:
await create_terrain_layer(
connection,
surface_model_id=model_id,
layer_name=layer["layer_name"],
geometry_type=layer["geometry_type"],
layer_file_path=layer["file_path"],
file_format=layer["file_format"],
file_size_mb=None,
statistics=None,
)
await connection.commit()
except Exception:
await connection.rollback()
raise
return SurfaceAnalyzeResponse(
project_id=str(project_id),
ground_summary=result["ground_summary"],
manifest_status=result["manifest"].get("status", "unknown"),
surface_model_ids=surface_model_ids,
)
except LookupError as exc:
return JSONResponse(status_code=404, content={"status": "error", "message": str(exc)})
except (OSError, ValueError) as exc:
return JSONResponse(status_code=400, content={"status": "error", "message": str(exc)})
except Exception:
logger.exception("B04 지표면 분석 실패: project_id=%s", project_id)
return JSONResponse(
status_code=500,
content={"status": "error", "message": "지표면 분석 처리 중 오류가 발생했습니다."},
)
@router.get("/{project_id}/surface/models", response_model=SurfaceModelListResponse)
async def get_surface_models(project_id: UUID) -> SurfaceModelListResponse | JSONResponse:
"""프로젝트의 지표면 모델 목록을 조회한다."""
pool = get_db_pool()
try:
async with pool.acquire() as connection:
models = await list_surface_models(connection, project_id)
return SurfaceModelListResponse(
project_id=str(project_id),
models=[SurfaceModelSummary(**model) for model in models],
)
except Exception:
logger.exception("B04 지표면 모델 목록 조회 실패: project_id=%s", project_id)
return JSONResponse(
status_code=500,
content={"status": "error", "message": "모델 목록 조회 중 오류가 발생했습니다."},
)