Dynamic Backends
The mosaic backend abstract BaseBackend has been designed to be really flexible and compatible with dynamic tiler built for rio-tiler BaseReader. It also enables the creation of dynamic mosaic where NO mosaicJSON document really exists.
The BaseBackend ABC class defines that the sub class should:
- have a mosaic_defobject (MosaicJSON) or a path as input
- have _read,writeandupdatemethods defined
Other attributes can default to the BaseBackend defaults:
- tmsis set to WebMercator
- minzoomis set to- 0(mosaicJSON default)
- maxzoomis set to- 30(mosaicJSON default)
- boundsis set to- (-180, -90, 180, 90)(mosaicJSON default)
all other methods are built on top of the MosaicJSON definition.
For a dynamic backend we do not want to construct nor store a mosaicJSON object but fetch the assets needed
on each tile() or point() request.
For this to be possible we need to :
- create a fakeempty mosaicJSON
- create passthrough _read,writeandupdatemethods
- create custom get_assets(),assets_for_tile()andassets_for_point()methods.
Here is an example of a Dynamic STAC backend where on each tile() or point() call, the backend will send a request to the STAC api endpoint to find the assets interesecting with the request.
from typing import Dict, Tuple, Type, Optional, List
import attr
import morecantile
from rasterio.crs import CRS
from rio_tiler.constants import WEB_MERCATOR_TMS, WGS84_CRS
from rio_tiler.io import BaseReader
from rio_tiler.io import STACReader
from cogeo_mosaic.backends.base import BaseBackend
from cogeo_mosaic.backends.stac import _fetch, default_stac_accessor
from cogeo_mosaic.mosaic import MosaicJSON
@attr.s
class DynamicStacBackend(BaseBackend):
    """Like a STAC backend but dynamic"""
    # input should be the STAC-API url
    input: str = attr.ib()
    # Addition required attribute (STAC Query)
    query: Dict = attr.ib(factory=dict)
    minzoom: int = attr.ib()
    maxzoom: int = attr.ib()
    tms: morecantile.TileMatrixSet = attr.ib(default=WEB_MERCATOR_TMS)
    reader: Type[BaseReader] = attr.ib(default=STACReader)
    reader_options: Dict = attr.ib(factory=dict)
    bounds: Tuple[float, float, float, float] = attr.ib(
        default=(-180, -90, 180, 90)
    )
    crs: CRS = attr.ib(default=WGS84_CRS)
    # STAC API related options
    # max_items |  next_link_key | limit
    stac_api_options: Dict = attr.ib(factory=dict)
    # The reader is read-only, we can't pass mosaic_def to the init method
    mosaic_def: MosaicJSON = attr.ib(init=False)
    _backend_name = "DynamicSTAC"
    @minzoom.default
    def _minzoom(self):
        return self.tms.minzoom
    @maxzoom.default
    def _maxzoom(self):
        return self.tms.maxzoom
    def __attrs_post_init__(self):
        """Post Init."""
        # Construct a FAKE/Empty mosaicJSON
        # mosaic_def has to be defined. As we do for the DynamoDB and SQLite backend
        self.mosaic_def = MosaicJSON(
            mosaicjson="0.0.3",
            name="it's fake but it's ok",
            bounds=self.bounds,
            minzoom=self.minzoom,
            maxzoom=self.maxzoom,
            tiles=[]  # we set `tiles` to an empty list.
        )
    def write(self, overwrite: bool = True):
        """This method is not used but is required by the abstract class."""
        raise NotImplementedError
    def update(self):
        """We overwrite the default method."""
        raise NotImplementedError
    def _read(self) -> MosaicJSON:
        """This method is not used but is required by the abstract class."""
        pass
    def assets_for_tile(self, x: int, y: int, z: int) -> List[str]:
        """Retrieve assets for tile."""
        bounds = self.tms.bounds(x, y, z)
        geom = {
            "type": "Polygon",
            "coordinates": [
                [
                    [bounds[0], bounds[3]],
                    [bounds[0], bounds[1]],
                    [bounds[2], bounds[1]],
                    [bounds[2], bounds[3]],
                    [bounds[0], bounds[3]],
                ]
            ],
        }
        return self.get_assets(geom)
    def assets_for_point(self, lng: float, lat: float) -> List[str]:
        """Retrieve assets for point.
        Note: some API only accept Polygon.
        """
        EPSILON = 1e-14
        geom = {
            "type": "Polygon",
            "coordinates": [
                [
                    [lng - EPSILON, lat + EPSILON],
                    [lng - EPSILON, lat - EPSILON],
                    [lng + EPSILON, lat - EPSILON],
                    [lng + EPSILON, lat + EPSILON],
                    [lng - EPSILON, lat + EPSILON],
                ]
            ],
        }
        return self.get_assets(geom)
    def get_assets(self, geom) -> List[str]:
        """Send query to the STAC-API and retrieve assets."""
        query = self.query.copy()
        query["intersects"] = geom
        features = _fetch(
            self.input,
            query,
            **self.stac_api_options,
        )
        return [default_stac_accessor(f) for f in features]
    @property
    def _quadkeys(self) -> List[str]:
        return []
Full examples can be found at examples/Create_a_Dynamic_StacBackend/ and examples/Create_a_Dynamic_RtreeBackend/.