Coverage Report

Created: 2025-06-09 07:02

/src/gdal/apps/gdaltindex_lib.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  MapServer
4
 * Purpose:  Commandline App to build tile index for raster files.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2001, Frank Warmerdam, DM Solutions Group Inc
9
 * Copyright (c) 2007-2023, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_port.h"
15
#include "cpl_conv.h"
16
#include "cpl_minixml.h"
17
#include "cpl_string.h"
18
#include "gdal_utils.h"
19
#include "gdal_priv.h"
20
#include "gdal_utils_priv.h"
21
#include "ogr_api.h"
22
#include "ogrsf_frmts.h"
23
#include "ogr_spatialref.h"
24
#include "commonutils.h"
25
#include "gdalargumentparser.h"
26
27
#include <ctype.h>
28
29
#include <algorithm>
30
#include <cmath>
31
#include <limits>
32
#include <set>
33
34
typedef enum
35
{
36
    FORMAT_AUTO,
37
    FORMAT_WKT,
38
    FORMAT_EPSG,
39
    FORMAT_PROJ
40
} SrcSRSFormat;
41
42
/************************************************************************/
43
/*                        GDALTileIndexRasterMetadata                   */
44
/************************************************************************/
45
46
struct GDALTileIndexRasterMetadata
47
{
48
    OGRFieldType eType = OFTString;
49
    std::string osFieldName{};
50
    std::string osRasterItemName{};
51
};
52
53
/************************************************************************/
54
/*                          GDALTileIndexOptions                        */
55
/************************************************************************/
56
57
struct GDALTileIndexOptions
58
{
59
    bool bOverwrite = false;
60
    std::string osFormat{};
61
    std::string osIndexLayerName{};
62
    std::string osLocationField = "location";
63
    CPLStringList aosLCO{};
64
    std::string osTargetSRS{};
65
    bool bWriteAbsolutePath = false;
66
    bool bSkipDifferentProjection = false;
67
    std::string osSrcSRSFieldName{};
68
    SrcSRSFormat eSrcSRSFormat = FORMAT_AUTO;
69
    double xres = std::numeric_limits<double>::quiet_NaN();
70
    double yres = std::numeric_limits<double>::quiet_NaN();
71
    double xmin = std::numeric_limits<double>::quiet_NaN();
72
    double ymin = std::numeric_limits<double>::quiet_NaN();
73
    double xmax = std::numeric_limits<double>::quiet_NaN();
74
    double ymax = std::numeric_limits<double>::quiet_NaN();
75
    std::string osBandCount{};
76
    std::string osNodata{};
77
    std::string osColorInterp{};
78
    std::string osDataType{};
79
    bool bMaskBand = false;
80
    std::vector<std::string> aosMetadata{};
81
    std::string osGTIFilename{};
82
    bool bRecursive = false;
83
    double dfMinPixelSize = std::numeric_limits<double>::quiet_NaN();
84
    double dfMaxPixelSize = std::numeric_limits<double>::quiet_NaN();
85
    std::vector<GDALTileIndexRasterMetadata> aoFetchMD{};
86
    std::set<std::string> oSetFilenameFilters{};
87
    GDALProgressFunc pfnProgress = nullptr;
88
    void *pProgressData = nullptr;
89
};
90
91
/************************************************************************/
92
/*                     GDALTileIndexAppOptionsGetParser()               */
93
/************************************************************************/
94
95
static std::unique_ptr<GDALArgumentParser> GDALTileIndexAppOptionsGetParser(
96
    GDALTileIndexOptions *psOptions,
97
    GDALTileIndexOptionsForBinary *psOptionsForBinary)
98
0
{
99
0
    auto argParser = std::make_unique<GDALArgumentParser>(
100
0
        "gdaltindex", /* bForBinary=*/psOptionsForBinary != nullptr);
101
102
0
    argParser->add_description(
103
0
        _("Build a tile index from a list of datasets."));
104
105
0
    argParser->add_epilog(
106
0
        _("For more details, see the full documentation for gdaltindex at\n"
107
0
          "https://gdal.org/programs/gdaltindex.html"));
108
109
0
    argParser->add_argument("-overwrite")
110
0
        .flag()
111
0
        .store_into(psOptions->bOverwrite)
112
0
        .help(_("Overwrite the output tile index file if it already exists."));
113
114
0
    argParser->add_argument("-recursive")
115
0
        .flag()
116
0
        .store_into(psOptions->bRecursive)
117
0
        .help(_("Whether directories specified in <file_or_dir> should be "
118
0
                "explored recursively."));
119
120
0
    argParser->add_argument("-filename_filter")
121
0
        .metavar("<val>")
122
0
        .append()
123
0
        .store_into(psOptions->oSetFilenameFilters)
124
0
        .help(_("Pattern that the filenames contained in directories pointed "
125
0
                "by <file_or_dir> should follow."));
126
127
0
    argParser->add_argument("-min_pixel_size")
128
0
        .metavar("<val>")
129
0
        .store_into(psOptions->dfMinPixelSize)
130
0
        .help(_("Minimum pixel size in term of geospatial extent per pixel "
131
0
                "(resolution) that a raster should have to be selected."));
132
133
0
    argParser->add_argument("-max_pixel_size")
134
0
        .metavar("<val>")
135
0
        .store_into(psOptions->dfMaxPixelSize)
136
0
        .help(_("Maximum pixel size in term of geospatial extent per pixel "
137
0
                "(resolution) that a raster should have to be selected."));
138
139
0
    argParser->add_output_format_argument(psOptions->osFormat);
140
141
0
    argParser->add_argument("-tileindex")
142
0
        .metavar("<field_name>")
143
0
        .store_into(psOptions->osLocationField)
144
0
        .help(_("Name of the layer in the tile index file."));
145
146
0
    argParser->add_argument("-write_absolute_path")
147
0
        .flag()
148
0
        .store_into(psOptions->bWriteAbsolutePath)
149
0
        .help(_("Write the absolute path of the raster files in the tile index "
150
0
                "file."));
151
152
0
    argParser->add_argument("-skip_different_projection")
153
0
        .flag()
154
0
        .store_into(psOptions->bSkipDifferentProjection)
155
0
        .help(_(
156
0
            "Only files with the same projection as files already inserted in "
157
0
            "the tile index will be inserted (unless -t_srs is specified)."));
158
159
0
    argParser->add_argument("-t_srs")
160
0
        .metavar("<srs_def>")
161
0
        .store_into(psOptions->osTargetSRS)
162
0
        .help(_("Geometries of input files will be transformed to the desired "
163
0
                "target coordinate reference system."));
164
165
0
    argParser->add_argument("-src_srs_name")
166
0
        .metavar("<field_name>")
167
0
        .store_into(psOptions->osSrcSRSFieldName)
168
0
        .help(_("Name of the field in the tile index file where the source SRS "
169
0
                "will be stored."));
170
171
0
    argParser->add_argument("-src_srs_format")
172
0
        .metavar("{AUTO|WKT|EPSG|PROJ}")
173
0
        .choices("AUTO", "WKT", "EPSG", "PROJ")
174
0
        .action(
175
0
            [psOptions](const auto &f)
176
0
            {
177
0
                if (f == "WKT")
178
0
                    psOptions->eSrcSRSFormat = FORMAT_WKT;
179
0
                else if (f == "EPSG")
180
0
                    psOptions->eSrcSRSFormat = FORMAT_EPSG;
181
0
                else if (f == "PROJ")
182
0
                    psOptions->eSrcSRSFormat = FORMAT_PROJ;
183
0
                else
184
0
                    psOptions->eSrcSRSFormat = FORMAT_AUTO;
185
0
            })
186
0
        .help(_("Format of the source SRS to store in the tile index file."));
187
188
0
    argParser->add_argument("-lyr_name")
189
0
        .metavar("<name>")
190
0
        .store_into(psOptions->osIndexLayerName)
191
0
        .help(_("Name of the layer in the tile index file."));
192
193
0
    argParser->add_layer_creation_options_argument(psOptions->aosLCO);
194
195
    // GTI driver options
196
197
0
    argParser->add_argument("-gti_filename")
198
0
        .metavar("<filename>")
199
0
        .store_into(psOptions->osGTIFilename)
200
0
        .help(_("Filename of the XML Virtual Tile Index file to generate."));
201
202
    // NOTE: no store_into
203
0
    argParser->add_argument("-tr")
204
0
        .metavar("<xres> <yres>")
205
0
        .nargs(2)
206
0
        .scan<'g', double>()
207
0
        .help(_("Set target resolution."));
208
209
    // NOTE: no store_into
210
0
    argParser->add_argument("-te")
211
0
        .metavar("<xmin> <ymin> <xmax> <ymax>")
212
0
        .nargs(4)
213
0
        .scan<'g', double>()
214
0
        .help(_("Set target extent in SRS unit."));
215
216
0
    argParser->add_argument("-ot")
217
0
        .metavar("<datatype>")
218
0
        .store_into(psOptions->osDataType)
219
0
        .help(_("Output data type."));
220
221
0
    argParser->add_argument("-bandcount")
222
0
        .metavar("<val>")
223
0
        .store_into(psOptions->osBandCount)
224
0
        .help(_("Number of bands of the tiles of the tile index."));
225
226
0
    argParser->add_argument("-nodata")
227
0
        .metavar("<val>")
228
0
        .append()
229
0
        .store_into(psOptions->osNodata)
230
0
        .help(_("Nodata value of the tiles of the tile index."));
231
232
    // Should we use choices here?
233
0
    argParser->add_argument("-colorinterp")
234
0
        .metavar("<val>")
235
0
        .append()
236
0
        .store_into(psOptions->osColorInterp)
237
0
        .help(_("Color interpretation of of the tiles of the tile index: red, "
238
0
                "green, blue, alpha, gray, undefined."));
239
240
0
    argParser->add_argument("-mask")
241
0
        .flag()
242
0
        .store_into(psOptions->bMaskBand)
243
0
        .help(_("Add a mask band to the tiles of the tile index."));
244
245
0
    argParser->add_argument("-mo")
246
0
        .metavar("<name>=<value>")
247
0
        .append()
248
0
        .store_into(psOptions->aosMetadata)
249
0
        .help(_("Write an arbitrary layer metadata item, for formats that "
250
0
                "support layer metadata."));
251
252
    // NOTE: no store_into
253
0
    argParser->add_argument("-fetch_md")
254
0
        .nargs(3)
255
0
        .metavar("<gdal_md_name> <fld_name> <fld_type>")
256
0
        .append()
257
0
        .help("Fetch a metadata item from the raster tile and write it as a "
258
0
              "field in the tile index.");
259
260
0
    if (psOptionsForBinary)
261
0
    {
262
0
        argParser->add_quiet_argument(&psOptionsForBinary->bQuiet);
263
264
0
        argParser->add_argument("index_file")
265
0
            .metavar("<index_file>")
266
0
            .store_into(psOptionsForBinary->osDest)
267
0
            .help(_("The name of the output file to create/append to."));
268
269
0
        argParser->add_argument("file_or_dir")
270
0
            .metavar("<file_or_dir>")
271
0
            .nargs(argparse::nargs_pattern::at_least_one)
272
0
            .action([psOptionsForBinary](const std::string &s)
273
0
                    { psOptionsForBinary->aosSrcFiles.AddString(s.c_str()); })
274
0
            .help(_(
275
0
                "The input GDAL raster files or directory, can be multiple "
276
0
                "locations separated by spaces. Wildcards may also be used."));
277
0
    }
278
279
0
    return argParser;
280
0
}
281
282
/************************************************************************/
283
/*                  GDALTileIndexAppGetParserUsage()                    */
284
/************************************************************************/
285
286
std::string GDALTileIndexAppGetParserUsage()
287
0
{
288
0
    try
289
0
    {
290
0
        GDALTileIndexOptions sOptions;
291
0
        GDALTileIndexOptionsForBinary sOptionsForBinary;
292
0
        auto argParser =
293
0
            GDALTileIndexAppOptionsGetParser(&sOptions, &sOptionsForBinary);
294
0
        return argParser->usage();
295
0
    }
296
0
    catch (const std::exception &err)
297
0
    {
298
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
299
0
                 err.what());
300
0
        return std::string();
301
0
    }
