Understanding minzoom in MosaicJSON #577
-
Hello! I've been using code like this to generate MosaicJSON files that I pass to Titiler to get an XYZ endpoint:
Using the result in OpenLayers through Titiler's mosaicjson/tiles endpoint works great. However, as far as I can tell, if I zoom out past 14, Titiler continues to generate tiles, even though a minzoom is defined (and I can see it clearly specified in For now, I have set layer zoom restrictions on the client side, but I'm hoping to share these endpoints with others, and can't expect minimum zooms to be explicitly handled on the client side in every case. Thanks and thanks for Titiler! |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
It's not a bug but a feature, we allow tiles creation on whatever zoom requested but requesting zoom < min zoom will be pretty inefficient (with MosaicJSON). Still we thought it wasn't titiler's place to decide.
which is what Users should do IMO. Still if you want you could update titiler/src/titiler/mosaic/titiler/mosaic/factory.py Lines 220 to 323 in d7ec0c7 # I didn't wrote all the imports
from titiler.mosaic import factory
from fastapi import HTTPException
@dataclass
class MosaicTilerFactory(factory.MosaicTilerFactory):
############################################################################
# /tiles
############################################################################
def tile(self): # noqa: C901
"""Register /tiles endpoints."""
@self.router.get(r"/tiles/{z}/{x}/{y}", **img_endpoint_params)
@self.router.get(r"/tiles/{z}/{x}/{y}.{format}", **img_endpoint_params)
@self.router.get(r"/tiles/{z}/{x}/{y}@{scale}x", **img_endpoint_params)
@self.router.get(r"/tiles/{z}/{x}/{y}@{scale}x.{format}", **img_endpoint_params)
@self.router.get(r"/tiles/{TileMatrixSetId}/{z}/{x}/{y}", **img_endpoint_params)
@self.router.get(
r"/tiles/{TileMatrixSetId}/{z}/{x}/{y}.{format}", **img_endpoint_params
)
@self.router.get(
r"/tiles/{TileMatrixSetId}/{z}/{x}/{y}@{scale}x", **img_endpoint_params
)
@self.router.get(
r"/tiles/{TileMatrixSetId}/{z}/{x}/{y}@{scale}x.{format}",
**img_endpoint_params,
)
def tile(
z: int = Path(..., ge=0, le=30, description="Mercator tiles's zoom level"),
x: int = Path(..., description="Mercator tiles's column"),
y: int = Path(..., description="Mercator tiles's row"),
TileMatrixSetId: Literal[tuple(self.supported_tms.list())] = Query(
self.default_tms,
description=f"TileMatrixSet Name (default: '{self.default_tms}')",
), # noqa
scale: int = Query(
1, gt=0, lt=4, description="Tile size scale. 1=256x256, 2=512x512..."
),
format: ImageType = Query(
None, description="Output image type. Default is auto."
),
src_path=Depends(self.path_dependency),
layer_params=Depends(self.layer_dependency),
dataset_params=Depends(self.dataset_dependency),
pixel_selection=Depends(self.pixel_selection_dependency),
buffer: Optional[float] = Query(
None,
gt=0,
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 * tile_buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).",
),
post_process=Depends(self.process_dependency),
rescale: Optional[List[Tuple[float, ...]]] = Depends(RescalingParams),
color_formula: Optional[str] = Query(
None,
title="Color Formula",
description="rio-color formula (info: https://github.com/mapbox/rio-color)",
),
colormap=Depends(self.colormap_dependency),
render_params=Depends(self.render_dependency),
backend_params=Depends(self.backend_dependency),
reader_params=Depends(self.reader_dependency),
env=Depends(self.environment_dependency),
):
"""Create map tile from a COG."""
threads = int(os.getenv("MOSAIC_CONCURRENCY", MAX_THREADS))
with rasterio.Env(**env):
with self.reader(
src_path,
reader=self.dataset_reader,
reader_options={**reader_params},
**backend_params,
) as src_dst:
# DO NOT ALLOW LOWER ZOOM LEVEL THAN Mosaic minzoom
if z < src_dst.minzoom:
raise HTTPException(
status_code=400,
detail=f"Invalid zoom level {z}, should be between {src_dst.minzoon} and {src_dst.maxzoom}",
)
image, assets = src_dst.tile(
x,
y,
z,
pixel_selection=pixel_selection,
tilesize=scale * 256,
threads=threads,
buffer=buffer,
**layer_params,
**dataset_params,
)
if post_process:
image = post_process(image)
if rescale:
image.rescale(rescale)
if color_formula:
image.apply_color_formula(color_formula)
if not format:
format = ImageType.jpeg if image.mask.all() else ImageType.png
content = image.render(
img_format=format.driver,
colormap=colormap,
**format.profile,
**render_params,
)
headers: Dict[str, str] = {}
if OptionalHeader.x_assets in self.optional_headers:
headers["X-Assets"] = ",".join(assets)
return Response(content, media_type=format.mediatype, headers=headers) if you feel this is a MUST HAVE thing we could set an environment variable or something to allow or disable this behaviour |
Beta Was this translation helpful? Give feedback.
-
Hi @vincentsarago, I really appreciate the thorough reply.
Agreed, but I don't think it's a reach to say that what users should do may not be what they will do. So if someone is panning around at a low zoom-level in some GIS client on a mosaic with hundreds of images, wondering why nothing is loading (while Titiler goes into overdrive), this is the situation where I would like to be able to control for on my end. It also may be that MosaicJSON is not the best solution for sharing endpoints far and wide, but I've been liking other aspects of how it works with my setup so far, so your code is plenty for me to get started with trying this out on my own. Thanks :). |
Beta Was this translation helpful? Give feedback.
👋 @mradamcox
It's not a bug but a feature, we allow tiles creation on whatever zoom requested but requesting zoom < min zoom will be pretty inefficient (with MosaicJSON). Still we thought it wasn't titiler's place to decide.