"""받은 예정노선 원본 꼭짓점(635-2 · 요구 546) — B05 「예정노선 수정」이 고칠 선. 계산 자리 ③ 서버 단독 · 읽기만(파일 안 만듦). 원본 = 올린(받은) 노선 파일 꼭짓점을 사업지 좌표로 · 사용 범위(`projects.route_start_m · route_end_m`) · 지표면 트림까지 — 체인 예정노선 (`load_design_route`)과 같은 차례에서 **조밀화만 뺌**. 고친 꼭짓점은 `POST /route/follow` 가 같은 간격(`spacing_m`)으로 조밀화한 뒤 추적 → 안 고친 원본 = 체인 초기선과 거리 0(`test_635_2`). GET /api/projects/{id}/route/plan/source → {status, expected_source: [[x, y], …], spacing_m} (원본 없음 = 빈 목록) 404 프로젝트 없음 """ from __future__ import annotations import asyncio from pathlib import Path from typing import Any from uuid import UUID from fastapi import APIRouter from fastapi.responses import JSONResponse from B05_Profile.B05_Profile_Router_Replan import _PROJECT_PATH_MISSING, _project_paths from common_util.common_util_route_geometry import load_design_route, trim_route_to_surface from common_util.common_util_surface_confirmation import get_surface_confirmation_params from common_util.common_util_surface_sampler import build_surface_sampler from config.config_db import get_db_pool from config.config_system import ( ROUTE_DIRECT_LINK_CELL_FACTOR, ROUTE_GRID_RES_M, ROUTE_PLANNED_DENSIFY_SAFETY, ) router = APIRouter(prefix="/api/projects", tags=["B05 Plan Source"]) #: 체인 예정노선 조밀화 간격(`load_design_route` 와 같은 값) SPACING_M = ROUTE_DIRECT_LINK_CELL_FACTOR * ROUTE_GRID_RES_M * ROUTE_PLANNED_DENSIFY_SAFETY def source_vertices( root: Path, surface: dict[str, Any] | None, route_range: tuple | None ) -> list[list[float]]: """원본 꼭짓점 — 사업지 좌표 · 범위 · 지표면 트림(지표면을 못 열면 트림 없이) · 조밀화 없음.""" planned = load_design_route(root, None, route_range) if planned is None: return [] points = [(v.x, v.y) for v in planned.vertices] if surface: try: sampler = build_surface_sampler( root / "B04_PreProcess" / "models", str(surface["source_filter"]), str(surface["method"]), bool(surface["smooth"]), ) except (FileNotFoundError, KeyError, OSError): sampler = None if sampler is not None: points = trim_route_to_surface(points, sampler) return [[float(x), float(y)] for x, y in points] if len(points) >= 2 else [] @router.get("/{project_id}/route/plan/source", response_model=None) async def read_plan_source(project_id: UUID) -> dict[str, Any] | JSONResponse: paths = await _project_paths(project_id) if paths is None: return JSONResponse(status_code=404, content=_PROJECT_PATH_MISSING) async with get_db_pool().acquire() as connection: surface = await get_surface_confirmation_params(connection, str(project_id)) async with connection.cursor() as cursor: await cursor.execute( "SELECT route_start_m, route_end_m FROM projects WHERE id = %s", (str(project_id),), ) row = await cursor.fetchone() vertices = await asyncio.to_thread( source_vertices, paths[0], surface, (row[0], row[1]) if row else None ) return { "status": "success", "project_id": str(project_id), "expected_source": vertices, "spacing_m": SPACING_M, }