Cutt maps for tiles. Clean project architecture
This commit is contained in:
parent
74977ccbfb
commit
b2eaaccbb8
17 changed files with 367 additions and 185 deletions
79
gnss/services/tiles.py
Normal file
79
gnss/services/tiles.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import base64
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Iterable, Optional
|
||||
|
||||
from fastapi.responses import FileResponse, Response
|
||||
|
||||
|
||||
DEFAULT_PLACEHOLDER_PNG = base64.b64decode(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMB/6Xq3lYAAAAASUVORK5CYII="
|
||||
)
|
||||
|
||||
|
||||
class TileCutError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class TileService:
|
||||
def __init__(self, tiles_dir: Path, placeholder: Path):
|
||||
self.tiles_dir = Path(tiles_dir)
|
||||
self.placeholder = Path(placeholder)
|
||||
|
||||
def ensure_tiles_dir(self) -> None:
|
||||
self.tiles_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def _find_gdal2tiles(self) -> str:
|
||||
for name in ("gdal2tiles.py", "gdal2tiles"):
|
||||
found = shutil.which(name)
|
||||
if found:
|
||||
return found
|
||||
raise TileCutError("gdal2tiles not found in PATH. Install GDAL (e.g., apt install gdal-bin).")
|
||||
|
||||
def slice_to_tiles(
|
||||
self,
|
||||
source: Path,
|
||||
destination: Path,
|
||||
min_zoom: int,
|
||||
max_zoom: int,
|
||||
resampling: str = "bilinear",
|
||||
tile_format: str = "png",
|
||||
web_mercator: bool = True,
|
||||
extra_args: Optional[Iterable[str]] = None,
|
||||
) -> Path:
|
||||
if not source.exists():
|
||||
raise TileCutError(f"source does not exist: {source}")
|
||||
if min_zoom < 0 or max_zoom < min_zoom:
|
||||
raise TileCutError(f"invalid zoom range: {min_zoom}-{max_zoom}")
|
||||
|
||||
destination.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
gdal2tiles = self._find_gdal2tiles()
|
||||
cmd = [
|
||||
gdal2tiles,
|
||||
"-z",
|
||||
f"{min_zoom}-{max_zoom}",
|
||||
"-r",
|
||||
resampling,
|
||||
"-f",
|
||||
tile_format.lower(),
|
||||
]
|
||||
if web_mercator:
|
||||
cmd += ["-p", "mercator"]
|
||||
if extra_args:
|
||||
cmd += list(extra_args)
|
||||
cmd += [str(source), str(destination)]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise TileCutError(f"gdal2tiles failed: {result.stderr or result.stdout}")
|
||||
return destination
|
||||
|
||||
def get_tile_response(self, zoom: int, x: int, y: int):
|
||||
tile_path = self.tiles_dir / str(zoom) / str(x) / f"{y}.png"
|
||||
if tile_path.is_file():
|
||||
return FileResponse(tile_path, media_type="image/png")
|
||||
if self.placeholder.is_file():
|
||||
return FileResponse(self.placeholder, media_type="image/png")
|
||||
return Response(content=DEFAULT_PLACEHOLDER_PNG, media_type="image/png")
|
||||
Loading…
Add table
Add a link
Reference in a new issue