Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_raster_index.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "raster index" subcommand
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_raster_index.h"
14
15
#include "cpl_conv.h"
16
#include "gdal_priv.h"
17
#include "gdal_utils_priv.h"
18
#include "ogrsf_frmts.h"
19
20
//! @cond Doxygen_Suppress
21
22
#ifndef _
23
0
#define _(x) (x)
24
#endif
25
26
/************************************************************************/
27
/*         GDALRasterIndexAlgorithm::GDALRasterIndexAlgorithm()         */
28
/************************************************************************/
29
30
GDALRasterIndexAlgorithm::GDALRasterIndexAlgorithm()
31
0
    : GDALVectorOutputAbstractAlgorithm(NAME, DESCRIPTION, HELP_URL)
32
0
{
33
0
    AddProgressArg();
34
0
    AddInputDatasetArg(&m_inputDatasets, GDAL_OF_RASTER)
35
0
        .SetAutoOpenDataset(false)
36
0
        .SetDatasetInputFlags(GADV_NAME);
37
0
    GDALVectorOutputAbstractAlgorithm::AddAllOutputArgs();
38
39
0
    AddCommonOptions();
40
41
0
    AddArg("source-crs-field-name", 0,
42
0
           _("Name of the field to store the CRS of each dataset"),
43
0
           &m_sourceCrsName)
44
0
        .SetMinCharCount(1);
45
0
    AddArg("source-crs-format", 0,
46
0
           _("Format in which the CRS of each dataset must be written"),
47
0
           &m_sourceCrsFormat)
48
0
        .SetMinCharCount(1)
49
0
        .SetDefault(m_sourceCrsFormat)
50
0
        .SetChoices("auto", "WKT", "EPSG", "PROJ");
51
0
}
52
53
/************************************************************************/
54
/*         GDALRasterIndexAlgorithm::GDALRasterIndexAlgorithm()         */
55
/************************************************************************/
56
57
GDALRasterIndexAlgorithm::GDALRasterIndexAlgorithm(
58
    const std::string &name, const std::string &description,
59
    const std::string &helpURL)
60
0
    : GDALVectorOutputAbstractAlgorithm(name, description, helpURL)