302
0
}
303
304
/************************************************************************/
305
/*                        GDALTileIndexTileIterator                     */
306
/************************************************************************/
307
308
struct GDALTileIndexTileIterator
309
{
310
    const GDALTileIndexOptions *psOptions = nullptr;
311
    int nSrcCount = 0;
312
    const char *const *papszSrcDSNames = nullptr;
313
    std::string osCurDir{};
314
    int iCurSrc = 0;
315
    VSIDIR *psDir = nullptr;
316
317
    CPL_DISALLOW_COPY_ASSIGN(GDALTileIndexTileIterator)
318
319
    GDALTileIndexTileIterator(const GDALTileIndexOptions *psOptionsIn,
320
                              int nSrcCountIn,
321
                              const char *const *papszSrcDSNamesIn)
322
0
        : psOptions(psOptionsIn), nSrcCount(nSrcCountIn),
323
0
          papszSrcDSNames(papszSrcDSNamesIn)
324
0
    {
325
0
    }
326
327
    void reset()
328
0
    {
329
0
        if (psDir)
330
0
            VSICloseDir(psDir);
331
0
        psDir = nullptr;
332
0
        iCurSrc = 0;
333
0
    }
334
335
    std::string next()
336
0
    {
337
0
        while (true)
338
0
        {
339
0
            if (!psDir)
340
0
            {
341
0
                if (iCurSrc == nSrcCount)
342
0
                {
343
0
                    break;
344
0
                }
345
346
0
                VSIStatBufL sStatBuf;
347
0
                const char *pszCurName = papszSrcDSNames[iCurSrc++];
348
0
                if (VSIStatL(pszCurName, &sStatBuf) == 0 &&
349
0
                    VSI_ISDIR(sStatBuf.st_mode))
350
0
                {
351
0
                    auto poSrcDS = std::unique_ptr<GDALDataset>(
352
0
                        GDALDataset::Open(pszCurName, GDAL_OF_RASTER, nullptr,
353
0
                                          nullptr, nullptr));
354
0
                    if (poSrcDS)
355
0
                        return pszCurName;
356
357
0
                    osCurDir = pszCurName;
358
0
                    psDir = VSIOpenDir(
359
0
                        osCurDir.c_str(),
360
0
                        /*nDepth=*/psOptions->bRecursive ? -1 : 0, nullptr);
361
0
                    if (!psDir)
362
0
                    {
363
0
                        CPLError(CE_Failure, CPLE_AppDefined,
364
0
                                 "Cannot open directory %s", osCurDir.c_str());
365
0
                        return std::string();
366
0
                    }
367
0
                }
368
0
                else
369
0
                {
370
0
                    return pszCurName;
371
0
                }
372
0
            }
373
374
0
            auto psEntry = VSIGetNextDirEntry(psDir);
375
0
            if (!psEntry)
376
0
            {
377
0
                VSICloseDir(psDir);
378
0
                psDir = nullptr;
379
0
                continue;
380
0
            }
381
382
0
            if (!psOptions->oSetFilenameFilters.empty())
383
0
            {
384
0
                bool bMatchFound = false;
385
0
                const std::string osFilenameOnly =
386
0
                    CPLGetFilename(psEntry->pszName);
387
0
                for (const auto &osFilter : psOptions->oSetFilenameFilters)
388
0
                {
389
0
                    if (GDALPatternMatch(osFilenameOnly.c_str(),
390
0
                                         osFilter.c_str()))
391
0
                    {
392
0
                        bMatchFound = true;
393
0
                        break;
394
0
                    }
395
0
                }
396
0
                if (!bMatchFound)
397
0
                    continue;
398
0
            }
399
400
0
            std::string osFilename = CPLFormFilenameSafe(
401
0
                osCurDir.c_str(), psEntry->pszName, nullptr);
402
0
            if (VSI_ISDIR(psEntry->nMode))
403
0
            {
404
0
                auto poSrcDS = std::unique_ptr<GDALDataset>(
405
0
                    GDALDataset::Open(osFilename.c_str(), GDAL_OF_RASTER,
406
0
                                      nullptr, nullptr, nullptr));
407
0
                if (poSrcDS)
408
0
                {
409
0
                    return osFilename;
410
0
                }
411
0
                continue;
412
0
            }
413
414
0
            return osFilename;
415
0
        }
416
0
        return std::string();
417
0
    }
418
};
419
420
/************************************************************************/
421
/*                           GDALTileIndex()                            */
422
/************************************************************************/
423
424
/* clang-format off */
425
/**
426
 * Build a tile index from a list of datasets.
427
 *
428
 * This is the equivalent of the
429
 * <a href="/programs/gdaltindex.html">gdaltindex</a> utility.
430
 *
431
 * GDALTileIndexOptions* must be allocated and freed with
432
 * GDALTileIndexOptionsNew() and GDALTileIndexOptionsFree() respectively.
433
 *
434
 * @param pszDest the destination dataset path.
435
 * @param nSrcCount the number of input datasets.
436
 * @param papszSrcDSNames the list of input dataset names
437
 * @param psOptionsIn the options struct returned by GDALTileIndexOptionsNew() or
438
 * NULL.
439
 * @param pbUsageError pointer to a integer output variable to store if any
440
 * usage error has occurred.
441
 * @return the output dataset (new dataset that must be closed using
442
 * GDALClose()) or NULL in case of error.
443
 *
444
 * @since GDAL3.9
445
 */
