Custom TileJSON endpoint
Goal: enable users to select a predefined configuration stored in the mosaic Metadata.
from typing import Optional
from dataclasses import dataclass
from morecantile import TileMatrixSet
from titiler.core.resources.enums import ImageType
from titiler.core.models.mapbox import TileJSON
from titiler.mosaic.resources.enums import PixelSelectionMethod
from titiler.pgstac import factory as TitilerPgSTACFactory
from titiler.pgstac.dependencies import PgSTACParams
from fastapi import Body, Depends, Query
from starlette.requests import Request
@dataclass
class MosaicTilerFactory(TitilerPgSTACFactory.MosaicTilerFactory):
"""Custom factory."""
def _tilejson_routes(self) -> None:
"""Custom TileJSON endpoint."""
@self.router.get(
"/{searchid}/tilejson.json",
response_model=TileJSON,
responses={200: {"description": "Return a tilejson"}},
response_model_exclude_none=True,
)
@self.router.get(
"/{searchid}/{TileMatrixSetId}/tilejson.json",
response_model=TileJSON,
responses={200: {"description": "Return a tilejson"}},
response_model_exclude_none=True,
)
def tilejson(
request: Request,
searchid=Depends(self.path_dependency),
tms: TileMatrixSet = Depends(self.tms_dependency),
tile_format: Optional[ImageType] = Query(
None, description="Output image type. Default is auto."
),
tile_scale: int = Query(
1, gt=0, lt=4, description="Tile size scale. 1=256x256, 2=512x512..."
),
minzoom: Optional[int] = Query(
None, description="Overwrite default minzoom."
),
maxzoom: Optional[int] = Query(
None, description="Overwrite default maxzoom."
),
layer_params=Depends(self.layer_dependency), # noqa
dataset_params=Depends(self.dataset_dependency), # noqa
pixel_selection: PixelSelectionMethod = Query(
PixelSelectionMethod.first, description="Pixel selection method."
), # noqa
buffer: Optional[float] = Query(
None,
gt=0,
alias="buffer",
title="Tile buffer.",
description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).",
), # noqa
post_process=Depends(self.process_dependency), # noqa
rescale: Optional[List[Tuple[float, ...]]] = Depends(
RescalingParams
), # noqa
color_formula: Optional[str] = Query(
None,
title="Color Formula",
description="rio-color formula (info: https://github.com/mapbox/rio-color)",
), # noqa
colormap=Depends(self.colormap_dependency), # noqa
render_params=Depends(self.render_dependency), # noqa
pgstac_params: PgSTACParams = Depends(), # noqa
backend_params=Depends(self.backend_dependency), # noqa
reader_params=Depends(self.reader_dependency), # noqa
layer: str = Query(None, description="Name of default configuration"),
):
"""Return TileJSON document for a SearchId."""
with request.app.state.dbpool.connection() as conn:
with conn.cursor(row_factory=class_row(model.Search)) as cursor:
cursor.execute(
"SELECT * FROM searches WHERE hash=%s;",
(searchid,),
)
search_info = cursor.fetchone()
if not search_info:
raise KeyError(f"search {searchid} not found")
route_params = {
"searchid": search_info.id,
"z": "{z}",
"x": "{x}",
"y": "{y}",
"scale": tile_scale,
"TileMatrixSetId": tms.identifier,
}
if tile_format:
route_params["format"] = tile_format.value
tiles_url = self.url_for(request, "tile", **route_params)
qs_key_to_remove = [
"tilematrixsetid",
"tile_format",
"tile_scale",
"minzoom",
"maxzoom",
"layer",
]
qs = [
(key, value)
for (key, value) in request.query_params._list
if key.lower() not in qs_key_to_remove
]
if layer:
config = search_info.defaults.get(layer)
if not config:
raise HTTPException(status_code=404, detail=f"Invalid {layer} configuration.")
# This assume the default configuration follows the endpoint expected format
# as `"true_color": [("assets", "B4"), ("assets", "B3"), ("assets", "B2")]`
qs = QueryParams(config)
if qs:
tiles_url += f"?{urlencode(qs)}"
minzoom = _first_value([minzoom, search_info.metadata.minzoom], tms.minzoom)
maxzoom = _first_value([maxzoom, search_info.metadata.maxzoom], tms.maxzoom)
bounds = _first_value(
[search_info.input_search.get("bbox"), search_info.metadata.bounds],
tms.bbox,
)
return {
"bounds": bounds,
"minzoom": minzoom,
"maxzoom": maxzoom,
"name": search_info.metadata.name or search_info.id,
"tiles": [tiles_url],
}