feat(D 예정노선): D02 기준 지정 걷음 · 지점 마킹 = D03 노선분석 · 기준점 둘레 1 km 처음 받기 · 조용히 이어 받기 (PLAN 591-2 · 요구 452 · 453)

- D02 걷음 — 길 · 단계(PLAN 단계 둘 = 노선 분석 · 확정) · [>] · 대시보드 단계 점 · 서버 길 · 폴더 만들기 · 캐시 등록 · 글 · 가이드 폴더
- 등록 [등록] · 받은 예정노선 [보기] = D03 으로 곧바로
- D03 지점 = 긋기에 붙는 점 · 목록 칸(여섯 색 · 「지점 N」 · 🗑) · 핀 끌기 = 그 자리 노선 노드 같이 · [저장] 몸에 실음 · [초기화] = 선만 지우고 지점 남김
- 지도 우클릭 메뉴(부품 상자) — 여기서 긋기 시작(빈 땅 = 새 본선) · 갈래 시작 · 이어 긋기 · 지점 놓기 · 지점 지우기 · 긋기 끝
- 받기 — 처음 = 등록 기준점 둘레 ± 1 km(지점 · 선 없을 때) · 지점 · 선 · 지도 보기 가운데가 받은 범위 밖이면 조용히 이어 받음(단추 · 막는 창 걷음 · 실패만 [다시 받기])
- 좌표계 = 파일 머리 · 없으면 기준점 경도 TM 띠
- 시험: 529_3(D02 지점 API) 지움 → 591_2 로 갈음 · 280_2 · 528 · 530_2 · 547_1 · 548 · 553_4 · 556_2 · 560_4 · 401 · 54_2 = D02 단계 · 페이지 걷음에 맞춤 · 590_1 · 530_2_analyze = 사업연도 · 기준점 한 번에 읽음 · 591_1 D04 씨앗 시험 = 590-1 이 씨앗 줄 걷음에 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-30 10:19:34 +09:00
co-authored by Claude Opus 5.5
parent 7d71230fca
commit 29512c001b
72 changed files with 895 additions and 1261 deletions
+16 -11
View File
@@ -9,7 +9,8 @@
`…/forest-roads`(GET · POST 올리기) · `…/landslide-zones` — 지도 겹
(PLAN 569-1 · 581-1 · 581-2 · 584-1 · 584-3)
범위 = 지점(D02 정본 `load_base`) + 그은 점(`extra`) 상자 + 여유.
범위 = 기준점 둘레(`SITE_RADIUS_M`) + 지점 + 그은 점(`extra` · 지도 보기 귀) 상자 + 여유 ∪ 받은 범위
(PLAN 591-2 · 기준점 = 처음 받는 기준일 뿐).
노선 정본(손 그은 선)은
`D03_PlanAnalysis_Network`(PLAN 562-2 · 옛 추천 엔진 · 꺾임점 R 걷음).
"""
@@ -27,14 +28,16 @@ from fastapi import APIRouter, File, HTTPException, Request, Response, UploadFil
from pydantic import BaseModel
from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path
from common_util.common_util_crs import lonlat_to_tm
from common_util.common_util_http_cache import cached_file_response
from common_util.common_util_storage import resolve_stored_project_path
from config.config_db import get_db_pool
from D02_PlanBase import D02_PlanBase_Router as base
from D03_PlanAnalysis import D03_PlanAnalysis_ForestRoad as forest_road
from D03_PlanAnalysis import D03_PlanAnalysis_Landslide as landslide
from D03_PlanAnalysis import D03_PlanAnalysis_LandslideZone as landslide_zone
from D03_PlanAnalysis import D03_PlanAnalysis_Layers as layers
from D03_PlanAnalysis import D03_PlanAnalysis_Network as network
from D03_PlanAnalysis import D03_PlanAnalysis_Store as store
from D03_PlanAnalysis import D03_PlanAnalysis_Terrain as terrain
from D03_PlanAnalysis import D03_PlanAnalysis_ZeroOrder as zero_order
@@ -55,16 +58,18 @@ class TerrainBody(BaseModel):
async def _plan_ends(project_id: UUID) -> tuple[Path, int, list[tuple[float, float]]]:
"""(실경로, 좌표계, [지점들]) — D02 정본(빈 칸이면 옛 연도 행 접음)."""
plan = await base.load_base(project_id)
if not plan["points"]:
raise HTTPException(status_code=400, detail="지점이 없습니다 — 1단계에서 지정하세요.")
async with get_db_pool().acquire() as connection:
stored = await get_project_storage_relative_path(connection, project_id)
if not stored:
raise HTTPException(status_code=404, detail="프로젝트 저장 경로를 찾을 수 없습니다.")
"""(실경로, 좌표계, [지점들]) — 설계 내용 파일 · 지점 · 선이 없으면 등록 기준점 둘레 두 귀."""
root, plan = await store.load_plan(project_id) # 없음 404 · 예정노선 아님 400
site = (await network.project_info(project_id))["site"]
epsg = network.work_epsg(plan, site)
if epsg is None:
raise HTTPException(status_code=400, detail="기준점 · 지점이 없습니다.")
ends = [(float(p["x"]), float(p["y"])) for p in plan["points"]]
return Path(resolve_stored_project_path(stored)), int(plan["epsg"]), ends
if site and not ends and not plan["lines"]: # 처음 = 기준점 둘레 · 그 뒤는 받은 범위가 덮음
ends = terrain.site_corners(*lonlat_to_tm(site["lon"], site["lat"], epsg))
if not ends:
raise HTTPException(status_code=400, detail="기준점 · 지점이 없습니다.")
return root, epsg, ends
def _query_xy(extra: str | None) -> list[tuple[float, float]]: