GDAL WMTS Extension
Goal: add a /wmts.xml
endpoint to return a GDAL WMTS service description XML file
requirements: titiler.extension >=0.11
1 - Create an extension
# wmts.py
"""gdal WMTS service Extension."""
import xml.etree.ElementTree as ET
from dataclasses import dataclass
from typing import Literal
from urllib.parse import urlencode
from fastapi import Depends, Query
from starlette.requests import Request
from titiler.core.factory import BaseTilerFactory, FactoryExtension
from titiler.core.resources.responses import XMLResponse
@dataclass
class gdalwmtsExtension(FactoryExtension):
"""Add /wmts.xml endpoint to a TilerFactory."""
def register(self, factory: BaseTilerFactory): # noqa: C901
"""Register endpoint to the tiler factory."""
@factory.router.get(
"/{tileMatrixSetId}/wmts.xml",
response_class=XMLResponse,
responses={
200: {
"description": "GDAL WMTS service description XML file",
"content": {
"application/xml": {},
},
},
},
)
def gdal_wmts(
request: Request,
tileMatrixSetId: Literal[tuple(factory.supported_tms.list())] = Path( # type: ignore
description="TileMatrixSet Name",
),
url: str = Depends(factory.path_dependency), # noqa
bandscount: int = Query(
..., description="Number of band returned by the tiler"
),
datatype: str = Query(..., description="Datatype returned by the tiler"),
maxconnections: int = Query(
None,
description="Maximum number of simultaneous connections (defaults to 2).",
),
timeout: int = Query(
None, description="Connection timeout in seconds (defaults to 30)."
),
cache: bool = Query(None, description="Allow local cache."),
):
"""Return a GDAL WMTS Service description."""
route_params = {
"tileMatrixSetId": tileMatrixSetId,
}
wmts_url = factory.url_for(request, "wmts", **route_params)
qs_key_to_remove = [
"tilematrixsetid",
"bandscount",
"datatype",
"maxconnections",
"timeout",
]
qs = [
(key, value)
for (key, value) in request.query_params._list
if key.lower() not in qs_key_to_remove
]
if qs:
wmts_url += f"?{urlencode(qs)}"
maxconnections = maxconnections or 2
timeout = timeout or 30
xml = ET.Element("GDAL_WMTS")
cap = ET.SubElement(xml, "GetCapabilitiesUrl")
cap.text = wmts_url
bandel = ET.SubElement(xml, "BandsCount")
bandel.text = str(bandscount)
datael = ET.SubElement(xml, "DataType")
datael.text = datatype
if cache:
cacheel = ET.SubElement(xml, "Cache")
connel = ET.SubElement(xml, "MaxConnections")
connel.text = str(maxconnections)
timeel = ET.SubElement(xml, "Timeout")
timeel.text = str(timeout)
codeel = ET.SubElement(xml, "ZeroBlockHttpCodes")
codeel.text = "404"
excepel = ET.SubElement(xml, "ZeroBlockOnServerException")
excepel.text = "true"
return XMLResponse(ET.tostring(xml))
2 - Create app and register our extension
"""app.
app/main.py
"""
from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers
from titiler.core.factory import TilerFactory
from fastapi import FastAPI
from .wmts import gdalwmtsExtension
app = FastAPI(title="My simple app with custom TMS")
# Create a set of endpoints using TilerFactory and add our extension
tiler = TilerFactory(extensions=[gdalwmtsExtension()])
app.include_router(tiler.router)
add_exception_handlers(app, DEFAULT_STATUS_CODES)
3 - Use it
from rio_tiler.io import Reader
with Reader("http://0.0.0.0/WebMercatorQuad/wmts.xml?url=file.tif&bidx=1&bandscount=1&datatype=float32&tile_format=tif") as src:
im = src.preview()
Notes
The /wmts.xml
endpoint has no idea about the data itself and do not care about the bidx
or expression
which is why we need to
set bandscount
and datatype
parameters.
In the example above we use tile_format=tif
so GDAL will fetch tif
tiles and keep the datatype from the data (which we assume to be float32)