61
0
{
62
0
}
63
64
/************************************************************************/
65
/*             GDALRasterIndexAlgorithm::AddCommonOptions()             */
66
/************************************************************************/
67
68
void GDALRasterIndexAlgorithm::AddCommonOptions()
69
0
{
70
0
    AddArg("recursive", 0,
71
0
           _("Whether input directories should be explored recursively."),
72
0
           &m_recursive);
73
0
    AddArg("filename-filter", 0,
74
0
           _("Pattern that the filenames in input directories should follow "
75
0
             "('*' and '?' wildcard)"),
76
0
           &m_filenameFilter);
77
0
    AddArg("min-pixel-size", 0,
78
0
           _("Minimum pixel size in term of geospatial extent per pixel "
79
0
             "(resolution) that a raster should have to be selected."),
80
0
           &m_minPixelSize)
81
0
        .SetMinValueExcluded(0);
82
0
    AddArg("max-pixel-size", 0,
83
0
           _("Maximum pixel size in term of geospatial extent per pixel "
84
0
             "(resolution) that a raster should have to be selected."),
85
0
           &m_maxPixelSize)
86
0
        .SetMinValueExcluded(0);
87
0
    AddArg("location-name", 0, _("Name of the field with the raster path"),
88
0
           &m_locationName)
89
0
        .SetDefault(m_locationName)
90
0
        .SetMinCharCount(1);
91
0
    AddAbsolutePathArg(
92
0
        &m_writeAbsolutePaths,
93
0
        _("Whether the path to the input datasets should be stored as an "
94
0
          "absolute path"));
95
0
    AddArg("dst-crs", 0, _("Destination CRS"), &m_crs)
96
0
        .SetIsCRSArg()
97
0
        .AddHiddenAlias("t_srs");
98
99
0
    {
100
0
        auto &arg =
101
0
            AddArg("metadata", 0, _("Add dataset metadata item"), &m_metadata)
102
0
                .SetMetaVar("<KEY>=<VALUE>")
103
0
                .SetPackedValuesAllowed(false);
104
0
        arg.AddValidationAction([this, &arg]()
105
0
                                { return ParseAndValidateKeyValue(arg); });
106
0
        arg.AddHiddenAlias("mo");
107
0
    }
108
109
0
    AddArg("skip-errors", 0, _("Skip errors related to input datasets"),
110
0
           &m_skipErrors);
111
0
    AddArg("profile", 0, _("Profile of output dataset"), &m_profile)
112
0
        .SetDefault(m_profile)
113
0
        .SetChoices(PROFILE_NONE, PROFILE_STAC_GEOPARQUET);
114
0
    AddArg("base-url", 0, _("Base URL for STAC-GeoParquet href"), &m_baseUrl);
115
0
    AddArg("id-method", 0, _("How to derive STAC-GeoParquet 'id'"), &m_idMethod)
116
0
        .SetDefault(m_idMethod)
117
0
        .SetChoices(ID_METHOD_FILENAME, ID_METHOD_MD5, ID_METHOD_METADATA_ITEM);
118
0
    AddArg("id-metadata-item", 0,
119
0
           _("Name of metadata item used to set STAC-GeoParquet 'id'"),
120
0
           &m_idMetadataItem)
121
0
        .SetDefault(m_idMetadataItem)
122
0
        .AddValidationAction(
123
0
            [this]()
124
0
            {
125
0
                m_idMethod = ID_METHOD_METADATA_ITEM;
126
0
                return true;
127
0
            });
128
129
0
    AddValidationAction(
130
0
        [this]()
131
0
        {
132
0
            if (m_profile == PROFILE_STAC_GEOPARQUET)
133
0
            {
134
0
                if (!m_outputFormat.empty() &&
135
0
                    !EQUAL(m_outputFormat.c_str(), "Parquet"))
136
0
                {
137
0
                    ReportError(CE_Failure, CPLE_NotSupported,
138
0
                                "STAC-GeoParquet profile is only compatible "
139
0
                                "with Parquet output format");
140
0
                    return false;
141
0
                }
142
0
                else if (m_outputFormat.empty() &&
143
0
                         !EQUAL(CPLGetExtensionSafe(
144
0
                                    m_outputDataset.GetName().c_str())
145
0
                                    .c_str(),
146
0
                                "parquet"))
147
0
                {
148
0
                    ReportError(CE_Failure, CPLE_NotSupported,
149
0
                                "STAC-GeoParquet profile is only compatible "
150
0
                                "with Parquet output format");
151
0
                    return false;
152
0
                }
153
0
                m_outputFormat = "Parquet";
154
155
0
                if (!m_crs.empty() && m_crs != "EPSG:4326")
156
0
                {
157
0
                    OGRSpatialReference oSRS;
158
0
                    CPL_IGNORE_RET_VAL(oSRS.SetFromUserInput(m_crs.c_str()));
159
0
                    const char *pszCelestialBodyName =
160
0
                        oSRS.GetCelestialBodyName();
161
                    // STAC-GeoParquet requires EPSG:4326, but let be nice
162
                    // with planetary use cases and allow a non-Earth geographic CRS...
163
0
                    if (!(pszCelestialBodyName &&
164
0
                          !EQUAL(pszCelestialBodyName, "Earth") &&
165
0
                          oSRS.IsGeographic()))
166
0
                    {
167
0
                        ReportError(
168
0
                            CE_Failure, CPLE_NotSupported,
169
0
                            "STAC-GeoParquet profile is only compatible "
170
0
                            "with --dst-crs=EPSG:4326");
171
0
                        return false;
172
0
                    }
173
0
                }
174
0
                if (m_crs.empty())
175
0
                    m_crs = "EPSG:4326";
176
0
            }
177
0
            return true;
178
0
        });
179
0
}
180
181
/************************************************************************/
182
/*                 GDALRasterIndexAlgorithm::RunImpl()                  */
183
/************************************************************************/
184
185
bool GDALRasterIndexAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
186
                                       void *pProgressData)
