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_def
object (MosaicJSON) or a path as input - have
_read
,write
andupdate
methods defined
Other attributes can default to the BaseBackend defaults:
tms
is set to WebMercatorminzoom
is set to0
(mosaicJSON default)maxzoom
is set to30
(mosaicJSON default)bounds
is 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
fake
empty mosaicJSON - create passthrough
_read
,write
andupdate
methods - 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/.