EVOLUTION-MANAGER
Edit File: validate_cloud_optimized_geotiff.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # ***************************************************************************** # $Id: validate_cloud_optimized_geotiff.py 37254 2017-01-31 12:17:52Z rouault $ # # Project: GDAL # Purpose: Validate Cloud Optimized GeoTIFF file structure # Author: Even Rouault, <even dot rouault at spatialys dot com> # # ***************************************************************************** # Copyright (c) 2017, 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 sys from osgeo import gdal def Usage(): print('Usage: validate_cloud_optimized_geotiff.py [-q] test.tif') print('') return 1 class ValidateCloudOptimizedGeoTIFFException(Exception): pass def validate(filename, check_tiled=True): """ Return if the passed file is a (Geo)TIFF file with a cloud optimized compatible structure, otherwise raise a ValidateCloudOptimizedGeoTIFFException exception. """ if int(gdal.VersionInfo('VERSION_NUM')) < 2020000: raise ValidateCloudOptimizedGeoTIFFException( "GDAL 2.2 or above required") gdal.PushErrorHandler() ds = gdal.Open(filename) gdal.PopErrorHandler() if ds is None: raise ValidateCloudOptimizedGeoTIFFException( "Invalid file : %s" % gdal.GetLastErrorMsg()) if ds.GetDriver().ShortName != 'GTiff': raise ValidateCloudOptimizedGeoTIFFException( "The file is not a GeoTIFF") main_band = ds.GetRasterBand(1) ovr_count = main_band.GetOverviewCount() if filename + '.ovr' in ds.GetFileList(): raise ValidateCloudOptimizedGeoTIFFException( "Overviews should be internal") if check_tiled: block_size = main_band.GetBlockSize() if block_size[0] == main_band.XSize: raise ValidateCloudOptimizedGeoTIFFException( "Full resolution image is not tiled") if main_band.XSize >= 512 or main_band.YSize >= 512: if ovr_count == 0: raise ValidateCloudOptimizedGeoTIFFException( "The file should have overviews") ifd_offset = \ [int(main_band.GetMetadataItem('IFD_OFFSET', 'TIFF'))] if ifd_offset[0] != 8: raise ValidateCloudOptimizedGeoTIFFException( "The offset of the main IFD should be 8. It is %d instead" % ifd_offset[0]) for i in range(ovr_count): # Check that overviews are by descending sizes ovr_band = ds.GetRasterBand(1).GetOverview(i) if i == 0: if ovr_band.XSize > main_band.XSize or \ ovr_band.YSize > main_band.YSize: raise ValidateCloudOptimizedGeoTIFFException( "First overview has larger dimension than main band") else: prev_ovr_band = ds.GetRasterBand(1).GetOverview(i-1) if ovr_band.XSize > prev_ovr_band.XSize or \ ovr_band.YSize > prev_ovr_band.YSize: raise ValidateCloudOptimizedGeoTIFFException( "Overview of index %d has larger dimension than " "overview of index %d" % (i, i-1)) if check_tiled: block_size = ovr_band.GetBlockSize() if block_size[0] == ovr_band.XSize: raise ValidateCloudOptimizedGeoTIFFException( "Overview of index %d is not tiled" % i) # Check that the IFD of descending overviews are sorted by increasing # offsets ifd_offset.append(int(ovr_band.GetMetadataItem('IFD_OFFSET', 'TIFF'))) if ifd_offset[-1] < ifd_offset[-2]: if i == 0: raise ValidateCloudOptimizedGeoTIFFException( "The offset of the IFD for overview of index %d is %d, " "whereas it should be greater than the one of the main " "image, which is at byte %d" % (i, ifd_offset[-1], ifd_offset[-2])) else: raise ValidateCloudOptimizedGeoTIFFException( "The offset of the IFD for overview of index %d is %d, " "whereas it should be greater than the one of index %d, " "which is at byte %d" % (i, ifd_offset[-1], i-1, ifd_offset[-2])) # Check that the imagery starts by the smallest overview and ends with # the main resolution dataset data_offset = [int(main_band.GetMetadataItem('BLOCK_OFFSET_0_0', 'TIFF'))] for i in range(ovr_count): ovr_band = ds.GetRasterBand(1).GetOverview(i) data_offset.append(int( ovr_band.GetMetadataItem('BLOCK_OFFSET_0_0', 'TIFF'))) if data_offset[-1] < ifd_offset[-1]: if ovr_count > 0: raise ValidateCloudOptimizedGeoTIFFException( "The offset of the first block of the smallest overview " "should be after its IFD") else: raise ValidateCloudOptimizedGeoTIFFException( "The offset of the first block of the image should " "be after its IFD") for i in range(len(data_offset)-2, 0, -1): if data_offset[i] < data_offset[i+1]: raise ValidateCloudOptimizedGeoTIFFException( "The offset of the first block of overview of index %d should " "be after the one of the overview of index %d" % (i-1, i)) if len(data_offset) >= 2 and data_offset[0] < data_offset[1]: raise ValidateCloudOptimizedGeoTIFFException( "The offset of the first block of the main resolution image " "should be after the one of the overview of index %d" % (ovr_count - 1)) def main(): """ Returns 0 in case of success """ i = 1 filename = None quiet = False while i < len(sys.argv): if sys.argv[i] == '-q': quiet = True elif sys.argv[i][0] == '-': return Usage() elif filename is None: filename = sys.argv[i] else: return Usage() i = i + 1 if filename is None: return Usage() try: validate(filename) ret = 0 if not quiet: print('%s is a valid cloud optimized GeoTIFF' % filename) except ValidateCloudOptimizedGeoTIFFException as e: if not quiet: print('%s is NOT a valid cloud optimized GeoTIFF : %s' % (filename, str(e))) ret = 1 return ret if __name__ == '__main__': sys.exit(main())