- 벌 예정노선 terrain_route.csv(벌 몫) — 체인이 받은 노선을 그 지형으로 잘라 한 번 적음 · 초기 폴리라인(merged_follow) · 화면 예상노선 · 고칠 선 끝이 이것을 바탕으로(route_source_path · 없으면 공유 예상노선) - 공유 예상노선 expected_route.csv = 설계 지형으로 자른 하나(추천 열쇠 그대로) · 고친 예정노선 expected_route_edit.csv 는 벌 몫(다른 벌에 안 번짐) - 검증_sub5: 라이다 벌 1,352.9 m · 곡선 19(옛 값과 같음) · 수치지형도 벌 2,110.2 m · 곡선 28(예정노선 끝까지) · 칩 왕복 · 벌마다 [초기화] 값 같음 · 공유 예상노선 그대로 - 시험 test_765_design_sets — 벌 몫 가름에 벌 예정노선 · 고친 예정노선 · 벌 예정노선 자름 · 초기 폴리라인 바탕 둘 덧 · 지운 csf 필터 글자를 ptd · classification 으로(637 · 763_3 · 765) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
109 lines
4.7 KiB
Python
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,
|
|
load_design_route,
|
|
read_planned_route_csv,
|
|
route_source_path,
|
|
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]]:
|
|
"""체인이 자른 벌 예정노선(765-8) 양끝에서 자름(755-3) — 파일 없음 · 2점 미만 = 그대로."""
|
|
path = route_source_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,
|
|
}
|