- 새 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>
57 lines
2.6 KiB
Python
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)
|