def test_fetch_stac(): with STACReader(STAC_PATH, include_asset_types=None) as stac: assert stac.minzoom == 0 assert stac.maxzoom == 24 assert stac.tms.identifier == "WebMercatorQuad" assert stac.filepath == STAC_PATH assert stac.assets == ALL_ASSETS with STACReader(STAC_PATH) as stac: assert stac.minzoom == 0 assert stac.maxzoom == 24 assert stac.tms.identifier == "WebMercatorQuad" assert stac.filepath == STAC_PATH assert "metadata" not in stac.assets assert "thumbnail" not in stac.assets assert "info" not in stac.assets with STACReader(STAC_PATH, include_assets={"B01", "B02"}) as stac: assert stac.assets == ["B01", "B02"] with STACReader(STAC_PATH, include_assets={"B01", "B02"}) as stac: assert stac.assets == ["B01", "B02"] with STACReader(STAC_PATH, exclude_assets={"overview", "visual", "AOT", "WVP", "SCL"}) as stac: assert stac.assets == [ "B01", "B02", "B03", "B04", "B05", "B06", "B07", "B08", "B8A", "B09", "B11", "B12", ] with STACReader(STAC_PATH, include_asset_types={"application/xml"}) as stac: assert stac.assets == ["metadata"] with STACReader( STAC_PATH, include_asset_types={"application/xml", "image/png"}, include_assets={"metadata", "overview"}, ) as stac: assert stac.assets == ["metadata"] with STACReader(STAC_PATH, tms=morecantile.tms.get("WorldCRS84Quad")) as stac: assert stac.minzoom == 0 assert stac.maxzoom == 17 tms = stac.reader_options.get("tms") assert tms.identifier == "WorldCRS84Quad"
async def stac_bounds( resp: Response, url: str = Query(..., description="STAC item URL."), ): """Return the bounds of the STAC item.""" resp.headers["Cache-Control"] = "max-age=3600" with STACReader(url) as stac: return {"bounds": stac.bounds}
async def stac_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="STAC Item URL."), assets: str = Query("", description="comma (,) separated list of asset names."), image_params: CommonImageParams = Depends(), ): """Create image from part of STAC assets.""" timings = [] headers: Dict[str, str] = {} if not image_params.expression and not assets: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Must pass Expression or Asset list.", ) with utils.Timer() as t: with STACReader(url) as stac: data, mask = stac.part( [minx, miny, maxx, maxy], height=image_params.height, width=image_params.width, max_size=image_params.max_size, assets=assets.split(","), expression=image_params.expression, indexes=image_params.indexes, nodata=image_params.nodata, **image_params.kwargs, ) 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: content = utils.reformat( data, mask, img_format=format, colormap=image_params.color_map ) timings.append(("Format", t.elapsed)) 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, )
def test_reader_info(rio): """Test STACReader.info.""" rio.open = mock_rasterio_open with STACReader(STAC_PATH) as stac: with pytest.raises(InvalidAssetName): stac.info(assets="B1") data = stac.info(assets="B01") assert len(data.keys()) == 1 assert data["B01"] with STACReader(STAC_PATH) as stac: data = stac.info(assets=["B04", "B02"]) assert len(data.keys()) == 2 assert data["B02"] assert data["B04"]
def test_reader_point(rio): """Test STACReader.point.""" rio.open = mock_rasterio_open lat = 32 lon = 23.7 with STACReader(STAC_PATH) as stac: with pytest.raises(InvalidAssetName): stac.point(lon, lat, assets="B1") with pytest.raises(MissingAssets): stac.point(lon, lat) data = stac.point(lon, lat, assets="B01") assert len(data) == 1 with STACReader(STAC_PATH) as stac: data = stac.point(lon, lat, expression="B04/B02") assert len(data) == 1 with STACReader(STAC_PATH) as stac: data = stac.point(lon, lat, assets=["B04", "B02"]) assert len(data) == 2 # This is possible but user should not to it ;-) # We are reading B01 and B02 and telling rasterio to return twice bidx 1. with STACReader(STAC_PATH) as stac: data = stac.point(lon, lat, assets=["B04", "B02"], indexes=(1, 1)) assert len(data) == 2 assert len(data[0]) == 2 # Power User might use expression for each assets with STACReader(STAC_PATH) as stac: data = stac.point(lon, lat, assets=["B04", "B02"], asset_expression="b1/2") assert len(data) == 2
async def stac_info( resp: Response, url: str = Query(..., description="STAC item URL."), assets: str = Query(None, description="comma (,) separated list of asset names."), ): """Return basic info on STAC item's COG.""" resp.headers["Cache-Control"] = "max-age=3600" with STACReader(url) as stac: if not assets: return stac.assets info = stac.info(assets.split(",")) return info
async def cog_point( lon: float = Path(..., description="Longitude"), lat: float = Path(..., description="Latitude"), url: str = Query(..., description="Cloud Optimized GeoTIFF URL."), assets: str = Query("", description="comma (,) separated list of asset names."), expression: Optional[str] = Query( None, title="Band Math expression", description="rio-tiler's band math expression (e.g B1/B2)", ), bidx: Optional[str] = Query( None, title="Band indexes", description="comma (',') delimited band indexes", ), asset_expression: Optional[str] = Query( None, title="Band Math expression for assets bands", description="rio-tiler's band math expression (e.g B1/B2)", ), ): """Get Point value for a COG.""" if not expression and not assets: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Must pass Expression or Asset list.", ) 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 STACReader(url) as stac: values = stac.point( lon, lat, assets=assets, expression=expression, indexes=indexes, asset_expression=asset_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 test_reader_tiles(rio): """Test STACReader.tile.""" rio.open = mock_rasterio_open tile = morecantile.Tile(z=9, x=289, y=207) with STACReader(STAC_PATH) as stac: with pytest.raises(InvalidAssetName): stac.tile(*tile, assets="B1") with pytest.raises(MissingAssets): stac.tile(*tile) data, mask = stac.tile(*tile, assets="B01") assert data.shape == (1, 256, 256) assert mask.shape == (256, 256) with STACReader(STAC_PATH) as stac: data, mask = stac.tile(*tile, expression="B01/B02") assert data.shape == (1, 256, 256) assert mask.shape == (256, 256) with STACReader(STAC_PATH) as stac: data, mask = stac.tile(*tile, assets=["B01", "B02"]) assert data.shape == (2, 256, 256) assert mask.shape == (256, 256) # This is possible but user should not to it ;-) # We are reading B01 and B02 and telling rasterio to return twice bidx 1. with STACReader(STAC_PATH) as stac: data, mask = stac.tile(*tile, assets=["B01", "B02"], indexes=(1, 1)) assert data.shape == (4, 256, 256) assert mask.shape == (256, 256) # Power User might use expression for each assets with STACReader(STAC_PATH) as stac: data, mask = stac.tile(*tile, assets=["B01", "B02"], asset_expression="b1/2") assert data.shape == (2, 256, 256) assert mask.shape == (256, 256) with STACReader(STAC_PATH, tms=morecantile.tms.get("WorldCRS84Quad")) as stac: data, mask = stac.tile(4, 1, 2, assets="B01") assert data.shape == (1, 256, 256) assert mask.shape == (256, 256)
def test_reader_part(rio): """Test STACReader.part.""" rio.open = mock_rasterio_open bbox = (23.7, 31.506, 24.1, 32.514) with STACReader(STAC_PATH) as stac: with pytest.raises(InvalidAssetName): stac.part(bbox, assets="B1") with pytest.raises(MissingAssets): stac.part(bbox) data, mask = stac.part(bbox, assets="B01") assert data.shape == (1, 172, 69) assert mask.shape == (172, 69) with STACReader(STAC_PATH) as stac: data, mask = stac.part(bbox, expression="B04/B02") assert data.shape == (1, 1024, 407) assert mask.shape == (1024, 407) with STACReader(STAC_PATH) as stac: data, mask = stac.part(bbox, assets=["B04", "B02"]) assert data.shape == (2, 1024, 407) assert mask.shape == (1024, 407) # This is possible but user should not to it ;-) # We are reading B01 and B02 and telling rasterio to return twice bidx 1. with STACReader(STAC_PATH) as stac: data, mask = stac.part(bbox, assets=["B04", "B02"], indexes=(1, 1)) assert data.shape == (4, 1024, 407) assert mask.shape == (1024, 407) # Power User might use expression for each assets with STACReader(STAC_PATH) as stac: data, mask = stac.part(bbox, assets=["B04", "B02"], asset_expression="b1/2") assert data.shape == (2, 1024, 407) assert mask.shape == (1024, 407) with STACReader(STAC_PATH) as stac: data, mask = stac.part(bbox, assets="B04", max_size=None) assert data.shape == (1, 1030, 409) assert mask.shape == (1030, 409)
async def stac_metadata( request: Request, resp: Response, url: str = Query(..., description="STAC item URL."), assets: str = Query(..., description="comma (,) separated list of asset names."), metadata_params: CommonMetadataParams = Depends(), ): """Return the metadata of the COG.""" with STACReader(url) as stac: info = stac.metadata( assets.split(","), 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, ) resp.headers["Cache-Control"] = "max-age=3600" return info
def test_reader_preview(rio): """Test STACReader.preview.""" rio.open = mock_rasterio_open with STACReader(STAC_PATH) as stac: with pytest.raises(InvalidAssetName): stac.preview(assets="B1") with pytest.raises(MissingAssets): stac.preview() data, mask = stac.preview(assets="B01") assert data.shape == (1, 183, 183) assert mask.shape == (183, 183) with STACReader(STAC_PATH) as stac: data, mask = stac.preview(expression="B04/B02") assert data.shape == (1, 1024, 1024) assert mask.shape == (1024, 1024) with STACReader(STAC_PATH) as stac: data, mask = stac.preview(assets=["B04", "B02"]) assert data.shape == (2, 1024, 1024) assert mask.shape == (1024, 1024) # This is possible but user should not to it ;-) # We are reading B01 and B02 and telling rasterio to return twice bidx 1. with STACReader(STAC_PATH) as stac: data, mask = stac.preview(assets=["B04", "B02"], indexes=(1, 1)) assert data.shape == (4, 1024, 1024) assert mask.shape == (1024, 1024) # Power User might use expression for each assets with STACReader(STAC_PATH) as stac: data, mask = stac.preview(assets=["B04", "B02"], asset_expression="b1/2") assert data.shape == (2, 1024, 1024) assert mask.shape == (1024, 1024) with STACReader(STAC_PATH) as stac: data, mask = stac.preview(assets="B04", max_size=512) assert data.shape == (1, 512, 512) assert mask.shape == (512, 512)
async def stac_tilejson( request: Request, response: Response, TileMatrixSetId: TileMatrixSetNames = Query( TileMatrixSetNames.WebMercatorQuad, # type: ignore description="TileMatrixSet Name (default: 'WebMercatorQuad')", ), url: str = Query(..., description="STAC Item URL."), assets: str = Query("", description="comma (,) separated list of asset names."), expression: Optional[str] = Query( None, title="Band Math expression", description="rio-tiler's band math expression (e.g B1/B2)", ), 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 a TileJSON document for a STAC item.""" 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) if not expression and not assets: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Expression or Assets HAVE to be set in the queryString.", ) qs = urlencode(list(kwargs.items())) if tile_format: tile_url = f"{scheme}://{host}/stac/tiles/{TileMatrixSetId.name}/{{z}}/{{x}}/{{y}}@{tile_scale}x.{tile_format}?{qs}" else: tile_url = f"{scheme}://{host}/stac/tiles/{TileMatrixSetId.name}/{{z}}/{{x}}/{{y}}@{tile_scale}x?{qs}" tms = morecantile.tms.get(TileMatrixSetId.name) with STACReader(url, tms=tms) as stac: center = list(stac.center) if minzoom: center[-1] = minzoom tjson = { "bounds": stac.bounds, "center": tuple(center), "minzoom": minzoom or stac.minzoom, "maxzoom": maxzoom or stac.maxzoom, "name": os.path.basename(url), "tiles": [tile_url], } response.headers["Cache-Control"] = "max-age=3600" return tjson
async def stac_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="STAC Item URL."), assets: str = Query("", description="comma (,) separated list of asset names."), image_params: CommonTileParams = Depends(), cache_client: CacheLayer = Depends(utils.get_cache), request_id: str = Depends(request_hash), ): """Create map tile from a STAC item.""" timings = [] headers: Dict[str, str] = {} if not image_params.expression and not assets: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Must pass Expression or Asset list.", ) 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 STACReader(url, tms=tms) as stac: tile, mask = stac.tile( x, y, z, assets=assets.split(","), tilesize=tilesize, indexes=image_params.indexes, expression=image_params.expression, nodata=image_params.nodata, ) 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)) bounds = tms.xy_bounds(x, y, z) dst_transform = from_bounds(*bounds, tilesize, tilesize) with utils.Timer() as t: content = utils.reformat( tile, mask, img_format=format, colormap=image_params.color_map, transform=dst_transform, crs=tms.crs, ) 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 mock_STACreader(src_path: str, *args, **kwargs) -> COGReader: """Mock rasterio.open.""" assert src_path.startswith("https://myurl.com/") stac_path = os.path.basename(src_path) return STACReader(os.path.join(DATA_DIR, stac_path), *args, **kwargs)