Files
Aislo/D04_PlanConfirm/D04_PlanConfirm_Context.py
T
eomsangdonandClaude Opus 5.5 e70201a626 feat(635-2): 받은 세트 문맥 GET plan-context · 원본 꼭짓점 GET route/plan/source · 다시 추적 전 조밀화 = 체인 초기선 거리 0(요구 546 · 547)
- 세트 연결 = 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>
2026-11-01 11:46:53 +09:00

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 [],
}