From 6de85e3690dab9377205adb2678aa4555a342b3c Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 28 Jan 2020 16:16:22 +0100 Subject: Add grid_tools/ --- grid_tools/cloud_optimize_gtiff.py | 390 +++++++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100755 grid_tools/cloud_optimize_gtiff.py (limited to 'grid_tools/cloud_optimize_gtiff.py') diff --git a/grid_tools/cloud_optimize_gtiff.py b/grid_tools/cloud_optimize_gtiff.py new file mode 100755 index 0000000..b8ef92c --- /dev/null +++ b/grid_tools/cloud_optimize_gtiff.py @@ -0,0 +1,390 @@ +#!/usr/bin/env python +############################################################################### +# $Id$ +# +# Project: PROJ +# Purpose: Cloud optimize a GeoTIFF file +# 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. +############################################################################### + +from osgeo import gdal + +import argparse +import os +import struct + + +def get_args(): + parser = argparse.ArgumentParser( + description='Convert a GeoTIFF file into a cloud optimized one.') + parser.add_argument('source', + help='Source GeoTIFF file') + parser.add_argument('dest', + help='Destination GeoTIFF file') + + return parser.parse_args() + + +def generate_optimized_file(srcfilename, destfilename): + + TIFF_BYTE = 1 # 8-bit unsigned integer + TIFF_ASCII = 2 # 8-bit bytes w/ last byte null + TIFF_SHORT = 3 # 16-bit unsigned integer + TIFF_LONG = 4 # 32-bit unsigned integer + TIFF_RATIONAL = 5 # 64-bit unsigned fraction + TIFF_SBYTE = 6 # !8-bit signed integer + TIFF_UNDEFINED = 7 # !8-bit untyped data + TIFF_SSHORT = 8 # !16-bit signed integer + TIFF_SLONG = 9 # !32-bit signed integer + TIFF_SRATIONAL = 10 # !64-bit signed fraction + TIFF_FLOAT = 11 # !32-bit IEEE floating point + TIFF_DOUBLE = 12 # !64-bit IEEE floating point + TIFF_IFD = 13 # %32-bit unsigned integer (offset) + TIFF_LONG8 = 16 # BigTIFF 64-bit unsigned integer + TIFF_SLONG8 = 17 # BigTIFF 64-bit signed integer + TIFF_IFD8 = 18 # BigTIFF 64-bit unsigned integer (offset) + + TIFFTAG_STRIPOFFSETS = 273 + TIFFTAG_SAMPLESPERPIXEL = 277 + TIFFTAG_STRIPBYTECOUNTS = 279 + TIFFTAG_PLANARCONFIG = 284 + TIFFTAG_TILEOFFSETS = 324 + TIFFTAG_TILEBYTECOUNTS = 325 + + PLANARCONFIG_CONTIG = 1 + PLANARCONFIG_SEPARATE = 2 + + TIFFTAG_GDAL_METADATA = 42112 + + typesize = {} + typesize[TIFF_BYTE] = 1 + typesize[TIFF_ASCII] = 1 + typesize[TIFF_SHORT] = 2 + typesize[TIFF_LONG] = 4 + typesize[TIFF_RATIONAL] = 8 + typesize[TIFF_SBYTE] = 1 + typesize[TIFF_UNDEFINED] = 1 + typesize[TIFF_SSHORT] = 2 + typesize[TIFF_SLONG] = 4 + typesize[TIFF_SRATIONAL] = 8 + typesize[TIFF_FLOAT] = 4 + typesize[TIFF_DOUBLE] = 8 + typesize[TIFF_IFD] = 4 + typesize[TIFF_LONG8] = 8 + typesize[TIFF_SLONG8] = 8 + typesize[TIFF_IFD8] = 8 + + class OfflineTag: + def __init__(self, tagtype, nvalues, data, fileoffset_in_out_ifd): + self.tagtype = tagtype + self.nvalues = nvalues + self.data = data + self.fileoffset_in_out_ifd = fileoffset_in_out_ifd + + def unpack_array(self): + if type(self.data) == type((0,)): + return self.data + elif self.tagtype == TIFF_SHORT: + return struct.unpack('<' + ('H' * self.nvalues), self.data) + elif self.tagtype == TIFF_LONG: + return struct.unpack('<' + ('I' * self.nvalues), self.data) + else: + assert False + + class IFD: + def __init__(self, tagdict): + self.tagdict = tagdict + + ds = gdal.Open(srcfilename) + assert ds + first_band_to_put_at_end = None + if ds.GetMetadataItem('TYPE') == 'HORIZONTAL_OFFSET' and \ + ds.RasterCount >= 4 and \ + ds.GetRasterBand(1).GetDescription() == 'latitude_offset' and \ + ds.GetRasterBand(2).GetDescription() == 'longitude_offset' and \ + ds.GetRasterBand(3).GetDescription() == 'latitude_offset_accuracy' and \ + ds.GetRasterBand(4).GetDescription() == 'longitude_offset_accuracy': + first_band_to_put_at_end = 3 + elif ds.GetMetadataItem('TYPE') == 'VELOCITY' and \ + ds.RasterCount >= 6 and \ + ds.GetRasterBand(1).GetDescription() == 'east_velocity' and \ + ds.GetRasterBand(2).GetDescription() == 'north_velocity' and \ + ds.GetRasterBand(3).GetDescription() == 'up_velocity' and \ + ds.GetRasterBand(4).GetDescription() == 'east_velocity_accuracy' and \ + ds.GetRasterBand(5).GetDescription() == 'north_velocity_accuracy' and \ + ds.GetRasterBand(6).GetDescription() == 'up_velocity_accuracy': + first_band_to_put_at_end = 4 + del ds + + in_f = open(srcfilename, 'rb') + signature = in_f.read(4) + assert signature == b'\x49\x49\x2A\x00' + next_ifd_offset = struct.unpack('= first_band_to_put_at_end: + for ifd in ifds: + if ifd.nbands < first_band_to_put_at_end: + continue + + for i in range(ifd.num_striles_per_band): + for iband in range(first_band_to_put_at_end-1, ifd.nbands): + idx_strile = ifd.num_striles_per_band * iband + i + in_f.seek(ifd.strile_offset_in[idx_strile]) + data = in_f.read(ifd.strile_length_in[idx_strile]) + ifd.strile_offset_out[idx_strile] = out_f.tell() + out_f.write(data) + + # Write strile offset arrays + for ifd in ifds: + tagdict = ifd.tagdict + + if TIFFTAG_STRIPOFFSETS in tagdict: + tagtype = tagdict[TIFFTAG_STRIPOFFSETS].tagtype + else: + tagtype = tagdict[TIFFTAG_TILEOFFSETS].tagtype + + if ifd.offset_out_offsets < 0: + assert ifd.offset_out_offsets != -1 + out_f.seek(-ifd.offset_out_offsets) + else: + out_f.seek(ifd.offset_out_offsets) + if tagtype == TIFF_SHORT: + for v in ifd.strile_offset_out: + assert v < 65536 + out_f.write(struct.pack('