def test_reader_part(): """Test COGReader.part.""" lon = -58.181 lat = 73.8794 zoom = 7 tms = morecantile.tms.get("WebMercatorQuad") bounds = tms.bounds(tms.tile(lon, lat, zoom)) with COGReader(COG_PATH) as cog: data, mask = cog.part(bounds, dst_crs=cog.dataset.crs) assert data.shape == (1, 896, 906) assert mask.shape == (896, 906) with COGReader(COG_PATH) as cog: data, mask = cog.part(bounds, indexes=(1, 1), dst_crs=cog.dataset.crs) assert data.shape == (2, 896, 906) assert mask.shape == (896, 906) with COGReader(COG_PATH) as cog: data, mask = cog.part(bounds, max_size=512, dst_crs=cog.dataset.crs) assert data.shape == (1, 507, 512) assert mask.shape == (507, 512) with COGReader(COG_PATH) as cog: data, mask = cog.part(bounds, expression="B1/2,B1+3", dst_crs=cog.dataset.crs) assert data.shape == (2, 896, 906) assert mask.shape == (896, 906)
def test_reader_stats(): """Test COGReader.stats.""" with COGReader(COG_PATH) as cog: data = cog.stats() assert data[1]["min"] == 1 assert data[1]["max"] == 7872 with COGReader(COG_CMAP_PATH) as cog: data = cog.stats() assert data[1]["histogram"][1] == [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ]
def test_reader_info(): """Test COGReader.info.""" with COGReader(COG_PATH) as cog: info = cog.info() assert info["minzoom"] == 5 assert info["maxzoom"] == 8 assert info["center"][-1] == 5 assert info["bounds"] assert info["band_metadata"] assert info["band_descriptions"] assert info["dtype"] assert info["colorinterp"] assert info["nodata_type"] assert not info.get("colormap") assert not info.get("scale") assert not info.get("offset") with COGReader(COG_CMAP_PATH) as cog: info = cog.info() assert info["colormap"] with COGReader(COG_SCALE_PATH) as cog: info = cog.info() assert info["scale"] assert info["offset"]
def test_reader_get_zoom(): """Test COGReader._get_zooms function with different projection.""" with COGReader(COG_PATH) as cog: assert cog.minzoom == 5 assert cog.maxzoom == 8 tms = morecantile.tms.get("WorldCRS84Quad") with COGReader(COG_PATH, tms=tms) as cog: assert cog.minzoom == 4 assert cog.maxzoom == 8
def test_reader_metadata(): """Test COGReader.info.""" with COGReader(COG_PATH) as cog: info = cog.metadata() assert info["band_metadata"] assert info["band_descriptions"] assert info["statistics"][1]
def test_reader_point(): """Test COGReader.point.""" lon = -58.181 lat = 73.8794 with COGReader(COG_PATH) as cog: data = cog.point(lon, lat) assert len(data) == 1 with COGReader(COG_PATH) as cog: data = cog.point(lon, lat, indexes=(1, 1)) assert len(data) == 2 with COGReader(COG_PATH) as cog: data = cog.point(lon, lat, expression="B1/2,B1+3") assert len(data) == 2
async def bounds( resp: Response, url: str = Query(..., description="Cloud Optimized GeoTIFF URL."), ): """Handle /bounds requests.""" resp.headers["Cache-Control"] = "max-age=3600" with COGReader(url) as cog: return {"bounds": cog.bounds}
def wtms( request: Request, response: Response, TileMatrixSetId: TileMatrixSetNames = Query( TileMatrixSetNames.WebMercatorQuad, # type: ignore description="TileMatrixSet Name (default: 'WebMercatorQuad')", ), url: str = Query(..., description="Cloud Optimized GeoTIFF URL."), 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..."), ): """OGC WMTS endpoit.""" scheme = request.url.scheme host = request.headers["host"] endpoint = f"{scheme}://{host}/cog" kwargs = dict(request.query_params) kwargs.pop("tile_format", None) kwargs.pop("tile_scale", None) qs = urlencode(list(kwargs.items())) tms = morecantile.tms.get(TileMatrixSetId.name) with COGReader(url, tms=tms) as cog: minzoom, maxzoom, bounds = cog.minzoom, cog.maxzoom, cog.bounds media_type = ImageMimeTypes[tile_format.value].value 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) tile_ext = f"@{tile_scale}x.{tile_format.value}" return templates.TemplateResponse( "wmts.xml", { "request": request, "endpoint": endpoint, "bounds": bounds, "tileMatrix": tileMatrix, "tms": tms, "title": "Cloud Optimized GeoTIFF", "query_string": qs, "tile_format": tile_ext, "media_type": media_type, }, media_type=MimeTypes.xml.value, )
def _wmts( request: Request, response: Response, identifier: str = Path("WebMercatorQuad", title="TMS identifier"), filename: str = Query(...), ): """Handle /tiles requests.""" tms = morecantile.tms.get(identifier) host = request.headers["host"] scheme = request.url.scheme endpoint = f"{scheme}://{host}" with COGReader(f"{filename}.tif", tms=tms) as cog: # type: ignore meta = cog.info return XMLResponse( ogc_wmts( endpoint, tms, bounds=meta["bounds"], query_string=f"filename={filename}", minzoom=meta["minzoom"], maxzoom=meta["maxzoom"], title=os.path.basename(filename), ))
async def cog_info(resp: Response, url: str = Query( ..., description="Cloud Optimized GeoTIFF URL.")): """Return basic info on COG.""" resp.headers["Cache-Control"] = "max-age=3600" with COGReader(url) as cog: info = cog.info return info
def test_reader_preview(): """Test COGReader.preview.""" with COGReader(COG_PATH) as cog: data, mask = cog.preview() assert data.shape == (1, 1024, 1021) assert mask.shape == (1024, 1021) with COGReader(COG_PATH) as cog: data, mask = cog.preview(indexes=(1, 1)) assert data.shape == (2, 1024, 1021) assert mask.shape == (1024, 1021) with COGReader(COG_PATH) as cog: data, mask = cog.preview(expression="B1/2,B1+3") assert data.shape == (2, 1024, 1021) assert mask.shape == (1024, 1021)
async def metadata( request: Request, resp: Response, url: str = Query(..., description="Cloud Optimized GeoTIFF URL."), bidx: Optional[str] = Query(None, description="Coma (',') delimited band indexes"), nodata: Optional[Union[str, int, float]] = Query( None, description="Overwrite internal Nodata value." ), pmin: float = Query(2.0, description="Minimum percentile"), pmax: float = Query(98.0, description="Maximum percentile"), max_size: int = Query(1024, description="Maximum image size to read onto."), histogram_bins: Optional[int] = None, histogram_range: Optional[str] = Query( None, description="Coma (',') delimited Min,Max bounds" ), ): """Handle /metadata requests.""" kwargs = dict(request.query_params) kwargs.pop("url", None) kwargs.pop("bidx", None) kwargs.pop("nodata", None) kwargs.pop("pmin", None) kwargs.pop("pmax", None) kwargs.pop("max_size", None) kwargs.pop("histogram_bins", None) kwargs.pop("histogram_range", None) indexes = tuple(int(s) for s in re.findall(r"\d+", bidx)) if bidx else None if nodata is not None: nodata = numpy.nan if nodata == "nan" else float(nodata) hist_options: Dict[str, Any] = dict() if histogram_bins: hist_options.update(dict(bins=histogram_bins)) if histogram_range: hist_options.update(dict(range=list(map(float, histogram_range.split(","))))) with COGReader(url) as cog: info = cog.info info.pop("maxzoom", None) # We don't use TMS here info.pop("minzoom", None) # We don't use TMS here info.pop("center", None) # We don't use TMS here stats = cog.stats( pmin, pmax, nodata=nodata, indexes=indexes, max_size=max_size, hist_options=hist_options, **kwargs, ) info["statistics"] = stats resp.headers["Cache-Control"] = "max-age=3600" return info
async def info( resp: Response, url: str = Query(..., description="Cloud Optimized GeoTIFF URL.") ): """Handle /info requests.""" resp.headers["Cache-Control"] = "max-age=3600" with COGReader(url) as cog: info = cog.info info.pop("maxzoom", None) # We don't use TMS here info.pop("minzoom", None) # We don't use TMS here info.pop("center", None) # We don't use TMS here return info
def test_COGReader_Options(): """Set options in reader.""" with COGReader(COG_PATH, nodata=1) as cog: meta = cog.metadata() assert meta["statistics"][1]["pc"] == [2720, 6896] with COGReader(COG_PATH, nodata=1) as cog: _, mask = cog.tile(43, 25, 7) assert not mask.all() with COGReader(COG_SCALE_PATH, unscale=True) as cog: p = cog.point(310000, 4100000, coord_crs=cog.dataset.crs) assert round(p[0], 3) == 1000.892 # passing unscale in method should overwrite the defaults p = cog.point(310000, 4100000, coord_crs=cog.dataset.crs, unscale=False) assert p[0] == 8917 cutline = "POLYGON ((13 1685, 1010 6, 2650 967, 1630 2655, 13 1685))" with COGReader(COG_PATH, vrt_options={"cutline": cutline}) as cog: _, mask = cog.preview() assert not mask.all()
async def cog_tilejson( request: Request, response: Response, TileMatrixSetId: TileMatrixSetNames = Query( TileMatrixSetNames.WebMercatorQuad, # type: ignore description="TileMatrixSet Name (default: 'WebMercatorQuad')", ), url: str = Query(..., description="Cloud Optimized GeoTIFF URL."), tile_format: Optional[ImageType] = Query( None, description="Output image type. Default is auto."), 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."), ): """Return TileJSON document for a COG.""" scheme = request.url.scheme host = request.headers["host"] kwargs = dict(request.query_params) kwargs.pop("tile_format", None) kwargs.pop("tile_scale", None) kwargs.pop("TileMatrixSetId", None) kwargs.pop("minzoom", None) kwargs.pop("maxzoom", None) qs = urlencode(list(kwargs.items())) if tile_format: tile_url = f"{scheme}://{host}/cog/tiles/{TileMatrixSetId.name}/{{z}}/{{x}}/{{y}}@{tile_scale}x.{tile_format}?{qs}" else: tile_url = f"{scheme}://{host}/cog/tiles/{TileMatrixSetId.name}/{{z}}/{{x}}/{{y}}@{tile_scale}x?{qs}" tms = morecantile.tms.get(TileMatrixSetId.name) with COGReader(url, tms=tms) as cog: center = list(cog.center) if minzoom: center[-1] = minzoom tjson = { "bounds": cog.bounds, "center": tuple(center), "minzoom": minzoom or cog.minzoom, "maxzoom": maxzoom or cog.maxzoom, "name": os.path.basename(url), "tiles": [tile_url], } response.headers["Cache-Control"] = "max-age=3600" return tjson
def test_reader_tiles(): """Test COGReader.tile.""" lon = -58.181 lat = 73.8794 zoom = 7 tms = morecantile.tms.get("WebMercatorQuad") x, y, z = tms.tile(lon, lat, zoom) with COGReader(COG_PATH, tms=tms) as cog: data, mask = cog.tile(x, y, z) assert data.shape == (1, 256, 256) assert mask.shape == (256, 256) with COGReader(COG_PATH, tms=tms) as cog: data, mask = cog.tile(x, y, z, indexes=(1, 1)) assert data.shape == (2, 256, 256) assert mask.shape == (256, 256) with COGReader(COG_PATH, tms=tms) as cog: # indexes should be ignored, TODO: add warning data, mask = cog.tile(x, y, z, indexes=1, expression="B1+2,B1/3") assert data.shape == (2, 256, 256) assert mask.shape == (256, 256) crs = CRS.from_epsg(32621) extent = [166021.44, 0.00, 534994.66, 9329005.18] # from http://epsg.io/32621 tms = morecantile.TileMatrixSet.custom(extent, crs) x, y, z = tms.tile(lon, lat, zoom) with COGReader(COG_PATH, tms=tms) as cog: data, mask = cog.tile(x, y, z) assert data.shape == (1, 256, 256) assert mask.shape == (256, 256) x, y, z = tms.tile(lon + 10, lat, zoom) with pytest.raises(TileOutsideBounds): cog.tile(x, y, z)
async def cog_metadata( resp: Response, url: str = Query(..., description="Cloud Optimized GeoTIFF URL."), metadata_params: CommonMetadataParams = Depends(), ): """Return the metadata of the COG.""" with COGReader(url) as cog: info = cog.info stats = cog.stats( metadata_params.pmin, metadata_params.pmax, nodata=metadata_params.nodata, indexes=metadata_params.indexes, max_size=metadata_params.max_size, hist_options=metadata_params.hist_options, bounds=metadata_params.bounds, **metadata_params.kwargs, ) info["statistics"] = stats resp.headers["Cache-Control"] = "max-age=3600" return info
def _tile( z: int, x: int, y: int, scale: int = Query( 1, gt=0, lt=4, description="Tile size scale. 1=256x256, 2=512x512..."), identifier: str = Query("WebMercatorQuad", title="TMS identifier"), filename: str = Query(...), ): """Handle /tiles requests.""" tms = morecantile.tms.get(identifier) with COGReader(f"{filename}.tif", tms=tms) as cog: # type: ignore tile, mask = cog.tile(x, y, z, tilesize=scale * 256) ext = ImageType.png driver = drivers[ext.value] options = img_profiles.get(driver.lower(), {}) img = render(tile, mask, img_format="png", **options) return TileResponse(img, media_type=mimetype[ext.value])
async def cog_point( lon: float = Path(..., description="Longitude"), lat: float = Path(..., description="Latitude"), url: str = Query(..., description="Cloud Optimized GeoTIFF URL."), bidx: Optional[str] = Query( None, title="Band indexes", description="comma (',') delimited band indexes", ), expression: Optional[str] = Query( None, title="Band Math expression", description="rio-tiler's band math expression (e.g B1/B2)", ), ): """Get Point value for a COG.""" indexes = tuple(int(s) for s in re.findall(r"\d+", bidx)) if bidx else None timings = [] headers: Dict[str, str] = {} with utils.Timer() as t: with COGReader(url) as cog: values = cog.point(lon, lat, indexes=indexes, expression=expression) timings.append(("Read", t.elapsed)) if timings: headers["X-Server-Timings"] = "; ".join([ "{} - {:0.2f}".format(name, time * 1000) for (name, time) in timings ]) return {"coordinates": [lon, lat], "values": values}
def worker(asset: str) -> Dict: with COGReader(asset) as cog: return cog.metadata(*args, **kwargs)
def worker(asset: str) -> Dict: with COGReader(asset, tms=self.tms) as cog: return cog.info
def mock_reader(src_path: str, *args, **kwargs) -> COGReader: """Mock rasterio.open.""" assert src_path.startswith("https://myurl.com/") cog_path = os.path.basename(src_path) return COGReader(os.path.join(DATA_DIR, cog_path), *args, **kwargs)
async def cog_part( minx: float = Path(..., description="Bounding box min X"), miny: float = Path(..., description="Bounding box min Y"), maxx: float = Path(..., description="Bounding box max X"), maxy: float = Path(..., description="Bounding box max Y"), format: ImageType = Query(None, description="Output image type."), url: str = Query(..., description="Cloud Optimized GeoTIFF URL."), image_params: CommonImageParams = Depends(), ): """Create image from part of a COG.""" timings = [] headers: Dict[str, str] = {} with utils.Timer() as t: with COGReader(url) as cog: data, mask = cog.part( [minx, miny, maxx, maxy], height=image_params.height, width=image_params.width, max_size=image_params.max_size, indexes=image_params.indexes, expression=image_params.expression, nodata=image_params.nodata, **image_params.kwargs, ) colormap = image_params.color_map or cog.colormap timings.append(("Read", t.elapsed)) with utils.Timer() as t: data = utils.postprocess( data, mask, rescale=image_params.rescale, color_formula=image_params.color_formula, ) timings.append(("Post-process", t.elapsed)) with utils.Timer() as t: if format == ImageType.npy: sio = BytesIO() numpy.save(sio, (data, mask)) sio.seek(0) content = sio.getvalue() else: driver = drivers[format.value] options = img_profiles.get(driver.lower(), {}) content = render(data, mask, img_format=driver, colormap=colormap, **options) timings.append(("Format", t.elapsed)) if timings: headers["X-Server-Timings"] = "; ".join([ "{} - {:0.2f}".format(name, time * 1000) for (name, time) in timings ]) return ImgResponse( content, media_type=ImageMimeTypes[format.value].value, headers=headers, )
async def cog_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: TileMatrixSetNames = Query( TileMatrixSetNames.WebMercatorQuad, # type: ignore description="TileMatrixSet Name (default: 'WebMercatorQuad')", ), 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."), url: str = Query(..., description="Cloud Optimized GeoTIFF URL."), image_params: CommonTileParams = Depends(), cache_client: CacheLayer = Depends(utils.get_cache), request_id: str = Depends(request_hash), ): """Create map tile from a COG.""" timings = [] headers: Dict[str, str] = {} tilesize = scale * 256 tms = morecantile.tms.get(TileMatrixSetId.name) content = None if cache_client: try: content, ext = cache_client.get_image_from_cache(request_id) format = ImageType[ext] headers["X-Cache"] = "HIT" except Exception: content = None if not content: with utils.Timer() as t: with COGReader(url, tms=tms) as cog: tile, mask = cog.tile( x, y, z, tilesize=tilesize, indexes=image_params.indexes, expression=image_params.expression, nodata=image_params.nodata, **image_params.kwargs, ) colormap = image_params.color_map or cog.colormap timings.append(("Read", t.elapsed)) if not format: format = ImageType.jpg if mask.all() else ImageType.png with utils.Timer() as t: tile = utils.postprocess( tile, mask, rescale=image_params.rescale, color_formula=image_params.color_formula, ) timings.append(("Post-process", t.elapsed)) with utils.Timer() as t: if format == ImageType.npy: sio = BytesIO() numpy.save(sio, (tile, mask)) sio.seek(0) content = sio.getvalue() else: driver = drivers[format.value] options = img_profiles.get(driver.lower(), {}) if format == ImageType.tif: bounds = tms.xy_bounds(x, y, z) dst_transform = from_bounds(*bounds, tilesize, tilesize) options = {"crs": tms.crs, "transform": dst_transform} content = render(tile, mask, img_format=driver, colormap=colormap, **options) timings.append(("Format", t.elapsed)) if cache_client and content: cache_client.set_image_cache(request_id, (content, format.value)) if timings: headers["X-Server-Timings"] = "; ".join([ "{} - {:0.2f}".format(name, time * 1000) for (name, time) in timings ]) return ImgResponse( content, media_type=ImageMimeTypes[format.value].value, headers=headers, )
def worker(asset: str) -> Dict: with COGReader(asset) as cog: return cog.stats(*args, **kwargs)
def _read_tile(url: str, *args, **kwargs) -> Tuple[numpy.ndarray, numpy.ndarray]: """Read tile from an asset""" with COGReader(url) as cog: tile, mask = cog.tile(*args, **kwargs) return tile, mask
def _read_point(asset: str, *args, **kwargs) -> List: """Read pixel value at a point from an asset""" with COGReader(asset) as cog: return cog.point(*args, **kwargs)
def _tiler(asset): with COGReader(asset) as cog: cog.tile(*tile) return True
def mock_reader(src_path: str, *args, **kwargs) -> COGReader: """Mock rasterio.open.""" prefix = os.path.join(os.path.dirname(__file__), "fixtures") assert src_path.startswith("https://myurl.com/") cog_path = os.path.basename(src_path) return COGReader(os.path.join(prefix, cog_path), *args, **kwargs)
def worker(asset: str) -> List: with COGReader(asset) as cog: return cog.point(*args, **kwargs)