def get_zooms(self, tilesize: int = 256) -> Tuple[int, int]: """Calculate raster min/max zoom level for input TMS.""" if self.dataset.crs != self.tms.rasterio_crs: dst_affine, w, h = calculate_default_transform( self.dataset.crs, self.tms.rasterio_crs, self.dataset.width, self.dataset.height, *self.dataset.bounds, ) else: dst_affine = list(self.dataset.transform) w = self.dataset.width h = self.dataset.height # The maxzoom is defined by finding the minimum difference between # the raster resolution and the zoom level resolution resolution = max(abs(dst_affine[0]), abs(dst_affine[4])) maxzoom = self.tms.zoom_for_res(resolution) # The minzoom is defined by the resolution of the maximum theoretical overview level overview_level = get_maximum_overview_level(w, h, minsize=tilesize) ovr_resolution = resolution * (2**overview_level) minzoom = self.tms.zoom_for_res(ovr_resolution) return (minzoom, maxzoom)
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_zooms( src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT], ensure_global_max_zoom: bool = False, tilesize: int = 256, ) -> Tuple[int, int]: """ Calculate raster min/max mercator zoom level. Parameters ---------- src_dst: rasterio.io.DatasetReader Rasterio io.DatasetReader object ensure_global_max_zoom: bool, optional Apply latitude correction factor to ensure max_zoom equality for global datasets covering different latitudes (default: False). tilesize: int, optional Mercator tile size (default: 256). Returns ------- min_zoom, max_zoom: Tuple Min/Max Mercator zoom levels. """ 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] lat = center[1] if ensure_global_max_zoom else 0 dst_affine, w, h = calculate_default_transform( src_dst.crs, constants.WEB_MERCATOR_CRS, src_dst.width, src_dst.height, *src_dst.bounds, ) mercator_resolution = max(abs(dst_affine[0]), abs(dst_affine[4])) # Correction factor for web-mercator projection latitude scale change latitude_correction_factor = math.cos(math.radians(lat)) adjusted_resolution = mercator_resolution * latitude_correction_factor max_zoom = zoom_for_pixelsize(adjusted_resolution, tilesize=tilesize) overview_level = get_maximum_overview_level(w, h, minsize=tilesize) ovr_resolution = adjusted_resolution * (2**overview_level) min_zoom = zoom_for_pixelsize(ovr_resolution, tilesize=tilesize) return (min_zoom, max_zoom)
def get_zooms(self, tilesize: int = 256) -> Tuple[int, int]: """Calculate raster min/max zoom level.""" dst_affine, w, h = calculate_default_transform( self.dataset.crs, self.tms.crs, self.dataset.width, self.dataset.height, *self.dataset.bounds, ) resolution = max(abs(dst_affine[0]), abs(dst_affine[4])) maxzoom = self.tms.zoom_for_res(resolution) overview_level = get_maximum_overview_level(w, h, minsize=tilesize) ovr_resolution = resolution * (2 ** overview_level) minzoom = self.tms.zoom_for_res(ovr_resolution) return minzoom, maxzoom
def get_zooms(self, tilesize: int = 256) -> Tuple[int, int]: """Calculate raster min/max zoom level.""" dst_affine, w, h = calculate_default_transform( CRS.from_epsg(self.epsg), self.tms.crs, self.profile["width"], self.profile["height"], *self.native_bounds, ) resolution = max(abs(dst_affine[0]), abs(dst_affine[4])) maxzoom = self.tms.zoom_for_res(resolution) overview_level = get_maximum_overview_level(w, h, minsize=tilesize) ovr_resolution = resolution * (2 ** overview_level) minzoom = self.tms.zoom_for_res(ovr_resolution) return minzoom, maxzoom
def get_zooms(src_dst, lat=0.0, tilesize=256) -> Tuple[int, int]: """ Calculate raster max zoom level. Parameters ---------- src: rasterio.io.DatasetReader Rasterio io.DatasetReader object lat: float, optional Center latitude of the dataset. This is only needed in case you want to apply latitude correction factor to ensure consitent maximum zoom level (default: 0.0). tilesize: int, optional Mercator tile size (default: 256). Returns ------- max_zoom: int Max zoom level. """ dst_affine, w, h = calculate_default_transform(src_dst.crs, "epsg:3857", src_dst.width, src_dst.height, *src_dst.bounds) native_resolution = max(abs(dst_affine[0]), abs(dst_affine[4])) # Correction factor for web-mercator projection latitude distortion latitude_correction_factor = math.cos(math.radians(lat)) corrected_resolution = native_resolution * latitude_correction_factor max_zoom = zoom_for_pixelsize(corrected_resolution, tilesize=tilesize) overview_level = get_maximum_overview_level(w, h, minsize=tilesize) ovr_resolution = corrected_resolution * (2**overview_level) min_zoom = zoom_for_pixelsize(ovr_resolution, tilesize=tilesize) return (min_zoom, max_zoom)
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 cog_translate( # noqa: C901 source: Union[str, pathlib.PurePath, DatasetReader, DatasetWriter, WarpedVRT], dst_path: Union[str, pathlib.PurePath], dst_kwargs: Dict, indexes: Optional[Sequence[int]] = None, nodata: Optional[Union[str, int, float]] = None, dtype: Optional[str] = None, add_mask: bool = False, overview_level: Optional[int] = None, overview_resampling: str = "nearest", web_optimized: bool = False, tms: morecantile.TileMatrixSet = morecantile.tms.get("WebMercatorQuad"), zoom_level_strategy: str = "auto", aligned_levels: Optional[int] = None, resampling: str = "nearest", in_memory: Optional[bool] = None, config: Optional[Dict] = None, allow_intermediate_compression: bool = False, forward_band_tags: bool = False, quiet: bool = False, temporary_compression: str = "DEFLATE", ): """ Create Cloud Optimized Geotiff. Parameters ---------- source : str, PathLike object or rasterio.io.DatasetReader A dataset path, URL or rasterio.io.DatasetReader object. Will be opened in "r" mode. dst_path : str or PathLike object An output dataset path or or PathLike object. Will be opened in "w" mode. dst_kwargs: dict Output dataset creation options. indexes : tuple or int, optional Raster band indexes to copy. nodata, int, optional Overwrite nodata masking values for input dataset. dtype: str, optional Overwrite output data type. Default will be the input data type. add_mask, bool, optional Force output dataset creation with a mask. overview_level : int, optional (default: None) COGEO overview (decimation) level. By default, inferred from data size. overview_resampling : str, optional (default: "nearest") Resampling algorithm for overviews web_optimized: bool, optional (default: False) Create web-optimized cogeo. tms: morecantile.TileMatrixSet, optional (default: "WebMercatorQuad") TileMatrixSet to use for reprojection, resolution and alignment. zoom_level_strategy: str, optional (default: auto) Strategy to determine zoom level (same as in GDAL 3.2). LOWER will select the zoom level immediately below the theoretical computed non-integral zoom level, leading to subsampling. On the contrary, UPPER will select the immediately above zoom level, leading to oversampling. Defaults to AUTO which selects the closest zoom level. ref: https://gdal.org/drivers/raster/cog.html#raster-cog aligned_levels: int, optional. Number of overview levels for which GeoTIFF tile and tiles defined in the tiling scheme match. Default is to use the maximum overview levels. resampling : str, optional (default: "nearest") Resampling algorithm. in_memory: bool, optional Force processing raster in memory (default: process in memory if small) config : dict Rasterio Env options. allow_intermediate_compression: bool, optional (default: False) Allow intermediate file compression to reduce memory/disk footprint. Note: This could reduce the speed of the process. Ref: https://github.com/cogeotiff/rio-cogeo/issues/103 forward_band_tags: bool, optional Forward band tags to output bands. Ref: https://github.com/cogeotiff/rio-cogeo/issues/19 quiet: bool, optional (default: False) Mask processing steps. temporary_compression: str, optional Compression used for the intermediate file, default is deflate. """ if isinstance(indexes, int): indexes = (indexes, ) config = config or {} with rasterio.Env(**config): with ExitStack() as ctx: if isinstance(source, (DatasetReader, DatasetWriter, WarpedVRT)): src_dst = source else: src_dst = ctx.enter_context(rasterio.open(source)) meta = src_dst.meta indexes = indexes if indexes else src_dst.indexes nodata = nodata if nodata is not None else src_dst.nodata dtype = dtype if dtype else src_dst.dtypes[0] alpha = utils.has_alpha_band(src_dst) mask = utils.has_mask_band(src_dst) if not add_mask and ( (nodata is not None or alpha) and dst_kwargs.get("compress") in ["JPEG", "jpeg"]): warnings.warn( "Using lossy compression with Nodata or Alpha band " "can results in unwanted artefacts.", LossyCompression, ) tilesize = min(int(dst_kwargs["blockxsize"]), int(dst_kwargs["blockysize"])) if src_dst.width < tilesize or src_dst.height < tilesize: tilesize = 2**int( math.log(min(src_dst.width, src_dst.height), 2)) if tilesize < 64: warnings.warn( "Raster has dimension < 64px. Output COG cannot be tiled" " and overviews cannot be added.", IncompatibleBlockRasterSize, ) dst_kwargs.pop("blockxsize", None) dst_kwargs.pop("blockysize", None) dst_kwargs.pop("tiled") overview_level = 0 else: warnings.warn( "Block Size are bigger than raster sizes. " "Setting blocksize to {}".format(tilesize), IncompatibleBlockRasterSize, ) dst_kwargs["blockxsize"] = tilesize dst_kwargs["blockysize"] = tilesize vrt_params = { "add_alpha": True, "dtype": dtype, "width": src_dst.width, "height": src_dst.height, } if nodata is not None: vrt_params.update( dict(nodata=nodata, add_alpha=False, src_nodata=nodata)) if alpha: vrt_params.update(dict(add_alpha=False)) if web_optimized: params = utils.get_web_optimized_params( src_dst, tilesize=tilesize, warp_resampling=resampling, zoom_level_strategy=zoom_level_strategy, aligned_levels=aligned_levels, tms=tms, ) vrt_params.update(**params) with WarpedVRT(src_dst, **vrt_params) as vrt_dst: meta = vrt_dst.meta meta["count"] = len(indexes) if add_mask: meta.pop("nodata", None) meta.pop("alpha", None) if (dst_kwargs.get("photometric", "").upper() == "YCBCR" and meta["count"] == 1): warnings.warn( "PHOTOMETRIC=YCBCR not supported on a 1-band raster" " and has been set to 'MINISBLACK'") dst_kwargs["photometric"] = "MINISBLACK" meta.update(**dst_kwargs) meta.pop("compress", None) meta.pop("photometric", None) if allow_intermediate_compression: meta["compress"] = temporary_compression if in_memory is None: in_memory = vrt_dst.width * vrt_dst.height < IN_MEMORY_THRESHOLD if in_memory: tmpfile = ctx.enter_context(MemoryFile()) tmp_dst = ctx.enter_context(tmpfile.open(**meta)) else: tmpfile = ctx.enter_context(TemporaryRasterFile(dst_path)) tmp_dst = ctx.enter_context( rasterio.open(tmpfile.name, "w", **meta)) # Transfer color interpolation if len(indexes) == 1 and (vrt_dst.colorinterp[indexes[0] - 1] is not ColorInterp.palette): tmp_dst.colorinterp = [ColorInterp.gray] else: tmp_dst.colorinterp = [ vrt_dst.colorinterp[b - 1] for b in indexes ] if tmp_dst.colorinterp[0] is ColorInterp.palette: try: tmp_dst.write_colormap(1, vrt_dst.colormap(1)) except ValueError: warnings.warn( "Dataset has `Palette` color interpretation" " but is missing colormap information") wind = list(tmp_dst.block_windows(1)) if not quiet: click.echo("Reading input: {}".format(source), err=True) fout = os.devnull if quiet else sys.stderr with click.progressbar( wind, file=fout, show_percent=True) as windows: # type: ignore for _, w in windows: matrix = vrt_dst.read(window=w, indexes=indexes) tmp_dst.write(matrix, window=w) if add_mask or mask: # Cast mask to uint8 to fix rasterio 1.1.2 error (ref #115) mask_value = vrt_dst.dataset_mask( window=w).astype("uint8") tmp_dst.write_mask(mask_value, window=w) if overview_level is None: overview_level = get_maximum_overview_level( vrt_dst.width, vrt_dst.height, minsize=tilesize) if not quiet and overview_level: click.echo("Adding overviews...", err=True) overviews = [2**j for j in range(1, overview_level + 1)] tmp_dst.build_overviews(overviews, ResamplingEnums[overview_resampling]) if not quiet: click.echo("Updating dataset tags...", err=True) for i, b in enumerate(indexes): tmp_dst.set_band_description(i + 1, src_dst.descriptions[b - 1]) if forward_band_tags: tmp_dst.update_tags(i + 1, **src_dst.tags(b)) tags = src_dst.tags() tags.update( dict( OVR_RESAMPLING_ALG=ResamplingEnums[overview_resampling] .name.upper())) tmp_dst.update_tags(**tags) tmp_dst._set_all_scales( [vrt_dst.scales[b - 1] for b in indexes]) tmp_dst._set_all_offsets( [vrt_dst.offsets[b - 1] for b in indexes]) if not quiet: click.echo("Writing output to: {}".format(dst_path), err=True) copy(tmp_dst, dst_path, copy_src_overviews=True, **dst_kwargs)
def create_overview_cogs( mosaic_path: str, output_profile: Dict, prefix: str = "mosaic_ovr", max_overview_level: int = 6, method: str = "first", config: Dict = None, threads=1, in_memory: bool = True, ) -> None: """ Create Low resolution mosaic image from a mosaicJSON. The output will be a web optimized COG with bounds matching the mosaicJSON bounds and with its resolution matching the mosaic MinZoom - 1. Attributes ---------- mosaic_path : str, required Mosaic definition path. output_profile : dict, required prefix : str max_overview_level : int method: str, optional pixel_selection method name (default is 'first'). config : dict Rasterio Env options. threads: int, optional maximum number of threads to use (default is 1). in_memory: bool, optional Force COG creation in memory (default is True). """ pixel_method = PIXSEL_METHODS[method] with MosaicBackend(mosaic_path) as mosaic: base_zoom = mosaic.metadata["minzoom"] - 1 mosaic_quadkey_zoom = mosaic.quadkey_zoom bounds = mosaic.metadata["bounds"] mosaic_quadkeys = set(mosaic._quadkeys) # Select a random quakey/asset and get dataset info tile = mercantile.quadkey_to_tile(random.sample(mosaic_quadkeys, 1)[0]) assets = mosaic.assets_for_tile(*tile) info = _get_info(assets[0]) extrema = tile_extrema(bounds, base_zoom) tilesize = 256 resolution = _meters_per_pixel(base_zoom, 0, tilesize=tilesize) # Create multiples files if coverage is too big extremas = _split_extrema(extrema, max_ovr=max_overview_level) for ix, extrema in enumerate(extremas): click.echo(f"Part {1 + ix}/{len(extremas)}", err=True) output_path = f"{prefix}_{ix}.tif" blocks = list(_get_blocks(extrema, tilesize)) random.shuffle(blocks) width = (extrema["x"]["max"] - extrema["x"]["min"]) * tilesize height = (extrema["y"]["max"] - extrema["y"]["min"]) * tilesize w, n = mercantile.xy(*mercantile.ul( extrema["x"]["min"], extrema["y"]["min"], base_zoom)) params = dict( driver="GTiff", dtype=info["dtype"], count=len(info["band_descriptions"]), width=width, height=height, crs="epsg:3857", transform=Affine(resolution, 0, w, 0, -resolution, n), nodata=info["nodata_value"], ) params.update(**output_profile) config = config or {} with rasterio.Env(**config): with ExitStack() as ctx: if in_memory: tmpfile = ctx.enter_context(MemoryFile()) tmp_dst = ctx.enter_context(tmpfile.open(**params)) else: tmpfile = ctx.enter_context( TemporaryRasterFile(output_path)) tmp_dst = ctx.enter_context( rasterio.open(tmpfile.name, "w", **params)) def _get_tile(wind): idx, window = wind x = extrema["x"]["min"] + idx[1] y = extrema["y"]["min"] + idx[0] t = mercantile.Tile(x, y, base_zoom) kds = set(find_quadkeys(t, mosaic_quadkey_zoom)) if not mosaic_quadkeys.intersection(kds): return window, None, None try: (tile, mask), _ = mosaic.tile( t.x, t.y, t.z, tilesize=tilesize, pixel_selection=pixel_method(), ) except NoAssetFoundError: return window, None, None return window, tile, mask with futures.ThreadPoolExecutor( max_workers=threads) as executor: future_work = [ executor.submit(_get_tile, item) for item in blocks ] with click.progressbar( futures.as_completed(future_work), length=len(future_work), show_percent=True, label="Loading tiles", ) as future: for res in future: pass for f in _filter_futures(future_work): window, tile, mask = f if tile is None: continue tmp_dst.write(tile, window=window) if info["nodata_type"] == "Mask": tmp_dst.write_mask(mask.astype("uint8"), window=window) min_tile_size = tilesize = min( int(output_profile["blockxsize"]), int(output_profile["blockysize"]), ) overview_level = get_maximum_overview_level( tmp_dst.width, tmp_dst.height, minsize=min_tile_size) overviews = [2**j for j in range(1, overview_level + 1)] tmp_dst.build_overviews(overviews) copy(tmp_dst, output_path, copy_src_overviews=True, **params)
def test_max_overview(width, height, minsize, expected): overview_level = get_maximum_overview_level(width, height, minsize) assert overview_level == expected
def cog_translate( # noqa: C901 source: Union[str, pathlib.PurePath, DatasetReader, DatasetWriter, WarpedVRT], dst_path: Union[str, pathlib.PurePath], dst_kwargs: Dict, indexes: Optional[Sequence[int]] = None, nodata: Optional[Union[str, int, float]] = None, dtype: Optional[str] = None, add_mask: bool = False, overview_level: Optional[int] = None, overview_resampling: str = "nearest", web_optimized: bool = False, tms: Optional[morecantile.TileMatrixSet] = None, zoom_level_strategy: str = "auto", zoom_level: Optional[int] = None, aligned_levels: Optional[int] = None, resampling: str = "nearest", in_memory: Optional[bool] = None, config: Optional[Dict] = None, allow_intermediate_compression: bool = False, forward_band_tags: bool = False, quiet: bool = False, temporary_compression: str = "DEFLATE", colormap: Optional[Dict] = None, additional_cog_metadata: Optional[Dict] = None, use_cog_driver: bool = False, ): """ Create Cloud Optimized Geotiff. Parameters ---------- source : str, PathLike object or rasterio.io.DatasetReader A dataset path, URL or rasterio.io.DatasetReader object. Will be opened in "r" mode. dst_path : str or PathLike object An output dataset path or or PathLike object. Will be opened in "w" mode. dst_kwargs: dict Output dataset creation options. indexes : tuple or int, optional Raster band indexes to copy. nodata, int, optional Overwrite nodata masking values for input dataset. dtype: str, optional Overwrite output data type. Default will be the input data type. add_mask, bool, optional Force output dataset creation with a mask. overview_level : int, optional (default: None) COGEO overview (decimation) level. By default, inferred from data size. overview_resampling : str, optional (default: "nearest") Resampling algorithm for overviews web_optimized: bool, optional (default: False) Create web-optimized cogeo. tms: morecantile.TileMatrixSet, optional (default: "WebMercatorQuad") TileMatrixSet to use for reprojection, resolution and alignment. zoom_level_strategy: str, optional (default: auto) Strategy to determine zoom level (same as in GDAL 3.2). LOWER will select the zoom level immediately below the theoretical computed non-integral zoom level, leading to subsampling. On the contrary, UPPER will select the immediately above zoom level, leading to oversampling. Defaults to AUTO which selects the closest zoom level. ref: https://gdal.org/drivers/raster/cog.html#raster-cog zoom_level: int, optional. Zoom level number (starting at 0 for coarsest zoom level). If this option is specified, `--zoom-level-strategy` is ignored. aligned_levels: int, optional. Number of overview levels for which GeoTIFF tile and tiles defined in the tiling scheme match. Default is to use the maximum overview levels. Note: GDAL use number of resolution levels instead of overview levels. resampling : str, optional (default: "nearest") Resampling algorithm. in_memory: bool, optional Force processing raster in memory (default: process in memory if small) config : dict Rasterio Env options. allow_intermediate_compression: bool, optional (default: False) Allow intermediate file compression to reduce memory/disk footprint. Note: This could reduce the speed of the process. Ref: https://github.com/cogeotiff/rio-cogeo/issues/103 forward_band_tags: bool, optional Forward band tags to output bands. Ref: https://github.com/cogeotiff/rio-cogeo/issues/19 quiet: bool, optional (default: False) Mask processing steps. temporary_compression: str, optional Compression used for the intermediate file, default is deflate. colormap: dict, optional Overwrite or add a colormap to the output COG. additional_cog_metadata: dict, optional Additional dataset metadata to add to the COG. use_cog_driver: bool, optional (default: False) Use GDAL COG driver if set to True. COG driver is available starting with GDAL 3.1. """ tms = tms or morecantile.tms.get("WebMercatorQuad") dst_kwargs = dst_kwargs.copy() if isinstance(indexes, int): indexes = (indexes, ) config = config or {} with rasterio.Env(**config): with ExitStack() as ctx: if isinstance(source, (DatasetReader, DatasetWriter, WarpedVRT)): src_dst = source else: src_dst = ctx.enter_context(rasterio.open(source)) meta = src_dst.meta indexes = indexes if indexes else src_dst.indexes nodata = nodata if nodata is not None else src_dst.nodata dtype = dtype if dtype else src_dst.dtypes[0] alpha = utils.has_alpha_band(src_dst) mask = utils.has_mask_band(src_dst) if colormap and len(indexes) > 1: raise IncompatibleOptions( "Cannot add a colormap for multiple bands data.") if not add_mask and ( (nodata is not None or alpha) and dst_kwargs.get("compress", "").lower() == "jpeg"): warnings.warn( "Nodata/Alpha band will be translated to an internal mask band.", ) add_mask = True indexes = (utils.non_alpha_indexes(src_dst) if len(indexes) not in [1, 3] else indexes) tilesize = min(int(dst_kwargs["blockxsize"]), int(dst_kwargs["blockysize"])) if src_dst.width < tilesize or src_dst.height < tilesize: tilesize = 2**int( math.log(min(src_dst.width, src_dst.height), 2)) if tilesize < 64: warnings.warn( "Raster has dimension < 64px. Output COG cannot be tiled" " and overviews cannot be added.", IncompatibleBlockRasterSize, ) dst_kwargs.pop("blockxsize", None) dst_kwargs.pop("blockysize", None) dst_kwargs.pop("tiled") overview_level = 0 else: warnings.warn( "Block Size are bigger than raster sizes. " "Setting blocksize to {}".format(tilesize), IncompatibleBlockRasterSize, ) dst_kwargs["blockxsize"] = tilesize dst_kwargs["blockysize"] = tilesize vrt_params = { "add_alpha": True, "dtype": dtype, "width": src_dst.width, "height": src_dst.height, "resampling": ResamplingEnums[resampling], } if nodata is not None: vrt_params.update( dict(nodata=nodata, add_alpha=False, src_nodata=nodata)) if alpha: vrt_params.update(dict(add_alpha=False)) if web_optimized and not use_cog_driver: params = utils.get_web_optimized_params( src_dst, zoom_level_strategy=zoom_level_strategy, zoom_level=zoom_level, aligned_levels=aligned_levels, tms=tms, ) vrt_params.update(**params) with WarpedVRT(src_dst, **vrt_params) as vrt_dst: meta = vrt_dst.meta meta["count"] = len(indexes) if add_mask: meta.pop("nodata", None) meta.pop("alpha", None) if (dst_kwargs.get("photometric", "").upper() == "YCBCR" and meta["count"] == 1): warnings.warn( "PHOTOMETRIC=YCBCR not supported on a 1-band raster" " and has been set to 'MINISBLACK'") dst_kwargs["photometric"] = "MINISBLACK" meta.update(**dst_kwargs) meta.pop("compress", None) meta.pop("photometric", None) if allow_intermediate_compression: meta["compress"] = temporary_compression if in_memory is None: in_memory = vrt_dst.width * vrt_dst.height < IN_MEMORY_THRESHOLD if in_memory: tmpfile = ctx.enter_context(MemoryFile()) tmp_dst = ctx.enter_context(tmpfile.open(**meta)) else: tmpfile = ctx.enter_context(TemporaryRasterFile(dst_path)) tmp_dst = ctx.enter_context( rasterio.open(tmpfile.name, "w", **meta)) # Transfer color interpolation if len(indexes) == 1 and (vrt_dst.colorinterp[indexes[0] - 1] is not ColorInterp.palette): tmp_dst.colorinterp = [ColorInterp.gray] else: tmp_dst.colorinterp = [ vrt_dst.colorinterp[b - 1] for b in indexes ] if colormap: if tmp_dst.colorinterp[0] is not ColorInterp.palette: tmp_dst.colorinterp = [ColorInterp.palette] warnings.warn( "Dataset color interpretation was set to `Palette`" ) tmp_dst.write_colormap(1, colormap) elif tmp_dst.colorinterp[0] is ColorInterp.palette: try: tmp_dst.write_colormap(1, vrt_dst.colormap(1)) except ValueError: warnings.warn( "Dataset has `Palette` color interpretation" " but is missing colormap information") wind = list(tmp_dst.block_windows(1)) if not quiet: click.echo("Reading input: {}".format(source), err=True) fout = ctx.enter_context(open(os.devnull, "w")) if quiet else sys.stderr with click.progressbar( wind, file=fout, show_percent=True) as windows: # type: ignore for _, w in windows: matrix = vrt_dst.read(window=w, indexes=indexes) tmp_dst.write(matrix, window=w) if add_mask or mask: # Cast mask to uint8 to fix rasterio 1.1.2 error (ref #115) mask_value = vrt_dst.dataset_mask( window=w).astype("uint8") tmp_dst.write_mask(mask_value, window=w) if overview_level is None: overview_level = get_maximum_overview_level( vrt_dst.width, vrt_dst.height, minsize=tilesize) if not quiet and overview_level: click.echo("Adding overviews...", err=True) overviews = [2**j for j in range(1, overview_level + 1)] tmp_dst.build_overviews(overviews, ResamplingEnums[overview_resampling]) if not quiet: click.echo("Updating dataset tags...", err=True) for i, b in enumerate(indexes): tmp_dst.set_band_description(i + 1, src_dst.descriptions[b - 1]) if forward_band_tags: tmp_dst.update_tags(i + 1, **src_dst.tags(b)) tags = src_dst.tags() tags.update( dict( OVR_RESAMPLING_ALG=ResamplingEnums[overview_resampling] .name.upper())) if additional_cog_metadata: tags.update(**additional_cog_metadata) if web_optimized and not use_cog_driver: default_zoom = tms.zoom_for_res( max(tmp_dst.res), max_z=30, zoom_level_strategy=zoom_level_strategy, ) dst_kwargs.update({ "@TILING_SCHEME_NAME": tms.identifier, "@TILING_SCHEME_ZOOM_LEVEL": zoom_level if zoom_level is not None else default_zoom, }) if aligned_levels: dst_kwargs.update( {"@TILING_SCHEME_ALIGNED_LEVELS": aligned_levels}) tmp_dst.update_tags(**tags) tmp_dst._set_all_scales( [vrt_dst.scales[b - 1] for b in indexes]) tmp_dst._set_all_offsets( [vrt_dst.offsets[b - 1] for b in indexes]) if not quiet: click.echo("Writing output to: {}".format(dst_path), err=True) if use_cog_driver: if not GDALVersion.runtime().at_least("3.1"): raise Exception( "GDAL 3.1 or above required to use the COG driver." ) dst_kwargs["driver"] = "COG" if web_optimized: dst_kwargs["TILING_SCHEME"] = ( "GoogleMapsCompatible" if tms.identifier == "WebMercatorQuad" else tms.identifier) if zoom_level is not None: if not GDALVersion.runtime().at_least("3.5"): warnings.warn( "ZOOM_LEVEL option is only available with GDAL >3.5." ) dst_kwargs["ZOOM_LEVEL"] = zoom_level dst_kwargs["ZOOM_LEVEL_STRATEGY"] = zoom_level_strategy if aligned_levels is not None: # GDAL uses Number of resolution (not overviews) # See https://github.com/OSGeo/gdal/issues/5336#issuecomment-1042946603 dst_kwargs["aligned_levels"] = aligned_levels + 1 if add_mask and dst_kwargs.get("compress", "") != "JPEG": warnings.warn( "With GDAL COG driver, mask band will be translated to an alpha band." ) dst_kwargs["overview_resampling"] = overview_resampling dst_kwargs["warp_resampling"] = resampling dst_kwargs["blocksize"] = tilesize dst_kwargs.pop("blockxsize", None) dst_kwargs.pop("blockysize", None) dst_kwargs.pop("tiled", None) dst_kwargs.pop("interleave", None) dst_kwargs.pop("photometric", None) copy(tmp_dst, dst_path, **dst_kwargs) else: copy(tmp_dst, dst_path, copy_src_overviews=True, **dst_kwargs)