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, )
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
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
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, 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( 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, )