Skip to content

Commit 8f2ec72

Browse files
committed
Add 'gdal driver gti create'
1 parent e5c5039 commit 8f2ec72

9 files changed

+686
-14
lines changed

apps/gdalalg_raster_index.cpp

+34-10
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,42 @@
3030
GDALRasterIndexAlgorithm::GDALRasterIndexAlgorithm()
3131
: GDALVectorOutputAbstractAlgorithm(NAME, DESCRIPTION, HELP_URL)
3232
{
33-
3433
AddProgressArg();
3534
AddInputDatasetArg(&m_inputDatasets, GDAL_OF_RASTER)
3635
.SetAutoOpenDataset(false);
3736
GDALVectorOutputAbstractAlgorithm::AddAllOutputArgs();
3837

38+
AddCommonOptions();
39+
40+
AddArg("source-crs-field-name", 0,
41+
_("Name of the field to store the SRS of each dataset"),
42+
&m_sourceCrsName)
43+
.SetMinCharCount(1);
44+
AddArg("source-crs-format", 0,
45+
_("Format in which the SRS of each dataset must be written"),
46+
&m_sourceCrsFormat)
47+
.SetDefault(m_sourceCrsFormat)
48+
.SetChoices("auto", "WKT", "EPSG", "PROJ");
49+
}
50+
51+
/************************************************************************/
52+
/* GDALRasterIndexAlgorithm::GDALRasterIndexAlgorithm() */
53+
/************************************************************************/
54+
55+
GDALRasterIndexAlgorithm::GDALRasterIndexAlgorithm(
56+
const std::string &name, const std::string &description,
57+
const std::string &helpURL)
58+
: GDALVectorOutputAbstractAlgorithm(name, description, helpURL)
59+
{
60+
}
61+
62+
/************************************************************************/
63+
/* GDALRasterIndexAlgorithm::AddCommonOptions() */
64+
/************************************************************************/
65+
66+
void GDALRasterIndexAlgorithm::AddCommonOptions()
67+
{
68+
3969
AddArg("recursive", 0,
4070
_("Whether input directories should be explored recursively."),
4171
&m_recursive);
@@ -64,15 +94,6 @@ GDALRasterIndexAlgorithm::GDALRasterIndexAlgorithm()
6494
AddArg("dst-crs", 0, _("Destination CRS"), &m_crs)
6595
.SetIsCRSArg()
6696
.AddHiddenAlias("t_srs");
67-
AddArg("source-crs-field-name", 0,
68-
_("Name of the field to store the SRS of each dataset"),
69-
&m_sourceCrsName)
70-
.SetMinCharCount(1);
71-
AddArg("source-crs-format", 0,
72-
_("Format in which the SRS of each dataset must be written"),
73-
&m_sourceCrsFormat)
74-
.SetDefault(m_sourceCrsFormat)
75-
.SetChoices("auto", "WKT", "EPSG", "PROJ");
7697

7798
{
7899
auto &arg =
@@ -170,6 +191,9 @@ bool GDALRasterIndexAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
170191
aosOptions.push_back(s);
171192
}
172193

194+
if (!AddExtraOptions(aosOptions))
195+
return false;
196+
173197
std::unique_ptr<GDALTileIndexOptions, decltype(&GDALTileIndexOptionsFree)>
174198
options(GDALTileIndexOptionsNew(aosOptions.List(), nullptr),
175199
GDALTileIndexOptionsFree);

apps/gdalalg_raster_index.h

+18-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
/* GDALRasterIndexAlgorithm */
2222
/************************************************************************/
2323

24-
class GDALRasterIndexAlgorithm final : public GDALVectorOutputAbstractAlgorithm
24+
class CPL_DLL GDALRasterIndexAlgorithm /* non final */
25+
: public GDALVectorOutputAbstractAlgorithm
2526
{
2627
public:
2728
static constexpr const char *NAME = "index";
@@ -31,10 +32,25 @@ class GDALRasterIndexAlgorithm final : public GDALVectorOutputAbstractAlgorithm
3132

3233
GDALRasterIndexAlgorithm();
3334

35+
GDALRasterIndexAlgorithm(const std::string &name,
36+
const std::string &description,
37+
const std::string &helpURL);
38+
39+
protected:
40+
void AddCommonOptions();
41+
42+
// Virtual method that may be overridden by derived classes to add options
43+
// to GDALTileIndex()
44+
virtual bool AddExtraOptions([[maybe_unused]] CPLStringList &aosOptions)
45+
{
46+
return true;
47+
}
48+
49+
std::vector<GDALArgDatasetValue> m_inputDatasets{};
50+
3451
private:
3552
bool RunImpl(GDALProgressFunc pfnProgress, void *pProgressData) override;
3653

37-
std::vector<GDALArgDatasetValue> m_inputDatasets{};
3854
bool m_recursive = false;
3955
std::vector<std::string> m_filenameFilter{};
4056
double m_minPixelSize = 0;

apps/gdalalg_vector_output_abstract.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
/* GDALVectorOutputAbstractAlgorithm */
2323
/************************************************************************/
2424

25-
class GDALVectorOutputAbstractAlgorithm /* non-final*/ : public GDALAlgorithm
25+
class CPL_DLL
26+
GDALVectorOutputAbstractAlgorithm /* non-final*/ : public GDALAlgorithm
2627
{
2728
protected:
2829
GDALVectorOutputAbstractAlgorithm(const std::string &name,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env pytest
2+
# -*- coding: utf-8 -*-
3+
###############################################################################
4+
# Project: GDAL/OGR Test Suite
5+
# Purpose: 'gdal raster inde' testing
6+
# Author: Even Rouault <even dot rouault @ spatialys.com>
7+
#
8+
###############################################################################
9+
# Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
10+
#
11+
# SPDX-License-Identifier: MIT
12+
###############################################################################
13+
14+
import pytest
15+
16+
from osgeo import gdal, ogr
17+
18+
pytestmark = pytest.mark.require_driver("GTI")
19+
20+
21+
def get_alg():
22+
return gdal.GetGlobalAlgorithmRegistry()["driver"]["gti"]["create"]
23+
24+
25+
def test_gdalalg_driver_gti_create_xml_filename(tmp_vsimem):
26+
27+
xml_filename = tmp_vsimem / "out.xml"
28+
29+
alg = get_alg()
30+
alg["input"] = "../gcore/data/byte.tif"
31+
alg["output"] = ""
32+
alg["output-format"] = "MEM"
33+
alg["layer"] = "my_layer"
34+
alg["xml-filename"] = xml_filename
35+
alg["fetch-metadata"] = "AREA_OR_POINT,area_or_point,String"
36+
assert alg.Run()
37+
38+
ds = alg["output"].GetDataset()
39+
lyr = ds.GetLayerByName("my_layer")
40+
assert lyr.GetSpatialRef().GetAuthorityCode(None) == "26711"
41+
assert lyr.GetLayerDefn().GetFieldCount() == 2
42+
assert lyr.GetLayerDefn().GetFieldDefn(0).GetName() == "location"
43+
assert lyr.GetLayerDefn().GetFieldDefn(0).GetType() == ogr.OFTString
44+
assert lyr.GetLayerDefn().GetFieldDefn(1).GetName() == "area_or_point"
45+
assert lyr.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTString
46+
f = lyr.GetNextFeature()
47+
assert f["location"] == "../gcore/data/byte.tif"
48+
assert f["area_or_point"] == "Area"
49+
assert (
50+
f.GetGeometryRef().ExportToWkt()
51+
== "POLYGON ((440720 3751320,441920 3751320,441920 3750120,440720 3750120,440720 3751320))"
52+
)
53+
assert lyr.GetMetadata_Dict() == {}
54+
55+
with gdal.VSIFile(xml_filename, "rb") as f:
56+
assert (
57+
f.read()
58+
== b"<GDALTileIndexDataset>\n <IndexDataset></IndexDataset>\n <IndexLayer>my_layer</IndexLayer>\n <LocationField>location</LocationField>\n</GDALTileIndexDataset>\n"
59+
)
60+
61+
62+
def test_gdalalg_driver_gti_create():
63+
64+
alg = get_alg()
65+
alg["input"] = "../gcore/data/byte.tif"
66+
alg["output"] = ""
67+
alg["output-format"] = "MEM"
68+
alg["layer"] = "my_layer"
69+
alg["resolution"] = [10, 11]
70+
alg["datatype"] = "UInt16"
71+
alg["bbox"] = [1, 2, 3, 4]
72+
alg["band-count"] = 2
73+
alg["nodata"] = [5, 6]
74+
alg["color-interpretation"] = ["red", "green"]
75+
alg["mask"] = True
76+
assert alg.Run()
77+
78+
ds = alg["output"].GetDataset()
79+
lyr = ds.GetLayerByName("my_layer")
80+
assert lyr.GetMetadata_Dict() == {
81+
"BAND_COUNT": "2",
82+
"COLOR_INTERPRETATION": "red,green",
83+
"DATA_TYPE": "UInt16",
84+
"LOCATION_FIELD": "location",
85+
"MASK_BAND": "YES",
86+
"MAXX": "3",
87+
"MAXY": "4",
88+
"MINX": "1",
89+
"MINY": "2",
90+
"NODATA": "5,6",
91+
"RESX": "10",
92+
"RESY": "11",
93+
}
94+
95+
96+
def test_gdalalg_driver_gti_create_wrong_nodata():
97+
98+
alg = get_alg()
99+
alg["input"] = "../gcore/data/byte.tif"
100+
alg["output"] = ""
101+
alg["output-format"] = "MEM"
102+
alg["layer"] = "my_layer"
103+
alg["band-count"] = 3
104+
alg["nodata"] = [5, 6]
105+
with pytest.raises(
106+
Exception, match="2 nodata values whereas one or 3 were expected"
107+
):
108+
alg.Run()
109+
110+
111+
def test_gdalalg_driver_gti_create_wrong_color_interpretation():
112+
113+
alg = get_alg()
114+
alg["input"] = "../gcore/data/byte.tif"
115+
alg["output"] = ""
116+
alg["output-format"] = "MEM"
117+
alg["layer"] = "my_layer"
118+
alg["band-count"] = 3
119+
alg["color-interpretation"] = ["red", "green"]
120+
with pytest.raises(
121+
Exception, match="2 color interpretations whereas one or 3 were expected"
122+
):
123+
alg.Run()
124+
125+
126+
def test_gdalalg_driver_gti_create_wrong_fetch_metadata():
127+
128+
alg = get_alg()
129+
with pytest.raises(
130+
Exception,
131+
match="'foo' is not of the form <gdal-metadata-name>,<field-name>,<field-type>",
132+
):
133+
alg["fetch-metadata"] = "foo"
134+
with pytest.raises(
135+
Exception,
136+
match="'foo,bar,baz' has an invalid field type 'baz'. It should be one of 'String', 'Integer', 'Integer64', 'Real', 'Date', 'DateTime'",
137+
):
138+
alg["fetch-metadata"] = "foo,bar,baz"

doc/source/drivers/raster/gti.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,8 @@ their syntax and semantics.
384384
How to build a GTI compatible index ?
385385
----------------------------------------
386386

387-
The :ref:`gdaltindex` program may be used to generate both a vector tile index,
387+
The :ref:`gdaltindex` program, or starting with GDA 3.11,
388+
:ref:`gdal_driver_gti_create_subcommand`, may be used to generate both a vector tile index,
388389
and optionally a wrapping .gti XML file.
389390

390391
A GTI compatible index may also be created by any programmatic means, provided

0 commit comments

Comments
 (0)