446
/* clang-format on */
447
448
GDALDatasetH GDALTileIndex(const char *pszDest, int nSrcCount,
449
                           const char *const *papszSrcDSNames,
450
                           const GDALTileIndexOptions *psOptionsIn,
451
                           int *pbUsageError)
452
0
{
453
0
    return GDALTileIndexInternal(pszDest, nullptr, nullptr, nSrcCount,
454
0
                                 papszSrcDSNames, psOptionsIn, pbUsageError);
455
0
}
456
457
GDALDatasetH GDALTileIndexInternal(const char *pszDest,
458
                                   GDALDatasetH hTileIndexDS, OGRLayerH hLayer,
459
                                   int nSrcCount,
460
                                   const char *const *papszSrcDSNames,
461
                                   const GDALTileIndexOptions *psOptionsIn,
462
                                   int *pbUsageError)
463
0
{
464
0
    if (nSrcCount == 0)
465
0
    {
466
0
        CPLError(CE_Failure, CPLE_AppDefined, "No input dataset specified.");
467
468
0
        if (pbUsageError)
469
0
            *pbUsageError = TRUE;
470
0
        return nullptr;
471
0
    }
472
473
0
    auto psOptions = psOptionsIn
474
0
                         ? std::make_unique<GDALTileIndexOptions>(*psOptionsIn)
475
0
                         : std::make_unique<GDALTileIndexOptions>();
476
477
0
    GDALTileIndexTileIterator oGDALTileIndexTileIterator(
478
0
        psOptions.get(), nSrcCount, papszSrcDSNames);
479
480
    /* -------------------------------------------------------------------- */
481
    /*      Create and validate target SRS if given.                        */
482
    /* -------------------------------------------------------------------- */
483
0
    OGRSpatialReference oTargetSRS;
484
0
    if (!psOptions->osTargetSRS.empty())
485
0
    {
486
0
        if (psOptions->bSkipDifferentProjection)
487
0
        {
488
0
            CPLError(CE_Warning, CPLE_AppDefined,
489
0
                     "-skip_different_projections does not apply "
490
0
                     "when -t_srs is requested.");
491
0
        }
492
0
        oTargetSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
493
        // coverity[tainted_data]
494
0
        oTargetSRS.SetFromUserInput(psOptions->osTargetSRS.c_str());
495
0
    }
496
497
    /* -------------------------------------------------------------------- */
498
    /*      Open or create the target datasource                            */
499
    /* -------------------------------------------------------------------- */
500
501
0
    std::unique_ptr<GDALDataset> poTileIndexDSUnique;
502
0
    GDALDataset *poTileIndexDS = GDALDataset::FromHandle(hTileIndexDS);
503
0
    OGRLayer *poLayer = OGRLayer::FromHandle(hLayer);
504
0
    bool bExistingLayer = false;
505
0
    std::string osFormat;
506
507
0
    if (!hTileIndexDS)
508
0
    {
509
0
        if (psOptions->bOverwrite)
510
0
        {
511
0
            CPLPushErrorHandler(CPLQuietErrorHandler);
512
0
            auto hDriver = GDALIdentifyDriver(pszDest, nullptr);
513
0
            if (hDriver)
514
0
                GDALDeleteDataset(hDriver, pszDest);
515
0
            else
516
0
                VSIUnlink(pszDest);
517
0
            CPLPopErrorHandler();
518
0
        }
519
520
0
        poTileIndexDSUnique.reset(
521
0
            GDALDataset::Open(pszDest, GDAL_OF_VECTOR | GDAL_OF_UPDATE, nullptr,
522
0
                              nullptr, nullptr));
523
524
0
        if (poTileIndexDSUnique != nullptr)
525
0
        {
526
0
            auto poDriver = poTileIndexDSUnique->GetDriver();
527
0
            if (poDriver)
528
0
                osFormat = poDriver->GetDescription();
529
530
0
            if (poTileIndexDSUnique->GetLayerCount() == 1)
531
0
            {
532
0
                poLayer = poTileIndexDSUnique->GetLayer(0);
533
0
            }
534
0
            else
535
0
            {
536
0
                if (psOptions->osIndexLayerName.empty())
537
0
                {
538
0
                    CPLError(CE_Failure, CPLE_AppDefined,
539
0
                             "Multiple layers detected: -lyr_name must be "
540
0
                             "specified.");
541
0
                    if (pbUsageError)
542
0
                        *pbUsageError = true;
543
0
                    return nullptr;
544
0
                }
545
0
                CPLPushErrorHandler(CPLQuietErrorHandler);
546
0
                poLayer = poTileIndexDSUnique->GetLayerByName(
547
0
                    psOptions->osIndexLayerName.c_str());
548
0
                CPLPopErrorHandler();
549
0
            }
550
0
        }
551
0
        else
552
0
        {
553
0
            if (psOptions->osFormat.empty())
554
0
            {
555
0
                const auto aoDrivers =
556
0
                    GetOutputDriversFor(pszDest, GDAL_OF_VECTOR);
557
0
                if (aoDrivers.empty())
558
0
                {
559
0
                    CPLError(CE_Failure, CPLE_AppDefined,
560
0
                             "Cannot guess driver for %s", pszDest);
561
0
                    return nullptr;
562
0
                }
563
0
                else
564
0
                {
565
0
                    if (aoDrivers.size() > 1)
566
0
                    {
567
0
                        CPLError(
568
0
                            CE_Warning, CPLE_AppDefined,
569
0
                            "Several drivers matching %s extension. Using %s",
570
0
                            CPLGetExtensionSafe(pszDest).c_str(),
571
0
                            aoDrivers[0].c_str());
572
0
                    }
573
0
                    osFormat = aoDrivers[0];
574
0
                }
575
0
            }
576
0
            else
577
0
            {
578
0
                osFormat = psOptions->osFormat;
579
0
            }
580
581
0
            auto poDriver =
582
0
                GetGDALDriverManager()->GetDriverByName(osFormat.c_str());
583
0
            if (poDriver == nullptr)
584
0
            {
585
0
                CPLError(CE_Warning, CPLE_AppDefined,
586
0
                         "%s driver not available.", osFormat.c_str());
587
0
                return nullptr;
588
0
            }
589
590
0
            poTileIndexDSUnique.reset(
591
0
                poDriver->Create(pszDest, 0, 0, 0, GDT_Unknown, nullptr));
592
0
            if (!poTileIndexDSUnique)
593
0
                return nullptr;
594
0
        }
595
596
0
        poTileIndexDS = poTileIndexDSUnique.get();
597
0
    }
598
599
0
    auto poOutDrv = poTileIndexDS->GetDriver();
600
0
    if (osFormat.empty() && poOutDrv)
601
0
        osFormat = poOutDrv->GetDescription();
602
603
0
    const char *pszVal =
604
0
        poOutDrv ? poOutDrv->GetMetadataItem(GDAL_DMD_MAX_STRING_LENGTH)
605
0
                 : nullptr;
606
0
    const int nMaxFieldSize = pszVal ? atoi(pszVal) : 0;
607
608
0
    if (poLayer)
609
0
    {
610
0
        bExistingLayer = true;
611
0
    }
612
0
    else
613
0
    {
614
0
        std::string osLayerName;
615
0
        if (psOptions->osIndexLayerName.empty())
616
0
        {
617
0
            VSIStatBuf sStat;
618
0
            if (EQUAL(osFormat.c_str(), "ESRI Shapefile") ||
619
0
                VSIStat(pszDest, &sStat) == 0)
620
0
            {
621
0
                osLayerName = CPLGetBasenameSafe(pszDest);
622
0
            }
623
0
            else
624
0
            {
625
0
                CPLError(CE_Failure, CPLE_AppDefined,
626
0
                         "-lyr_name must be specified.");
627
0
                if (pbUsageError)
628
0
                    *pbUsageError = true;
629
0
                return nullptr;
630
0
            }
631
0
        }
632
0
        else
633
0
        {
634
0
            if (psOptions->bOverwrite)
635
0
            {
636
0
                for (int i = 0; i < poTileIndexDS->GetLayerCount(); ++i)
637
0
                {
638
0
                    auto poExistingLayer = poTileIndexDS->GetLayer(i);
639
0
                    if (poExistingLayer && poExistingLayer->GetName() ==
640
0
                                               psOptions->osIndexLayerName)
641
0
                    {
642
0
                        if (poTileIndexDS->DeleteLayer(i) != OGRERR_NONE)
643
0
                            return nullptr;
644
0
                        break;
645
0
                    }
646
0
                }
647
0
            }
648
649
0
            osLayerName = psOptions->osIndexLayerName;
650
0
        }
651
652
        /* get spatial reference for output file from target SRS (if set) */
653
        /* or from first input file */
654
0
        OGRSpatialReference oSRS;
655
0
        if (!oTargetSRS.IsEmpty())
656
0
        {
657
0
            oSRS = oTargetSRS;
658
0
        }
659
0
        else
660
0
        {
661
0
            std::string osFilename = oGDALTileIndexTileIterator.next();
662
0
            if (osFilename.empty())
663
0
            {
664
0
                CPLError(CE_Failure, CPLE_AppDefined, "Cannot find any tile");
665
0
                return nullptr;
666
0
            }
667
0
            oGDALTileIndexTileIterator.reset();
668
0
            auto poSrcDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
669
0
                osFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
670
0
                nullptr, nullptr, nullptr));
671
0
            if (!poSrcDS)
672
0
                return nullptr;
673
674
0
            auto poSrcSRS = poSrcDS->GetSpatialRef();
675
0
            if (poSrcSRS)
676
0
                oSRS = *poSrcSRS;
677
0
        }
