Files
Aislo/B05_Profile/B05_Profile_Router_PlanSource.py
T
eomsangdonandClaude Opus 5.5 8b3560c4ce fix(755-3): 받은 노선 원본 꼭짓점을 체인 예정노선 파일 양끝에서 자름 — 지표면 넓어진 뒤 280 m 긴 흠
- /route/plan/source = 지표면 트림 뒤 expected_route.csv 시점 · 종점 투영 자리에서 자름(파일 없음 = 그대로) · 0.1 mm 반올림 겹친 점 하나만
- 검증_sub2 source 1,377.4 → 1,097.4 m(= csv) · follow 1,356.5 → 1,080.2 m · 검증_sub1 은 전후 같음(1,097.4)
- 시험 test_635_2 양끝 자름 · 파일 없음 그대로 한 건 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:21:40 +09:00

109 lines
4.7 KiB
Python

"""받은 예정노선 원본 꼭짓점(635-2 · 요구 546) — B05 「예정노선 수정」이 고칠 선.
계산 자리 ③ 서버 단독 · 읽기만(파일 안 만듦). 원본 = 올린(받은) 노선 파일 꼭짓점을 사업지 좌표로 ·
사용 범위(`projects.route_start_m · route_end_m`) · 지표면 트림까지 — 체인 예정노선
(`load_design_route`)과 같은 차례에서 **조밀화만 뺌**. 끝 = 체인이 자른 `expected_route.csv`
양끝(755-3 · 지표면이 그 뒤 넓어져도 화면 예상노선 · 저장 csv 와 같은 길이).
고친 꼭짓점은 `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
import math
from pathlib import Path
from typing import Any
from uuid import UUID
from fastapi import APIRouter
from fastapi.responses import JSONResponse
from shapely.geometry import LineString, Point
from B05_Profile.B05_Profile_Router_Replan import _PROJECT_PATH_MISSING, _project_paths
from common_util.common_util_route_geometry import (
clip_route_by_chainage,
expected_route_csv_path,
load_design_route,
read_planned_route_csv,
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)
points = clip_to_expected(root, points)
return [[float(x), float(y)] for x, y in points] if len(points) >= 2 else []
def clip_to_expected(root: Path, points: list[tuple[float, float]]) -> list[tuple[float, float]]:
"""체인이 자른 예정노선 파일 양끝에서 자름(755-3) — 파일 없음 · 2점 미만 = 그대로."""
path = expected_route_csv_path(root)
stored = read_planned_route_csv(path) if path.is_file() else None
if stored is None or len(stored.vertices) < 2 or len(points) < 2:
return points
line = LineString(points)
ends = [line.project(Point(v.x, v.y)) for v in (stored.vertices[0], stored.vertices[-1])]
clipped = clip_route_by_chainage(points, min(ends), max(ends))
# 파일 좌표 반올림(0.1 mm) = 끝이 꼭짓점 바로 앞에 떨어짐 — 1 mm 안 겹친 점은 하나만
return [p for i, p in enumerate(clipped) if not i or math.dist(p, clipped[i - 1]) > 1e-3]
@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,
}