def test_get_zooms_valid(): """Should work as expected (return zooms).""" with rasterio.open(dataset) as src_dst: minzoom, maxzoom = mercator.get_zooms(src_dst) assert minzoom == 4 assert maxzoom == 8 minzoom, maxzoom = mercator.get_zooms(src_dst, tilesize=512) assert minzoom == 4 assert maxzoom == 7 minzoom, maxzoom = mercator.get_zooms(src_dst, ensure_global_max_zoom=True) assert minzoom == 6 assert maxzoom == 10
def _get_tiles(src_path, nb_tiles=5): with rasterio.open(src_path) as src_dst: bounds = warp.transform_bounds( src_dst.crs, "epsg:4326", *src_dst.bounds, densify_pts=21 ) minzoom, maxzoom = mercator.get_zooms(src_dst) while True: zoom = random.randint(maxzoom - 2, maxzoom) tiles = list(mercantile.tiles(*bounds, zoom)) def _f(tile): x, y, z = tile ulx, uly = mercantile.ul(x, y, z) lrx, lry = mercantile.ul(x + 1, y + 1, zoom) return ( (bounds[0] < ulx < bounds[2]) and (bounds[1] < uly < bounds[3]) and (bounds[0] < lrx < bounds[2]) and (bounds[1] < lry < bounds[3]) ) tiles = list(filter(lambda x: _f(x), tiles)) if not tiles: continue if len(tiles) > nb_tiles: return random.sample(tiles, nb_tiles) else: return tiles
def tilejson_handler(url: str, tile_format: str = "png", tile_scale: int = 1, **kwargs: Any) -> Tuple[str, str, str]: """Handle /tilejson.json requests.""" kwargs.update(dict(url=url)) qs = urllib.parse.urlencode(list(kwargs.items())) tile_url = f"{APP.host}/tiles/{{z}}/{{x}}/{{y}}@{tile_scale}x.{tile_format}" if qs: tile_url += f"?{qs}" with rasterio.open(url) as src_dst: bounds = warp.transform_bounds(*[src_dst.crs, "epsg:4326"] + list(src_dst.bounds), densify_pts=21) minzoom, maxzoom = get_zooms(src_dst) center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2, minzoom] meta = dict( bounds=bounds, center=center, minzoom=minzoom, maxzoom=maxzoom, name=os.path.basename(url), tilejson="2.1.0", tiles=[tile_url], ) return ("OK", "application/json", json.dumps(meta))
def tilejson_handler(url: str, tile_format: str = "png", **kwargs: Dict): """Handle /tilejson.json requests.""" qs = urllib.parse.urlencode(list(kwargs.items())) tile_url = f"{APP.host}/tiles/{{z}}/{{x}}/{{y}}.{tile_format}?url={url}" if qs: tile_url += f"&{qs}" with rasterio.open(url) as src_dst: bounds = warp.transform_bounds(src_dst.crs, "epsg:4326", *src_dst.bounds, densify_pts=21) center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2] minzoom, maxzoom = get_zooms(src_dst) meta = dict( bounds=bounds, center=center, minzoom=minzoom, maxzoom=maxzoom, name=os.path.basename(url), tilejson="2.1.0", tiles=[tile_url], ) return ("OK", "application/json", json.dumps(meta))
def get_dataset_info(src_path: str) -> Dict: """Get rasterio dataset meta.""" with rasterio.open(src_path) as src_dst: bounds = transform_bounds(src_dst.crs, "epsg:4326", *src_dst.bounds, densify_pts=21) min_zoom, max_zoom = get_zooms(src_dst, ensure_global_max_zoom=True) return { "geometry": { "type": "Polygon", "coordinates": [[ [bounds[0], bounds[3]], [bounds[0], bounds[1]], [bounds[2], bounds[1]], [bounds[2], bounds[3]], [bounds[0], bounds[3]], ]], }, "properties": { "path": src_path, "bounds": bounds, "minzoom": min_zoom, "maxzoom": max_zoom, "datatype": src_dst.meta["dtype"], }, "type": "Feature", }
def spatial_info(address: str) -> Dict: """ Return COGEO spatial info. Attributes ---------- address : str or PathLike object A dataset path or URL. Will be opened in "r" mode. Returns ------- out : dict. """ with rasterio.open(address) as src_dst: minzoom, maxzoom = get_zooms(src_dst) bounds = transform_bounds(src_dst.crs, constants.WGS84_CRS, *src_dst.bounds, densify_pts=21) center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2, minzoom] return dict(address=address, bounds=bounds, center=center, minzoom=minzoom, maxzoom=maxzoom)
def tilejson_handler( url: str, tile_scale: int = 1, tile_format: str = None, **kwargs: Any ) -> Tuple[str, str, str]: """Handle /tilejson.json requests.""" if tile_scale is not None and isinstance(tile_scale, str): tile_scale = int(tile_scale) kwargs.update(dict(url=url)) qs = urlencode(list(kwargs.items())) if tile_format: tile_url = f"{app.host}/{{z}}/{{x}}/{{y}}@{tile_scale}x.{tile_format}?{qs}" else: tile_url = f"{app.host}/{{z}}/{{x}}/{{y}}@{tile_scale}x?{qs}" with rasterio.Env(aws_session): with rasterio.open(url) as src_dst: bounds = list( warp.transform_bounds( src_dst.crs, "epsg:4326", *src_dst.bounds, densify_pts=21 ) ) minzoom, maxzoom = get_zooms(src_dst) center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2, minzoom] meta = dict( bounds=bounds, center=center, minzoom=minzoom, maxzoom=maxzoom, name=os.path.basename(url), tilejson="2.1.0", tiles=[tile_url], ) return ("OK", "application/json", json.dumps(meta))
def __init__(self, src_path: str, dataset: Union[DatasetReader, WarpedVRT] = None): self.src_path = src_path if src_path: self.src_dst = rasterio.open(self.src_path) elif dataset: self.src_dst = dataset else: raise Exception("Missing path or dataset") self._bounds = transform_bounds(self.src_dst.crs, WGS84_CRS, *self.src_dst.bounds, densify_pts=21) minzoom, maxzoom = get_zooms(self.src_dst) self._minzoom = minzoom self._maxzoom = maxzoom try: self._colormap = self.src_dst.colormap(1) except ValueError: self._colormap = None pass
def get(self, request, pk=None, project_pk=None, tile_type=""): """ Get tile.json for this tasks's asset type """ task = self.get_and_check_task(request, pk) raster_path = get_raster_path(task, tile_type) if not os.path.isfile(raster_path): raise exceptions.NotFound() with rasterio.open(raster_path) as src_dst: minzoom, maxzoom = get_zooms(src_dst) return Response({ 'tilejson': '2.1.0', 'name': task.name, 'version': '1.0.0', 'scheme': 'xyz', 'tiles': [get_tile_url(task, tile_type, self.request.query_params)], 'minzoom': minzoom, 'maxzoom': maxzoom, 'bounds': get_extent(task, tile_type).extent })
def tilejson_handler(request: Dict, url: str, tile_format: str = "png", **kwargs: Dict): """Handle /tilejson.json requests.""" host = request["headers"].get( "X-Forwarded-Host", request["headers"].get("Host", "") ) # Check for API gateway stage if ".execute-api." in host and ".amazonaws.com" in host: stage = request["requestContext"].get("stage", "") host = f"{host}/{stage}" scheme = "http" if host.startswith("127.0.0.1") else "https" qs = urllib.parse.urlencode(list(kwargs.items())) tile_url = f"{scheme}://{host}/tiles/{{z}}/{{x}}/{{y}}.{tile_format}?url={url}" if qs: tile_url += f"&{qs}" with rasterio.open(url) as src_dst: bounds = warp.transform_bounds( *[src_dst.crs, "epsg:4326"] + list(src_dst.bounds), densify_pts=21 ) center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2] minzoom, maxzoom = get_zooms(src_dst) meta = dict( bounds=bounds, center=center, minzoom=minzoom, maxzoom=maxzoom, name=os.path.basename(url), tilejson="2.1.0", tiles=[tile_url], ) return ("OK", "application/json", json.dumps(meta))
def wtms( request: Request, response: Response, 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..."), ): """Wmts endpoit.""" scheme = request.url.scheme host = request.headers["host"] if config.API_VERSION_STR: host += config.API_VERSION_STR endpoint = f"{scheme}://{host}" kwargs = dict(request.query_params) kwargs.pop("tile_format", None) kwargs.pop("tile_scale", None) qs = urlencode(list(kwargs.items())) with rasterio.open(url) as src_dst: bounds = list( warp.transform_bounds(src_dst.crs, constants.WGS84_CRS, *src_dst.bounds, densify_pts=21)) minzoom, maxzoom = get_zooms(src_dst) media_type = mimetype[tile_format.value] tilesize = tile_scale * 256 tileMatrix = [] for zoom in range(minzoom, maxzoom + 1): tileMatrix.append(f"""<TileMatrix> <ows:Identifier>{zoom}</ows:Identifier> <ScaleDenominator>{559082264.02872 / 2 ** zoom / tile_scale}</ScaleDenominator> <TopLeftCorner>-20037508.34278925 20037508.34278925</TopLeftCorner> <TileWidth>{tilesize}</TileWidth> <TileHeight>{tilesize}</TileHeight> <MatrixWidth>{2 ** zoom}</MatrixWidth> <MatrixHeight>{2 ** zoom}</MatrixHeight> </TileMatrix>""") return templates.TemplateResponse( "wmts.xml", { "request": request, "endpoint": endpoint, "bounds": bounds, "tileMatrix": tileMatrix, "title": "Cloud Optimized GeoTIFF", "query_string": qs, "tile_scale": tile_scale, "tile_format": tile_format.value, "media_type": media_type, }, media_type="application/xml", )
def tilejson(request, url, tile_format="png", **kwargs): """ Handle /tilejson.json requests. Note: All the querystring parameters are translated to function keywords and passed as string value by lambda_proxy Attributes ---------- url : str, required Dataset url to read from. image_format : str Image format to return (default: png). kwargs: dict, optional Querystring parameters to forward to the tile url. Returns ------- status : str Status of the request (e.g. OK, NOK). MIME type : str response body MIME type (e.g. application/json). body : str String encoded TileJSON. """ host = request["headers"].get("X-Forwarded-Host", request["headers"].get("Host", "")) # Check for API gateway stage if ".execute-api." in host and ".amazonaws.com" in host: stage = request["requestContext"].get("stage", "") host = f"{host}/{stage}" scheme = "http" if host.startswith("127.0.0.1") else "https" qs = [f"{k}={v}" for k, v in kwargs.items()] qs = "&".join(qs) tile_url = f"{scheme}://{host}/tiles/{{z}}/{{x}}/{{y}}.{tile_format}?url={url}" if qs: tile_url += f"&{qs}" with rasterio.open(url) as src_dst: bounds = warp.transform_bounds(*[src_dst.crs, "epsg:4326"] + list(src_dst.bounds), densify_pts=21) center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2] minzoom, maxzoom = get_zooms(src_dst) meta = dict(bounds=bounds, center=center, minzoom=minzoom, maxzoom=maxzoom, name=os.path.basename(url), tilejson="2.1.0", tiles=[tile_url]) return ("OK", "application/json", json.dumps(meta))
def _get_info(url): with rasterio.open(url) as src_dst: wgs_bounds = warp.transform_bounds(*[src_dst.crs, "epsg:4326"] + list(src_dst.bounds), densify_pts=21) minzoom, maxzoom = get_zooms(src_dst) return { "bounds": list(wgs_bounds), "minzoom": minzoom, "maxzoom": maxzoom }
async def test_cog_read_single_band(create_cog_reader): infile = "https://async-cog-reader-test-data.s3.amazonaws.com/int16_deflate.tif" async with create_cog_reader(infile) as cog: assert cog.profile["count"] == 1 with rasterio.open(infile) as ds: _, zoom = get_zooms(ds) centroid = Polygon.from_bounds( *transform_bounds(cog.epsg, "EPSG:4326", *cog.bounds)).centroid tile = mercantile.tile(centroid.x, centroid.y, zoom) tile_native_bounds = transform_bounds("EPSG:4326", cog.epsg, *mercantile.bounds(tile)) arr = await cog.read(tile_native_bounds, (256, 256)) assert arr.shape == (256, 256) assert arr.dtype == cog.profile["dtype"]
def _get_info(src_path): with rasterio.open(src_path) as src_dst: bounds = transform_bounds( *[src_dst.crs, "epsg:4326"] + list(src_dst.bounds), densify_pts=21 ) center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2] minzoom, maxzoom = get_zooms(src_dst) def _get_name(ix): name = src_dst.descriptions[ix - 1] if not name: name = f"band{ix}" return name band_descriptions = [_get_name(ix) for ix in src_dst.indexes] return bounds, center, minzoom, maxzoom, band_descriptions
def wmts_handler( url: str = None, tile_format: str = "png", tile_scale: int = 1, title: str = "Cloud Optimizied GeoTIFF", **kwargs: Any, ) -> Tuple[str, str, str]: """Handle /wmts requests.""" if tile_scale is not None and isinstance(tile_scale, str): tile_scale = int(tile_scale) # Remove QGIS arguments kwargs.pop("SERVICE", None) kwargs.pop("REQUEST", None) kwargs.update(dict(url=url)) query_string = urllib.parse.urlencode(list(kwargs.items())) # & is an invalid character in XML query_string = query_string.replace("&", "&") with rasterio.Env(aws_session): with rasterio.open(url) as src_dst: bounds = list( warp.transform_bounds(src_dst.crs, "epsg:4326", *src_dst.bounds, densify_pts=21)) minzoom, maxzoom = get_zooms(src_dst) return ( "OK", "application/xml", wmts_template( app.host, query_string, minzoom=minzoom, maxzoom=maxzoom, bounds=bounds, tile_scale=tile_scale, tile_format=tile_format, title=title, ), )
async def test_cog_tiler_tile(create_cog_reader): infile = "https://async-cog-reader-test-data.s3.amazonaws.com/webp_cog.tif" async with create_cog_reader(infile) as cog: tiler = COGTiler(cog) with rasterio.open(infile) as ds: _, zoom = get_zooms(ds) centroid = Polygon.from_bounds( *transform_bounds(cog.epsg, "EPSG:4326", *cog.bounds)).centroid tile = mercantile.tile(centroid.x, centroid.y, zoom) # tile_native_bounds = transform_bounds("EPSG:4326", cog.epsg, *mercantile.bounds(tile)) arr = await tiler.tile(tile.x, tile.y, tile.z, tile_size=256, resample_method=Image.BILINEAR) with cogeo_reader(infile) as ds: rio_tile_arr, rio_tile_mask = ds.tile(tile.x, tile.y, tile.z, tilesize=256, resampling_method="bilinear") if cog.is_masked: tile_arr = np.ma.getdata(arr) tile_mask = np.ma.getmask(arr) # Make sure image data is the same assert pytest.approx(tile_arr - rio_tile_arr, 1) == np.zeros(tile_arr.shape) # Make sure mask data is the same rio_mask_counts = np.unique(rio_tile_mask, return_counts=True) tile_mask_counts = np.unique(tile_mask, return_counts=True) assert len(rio_mask_counts[0]) == len(tile_mask_counts[0]) assert (rio_mask_counts[1][0] * cog.profile["count"] == tile_mask_counts[1][0]) else: assert pytest.approx(arr - rio_tile_arr, 1) == np.zeros(arr.shape)
def _wmts( mosaicid: str = None, url: str = None, tile_format: str = "png", tile_scale: int = 1, title: str = "Cloud Optimizied GeoTIFF Mosaic", **kwargs: Any, ) -> Tuple[str, str, str]: """Handle /wmts requests.""" if tile_scale is not None and isinstance(tile_scale, str): tile_scale = int(tile_scale) kwargs.pop("SERVICE", None) kwargs.pop("REQUEST", None) kwargs.update(dict(url=url)) query_string = urllib.parse.urlencode(list(kwargs.items())) query_string = query_string.replace( "&", "&") # & is an invalid character in XML with rasterio.open(url) as src_dst: bounds = warp.transform_bounds(src_dst.crs, "epsg:4326", *src_dst.bounds, densify_pts=21) minzoom, maxzoom = get_zooms(src_dst) return ( "OK", "application/xml", wmts_template( f"{APP.host}", os.path.basename(url), query_string, minzoom=minzoom, maxzoom=maxzoom, bounds=bounds, tile_scale=tile_scale, tile_format=tile_format, title=title, ), )
def tilejson_handler( event: Dict, sceneid: str, tile_format: str = "png", tile_scale: int = 1, **kwargs: Any, ) -> Tuple[str, str, str]: """Handle /tilejson.json requests.""" # HACK token = event["multiValueQueryStringParameters"].get("access_token") if token: kwargs.update(dict(access_token=token[0])) qs = urllib.parse.urlencode(list(kwargs.items())) tile_url = ( f"{APP.host}/tiles/{sceneid}/{{z}}/{{x}}/{{y}}@{tile_scale}x.{tile_format}?{qs}" ) scene_params = landsat8._landsat_parse_scene_id(sceneid) landsat_address = f"{LANDSAT_BUCKET}/{scene_params['key']}_BQA.TIF" with rasterio.open(landsat_address) as src_dst: bounds = warp.transform_bounds(src_dst.crs, "epsg:4326", *src_dst.bounds, densify_pts=21) minzoom, maxzoom = get_zooms(src_dst) center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2, minzoom] meta = dict( bounds=bounds, center=center, minzoom=minzoom, maxzoom=maxzoom, name=sceneid, tilejson="2.1.0", tiles=[tile_url], ) return ("OK", "application/json", json.dumps(meta))
def _get_info(src_path: str) -> Any: with rasterio.open(src_path) as src_dst: bounds = transform_bounds( src_dst.crs, constants.WGS84_CRS, *src_dst.bounds, densify_pts=21 ) minzoom, maxzoom = get_zooms(src_dst) center = ((bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2, minzoom) def _get_name(ix): name = src_dst.descriptions[ix - 1] if not name: name = f"band{ix}" return name band_descriptions = [_get_name(ix) for ix in src_dst.indexes] data_type = src_dst.meta["dtype"] try: cmap = src_dst.colormap(1) except ValueError: cmap = None return bounds, center, minzoom, maxzoom, band_descriptions, data_type, cmap
async def test_cog_read(infile, create_cog_reader): async with create_cog_reader(infile) as cog: with rasterio.open(infile) as ds: _, zoom = get_zooms(ds) centroid = Polygon.from_bounds( *transform_bounds(cog.epsg, "EPSG:4326", *cog.bounds)).centroid tile = mercantile.tile(centroid.x, centroid.y, zoom) tile_native_bounds = transform_bounds("EPSG:4326", cog.epsg, *mercantile.bounds(tile)) arr = await cog.read(tile_native_bounds, (256, 256)) with cogeo_reader(infile) as ds: rio_tile_arr, rio_tile_mask = ds.tile(tile.x, tile.y, tile.z, tilesize=256, resampling_method="bilinear") if cog.is_masked: tile_arr = np.ma.getdata(arr) tile_mask = np.ma.getmask(arr) # Make sure image data is the same assert pytest.approx(tile_arr - rio_tile_arr, 1) == np.zeros(tile_arr.shape) # Make sure mask data is the same rio_mask_counts = np.unique(rio_tile_mask, return_counts=True) tile_mask_counts = np.unique(tile_mask, return_counts=True) assert len(rio_mask_counts[0]) == len(tile_mask_counts[0]) assert (rio_mask_counts[1][0] * cog.profile["count"] == tile_mask_counts[1][0]) else: assert pytest.approx(arr - rio_tile_arr, 1) == np.zeros(arr.shape)
def tilejson_handler( event: Dict, scene: str, tile_format: str = "png", tile_scale: int = 1, **kwargs: Any, ) -> Tuple[str, str, str]: """Handle /tilejson.json requests.""" # HACK token = event["multiValueQueryStringParameters"].get("access_token") if token: kwargs.update(dict(access_token=token[0])) qs = urllib.parse.urlencode(list(kwargs.items())) tile_url = f"{APP.host}/s2/tiles/{scene}/{{z}}/{{x}}/{{y}}@{tile_scale}x.{tile_format}?{qs}" scene_params = sentinel2._sentinel_parse_scene_id(scene) sentinel_address = "s3://{}/{}/B{}.jp2".format(sentinel2.SENTINEL_BUCKET, scene_params["key"], "04") with rasterio.open(sentinel_address) as src_dst: bounds = warp.transform_bounds(*[src_dst.crs, "epsg:4326"] + list(src_dst.bounds), densify_pts=21) minzoom, maxzoom = get_zooms(src_dst) center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2, minzoom] meta = dict( bounds=bounds, center=center, minzoom=minzoom, maxzoom=maxzoom, name=scene, tilejson="2.1.0", tiles=[tile_url], ) return ("OK", "application/json", json.dumps(meta))
def get_zoom_safe(src_dst): minzoom, maxzoom = get_zooms(src_dst) if maxzoom < minzoom: maxzoom = minzoom return minzoom, maxzoom
def info(address: str) -> Dict: """ Return simple metadata about the file. Attributes ---------- address : str or PathLike object A dataset path or URL. Will be opened in "r" mode. Returns ------- out : dict. """ with rasterio.open(address) as src_dst: minzoom, maxzoom = get_zooms(src_dst) bounds = transform_bounds(src_dst.crs, constants.WGS84_CRS, *src_dst.bounds, densify_pts=21) center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2, minzoom] def _get_descr(ix): """Return band description.""" name = src_dst.descriptions[ix - 1] if not name: name = "band{}".format(ix) return name band_descriptions = [(ix, _get_descr(ix)) for ix in src_dst.indexes] tags = [(ix, src_dst.tags(ix)) for ix in src_dst.indexes] other_meta = dict() if src_dst.scales[0] and src_dst.offsets[0]: other_meta.update(dict(scale=src_dst.scales[0])) other_meta.update(dict(offset=src_dst.offsets[0])) if has_alpha_band(src_dst): nodata_type = "Alpha" elif has_mask_band(src_dst): nodata_type = "Mask" elif src_dst.nodata is not None: nodata_type = "Nodata" else: nodata_type = "None" try: cmap = src_dst.colormap(1) other_meta.update(dict(colormap=cmap)) except ValueError: pass return dict( address=address, bounds=bounds, center=center, minzoom=minzoom, maxzoom=maxzoom, band_metadata=tags, band_descriptions=band_descriptions, dtype=src_dst.meta["dtype"], colorinterp=[ src_dst.colorinterp[ix - 1].name for ix in src_dst.indexes ], nodata_type=nodata_type, **other_meta, )
def _raster_get_stats( src_dst, indexes=None, nodata=None, overview_level=None, max_size=1024, percentiles=(2, 98), dst_crs=CRS({"init": "EPSG:5514"}), histogram_bins=10, histogram_range=None, resampling_method="bilinear", warp_vrt_option={}, expr=None, ): """ Retrieve dataset statistics. Attributes ---------- src_dst : rasterio.io.DatasetReader rasterio.io.DatasetReader object indexes : tuple, list, int, optional Dataset band indexes. nodata, int, optional Custom nodata value if not preset in dataset. overview_level : int, optional Overview (decimation) level to fetch. max_size: int, optional Maximum size of dataset to retrieve (will be used to calculate the overview level to fetch). percentiles : tulple, optional Percentile or sequence of percentiles to compute, which must be between 0 and 100 inclusive (default: (2, 98)). dst_crs: CRS or dict Target coordinate reference system (default: EPSG:5514). histogram_bins: int, optional Defines the number of equal-width histogram bins (default: 10). histogram_range: tuple or list, optional The lower and upper range of the bins. If not provided, range is simply the min and max of the array. resampling_method : str, optional (default: "bilinear") Resampling algorithm. warp_vrt_option: dict, optional (default: {}) These will be passed to the rasterio.warp.WarpedVRT class. expr : str, optional (default: None) Expression to apply to raster before computing statistics Returns ------- out : dict bounds, mercator zoom range, band descriptions and band statistics: (percentiles), min, max, stdev, histogram e.g. { 'bounds': { 'value': (145.72265625, 14.853515625, 145.810546875, 14.94140625), 'crs': '+init=EPSG:5514' }, 'minzoom': 8, 'maxzoom': 12, 'band_descriptions': [(1, 'red'), (2, 'green'), (3, 'blue'), (4, 'nir')] 'statistics': { 1: { 'pc': [38, 147], 'min': 20, 'max': 180, 'std': 28.123562304138662, 'histogram': [ [1625, 219241, 28344, 15808, 12325, 10687, 8535, 7348, 4656, 1208], [20.0, 36.0, 52.0, 68.0, 84.0, 100.0, 116.0, 132.0, 148.0, 164.0, 180.0] ] } ... 3: {...} 4: {...} } } """ if isinstance(indexes, int): indexes = [indexes] elif isinstance(indexes, tuple): indexes = list(indexes) if expr is not None: bands_names = tuple( sorted(set(re.findall(r"(?P<bands>b[0-9]+)", expr)))) if indexes is None: indexes = tuple([int(b.replace("b", "")) for b in bands_names]) levels = src_dst.overviews(1) width = src_dst.width height = src_dst.height indexes = indexes if indexes else src_dst.indexes nodata = nodata if nodata is not None else src_dst.nodata bounds = transform_bounds(src_dst.crs, dst_crs, *src_dst.bounds, densify_pts=21) minzoom, maxzoom = get_zooms(src_dst) def _get_descr(ix): """Return band description.""" name = src_dst.descriptions[ix - 1] if not name: name = "band{}".format(ix) return name band_descriptions = [(ix, _get_descr(ix)) for ix in indexes] if len(levels): if overview_level: decim = levels[overview_level] else: # determine which zoom level to read for ii, decim in enumerate(levels): if (max(_div_round_up(width, decim), _div_round_up(height, decim)) < max_size): break else: decim = 1 warnings.warn("Dataset has no overviews, reading the full dataset", NoOverviewWarning) out_shape = ( len(indexes), _div_round_up(height, decim), _div_round_up(width, decim), ) vrt_params = dict(add_alpha=True) if has_alpha_band(src_dst): vrt_params.update(dict(add_alpha=False)) if nodata is not None: vrt_params.update( dict(nodata=nodata, add_alpha=False, src_nodata=nodata)) vrt_params.update(warp_vrt_option) with WarpedVRT(src_dst, **vrt_params) as vrt: arr = vrt.read( out_shape=out_shape, indexes=indexes, resampling=Resampling[resampling_method], masked=True, ) if expr is not None: rgb = expr.split(",") mask = np.array([arr.mask[0]]) arr = dict(zip(bands_names, arr)) arr = np.ma.array([ np.nan_to_num(ne.evaluate(bloc.strip(), local_dict=arr)) for bloc in rgb ], mask=mask) # Mask infinite values (we're just calculating stats) arr.mask[arr > 1e+300] = True arr.mask[arr < -1e+300] = True params = {} if histogram_bins: params.update(dict(bins=histogram_bins)) if histogram_range: params.update(dict(range=histogram_range)) stats = { indexes[b]: _stats(arr[b], percentiles=percentiles, **params) for b in range(arr.shape[0]) if vrt.colorinterp[b] != ColorInterp.alpha } return { "bounds": { "value": bounds, "crs": dst_crs.to_string() if isinstance(dst_crs, CRS) else dst_crs, }, "minzoom": minzoom, "maxzoom": maxzoom, "band_descriptions": band_descriptions, "statistics": stats, }
def get(self, request, pk=None, project_pk=None, tile_type="", z="", x="", y="", scale=1): """ Get a tile image """ task = self.get_and_check_task(request, pk) z = int(z) x = int(x) y = int(y) if x == 0 and y == 0 and z == 0: raise exceptions.NotFound() scale = int(scale) ext = "png" driver = "jpeg" if ext == "jpg" else ext indexes = None nodata = None formula = self.request.query_params.get('formula') bands = self.request.query_params.get('bands') rescale = self.request.query_params.get('rescale') color_map = self.request.query_params.get('color_map') hillshade = self.request.query_params.get('hillshade') if formula == '': formula = None if bands == '': bands = None if rescale == '': rescale = None if color_map == '': color_map = None if hillshade == '' or hillshade == '0': hillshade = None try: expr, _ = lookup_formula(formula, bands) except ValueError as e: raise exceptions.ValidationError(str(e)) if tile_type in ['dsm', 'dtm'] and rescale is None: rescale = "0,1000" if tile_type in ['dsm', 'dtm'] and color_map is None: color_map = "gray" if tile_type == 'orthophoto' and formula is not None: if color_map is None: color_map = "gray" if rescale is None: rescale = "-1,1" if nodata is not None: nodata = np.nan if nodata == "nan" else float(nodata) tilesize = scale * 256 url = get_raster_path(task, tile_type) if not os.path.isfile(url): raise exceptions.NotFound() try: if expr is not None: tile, mask = expression(url, x, y, z, expr=expr, tilesize=tilesize, nodata=nodata) else: tile, mask = main.tile(url, x, y, z, indexes=indexes, tilesize=tilesize, nodata=nodata) except TileOutsideBounds: raise exceptions.NotFound("Outside of bounds") # Use alpha channel for transparency, don't use the mask if one is provided (redundant) if tile.shape[0] == 4: mask = None if color_map: try: color_map = get_colormap(color_map, format="gdal") except FileNotFoundError: raise exceptions.ValidationError("Not a valid color_map value") intensity = None if hillshade is not None: try: hillshade = float(hillshade) if hillshade <= 0: hillshade = 1.0 print(hillshade) except ValueError: raise exceptions.ValidationError("Invalid hillshade value") if tile.shape[0] != 1: raise exceptions.ValidationError( "Cannot compute hillshade of non-elevation raster (multiple bands found)" ) with rasterio.open(url) as src: minzoom, maxzoom = get_zooms(src) z_value = min(maxzoom, max(z, minzoom)) delta_scale = (maxzoom + 1 - z_value) * 4 dx = src.meta["transform"][0] * delta_scale dy = -src.meta["transform"][4] * delta_scale ls = LightSource(azdeg=315, altdeg=45) # Hillshading is not a local tile operation and # requires neighbor tiles to be rendered seamlessly elevation = get_elevation_tiles(tile[0], url, x, y, z, tilesize, nodata) intensity = ls.hillshade(elevation, dx=dx, dy=dy, vert_exag=hillshade) intensity = intensity[tilesize:tilesize * 2, tilesize:tilesize * 2] rgb, rmask = rescale_tile(tile, mask, rescale=rescale) rgb = apply_colormap(rgb, color_map) if intensity is not None: # Quick check if rgb.shape[0] != 3: raise exceptions.ValidationError( "Cannot process tile: intensity image provided, but no RGB data was computed." ) intensity = intensity * 255.0 rgb = hsv_blend(rgb, intensity) options = img_profiles.get(driver, {}) return HttpResponse(array_to_image(rgb, rmask, img_format=driver, **options), content_type="image/{}".format(ext))