def test_cog_translate_web_align(): """ Test Web-Optimized COG. - Test COG bounds (thus block) is aligned with Zoom levels """ tms = morecantile.tms.get("WebMercatorQuad") runner = CliRunner() with runner.isolated_filesystem(): web_profile = cog_profiles.get("raw") web_profile.update({"blockxsize": 256, "blockysize": 256}) config = dict(GDAL_TIFF_OVR_BLOCKSIZE="256") with rasterio.open(raster_path_web) as src_dst: _, max_zoom = get_zooms(src_dst) cog_translate( raster_path_web, "cogeo.tif", web_profile, quiet=True, web_optimized=True, config=config, aligned_levels=2, ) with COGReader(raster_path_web) as src_dst: bounds = src_dst.bounds with COGReader("cogeo.tif") as cog: _, max_zoom = get_zooms(cog.dataset) ulTile = tms.xy_bounds(tms.tile(bounds[0], bounds[3], max_zoom - 2)) assert round(cog.dataset.bounds[0], 5) == round(ulTile.left, 5) assert round(cog.dataset.bounds[3], 5) == round(ulTile.top, 5) lrTile = tms.xy_bounds(tms.tile(bounds[2], bounds[1], max_zoom - 2)) assert round(cog.dataset.bounds[2], 5) == round(lrTile.right, 5) assert round(cog.dataset.bounds[1], 5) == round(lrTile.bottom, 5) cog_translate( raster_path_web, "cogeo.tif", web_profile, quiet=True, web_optimized=True, config=config, aligned_levels=3, ) with COGReader(raster_path_web) as src_dst: bounds = src_dst.bounds with COGReader("cogeo.tif") as cog_dst: _, max_zoom = get_zooms(cog_dst.dataset) ulTile = tms.xy_bounds(tms.tile(bounds[0], bounds[3], max_zoom - 3)) assert round(cog_dst.dataset.bounds[0], 5) == round(ulTile.left, 5) assert round(cog_dst.dataset.bounds[3], 5) == round(ulTile.top, 5) lrTile = tms.xy_bounds(tms.tile(bounds[2], bounds[1], max_zoom - 3)) assert round(cog_dst.dataset.bounds[2], 5) == round(lrTile.right, 5) assert round(cog_dst.dataset.bounds[1], 5) == round(lrTile.bottom, 5)
def test_cog_translate_webZooms(): """ Test Web-Optimized COG. - Test COG size is a multiple of 256 (mercator tile size) - Test COG bounds are aligned with mercator grid at max zoom - Test high resolution internal tiles are equal to mercator tile using cogdumper and rio-tiler - Test overview internal tiles are equal to mercator tile using cogdumper and rio-tiler """ runner = CliRunner() with runner.isolated_filesystem(): web_profile = cog_profiles.get("raw") web_profile.update({"blockxsize": 256, "blockysize": 256}) config = dict(GDAL_TIFF_OVR_BLOCKSIZE="128") cog_translate( raster_path_north, "cogeo.tif", web_profile, quiet=True, web_optimized=True, config=config, ) with rasterio.open("cogeo.tif") as out_dst: _, maxzoom = get_zooms(out_dst) assert maxzoom == 8 cog_translate( raster_path_north, "cogeo.tif", web_profile, quiet=True, web_optimized=True, latitude_adjustment=False, config=config, ) with rasterio.open("cogeo.tif") as out_dst: _, maxzoom = get_zooms(out_dst) assert maxzoom == 10
def test_cog_translate_web(): """ Test Web-Optimized COG. - Test COG size is a multiple of 256 (mercator tile size) - Test COG bounds are aligned with mercator grid at max zoom """ tms = morecantile.tms.get("WebMercatorQuad") runner = CliRunner() with runner.isolated_filesystem(): web_profile = cog_profiles.get("raw") web_profile.update({"blockxsize": 256, "blockysize": 256}) config = dict(GDAL_TIFF_OVR_BLOCKSIZE="256") cog_translate( raster_path_web, "cogeo.tif", web_profile, quiet=True, web_optimized=True, config=config, aligned_levels=0, ) with rasterio.open(raster_path_web) as src_dst: with rasterio.open("cogeo.tif") as out_dst: blocks = list(set(out_dst.block_shapes)) assert len(blocks) == 1 ts = blocks[0][0] assert not out_dst.width % ts assert not out_dst.height % ts _, max_zoom = get_zooms(out_dst) bounds = list( transform_bounds(src_dst.crs, "epsg:4326", *src_dst.bounds, densify_pts=21)) ulTile = tms.xy_bounds(tms.tile(bounds[0], bounds[3], max_zoom)) assert out_dst.bounds.left == ulTile.left assert out_dst.bounds.top == ulTile.top lrTile = tms.xy_bounds(tms.tile(bounds[2], bounds[1], max_zoom)) assert out_dst.bounds.right == lrTile.right assert round(out_dst.bounds.bottom, 6) == round(lrTile.bottom, 6)
def test_cogeo_zoom_level(runner): """Should work as expected.""" with runner.isolated_filesystem(): result = runner.invoke( cogeo, [ "create", raster_path_rgb, "output.tif", "--web-optimized", "--zoom-level", "18", ], ) assert not result.exception assert result.exit_code == 0 with rasterio.open("output.tif") as src: _, max_zoom = get_zooms(src) assert max_zoom == 18 result = runner.invoke( cogeo, [ "create", raster_path_rgb, "output.tif", "--web-optimized", "--zoom-level", "19", ], ) assert not result.exception assert result.exit_code == 0 with rasterio.open("output.tif") as src: _, max_zoom = get_zooms(src) assert max_zoom == 19
def cog_info(src_path: Union[str, pathlib.PurePath], **kwargs: Any) -> Dict: """Get general info and validate Cloud Optimized Geotiff.""" is_valid, validation_errors, validation_warnings = cog_validate( src_path, quiet=True, **kwargs, ) with rasterio.open(src_path) as src_dst: _info = { "Path": str(src_path), "Driver": src_dst.driver, "COG": is_valid, "Compression": src_dst.compression.value if src_dst.compression else None, "ColorSpace": src_dst.photometric.value if src_dst.photometric else None, } if validation_errors: _info["COG_errors"] = validation_errors if validation_warnings: _info["COG_warnings"] = validation_warnings try: colormap = src_dst.colormap(1) except ValueError: colormap = None profile = { "Bands": src_dst.count, "Width": src_dst.width, "Height": src_dst.height, "Tiled": src_dst.is_tiled, "Dtype": src_dst.dtypes[0], "Interleave": src_dst.interleaving.value, "Alpha Band": utils.has_alpha_band(src_dst), "Internal Mask": utils.has_mask_band(src_dst), "Nodata": src_dst.nodata, "ColorInterp": tuple([color.name for color in src_dst.colorinterp]), "ColorMap": colormap is not None, "Scales": src_dst.scales, "Offsets": src_dst.offsets, } try: crs = (f"EPSG:{src_dst.crs.to_epsg()}" if src_dst.crs.to_epsg() else src_dst.crs.to_wkt()) except AttributeError: crs = None minzoom: Optional[int] = None maxzoom: Optional[int] = None try: minzoom, maxzoom = utils.get_zooms(src_dst) except Exception: pass geo = { "CRS": crs, "BoundingBox": tuple(src_dst.bounds), "Origin": (src_dst.transform.c, src_dst.transform.f), "Resolution": (src_dst.transform.a, src_dst.transform.e), "MinZoom": minzoom, "MaxZoom": maxzoom, } ifd_raw = [{ "Level": 0, "Width": src_dst.width, "Height": src_dst.height, "Blocksize": src_dst.block_shapes[0], "Decimation": 0, }] overviews = src_dst.overviews(1) tags = src_dst.tags() ifd_ovr = [] for ix, decim in enumerate(overviews): with rasterio.open(src_path, OVERVIEW_LEVEL=ix) as ovr_dst: ifd_ovr.append({ "Level": ix + 1, "Width": ovr_dst.width, "Height": ovr_dst.height, "Blocksize": ovr_dst.block_shapes[0], "Decimation": decim, }) ifds = ifd_raw + ifd_ovr output = _info.copy() output["Profile"] = profile output["GEO"] = geo output["Tags"] = tags output["IFD"] = ifds return output
def test_cog_translate_Internal(): """ Test Web-Optimized COG. - Test COG size is a multiple of 256 (mercator tile size) - Test COG bounds are aligned with mercator grid at max zoom - Test high resolution internal tiles are equal to mercator tile using cogdumper and rio-tiler - Test overview internal tiles are equal to mercator tile using cogdumper and rio-tiler """ tms = morecantile.tms.get("WebMercatorQuad") runner = CliRunner() with runner.isolated_filesystem(): web_profile = cog_profiles.get("raw") web_profile.update({"blockxsize": 256, "blockysize": 256}) config = dict(GDAL_TIFF_OVR_BLOCKSIZE="256") cog_translate( raster_path_web, "cogeo.tif", web_profile, quiet=True, web_optimized=True, config=config, aligned_levels=0, ) with rasterio.open(raster_path_web) as src_dst: with rasterio.open("cogeo.tif") as out_dst: blocks = list(set(out_dst.block_shapes)) assert len(blocks) == 1 ts = blocks[0][0] assert not out_dst.width % ts assert not out_dst.height % ts _, max_zoom = get_zooms(out_dst) bounds = list( transform_bounds(src_dst.crs, "epsg:4326", *src_dst.bounds, densify_pts=21)) minimumTile = tms.tile(bounds[0], bounds[3], max_zoom) maximumTile = tms.tile(bounds[2], bounds[1], max_zoom) with open("cogeo.tif", "rb") as out_body: reader = FileReader(out_body) cog = COGTiff(reader.read) # High resolution # Top Left tile _, tile = cog.get_tile(0, 0, 0) tile_length = 256 * 256 * 3 t = struct.unpack_from("{}b".format(tile_length), tile) arr = numpy.array(t).reshape(256, 256, 3).astype(numpy.uint8) arr = numpy.transpose(arr, [2, 0, 1]) with rasterio.open("cogeo.tif") as src_dst: w = from_bounds(*tms.xy_bounds(minimumTile), src_dst.transform) data = src_dst.read(window=w, out_shape=(src_dst.count, 256, 256)) numpy.testing.assert_array_equal(data, arr) # Bottom right tile _, tile = cog.get_tile(4, 3, 0) tile_length = 256 * 256 * 3 t = struct.unpack_from("{}b".format(tile_length), tile) arr = numpy.array(t).reshape(256, 256, 3).astype(numpy.uint8) arr = numpy.transpose(arr, [2, 0, 1]) with rasterio.open("cogeo.tif") as src_dst: w = from_bounds(*tms.xy_bounds(maximumTile), src_dst.transform) data = src_dst.read(window=w, out_shape=(src_dst.count, 256, 256)) numpy.testing.assert_array_equal(data, arr)
def test_cog_translate_Internal(): """ Test Web-Optimized COG. - Test COG size is a multiple of 256 (mercator tile size) - Test COG bounds are aligned with mercator grid at max zoom - Test high resolution internal tiles are equal to mercator tile using cogdumper and rio-tiler - Test overview internal tiles are equal to mercator tile using cogdumper and rio-tiler """ from cogdumper.cog_tiles import COGTiff from cogdumper.filedumper import Reader as FileReader runner = CliRunner() with runner.isolated_filesystem(): web_profile = cog_profiles.get("raw") web_profile.update({"blockxsize": 256, "blockysize": 256}) config = dict(GDAL_TIFF_OVR_BLOCKSIZE="128") cog_translate( raster_path_web, "cogeo.tif", web_profile, quiet=True, web_optimized=True, config=config, ) with rasterio.open(raster_path_web) as src_dst: with rasterio.open("cogeo.tif") as out_dst: blocks = list(set(out_dst.block_shapes)) assert len(blocks) == 1 ts = blocks[0][0] assert not out_dst.width % ts assert not out_dst.height % ts _, max_zoom = get_zooms(out_dst) bounds = list( transform_bounds(src_dst.crs, "epsg:4326", *src_dst.bounds, densify_pts=21)) minimumTile = mercantile.tile(bounds[0], bounds[3], max_zoom) maximumTile = mercantile.tile(bounds[2], bounds[1], max_zoom) with open("cogeo.tif", "rb") as out_body: reader = FileReader(out_body) cog = COGTiff(reader.read) # High resolution # Top Left tile _, tile = cog.get_tile(0, 0, 0) tile_length = 256 * 256 * 3 t = struct.unpack_from("{}b".format(tile_length), tile) arr = numpy.array(t).reshape(256, 256, 3).astype(numpy.uint8) arr = numpy.transpose(arr, [2, 0, 1]) with rasterio.open("cogeo.tif") as src_dst: data, _ = COGreader.tile(src_dst, *minimumTile, resampling_method="nearest") numpy.testing.assert_array_equal(data, arr) # Bottom right tile _, tile = cog.get_tile(4, 3, 0) tile_length = 256 * 256 * 3 t = struct.unpack_from("{}b".format(tile_length), tile) arr = numpy.array(t).reshape(256, 256, 3).astype(numpy.uint8) arr = numpy.transpose(arr, [2, 0, 1]) with rasterio.open("cogeo.tif") as src_dst: data, _ = COGreader.tile(src_dst, *maximumTile, resampling_method="nearest") numpy.testing.assert_array_equal(data, arr) # Low resolution (overview 1) # Top Left tile # NOTE: overview internal tile size is 128px # We need to stack two internal tiles to compare with # the 256px mercator tile fetched by rio-tiler # ref: https://github.com/cogeotiff/rio-cogeo/issues/60 _, tile = cog.get_tile(1, 0, 1) tile_length = 128 * 128 * 3 t = struct.unpack_from("{}b".format(tile_length), tile) arr1 = numpy.array(t).reshape(128, 128, 3).astype(numpy.uint8) arr1 = numpy.transpose(arr1, [2, 0, 1]) _, tile = cog.get_tile(2, 0, 1) tile_length = 128 * 128 * 3 t = struct.unpack_from("{}b".format(tile_length), tile) arr2 = numpy.array(t).reshape(128, 128, 3).astype(numpy.uint8) arr2 = numpy.transpose(arr2, [2, 0, 1]) arr = numpy.dstack((arr1, arr2)) with rasterio.open("cogeo.tif") as src_dst: data, _ = COGreader.tile(src_dst, 118594, 60034, 17, resampling_method="nearest") data = data[:, 128:, :] numpy.testing.assert_array_equal(data, arr)
def cog_info(src_path: Union[str, pathlib.PurePath], **kwargs: Any) -> models.Info: """Get general info and validate Cloud Optimized Geotiff.""" is_valid, validation_errors, validation_warnings = cog_validate( src_path, quiet=True, **kwargs, ) with rasterio.open(src_path) as src_dst: driver = src_dst.driver compression = src_dst.compression.value if src_dst.compression else None colorspace = src_dst.photometric.value if src_dst.photometric else None overviews = src_dst.overviews(1) tags = {"Image Metadata": src_dst.tags()} namespaces = src_dst.tag_namespaces() for ns in namespaces: if ns in ["DERIVED_SUBDATASETS"]: continue tags.update({str.title(ns).replace("_", " "): src_dst.tags(ns=ns)}) band_metadata = { f"Band {ix}": models.BandMetadata( **{ "Description": src_dst.descriptions[ix - 1], "ColorInterp": src_dst.colorinterp[ix - 1].name, "Offset": src_dst.offsets[ix - 1], "Scale": src_dst.scales[ix - 1], "Metadata": src_dst.tags(ix), }) for ix in src_dst.indexes } try: colormap = src_dst.colormap(1) except ValueError: colormap = None profile = models.Profile( Bands=src_dst.count, Width=src_dst.width, Height=src_dst.height, Tiled=src_dst.is_tiled, Dtype=src_dst.dtypes[0], Interleave=src_dst.interleaving.value, AlphaBand=utils.has_alpha_band(src_dst), InternalMask=utils.has_mask_band(src_dst), Nodata=src_dst.nodata, ColorInterp=tuple([color.name for color in src_dst.colorinterp]), ColorMap=colormap is not None, Scales=src_dst.scales, Offsets=src_dst.offsets, ) try: crs = (f"EPSG:{src_dst.crs.to_epsg()}" if src_dst.crs.to_epsg() else src_dst.crs.to_wkt()) except AttributeError: crs = None minzoom: Optional[int] = None maxzoom: Optional[int] = None try: minzoom, maxzoom = utils.get_zooms(src_dst) except Exception: pass geo = models.Geo( CRS=crs, BoundingBox=tuple(src_dst.bounds), Origin=(src_dst.transform.c, src_dst.transform.f), Resolution=(src_dst.transform.a, src_dst.transform.e), MinZoom=minzoom, MaxZoom=maxzoom, ) ifds = [ models.IFD( Level=0, Width=src_dst.width, Height=src_dst.height, Blocksize=src_dst.block_shapes[0], Decimation=0, ) ] for ix, decim in enumerate(overviews): with rasterio.open(src_path, OVERVIEW_LEVEL=ix) as ovr_dst: ifds.append( models.IFD( Level=ix + 1, Width=ovr_dst.width, Height=ovr_dst.height, Blocksize=ovr_dst.block_shapes[0], Decimation=decim, )) return models.Info( Path=str(src_path), Driver=driver, COG=is_valid, Compression=compression, ColorSpace=colorspace, COG_errors=validation_errors or None, COG_warnings=validation_warnings or None, Profile=profile, GEO=geo, Tags=tags, Band_Metadata=band_metadata, IFD=ifds, )