feat(D 예정노선): 올린 임도망 묶음 쌓기 · 목록(고르기 · 지움) · 지도 밝은 분홍 + 검은 테 (PLAN 605-1 · 605-2 · 요구 476-1)

- 서버 — 올릴 때마다 묶음 하나(`D03_PlanAnalysis/임도망도/<번호>`) · 옛 한 파일 꼴 = 묶음 legacy · 합친 값 + 묶음 줄 · 목록 GET · 묶음 DELETE · 20 묶음까지
- 화면 — 위치 칸 목록(묶음 줄 · 펼치면 선 줄 · 고름 = 지도 강조 + 맞춤 · 지움 = 물음 뒤) · 색 주황 → 밝은 분홍(`--draw-map-forest-road-file`)
- 시험 584_3 — 묶음 쌓기 · 지움 · legacy · 20 묶음 · 화면 정의

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-30 21:49:20 +09:00
co-authored by Claude Sonnet 5.5
parent 20ed22709c
commit 40cecf27a2
12 changed files with 629 additions and 53 deletions
+30 -4
View File
@@ -6,8 +6,8 @@
- `GET …/plan/tile/{z}/{x}/{y}` — 위성 조각 대리 · 서버 기억(586 GL 바탕)
- `GET …/plan/layers/sheet?key=` · `…/cadastral` · `…/raster?kind=slope|hillshade|landslide` ·
`…/zones` · `…/soils` · `…/bands` · `…/lines?kind=` · `…/zero-order` ·
`…/forest-roads`(GET · POST 올리기) · `…/landslide-zones` — 지도 겹
(PLAN 569-1 · 581-1 · 581-2 · 584-1 · 584-3)
`…/forest-roads`(GET · POST 올리기 · `/bundles` 목록 · DELETE `/<번호>`) ·
`…/landslide-zones` — 지도 겹 (PLAN 569-1 · 581-1 · 581-2 · 584-1 · 584-3 · 605-1)
범위 = 기준점 둘레(`SITE_RADIUS_M`) + 지점 + 그은 점(`extra` · 지도 보기 귀) 상자 + 여유 ∪ 받은 범위
(PLAN 591-2 · 기준점 = 처음 받는 기준일 뿐).
@@ -432,8 +432,8 @@ async def _road_frame(project_id: UUID) -> tuple[Path, int, terrain.Box, tuple[f
@router.get("/{project_id}/plan/layers/forest-roads", response_model=None)
async def plan_layer_forest_roads(project_id: UUID) -> dict[str, Any]:
"""올린 임도망도(기준점 ± 5 km ∪ 받은 지형으로 자른 선 · 가닥 · 길이 · 잘린 상자 · 기준점
거리) — 올린 적 없으면 404."""
"""올린 임도망도(묶음마다 기준점 ± 5 km ∪ 받은 지형으로 자른 선 · 합친 가닥 · 길이 · 잘린 상자 ·
기준점 거리 · `bundles` 묶음 줄) — 올린 적 없으면 404."""
root, epsg, box, origin = await _road_frame(project_id)
try:
value = await asyncio.to_thread(forest_road.road_layer, root, box, epsg, origin)
@@ -442,6 +442,32 @@ async def plan_layer_forest_roads(project_id: UUID) -> dict[str, Any]:
return {"status": "success", **value}
@router.get("/{project_id}/plan/layers/forest-roads/bundles", response_model=None)
async def plan_layer_forest_road_bundles(project_id: UUID) -> dict[str, Any]:
"""올린 묶음 목록(선 없이 · 올린 순서) — 올린 적 없으면 빈 목록."""
root, epsg, box, origin = await _road_frame(project_id)
try:
value = await asyncio.to_thread(forest_road.road_layer, root, box, epsg, origin)
except FileNotFoundError:
return {"status": "success", "bundles": []}
return {"status": "success", "bundles": value["bundles"]}
@router.delete("/{project_id}/plan/layers/forest-roads/{bundle}", response_model=None)
async def plan_layer_forest_road_delete(project_id: UUID, bundle: str) -> dict[str, Any]:
"""올린 묶음 하나 지움 — 남은 묶음 합친 값(남은 것 없으면 `bundles` 빈 목록) · 없으면 404."""
root, epsg, box, origin = await _road_frame(project_id)
try:
await asyncio.to_thread(forest_road.delete_bundle, root, bundle)
except FileNotFoundError as error:
raise HTTPException(status_code=404, detail=str(error)) from error
try:
value = await asyncio.to_thread(forest_road.road_layer, root, box, epsg, origin)
except FileNotFoundError:
return {"status": "success", "bundles": []}
return {"status": "success", **value}
@router.post("/{project_id}/plan/layers/forest-roads", response_model=None)
async def plan_layer_forest_roads_upload(
project_id: UUID, files: list[UploadFile] = File(...)