#!/usr/bin/env pytest # -*- coding: utf-8 -*- ############################################################################### # $Id$ # # Project: GDAL/OGR Test Suite # Purpose: gdal2tiles.py testing # Author: Even Rouault # ############################################################################### # Copyright (c) 2015, 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 glob import os import os.path import shutil import sys import pytest import test_py_scripts # noqa # pylint: disable=E0401 from osgeo import gdal # noqa from osgeo_utils.gdalcompare import compare_db pytestmark = pytest.mark.skipif( test_py_scripts.get_py_script("gdal2tiles") is None, reason="gdal2tiles not available", ) @pytest.fixture() def script_path(): return test_py_scripts.get_py_script("gdal2tiles") def _verify_raster_band_checksums(filename, expected_cs=[]): ds = gdal.Open(filename) if ds is None: pytest.fail('cannot open output file "%s"' % filename) got_cs = [ds.GetRasterBand(i + 1).Checksum() for i in range(ds.RasterCount)] if isinstance(expected_cs[0], list): assert got_cs in expected_cs else: assert got_cs == expected_cs ds = None @pytest.mark.require_driver("PNG") def test_gdal2tiles_py_simple(script_path): shutil.copy( test_py_scripts.get_data_path("gdrivers") + "small_world.tif", "tmp/out_gdal2tiles_smallworld.tif", ) os.chdir("tmp") test_py_scripts.run_py_script( script_path, "gdal2tiles", "-q out_gdal2tiles_smallworld.tif" ) os.chdir("..") os.unlink("tmp/out_gdal2tiles_smallworld.tif") _verify_raster_band_checksums( "tmp/out_gdal2tiles_smallworld/0/0/0.png", expected_cs=[31420, 32522, 16314, 17849], ) for filename in [ "googlemaps.html", "leaflet.html", "openlayers.html", "tilemapresource.xml", ]: assert os.path.exists("tmp/out_gdal2tiles_smallworld/" + filename), ( "%s missing" % filename ) @pytest.mark.require_driver("PNG") def test_gdal2tiles_py_zoom_option(script_path): shutil.rmtree("tmp/out_gdal2tiles_smallworld", ignore_errors=True) # Because of multiprocessing, run as external process, to avoid issues with # Ubuntu 12.04 and socket.setdefaulttimeout() # as well as on Windows that doesn't manage to fork test_py_scripts.run_py_script_as_external_script( script_path, "gdal2tiles", "-q --force-kml --processes=2 -z 0-1 " + test_py_scripts.get_data_path("gdrivers") + "small_world.tif tmp/out_gdal2tiles_smallworld", ) _verify_raster_band_checksums( "tmp/out_gdal2tiles_smallworld/1/0/0.png", expected_cs=[24063, 23632, 14707, 17849], ) assert not os.path.exists("tmp/out_gdal2tiles_smallworld/0/0/0.png.aux.xml") assert not os.path.exists("tmp/out_gdal2tiles_smallworld/1/0/0.png.aux.xml") if gdal.GetDriverByName("KMLSuperOverlay") is None: pytest.skip("KMLSuperOverlay driver missing") ds = gdal.Open("tmp/out_gdal2tiles_smallworld/doc.kml") assert ds is not None, "did not get kml" @pytest.mark.require_driver("PNG") def test_gdal2tiles_py_resampling_option(script_path): resampling_list = [ "average", "near", "bilinear", "cubic", "cubicspline", "lanczos", "antialias", "mode", "max", "min", "med", "q1", "q3", ] try: import numpy from PIL import Image import osgeo.gdal_array as gdalarray del Image, numpy, gdalarray except ImportError: # 'antialias' resampling is not available resampling_list.remove("antialias") out_dir = "tmp/out_gdal2tiles_smallworld" for resample in resampling_list: shutil.rmtree(out_dir, ignore_errors=True) test_py_scripts.run_py_script_as_external_script( script_path, "gdal2tiles", "-q --resampling={0} {1} {2}".format( resample, test_py_scripts.get_data_path("gdrivers") + "small_world.tif", out_dir, ), ) # very basic check ds = gdal.Open("tmp/out_gdal2tiles_smallworld/0/0/0.png") if ds is None: pytest.fail("resample option {0!r} failed".format(resample)) ds = None shutil.rmtree(out_dir, ignore_errors=True) @pytest.mark.require_driver("PNG") def test_gdal2tiles_py_xyz(script_path): try: shutil.copy( test_py_scripts.get_data_path("gdrivers") + "small_world.tif", "tmp/out_gdal2tiles_smallworld_xyz.tif", ) os.chdir("tmp") ret = test_py_scripts.run_py_script( script_path, "gdal2tiles", "-q --xyz --zoom=0-1 out_gdal2tiles_smallworld_xyz.tif", ) os.chdir("..") assert "ERROR ret code" not in ret os.unlink("tmp/out_gdal2tiles_smallworld_xyz.tif") _verify_raster_band_checksums( "tmp/out_gdal2tiles_smallworld_xyz/0/0/0.png", expected_cs=[31747, 33381, 18447, 17849], ) _verify_raster_band_checksums( "tmp/out_gdal2tiles_smallworld_xyz/1/0/0.png", expected_cs=[15445, 16942, 13681, 17849], ) for filename in ["googlemaps.html", "leaflet.html", "openlayers.html"]: assert os.path.exists("tmp/out_gdal2tiles_smallworld_xyz/" + filename), ( "%s missing" % filename ) assert not os.path.exists( "tmp/out_gdal2tiles_smallworld_xyz/tilemapresource.xml" ) finally: shutil.rmtree("tmp/out_gdal2tiles_smallworld_xyz") @pytest.mark.require_driver("PNG") def test_gdal2tiles_py_invalid_srs(script_path): """ Case where the input image is not georeferenced, i.e. it's missing the SRS info, and no --s_srs option is provided. The script should fail validation and terminate. """ shutil.copy( test_py_scripts.get_data_path("gdrivers") + "test_nosrs.vrt", "tmp/out_gdal2tiles_test_nosrs.vrt", ) shutil.copy(test_py_scripts.get_data_path("gdrivers") + "byte.tif", "tmp/byte.tif") os.chdir("tmp") # try running on image with missing SRS ret = test_py_scripts.run_py_script( script_path, "gdal2tiles", "-q --zoom=0-1 out_gdal2tiles_test_nosrs.vrt" ) # this time pass the spatial reference system via cli options ret2 = test_py_scripts.run_py_script( script_path, "gdal2tiles", "-q --zoom=0-1 --s_srs EPSG:4326 out_gdal2tiles_test_nosrs.vrt", ) os.chdir("..") os.unlink("tmp/out_gdal2tiles_test_nosrs.vrt") os.unlink("tmp/byte.tif") shutil.rmtree("tmp/out_gdal2tiles_test_nosrs") assert "ERROR ret code = 2" in ret assert "ERROR ret code" not in ret2 def test_does_not_error_when_source_bounds_close_to_tiles_bound(script_path): """ Case where the border coordinate of the input file is inside a tile T but the first pixel is actually assigned to the tile next to T (nearest neighbour), meaning that when the query is done to get the content of T, nothing is returned from the raster. """ in_files = [ "./data/test_bounds_close_to_tile_bounds_x.vrt", "./data/test_bounds_close_to_tile_bounds_y.vrt", ] out_folder = "tmp/out_gdal2tiles_bounds_approx" try: shutil.rmtree(out_folder) except Exception: pass try: for in_file in in_files: test_py_scripts.run_py_script( script_path, "gdal2tiles", "-q -z 21-21 %s %s" % (in_file, out_folder) ) except TypeError: pytest.fail( "Case of tile not getting any data not handled properly " "(tiles at the border of the image)" ) def test_does_not_error_when_nothing_to_put_in_the_low_zoom_tile(script_path): """ Case when the highest zoom level asked is actually too low for any pixel of the raster to be selected """ in_file = "./data/test_bounds_close_to_tile_bounds_x.vrt" out_folder = "tmp/out_gdal2tiles_bounds_approx" try: shutil.rmtree(out_folder) except OSError: pass try: test_py_scripts.run_py_script( script_path, "gdal2tiles", "-q -z 10 %s %s" % (in_file, out_folder) ) except TypeError: pytest.fail( "Case of low level tile not getting any data not handled properly " "(tile at a zoom level too low)" ) @pytest.mark.require_driver("PNG") def test_handle_utf8_filename(script_path): input_file = "data/test_utf8_漢字.vrt" out_folder = "tmp/utf8_test" try: shutil.rmtree(out_folder) except OSError: pass args = f"-q -z 21 {input_file} {out_folder}" test_py_scripts.run_py_script(script_path, "gdal2tiles", args) openlayers_html = open( os.path.join(out_folder, "openlayers.html"), "rt", encoding="utf-8" ).read() assert "test_utf8_漢字.vrt" in openlayers_html try: shutil.rmtree(out_folder) except OSError: pass def test_gdal2tiles_py_cleanup(): lst = ["tmp/out_gdal2tiles_smallworld", "tmp/out_gdal2tiles_bounds_approx"] for filename in lst: try: shutil.rmtree(filename) except Exception: pass @pytest.mark.require_driver("PNG") def test_exclude_transparent_tiles(script_path): output_folder = "tmp/test_exclude_transparent_tiles" os.makedirs(output_folder) try: test_py_scripts.run_py_script_as_external_script( script_path, "gdal2tiles", "-x -z 14-16 data/test_gdal2tiles_exclude_transparent.tif %s" % output_folder, ) # First row totally transparent - no tiles tiles_folder = os.path.join(output_folder, "15", "21898") dir_files = os.listdir(tiles_folder) assert not dir_files, "Generated empty tiles for row 21898: %s" % dir_files # Second row - only 2 non-transparent tiles tiles_folder = os.path.join(output_folder, "15", "21899") dir_files = sorted(os.listdir(tiles_folder)) assert ["22704.png", "22705.png"] == dir_files, ( "Generated empty tiles for row 21899: %s" % dir_files ) # Third row - only 1 non-transparent tile tiles_folder = os.path.join(output_folder, "15", "21900") dir_files = os.listdir(tiles_folder) assert ["22705.png"] == dir_files, ( "Generated empty tiles for row 21900: %s" % dir_files ) finally: shutil.rmtree(output_folder) @pytest.mark.require_driver("PNG") def test_gdal2tiles_py_profile_raster(script_path): shutil.rmtree("tmp/out_gdal2tiles_smallworld", ignore_errors=True) test_py_scripts.run_py_script_as_external_script( script_path, "gdal2tiles", "-q -p raster -z 0-1 " + test_py_scripts.get_data_path("gdrivers") + "small_world.tif tmp/out_gdal2tiles_smallworld", ) try: _verify_raster_band_checksums( "tmp/out_gdal2tiles_smallworld/0/0/0.png", expected_cs=[10125, 10802, 27343, 48852], ) _verify_raster_band_checksums( "tmp/out_gdal2tiles_smallworld/1/0/0.png", expected_cs=[62125, 59756, 43894, 38539], ) if gdal.GetDriverByName("KMLSuperOverlay") is None: pytest.skip("KMLSuperOverlay driver missing") if sys.platform != "win32": # For some reason, the checksums on the kml file on Windows are the ones of the below png _verify_raster_band_checksums( "tmp/out_gdal2tiles_smallworld/0/0/0.kml", expected_cs=[29839, 34244, 42706, 64319], ) finally: shutil.rmtree("tmp/out_gdal2tiles_smallworld", ignore_errors=True) @pytest.mark.require_driver("PNG") def test_gdal2tiles_py_profile_raster_oversample(script_path): shutil.rmtree("tmp/out_gdal2tiles_smallworld", ignore_errors=True) test_py_scripts.run_py_script_as_external_script( script_path, "gdal2tiles", "-q -p raster -z 0-2 " + test_py_scripts.get_data_path("gdrivers") + "small_world.tif tmp/out_gdal2tiles_smallworld", ) assert os.path.exists("tmp/out_gdal2tiles_smallworld/2/0/0.png") assert os.path.exists("tmp/out_gdal2tiles_smallworld/2/3/1.png") _verify_raster_band_checksums( "tmp/out_gdal2tiles_smallworld/2/0/0.png", expected_cs=[[51434, 55441, 63427, 17849], [51193, 55320, 63324, 17849]], # icc ) _verify_raster_band_checksums( "tmp/out_gdal2tiles_smallworld/2/3/1.png", expected_cs=[[44685, 45074, 50871, 56563], [44643, 45116, 50863, 56563]], # icc ) shutil.rmtree("tmp/out_gdal2tiles_smallworld", ignore_errors=True) @pytest.mark.require_driver("PNG") def test_gdal2tiles_py_profile_raster_xyz(script_path): shutil.rmtree("tmp/out_gdal2tiles_smallworld", ignore_errors=True) test_py_scripts.run_py_script_as_external_script( script_path, "gdal2tiles", "-q -p raster --xyz -z 0-1 " + test_py_scripts.get_data_path("gdrivers") + "small_world.tif tmp/out_gdal2tiles_smallworld", ) try: _verify_raster_band_checksums( "tmp/out_gdal2tiles_smallworld/0/0/0.png", expected_cs=[11468, 10719, 27582, 48827], ) _verify_raster_band_checksums( "tmp/out_gdal2tiles_smallworld/1/0/0.png", expected_cs=[60550, 62572, 46338, 38489], ) if gdal.GetDriverByName("KMLSuperOverlay") is None: pytest.skip("KMLSuperOverlay driver missing") if sys.platform != "win32": # For some reason, the checksums on the kml file on Windows are the ones of the below png _verify_raster_band_checksums( "tmp/out_gdal2tiles_smallworld/0/0/0.kml", expected_cs=[27644, 31968, 38564, 64301], ) finally: shutil.rmtree("tmp/out_gdal2tiles_smallworld", ignore_errors=True) @pytest.mark.require_driver("PNG") def test_gdal2tiles_py_profile_geodetic_tmscompatible_xyz(script_path): shutil.rmtree("tmp/out_gdal2tiles_smallworld", ignore_errors=True) test_py_scripts.run_py_script_as_external_script( script_path, "gdal2tiles", "-q -p geodetic --tmscompatible --xyz -z 0-1 " + test_py_scripts.get_data_path("gdrivers") + "small_world.tif tmp/out_gdal2tiles_smallworld", ) try: _verify_raster_band_checksums( "tmp/out_gdal2tiles_smallworld/0/0/0.png", expected_cs=[8560, 8031, 7209, 17849], ) _verify_raster_band_checksums( "tmp/out_gdal2tiles_smallworld/1/0/0.png", expected_cs=[2799, 3468, 8686, 17849], ) if gdal.GetDriverByName("KMLSuperOverlay") is None: pytest.skip("KMLSuperOverlay driver missing") if sys.platform != "win32": # For some reason, the checksums on the kml file on Windows are the ones of the below png _verify_raster_band_checksums( "tmp/out_gdal2tiles_smallworld/0/0/0.kml", expected_cs=[12361, 18212, 21827, 5934], ) finally: shutil.rmtree("tmp/out_gdal2tiles_smallworld", ignore_errors=True) @pytest.mark.require_driver("PNG") def test_gdal2tiles_py_mapml(script_path): shutil.rmtree("tmp/out_gdal2tiles_mapml", ignore_errors=True) gdal.Translate( "tmp/byte_APS.tif", test_py_scripts.get_data_path("gcore") + "byte.tif", options="-a_srs EPSG:5936 -a_ullr 0 40 40 0", ) test_py_scripts.run_py_script_as_external_script( script_path, "gdal2tiles", '-q -p APSTILE -w mapml -z 16-18 --url "https://foo" tmp/byte_APS.tif tmp/out_gdal2tiles_mapml', ) mapml = open("tmp/out_gdal2tiles_mapml/mapml.mapml", "rb").read().decode("utf-8") # print(mapml) assert '' in mapml assert '' in mapml assert ( '' in mapml ) assert ( '' in mapml ) assert ( '' in mapml ) shutil.rmtree("tmp/out_gdal2tiles_mapml", ignore_errors=True) gdal.Unlink("tmp/byte_APS.tif") def _convert_png_to_webp(frm, to, quality): src_ds = gdal.Open(frm) driver = gdal.GetDriverByName("WEBP") driver.CreateCopy(to, src_ds, 0, options=["LOSSLESS=True"]) def _run_webp_test(script_path, resampling): shutil.rmtree("tmp/out_gdal2tiles_smallworld_png", ignore_errors=True) shutil.rmtree("tmp/out_gdal2tiles_smallworld_webp_from_png", ignore_errors=True) shutil.rmtree("tmp/out_gdal2tiles_smallworld_webp", ignore_errors=True) base_args = "-q --processes=2 -z 0-1 -r " + resampling + " " test_py_scripts.run_py_script_as_external_script( script_path, "gdal2tiles", base_args + test_py_scripts.get_data_path("gdrivers") + "small_world.tif tmp/out_gdal2tiles_smallworld_png", ) quality = 50 test_py_scripts.run_py_script_as_external_script( script_path, "gdal2tiles", base_args + "--tiledriver=WEBP --webp-lossless " + test_py_scripts.get_data_path("gdrivers") + "small_world.tif tmp/out_gdal2tiles_smallworld_webp", ) to_convert = glob.glob("tmp/out_gdal2tiles_smallworld_png/*/*/*.png") for filename in to_convert: to_filename = filename.replace( "tmp/out_gdal2tiles_smallworld_png/", "tmp/out_gdal2tiles_smallworld_webp_from_png/", ) to_filename = to_filename.replace(".png", ".webp") to_folder = os.path.dirname(to_filename) os.makedirs(to_folder, exist_ok=True) _convert_png_to_webp(filename, to_filename, quality) to_compare = glob.glob("tmp/out_gdal2tiles_smallworld_webp_from_png/*/*/*.webp") for filename in to_compare: webp_filename = filename.replace( "tmp/out_gdal2tiles_smallworld_webp_from_png/", "tmp/out_gdal2tiles_smallworld_webp/", ) diff_found = compare_db(gdal.Open(webp_filename), gdal.Open(filename)) assert not diff_found, (resampling, filename) shutil.rmtree("tmp/out_gdal2tiles_smallworld_png", ignore_errors=True) shutil.rmtree("tmp/out_gdal2tiles_smallworld_webp_from_png", ignore_errors=True) shutil.rmtree("tmp/out_gdal2tiles_smallworld_webp", ignore_errors=True) @pytest.mark.require_driver("WEBP") def test_gdal2tiles_py_webp(script_path): _run_webp_test(script_path, "average") try: import numpy from PIL import Image import osgeo.gdal_array as gdalarray del Image, numpy, gdalarray pil_available = True except ImportError: pil_available = False if pil_available: _run_webp_test(script_path, "antialias")