678
679
0
        poLayer = poTileIndexDS->CreateLayer(
680
0
            osLayerName.c_str(), oSRS.IsEmpty() ? nullptr : &oSRS, wkbPolygon,
681
0
            psOptions->aosLCO.List());
682
0
        if (!poLayer)
683
0
            return nullptr;
684
685
0
        OGRFieldDefn oLocationField(psOptions->osLocationField.c_str(),
686
0
                                    OFTString);
687
0
        oLocationField.SetWidth(nMaxFieldSize);
688
0
        if (poLayer->CreateField(&oLocationField) != OGRERR_NONE)
689
0
            return nullptr;
690
691
0
        if (!psOptions->osSrcSRSFieldName.empty())
692
0
        {
693
0
            OGRFieldDefn oSrcSRSField(psOptions->osSrcSRSFieldName.c_str(),
694
0
                                      OFTString);
695
0
            oSrcSRSField.SetWidth(nMaxFieldSize);
696
0
            if (poLayer->CreateField(&oSrcSRSField) != OGRERR_NONE)
697
0
                return nullptr;
698
0
        }
699
0
    }
700
701
0
    auto poLayerDefn = poLayer->GetLayerDefn();
702
703
0
    for (const auto &oFetchMD : psOptions->aoFetchMD)
704
0
    {
705
0
        if (poLayerDefn->GetFieldIndex(oFetchMD.osFieldName.c_str()) < 0)
706
0
        {
707
0
            OGRFieldDefn oField(oFetchMD.osFieldName.c_str(), oFetchMD.eType);
708
0
            if (poLayer->CreateField(&oField) != OGRERR_NONE)
709
0
                return nullptr;
710
0
        }
711
0
    }
712
713
0
    if (!psOptions->osGTIFilename.empty())
714
0
    {
715
0
        if (!psOptions->aosMetadata.empty())
716
0
        {
717
0
            CPLError(CE_Failure, CPLE_NotSupported,
718
0
                     "-mo is not supported when -gti_filename is used");
719
0
            return nullptr;
720
0
        }
721
0
        CPLXMLNode *psRoot =
722
0
            CPLCreateXMLNode(nullptr, CXT_Element, "GDALTileIndexDataset");
723
0
        CPLCreateXMLElementAndValue(psRoot, "IndexDataset", pszDest);
724
0
        CPLCreateXMLElementAndValue(psRoot, "IndexLayer", poLayer->GetName());
725
0
        CPLCreateXMLElementAndValue(psRoot, "LocationField",
726
0
                                    psOptions->osLocationField.c_str());
727
0
        if (!std::isnan(psOptions->xres))
728
0
        {
729
0
            CPLCreateXMLElementAndValue(psRoot, "ResX",
730
0
                                        CPLSPrintf("%.18g", psOptions->xres));
731
0
            CPLCreateXMLElementAndValue(psRoot, "ResY",
732
0
                                        CPLSPrintf("%.18g", psOptions->yres));
733
0
        }
734
0
        if (!std::isnan(psOptions->xmin))
735
0
        {
736
0
            CPLCreateXMLElementAndValue(psRoot, "MinX",
737
0
                                        CPLSPrintf("%.18g", psOptions->xmin));
738
0
            CPLCreateXMLElementAndValue(psRoot, "MinY",
739
0
                                        CPLSPrintf("%.18g", psOptions->ymin));
740
0
            CPLCreateXMLElementAndValue(psRoot, "MaxX",
741
0
                                        CPLSPrintf("%.18g", psOptions->xmax));
742
0
            CPLCreateXMLElementAndValue(psRoot, "MaxY",
743
0
                                        CPLSPrintf("%.18g", psOptions->ymax));
744
0
        }
745
746
0
        int nBandCount = 0;
747
0
        if (!psOptions->osBandCount.empty())
748
0
        {
749
0
            nBandCount = atoi(psOptions->osBandCount.c_str());
750
0
        }
751
0
        else
752
0
        {
753
0
            if (!psOptions->osDataType.empty())
754
0
            {
755
0
                nBandCount = std::max(
756
0
                    nBandCount,
757
0
                    CPLStringList(CSLTokenizeString2(
758
0
                                      psOptions->osDataType.c_str(), ", ", 0))
759
0
                        .size());
760
0
            }
761
0
            if (!psOptions->osNodata.empty())
762
0
            {
763
0
                nBandCount = std::max(
764
0
                    nBandCount,
765
0
                    CPLStringList(CSLTokenizeString2(
766
0
                                      psOptions->osNodata.c_str(), ", ", 0))
767
0
                        .size());
768
0
            }
769
0
            if (!psOptions->osColorInterp.empty())
770
0
            {
771
0
                nBandCount =
772
0
                    std::max(nBandCount,
773
0
                             CPLStringList(
774
0
                                 CSLTokenizeString2(
775
0
                                     psOptions->osColorInterp.c_str(), ", ", 0))
776
0
                                 .size());
777
0
            }
778
0
        }
779
780
0
        for (int i = 0; i < nBandCount; ++i)
781
0
        {
782
0
            auto psBand = CPLCreateXMLNode(psRoot, CXT_Element, "Band");
783
0
            CPLAddXMLAttributeAndValue(psBand, "band", CPLSPrintf("%d", i + 1));
784
0
            if (!psOptions->osDataType.empty())
785
0
            {
786
0
                const CPLStringList aosTokens(
787
0
                    CSLTokenizeString2(psOptions->osDataType.c_str(), ", ", 0));
788
0
                if (aosTokens.size() == 1)
789
0
                    CPLAddXMLAttributeAndValue(psBand, "dataType",
790
0
                                               aosTokens[0]);
791
0
                else if (i < aosTokens.size())
792
0
                    CPLAddXMLAttributeAndValue(psBand, "dataType",
793
0
                                               aosTokens[i]);
794
0
            }
795
0
            if (!psOptions->osNodata.empty())
796
0
            {
797
0
                const CPLStringList aosTokens(
798
0
                    CSLTokenizeString2(psOptions->osNodata.c_str(), ", ", 0));
799
0
                if (aosTokens.size() == 1)
800
0
                    CPLCreateXMLElementAndValue(psBand, "NoDataValue",
801
0
                                                aosTokens[0]);
802
0
                else if (i < aosTokens.size())
803
0
                    CPLCreateXMLElementAndValue(psBand, "NoDataValue",
804
0
                                                aosTokens[i]);
805
0
            }
806
0
            if (!psOptions->osColorInterp.empty())
807
0
            {
808
0
                const CPLStringList aosTokens(CSLTokenizeString2(
809
0
                    psOptions->osColorInterp.c_str(), ", ", 0));
810
0
                if (aosTokens.size() == 1)
811
0
                    CPLCreateXMLElementAndValue(psBand, "ColorInterp",
812
0
                                                aosTokens[0]);
813
0
                else if (i < aosTokens.size())
814
0
                    CPLCreateXMLElementAndValue(psBand, "ColorInterp",
815
0
                                                aosTokens[i]);
816
0
            }
817
0
        }
818
819
0
        if (psOptions->bMaskBand)
820
0
        {
821
0
            CPLCreateXMLElementAndValue(psRoot, "MaskBand", "true");
822
0
        }
823
0
        int res =
824
0
            CPLSerializeXMLTreeToFile(psRoot, psOptions->osGTIFilename.c_str());
825
0
        CPLDestroyXMLNode(psRoot);
826
0
        if (!res)
827
0
            return nullptr;
828
0
    }
