Files
Aislo/B05_Profile/B05_Profile_Router_Coords.py
T
eomsangdonandClaude Opus 5.5 e6a420788f feat(635-5): B05 경위도 ↔ 사업지 좌표 서버 변환 길 · 찾기 이동 정확 · 속성 판 경위도(요구 546)
- 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>
2026-11-01 12:51:03 +09:00

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}