- POST /api/projects/{id}/route/coords — 작업 좌표계(resolve_project_crs · WKT 그대로) pyproj 묶음 변환
- 찾기 이동 = 서버 변환 좌표 · 바탕 tm_grid 늘려 쓰기(ponytail) 걷음
- 키트 lonLat = 노드 고를 때 한 번 묻기 · 같은 점 캐시
- test_635_3 찾기 줄 = 서버 변환 부름으로 고침(옛 tm_grid 부름 걷힘)
- 실측 검증_sub6 B: 50.4 km · 181.2 km 찾기 이동 어긋남 0.000 m · 경위도 = pyproj
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
68 lines
2.6 KiB
Python
68 lines
2.6 KiB
Python
"""경위도 ↔ 사업지 좌표 묶음 변환(635-5 · 요구 546) — B05 위치 찾기 이동 · 속성 판 경위도.
|
|
|
|
계산 자리 ③ 서버 단독 · 읽기만. 사업지 좌표계 = 작업 좌표계 창구(`resolve_project_crs` · 설계 노선
|
|
`crs_input` 먼저 · WKT 원문 그대로 = 지역 TOWGS84 보존) · pyproj 한 번에 여러 점.
|
|
화면은 찾기 · 노드 고름 때만 부름(끌기 중 아님).
|
|
|
|
POST /api/projects/{id}/route/coords {to: "plan" | "lonlat", points: [[a, b], …]}
|
|
→ {points: [[a, b], …]} plan = 경위도 [lon, lat] → [x, y] m · lonlat = 반대
|
|
404 프로젝트 없음 · 422 점 꼴 틀림
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
from typing import Literal
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel, Field
|
|
from pyproj import Transformer
|
|
|
|
from B05_Profile.B05_Profile_Router_Replan import _project_paths
|
|
|
|
router = APIRouter(prefix="/api/projects", tags=["B05 Route Coords"])
|
|
|
|
MAX_POINTS = 5000
|
|
|
|
|
|
class CoordsRequest(BaseModel):
|
|
to: Literal["plan", "lonlat"]
|
|
points: list[tuple[float, float]] = Field(max_length=MAX_POINTS)
|
|
|
|
|
|
def project_crs_text(root: Path) -> str:
|
|
"""작업 좌표계 pyproj 입력(`EPSG:n` · WKT) — 라이다 `project_epsg` 와 같은 사다리."""
|
|
from common_util.common_util_crs import resolve_project_crs
|
|
from common_util.common_util_route_geometry import load_design_route
|
|
|
|
planned = load_design_route(root)
|
|
return resolve_project_crs(root, route_crs_input=planned.crs_input if planned else None)
|
|
|
|
|
|
@lru_cache(maxsize=32)
|
|
def _transformer(crs_text: str, to: str) -> Transformer:
|
|
pair = ("EPSG:4326", crs_text) if to == "plan" else (crs_text, "EPSG:4326")
|
|
return Transformer.from_crs(*pair, always_xy=True)
|
|
|
|
|
|
def convert(crs_text: str, to: str, points: list[tuple[float, float]]) -> list[list[float]]:
|
|
"""점 묶음 변환 — 경위도 순서 [lon, lat] · 사업지 [x 동, y 북]."""
|
|
if not points:
|
|
return []
|
|
xs, ys = zip(*points)
|
|
a, b = _transformer(crs_text, to).transform(xs, ys)
|
|
return [[float(p), float(q)] for p, q in zip(a, b)]
|
|
|
|
|
|
@router.post("/{project_id}/route/coords", response_model=None)
|
|
async def route_coords(project_id: UUID, body: CoordsRequest) -> dict:
|
|
paths = await _project_paths(project_id)
|
|
if paths is None:
|
|
raise HTTPException(status_code=404, detail="프로젝트 저장 경로를 찾을 수 없습니다.")
|
|
crs_text = await asyncio.to_thread(project_crs_text, paths[0])
|
|
points = await asyncio.to_thread(convert, crs_text, body.to, body.points)
|
|
return {"points": points}
|