829
0
    else
830
0
    {
831
0
        poLayer->SetMetadataItem("LOCATION_FIELD",
832
0
                                 psOptions->osLocationField.c_str());
833
0
        if (!std::isnan(psOptions->xres))
834
0
        {
835
0
            poLayer->SetMetadataItem("RESX",
836
0
                                     CPLSPrintf("%.18g", psOptions->xres));
837
0
            poLayer->SetMetadataItem("RESY",
838
0
                                     CPLSPrintf("%.18g", psOptions->yres));
839
0
        }
840
0
        if (!std::isnan(psOptions->xmin))
841
0
        {
842
0
            poLayer->SetMetadataItem("MINX",
843
0
                                     CPLSPrintf("%.18g", psOptions->xmin));
844
0
            poLayer->SetMetadataItem("MINY",
845
0
                                     CPLSPrintf("%.18g", psOptions->ymin));
846
0
            poLayer->SetMetadataItem("MAXX",
847
0
                                     CPLSPrintf("%.18g", psOptions->xmax));
848
0
            poLayer->SetMetadataItem("MAXY",
849
0
                                     CPLSPrintf("%.18g", psOptions->ymax));
850
0
        }
851
0
        if (!psOptions->osBandCount.empty())
852
0
        {
853
0
            poLayer->SetMetadataItem("BAND_COUNT",
854
0
                                     psOptions->osBandCount.c_str());
855
0
        }
856
0
        if (!psOptions->osDataType.empty())
857
0
        {
858
0
            poLayer->SetMetadataItem("DATA_TYPE",
859
0
                                     psOptions->osDataType.c_str());
860
0
        }
861
0
        if (!psOptions->osNodata.empty())
862
0
        {
863
0
            poLayer->SetMetadataItem("NODATA", psOptions->osNodata.c_str());
864
0
        }
865
0
        if (!psOptions->osColorInterp.empty())
866
0
        {
867
0
            poLayer->SetMetadataItem("COLOR_INTERPRETATION",
868
0
                                     psOptions->osColorInterp.c_str());
869
0
        }
870
0
        if (psOptions->bMaskBand)
871
0
        {
872
0
            poLayer->SetMetadataItem("MASK_BAND", "YES");
873
0
        }
874
0
        const CPLStringList aosMetadata(psOptions->aosMetadata);
875
0
        for (const auto &[pszKey, pszValue] :
876
0
             cpl::IterateNameValue(aosMetadata))
877
0
        {
878
0
            poLayer->SetMetadataItem(pszKey, pszValue);
879
0
        }
880
0
    }
881
882
0
    const int ti_field =
883
0
        poLayerDefn->GetFieldIndex(psOptions->osLocationField.c_str());
884
0
    if (ti_field < 0)
885
0
    {
886
0
        CPLError(CE_Failure, CPLE_AppDefined,
887
0
                 "Unable to find field `%s' in file `%s'.",
888
0
                 psOptions->osLocationField.c_str(), pszDest);
889
0
        return nullptr;
890
0
    }
891
892
0
    int i_SrcSRSName = -1;
893
0
    if (!psOptions->osSrcSRSFieldName.empty())
894
0
    {
895
0
        i_SrcSRSName =
896
0
            poLayerDefn->GetFieldIndex(psOptions->osSrcSRSFieldName.c_str());
897
0
        if (i_SrcSRSName < 0)
898
0
        {
899
0
            CPLError(CE_Failure, CPLE_AppDefined,
900
0
                     "Unable to find field `%s' in file `%s'.",
901
0
                     psOptions->osSrcSRSFieldName.c_str(), pszDest);
902
0
            return nullptr;
903
0
        }
904
0
    }
905
906
    // Load in memory existing file names in tile index.
907
0
    std::set<std::string> oSetExistingFiles;
908
0
    OGRSpatialReference oAlreadyExistingSRS;
909
0
    if (bExistingLayer)
910
0
    {
911
0
        for (auto &&poFeature : poLayer)
912
0
        {
913
0
            if (poFeature->IsFieldSetAndNotNull(ti_field))
914
0
            {
915
0
                if (oSetExistingFiles.empty())
916
0
                {
917
0
                    auto poSrcDS =
918
0
                        std::unique_ptr<GDALDataset>(GDALDataset::Open(
919
0
                            poFeature->GetFieldAsString(ti_field),
920
0
                            GDAL_OF_RASTER, nullptr, nullptr, nullptr));
921
0
                    if (poSrcDS)
922
0
                    {
923
0
                        auto poSrcSRS = poSrcDS->GetSpatialRef();
924
0
                        if (poSrcSRS)
925
0
                            oAlreadyExistingSRS = *poSrcSRS;
926
0
                    }
927
0
                }
928
0
                oSetExistingFiles.insert(poFeature->GetFieldAsString(ti_field));
929
0
            }
930
0
        }
931
0
    }
932
933
0
    std::string osCurrentPath;
934
0
    if (psOptions->bWriteAbsolutePath)
935
0
    {
936
0
        char *pszCurrentPath = CPLGetCurrentDir();
937
0
        if (pszCurrentPath)
938
0
        {
939
0
            osCurrentPath = pszCurrentPath;
940
0
        }
941
0
        else
942
0
        {
943
0
            CPLError(CE_Warning, CPLE_AppDefined,
944
0
                     "This system does not support the CPLGetCurrentDir call. "
945
0
                     "The option -bWriteAbsolutePath will have no effect.");
946
0
        }
947
0
        CPLFree(pszCurrentPath);
948
0
    }
