def get_zooms( src_dst, tilesize: int = 256, tms: morecantile.TileMatrixSet = morecantile.tms.get("WebMercatorQuad"), zoom_level_strategy: str = "auto", ) -> Tuple[int, int]: """Calculate raster min/max zoom level.""" if src_dst.crs != tms.crs: aff, w, h = calculate_default_transform( src_dst.crs, tms.crs, src_dst.width, src_dst.height, *src_dst.bounds, ) else: aff = list(src_dst.transform) w = src_dst.width h = src_dst.height resolution = max(abs(aff[0]), abs(aff[4])) max_zoom = tms.zoom_for_res( resolution, max_z=30, zoom_level_strategy=zoom_level_strategy, ) overview_level = get_maximum_overview_level(w, h, minsize=tilesize) ovr_resolution = resolution * (2**overview_level) min_zoom = tms.zoom_for_res(ovr_resolution, max_z=30) return (min_zoom, max_zoom)
def get_web_optimized_params( src_dst, tilesize=256, warp_resampling: str = "nearest", zoom_level_strategy: str = "auto", aligned_levels: Optional[int] = None, tms: morecantile.TileMatrixSet = morecantile.tms.get("WebMercatorQuad"), ) -> Dict: """Return VRT parameters for a WebOptimized COG.""" bounds = list( transform_bounds(src_dst.crs, CRS.from_epsg(4326), *src_dst.bounds, densify_pts=21)) min_zoom, max_zoom = get_zooms( src_dst, tilesize=tilesize, tms=tms, zoom_level_strategy=zoom_level_strategy, ) if aligned_levels is not None: min_zoom = max_zoom - aligned_levels ul_tile = tms.tile(bounds[0], bounds[3], min_zoom) left, _, _, top = tms.xy_bounds(ul_tile.x, ul_tile.y, ul_tile.z) vrt_res = tms._resolution(tms.matrix(max_zoom)) vrt_transform = Affine(vrt_res, 0, left, 0, -vrt_res, top) lr_tile = tms.tile(bounds[2], bounds[1], min_zoom) extrema = { "x": { "min": ul_tile.x, "max": lr_tile.x + 1 }, "y": { "min": ul_tile.y, "max": lr_tile.y + 1 }, } vrt_width = ((extrema["x"]["max"] - extrema["x"]["min"]) * tilesize * 2**(max_zoom - min_zoom)) vrt_height = ((extrema["y"]["max"] - extrema["y"]["min"]) * tilesize * 2**(max_zoom - min_zoom)) return dict( crs=tms.crs, transform=vrt_transform, width=vrt_width, height=vrt_height, resampling=ResamplingEnums[warp_resampling], )
async def tile( self, x: int, y: int, z: int, tile_size: int = 256, tms: TileMatrixSet = DEFAULT_TMS, resample_method: int = Image.NEAREST, ) -> np.ndarray: tile = morecantile.Tile(x=x, y=y, z=z) tile_bounds = tms.xy_bounds(tile) width = height = tile_size if self.cog.epsg != tms.crs: arr = await self._warped_read( tile_bounds, width, height, bounds_crs=tms.crs, resample_method=resample_method, ) else: arr = await self.cog.read(tile_bounds, shape=(width, height), resample_method=resample_method) return arr
async def tile_from_function( self, db: AsyncSession, tile: morecantile.Tile, tms: morecantile.TileMatrixSet, obj_in: VectorTileFunction, **kwargs: Any, ) -> Any: """Get Tile Data.""" bbox = tms.xy_bounds(tile) async with pool.acquire() as conn: transaction = conn.transaction() await transaction.start() await conn.execute(obj_in.sql) function_params = ":xmin, :ymin, :xmax, :ymax, :epsg" if kwargs: params = ", ".join([f"{k} => {v}" for k, v in kwargs.items()]) function_params += f", {params}" sql_query = text( f"SELECT {obj_in.function_name}({function_params})") content = await conn.fetchval_b( sql_query, xmin=bbox.left, ymin=bbox.bottom, xmax=bbox.right, ymax=bbox.top, epsg=tms.crs.to_epsg(), ) await transaction.rollback() return content
def geotiff_options( x: int, y: int, z: int, tilesize: int = 256, tms: morecantile.TileMatrixSet = default_tms, ) -> Dict: """GeoTIFF options.""" bounds = tms.xy_bounds(morecantile.Tile(x=x, y=y, z=z)) dst_transform = from_bounds(*bounds, tilesize, tilesize) return dict(crs=tms.crs, transform=dst_transform)
def get_web_optimized_params( src_dst, zoom_level_strategy: str = "auto", zoom_level: Optional[int] = None, aligned_levels: Optional[int] = None, tms: morecantile.TileMatrixSet = morecantile.tms.get("WebMercatorQuad"), ) -> Dict: """Return VRT parameters for a WebOptimized COG.""" if src_dst.crs != tms.rasterio_crs: with WarpedVRT(src_dst, crs=tms.rasterio_crs) as vrt: bounds = vrt.bounds aff = list(vrt.transform) else: bounds = src_dst.bounds aff = list(src_dst.transform) resolution = max(abs(aff[0]), abs(aff[4])) if zoom_level is None: # find max zoom (closest to the raster resolution) max_zoom = tms.zoom_for_res( resolution, max_z=30, zoom_level_strategy=zoom_level_strategy, ) else: max_zoom = zoom_level # defined the zoom level we want to align the raster aligned_levels = aligned_levels or 0 base_zoom = max_zoom - aligned_levels # find new raster bounds (bounds of UL tile / LR tile) ul_tile = tms._tile(bounds[0], bounds[3], base_zoom) w, _, _, n = tms.xy_bounds(ul_tile) # The output resolution should match the TMS resolution at MaxZoom vrt_res = tms._resolution(tms.matrix(max_zoom)) # Output transform is built from the origin (UL tile) and output resolution vrt_transform = Affine(vrt_res, 0, w, 0, -vrt_res, n) lr_tile = tms._tile(bounds[2], bounds[1], base_zoom) e, _, _, s = tms.xy_bounds( morecantile.Tile(lr_tile.x + 1, lr_tile.y + 1, lr_tile.z) ) vrt_width = max(1, round((e - w) / vrt_transform.a)) vrt_height = max(1, round((s - n) / vrt_transform.e)) return dict( crs=tms.rasterio_crs, transform=vrt_transform, width=vrt_width, height=vrt_height, )
def get_zooms( src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT], tilesize: int = 256, tms: morecantile.TileMatrixSet = morecantile.tms.get("WebMercatorQuad"), zoom_level_strategy: str = "auto", ) -> Tuple[int, int]: """Calculate raster min/max zoom level.""" # If the raster is not in the TMS CRS we calculate its projected properties (height, width, resolution) if src_dst.crs != tms.rasterio_crs: aff, w, h = calculate_default_transform( src_dst.crs, tms.rasterio_crs, src_dst.width, src_dst.height, *src_dst.bounds, ) else: aff = list(src_dst.transform) w = src_dst.width h = src_dst.height resolution = max(abs(aff[0]), abs(aff[4])) # The maxzoom is defined by finding the minimum difference between # the raster resolution and the zoom level resolution max_zoom = tms.zoom_for_res( resolution, max_z=30, zoom_level_strategy=zoom_level_strategy, ) # The minzoom is defined by the resolution of the maximum theoretical overview level max_possible_overview_level = get_maximum_overview_level(w, h, minsize=tilesize) ovr_resolution = resolution * (2**max_possible_overview_level) min_zoom = tms.zoom_for_res(ovr_resolution, max_z=30) return (min_zoom, max_zoom)
def test_nonearthbody(): """COGReader should work with non-earth dataset.""" with pytest.warns(UserWarning): with COGReader(COG_EUROPA) as cog: assert cog.minzoom == 0 assert cog.maxzoom == 24 with pytest.warns(None) as warnings: with COGReader(COG_EUROPA) as cog: assert cog.info() assert len(warnings) == 2 img = cog.read() assert numpy.array_equal(img.data, cog.dataset.read(indexes=(1, ))) assert img.width == cog.dataset.width assert img.height == cog.dataset.height assert img.count == cog.dataset.count img = cog.preview() assert img.bounds == cog.bounds part = cog.part(cog.bounds, bounds_crs=cog.crs) assert part.bounds == cog.bounds lon = (cog.bounds[0] + cog.bounds[2]) / 2 lat = (cog.bounds[1] + cog.bounds[3]) / 2 assert cog.point(lon, lat, coord_crs=cog.crs)[0] is not None europa_crs = CRS.from_authority("ESRI", 104915) tms = TileMatrixSet.custom( crs=europa_crs, extent=europa_crs.area_of_use.bounds, matrix_scale=[2, 1], ) with pytest.warns(None) as warnings: with COGReader(COG_EUROPA, tms=tms) as cog: assert cog.minzoom == 4 assert cog.maxzoom == 6 # Get Tile covering the UL corner bounds = transform_bounds(cog.crs, tms.rasterio_crs, *cog.bounds) t = tms._tile(bounds[0], bounds[1], cog.minzoom) img = cog.tile(t.x, t.y, t.z) assert img.height == 256 assert img.width == 256 assert img.crs == tms.rasterio_crs assert len(warnings) == 0
def test_nonearth_custom(): """Test Custom geographic_crs.""" MARS2000_SPHERE = CRS.from_proj4("+proj=longlat +R=3396190 +no_defs") MARS_MERCATOR = CRS.from_proj4( "+proj=merc +R=3396190 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +no_defs" ) mars_tms = TileMatrixSet.custom( [ -179.9999999999996, -85.05112877980656, 179.9999999999996, 85.05112877980656, ], MARS_MERCATOR, extent_crs=MARS2000_SPHERE, title="Web Mercator Mars", geographic_crs=MARS2000_SPHERE, ) @attr.s class MarsReader(COGReader): """Use custom geographic CRS.""" geographic_crs: rasterio.crs.CRS = attr.ib( init=False, default=rasterio.crs.CRS.from_proj4( "+proj=longlat +R=3396190 +no_defs"), ) with pytest.warns(None) as warnings: with MarsReader(COG_MARS, tms=mars_tms) as cog: assert cog.geographic_bounds[0] > -180 assert len(warnings) == 0 with pytest.warns(None) as warnings: with COGReader( COG_MARS, tms=mars_tms, geographic_crs=rasterio.crs.CRS.from_proj4( "+proj=longlat +R=3396190 +no_defs"), ) as cog: assert cog.geographic_bounds[0] > -180 assert len(warnings) == 0
def wmts( request: Request, tms: TileMatrixSet = Depends(self.tms_dependency), src_path=Depends(self.path_dependency), tile_format: ImageType = Query( ImageType.png, description="Output image type. Default is png." ), 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 render_params=Depends(self.render_dependency), # noqa kwargs: Dict = Depends(self.additional_dependency), # noqa ): """OGC WMTS endpoint.""" route_params = { "z": "{TileMatrix}", "x": "{TileCol}", "y": "{TileRow}", "scale": tile_scale, "format": tile_format.value, "TileMatrixSetId": tms.identifier, } tiles_url = self.url_for(request, "tile", **route_params) q = dict(request.query_params) q.pop("TileMatrixSetId", None) q.pop("tile_format", None) q.pop("tile_scale", None) q.pop("minzoom", None) q.pop("maxzoom", None) q.pop("SERVICE", None) q.pop("REQUEST", None) qs = urlencode(list(q.items())) tiles_url += f"?{qs}" with rasterio.Env(**self.gdal_config): with self.reader( src_path.url, tms=tms, **self.reader_options ) as src_dst: bounds = src_dst.bounds minzoom = minzoom if minzoom is not None else src_dst.minzoom maxzoom = maxzoom if maxzoom is not None else src_dst.maxzoom tileMatrix = [] for zoom in range(minzoom, maxzoom + 1): matrix = tms.matrix(zoom) tm = f""" <TileMatrix> <ows:Identifier>{matrix.identifier}</ows:Identifier> <ScaleDenominator>{matrix.scaleDenominator}</ScaleDenominator> <TopLeftCorner>{matrix.topLeftCorner[0]} {matrix.topLeftCorner[1]}</TopLeftCorner> <TileWidth>{matrix.tileWidth}</TileWidth> <TileHeight>{matrix.tileHeight}</TileHeight> <MatrixWidth>{matrix.matrixWidth}</MatrixWidth> <MatrixHeight>{matrix.matrixHeight}</MatrixHeight> </TileMatrix>""" tileMatrix.append(tm) return templates.TemplateResponse( "wmts.xml", { "request": request, "tiles_endpoint": tiles_url, "bounds": bounds, "tileMatrix": tileMatrix, "tms": tms, "title": "Cloud Optimized GeoTIFF", "layer_name": "cogeo", "media_type": tile_format.mimetype, }, media_type=MimeTypes.xml.value, )
def ogc_wmts( endpoint: str, tms: morecantile.TileMatrixSet, bounds: List[float] = [-180.0, -90.0, 180.0, 90.0], minzoom: int = 0, maxzoom: int = 24, query_string: str = "", title: str = "Cloud Optimizied GeoTIFF", ) -> str: """ Create WMTS XML template. Attributes ---------- endpoint : str, required tiler endpoint. tms : morecantile.TileMatrixSet Custom Tile Matrix Set. bounds : tuple, optional WGS84 layer bounds (default: [-180.0, -90.0, 180.0, 90.0]). query_string : str, optional Endpoint querystring. minzoom : int, optional (default: 0) min zoom. maxzoom : int, optional (default: 25) max zoom. title: str, optional (default: "Cloud Optimizied GeoTIFF") Layer title. Returns ------- xml : str OGC Web Map Tile Service (WMTS) XML template. """ content_type = "image/png" layer = tms.identifier tileMatrixArray = [] for zoom in range(minzoom, maxzoom + 1): matrix = tms.matrix(zoom) tm = f""" <TileMatrix> <ows:Identifier>{matrix.identifier}</ows:Identifier> <ScaleDenominator>{matrix.scaleDenominator}</ScaleDenominator> <TopLeftCorner>{matrix.topLeftCorner[0]} {matrix.topLeftCorner[1]}</TopLeftCorner> <TileWidth>{matrix.tileWidth}</TileWidth> <TileHeight>{matrix.tileHeight}</TileHeight> <MatrixWidth>{matrix.matrixWidth}</MatrixWidth> <MatrixHeight>{matrix.matrixHeight}</MatrixHeight> </TileMatrix>""" tileMatrixArray.append(tm) tileMatrix = "\n".join(tileMatrixArray) xml = f"""<Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd" version="1.0.0"> <ows:ServiceIdentification> <ows:Title>{title}</ows:Title> <ows:ServiceType>OGC WMTS</ows:ServiceType> <ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion> </ows:ServiceIdentification> <ows:OperationsMetadata> <ows:Operation name="GetCapabilities"> <ows:DCP> <ows:HTTP> <ows:Get xlink:href="{endpoint}/{layer}/wmts?{query_string}"> <ows:Constraint name="GetEncoding"> <ows:AllowedValues> <ows:Value>RESTful</ows:Value> </ows:AllowedValues> </ows:Constraint> </ows:Get> </ows:HTTP> </ows:DCP> </ows:Operation> <ows:Operation name="GetTile"> <ows:DCP> <ows:HTTP> <ows:Get xlink:href="{endpoint}/{layer}/wmts?{query_string}"> <ows:Constraint name="GetEncoding"> <ows:AllowedValues> <ows:Value>RESTful</ows:Value> </ows:AllowedValues> </ows:Constraint> </ows:Get> </ows:HTTP> </ows:DCP> </ows:Operation> </ows:OperationsMetadata> <Contents> <Layer> <ows:Identifier>{layer}</ows:Identifier> <ows:WGS84BoundingBox crs="urn:ogc:def:crs:OGC:2:84"> <ows:LowerCorner>{bounds[0]} {bounds[1]}</ows:LowerCorner> <ows:UpperCorner>{bounds[2]} {bounds[3]}</ows:UpperCorner> </ows:WGS84BoundingBox> <Style isDefault="true"> <ows:Identifier>default</ows:Identifier> </Style> <Format>{content_type}</Format> <TileMatrixSetLink> <TileMatrixSet>{layer}</TileMatrixSet> </TileMatrixSetLink> <ResourceURL format="{content_type}" resourceType="tile" template="{endpoint}/tiles/{layer}/{{TileMatrix}}/{{TileCol}}/{{TileRow}}.png?{query_string}"/> </Layer> <TileMatrixSet> <ows:Identifier>{layer}</ows:Identifier> <ows:SupportedCRS>EPSG:{tms.crs.to_epsg()}</ows:SupportedCRS> {tileMatrix} </TileMatrixSet> </Contents> <ServiceMetadataURL xlink:href='{endpoint}/{layer}/wmts?{query_string}'/> </Capabilities>""" return xml
def wmts( request: Request, tms: TileMatrixSet = Depends(self.tms_dependency), src_path=Depends(self.path_dependency), tile_format: ImageType = Query( ImageType.png, description="Output image type. Default is png."), 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 render_params=Depends(self.render_dependency), # noqa colormap=Depends(self.colormap_dependency), # noqa pixel_selection: PixelSelectionMethod = Query( PixelSelectionMethod.first, description="Pixel selection method."), # noqa kwargs: Dict = Depends(self.additional_dependency), # noqa ): """OGC WMTS endpoint.""" route_params = { "z": "{TileMatrix}", "x": "{TileCol}", "y": "{TileRow}", "scale": tile_scale, "format": tile_format.value, "TileMatrixSetId": tms.identifier, } tiles_url = self.url_for(request, "tile", **route_params) qs_key_to_remove = [ "tilematrixsetid", "tile_format", "tile_scale", "minzoom", "maxzoom", "service", "request", ] qs = [(key, value) for (key, value) in request.query_params._list if key.lower() not in qs_key_to_remove] if qs: tiles_url += f"?{urlencode(qs)}" with self.reader(src_path, **self.backend_options) as src_dst: bounds = src_dst.bounds minzoom = minzoom if minzoom is not None else src_dst.minzoom maxzoom = maxzoom if maxzoom is not None else src_dst.maxzoom tileMatrix = [] for zoom in range(minzoom, maxzoom + 1): matrix = tms.matrix(zoom) tm = f""" <TileMatrix> <ows:Identifier>{matrix.identifier}</ows:Identifier> <ScaleDenominator>{matrix.scaleDenominator}</ScaleDenominator> <TopLeftCorner>{matrix.topLeftCorner[0]} {matrix.topLeftCorner[1]}</TopLeftCorner> <TileWidth>{matrix.tileWidth}</TileWidth> <TileHeight>{matrix.tileHeight}</TileHeight> <MatrixWidth>{matrix.matrixWidth}</MatrixWidth> <MatrixHeight>{matrix.matrixHeight}</MatrixHeight> </TileMatrix>""" tileMatrix.append(tm) return templates.TemplateResponse( "wmts.xml", { "request": request, "tiles_endpoint": tiles_url, "bounds": bounds, "tileMatrix": tileMatrix, "tms": tms, "title": "Cloud Optimized GeoTIFF", "layer_name": "cogeo", "media_type": tile_format.mediatype, }, media_type=MediaType.xml.value, )
async def tile( request: Request, table: TableMetadata = Depends(TableParams), tile: Tile = Depends(TileParams), tms: TileMatrixSet = Depends(TileMatrixSetParams), db_pool: Pool = Depends(_get_db_pool), columns: str = None, ) -> TileResponse: """Handle /tiles requests.""" timings = [] headers: Dict[str, str] = {} bbox = tms.xy_bounds(tile) epsg = tms.crs.to_epsg() segSize = (bbox.xmax - bbox.xmin) / 4 geometry_column = table.geometry_column cols = table.properties if geometry_column in cols: del cols[geometry_column] if columns is not None: include_cols = [c.strip() for c in columns.split(",")] for c in cols.copy(): if c not in include_cols: del cols[c] colstring = ", ".join(list(cols)) limitval = str(int(MAX_FEATURES_PER_TILE)) limit = f"LIMIT {limitval}" if MAX_FEATURES_PER_TILE > -1 else "" sql_query = f""" WITH bounds AS ( SELECT ST_Segmentize( ST_MakeEnvelope( $1, $2, $3, $4, $5 ), $6 ) AS geom ), mvtgeom AS ( SELECT ST_AsMVTGeom( ST_Transform(t.{geometry_column}, $5), bounds.geom, $7, $8 ) AS geom, {colstring} FROM {table.id} t, bounds WHERE ST_Intersects( ST_Transform(t.geom, 4326), ST_Transform(bounds.geom, 4326) ) {limit} ) SELECT ST_AsMVT(mvtgeom.*) FROM mvtgeom """ with Timer() as t: async with db_pool.acquire() as conn: q = await conn.prepare(sql_query) content = await q.fetchval( bbox.xmin, # 1 bbox.ymin, # 2 bbox.xmax, # 3 bbox.ymax, # 4 epsg, # 5 segSize, # 6 TILE_RESOLUTION, # 7 TILE_BUFFER, # 8 ) timings.append(("db-read", t.elapsed)) if timings: headers["X-Server-Timings"] = "; ".join([ "{} - {:0.2f}".format(name, time * 1000) for (name, time) in timings ]) return TileResponse(bytes(content), media_type=MimeTypes.pbf.value, headers=headers)
async def tile_from_table( self, db: AsyncSession, tile: morecantile.Tile, tms: morecantile.TileMatrixSet, obj_in: VectorTileTable, **kwargs: Any, ) -> Any: """Get Tile Data.""" bbox = tms.xy_bounds(tile) limit = kwargs.get("limit", str(settings.MAX_FEATURES_PER_TILE) ) # Number of features to write to a tile. columns = kwargs.get( "columns" ) # Comma-seprated list of properties (column's name) to include in the tile resolution = kwargs.get("resolution", str( settings.TILE_RESOLUTION)) # Tile's resolution buffer = kwargs.get("buffer", str( settings.TILE_BUFFER)) # Size of extra data to add for a tile. limitstr = f"LIMIT {limit}" if int(limit) > -1 else "" # create list of columns to return geometry_column = obj_in.geometry_column cols = obj_in.properties if geometry_column in cols: del cols[geometry_column] if columns is not None: include_cols = [c.strip() for c in columns.split(",")] for c in cols.copy(): if c not in include_cols: del cols[c] colstring = ", ".join(list(cols)) segSize = bbox.right - bbox.left sql_query = f""" WITH bounds AS ( SELECT ST_Segmentize( ST_MakeEnvelope( :xmin, :ymin, :xmax, :ymax, {tms.crs.to_epsg()} ), :seg_size ) AS geom ), mvtgeom AS ( SELECT ST_AsMVTGeom( ST_Transform(t.{geometry_column}, {tms.crs.to_epsg()}), bounds.geom, :tile_resolution, :tile_buffer ) AS geom, {colstring} FROM {obj_in.id} t, bounds WHERE ST_Intersects( ST_Transform(t.{geometry_column}, 4326), ST_Transform(bounds.geom, 4326) ) {limitstr} ) SELECT ST_AsMVT(mvtgeom.*) FROM mvtgeom """ input_data = { "xmin": bbox.left, "ymin": bbox.bottom, "xmax": bbox.right, "ymax": bbox.top, "seg_size": segSize, "tile_resolution": int(resolution), "tile_buffer": int(buffer), } cursor = await db.execute(sql_query, input_data) feature = cursor.first()["st_asmvt"] return feature
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"), tms: TileMatrixSet = Depends(self.tms_dependency), 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), render_params=Depends(self.render_dependency), pixel_selection: PixelSelectionMethod = Query( PixelSelectionMethod.first, description="Pixel selection method."), kwargs: Dict = Depends(self.additional_dependency), ): """Create map tile from a COG.""" timings = [] headers: Dict[str, str] = {} tilesize = scale * 256 threads = int(os.getenv("MOSAIC_CONCURRENCY", MAX_THREADS)) with utils.Timer() as t: with self.reader( src_path.url, reader=self.dataset_reader, reader_options=self.reader_options, ) as src_dst: mosaic_read = t.from_start timings.append(("mosaicread", round(mosaic_read * 1000, 2))) (data, mask), assets_used = src_dst.tile( x, y, z, pixel_selection=pixel_selection.method(), threads=threads, tilesize=tilesize, **layer_params.kwargs, **dataset_params.kwargs, **kwargs, ) timings.append( ("dataread", round((t.elapsed - mosaic_read) * 1000, 2))) if data is None: raise TileNotFoundError(f"Tile {z}/{x}/{y} was not found") if not format: format = ImageType.jpg if mask.all() else ImageType.png with utils.Timer() as t: data = utils.postprocess( data, mask, rescale=render_params.rescale, color_formula=render_params.color_formula, ) timings.append(("postprocess", round(t.elapsed * 1000, 2))) bounds = tms.xy_bounds(x, y, z) dst_transform = from_bounds(*bounds, tilesize, tilesize) with utils.Timer() as t: content = utils.reformat( data, mask if render_params.return_mask else None, format, colormap=render_params.colormap, transform=dst_transform, crs=tms.crs, ) timings.append(("format", round(t.elapsed * 1000, 2))) if timings: headers["Server-Timing"] = ", ".join( [f"{name};dur={time}" for (name, time) in timings]) if assets_used: headers["X-Assets"] = ",".join(assets_used) return Response( content, media_type=ImageMimeTypes[format.value].value, headers=headers, )
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"), tms: TileMatrixSet = Depends(self.tms_dependency), 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), render_params=Depends(self.render_dependency), kwargs: Dict = Depends(self.additional_dependency), ): """Create map tile from a dataset.""" timings = [] headers: Dict[str, str] = {} tilesize = scale * 256 with utils.Timer() as t: with self.reader(src_path.url, tms=tms, **self.reader_options) as src_dst: tile, mask = src_dst.tile( x, y, z, tilesize=tilesize, **layer_params.kwargs, **dataset_params.kwargs, **kwargs, ) colormap = render_params.colormap or getattr( src_dst, "colormap", None) timings.append(("dataread", round(t.elapsed * 1000, 2))) if not format: format = ImageType.jpg if mask.all() else ImageType.png with utils.Timer() as t: tile = utils.postprocess( tile, mask, rescale=render_params.rescale, color_formula=render_params.color_formula, ) timings.append(("postprocess", round(t.elapsed * 1000, 2))) bounds = tms.xy_bounds(x, y, z) dst_transform = from_bounds(*bounds, tilesize, tilesize) with utils.Timer() as t: content = utils.reformat( tile, mask if render_params.return_mask else None, format, colormap=colormap, transform=dst_transform, crs=tms.crs, ) timings.append(("format", round(t.elapsed * 1000, 2))) if timings: headers["Server-Timing"] = ", ".join( [f"{name};dur={time}" for (name, time) in timings]) return Response( content, media_type=ImageMimeTypes[format.value].value, headers=headers, )