Files
Aislo/D03_PlanAnalysis/D03_PlanAnalysis_Router_Tiles.py
T
eomsangdonandClaude Opus 5.5 251dfd9a5b feat(D03): 받은 산림 · 환경 자료 줌별 조각 — 벡터(임도 · 산림토양도 · 경제림) · 그림 대리(생태자연도 · 산사태) (PLAN 622-1 · 요구 529)
- 새 common_util_vector_tiles: SHP(cp949 · .prj) → 3857 색인 GeoPackage(data_forest/_index) → z8~15 MVT · 원자료마다 규칙 한 줄(줌 · 단순화 · 작은 면 거름 · 남길 칸) · 창고 storage/_shared/vector_tiles/<name>/v1_<지문> · 615 꼴 잠금 · .part
- 새 common_util_wms_tiles: WMS → 256 px PNG(3857 bbox) · 처음 받고 storage/_shared/wms_tiles 기억 · 빈 · 오류 = 투명 PNG 300 s 기억 · 키 없음 = 404 · 키 안 샘
- 새 D03_PlanAnalysis_Router_Tiles: /plan/vt/{name}/{z}/{x}/{y}.pbf · /plan/wmt/{name}/{z}/{x}/{y}.png · main.py 등록
- 새 tools/precompute_vector_tiles.py: 원자료 이름 · 줌 · 경위도 상자 · 일꾼
- 시험 test_622_vector_tiles.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:16:05 +09:00

57 lines
2.6 KiB
Python

"""D 예정노선 받은 산림 · 환경 자료 조각 API (PLAN 622-1 · 요구 529).
- `GET /api/projects/{id}/plan/vt/{name}/{z}/{x}/{y}.pbf` — 벡터 조각(forest_road · forest_soil ·
eco_forest · 공용 창고 · `common_util_vector_tiles`)
- `GET …/plan/wmt/{name}/{z}/{x}/{y}.png` — 그림 조각 대리(ecology · landslide · 처음 받고 기억 ·
`common_util_wms_tiles`) · 빈 · 오류 조각 = 투명 PNG + `X-Tile-Empty` · 짧게 기억
권한 = 다른 plan 길과 같음(`main.py` 등록 때 회사 보호).
계산 자리 = ③ 서버 단독.
"""
from __future__ import annotations
import asyncio
from uuid import UUID
from fastapi import APIRouter, HTTPException, Response
from common_util import common_util_vector_tiles as vector_tiles
from common_util import common_util_wms_tiles as wms_tiles
router = APIRouter(prefix="/api/projects", tags=["D03 Plan Tiles"])
KEEP = "private, max-age=86400"
SHORT = f"private, max-age={wms_tiles.SHORT_S}"
@router.get("/{project_id}/plan/vt/{name}/{z}/{x}/{y}.pbf", response_model=None)
async def plan_vector_tile(project_id: UUID, name: str, z: int, x: int, y: int) -> Response:
"""받은 산림 자료 벡터 조각 — 공용 창고 · 없으면 원자료에서 만듦 · 빈 조각 = 빈 MVT."""
if name not in vector_tiles.RULES:
raise HTTPException(status_code=404, detail=f"모르는 겹입니다 — {name}")
try:
tile = await asyncio.to_thread(vector_tiles.ensure_tile, name, z, x, y)
except KeyError as error:
raise HTTPException(status_code=400, detail="조각 번호가 범위를 벗어났습니다.") from error
return Response(
content=tile, media_type="application/x-protobuf", headers={"Cache-Control": KEEP}
)
@router.get("/{project_id}/plan/wmt/{name}/{z}/{x}/{y}.png", response_model=None)
async def plan_wms_tile(project_id: UUID, name: str, z: int, x: int, y: int) -> Response:
"""그림 조각 대리 — 처음 받고 기억 · 키 없음 = 404 + 까닭."""
if name not in wms_tiles.SOURCES:
raise HTTPException(status_code=404, detail=f"모르는 겹입니다 — {name}")
try:
png, status = await asyncio.to_thread(wms_tiles.ensure_tile, name, z, x, y)
except wms_tiles.MissingKeyError as error:
raise HTTPException(status_code=404, detail=str(error)) from error
except KeyError as error:
raise HTTPException(status_code=400, detail="조각 번호가 범위를 벗어났습니다.") from error
headers = {"Cache-Control": SHORT if status else KEEP}
if status:
headers["X-Tile-Empty"] = status
return Response(content=png, media_type="image/png", headers=headers)