949
950
0
    const bool bIsGTIContext =
951
0
        !std::isnan(psOptions->xres) || !std::isnan(psOptions->xmin) ||
952
0
        !psOptions->osBandCount.empty() || !psOptions->osNodata.empty() ||
953
0
        !psOptions->osColorInterp.empty() || !psOptions->osDataType.empty() ||
954
0
        psOptions->bMaskBand || !psOptions->aosMetadata.empty() ||
955
0
        !psOptions->osGTIFilename.empty();
956
957
    /* -------------------------------------------------------------------- */
958
    /*      loop over GDAL files, processing.                               */
959
    /* -------------------------------------------------------------------- */
960
0
    int iCur = 0;
961
0
    int nTotal = nSrcCount + 1;
962
0
    while (true)
963
0
    {
964
0
        const std::string osSrcFilename = oGDALTileIndexTileIterator.next();
965
0
        if (osSrcFilename.empty())
966
0
            break;
967
968
0
        std::string osFileNameToWrite;
969
0
        VSIStatBuf sStatBuf;
970
971
        // Make sure it is a file before building absolute path name.
972
0
        if (!osCurrentPath.empty() &&
973
0
            CPLIsFilenameRelative(osSrcFilename.c_str()) &&
974
0
            VSIStat(osSrcFilename.c_str(), &sStatBuf) == 0)
975
0
        {
976
0
            osFileNameToWrite = CPLProjectRelativeFilenameSafe(
977
0
                osCurrentPath.c_str(), osSrcFilename.c_str());
978
0
        }
979
0
        else
980
0
        {
981
0
            osFileNameToWrite = osSrcFilename.c_str();
982
0
        }
983
984
        // Checks that file is not already in tileindex.
985
0
        if (oSetExistingFiles.find(osFileNameToWrite) !=
986
0
            oSetExistingFiles.end())
987
0
        {
988
0
            CPLError(CE_Warning, CPLE_AppDefined,
989
0
                     "File %s is already in tileindex. Skipping it.",
990
0
                     osFileNameToWrite.c_str());
991
0
            continue;
992
0
        }
993
994
0
        auto poSrcDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
995
0
            osSrcFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
996
0
            nullptr, nullptr, nullptr));
997
0
        if (poSrcDS == nullptr)
998
0
        {
999
0
            CPLError(CE_Warning, CPLE_AppDefined,
1000
0
                     "Unable to open %s, skipping.", osSrcFilename.c_str());
1001
0
            continue;
1002
0
        }
1003
1004
0
        double adfGeoTransform[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
1005
0
        if (poSrcDS->GetGeoTransform(adfGeoTransform) != CE_None)
1006
0
        {
1007
0
            CPLError(CE_Warning, CPLE_AppDefined,
1008
0
                     "It appears no georeferencing is available for\n"
1009
0
                     "`%s', skipping.",
1010
0
                     osSrcFilename.c_str());
1011
0
            continue;
1012
0
        }
1013
1014
0
        auto poSrcSRS = poSrcDS->GetSpatialRef();
1015
        // If not set target srs, test that the current file uses same
1016
        // projection as others.
1017
0
        if (oTargetSRS.IsEmpty())
1018
0
        {
1019
0
            if (!oAlreadyExistingSRS.IsEmpty())
1020
0
            {
1021
0
                if (poSrcSRS == nullptr ||
1022
0
                    !poSrcSRS->IsSame(&oAlreadyExistingSRS))
1023
0
                {
1024
0
                    CPLError(
1025
0
                        CE_Warning, CPLE_AppDefined,
1026
0
                        "%s is not using the same projection system "
1027
0
                        "as other files in the tileindex.\n"
1028
0
                        "This may cause problems when using it in MapServer "
1029
0
                        "for example.\n"
1030
0
                        "Use -t_srs option to set target projection system. %s",
1031
0
                        osSrcFilename.c_str(),
1032
0
                        psOptions->bSkipDifferentProjection
1033
0
                            ? "Skipping this file."
1034
0
                            : "");
1035
0
                    if (psOptions->bSkipDifferentProjection)
1036
0
                    {
1037
0
                        continue;
1038
0
                    }
1039
0
                }
1040
0
            }
1041
0
            else
1042
0
            {
1043
0
                if (poSrcSRS)
1044
0
                    oAlreadyExistingSRS = *poSrcSRS;
1045
0
            }
1046
0
        }
1047
1048
0
        const int nXSize = poSrcDS->GetRasterXSize();
1049
0
        const int nYSize = poSrcDS->GetRasterYSize();
1050
0
        if (nXSize == 0 || nYSize == 0)
1051
0
        {
1052
0
            CPLError(CE_Warning, CPLE_AppDefined,
1053
0
                     "%s has 0 width or height. Skipping",
1054
0
                     osSrcFilename.c_str());
1055
0
            continue;
1056
0
        }
1057
1058
0
        double adfX[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
1059
0
        double adfY[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
1060
0
        adfX[0] = adfGeoTransform[0] + 0 * adfGeoTransform[1] +
1061
0
                  0 * adfGeoTransform[2];
1062
0
        adfY[0] = adfGeoTransform[3] + 0 * adfGeoTransform[4] +
1063
0
                  0 * adfGeoTransform[5];
1064
1065
0
        adfX[1] = adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
1066
0
                  0 * adfGeoTransform[2];
1067
0
        adfY[1] = adfGeoTransform[3] + nXSize * adfGeoTransform[4] +
1068
0
                  0 * adfGeoTransform[5];
1069
1070
0
        adfX[2] = adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
1071
0
                  nYSize * adfGeoTransform[2];
1072
0
        adfY[2] = adfGeoTransform[3] + nXSize * adfGeoTransform[4] +
1073
0
                  nYSize * adfGeoTransform[5];
1074
1075
0
        adfX[3] = adfGeoTransform[0] + 0 * adfGeoTransform[1] +
1076
0
                  nYSize * adfGeoTransform[2];
1077
0
        adfY[3] = adfGeoTransform[3] + 0 * adfGeoTransform[4] +
1078
0
                  nYSize * adfGeoTransform[5];
1079
1080
0
        adfX[4] = adfGeoTransform[0] + 0 * adfGeoTransform[1] +
1081
0
                  0 * adfGeoTransform[2];
1082
0
        adfY[4] = adfGeoTransform[3] + 0 * adfGeoTransform[4] +
1083
0
                  0 * adfGeoTransform[5];
1084
1085
        // If set target srs, do the forward transformation of all points.
1086
0
        if (!oTargetSRS.IsEmpty() && poSrcSRS)
1087
0
        {
1088
0
            if (!poSrcSRS->IsSame(&oTargetSRS))
1089
0
            {
1090
0
                auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
1091
0
                    OGRCreateCoordinateTransformation(poSrcSRS, &oTargetSRS));
1092
0
                if (!poCT || !poCT->Transform(5, adfX, adfY, nullptr))
1093
0
                {
1094
0
                    CPLError(CE_Warning, CPLE_AppDefined,
1095
0
                             "unable to transform points from source "
1096
0
                             "SRS `%s' to target SRS `%s' for file `%s' - file "
1097
0
                             "skipped",
1098
0
                             poSrcDS->GetProjectionRef(),
1099
0
                             psOptions->osTargetSRS.c_str(),
1100
0
                             osFileNameToWrite.c_str());
1101
0
                    continue;
1102
0
                }
1103
0
            }
1104
0
        }
1105
0
        else if (bIsGTIContext && !oAlreadyExistingSRS.IsEmpty() &&
1106
0
                 (poSrcSRS == nullptr ||
1107
0
                  !poSrcSRS->IsSame(&oAlreadyExistingSRS)))
1108
0
        {
1109
0
            CPLError(
1110
0
                CE_Failure, CPLE_AppDefined,
1111
0
                "%s is not using the same projection system "
1112
0
                "as other files in the tileindex. This is not compatible of "
1113
0
                "GTI use. Use -t_srs option to reproject tile extents "
1114
0
                "to a common SRS.",
1115
0
                osSrcFilename.c_str());
1116
0
            return nullptr;
1117
0
        }
1118
1119
0
        const double dfMinX =
1120
0
            std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3]));
