- 세트 연결 = 638-1 그대로(B 하나 = 세트 하나 · 열쇠만 세트) - 새 D04_PlanConfirm_Context — 같은 예정노선 세트 모두 · 내 세트 · 연도 · 임도 좌표계로 pyproj 변환 · 업체 이름 없음 · 보기 전용 - 새 B05_Profile_Router_PlanSource — 원본 꼭짓점 = 범위 + 지표면 트림 · 조밀화만 뺌(Router_Replan 안 늘림 · 덧칸 대신 새 길) - RouteFollow 추적 전 체인과 같은 3.6 m 조밀화(브레인 허락 · 옛 촘촘 입력은 그대로 = 626-2 같음) - 실측 검증_sub3: 원본 64 점 → follow = 초기선 181 점 거리 0 · 파일 안 바뀜 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
82 lines
3.4 KiB
Python
82 lines
3.4 KiB
Python
"""임도 프로젝트가 받은 예정노선 문맥(635-2 · 요구 546 · 547) — 같은 예정노선의 세트 모두 · 내 세트.
|
|
|
|
계산 자리 ③ 서버 단독 · 보기 전용(파일 · DB 안 씀) — 예정노선 설계 내용 파일을 그때 읽어
|
|
(`D03_PlanAnalysis_Store.plan_state`) 조각 꺾은선을 임도 프로젝트 좌표계로 바꿈(pyproj ·
|
|
예정노선 = GRS80 TM · 임도 = 사업지 좌표계 `resolve_project_crs`). 업체 이름 · 메일은 안 실음.
|
|
|
|
GET /api/projects/{id}/plan-context
|
|
→ {status, linked: False} | {status, linked: True, plan_name, epsg(임도), mine, year,
|
|
sets: [{key, year, no, mine, parts, branch, length_m, lines: [[[x, y], …] 조각마다]}]}
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter
|
|
from pyproj import CRS, Transformer
|
|
|
|
from common_util.common_util_crs import resolve_project_crs
|
|
from common_util.common_util_storage import resolve_stored_project_path
|
|
from D03_PlanAnalysis import D03_PlanAnalysis_Store as store
|
|
from D04_PlanConfirm.D04_PlanConfirm_Link import _fetch_one
|
|
|
|
router = APIRouter(prefix="/api/projects", tags=["D04 Plan Context"])
|
|
|
|
|
|
def context_sets(
|
|
state: dict[str, Any], mine: dict[str, Any] | None, target: str
|
|
) -> list[dict[str, Any]]:
|
|
"""세트마다 조각 꺾은선(임도 좌표) — 세트 · 조각 차례 그대로."""
|
|
source = f"EPSG:{int(state['plan']['epsg'])}"
|
|
move = Transformer.from_crs(source, target, always_xy=True)
|
|
pieces = {p["id"]: p for p in state["pieces"]}
|
|
out = []
|
|
for unit in state["units"]:
|
|
lines = [
|
|
[list(move.transform(x, y)) for x, y in pieces[pid]["planned"]]
|
|
for pid in unit["pieces"]
|
|
]
|
|
out.append(
|
|
{
|
|
"key": unit["id"],
|
|
"year": unit["year"],
|
|
"no": unit["set"],
|
|
"mine": mine is not None and unit["id"] == mine["id"],
|
|
"parts": unit["parts"],
|
|
"branch": unit["branch"],
|
|
"length_m": unit["length_m"],
|
|
"lines": lines,
|
|
}
|
|
)
|
|
return out
|
|
|
|
|
|
@router.get("/{project_id}/plan-context", response_model=None)
|
|
async def plan_context(project_id: UUID) -> dict[str, Any]:
|
|
"""받은 세트 · 같은 예정노선의 다른 세트(다른 해 · 같은 해 다른 업체) ·
|
|
연결 없으면 linked False."""
|
|
row = await _fetch_one(
|
|
"SELECT a.project_id, a.piece_id, s.name AS plan_name FROM plan_route_assignees a"
|
|
" JOIN projects s ON s.id = a.project_id AND s.deleted_at IS NULL"
|
|
" WHERE a.linked_project_id = %s ORDER BY a.id LIMIT 1",
|
|
(str(project_id),),
|
|
)
|
|
if not row:
|
|
return {"status": "success", "linked": False}
|
|
road = await store.project_row(project_id)
|
|
target = resolve_project_crs(Path(resolve_stored_project_path(road["storage_path"])))
|
|
state = await store.plan_state(str(row["project_id"]))
|
|
mine = store.find_unit(state["units"], row["piece_id"])
|
|
return {
|
|
"status": "success",
|
|
"linked": True,
|
|
"plan_name": row["plan_name"],
|
|
"epsg": CRS.from_user_input(target).to_epsg(), # WKT 원문이면 못 찾을 때 None
|
|
"mine": mine and mine["id"],
|
|
"year": mine and mine["year"],
|
|
"sets": context_sets(state, mine, target) if state["plan"]["epsg"] else [],
|
|
}
|