#!/usr/bin/env pytest # -*- coding: utf-8 -*- ############################################################################### # $Id$ # # Project: GDAL/OGR Test Suite # Purpose: COG driver testing # Author: Even Rouault # ############################################################################### # Copyright (c) 2019, Even Rouault # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. ############################################################################### import struct import sys import gdaltest import pytest from test_py_scripts import samples_path from osgeo import gdal, osr ############################################################################### @pytest.fixture(autouse=True, scope="module") def module_disable_exceptions(): with gdaltest.disable_exceptions(): yield ############################################################################### def _check_cog(filename): path = samples_path if path not in sys.path: sys.path.append(path) import validate_cloud_optimized_geotiff try: _, errors, _ = validate_cloud_optimized_geotiff.validate( filename, full_check=True ) assert not errors, "validate_cloud_optimized_geotiff failed" except OSError: pytest.fail("validate_cloud_optimized_geotiff failed") ############################################################################### def check_libtiff_internal_or_at_least(expected_maj, expected_min, expected_micro): md = gdal.GetDriverByName("GTiff").GetMetadata() if md["LIBTIFF"] == "INTERNAL": return True if md["LIBTIFF"].startswith("LIBTIFF, Version "): version = md["LIBTIFF"][len("LIBTIFF, Version ") :] version = version[0 : version.find("\n")] got_maj, got_min, got_micro = version.split(".") got_maj = int(got_maj) got_min = int(got_min) got_micro = int(got_micro) if got_maj > expected_maj: return True if got_maj < expected_maj: return False if got_min > expected_min: return True if got_min < expected_min: return False return got_micro >= expected_micro return False ############################################################################### # Basic test def test_cog_basic(): tab = [0] def my_cbk(pct, _, arg): assert pct >= tab[0] tab[0] = pct return 1 filename = "/vsimem/cog.tif" src_ds = gdal.Open("data/byte.tif") assert src_ds.GetMetadataItem("GDAL_STRUCTURAL_METADATA", "TIFF") is None ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, callback=my_cbk, callback_data=tab ) src_ds = None assert tab[0] == 1.0 assert ds ds = None ds = gdal.Open(filename) assert ds.GetRasterBand(1).Checksum() == 4672 assert ds.GetMetadataItem("LAYOUT", "IMAGE_STRUCTURE") == "COG" assert ds.GetMetadataItem("COMPRESSION", "IMAGE_STRUCTURE") == "LZW" assert ds.GetRasterBand(1).GetOverviewCount() == 0 assert ds.GetRasterBand(1).GetBlockSize() == [512, 512] assert ( ds.GetMetadataItem("GDAL_STRUCTURAL_METADATA", "TIFF") == """GDAL_STRUCTURAL_METADATA_SIZE=000140 bytes LAYOUT=IFDS_BEFORE_DATA BLOCK_ORDER=ROW_MAJOR BLOCK_LEADER=SIZE_AS_UINT4 BLOCK_TRAILER=LAST_4_BYTES_REPEATED KNOWN_INCOMPATIBLE_EDITION=NO """ ) ds = None _check_cog(filename) gdal.GetDriverByName("GTiff").Delete(filename) ############################################################################### # Test creation options def test_cog_creation_options(): filename = "/vsimem/cog.tif" src_ds = gdal.Open("data/byte.tif") ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=DEFLATE", "LEVEL=1", "NUM_THREADS=2"] ) assert ds ds = None ds = gdal.Open(filename) assert ds.GetRasterBand(1).Checksum() == 4672 assert ds.GetMetadataItem("COMPRESSION", "IMAGE_STRUCTURE") == "DEFLATE" assert ds.GetMetadataItem("PREDICTOR", "IMAGE_STRUCTURE") is None ds = None filesize = gdal.VSIStatL(filename).size _check_cog(filename) gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=DEFLATE", "BIGTIFF=YES", "LEVEL=1"] ) assert gdal.VSIStatL(filename).size != filesize gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=DEFLATE", "PREDICTOR=YES", "LEVEL=1"] ) assert gdal.VSIStatL(filename).size != filesize ds = gdal.Open(filename) assert ds.GetMetadataItem("PREDICTOR", "IMAGE_STRUCTURE") == "2" ds = None gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=DEFLATE", "LEVEL=9"] ) assert gdal.VSIStatL(filename).size < filesize colist = gdal.GetDriverByName("COG").GetMetadataItem("DMD_CREATIONOPTIONLIST") if "ZSTD" in colist: gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=ZSTD"] ) ds = gdal.Open(filename) assert ds.GetMetadataItem("COMPRESSION", "IMAGE_STRUCTURE") == "ZSTD" ds = None if "LZMA" in colist: gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=LZMA"] ) ds = gdal.Open(filename) assert ds.GetMetadataItem("COMPRESSION", "IMAGE_STRUCTURE") == "LZMA" ds = None if "WEBP" in colist: with gdaltest.error_handler(): assert not gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=WEBP"] ) if "LERC" in colist: assert gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=LERC"] ) filesize_no_z_error = gdal.VSIStatL(filename).size assert gdal.VSIStatL(filename).size != filesize assert gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=LERC", "MAX_Z_ERROR=10"] ) filesize_with_z_error = gdal.VSIStatL(filename).size assert filesize_with_z_error < filesize_no_z_error assert gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=LERC_DEFLATE"] ) filesize_lerc_deflate = gdal.VSIStatL(filename).size assert filesize_lerc_deflate < filesize_no_z_error assert gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=LERC_DEFLATE", "LEVEL=1"] ) filesize_lerc_deflate_level_1 = gdal.VSIStatL(filename).size assert filesize_lerc_deflate_level_1 > filesize_lerc_deflate if "ZSTD" in colist: assert gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=LERC_ZSTD"] ) filesize_lerc_zstd = gdal.VSIStatL(filename).size assert filesize_lerc_zstd < filesize_no_z_error assert gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=LERC_ZSTD", "LEVEL=1"] ) filesize_lerc_zstd_level_1 = gdal.VSIStatL(filename).size assert filesize_lerc_zstd_level_1 > filesize_lerc_zstd src_ds = None with gdaltest.error_handler(): gdal.GetDriverByName("GTiff").Delete(filename) ############################################################################### # Test creation of overviews def test_cog_creation_of_overviews(): tab = [0] def my_cbk(pct, _, arg): assert pct >= tab[0] tab[0] = pct return 1 directory = "/vsimem/test_cog_creation_of_overviews" filename = directory + "/cog.tif" src_ds = gdal.Translate("", "data/byte.tif", options="-of MEM -outsize 2048 300") with gdaltest.config_option("GDAL_TIFF_INTERNAL_MASK", "YES"): check_filename = "/vsimem/tmp.tif" ds = gdal.GetDriverByName("GTiff").CreateCopy( check_filename, src_ds, options=["TILED=YES"] ) ds.BuildOverviews("CUBIC", [2, 4]) cs1 = ds.GetRasterBand(1).GetOverview(0).Checksum() cs2 = ds.GetRasterBand(1).GetOverview(1).Checksum() ds = None gdal.Unlink(check_filename) ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, callback=my_cbk, callback_data=tab ) assert tab[0] == 1.0 assert ds assert len(gdal.ReadDir(directory)) == 1 # check that the temp file has gone away ds = None ds = gdal.Open(filename) assert ds.GetRasterBand(1).Checksum() == src_ds.GetRasterBand(1).Checksum() assert ds.GetRasterBand(1).GetOverviewCount() == 2 assert ds.GetRasterBand(1).GetOverview(0).Checksum() == cs1 assert ds.GetRasterBand(1).GetOverview(1).Checksum() == cs2 ds = None _check_cog(filename) src_ds = None gdal.GetDriverByName("GTiff").Delete(filename) gdal.Unlink(directory) ############################################################################### # Test creation of overviews with a different compression method @pytest.mark.require_creation_option("COG", "JPEG") def test_cog_creation_of_overviews_with_compression(): directory = "/vsimem/test_cog_creation_of_overviews_with_compression" filename = directory + "/cog.tif" src_ds = gdal.Translate("", "data/byte.tif", options="-of MEM -outsize 2048 300") ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=LZW", "OVERVIEW_COMPRESS=JPEG", "OVERVIEW_QUALITY=50"], ) assert ds.GetRasterBand(1).GetOverviewCount() == 2 assert ds.GetMetadata("IMAGE_STRUCTURE")["COMPRESSION"] == "LZW" ds_overview_a = gdal.Open("GTIFF_DIR:2:" + filename) assert ds_overview_a.GetMetadata("IMAGE_STRUCTURE")["COMPRESSION"] == "JPEG" assert ds_overview_a.GetMetadata("IMAGE_STRUCTURE")["JPEG_QUALITY"] == "50" ds_overview_b = gdal.Open("GTIFF_DIR:3:" + filename) assert ds_overview_b.GetMetadata("IMAGE_STRUCTURE")["COMPRESSION"] == "JPEG" assert ds_overview_a.GetMetadata("IMAGE_STRUCTURE")["JPEG_QUALITY"] == "50" ds_overview_a = None ds_overview_b = None ds = None src_ds = None gdal.GetDriverByName("GTiff").Delete(filename) gdal.Unlink(directory) ############################################################################### # Test creation of overviews with a dataset with a mask def test_cog_creation_of_overviews_with_mask(): tab = [0] def my_cbk(pct, _, arg): assert pct >= tab[0] tab[0] = pct return 1 directory = "/vsimem/test_cog_creation_of_overviews_with_mask" gdal.Mkdir(directory, 0o755) filename = directory + "/cog.tif" src_ds = gdal.Translate("", "data/byte.tif", options="-of MEM -outsize 2048 300") src_ds.CreateMaskBand(gdal.GMF_PER_DATASET) src_ds.GetRasterBand(1).GetMaskBand().WriteRaster( 0, 0, 1024, 300, b"\xFF", buf_xsize=1, buf_ysize=1 ) with gdaltest.config_option("GDAL_TIFF_INTERNAL_MASK", "YES"): check_filename = "/vsimem/tmp.tif" ds = gdal.GetDriverByName("GTiff").CreateCopy( check_filename, src_ds, options=["TILED=YES"] ) ds.BuildOverviews("CUBIC", [2, 4]) cs1 = ds.GetRasterBand(1).GetOverview(0).Checksum() cs2 = ds.GetRasterBand(1).GetOverview(1).Checksum() ds = None gdal.Unlink(check_filename) ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, callback=my_cbk, callback_data=tab ) assert tab[0] == 1.0 assert ds assert len(gdal.ReadDir(directory)) == 1 # check that the temp file has gone away ds = None ds = gdal.Open(filename) assert ds.GetRasterBand(1).Checksum() == src_ds.GetRasterBand(1).Checksum() assert ds.GetRasterBand(1).GetOverviewCount() == 2 assert ds.GetRasterBand(1).GetOverview(0).GetBlockSize() == [512, 512] assert ds.GetRasterBand(1).GetOverview(0).Checksum() == cs1 assert ds.GetRasterBand(1).GetOverview(1).Checksum() == cs2 ds = None _check_cog(filename) src_ds = None gdal.GetDriverByName("GTiff").Delete(filename) gdal.Unlink(directory) ############################################################################### # Test full world reprojection to WebMercator @pytest.mark.require_creation_option("COG", "JPEG") def test_cog_small_world_to_web_mercator(): tab = [0] def my_cbk(pct, _, arg): assert pct >= tab[0] tab[0] = pct return 1 directory = "/vsimem/test_cog_small_world_to_web_mercator" gdal.Mkdir(directory, 0o755) filename = directory + "/cog.tif" src_ds = gdal.Open("../gdrivers/data/small_world.tif") ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["TILING_SCHEME=GoogleMapsCompatible", "COMPRESS=JPEG"], callback=my_cbk, callback_data=tab, ) assert tab[0] == 1.0 assert ds assert len(gdal.ReadDir(directory)) == 1 # check that the temp file has gone away ds = None ds = gdal.Open(filename) assert ds.RasterCount == 3 assert ds.RasterXSize == 256 assert ds.RasterYSize == 256 assert ds.GetRasterBand(1).GetMaskFlags() == gdal.GMF_PER_DATASET assert ds.GetRasterBand(1).GetBlockSize() == [256, 256] gt = ds.GetGeoTransform() assert gt[1] == -gt[5] # yes, checking for strict equality expected_gt = [ -20037508.342789248, 156543.033928041, 0.0, 20037508.342789248, 0.0, -156543.033928041, ] for i in range(6): if gt[i] != pytest.approx(expected_gt[i], abs=1e-10 * abs(expected_gt[i])): assert False, gt got_cs = [ds.GetRasterBand(i + 1).Checksum() for i in range(3)] assert got_cs in ( [26293, 23439, 14955], [26228, 22085, 12992], [25088, 23140, 13265], # libjpeg 9e ) assert ds.GetRasterBand(1).GetMaskBand().Checksum() == 17849 assert ds.GetRasterBand(1).GetOverviewCount() == 0 ds = None _check_cog(filename) src_ds = None gdal.GetDriverByName("GTiff").Delete(filename) gdal.Unlink(directory) ############################################################################### # Test reprojection of small extent to WebMercator def test_cog_byte_to_web_mercator(): tab = [0] def my_cbk(pct, _, arg): assert pct >= tab[0] tab[0] = pct return 1 directory = "/vsimem/test_cog_byte_to_web_mercator" gdal.Mkdir(directory, 0o755) filename = directory + "/cog.tif" src_ds = gdal.Open("data/byte.tif") ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["TILING_SCHEME=GoogleMapsCompatible", "ALIGNED_LEVELS=3"], callback=my_cbk, callback_data=tab, ) assert tab[0] == 1.0 assert ds assert len(gdal.ReadDir(directory)) == 1 # check that the temp file has gone away ds = None ds = gdal.Open(filename) assert ds.RasterCount == 2 assert ds.RasterXSize == 1024 assert ds.RasterYSize == 1024 assert ds.GetRasterBand(1).GetMaskFlags() == gdal.GMF_ALPHA + gdal.GMF_PER_DATASET assert ds.GetRasterBand(1).GetBlockSize() == [256, 256] gt = ds.GetGeoTransform() assert gt[1] == -gt[5] # yes, checking for strict equality expected_gt = [ -13149614.849955443, 76.43702828517598, 0.0, 4070118.8821290657, 0.0, -76.43702828517598, ] for i in range(6): if gt[i] != pytest.approx(expected_gt[i], abs=1e-10 * abs(expected_gt[i])): assert False, gt assert ds.GetRasterBand(1).Checksum() in ( 4363, 4264, # got on Mac at some point 4362, # libjpeg 9d 4569, # libjpeg 9e ) assert ds.GetRasterBand(1).GetMaskBand().Checksum() == 4356 assert ds.GetRasterBand(1).GetOverviewCount() == 2 ds = None _check_cog(filename) # Use our generated COG as the input of the same COG generation: reprojection # should be skipped filename2 = directory + "/cog2.tif" src_ds = gdal.Open(filename) class my_error_handler(object): def __init__(self): self.debug_msg_list = [] self.other_msg_list = [] def handler(self, eErrClass, err_no, msg): if eErrClass == gdal.CE_Debug: self.debug_msg_list.append(msg) else: self.other_msg_list.append(msg) handler = my_error_handler() try: gdal.PushErrorHandler(handler.handler) gdal.SetCurrentErrorHandlerCatchDebug(True) with gdaltest.config_option("CPL_DEBUG", "COG"): ds = gdal.GetDriverByName("COG").CreateCopy( filename2, src_ds, options=["TILING_SCHEME=GoogleMapsCompatible", "ALIGNED_LEVELS=3"], ) finally: gdal.PopErrorHandler() assert ds assert ( "COG: Skipping reprojection step: source dataset matches reprojection specifications" in handler.debug_msg_list ) assert handler.other_msg_list == [] src_ds = None ds = None # Cleanup gdal.GetDriverByName("GTiff").Delete(filename) gdal.GetDriverByName("GTiff").Delete(filename2) gdal.Unlink(directory) ############################################################################### # Same as previous test case but with other input options def test_cog_byte_to_web_mercator_manual(): directory = "/vsimem/test_cog_byte_to_web_mercator_manual" gdal.Mkdir(directory, 0o755) filename = directory + "/cog.tif" src_ds = gdal.Open("data/byte.tif") res = 76.43702828517598 minx = -13149614.849955443 maxx = minx + 1024 * res maxy = 4070118.8821290657 miny = maxy - 1024 * res ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=[ "BLOCKSIZE=256", "TARGET_SRS=EPSG:3857", "RES=%.18g" % res, "EXTENT=%.18g,%.18g,%.18g,%.18g" % (minx, miny, maxx, maxy), ], ) assert ds ds = None ds = gdal.Open(filename) assert ds.RasterCount == 2 assert ds.RasterXSize == 1024 assert ds.RasterYSize == 1024 assert ds.GetRasterBand(1).GetMaskFlags() == gdal.GMF_ALPHA + gdal.GMF_PER_DATASET assert ds.GetRasterBand(1).GetBlockSize() == [256, 256] expected_gt = [ -13149614.849955443, 76.43702828517598, 0.0, 4070118.8821290657, 0.0, -76.43702828517598, ] assert ds.GetGeoTransform() == pytest.approx(expected_gt, rel=1e-10) assert ds.GetRasterBand(1).Checksum() in ( 4363, 4264, # got on Mac at some point 4362, # libjpeg 9d 4569, # libjpeg 9e ) assert ds.GetRasterBand(1).GetMaskBand().Checksum() == 4356 assert ds.GetRasterBand(1).GetOverviewCount() == 2 ds = None src_ds = None # Check that we correctly round to the closest tile if input bounds are # very close to its boundary (less than half a pixel) filename2 = directory + "/cog2.tif" eps = 0.49 * res gdal.Translate( filename2, filename, options="-of COG -co TILING_SCHEME=GoogleMapsCompatible -a_ullr %.18g %.18g %.18g %.18g" % (minx - eps, maxy + eps, maxx + eps, miny - eps), ) ds = gdal.Open(filename2) assert ds.RasterXSize == 1024 assert ds.RasterYSize == 1024 assert ds.GetGeoTransform() == pytest.approx(expected_gt, rel=1e-10) ds = None gdal.GetDriverByName("GTiff").Delete(filename) gdal.GetDriverByName("GTiff").Delete(filename2) gdal.Unlink(directory) ############################################################################### # Test OVERVIEWS creation option def test_cog_overviews_co(): def my_cbk(pct, _, arg): assert pct >= tab[0] tab[0] = pct return 1 directory = "/vsimem/test_cog_overviews_co" filename = directory + "/cog.tif" src_ds = gdal.Translate("", "data/byte.tif", options="-of MEM -outsize 2048 300") for val in ["NONE", "FORCE_USE_EXISTING"]: tab = [0] ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["OVERVIEWS=" + val], callback=my_cbk, callback_data=tab, ) assert tab[0] == 1.0 assert ds ds = None ds = gdal.Open(filename) assert ds.GetRasterBand(1).Checksum() == src_ds.GetRasterBand(1).Checksum() assert ds.GetRasterBand(1).GetOverviewCount() == 0 ds = None _check_cog(filename) for val in ["AUTO", "IGNORE_EXISTING"]: tab = [0] ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["OVERVIEWS=" + val], callback=my_cbk, callback_data=tab, ) assert tab[0] == 1.0 assert ds ds = None ds = gdal.Open(filename) assert ds.GetRasterBand(1).Checksum() == src_ds.GetRasterBand(1).Checksum() assert ds.GetRasterBand(1).GetOverviewCount() == 2 assert ds.GetRasterBand(1).GetOverview(0).Checksum() != 0 ds = None _check_cog(filename) # Add overviews to source src_ds.BuildOverviews("NONE", [2]) tab = [0] ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["OVERVIEWS=NONE"], callback=my_cbk, callback_data=tab ) assert tab[0] == 1.0 assert ds ds = None ds = gdal.Open(filename) assert ds.GetRasterBand(1).Checksum() == src_ds.GetRasterBand(1).Checksum() assert ds.GetRasterBand(1).GetOverviewCount() == 0 ds = None _check_cog(filename) tab = [0] ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["OVERVIEWS=FORCE_USE_EXISTING"], callback=my_cbk, callback_data=tab, ) assert tab[0] == 1.0 assert ds ds = None ds = gdal.Open(filename) assert ds.GetRasterBand(1).Checksum() == src_ds.GetRasterBand(1).Checksum() assert ds.GetRasterBand(1).GetOverviewCount() == 1 assert ds.GetRasterBand(1).GetOverview(0).Checksum() == 0 ds = None _check_cog(filename) tab = [0] ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["OVERVIEWS=IGNORE_EXISTING"], callback=my_cbk, callback_data=tab, ) assert tab[0] == 1.0 assert ds ds = None ds = gdal.Open(filename) assert ds.GetRasterBand(1).Checksum() == src_ds.GetRasterBand(1).Checksum() assert ds.GetRasterBand(1).GetOverviewCount() == 2 assert ds.GetRasterBand(1).GetOverview(0).Checksum() != 0 ds = None _check_cog(filename) src_ds = None gdal.GetDriverByName("GTiff").Delete(filename) gdal.Unlink(directory) ############################################################################### # Test editing and invalidating a COG file def test_cog_invalidation_by_data_change(): filename = "/vsimem/cog.tif" src_ds = gdal.GetDriverByName("MEM").Create("", 100, 100) ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=DEFLATE"] ) ds = None ds = gdal.Open(filename, gdal.GA_Update) assert ds.GetMetadataItem("LAYOUT", "IMAGE_STRUCTURE") == "COG" src_ds = gdal.Open("data/byte.tif") data = src_ds.ReadRaster() ds.GetRasterBand(1).WriteRaster(0, 0, 20, 20, data) with gdaltest.error_handler(): assert ds.FlushCache() == gdal.CE_None ds = None with gdaltest.error_handler(): ds = gdal.Open(filename) assert ds.GetMetadataItem("LAYOUT", "IMAGE_STRUCTURE") is None ds = None with pytest.raises( AssertionError, match="KNOWN_INCOMPATIBLE_EDITION=YES is declared in the file" ): _check_cog(filename) with gdaltest.error_handler(): gdal.GetDriverByName("GTiff").Delete(filename) ############################################################################### # Test editing and invalidating a COG file def test_cog_invalidation_by_metadata_change(): filename = "/vsimem/cog.tif" src_ds = gdal.GetDriverByName("MEM").Create("", 1, 1) ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["COMPRESS=DEFLATE"] ) ds = None ds = gdal.Open(filename, gdal.GA_Update) ds.GetRasterBand(1).ComputeStatistics(False) ds = None with gdaltest.error_handler(): ds = gdal.Open(filename) assert ds.GetMetadataItem("LAYOUT", "IMAGE_STRUCTURE") is None ds = None with gdaltest.error_handler(): gdal.GetDriverByName("GTiff").Delete(filename) ############################################################################### # Test a tiling scheme with a CRS with northing/easting axis order # and non power-of-two ratios of scales. def test_cog_northing_easting_and_non_power_of_two_ratios(): filename = "/vsimem/cog.tif" x0_NZTM2000 = -1000000 y0_NZTM2000 = 10000000 blocksize = 256 scale_denom_zoom_level_14 = 1000 scale_denom_zoom_level_13 = 2500 scale_denom_zoom_level_12 = 5000 ds = gdal.Translate( filename, "data/byte.tif", options="-of COG -a_srs EPSG:2193 -a_ullr 1000001 5000001 1000006.6 4999995.4 -co TILING_SCHEME=NZTM2000 -co ALIGNED_LEVELS=2", ) assert ds.RasterXSize == 1280 assert ds.RasterYSize == 1280 b = ds.GetRasterBand(1) assert [ (b.GetOverview(i).XSize, b.GetOverview(i).YSize) for i in range(b.GetOverviewCount()) ] == [(512, 512), (256, 256)] gt = ds.GetGeoTransform() assert gt[1] == -gt[5] # yes, checking for strict equality res_zoom_level_14 = ( scale_denom_zoom_level_14 * 0.28e-3 ) # According to OGC Tile Matrix Set formula assert gt == pytest.approx( (999872, res_zoom_level_14, 0, 5000320, 0, -res_zoom_level_14), abs=1e-8 ) # Check that gt origin matches the corner of a tile at zoom 14 res = gt[1] tile_x = (gt[0] - x0_NZTM2000) / (blocksize * res) assert tile_x == pytest.approx(round(tile_x)) tile_y = (y0_NZTM2000 - gt[3]) / (blocksize * res) assert tile_y == pytest.approx(round(tile_y)) # Check that overview=0 corresponds to the resolution of zoom level=13 / OGC ScaleDenom = 2500 ovr0_xsize = b.GetOverview(0).XSize assert ( float(ovr0_xsize) / ds.RasterXSize == float(scale_denom_zoom_level_14) / scale_denom_zoom_level_13 ) # Check that gt origin matches the corner of a tile at zoom 13 ovr0_res = res * scale_denom_zoom_level_13 / scale_denom_zoom_level_14 tile_x = (gt[0] - x0_NZTM2000) / (blocksize * ovr0_res) assert tile_x == pytest.approx(round(tile_x)) tile_y = (y0_NZTM2000 - gt[3]) / (blocksize * ovr0_res) assert tile_y == pytest.approx(round(tile_y)) # Check that overview=1 corresponds to the resolution of zoom level=12 / OGC ScaleDenom = 5000 ovr1_xsize = b.GetOverview(1).XSize assert ( float(ovr1_xsize) / ds.RasterXSize == float(scale_denom_zoom_level_14) / scale_denom_zoom_level_12 ) # Check that gt origin matches the corner of a tile at zoom 12 ovr1_res = res * scale_denom_zoom_level_12 / scale_denom_zoom_level_14 tile_x = (gt[0] - x0_NZTM2000) / (blocksize * ovr1_res) assert tile_x == pytest.approx(round(tile_x)) tile_y = (y0_NZTM2000 - gt[3]) / (blocksize * ovr1_res) assert tile_y == pytest.approx(round(tile_y)) assert ds.GetMetadata("TILING_SCHEME") == { "NAME": "NZTM2000", "ZOOM_LEVEL": "14", "ALIGNED_LEVELS": "2", } ds = None gdal.GetDriverByName("GTiff").Delete(filename) ############################################################################### # Test SPARSE_OK=YES def test_cog_sparse(): filename = "/vsimem/cog.tif" src_ds = gdal.GetDriverByName("MEM").Create("", 512, 512) src_ds.GetRasterBand(1).Fill(255) src_ds.WriteRaster(0, 0, 256, 256, "\x00" * 256 * 256) src_ds.WriteRaster(256, 256, 128, 128, "\x00" * 128 * 128) src_ds.BuildOverviews("NEAREST", [2]) gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["BLOCKSIZE=128", "SPARSE_OK=YES", "COMPRESS=LZW"] ) _check_cog(filename) with gdaltest.config_option("GTIFF_HAS_OPTIMIZED_READ_MULTI_RANGE", "YES"): ds = gdal.Open(filename) assert ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is None assert ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_1_0", "TIFF") is None assert ( ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_2_0", "TIFF") is not None ) assert ( ds.GetRasterBand(1) .GetOverview(0) .GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is None ) assert ( ds.GetRasterBand(1) .GetOverview(0) .GetMetadataItem("BLOCK_OFFSET_1_0", "TIFF") is not None ) assert ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512) == src_ds.GetRasterBand( 1 ).ReadRaster(0, 0, 512, 512) assert ds.GetRasterBand(1).GetOverview(0).ReadRaster( 0, 0, 256, 256 ) == src_ds.GetRasterBand(1).GetOverview(0).ReadRaster(0, 0, 256, 256) if check_libtiff_internal_or_at_least(4, 0, 11): # This file is the same as the one generated above, except that we have, # with an hex editor, zeroify all entries of TileByteCounts except the # last tile of the main IFD, and for a tile when the next tile is sparse ds = gdal.Open("data/cog_sparse_strile_arrays_zeroified_when_possible.tif") assert ds.GetRasterBand(1).ReadRaster( 0, 0, 512, 512 ) == src_ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512) ds = None gdal.Unlink(filename) ############################################################################### # Test SPARSE_OK=YES with mask @pytest.mark.require_creation_option("COG", "JPEG") def test_cog_sparse_mask(): filename = "/vsimem/cog.tif" src_ds = gdal.GetDriverByName("MEM").Create("", 512, 512, 4) for i in range(4): src_ds.GetRasterBand(i + 1).SetColorInterpretation(gdal.GCI_RedBand + i) src_ds.GetRasterBand(i + 1).Fill(255) src_ds.GetRasterBand(i + 1).WriteRaster(0, 0, 256, 256, "\x00" * 256 * 256) src_ds.GetRasterBand(i + 1).WriteRaster(256, 256, 128, 128, "\x00" * 128 * 128) src_ds.BuildOverviews("NEAREST", [2]) gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=[ "BLOCKSIZE=128", "SPARSE_OK=YES", "COMPRESS=JPEG", "RESAMPLING=NEAREST", ], ) assert gdal.GetLastErrorMsg() == "" _check_cog(filename) with gdaltest.config_option("GTIFF_HAS_OPTIMIZED_READ_MULTI_RANGE", "YES"): ds = gdal.Open(filename) assert ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is None assert ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_1_0", "TIFF") is None assert ( ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_2_0", "TIFF") is not None ) assert ( ds.GetRasterBand(1) .GetMaskBand() .GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is None ) assert ( ds.GetRasterBand(1) .GetMaskBand() .GetMetadataItem("BLOCK_OFFSET_1_0", "TIFF") is None ) assert ( ds.GetRasterBand(1) .GetMaskBand() .GetMetadataItem("BLOCK_OFFSET_2_0", "TIFF") is not None ) assert ( ds.GetRasterBand(1) .GetOverview(0) .GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is None ) assert ( ds.GetRasterBand(1) .GetOverview(0) .GetMetadataItem("BLOCK_OFFSET_1_0", "TIFF") is not None ) assert ( ds.GetRasterBand(1) .GetOverview(0) .GetMaskBand() .GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is None ) assert ( ds.GetRasterBand(1) .GetOverview(0) .GetMaskBand() .GetMetadataItem("BLOCK_OFFSET_1_0", "TIFF") is not None ) assert ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512) == src_ds.GetRasterBand( 1 ).ReadRaster(0, 0, 512, 512) assert ds.GetRasterBand(1).GetMaskBand().ReadRaster( 0, 0, 512, 512 ) == src_ds.GetRasterBand(4).ReadRaster(0, 0, 512, 512) assert ds.GetRasterBand(1).GetOverview(0).ReadRaster( 0, 0, 256, 256 ) == src_ds.GetRasterBand(1).GetOverview(0).ReadRaster(0, 0, 256, 256) assert ds.GetRasterBand(1).GetOverview(0).GetMaskBand().ReadRaster( 0, 0, 256, 256 ) == src_ds.GetRasterBand(4).GetOverview(0).ReadRaster(0, 0, 256, 256) ds = None gdal.Unlink(filename) ############################################################################### # Test SPARSE_OK=YES with imagery at 0 and mask at 255 @pytest.mark.require_creation_option("COG", "JPEG") def test_cog_sparse_imagery_0_mask_255(): filename = "/vsimem/cog.tif" src_ds = gdal.GetDriverByName("MEM").Create("", 512, 512, 4) for i in range(4): src_ds.GetRasterBand(i + 1).SetColorInterpretation(gdal.GCI_RedBand + i) src_ds.GetRasterBand(i + 1).Fill(0 if i < 3 else 255) src_ds.BuildOverviews("NEAREST", [2]) gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["BLOCKSIZE=128", "SPARSE_OK=YES", "COMPRESS=JPEG"] ) _check_cog(filename) with gdaltest.config_option("GTIFF_HAS_OPTIMIZED_READ_MULTI_RANGE", "YES"): ds = gdal.Open(filename) assert ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is None assert ( ds.GetRasterBand(1) .GetMaskBand() .GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is not None ) assert ( ds.GetRasterBand(1) .GetOverview(0) .GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is None ) assert ( ds.GetRasterBand(1) .GetOverview(0) .GetMaskBand() .GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is not None ) assert ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512) == src_ds.GetRasterBand( 1 ).ReadRaster(0, 0, 512, 512) assert ds.GetRasterBand(1).GetMaskBand().ReadRaster( 0, 0, 512, 512 ) == src_ds.GetRasterBand(4).ReadRaster(0, 0, 512, 512) assert ds.GetRasterBand(1).GetOverview(0).ReadRaster( 0, 0, 256, 256 ) == src_ds.GetRasterBand(1).GetOverview(0).ReadRaster(0, 0, 256, 256) assert ds.GetRasterBand(1).GetOverview(0).GetMaskBand().ReadRaster( 0, 0, 256, 256 ) == src_ds.GetRasterBand(4).GetOverview(0).ReadRaster(0, 0, 256, 256) ds = None gdal.Unlink(filename) ############################################################################### # Test SPARSE_OK=YES with imagery at 0 or 255 and mask at 255 @pytest.mark.require_creation_option("COG", "JPEG") def test_cog_sparse_imagery_0_or_255_mask_255(): filename = "/vsimem/cog.tif" src_ds = gdal.GetDriverByName("MEM").Create("", 512, 512, 4) for i in range(4): src_ds.GetRasterBand(i + 1).SetColorInterpretation(gdal.GCI_RedBand + i) for i in range(3): src_ds.GetRasterBand(i + 1).Fill(255) src_ds.GetRasterBand(i + 1).WriteRaster(0, 0, 256, 256, "\x00" * 256 * 256) src_ds.GetRasterBand(i + 1).WriteRaster(256, 256, 128, 128, "\x00" * 128 * 128) src_ds.GetRasterBand(4).Fill(255) src_ds.BuildOverviews("NEAREST", [2]) gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=[ "BLOCKSIZE=128", "SPARSE_OK=YES", "COMPRESS=JPEG", "RESAMPLING=NEAREST", ], ) _check_cog(filename) with gdaltest.config_option("GTIFF_HAS_OPTIMIZED_READ_MULTI_RANGE", "YES"): ds = gdal.Open(filename) assert ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is None assert ( ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_2_0", "TIFF") is not None ) assert ( ds.GetRasterBand(1) .GetMaskBand() .GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is not None ) assert ( ds.GetRasterBand(1) .GetOverview(0) .GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is None ) assert ( ds.GetRasterBand(1) .GetOverview(0) .GetMaskBand() .GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is not None ) assert ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512) == src_ds.GetRasterBand( 1 ).ReadRaster(0, 0, 512, 512) assert ds.GetRasterBand(1).GetMaskBand().ReadRaster( 0, 0, 512, 512 ) == src_ds.GetRasterBand(4).ReadRaster(0, 0, 512, 512) assert ds.GetRasterBand(1).GetOverview(0).ReadRaster( 0, 0, 256, 256 ) == src_ds.GetRasterBand(1).GetOverview(0).ReadRaster(0, 0, 256, 256) assert ds.GetRasterBand(1).GetOverview(0).GetMaskBand().ReadRaster( 0, 0, 256, 256 ) == src_ds.GetRasterBand(4).GetOverview(0).ReadRaster(0, 0, 256, 256) ds = None gdal.Unlink(filename) ############################################################################### # Test SPARSE_OK=YES with imagery and mask at 0 @pytest.mark.require_creation_option("COG", "JPEG") def test_cog_sparse_imagery_mask_0(): filename = "/vsimem/cog.tif" src_ds = gdal.GetDriverByName("MEM").Create("", 512, 512, 4) for i in range(4): src_ds.GetRasterBand(i + 1).SetColorInterpretation(gdal.GCI_RedBand + i) src_ds.GetRasterBand(i + 1).Fill(0) src_ds.BuildOverviews("NEAREST", [2]) gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["BLOCKSIZE=128", "SPARSE_OK=YES", "COMPRESS=JPEG"] ) _check_cog(filename) with gdaltest.config_option("GTIFF_HAS_OPTIMIZED_READ_MULTI_RANGE", "YES"): ds = gdal.Open(filename) assert ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is None assert ( ds.GetRasterBand(1) .GetMaskBand() .GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is None ) assert ( ds.GetRasterBand(1) .GetOverview(0) .GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is None ) assert ( ds.GetRasterBand(1) .GetOverview(0) .GetMaskBand() .GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is None ) assert ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512) == src_ds.GetRasterBand( 1 ).ReadRaster(0, 0, 512, 512) assert ds.GetRasterBand(1).GetMaskBand().ReadRaster( 0, 0, 512, 512 ) == src_ds.GetRasterBand(4).ReadRaster(0, 0, 512, 512) assert ds.GetRasterBand(1).GetOverview(0).ReadRaster( 0, 0, 256, 256 ) == src_ds.GetRasterBand(1).GetOverview(0).ReadRaster(0, 0, 256, 256) assert ds.GetRasterBand(1).GetOverview(0).GetMaskBand().ReadRaster( 0, 0, 256, 256 ) == src_ds.GetRasterBand(4).GetOverview(0).ReadRaster(0, 0, 256, 256) ds = None gdal.Unlink(filename) ############################################################################### # Test ZOOM_LEVEL_STRATEGY option @pytest.mark.parametrize( "zoom_level_strategy,expected_gt", [ ( "AUTO", ( -13110479.09147343, 76.43702828517416, 0.0, 4030983.1236470547, 0.0, -76.43702828517416, ), ), ( "LOWER", ( -13110479.09147343, 76.43702828517416, 0.0, 4030983.1236470547, 0.0, -76.43702828517416, ), ), ( "UPPER", ( -13100695.151852928, 38.21851414258708, 0.0, 4021199.1840265524, 0.0, -38.21851414258708, ), ), ], ) def test_cog_zoom_level_strategy(zoom_level_strategy, expected_gt): filename = "/vsimem/test_cog_zoom_level_strategy.tif" src_ds = gdal.Open("data/byte.tif") ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=[ "TILING_SCHEME=GoogleMapsCompatible", "ZOOM_LEVEL_STRATEGY=" + zoom_level_strategy, ], ) gt = ds.GetGeoTransform() assert gt == pytest.approx(expected_gt, rel=1e-10) # Test that the zoom level strategy applied on input data already on a # zoom level doesn't lead to selecting another zoom level filename2 = "/vsimem/test_cog_zoom_level_strategy_2.tif" src_ds = gdal.Open("data/byte.tif") ds2 = gdal.GetDriverByName("COG").CreateCopy( filename2, ds, options=[ "TILING_SCHEME=GoogleMapsCompatible", "ZOOM_LEVEL_STRATEGY=" + zoom_level_strategy, ], ) gt = ds2.GetGeoTransform() assert gt == pytest.approx(expected_gt, rel=1e-10) ds2 = None gdal.Unlink(filename2) ds = None gdal.Unlink(filename) ############################################################################### # Test ZOOM_LEVEL option def test_cog_zoom_level(): filename = "/vsimem/test_cog_zoom_level.tif" src_ds = gdal.Open("data/byte.tif") with gdaltest.error_handler(): assert ( gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["TILING_SCHEME=GoogleMapsCompatible", "ZOOM_LEVEL=-1"], ) is None ) assert ( gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["TILING_SCHEME=GoogleMapsCompatible", "ZOOM_LEVEL=31"], ) is None ) ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["TILING_SCHEME=GoogleMapsCompatible", "ZOOM_LEVEL=12"], ) gt = ds.GetGeoTransform() expected_gt = ( -13100695.151852928, 38.21851414258813, 0.0, 4021199.1840265524, 0.0, -38.21851414258813, ) assert gt == pytest.approx(expected_gt, rel=1e-10) ds = None gdal.Unlink(filename) ############################################################################### def test_cog_resampling_options(): filename = "/vsimem/test_cog_resampling_options.tif" src_ds = gdal.Open("data/byte.tif") ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["TILING_SCHEME=GoogleMapsCompatible", "WARP_RESAMPLING=NEAREST"], ) cs1 = ds.GetRasterBand(1).Checksum() ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["TILING_SCHEME=GoogleMapsCompatible", "WARP_RESAMPLING=CUBIC"], ) cs2 = ds.GetRasterBand(1).Checksum() ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=[ "TILING_SCHEME=GoogleMapsCompatible", "RESAMPLING=NEAREST", "WARP_RESAMPLING=CUBIC", ], ) cs3 = ds.GetRasterBand(1).Checksum() assert cs1 != cs2 assert cs2 == cs3 src_ds = gdal.Translate("", "data/byte.tif", options="-of MEM -outsize 129 0") ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["BLOCKSIZE=128", "OVERVIEW_RESAMPLING=NEAREST"] ) cs1 = ds.GetRasterBand(1).GetOverview(0).Checksum() ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["BLOCKSIZE=128", "OVERVIEW_RESAMPLING=BILINEAR"] ) cs2 = ds.GetRasterBand(1).GetOverview(0).Checksum() ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["BLOCKSIZE=128", "RESAMPLING=NEAREST", "OVERVIEW_RESAMPLING=BILINEAR"], ) cs3 = ds.GetRasterBand(1).GetOverview(0).Checksum() assert cs1 != cs2 assert cs2 == cs3 ds = None gdal.Unlink(filename) ############################################################################### def test_cog_invalid_warp_resampling(): filename = "/vsimem/test_cog_invalid_warp_resampling.tif" src_ds = gdal.Open("data/byte.tif") with gdaltest.error_handler(): assert ( gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["TILING_SCHEME=GoogleMapsCompatible", "RESAMPLING=INVALID"], ) is None ) gdal.Unlink(filename) ############################################################################### def test_cog_overview_size(): src_ds = gdal.GetDriverByName("MEM").Create("", 20480 // 4, 40960 // 4) src_ds.SetGeoTransform([1723840, 7 * 4, 0, 5555840, 0, -7 * 4]) srs = osr.SpatialReference() srs.ImportFromEPSG(2193) src_ds.SetProjection(srs.ExportToWkt()) filename = "/vsimem/test_cog_overview_size.tif" ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=[ "TILING_SCHEME=NZTM2000", "ALIGNED_LEVELS=4", "OVERVIEW_RESAMPLING=NONE", ], ) assert (ds.RasterXSize, ds.RasterYSize) == (20480 // 4, 40960 // 4) ovr_size = [ ( ds.GetRasterBand(1).GetOverview(i).XSize, ds.GetRasterBand(1).GetOverview(i).YSize, ) for i in range(ds.GetRasterBand(1).GetOverviewCount()) ] assert ovr_size == [(2048, 4096), (1024, 2048), (512, 1024), (256, 512), (128, 256)] gdal.Unlink(filename) ############################################################################### # Test bugfix for https://github.com/OSGeo/gdal/issues/2946 def test_cog_float32_color_table(): src_ds = gdal.GetDriverByName("MEM").Create("", 1024, 1024, 1, gdal.GDT_Float32) src_ds.GetRasterBand(1).Fill(1.0) ct = gdal.ColorTable() src_ds.GetRasterBand(1).SetColorTable(ct) filename = "/vsimem/test_cog_float32_color_table.tif" # Silence warning about color table not being copied with gdaltest.error_handler(): ds = gdal.GetDriverByName("COG").CreateCopy(filename, src_ds) # segfault assert ds assert ds.GetRasterBand(1).GetColorTable() is None assert struct.unpack("f", ds.ReadRaster(0, 0, 1, 1))[0] == 1.0 assert ( struct.unpack("f", ds.GetRasterBand(1).GetOverview(0).ReadRaster(0, 0, 1, 1))[0] == 1.0 ) gdal.Unlink(filename) ############################################################################### # Test copy XMP def test_cog_copy_xmp(): filename = "/vsimem/cog_xmp.tif" src_ds = gdal.Open("../gdrivers/data/gtiff/byte_with_xmp.tif") ds = gdal.GetDriverByName("COG").CreateCopy(filename, src_ds) assert ds ds = None ds = gdal.Open(filename) xmp = ds.GetMetadata("xml:XMP") ds = None assert "W5M0MpCehiHzreSzNTczkc9d" in xmp[0], "Wrong input file without XMP" _check_cog(filename) gdal.Unlink(filename) ############################################################################### # Test creating COG from a source dataset that has overview with 'odd' sizes # and a mask without overview def test_cog_odd_overview_size_and_msk(): filename = "/vsimem/test_cog_odd_overview_size_and_msk.tif" src_ds = gdal.GetDriverByName("MEM").Create("", 511, 511) src_ds.BuildOverviews("NEAR", [2]) src_ds.CreateMaskBand(gdal.GMF_PER_DATASET) ds = gdal.GetDriverByName("COG").CreateCopy( filename, src_ds, options=["BLOCKSIZE=256"] ) assert ds assert ds.GetRasterBand(1).GetOverview(0).XSize == 256 assert ds.GetRasterBand(1).GetMaskBand().GetOverview(0).XSize == 256 ds = None gdal.Unlink(filename) ############################################################################### # Test turning on lossy WEBP compression if OVERVIEW_QUALITY < 100 specified @pytest.mark.require_creation_option("COG", "WEBP") @pytest.mark.require_driver("WEBP") def test_cog_webp_overview_turn_on_lossy_if_webp_level(): tmpfilename = "/vsimem/test_cog_webp_overview_turn_on_lossy_if_webp_level.tif" gdal.Translate( tmpfilename, "../gdrivers/data/small_world.tif", options="-of COG -outsize 513 0 -co COMPRESS=WEBP -co QUALITY=100 -co OVERVIEW_QUALITY=75", ) ds = gdal.Open(tmpfilename) assert ( ds.GetMetadataItem("COMPRESSION_REVERSIBILITY", "IMAGE_STRUCTURE") == "LOSSLESS" ) assert ( ds.GetRasterBand(1) .GetOverview(0) .GetDataset() .GetMetadataItem("COMPRESSION_REVERSIBILITY", "IMAGE_STRUCTURE") == "LOSSY" ) ds = None gdal.Unlink(tmpfilename) ############################################################################### # Test lossless WEBP compression @pytest.mark.require_creation_option("COG", "WEBP") @pytest.mark.require_driver("WEBP") def test_cog_webp_lossless_webp(): tmpfilename = "/vsimem/test_cog_webp_lossless_webp.tif" src_ds = gdal.Open("../gdrivers/data/small_world.tif") gdal.ErrorReset() gdal.Translate( tmpfilename, src_ds, options="-of COG -co COMPRESS=WEBP -co QUALITY=100", ) assert gdal.GetLastErrorMsg() == "" ds = gdal.Open(tmpfilename) assert ( ds.GetMetadataItem("COMPRESSION_REVERSIBILITY", "IMAGE_STRUCTURE") == "LOSSLESS" ) assert ds.GetRasterBand(1).Checksum() == src_ds.GetRasterBand(1).Checksum() ds = None gdal.Unlink(tmpfilename) ############################################################################### # Test OVERVIEW_COUNT option @pytest.mark.parametrize( "options,expected_count", [ ("", 1), ("-co OVERVIEW_COUNT=1", 1), ("-co OVERVIEW_COUNT=2", 2), ("-co OVERVIEW_COUNT=10", 8), ("-co TILING_SCHEME=GoogleMapsCompatible -co OVERVIEW_COUNT=1", 1), ("-co TILING_SCHEME=GoogleMapsCompatible -co OVERVIEW_COUNT=2", 2), ("-co TILING_SCHEME=GoogleMapsCompatible -co OVERVIEW_COUNT=10", 8), ], ) def test_cog_overview_count(options, expected_count): tmpfilename = "/vsimem/test_cog_overview_count.tif" with gdaltest.error_handler(): gdal.Translate( tmpfilename, "../gdrivers/data/small_world.tif", options="-of COG -co BLOCKSIZE=256 " + options, ) ds = gdal.Open(tmpfilename) assert ds.GetRasterBand(1).GetOverviewCount() == expected_count ds = None gdal.Unlink(tmpfilename) ############################################################################### # Test OVERVIEW_COUNT option with dataset with existing overviews def test_cog_overview_count_existing(): tmpfilename = "/vsimem/test_cog_overview_count_existing.tif" src_ds = gdal.GetDriverByName("MEM").Create("", 512, 512) src_ds.GetRasterBand(1).Fill(255) src_ds.BuildOverviews("NONE", [2, 4, 8, 16]) gdal.Translate(tmpfilename, src_ds, options="-of COG -co OVERVIEW_COUNT=3") ds = gdal.Open(tmpfilename) assert ds.GetRasterBand(1).GetOverviewCount() == 3 assert ds.GetRasterBand(1).GetOverview(0).Checksum() == 0 ds = None gdal.Unlink(tmpfilename) ############################################################################### # Test JPEGXL compression with alpha @pytest.mark.require_creation_option("COG", "JXL") def test_cog_write_jpegxl_alpha(): src_ds = gdal.Open("data/stefan_full_rgba.tif") filename = "/vsimem/test_tiff_write_jpegxl_alpha_distance_zero.tif" gdal.GetDriverByName("GTiff").CreateCopy( filename, src_ds, options=[ "COMPRESS=JXL", "JXL_LOSSLESS=NO", "TILED=YES", "BLOCKSIZE=512", "BLOCKYSIZE=512", ], ) ds = gdal.Open(filename) ref_checksum = [ds.GetRasterBand(i + 1).Checksum() for i in range(4)] ds = None gdal.Unlink(filename) drv = gdal.GetDriverByName("COG") drv.CreateCopy( filename, src_ds, options=["COMPRESS=JXL", "JXL_LOSSLESS=NO"], ) ds = gdal.Open(filename) assert [ds.GetRasterBand(i + 1).Checksum() for i in range(4)] == ref_checksum ds = None gdal.Unlink(filename) ############################################################################### # Test JXL_ALPHA_DISTANCE creation option @pytest.mark.require_creation_option( "COG", "JXL_ALPHA_DISTANCE" ) # "libjxl > 0.8.1 required" def test_cog_write_jpegxl_alpha_distance_zero(): drv = gdal.GetDriverByName("COG") src_ds = gdal.Open("data/stefan_full_rgba.tif") filename = "/vsimem/test_tiff_write_jpegxl_alpha_distance_zero.tif" drv.CreateCopy( filename, src_ds, options=["COMPRESS=JXL", "JXL_LOSSLESS=NO", "JXL_ALPHA_DISTANCE=0"], ) ds = gdal.Open(filename) assert float(ds.GetMetadataItem("JXL_ALPHA_DISTANCE", "IMAGE_STRUCTURE")) == 0 assert ds.GetRasterBand(1).Checksum() != src_ds.GetRasterBand(1).Checksum() assert ds.GetRasterBand(4).Checksum() == src_ds.GetRasterBand(4).Checksum() ds = None gdal.Unlink(filename) ############################################################################### # Test NBITS creation option def test_cog_NBITS(): src_ds = gdal.GetDriverByName("MEM").Create("", 1, 1) drv = gdal.GetDriverByName("COG") filename = "/vsimem/test_cog_NBITS.tif" drv.CreateCopy( filename, src_ds, options=["NBITS=7"], ) ds = gdal.Open(filename) assert ds.GetRasterBand(1).GetMetadataItem("NBITS", "IMAGE_STRUCTURE") == "7" ds = None gdal.Unlink(filename)