1121
0
        const double dfMinY =
1122
0
            std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
1123
0
        const double dfMaxX =
1124
0
            std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3]));
1125
0
        const double dfMaxY =
1126
0
            std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
1127
0
        const double dfRes =
1128
0
            sqrt((dfMaxX - dfMinX) * (dfMaxY - dfMinY) / nXSize / nYSize);
1129
0
        if (!std::isnan(psOptions->dfMinPixelSize) &&
1130
0
            dfRes < psOptions->dfMinPixelSize)
1131
0
        {
1132
0
            CPLError(CE_Warning, CPLE_AppDefined,
1133
0
                     "%s has %f as pixel size (< %f). Skipping",
1134
0
                     osSrcFilename.c_str(), dfRes, psOptions->dfMinPixelSize);
1135
0
            continue;
1136
0
        }
1137
0
        if (!std::isnan(psOptions->dfMaxPixelSize) &&
1138
0
            dfRes > psOptions->dfMaxPixelSize)
1139
0
        {
1140
0
            CPLError(CE_Warning, CPLE_AppDefined,
1141
0
                     "%s has %f as pixel size (> %f). Skipping",
1142
0
                     osSrcFilename.c_str(), dfRes, psOptions->dfMaxPixelSize);
1143
0
            continue;
1144
0
        }
1145
1146
0
        auto poFeature = std::make_unique<OGRFeature>(poLayerDefn);
1147
0
        poFeature->SetField(ti_field, osFileNameToWrite.c_str());
1148
1149
0
        if (i_SrcSRSName >= 0 && poSrcSRS)
1150
0
        {
1151
0
            const char *pszAuthorityCode = poSrcSRS->GetAuthorityCode(nullptr);
1152
0
            const char *pszAuthorityName = poSrcSRS->GetAuthorityName(nullptr);
1153
0
            if (psOptions->eSrcSRSFormat == FORMAT_AUTO)
1154
0
            {
1155
0
                if (pszAuthorityName != nullptr && pszAuthorityCode != nullptr)
1156
0
                {
1157
0
                    poFeature->SetField(i_SrcSRSName,
1158
0
                                        CPLSPrintf("%s:%s", pszAuthorityName,
1159
0
                                                   pszAuthorityCode));
1160
0
                }
1161
0
                else if (nMaxFieldSize == 0 ||
1162
0
                         strlen(poSrcDS->GetProjectionRef()) <=
1163
0
                             static_cast<size_t>(nMaxFieldSize))
1164
0
                {
1165
0
                    poFeature->SetField(i_SrcSRSName,
1166
0
                                        poSrcDS->GetProjectionRef());
1167
0
                }
1168
0
                else
1169
0
                {
1170
0
                    char *pszProj4 = nullptr;
1171
0
                    if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
1172
0
                    {
1173
0
                        poFeature->SetField(i_SrcSRSName, pszProj4);
1174
0
                    }
1175
0
                    else
1176
0
                    {
1177
0
                        poFeature->SetField(i_SrcSRSName,
1178
0
                                            poSrcDS->GetProjectionRef());
1179
0
                    }
1180
0
                    CPLFree(pszProj4);
1181
0
                }
1182
0
            }
1183
0
            else if (psOptions->eSrcSRSFormat == FORMAT_WKT)
1184
0
            {
1185
0
                if (nMaxFieldSize == 0 ||
1186
0
                    strlen(poSrcDS->GetProjectionRef()) <=
1187
0
                        static_cast<size_t>(nMaxFieldSize))
1188
0
                {
1189
0
                    poFeature->SetField(i_SrcSRSName,
1190
0
                                        poSrcDS->GetProjectionRef());
1191
0
                }
1192
0
                else
1193
0
                {
1194
0
                    CPLError(CE_Warning, CPLE_AppDefined,
1195
0
                             "Cannot write WKT for file %s as it is too long!",
1196
0
                             osFileNameToWrite.c_str());
1197
0
                }
1198
0
            }
1199
0
            else if (psOptions->eSrcSRSFormat == FORMAT_PROJ)
1200
0
            {
1201
0
                char *pszProj4 = nullptr;
1202
0
                if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
1203
0
                {
1204
0
                    poFeature->SetField(i_SrcSRSName, pszProj4);
1205
0
                }
1206
0
                CPLFree(pszProj4);
1207
0
            }
1208
0
            else if (psOptions->eSrcSRSFormat == FORMAT_EPSG)
1209
0
            {
1210
0
                if (pszAuthorityName != nullptr && pszAuthorityCode != nullptr)
1211
0
                    poFeature->SetField(i_SrcSRSName,
1212
0
                                        CPLSPrintf("%s:%s", pszAuthorityName,
1213
0
                                                   pszAuthorityCode));
1214
0
            }
1215
0
        }
1216
1217
0
        for (const auto &oFetchMD : psOptions->aoFetchMD)
1218
0
        {
1219
0
            if (EQUAL(oFetchMD.osRasterItemName.c_str(), "{PIXEL_SIZE}"))
1220
0
            {
1221
0
                poFeature->SetField(oFetchMD.osFieldName.c_str(), dfRes);
1222
0
                continue;
1223
0
            }
1224
1225
0
            const char *pszMD =
1226
0
                poSrcDS->GetMetadataItem(oFetchMD.osRasterItemName.c_str());
1227
0
            if (pszMD)
1228
0
            {
1229
0
                if (EQUAL(oFetchMD.osRasterItemName.c_str(),
1230
0
                          "TIFFTAG_DATETIME"))
1231
0
                {
1232
0
                    int nYear, nMonth, nDay, nHour, nMin, nSec;
1233
0
                    if (sscanf(pszMD, "%04d:%02d:%02d %02d:%02d:%02d", &nYear,
1234
0
                               &nMonth, &nDay, &nHour, &nMin, &nSec) == 6)
1235
0
                    {
1236
0
                        poFeature->SetField(
1237
0
                            oFetchMD.osFieldName.c_str(),
1238
0
                            CPLSPrintf("%04d/%02d/%02d %02d:%02d:%02d", nYear,
1239
0
                                       nMonth, nDay, nHour, nMin, nSec));
1240
0
                        continue;
1241
0
                    }
1242
0
                }
1243
0
                poFeature->SetField(oFetchMD.osFieldName.c_str(), pszMD);
1244
0
            }
1245
0
        }
1246
1247
0
        auto poPoly = std::make_unique<OGRPolygon>();
1248
0
        auto poRing = std::make_unique<OGRLinearRing>();
1249
0
        for (int k = 0; k < 5; k++)
1250
0
            poRing->addPoint(adfX[k], adfY[k]);
1251
0
        poPoly->addRing(std::move(poRing));
1252
0
        poFeature->SetGeometryDirectly(poPoly.release());
1253
1254
0
        if (poLayer->CreateFeature(poFeature.get()) != OGRERR_NONE)
1255
0
        {
1256
0
            CPLError(CE_Failure, CPLE_AppDefined,
1257
0
                     "Failed to create feature in tile index.");
1258
0
            return nullptr;
1259
0
        }
1260
1261
0
        ++iCur;
1262
0
        if (psOptions->pfnProgress &&
1263
0
            !psOptions->pfnProgress(static_cast<double>(iCur) / nTotal, "",
1264
0
                                    psOptions->pProgressData))
1265
0
        {
1266
0
            return nullptr;
1267
0
        }
1268
0
        if (iCur >= nSrcCount)
1269
0
            ++nTotal;
1270
0
    }
1271
0
    if (psOptions->pfnProgress)
1272
0
        psOptions->pfnProgress(1.0, "", psOptions->pProgressData);
1273
1274
0
    if (poTileIndexDSUnique)
1275
0
        return GDALDataset::ToHandle(poTileIndexDSUnique.release());
