446 lines
13 KiB
Python
446 lines
13 KiB
Python
#!/usr/bin/env pytest
|
|
# -*- coding: utf-8 -*-
|
|
###############################################################################
|
|
# $Id$
|
|
#
|
|
# Project: GDAL/OGR Test Suite
|
|
# Purpose: Library version of gdaltindex testing
|
|
# Author: Even Rouault <even dot rouault @ spatialys.com>
|
|
#
|
|
###############################################################################
|
|
# Copyright (c) 2023, Even Rouault <even dot rouault at spatialys.com>
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
###############################################################################
|
|
|
|
import os
|
|
|
|
import gdaltest
|
|
import pytest
|
|
|
|
from osgeo import gdal, ogr
|
|
|
|
###############################################################################
|
|
# Simple test
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def four_tiles(tmp_path_factory):
|
|
|
|
drv = gdal.GetDriverByName("GTiff")
|
|
wkt = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9108"]],AUTHORITY["EPSG","4326"]]'
|
|
|
|
dirname = tmp_path_factory.mktemp("test_gdaltindex")
|
|
fnames = [f"{dirname}/gdaltindex{i}.tif" for i in (1, 2, 3, 4)]
|
|
|
|
ds = drv.Create(fnames[0], 10, 10, 1)
|
|
ds.SetMetadataItem("foo", "bar")
|
|
ds.SetMetadataItem("TIFFTAG_DATETIME", "2023:12:20 16:10:00")
|
|
ds.SetProjection(wkt)
|
|
ds.SetGeoTransform([49, 0.1, 0, 2, 0, -0.1])
|
|
ds = None
|
|
|
|
ds = drv.Create(fnames[1], 10, 10, 1)
|
|
ds.SetProjection(wkt)
|
|
ds.SetGeoTransform([49, 0.1, 0, 3, 0, -0.1])
|
|
ds = None
|
|
|
|
ds = drv.Create(fnames[2], 10, 10, 1)
|
|
ds.SetProjection(wkt)
|
|
ds.SetGeoTransform([48, 0.1, 0, 2, 0, -0.1])
|
|
ds = None
|
|
|
|
ds = drv.Create(fnames[3], 10, 10, 1)
|
|
ds.SetProjection(wkt)
|
|
ds.SetGeoTransform([48, 0.1, 0, 3, 0, -0.1])
|
|
ds = None
|
|
|
|
return fnames
|
|
|
|
|
|
@pytest.fixture()
|
|
def four_tile_index(four_tiles, tmp_path):
|
|
|
|
gdal.TileIndex(f"{tmp_path}/tileindex.shp", [four_tiles[0], four_tiles[1]])
|
|
gdal.TileIndex(f"{tmp_path}/tileindex.shp", [four_tiles[2], four_tiles[3]])
|
|
return f"{tmp_path}/tileindex.shp"
|
|
|
|
|
|
def test_gdaltindex_lib_basic(four_tile_index):
|
|
|
|
ds = ogr.Open(four_tile_index)
|
|
assert ds.GetLayer(0).GetFeatureCount() == 4
|
|
|
|
tileindex_wkt = ds.GetLayer(0).GetSpatialRef().ExportToWkt()
|
|
assert "WGS_1984" in tileindex_wkt
|
|
|
|
expected_wkts = [
|
|
"POLYGON ((49 2,50 2,50 1,49 1,49 2))",
|
|
"POLYGON ((49 3,50 3,50 2,49 2,49 3))",
|
|
"POLYGON ((48 2,49 2,49 1,48 1,48 2))",
|
|
"POLYGON ((48 3,49 3,49 2,48 2,48 3))",
|
|
]
|
|
|
|
for i, feat in enumerate(ds.GetLayer(0)):
|
|
assert (
|
|
feat.GetGeometryRef().ExportToWkt() == expected_wkts[i]
|
|
), "i=%d, wkt=%s" % (i, feat.GetGeometryRef().ExportToWkt())
|
|
|
|
|
|
###############################################################################
|
|
# Try adding the same rasters again
|
|
|
|
|
|
def test_gdaltindex_lib_already_existing_rasters(four_tiles, four_tile_index, tmp_path):
|
|
class GdalErrorHandler:
|
|
def __init__(self):
|
|
self.warnings = []
|
|
|
|
def handler(self, err_level, err_no, err_msg):
|
|
if err_level == gdal.CE_Warning:
|
|
self.warnings.append(err_msg)
|
|
|
|
err_handler = GdalErrorHandler()
|
|
with gdaltest.error_handler(err_handler.handler):
|
|
ds = gdal.TileIndex(four_tile_index, four_tiles)
|
|
del ds
|
|
|
|
assert len(err_handler.warnings) == 4
|
|
assert (
|
|
"gdaltindex1.tif is already in tileindex. Skipping it."
|
|
in err_handler.warnings[0]
|
|
)
|
|
assert (
|
|
"gdaltindex2.tif is already in tileindex. Skipping it."
|
|
in err_handler.warnings[1]
|
|
)
|
|
assert (
|
|
"gdaltindex3.tif is already in tileindex. Skipping it."
|
|
in err_handler.warnings[2]
|
|
)
|
|
assert (
|
|
"gdaltindex4.tif is already in tileindex. Skipping it."
|
|
in err_handler.warnings[3]
|
|
)
|
|
|
|
ds = ogr.Open(four_tile_index)
|
|
assert ds.GetLayer(0).GetFeatureCount() == 4
|
|
|
|
|
|
###############################################################################
|
|
# Try adding a raster in another projection with -skip_different_projection
|
|
# 5th tile should NOT be inserted
|
|
|
|
|
|
def test_gdaltindex_skipDifferentProjection(tmp_path, four_tile_index):
|
|
|
|
drv = gdal.GetDriverByName("GTiff")
|
|
wkt = """GEOGCS["WGS 72",
|
|
DATUM["WGS_1972",
|
|
SPHEROID["WGS 72",6378135,298.26],
|
|
TOWGS84[0,0,4.5,0,0,0.554,0.2263]],
|
|
PRIMEM["Greenwich",0],
|
|
UNIT["degree",0.0174532925199433]]"""
|
|
|
|
ds = drv.Create(f"{tmp_path}/gdaltindex5.tif", 10, 10, 1)
|
|
ds.SetProjection(wkt)
|
|
ds.SetGeoTransform([47, 0.1, 0, 2, 0, -0.1])
|
|
ds = None
|
|
|
|
class GdalErrorHandler:
|
|
def __init__(self):
|
|
self.warning = None
|
|
|
|
def handler(self, err_level, err_no, err_msg):
|
|
if err_level == gdal.CE_Warning:
|
|
self.warning = err_msg
|
|
|
|
err_handler = GdalErrorHandler()
|
|
with gdaltest.error_handler(err_handler.handler):
|
|
gdal.TileIndex(
|
|
four_tile_index,
|
|
[f"{tmp_path}/gdaltindex5.tif"],
|
|
skipDifferentProjection=True,
|
|
)
|
|
assert (
|
|
"gdaltindex5.tif is not using the same projection system as other files in the tileindex"
|
|
in err_handler.warning
|
|
)
|
|
|
|
ds = ogr.Open(four_tile_index)
|
|
assert ds.GetLayer(0).GetFeatureCount() == 4
|
|
|
|
|
|
###############################################################################
|
|
# Try adding a raster in another projection with -t_srs
|
|
# 5th tile should be inserted, will not be if there is a srs transformation error
|
|
|
|
|
|
def test_gdaltindex_lib_outputSRS_writeAbsoluePath(tmp_path, four_tile_index):
|
|
|
|
drv = gdal.GetDriverByName("GTiff")
|
|
wkt = """GEOGCS["WGS 72",
|
|
DATUM["WGS_1972",
|
|
SPHEROID["WGS 72",6378135,298.26],
|
|
TOWGS84[0,0,4.5,0,0,0.554,0.2263]],
|
|
PRIMEM["Greenwich",0],
|
|
UNIT["degree",0.0174532925199433]]"""
|
|
|
|
ds = drv.Create(f"{tmp_path}/gdaltindex5.tif", 10, 10, 1)
|
|
ds.SetProjection(wkt)
|
|
ds.SetGeoTransform([47, 0.1, 0, 2, 0, -0.1])
|
|
ds = None
|
|
|
|
saved_dir = os.getcwd()
|
|
try:
|
|
os.chdir(tmp_path)
|
|
gdal.TileIndex(
|
|
four_tile_index,
|
|
["gdaltindex5.tif"],
|
|
outputSRS="EPSG:4326",
|
|
writeAbsolutePath=True,
|
|
)
|
|
finally:
|
|
os.chdir(saved_dir)
|
|
|
|
ds = ogr.Open(four_tile_index)
|
|
lyr = ds.GetLayer(0)
|
|
assert lyr.GetFeatureCount() == 5, (
|
|
"got %d features, expecting 5" % ds.GetLayer(0).GetFeatureCount()
|
|
)
|
|
filename = lyr.GetFeature(4).GetField("location")
|
|
# Check that path is absolute
|
|
assert filename.endswith("gdaltindex5.tif")
|
|
assert filename != "gdaltindex5.tif"
|
|
|
|
|
|
###############################################################################
|
|
# Test -f, -lyr_name
|
|
|
|
|
|
@pytest.mark.require_driver("MapInfo File")
|
|
def test_gdaltindex_lib_format_layerName(tmp_path, four_tiles):
|
|
|
|
index_mif = str(tmp_path / "test_gdaltindex6.mif")
|
|
|
|
gdal.TileIndex(
|
|
index_mif, [four_tiles[0]], format="MapInfo File", layerName="tileindex"
|
|
)
|
|
ds = ogr.Open(index_mif)
|
|
lyr = ds.GetLayer(0)
|
|
assert lyr.GetFeatureCount() == 1, (
|
|
"got %d features, expecting 1" % lyr.GetFeatureCount()
|
|
)
|
|
ds = None
|
|
|
|
|
|
###############################################################################
|
|
# Test -overwrite
|
|
|
|
|
|
def test_gdaltindex_lib_overwrite(tmp_path, four_tiles):
|
|
|
|
index_filename = str(tmp_path / "test_gdaltindex_lib_overwrite.shp")
|
|
|
|
gdal.TileIndex(index_filename, [four_tiles[0]])
|
|
gdal.TileIndex(index_filename, [four_tiles[1], four_tiles[2]], overwrite=True)
|
|
ds = ogr.Open(index_filename)
|
|
lyr = ds.GetLayer(0)
|
|
assert lyr.GetFeatureCount() == 2
|
|
|
|
|
|
###############################################################################
|
|
# Test GTI related options
|
|
|
|
|
|
@pytest.mark.require_driver("GPKG")
|
|
@pytest.mark.require_driver("GTI")
|
|
def test_gdaltindex_lib_gti_non_xml(tmp_path, four_tiles):
|
|
|
|
index_filename = str(tmp_path / "test_gdaltindex_lib_gti_non_xml.gti.gpkg")
|
|
|
|
gdal.TileIndex(
|
|
index_filename,
|
|
four_tiles,
|
|
layerName="tileindex",
|
|
xRes=60,
|
|
yRes=60,
|
|
outputBounds=[0, 1, 2, 3],
|
|
bandCount=1,
|
|
noData=0,
|
|
colorInterpretation="gray",
|
|
mask=True,
|
|
metadataOptions={"foo": "bar"},
|
|
layerCreationOptions=["FID=my_fid"],
|
|
)
|
|
|
|
ds = ogr.Open(index_filename)
|
|
lyr = ds.GetLayer(0)
|
|
assert lyr.GetFIDColumn() == "my_fid"
|
|
assert lyr.GetMetadataItem("RESX") == "60"
|
|
assert lyr.GetMetadataItem("RESY") == "60"
|
|
assert lyr.GetMetadataItem("MINX") == "0"
|
|
assert lyr.GetMetadataItem("MINY") == "1"
|
|
assert lyr.GetMetadataItem("MAXX") == "2"
|
|
assert lyr.GetMetadataItem("MAXY") == "3"
|
|
assert lyr.GetMetadataItem("BAND_COUNT") == "1"
|
|
assert lyr.GetMetadataItem("NODATA") == "0"
|
|
assert lyr.GetMetadataItem("COLOR_INTERPRETATION") == "gray"
|
|
assert lyr.GetMetadataItem("MASK_BAND") == "YES"
|
|
assert lyr.GetMetadataItem("foo") == "bar"
|
|
del ds
|
|
|
|
ds = gdal.Open(index_filename)
|
|
assert ds.GetGeoTransform() == (0.0, 60.0, 0.0, 3.0, 0.0, -60.0)
|
|
assert ds.RasterCount == 1
|
|
assert ds.GetRasterBand(1).GetNoDataValue() == 0
|
|
assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_GrayIndex
|
|
assert ds.GetRasterBand(1).GetMaskFlags() == gdal.GMF_PER_DATASET
|
|
del ds
|
|
|
|
|
|
###############################################################################
|
|
# Test GTI related options
|
|
|
|
|
|
@pytest.mark.require_driver("GPKG")
|
|
@pytest.mark.require_driver("GTI")
|
|
def test_gdaltindex_lib_gti_xml(tmp_path, four_tiles):
|
|
|
|
index_filename = str(tmp_path / "test_gdaltindex_lib_gti_non_xml.gti.gpkg")
|
|
gti_filename = str(tmp_path / "test_gdaltindex_lib_gti_non_xml.gti")
|
|
|
|
gdal.TileIndex(
|
|
index_filename,
|
|
four_tiles,
|
|
layerName="tileindex",
|
|
gtiFilename=gti_filename,
|
|
xRes=60,
|
|
yRes=60,
|
|
outputBounds=[0, 1, 2, 3],
|
|
noData=[0],
|
|
colorInterpretation=["gray"],
|
|
mask=True,
|
|
)
|
|
|
|
xml = open(gti_filename, "rb").read().decode("UTF-8")
|
|
assert "test_gdaltindex_lib_gti_non_xml.gti.gpkg</IndexDataset>" in xml
|
|
assert "<IndexLayer>tileindex</IndexLayer>" in xml
|
|
assert "<LocationField>location</LocationField>" in xml
|
|
assert "<ResX>60</ResX>" in xml
|
|
assert "<ResY>60</ResY>" in xml
|
|
assert "<MinX>0</MinX>" in xml
|
|
assert "<MinY>1</MinY>" in xml
|
|
assert "<MaxX>2</MaxX>" in xml
|
|
assert "<MaxY>3</MaxY>" in xml
|
|
assert (
|
|
"""<Band band="1">\n <NoDataValue>0</NoDataValue>\n <ColorInterp>gray</ColorInterp>\n </Band>"""
|
|
in xml
|
|
)
|
|
assert "<MaskBand>true</MaskBand>" in xml
|
|
|
|
ds = gdal.Open(gti_filename)
|
|
assert ds.GetGeoTransform() == (0.0, 60.0, 0.0, 3.0, 0.0, -60.0)
|
|
assert ds.RasterCount == 1
|
|
assert ds.GetRasterBand(1).GetNoDataValue() == 0
|
|
assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_GrayIndex
|
|
assert ds.GetRasterBand(1).GetMaskFlags() == gdal.GMF_PER_DATASET
|
|
|
|
|
|
###############################################################################
|
|
# Test directory exploration and filtering
|
|
|
|
|
|
def test_gdaltindex_lib_directory(tmp_path, four_tiles):
|
|
|
|
index_filename = str(tmp_path / "test_gdaltindex_lib_overwrite.shp")
|
|
|
|
gdal.TileIndex(
|
|
index_filename,
|
|
[os.path.dirname(four_tiles[0])],
|
|
recursive=True,
|
|
filenameFilter="*.?if",
|
|
minPixelSize=0,
|
|
maxPixelSize=1,
|
|
)
|
|
|
|
ds = ogr.Open(index_filename)
|
|
lyr = ds.GetLayer(0)
|
|
assert lyr.GetFeatureCount() == 4
|
|
del ds
|
|
|
|
with gdal.quiet_errors():
|
|
# triggers warnings: "file XXXX has 0.010000 as pixel size (< 10.000000). Skipping"
|
|
gdal.TileIndex(
|
|
index_filename,
|
|
[os.path.dirname(four_tiles[0])],
|
|
minPixelSize=10,
|
|
overwrite=True,
|
|
)
|
|
|
|
ds = ogr.Open(index_filename)
|
|
lyr = ds.GetLayer(0)
|
|
assert lyr.GetFeatureCount() == 0
|
|
del ds
|
|
|
|
with gdal.quiet_errors():
|
|
gdal.TileIndex(
|
|
index_filename,
|
|
[os.path.dirname(four_tiles[0])],
|
|
maxPixelSize=1e-3,
|
|
overwrite=True,
|
|
)
|
|
|
|
ds = ogr.Open(index_filename)
|
|
lyr = ds.GetLayer(0)
|
|
assert lyr.GetFeatureCount() == 0
|
|
del ds
|
|
|
|
with pytest.raises(Exception, match="Cannot find any tile"):
|
|
gdal.TileIndex(
|
|
index_filename,
|
|
[os.path.dirname(four_tiles[0])],
|
|
filenameFilter="*.xyz",
|
|
overwrite=True,
|
|
)
|
|
|
|
|
|
###############################################################################
|
|
# Test -fetchMD
|
|
|
|
|
|
@pytest.mark.require_driver("GPKG")
|
|
def test_gdaltindex_lib_fetch_md(tmp_path, four_tiles):
|
|
|
|
index_filename = str(tmp_path / "test_gdaltindex_lib_fetch_md.gpkg")
|
|
|
|
gdal.TileIndex(
|
|
index_filename,
|
|
four_tiles[0],
|
|
fetchMD=[
|
|
("foo", "foo_field", "String"),
|
|
("{PIXEL_SIZE}", "pixel_size", "Real"),
|
|
("TIFFTAG_DATETIME", "dt", "DateTime"),
|
|
],
|
|
)
|
|
|
|
ds = ogr.Open(index_filename)
|
|
lyr = ds.GetLayer(0)
|
|
f = lyr.GetNextFeature()
|
|
assert f["foo_field"] == "bar"
|
|
assert f["dt"] == "2023/12/20 16:10:00"
|
|
assert f["pixel_size"] == pytest.approx(0.01)
|
|
del ds
|
|
|
|
gdal.TileIndex(
|
|
index_filename,
|
|
four_tiles[0],
|
|
fetchMD=("foo", "foo_field", "String"),
|
|
overwrite=True,
|
|
)
|
|
|
|
ds = ogr.Open(index_filename)
|
|
lyr = ds.GetLayer(0)
|
|
f = lyr.GetNextFeature()
|
|
assert f["foo_field"] == "bar"
|