187
0
{
188
0
    CPLStringList aosSources;
189
0
    for (auto &srcDS : m_inputDatasets)
190
0
    {
191
0
        if (srcDS.GetDatasetRef())
192
0
        {
193
0
            ReportError(
194
0
                CE_Failure, CPLE_IllegalArg,
195
0
                "Input datasets must be provided by name, not as object");
196
0
            return false;
197
0
        }
198
0
        aosSources.push_back(srcDS.GetName());
199
0
    }
200
201
0
    auto setupRet = SetupOutputDataset();
202
0
    if (!setupRet.outDS)
203
0
        return false;
204
205
0
    if (!SetDefaultOutputLayerNameIfNeeded(setupRet.outDS))
206
0
        return false;
207
208
0
    CPLStringList aosOptions;
209
0
    aosOptions.push_back("--invoked-from-gdal-raster-index");
210
211
0
    if (m_profile != PROFILE_NONE)
212
0
    {
213
0
        aosOptions.push_back("-profile");
214
0
        aosOptions.push_back(m_profile);
215
216
0
        if (!m_baseUrl.empty())
217
0
        {
218
0
            aosOptions.push_back("--base-url");
219
0
            aosOptions.push_back(m_baseUrl);
220
0
        }
221
222
0
        aosOptions.push_back("--id-metadata-item");
223
0
        aosOptions.push_back(m_idMetadataItem);
224
225
0
        aosOptions.push_back("--id-method");
226
0
        aosOptions.push_back(m_idMethod);
227
0
    }
228
229
0
    if (m_skipErrors)
230
0
    {
231
0
        aosOptions.push_back("-skip_errors");
232
0
    }
233
0
    if (m_recursive)
234
0
    {
235
0
        aosOptions.push_back("-recursive");
236
0
    }
237
0
    for (const std::string &s : m_filenameFilter)
238
0
    {
239
0
        aosOptions.push_back("-filename_filter");
240
0
        aosOptions.push_back(s);
241
0
    }
242
0
    if (m_minPixelSize > 0)
243
0
    {
244
0
        aosOptions.push_back("-min_pixel_size");
245
0
        aosOptions.push_back(CPLSPrintf("%.17g", m_minPixelSize));
246
0
    }
247
0
    if (m_maxPixelSize > 0)
248
0
    {
249
0
        aosOptions.push_back("-max_pixel_size");
250
0
        aosOptions.push_back(CPLSPrintf("%.17g", m_maxPixelSize));
251
0
    }
252
253
0
    if (!m_outputLayerName.empty())
254
0
    {
255
0
        aosOptions.push_back("-lyr_name");
256
0
        aosOptions.push_back(m_outputLayerName);
257
0
    }
258
259
0
    aosOptions.push_back("-tileindex");
260
0
    aosOptions.push_back(m_locationName);
261
262
0
    if (m_writeAbsolutePaths)
263
0
    {
264
0
        aosOptions.push_back("-write_absolute_path");
265
0
    }
266
0
    if (m_crs.empty())
267
0
    {
268
0
        if (m_sourceCrsName.empty())
269
0
            aosOptions.push_back("-skip_different_projection");
270
0
    }
271
0
    else
272
0
    {
273
0
        aosOptions.push_back("-t_srs");
274
0
        aosOptions.push_back(m_crs);
275
0
    }
276
0
    if (!m_sourceCrsName.empty())
277
0
    {
278
0
        aosOptions.push_back("-src_srs_name");
279
0
        aosOptions.push_back(m_sourceCrsName);
280
281
0
        aosOptions.push_back("-src_srs_format");
282
0
        aosOptions.push_back(CPLString(m_sourceCrsFormat).toupper());
283
0
    }
284
285
0
    for (const std::string &s : m_metadata)
286
0
    {
287
0
        aosOptions.push_back("-mo");
288
0
        aosOptions.push_back(s);
289
0
    }
290
291
0
    if (!AddExtraOptions(aosOptions))
292
0
        return false;
293
294
0
    std::unique_ptr<GDALTileIndexOptions, decltype(&GDALTileIndexOptionsFree)>
295
0
        options(GDALTileIndexOptionsNew(aosOptions.List(), nullptr),
296
0
                GDALTileIndexOptionsFree);
297
298
0
    if (options)
299
0
    {
300
0
        GDALTileIndexOptionsSetProgress(options.get(), pfnProgress,
301
0
                                        pProgressData);
302
0
    }
303
304
0
    const bool ret =
305
0
        options && GDALTileIndexInternal(m_outputDataset.GetName().c_str(),
306
0
                                         GDALDataset::ToHandle(setupRet.outDS),
307
0
                                         OGRLayer::ToHandle(setupRet.layer),
308
0
                                         aosSources.size(), aosSources.List(),
309
0
                                         options.get(), nullptr) != nullptr;
310
311
0
    if (ret && setupRet.newDS)
312
0
    {
313
0
        m_outputDataset.Set(std::move(setupRet.newDS));
314
0
    }
315
316
0
    return ret;
317
0
}
318
319
//! @endcond