1276
0
    else
1277
0
        return GDALDataset::ToHandle(poTileIndexDS);
1278
0
}
1279
1280
/************************************************************************/
1281
/*                             SanitizeSRS                              */
1282
/************************************************************************/
1283
1284
static char *SanitizeSRS(const char *pszUserInput)
1285
1286
0
{
1287
0
    OGRSpatialReferenceH hSRS;
1288
0
    char *pszResult = nullptr;
1289
1290
0
    CPLErrorReset();
1291
1292
0
    hSRS = OSRNewSpatialReference(nullptr);
1293
0
    if (OSRSetFromUserInput(hSRS, pszUserInput) == OGRERR_NONE)
1294
0
        OSRExportToWkt(hSRS, &pszResult);
1295
0
    else
1296
0
    {
1297
0
        CPLError(CE_Failure, CPLE_AppDefined, "Translating SRS failed:\n%s",
1298
0
                 pszUserInput);
1299
0
    }
1300
1301
0
    OSRDestroySpatialReference(hSRS);
1302
1303
0
    return pszResult;
1304
0
}
1305
1306
/************************************************************************/
1307
/*                          GDALTileIndexOptionsNew()                   */
1308
/************************************************************************/
1309
1310
/**
1311
 * Allocates a GDALTileIndexOptions struct.
1312
 *
1313
 * @param papszArgv NULL terminated list of options (potentially including
1314
 * filename and open options too), or NULL. The accepted options are the ones of
1315
 * the <a href="/programs/gdaltindex.html">gdaltindex</a> utility.
1316
 * @param psOptionsForBinary (output) may be NULL (and should generally be
1317
 * NULL), otherwise (gdaltindex_bin.cpp use case) must be allocated with
1318
 * GDALTileIndexOptionsForBinaryNew() prior to this function. Will be filled
1319
 * with potentially present filename, open options,...
1320
 * @return pointer to the allocated GDALTileIndexOptions struct. Must be freed
1321
 * with GDALTileIndexOptionsFree().
1322
 *
1323
 * @since GDAL 3.9
1324
 */
1325
1326
GDALTileIndexOptions *
1327
GDALTileIndexOptionsNew(char **papszArgv,
1328
                        GDALTileIndexOptionsForBinary *psOptionsForBinary)
1329
0
{
1330
0
    auto psOptions = std::make_unique<GDALTileIndexOptions>();
1331
1332
    /* -------------------------------------------------------------------- */
1333
    /*      Parse arguments.                                                */
1334
    /* -------------------------------------------------------------------- */
1335
1336
0
    CPLStringList aosArgv;
1337
1338
0
    if (papszArgv)
1339
0
    {
1340
0
        const int nArgc = CSLCount(papszArgv);
1341
0
        for (int i = 0; i < nArgc; i++)
1342
0
        {
1343
0
            aosArgv.AddString(papszArgv[i]);
1344
0
        }
1345
0
    }
1346
1347
0
    try
1348
0
    {
1349
0
        auto argParser = GDALTileIndexAppOptionsGetParser(psOptions.get(),
1350
0
                                                          psOptionsForBinary);
1351
0
        argParser->parse_args_without_binary_name(aosArgv.List());
1352
1353
        // Check all no store_into args
1354
0
        if (auto oTr = argParser->present<std::vector<double>>("-tr"))
1355
0
        {
1356
0
            psOptions->xres = (*oTr)[0];
1357
0
            psOptions->yres = (*oTr)[1];
1358
0
        }
1359
1360
0
        if (auto oTargetExtent = argParser->present<std::vector<double>>("-te"))
1361
0
        {
1362
0
            psOptions->xmin = (*oTargetExtent)[0];
1363
0
            psOptions->ymin = (*oTargetExtent)[1];
1364
0
            psOptions->xmax = (*oTargetExtent)[2];
1365
0
            psOptions->ymax = (*oTargetExtent)[3];
1366
0
        }
1367
1368
0
        if (auto fetchMd =
1369
0
                argParser->present<std::vector<std::string>>("-fetch_md"))
1370
0
        {
1371
1372
0
            CPLAssert(fetchMd->size() % 3 == 0);
1373
1374
            // Loop
1375
0
            for (size_t i = 0; i < fetchMd->size(); i += 3)
1376
0
            {
1377
0
                OGRFieldType type;
1378
0
                const auto &typeName{fetchMd->at(i + 2)};
1379
0
                if (typeName == "String")
1380
0
                {
1381
0
                    type = OFTString;
1382
0
                }
1383
0
                else if (typeName == "Integer")
1384
0
                {
1385
0
                    type = OFTInteger;
1386
0
                }
1387
0
                else if (typeName == "Integer64")
1388
0
                {
1389
0
                    type = OFTInteger64;
1390
0
                }
1391
0
                else if (typeName == "Real")
1392
0
                {
1393
0
                    type = OFTReal;
1394
0
                }
1395
0
                else if (typeName == "Date")
1396
0
                {
1397
0
                    type = OFTDate;
1398
0
                }
1399
0
                else if (typeName == "DateTime")
1400
0
                {
1401
0
                    type = OFTDateTime;
1402
0
                }
1403
0
                else
1404
0
                {
1405
0
                    CPLError(CE_Failure, CPLE_AppDefined,
1406
0
                             "-fetch_md requires a valid type name as third "
1407
0
                             "argument: %s was given.",
1408
0
                             fetchMd->at(i).c_str());
1409
0
                    return nullptr;
1410
0
                }
1411
1412
0
                const GDALTileIndexRasterMetadata oMD{type, fetchMd->at(i + 1),
1413
0
                                                      fetchMd->at(i)};
1414
0
                psOptions->aoFetchMD.push_back(std::move(oMD));
1415
0
            }
1416
0
        }
1417
1418
        // Check -t_srs
1419
0
        if (!psOptions->osTargetSRS.empty())
1420
0
        {
1421
0
            auto sanitized{SanitizeSRS(psOptions->osTargetSRS.c_str())};
1422
0
            if (sanitized)
1423
0
            {
1424
0
                psOptions->osTargetSRS = sanitized;
1425
0
                CPLFree(sanitized);
1426
0
            }
1427
0
            else
1428
0
            {
1429
                // Error was already reported by SanitizeSRS, just return nullptr
1430
0
                psOptions->osTargetSRS.clear();
1431
0
                return nullptr;
1432
0
            }
1433
0
        }
1434
0
    }
1435
0
    catch (const std::exception &error)
1436
0
    {
1437
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
1438
0
        return nullptr;
1439
0
    }
1440
1441
0
    return psOptions.release();
1442
0
}
1443
1444
/************************************************************************/
1445
/*                        GDALTileIndexOptionsFree()                    */
1446
/************************************************************************/
1447
1448
/**
1449
 * Frees the GDALTileIndexOptions struct.
1450
 *
1451
 * @param psOptions the options struct for GDALTileIndex().
1452
 *
1453
 * @since GDAL 3.9
1454
 */
1455
1456
void GDALTileIndexOptionsFree(GDALTileIndexOptions *psOptions)
1457
0
{
1458
0
    delete psOptions;
1459
0
}
1460
1461
/************************************************************************/
1462
/*                 GDALTileIndexOptionsSetProgress()                    */
1463
/************************************************************************/
1464
1465
/**
1466
 * Set a progress function.
1467
 *
1468
 * @param psOptions the options struct for GDALTileIndex().
1469
 * @param pfnProgress the progress callback.
1470
 * @param pProgressData the user data for the progress callback.
1471
 *
1472
 * @since GDAL 3.11
1473
 */
1474
1475
void GDALTileIndexOptionsSetProgress(GDALTileIndexOptions *psOptions,
1476
                                     GDALProgressFunc pfnProgress,
1477
                                     void *pProgressData)
1478
0
{
1479
0
    psOptions->pfnProgress = pfnProgress;
1480
0
    psOptions->pProgressData = pProgressData;
1481
0
}
1482
1483
#undef CHECK_HAS_ENOUGH_ADDITIONAL_ARGS