Coverage Report

Created: 2026-04-01 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdaltindex_lib.cpp
Line
Count
Source
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_md5.h"
17
#include "cpl_minixml.h"
18
#include "cpl_string.h"
19
#include "cpl_vsi_virtual.h"
20
#include "gdal_utils.h"
21
#include "gdal_priv.h"
22
#include "gdal_utils_priv.h"
23
#include "ogr_api.h"
24
#include "ograrrowarrayhelper.h"
25
#include "ogrsf_frmts.h"
26
#include "ogr_recordbatch.h"
27
#include "ogr_spatialref.h"
28
#include "commonutils.h"
29
#include "gdalargumentparser.h"
30
31
#include <ctype.h>
32
33
#include <algorithm>
34
#include <cmath>
35
#include <limits>
36
#include <set>
37
38
constexpr const char ARROW_FORMAT_INT32[] = "i";
39
constexpr const char ARROW_FORMAT_FLOAT32[] = "f";
40
constexpr const char ARROW_FORMAT_FLOAT64[] = "g";
41
constexpr const char ARROW_FORMAT_STRING[] = "u";
42
constexpr const char ARROW_FORMAT_BINARY[] = "z";
43
constexpr const char ARROW_FORMAT_LIST[] = "+l";
44
constexpr const char ARROW_FORMAT_STRUCT[] = "+s";
45
constexpr const char GEOPARQUET_GEOM_COL_NAME[] = "geometry";
46
47
constexpr int NUM_ITEMS_PROJ_BBOX = 4;
48
constexpr int NUM_ITEMS_PROJ_SHAPE = 2;
49
constexpr int NUM_ITEMS_PROJ_TRANSFORM = 9;
50
51
constexpr int ARROW_BUF_VALIDITY = 0;
52
constexpr int ARROW_BUF_DATA = 1;
53
constexpr int ARROW_BUF_BYTES = 2;
54
55
constexpr int COUNT_STAC_EXTENSIONS = 2;
56
57
typedef enum
58
{
59
    FORMAT_AUTO,
60
    FORMAT_WKT,
61
    FORMAT_EPSG,
62
    FORMAT_PROJ
63
} SrcSRSFormat;
64
65
/************************************************************************/
66
/*                     GDALTileIndexRasterMetadata                      */
67
/************************************************************************/
68
69
struct GDALTileIndexRasterMetadata
70
{
71
    OGRFieldType eType = OFTString;
72
    std::string osFieldName{};
73
    std::string osRasterItemName{};
74
};
75
76
/************************************************************************/
77
/*                         GDALTileIndexOptions                         */
78
/************************************************************************/
79
80
struct GDALTileIndexOptions
81
{
82
    bool bInvokedFromGdalRasterIndex = false;
83
    bool bOverwrite = false;
84
    bool bSkipErrors = false;
85
    std::string osFormat{};
86
    std::string osIndexLayerName{};
87
    std::string osLocationField = "location";
88
    CPLStringList aosLCO{};
89
    std::string osTargetSRS{};
90
    bool bWriteAbsolutePath = false;
91
    bool bSkipDifferentProjection = false;
92
    std::string osSrcSRSFieldName{};
93
    SrcSRSFormat eSrcSRSFormat = FORMAT_AUTO;
94
    double xres = std::numeric_limits<double>::quiet_NaN();
95
    double yres = std::numeric_limits<double>::quiet_NaN();
96
    double xmin = std::numeric_limits<double>::quiet_NaN();
97
    double ymin = std::numeric_limits<double>::quiet_NaN();
98
    double xmax = std::numeric_limits<double>::quiet_NaN();
99
    double ymax = std::numeric_limits<double>::quiet_NaN();
100
    std::string osBandCount{};
101
    std::string osNodata{};
102
    std::string osColorInterp{};
103
    std::string osDataType{};
104
    bool bMaskBand = false;
105
    std::vector<std::string> aosMetadata{};
106
    std::string osGTIFilename{};
107
    bool bRecursive = false;
108
    double dfMinPixelSize = std::numeric_limits<double>::quiet_NaN();
109
    double dfMaxPixelSize = std::numeric_limits<double>::quiet_NaN();
110
    std::vector<GDALTileIndexRasterMetadata> aoFetchMD{};
111
    std::set<std::string> oSetFilenameFilters{};
112
    GDALProgressFunc pfnProgress = nullptr;
113
    void *pProgressData = nullptr;
114
    std::string osProfile{};         // Only "STAC-GeoParquet" handled
115
    std::string osBaseURL{};         // Used for "STAC-GeoParquet"
116
    std::string osIdMethod{};        // Used for "STAC-GeoParquet"
117
    std::string osIdMetadataItem{};  // Used for "STAC-GeoParquet"
118
};
119
120
/************************************************************************/
121
/*                            ReleaseArray()                            */
122
/************************************************************************/
123
124
static void ReleaseArray(struct ArrowArray *array)
125
0
{
126
0
    CPLAssert(array->release != nullptr);
127
0
    if (array->buffers)
128
0
    {
129
0
        for (int i = 0; i < static_cast<int>(array->n_buffers); ++i)
130
0
            VSIFree(const_cast<void *>(array->buffers[i]));
131
0
        CPLFree(array->buffers);
132
0
    }
133
0
    if (array->children)
134
0
    {
135
0
        for (int i = 0; i < static_cast<int>(array->n_children); ++i)
136
0
        {
137
0
            if (array->children[i] && array->children[i]->release)
138
0
            {
139
0
                array->children[i]->release(array->children[i]);
140
0
                CPLFree(array->children[i]);
141
0
            }
142
0
        }
143
0
        CPLFree(array->children);
144
0
    }
145
0
    array->release = nullptr;
146
0
}
147
148
/************************************************************************/
149
/*                  GDALTileIndexAppOptionsGetParser()                  */
150
/************************************************************************/
151
152
static std::unique_ptr<GDALArgumentParser> GDALTileIndexAppOptionsGetParser(
153
    GDALTileIndexOptions *psOptions,
154
    GDALTileIndexOptionsForBinary *psOptionsForBinary)
155
0
{
156
0
    auto argParser = std::make_unique<GDALArgumentParser>(
157
0
        "gdaltindex", /* bForBinary=*/psOptionsForBinary != nullptr);
158
159
0
    argParser->add_description(
160
0
        _("Build a tile index from a list of datasets."));
161
162
0
    argParser->add_epilog(
163
0
        _("For more details, see the full documentation for gdaltindex at\n"
164
0
          "https://gdal.org/programs/gdaltindex.html"));
165
166
    // Hidden as used by gdal raster index
167
0
    argParser->add_argument("--invoked-from-gdal-raster-index")
168
0
        .store_into(psOptions->bInvokedFromGdalRasterIndex)
169
0
        .hidden();
170
171
    // Hidden as used by gdal raster index
172
0
    argParser->add_argument("-skip_errors")
173
0
        .store_into(psOptions->bSkipErrors)
174
0
        .hidden();
175
176
    // Hidden as used by gdal raster index
177
0
    argParser->add_argument("-profile")
178
0
        .store_into(psOptions->osProfile)
179
0
        .hidden();
180
181
    // Hidden as used by gdal raster index
182
0
    argParser->add_argument("--base-url")
183
0
        .store_into(psOptions->osBaseURL)
184
0
        .hidden();
185
186
    // Hidden as used by gdal raster index
187
0
    argParser->add_argument("--id-method")
188
0
        .store_into(psOptions->osIdMethod)
189
0
        .hidden();
190
191
    // Hidden as used by gdal raster index
192
0
    argParser->add_argument("--id-metadata-item")
193
0
        .store_into(psOptions->osIdMetadataItem)
194
0
        .hidden();
195
196
0
    argParser->add_argument("-overwrite")
197
0
        .flag()
198
0
        .store_into(psOptions->bOverwrite)
199
0
        .help(_("Overwrite the output tile index file if it already exists."));
200
201
0
    argParser->add_argument("-recursive")
202
0
        .flag()
203
0
        .store_into(psOptions->bRecursive)
204
0
        .help(_("Whether directories specified in <file_or_dir> should be "
205
0
                "explored recursively."));
206
207
0
    argParser->add_argument("-filename_filter")
208
0
        .metavar("<val>")
209
0
        .append()
210
0
        .store_into(psOptions->oSetFilenameFilters)
211
0
        .help(_("Pattern that the filenames contained in directories pointed "
212
0
                "by <file_or_dir> should follow."));
213
214
0
    argParser->add_argument("-min_pixel_size")
215
0
        .metavar("<val>")
216
0
        .store_into(psOptions->dfMinPixelSize)
217
0
        .help(_("Minimum pixel size in term of geospatial extent per pixel "
218
0
                "(resolution) that a raster should have to be selected."));
219
220
0
    argParser->add_argument("-max_pixel_size")
221
0
        .metavar("<val>")
222
0
        .store_into(psOptions->dfMaxPixelSize)
223
0
        .help(_("Maximum pixel size in term of geospatial extent per pixel "
224
0
                "(resolution) that a raster should have to be selected."));
225
226
0
    argParser->add_output_format_argument(psOptions->osFormat);
227
228
0
    argParser->add_argument("-tileindex")
229
0
        .metavar("<field_name>")
230
0
        .store_into(psOptions->osLocationField)
231
0
        .help(_("Name of the layer in the tile index file."));
232
233
0
    argParser->add_argument("-write_absolute_path")
234
0
        .flag()
235
0
        .store_into(psOptions->bWriteAbsolutePath)
236
0
        .help(_("Write the absolute path of the raster files in the tile index "
237
0
                "file."));
238
239
0
    argParser->add_argument("-skip_different_projection")
240
0
        .flag()
241
0
        .store_into(psOptions->bSkipDifferentProjection)
242
0
        .help(_(
243
0
            "Only files with the same projection as files already inserted in "
244
0
            "the tile index will be inserted (unless -t_srs is specified)."));
245
246
0
    argParser->add_argument("-t_srs")
247
0
        .metavar("<srs_def>")
248
0
        .store_into(psOptions->osTargetSRS)
249
0
        .help(_("Geometries of input files will be transformed to the desired "
250
0
                "target coordinate reference system."));
251
252
0
    argParser->add_argument("-src_srs_name")
253
0
        .metavar("<field_name>")
254
0
        .store_into(psOptions->osSrcSRSFieldName)
255
0
        .help(_("Name of the field in the tile index file where the source SRS "
256
0
                "will be stored."));
257
258
0
    argParser->add_argument("-src_srs_format")
259
0
        .metavar("{AUTO|WKT|EPSG|PROJ}")
260
0
        .choices("AUTO", "WKT", "EPSG", "PROJ")
261
0
        .action(
262
0
            [psOptions](const auto &f)
263
0
            {
264
0
                if (f == "WKT")
265
0
                    psOptions->eSrcSRSFormat = FORMAT_WKT;
266
0
                else if (f == "EPSG")
267
0
                    psOptions->eSrcSRSFormat = FORMAT_EPSG;
268
0
                else if (f == "PROJ")
269
0
                    psOptions->eSrcSRSFormat = FORMAT_PROJ;
270
0
                else
271
0
                    psOptions->eSrcSRSFormat = FORMAT_AUTO;
272
0
            })
273
0
        .help(_("Format of the source SRS to store in the tile index file."));
274
275
0
    argParser->add_argument("-lyr_name")
276
0
        .metavar("<name>")
277
0
        .store_into(psOptions->osIndexLayerName)
278
0
        .help(_("Name of the layer in the tile index file."));
279
280
0
    argParser->add_layer_creation_options_argument(psOptions->aosLCO);
281
282
    // GTI driver options
283
284
0
    argParser->add_argument("-gti_filename")
285
0
        .metavar("<filename>")
286
0
        .store_into(psOptions->osGTIFilename)
287
0
        .help(_("Filename of the XML Virtual Tile Index file to generate."));
288
289
    // NOTE: no store_into
290
0
    argParser->add_argument("-tr")
291
0
        .metavar("<xres> <yres>")
292
0
        .nargs(2)
293
0
        .scan<'g', double>()
294
0
        .help(_("Set target resolution."));
295
296
    // NOTE: no store_into
297
0
    argParser->add_argument("-te")
298
0
        .metavar("<xmin> <ymin> <xmax> <ymax>")
299
0
        .nargs(4)
300
0
        .scan<'g', double>()
301
0
        .help(_("Set target extent in SRS unit."));
302
303
0
    argParser->add_argument("-ot")
304
0
        .metavar("<datatype>")
305
0
        .store_into(psOptions->osDataType)
306
0
        .help(_("Output data type."));
307
308
0
    argParser->add_argument("-bandcount")
309
0
        .metavar("<val>")
310
0
        .store_into(psOptions->osBandCount)
311
0
        .help(_("Number of bands of the tiles of the tile index."));
312
313
0
    argParser->add_argument("-nodata")
314
0
        .metavar("<val>")
315
0
        .append()
316
0
        .store_into(psOptions->osNodata)
317
0
        .help(_("Nodata value of the tiles of the tile index."));
318
319
    // Should we use choices here?
320
0
    argParser->add_argument("-colorinterp")
321
0
        .metavar("<val>")
322
0
        .append()
323
0
        .store_into(psOptions->osColorInterp)
324
0
        .help(_("Color interpretation of of the tiles of the tile index: red, "
325
0
                "green, blue, alpha, gray, undefined."));
326
327
0
    argParser->add_argument("-mask")
328
0
        .flag()
329
0
        .store_into(psOptions->bMaskBand)
330
0
        .help(_("Add a mask band to the tiles of the tile index."));
331
332
0
    argParser->add_argument("-mo")
333
0
        .metavar("<name>=<value>")
334
0
        .append()
335
0
        .store_into(psOptions->aosMetadata)
336
0
        .help(_("Write an arbitrary layer metadata item, for formats that "
337
0
                "support layer metadata."));
338
339
    // NOTE: no store_into
340
0
    argParser->add_argument("-fetch_md")
341
0
        .nargs(3)
342
0
        .metavar("<gdal_md_name> <fld_name> <fld_type>")
343
0
        .append()
344
0
        .help("Fetch a metadata item from the raster tile and write it as a "
345
0
              "field in the tile index.");
346
347
0
    if (psOptionsForBinary)
348
0
    {
349
0
        argParser->add_quiet_argument(&psOptionsForBinary->bQuiet);
350
351
0
        argParser->add_argument("index_file")
352
0
            .metavar("<index_file>")
353
0
            .store_into(psOptionsForBinary->osDest)
354
0
            .help(_("The name of the output file to create/append to."));
355
356
0
        argParser->add_argument("file_or_dir")
357
0
            .metavar("<file_or_dir>")
358
0
            .nargs(argparse::nargs_pattern::at_least_one)
359
0
            .action([psOptionsForBinary](const std::string &s)
360
0
                    { psOptionsForBinary->aosSrcFiles.AddString(s.c_str()); })
361
0
            .help(_(
362
0
                "The input GDAL raster files or directory, can be multiple "
363
0
                "locations separated by spaces. Wildcards may also be used."));
364
0
    }
365
366
0
    return argParser;
367
0
}
368
369
/************************************************************************/
370
/*                   GDALTileIndexAppGetParserUsage()                   */
371
/************************************************************************/
372
373
std::string GDALTileIndexAppGetParserUsage()
374
0
{
375
0
    try
376
0
    {
377
0
        GDALTileIndexOptions sOptions;
378
0
        GDALTileIndexOptionsForBinary sOptionsForBinary;
379
0
        auto argParser =
380
0
            GDALTileIndexAppOptionsGetParser(&sOptions, &sOptionsForBinary);
381
0
        return argParser->usage();
382
0
    }
383
0
    catch (const std::exception &err)
384
0
    {
385
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
386
0
                 err.what());
387
0
        return std::string();
388
0
    }
389
0
}
390
391
/************************************************************************/
392
/*                      GDALTileIndexTileIterator                       */
393
/************************************************************************/
394
395
struct GDALTileIndexTileIterator
396
{
397
    const GDALTileIndexOptions *psOptions = nullptr;
398
    int nSrcCount = 0;
399
    const char *const *papszSrcDSNames = nullptr;
400
    std::string osCurDir{};
401
    int iCurSrc = 0;
402
    VSIDIR *psDir = nullptr;
403
404
    CPL_DISALLOW_COPY_ASSIGN(GDALTileIndexTileIterator)
405
406
    GDALTileIndexTileIterator(const GDALTileIndexOptions *psOptionsIn,
407
                              int nSrcCountIn,
408
                              const char *const *papszSrcDSNamesIn)
409
0
        : psOptions(psOptionsIn), nSrcCount(nSrcCountIn),
410
0
          papszSrcDSNames(papszSrcDSNamesIn)
411
0
    {
412
0
    }
413
414
    void reset()
415
0
    {
416
0
        if (psDir)
417
0
            VSICloseDir(psDir);
418
0
        psDir = nullptr;
419
0
        iCurSrc = 0;
420
0
    }
421
422
    std::string next()
423
0
    {
424
0
        while (true)
425
0
        {
426
0
            if (!psDir)
427
0
            {
428
0
                if (iCurSrc == nSrcCount)
429
0
                {
430
0
                    break;
431
0
                }
432
433
0
                VSIStatBufL sStatBuf;
434
0
                const char *pszCurName = papszSrcDSNames[iCurSrc++];
435
0
                if (VSIStatL(pszCurName, &sStatBuf) == 0 &&
436
0
                    VSI_ISDIR(sStatBuf.st_mode))
437
0
                {
438
0
                    auto poSrcDS = std::unique_ptr<GDALDataset>(
439
0
                        GDALDataset::Open(pszCurName, GDAL_OF_RASTER, nullptr,
440
0
                                          nullptr, nullptr));
441
0
                    if (poSrcDS)
442
0
                        return pszCurName;
443
444
0
                    osCurDir = pszCurName;
445
0
                    psDir = VSIOpenDir(
446
0
                        osCurDir.c_str(),
447
0
                        /*nDepth=*/psOptions->bRecursive ? -1 : 0, nullptr);
448
0
                    if (!psDir)
449
0
                    {
450
0
                        CPLError(CE_Failure, CPLE_AppDefined,
451
0
                                 "Cannot open directory %s", osCurDir.c_str());
452
0
                        return std::string();
453
0
                    }
454
0
                }
455
0
                else
456
0
                {
457
0
                    return pszCurName;
458
0
                }
459
0
            }
460
461
0
            auto psEntry = VSIGetNextDirEntry(psDir);
462
0
            if (!psEntry)
463
0
            {
464
0
                VSICloseDir(psDir);
465
0
                psDir = nullptr;
466
0
                continue;
467
0
            }
468
469
0
            if (!psOptions->oSetFilenameFilters.empty())
470
0
            {
471
0
                bool bMatchFound = false;
472
0
                const std::string osFilenameOnly =
473
0
                    CPLGetFilename(psEntry->pszName);
474
0
                for (const auto &osFilter : psOptions->oSetFilenameFilters)
475
0
                {
476
0
                    if (GDALPatternMatch(osFilenameOnly.c_str(),
477
0
                                         osFilter.c_str()))
478
0
                    {
479
0
                        bMatchFound = true;
480
0
                        break;
481
0
                    }
482
0
                }
483
0
                if (!bMatchFound)
484
0
                    continue;
485
0
            }
486
487
0
            std::string osFilename = CPLFormFilenameSafe(
488
0
                osCurDir.c_str(), psEntry->pszName, nullptr);
489
0
            if (VSI_ISDIR(psEntry->nMode))
490
0
            {
491
0
                auto poSrcDS = std::unique_ptr<GDALDataset>(
492
0
                    GDALDataset::Open(osFilename.c_str(), GDAL_OF_RASTER,
493
0
                                      nullptr, nullptr, nullptr));
494
0
                if (poSrcDS)
495
0
                {
496
0
                    return osFilename;
497
0
                }
498
0
                continue;
499
0
            }
500
501
0
            return osFilename;
502
0
        }
503
0
        return std::string();
504
0
    }
505
};
506
507
/************************************************************************/
508
/*                           GDALTileIndex()                            */
509
/************************************************************************/
510
511
/* clang-format off */
512
/**
513
 * Build a tile index from a list of datasets.
514
 *
515
 * This is the equivalent of the
516
 * <a href="/programs/gdaltindex.html">gdaltindex</a> utility.
517
 *
518
 * GDALTileIndexOptions* must be allocated and freed with
519
 * GDALTileIndexOptionsNew() and GDALTileIndexOptionsFree() respectively.
520
 *
521
 * @param pszDest the destination dataset path.
522
 * @param nSrcCount the number of input datasets.
523
 * @param papszSrcDSNames the list of input dataset names
524
 * @param psOptionsIn the options struct returned by GDALTileIndexOptionsNew() or
525
 * NULL.
526
 * @param pbUsageError pointer to a integer output variable to store if any
527
 * usage error has occurred.
528
 * @return the output dataset (new dataset that must be closed using
529
 * GDALClose()) or NULL in case of error.
530
 *
531
 * @since GDAL3.9
532
 */
533
/* clang-format on */
534
535
GDALDatasetH GDALTileIndex(const char *pszDest, int nSrcCount,
536
                           const char *const *papszSrcDSNames,
537
                           const GDALTileIndexOptions *psOptionsIn,
538
                           int *pbUsageError)
539
0
{
540
0
    return GDALTileIndexInternal(pszDest, nullptr, nullptr, nSrcCount,
541
0
                                 papszSrcDSNames, psOptionsIn, pbUsageError);
542
0
}
543
544
GDALDatasetH GDALTileIndexInternal(const char *pszDest,
545
                                   GDALDatasetH hTileIndexDS, OGRLayerH hLayer,
546
                                   int nSrcCount,
547
                                   const char *const *papszSrcDSNames,
548
                                   const GDALTileIndexOptions *psOptionsIn,
549
                                   int *pbUsageError)
550
0
{
551
0
    if (nSrcCount == 0)
552
0
    {
553
0
        CPLError(CE_Failure, CPLE_AppDefined, "No input dataset specified.");
554
555
0
        if (pbUsageError)
556
0
            *pbUsageError = TRUE;
557
0
        return nullptr;
558
0
    }
559
560
0
    auto psOptions = psOptionsIn
561
0
                         ? std::make_unique<GDALTileIndexOptions>(*psOptionsIn)
562
0
                         : std::make_unique<GDALTileIndexOptions>();
563
564
0
    GDALTileIndexTileIterator oGDALTileIndexTileIterator(
565
0
        psOptions.get(), nSrcCount, papszSrcDSNames);
566
567
    /* -------------------------------------------------------------------- */
568
    /*      Create and validate target SRS if given.                        */
569
    /* -------------------------------------------------------------------- */
570
0
    OGRSpatialReference oTargetSRS;
571
0
    if (!psOptions->osTargetSRS.empty())
572
0
    {
573
0
        if (psOptions->bSkipDifferentProjection)
574
0
        {
575
0
            CPLError(CE_Warning, CPLE_AppDefined,
576
0
                     "-skip_different_projections does not apply "
577
0
                     "when -t_srs is requested.");
578
0
        }
579
0
        oTargetSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
580
        // coverity[tainted_data]
581
0
        oTargetSRS.SetFromUserInput(psOptions->osTargetSRS.c_str());
582
0
    }
583
584
    /* -------------------------------------------------------------------- */
585
    /*      Open or create the target datasource                            */
586
    /* -------------------------------------------------------------------- */
587
588
0
    std::unique_ptr<GDALDataset> poTileIndexDSUnique;
589
0
    GDALDataset *poTileIndexDS = GDALDataset::FromHandle(hTileIndexDS);
590
0
    OGRLayer *poLayer = OGRLayer::FromHandle(hLayer);
591
0
    bool bExistingLayer = false;
592
0
    std::string osFormat;
593
594
0
    if (!hTileIndexDS)
595
0
    {
596
0
        if (psOptions->bOverwrite)
597
0
        {
598
0
            CPLPushErrorHandler(CPLQuietErrorHandler);
599
0
            auto hDriver = GDALIdentifyDriver(pszDest, nullptr);
600
0
            if (hDriver)
601
0
                GDALDeleteDataset(hDriver, pszDest);
602
0
            else
603
0
                VSIUnlink(pszDest);
604
0
            CPLPopErrorHandler();
605
0
        }
606
607
0
        poTileIndexDSUnique.reset(
608
0
            GDALDataset::Open(pszDest, GDAL_OF_VECTOR | GDAL_OF_UPDATE, nullptr,
609
0
                              nullptr, nullptr));
610
611
0
        if (poTileIndexDSUnique != nullptr)
612
0
        {
613
0
            auto poDriver = poTileIndexDSUnique->GetDriver();
614
0
            if (poDriver)
615
0
                osFormat = poDriver->GetDescription();
616
617
0
            if (poTileIndexDSUnique->GetLayerCount() == 1)
618
0
            {
619
0
                poLayer = poTileIndexDSUnique->GetLayer(0);
620
0
            }
621
0
            else
622
0
            {
623
0
                if (psOptions->osIndexLayerName.empty())
624
0
                {
625
0
                    CPLError(CE_Failure, CPLE_AppDefined,
626
0
                             "Multiple layers detected: -lyr_name must be "
627
0
                             "specified.");
628
0
                    if (pbUsageError)
629
0
                        *pbUsageError = true;
630
0
                    return nullptr;
631
0
                }
632
0
                CPLPushErrorHandler(CPLQuietErrorHandler);
633
0
                poLayer = poTileIndexDSUnique->GetLayerByName(
634
0
                    psOptions->osIndexLayerName.c_str());
635
0
                CPLPopErrorHandler();
636
0
            }
637
0
        }
638
0
        else
639
0
        {
640
0
            if (psOptions->osFormat.empty())
641
0
            {
642
0
                const auto aoDrivers =
643
0
                    GetOutputDriversFor(pszDest, GDAL_OF_VECTOR);
644
0
                if (aoDrivers.empty())
645
0
                {
646
0
                    CPLError(CE_Failure, CPLE_AppDefined,
647
0
                             "Cannot guess driver for %s", pszDest);
648
0
                    return nullptr;
649
0
                }
650
0
                else
651
0
                {
652
0
                    if (aoDrivers.size() > 1)
653
0
                    {
654
0
                        CPLError(
655
0
                            CE_Warning, CPLE_AppDefined,
656
0
                            "Several drivers matching %s extension. Using %s",
657
0
                            CPLGetExtensionSafe(pszDest).c_str(),
658
0
                            aoDrivers[0].c_str());
659
0
                    }
660
0
                    osFormat = aoDrivers[0];
661
0
                }
662
0
            }
663
0
            else
664
0
            {
665
0
                osFormat = psOptions->osFormat;
666
0
            }
667
668
0
            auto poDriver =
669
0
                GetGDALDriverManager()->GetDriverByName(osFormat.c_str());
670
0
            if (poDriver == nullptr)
671
0
            {
672
0
                CPLError(CE_Warning, CPLE_AppDefined,
673
0
                         "%s driver not available.", osFormat.c_str());
674
0
                return nullptr;
675
0
            }
676
677
0
            poTileIndexDSUnique.reset(
678
0
                poDriver->Create(pszDest, 0, 0, 0, GDT_Unknown, nullptr));
679
0
            if (!poTileIndexDSUnique)
680
0
                return nullptr;
681
0
        }
682
683
0
        poTileIndexDS = poTileIndexDSUnique.get();
684
0
    }
685
686
0
    const bool bIsSTACGeoParquet =
687
0
        EQUAL(psOptions->osProfile.c_str(), "STAC-GeoParquet");
688
689
0
    auto poOutDrv = poTileIndexDS->GetDriver();
690
0
    if (osFormat.empty() && poOutDrv)
691
0
        osFormat = poOutDrv->GetDescription();
692
693
0
    const char *pszVal =
694
0
        poOutDrv ? poOutDrv->GetMetadataItem(GDAL_DMD_MAX_STRING_LENGTH)
695
0
                 : nullptr;
696
0
    const int nMaxFieldSize = pszVal ? atoi(pszVal) : 0;
697
698
0
    const bool bFailOnErrors =
699
0
        psOptions->bInvokedFromGdalRasterIndex && !psOptions->bSkipErrors;
700
0
    bool bSkipFirstTile = false;
701
702
    // Configurable mostly/only for autotest purposes.
703
0
    const int nMaxBatchSize = std::max(
704
0
        1, atoi(CPLGetConfigOption("GDAL_RASTER_INDEX_BATCH_SIZE", "65536")));
705
706
0
    std::vector<ArrowSchema> topSchemas;
707
0
    std::vector<ArrowSchema *> topSchemasPointers;
708
0
    std::vector<std::unique_ptr<ArrowSchema>> auxSchemas;
709
0
    std::vector<ArrowSchema *> stacExtensionsSchemaChildren,
710
0
        linksSchemaChildren, linksItemSchemaChildren, assetsSchemaChildren,
711
0
        imageAssetSchemaChildren, imageAssetRolesSchemaChildren,
712
0
        bandsSchemaChildren, bandsItemSchemaChildren, projBboxSchemaChildren,
713
0
        projShapeSchemaChildren, projTransformSchemaChildren;
714
0
    ArrowSchema topSchema{};
715
0
    const auto noop_release = [](struct ArrowSchema *) {};
716
0
    const auto AddTopSchema = [&topSchemas, &noop_release]() -> ArrowSchema &
717
0
    {
718
0
        topSchemas.push_back(ArrowSchema{});
719
0
        topSchemas.back().release = noop_release;
720
0
        return topSchemas.back();
721
0
    };
722
723
0
    if (poLayer)
724
0
    {
725
0
        bExistingLayer = true;
726
0
    }
727
0
    else
728
0
    {
729
0
        std::string osLayerName;
730
0
        if (psOptions->osIndexLayerName.empty())
731
0
        {
732
0
            VSIStatBuf sStat;
733
0
            if (EQUAL(osFormat.c_str(), "ESRI Shapefile") ||
734
0
                VSIStat(pszDest, &sStat) == 0)
735
0
            {
736
0
                osLayerName = CPLGetBasenameSafe(pszDest);
737
0
            }
738
0
            else
739
0
            {
740
0
                CPLError(CE_Failure, CPLE_AppDefined,
741
0
                         "-lyr_name must be specified.");
742
0
                if (pbUsageError)
743
0
                    *pbUsageError = true;
744
0
                return nullptr;
745
0
            }
746
0
        }
747
0
        else
748
0
        {
749
0
            if (psOptions->bOverwrite)
750
0
            {
751
0
                for (int i = 0; i < poTileIndexDS->GetLayerCount(); ++i)
752
0
                {
753
0
                    auto poExistingLayer = poTileIndexDS->GetLayer(i);
754
0
                    if (poExistingLayer && poExistingLayer->GetName() ==
755
0
                                               psOptions->osIndexLayerName)
756
0
                    {
757
0
                        if (poTileIndexDS->DeleteLayer(i) != OGRERR_NONE)
758
0
                            return nullptr;
759
0
                        break;
760
0
                    }
761
0
                }
762
0
            }
763
764
0
            osLayerName = psOptions->osIndexLayerName;
765
0
        }
766
767
        /* get spatial reference for output file from target SRS (if set) */
768
        /* or from first input file */
769
0
        OGRSpatialReference oSRS;
770
0
        if (!oTargetSRS.IsEmpty())
771
0
        {
772
0
            oSRS = oTargetSRS;
773
0
        }
774
0
        else
775
0
        {
776
0
            std::string osFilename = oGDALTileIndexTileIterator.next();
777
0
            if (osFilename.empty())
778
0
            {
779
0
                CPLError(CE_Failure, CPLE_AppDefined, "Cannot find any tile");
780
0
                return nullptr;
781
0
            }
782
0
            oGDALTileIndexTileIterator.reset();
783
0
            std::unique_ptr<CPLTurnFailureIntoWarningBackuper>
784
0
                poFailureIntoWarning;
785
0
            if (!bFailOnErrors)
786
0
                poFailureIntoWarning =
787
0
                    std::make_unique<CPLTurnFailureIntoWarningBackuper>();
788
0
            CPL_IGNORE_RET_VAL(poFailureIntoWarning);
789
0
            auto poSrcDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
790
0
                osFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
791
0
                nullptr, nullptr, nullptr));
792
0
            if (!poSrcDS)
793
0
            {
794
0
                CPLError(bFailOnErrors ? CE_Failure : CE_Warning,
795
0
                         CPLE_AppDefined, "Unable to open %s%s.",
796
0
                         osFilename.c_str(), bFailOnErrors ? "" : ", skipping");
797
0
                if (bFailOnErrors)
798
0
                    return nullptr;
799
0
                bSkipFirstTile = true;
800
0
            }
801
0
            else
802
0
            {
803
0
                auto poSrcSRS = poSrcDS->GetSpatialRef();
804
0
                if (poSrcSRS)
805
0
                    oSRS = *poSrcSRS;
806
0
            }
807
0
        }
808
809
0
        if (bIsSTACGeoParquet)
810
0
        {
811
0
            psOptions->aosLCO.SetNameValue("ROW_GROUP_SIZE",
812
0
                                           CPLSPrintf("%d", nMaxBatchSize));
813
0
            psOptions->aosLCO.SetNameValue("GEOMETRY_ENCODING", "WKB");
814
0
            psOptions->aosLCO.SetNameValue("GEOMETRY_NAME",
815
0
                                           GEOPARQUET_GEOM_COL_NAME);
816
0
            psOptions->aosLCO.SetNameValue("FID", "");
817
0
            psOptions->aosLCO.SetNameValue("WRITE_COVERING_BBOX", "YES");
818
0
            psOptions->aosLCO.SetNameValue("COVERING_BBOX_NAME", "bbox");
819
0
            if (CPLTestBool(
820
0
                    psOptions->aosLCO.FetchNameValueDef("SORT_BY_BBOX", "NO")))
821
0
            {
822
0
                CPLError(CE_Failure, CPLE_AppDefined,
823
0
                         "SORT_BY_BBOX=YES is not compatible with "
824
0
                         "STAC-GeoParquet profile");
825
0
                return nullptr;
826
0
            }
827
0
        }
828
829
0
        poLayer = poTileIndexDS->CreateLayer(
830
0
            osLayerName.c_str(), oSRS.IsEmpty() ? nullptr : &oSRS, wkbPolygon,
831
0
            psOptions->aosLCO.List());
832
0
        if (!poLayer)
833
0
            return nullptr;
834
835
0
        if (bIsSTACGeoParquet)
836
0
        {
837
0
            const auto AddAuxSchema = [&auxSchemas, &noop_release]()
838
0
            {
839
0
                auto newSchema = std::make_unique<ArrowSchema>(ArrowSchema{});
840
0
                newSchema->release = noop_release;
841
0
                auxSchemas.push_back(std::move(newSchema));
842
0
                return auxSchemas.back().get();
843
0
            };
844
845
            // "id" field
846
0
            {
847
0
                auto &schema = AddTopSchema();
848
0
                schema.format = ARROW_FORMAT_STRING;
849
0
                schema.name = "id";
850
0
                if (!poLayer->CreateFieldFromArrowSchema(&schema))
851
0
                    return nullptr;
852
0
            }
853
            // "stac_extensions" field
854
0
            {
855
0
                auto &schema = AddTopSchema();
856
857
0
                auto &sub_schema = *AddAuxSchema();
858
0
                stacExtensionsSchemaChildren.push_back(&sub_schema);
859
860
0
                schema.format = ARROW_FORMAT_LIST;
861
0
                schema.name = "stac_extensions";
862
0
                schema.n_children = 1;
863
0
                schema.children = stacExtensionsSchemaChildren.data();
864
0
                sub_schema.format = ARROW_FORMAT_STRING;
865
0
                sub_schema.name = "item";
866
0
                if (!poLayer->CreateFieldFromArrowSchema(&schema))
867
0
                    return nullptr;
868
0
            }
869
            // "links" field
870
0
            {
871
0
                auto &links = AddTopSchema();
872
873
0
                auto &item = *AddAuxSchema();
874
0
                linksSchemaChildren.push_back(&item);
875
876
0
                auto &href = *AddAuxSchema();
877
0
                linksItemSchemaChildren.push_back(&href);
878
879
0
                auto &rel = *AddAuxSchema();
880
0
                linksItemSchemaChildren.push_back(&rel);
881
882
0
                auto &type = *AddAuxSchema();
883
0
                linksItemSchemaChildren.push_back(&type);
884
885
0
                auto &title = *AddAuxSchema();
886
0
                linksItemSchemaChildren.push_back(&title);
887
888
0
                links.format = ARROW_FORMAT_LIST;
889
0
                links.name = "links";
890
0
                links.n_children = linksSchemaChildren.size();
891
0
                links.children = linksSchemaChildren.data();
892
893
0
                item.format = ARROW_FORMAT_STRUCT;
894
0
                item.name = "item";
895
0
                item.n_children = linksItemSchemaChildren.size();
896
0
                item.children = linksItemSchemaChildren.data();
897
898
0
                href.format = ARROW_FORMAT_STRING;
899
0
                href.name = "href";
900
901
0
                rel.format = ARROW_FORMAT_STRING;
902
0
                rel.name = "rel";
903
904
0
                type.format = ARROW_FORMAT_STRING;
905
0
                type.name = "type";
906
0
                type.flags = ARROW_FLAG_NULLABLE;
907
908
0
                title.format = ARROW_FORMAT_STRING;
909
0
                title.name = "title";
910
0
                title.flags = ARROW_FLAG_NULLABLE;
911
912
0
                if (!poLayer->CreateFieldFromArrowSchema(&links))
913
0
                    return nullptr;
914
0
            }
915
916
            // "assets" field
917
0
            {
918
0
                auto &assets = AddTopSchema();
919
920
0
                auto &image = *AddAuxSchema();
921
0
                assetsSchemaChildren.push_back(&image);
922
923
0
                auto &href = *AddAuxSchema();
924
0
                imageAssetSchemaChildren.push_back(&href);
925
926
0
                auto &roles = *AddAuxSchema();
927
0
                imageAssetSchemaChildren.push_back(&roles);
928
929
0
                auto &title = *AddAuxSchema();
930
0
                imageAssetSchemaChildren.push_back(&title);
931
932
0
                auto &type = *AddAuxSchema();
933
0
                imageAssetSchemaChildren.push_back(&type);
934
935
0
                auto &roles_item = *AddAuxSchema();
936
0
                imageAssetRolesSchemaChildren.push_back(&roles_item);
937
938
0
                assets.format = ARROW_FORMAT_STRUCT;
939
0
                assets.name = "assets";
940
0
                assets.n_children = assetsSchemaChildren.size();
941
0
                assets.children = assetsSchemaChildren.data();
942
943
0
                image.format = ARROW_FORMAT_STRUCT;
944
0
                image.name = "image";
945
0
                image.n_children = imageAssetSchemaChildren.size();
946
0
                image.children = imageAssetSchemaChildren.data();
947
948
0
                href.format = ARROW_FORMAT_STRING;
949
0
                href.name = "href";
950
951
0
                roles.format = ARROW_FORMAT_LIST;
952
0
                roles.name = "roles";
953
0
                roles.flags = ARROW_FLAG_NULLABLE;
954
0
                roles.n_children = imageAssetRolesSchemaChildren.size();
955
0
                roles.children = imageAssetRolesSchemaChildren.data();
956
957
0
                roles_item.format = ARROW_FORMAT_STRING;
958
0
                roles_item.name = "item";
959
0
                roles_item.flags = ARROW_FLAG_NULLABLE;
960
961
0
                title.format = ARROW_FORMAT_STRING;
962
0
                title.name = "title";
963
0
                title.flags = ARROW_FLAG_NULLABLE;
964
965
0
                type.format = ARROW_FORMAT_STRING;
966
0
                type.name = "type";
967
0
                type.flags = ARROW_FLAG_NULLABLE;
968
969
0
                if (!poLayer->CreateFieldFromArrowSchema(&assets))
970
0
                    return nullptr;
971
0
            }
972
973
            // "bands" field
974
0
            {
975
0
                auto &bands = AddTopSchema();
976
977
0
                auto &bandsItem = *AddAuxSchema();
978
0
                bandsSchemaChildren.push_back(&bandsItem);
979
980
0
                bands.format = ARROW_FORMAT_LIST;
981
0
                bands.name = "bands";
982
0
                bands.n_children = bandsSchemaChildren.size();
983
0
                bands.children = bandsSchemaChildren.data();
984
985
0
                auto &name = *AddAuxSchema();
986
0
                bandsItemSchemaChildren.push_back(&name);
987
988
0
                auto &commonName = *AddAuxSchema();
989
0
                bandsItemSchemaChildren.push_back(&commonName);
990
991
0
                auto &centerWavelength = *AddAuxSchema();
992
0
                bandsItemSchemaChildren.push_back(&centerWavelength);
993
994
0
                auto &fullWidthHalfMax = *AddAuxSchema();
995
0
                bandsItemSchemaChildren.push_back(&fullWidthHalfMax);
996
997
0
                auto &nodata = *AddAuxSchema();
998
0
                bandsItemSchemaChildren.push_back(&nodata);
999
1000
0
                auto &data_type = *AddAuxSchema();
1001
0
                bandsItemSchemaChildren.push_back(&data_type);
1002
1003
0
                auto &unit = *AddAuxSchema();
1004
0
                bandsItemSchemaChildren.push_back(&unit);
1005
1006
0
                bandsItem.format = ARROW_FORMAT_STRUCT;
1007
0
                bandsItem.name = "item";
1008
0
                bandsItem.n_children = bandsItemSchemaChildren.size();
1009
0
                bandsItem.children = bandsItemSchemaChildren.data();
1010
1011
0
                name.format = ARROW_FORMAT_STRING;
1012
0
                name.name = "name";
1013
1014
0
                commonName.format = ARROW_FORMAT_STRING;
1015
0
                commonName.name = "eo:common_name";
1016
0
                commonName.flags = ARROW_FLAG_NULLABLE;
1017
1018
0
                centerWavelength.format = ARROW_FORMAT_FLOAT32;
1019
0
                centerWavelength.name = "eo:center_wavelength";
1020
0
                centerWavelength.flags = ARROW_FLAG_NULLABLE;
1021
1022
0
                fullWidthHalfMax.format = ARROW_FORMAT_FLOAT32;
1023
0
                fullWidthHalfMax.name = "eo:full_width_half_max";
1024
0
                fullWidthHalfMax.flags = ARROW_FLAG_NULLABLE;
1025
1026
0
                nodata.format = ARROW_FORMAT_STRING;
1027
0
                nodata.name = "nodata";
1028
0
                nodata.flags = ARROW_FLAG_NULLABLE;
1029
1030
0
                data_type.format = ARROW_FORMAT_STRING;
1031
0
                data_type.name = "data_type";
1032
1033
0
                unit.format = ARROW_FORMAT_STRING;
1034
0
                unit.name = "unit";
1035
0
                unit.flags = ARROW_FLAG_NULLABLE;
1036
1037
0
                if (!poLayer->CreateFieldFromArrowSchema(&bands))
1038
0
                    return nullptr;
1039
0
            }
1040
1041
            // "proj:code" field
1042
0
            {
1043
0
                auto &schema = AddTopSchema();
1044
0
                schema.format = ARROW_FORMAT_STRING;
1045
0
                schema.name = "proj:code";
1046
0
                schema.flags = ARROW_FLAG_NULLABLE;
1047
0
                if (!poLayer->CreateFieldFromArrowSchema(&schema))
1048
0
                    return nullptr;
1049
0
            }
1050
            // "proj:wkt2" field
1051
0
            {
1052
0
                auto &schema = AddTopSchema();
1053
0
                schema.format = ARROW_FORMAT_STRING;
1054
0
                schema.name = "proj:wkt2";
1055
0
                schema.flags = ARROW_FLAG_NULLABLE;
1056
0
                if (!poLayer->CreateFieldFromArrowSchema(&schema))
1057
0
                    return nullptr;
1058
0
            }
1059
            // "proj:projjson" field
1060
0
            {
1061
0
                auto &schema = AddTopSchema();
1062
0
                schema.format = ARROW_FORMAT_STRING;
1063
0
                schema.name = "proj:projjson";
1064
                // clang-format off
1065
0
                static const char jsonMetadata[] = {
1066
                    // Number of key/value pairs (uint32)
1067
0
                    1, 0, 0, 0,
1068
                    // Length of key (uint32)
1069
0
                    20, 0, 0, 0,
1070
0
                    'A', 'R', 'R', 'O', 'W', ':',
1071
0
                    'e', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n', ':',
1072
0
                    'n', 'a', 'm', 'e',
1073
                    // Length of value (uint32)
1074
0
                    10, 0, 0, 0,
1075
0
                    'a', 'r', 'r', 'o', 'w', '.', 'j', 's', 'o', 'n',
1076
0
                };
1077
                // clang-format on
1078
0
                schema.metadata = jsonMetadata;
1079
0
                schema.flags = ARROW_FLAG_NULLABLE;
1080
0
                if (!poLayer->CreateFieldFromArrowSchema(&schema))
1081
0
                    return nullptr;
1082
0
            }
1083
            // "proj:bbox" field
1084
0
            {
1085
0
                auto &schema = AddTopSchema();
1086
0
                static const char FORMAT_PROJ_BBOX[] = {
1087
0
                    '+', 'w', ':', '0' + NUM_ITEMS_PROJ_BBOX, 0};
1088
0
                schema.format = FORMAT_PROJ_BBOX;
1089
0
                schema.name = "proj:bbox";
1090
1091
0
                auto &sub_schema = *AddAuxSchema();
1092
0
                projBboxSchemaChildren.push_back(&sub_schema);
1093
1094
0
                schema.n_children = projBboxSchemaChildren.size();
1095
0
                schema.children = projBboxSchemaChildren.data();
1096
0
                sub_schema.format = ARROW_FORMAT_FLOAT64;
1097
0
                sub_schema.name = "item";
1098
1099
0
                if (!poLayer->CreateFieldFromArrowSchema(&schema))
1100
0
                    return nullptr;
1101
0
            }
1102
            // "proj:shape" field
1103
0
            {
1104
0
                auto &schema = AddTopSchema();
1105
0
                static const char FORMAT_PROJ_SHAPE[] = {
1106
0
                    '+', 'w', ':', '0' + NUM_ITEMS_PROJ_SHAPE, 0};
1107
0
                schema.format = FORMAT_PROJ_SHAPE;
1108
0
                schema.name = "proj:shape";
1109
1110
0
                auto &sub_schema = *AddAuxSchema();
1111
0
                projShapeSchemaChildren.push_back(&sub_schema);
1112
1113
0
                schema.n_children = projShapeSchemaChildren.size();
1114
0
                schema.children = projShapeSchemaChildren.data();
1115
0
                sub_schema.format = ARROW_FORMAT_INT32;
1116
0
                sub_schema.name = "item";
1117
1118
0
                if (!poLayer->CreateFieldFromArrowSchema(&schema))
1119
0
                    return nullptr;
1120
0
            }
1121
            // "proj:transform" field
1122
0
            {
1123
0
                auto &schema = AddTopSchema();
1124
0
                static const char FORMAT_PROJ_TRANSFORM[] = {
1125
0
                    '+', 'w', ':', '0' + NUM_ITEMS_PROJ_TRANSFORM, 0};
1126
0
                schema.format = FORMAT_PROJ_TRANSFORM;
1127
0
                schema.name = "proj:transform";
1128
1129
0
                auto &sub_schema = *AddAuxSchema();
1130
0
                projTransformSchemaChildren.push_back(&sub_schema);
1131
1132
0
                schema.n_children = projTransformSchemaChildren.size();
1133
0
                schema.children = projTransformSchemaChildren.data();
1134
0
                sub_schema.format = ARROW_FORMAT_FLOAT64;
1135
0
                sub_schema.name = "item";
1136
1137
0
                if (!poLayer->CreateFieldFromArrowSchema(&schema))
1138
0
                    return nullptr;
1139
0
            }
1140
0
        }
1141
0
        else
1142
0
        {
1143
0
            OGRFieldDefn oLocationField(psOptions->osLocationField.c_str(),
1144
0
                                        OFTString);
1145
0
            oLocationField.SetWidth(nMaxFieldSize);
1146
0
            if (poLayer->CreateField(&oLocationField) != OGRERR_NONE)
1147
0
                return nullptr;
1148
0
        }
1149
1150
0
        if (!psOptions->osSrcSRSFieldName.empty())
1151
0
        {
1152
0
            OGRFieldDefn oSrcSRSField(psOptions->osSrcSRSFieldName.c_str(),
1153
0
                                      OFTString);
1154
0
            oSrcSRSField.SetWidth(nMaxFieldSize);
1155
0
            if (poLayer->CreateField(&oSrcSRSField) != OGRERR_NONE)
1156
0
                return nullptr;
1157
0
        }
1158
0
    }
1159
1160
0
    auto poLayerDefn = poLayer->GetLayerDefn();
1161
1162
0
    if (!bIsSTACGeoParquet)
1163
0
    {
1164
0
        for (const auto &oFetchMD : psOptions->aoFetchMD)
1165
0
        {
1166
0
            if (poLayerDefn->GetFieldIndex(oFetchMD.osFieldName.c_str()) < 0)
1167
0
            {
1168
0
                OGRFieldDefn oField(oFetchMD.osFieldName.c_str(),
1169
0
                                    oFetchMD.eType);
1170
0
                if (poLayer->CreateField(&oField) != OGRERR_NONE)
1171
0
                    return nullptr;
1172
0
            }
1173
0
        }
1174
0
    }
1175
1176
0
    if (bIsSTACGeoParquet)
1177
0
    {
1178
0
        {
1179
0
            auto &geometry = AddTopSchema();
1180
0
            geometry.format = ARROW_FORMAT_BINARY;
1181
0
            geometry.name = GEOPARQUET_GEOM_COL_NAME;
1182
0
        }
1183
1184
0
        for (auto &schema : topSchemas)
1185
0
            topSchemasPointers.push_back(&schema);
1186
1187
0
        topSchema.format = ARROW_FORMAT_STRUCT;
1188
0
        topSchema.name = "main";
1189
0
        topSchema.release = noop_release;
1190
0
        topSchema.n_children = topSchemasPointers.size();
1191
0
        topSchema.children = topSchemasPointers.data();
1192
0
    }
1193
1194
0
    CPLXMLTreeCloser psRoot(nullptr);
1195
0
    if (!psOptions->osGTIFilename.empty())
1196
0
    {
1197
0
        if (!psOptions->aosMetadata.empty())
1198
0
        {
1199
0
            CPLError(CE_Failure, CPLE_NotSupported,
1200
0
                     "-mo is not supported when -gti_filename is used");
1201
0
            return nullptr;
1202
0
        }
1203
0
        psRoot.reset(
1204
0
            CPLCreateXMLNode(nullptr, CXT_Element, "GDALTileIndexDataset"));
1205
0
        CPLCreateXMLElementAndValue(psRoot.get(), "IndexDataset", pszDest);
1206
0
        CPLCreateXMLElementAndValue(psRoot.get(), "IndexLayer",
1207
0
                                    poLayer->GetName());
1208
0
        CPLCreateXMLElementAndValue(psRoot.get(), "LocationField",
1209
0
                                    psOptions->osLocationField.c_str());
1210
0
        if (!std::isnan(psOptions->xres))
1211
0
        {
1212
0
            CPLCreateXMLElementAndValue(psRoot.get(), "ResX",
1213
0
                                        CPLSPrintf("%.18g", psOptions->xres));
1214
0
            CPLCreateXMLElementAndValue(psRoot.get(), "ResY",
1215
0
                                        CPLSPrintf("%.18g", psOptions->yres));
1216
0
        }
1217
0
        if (!std::isnan(psOptions->xmin))
1218
0
        {
1219
0
            CPLCreateXMLElementAndValue(psRoot.get(), "MinX",
1220
0
                                        CPLSPrintf("%.18g", psOptions->xmin));
1221
0
            CPLCreateXMLElementAndValue(psRoot.get(), "MinY",
1222
0
                                        CPLSPrintf("%.18g", psOptions->ymin));
1223
0
            CPLCreateXMLElementAndValue(psRoot.get(), "MaxX",
1224
0
                                        CPLSPrintf("%.18g", psOptions->xmax));
1225
0
            CPLCreateXMLElementAndValue(psRoot.get(), "MaxY",
1226
0
                                        CPLSPrintf("%.18g", psOptions->ymax));
1227
0
        }
1228
1229
0
        int nBandCount = 0;
1230
0
        if (!psOptions->osBandCount.empty())
1231
0
        {
1232
0
            nBandCount = atoi(psOptions->osBandCount.c_str());
1233
0
        }
1234
0
        else
1235
0
        {
1236
0
            if (!psOptions->osDataType.empty())
1237
0
            {
1238
0
                nBandCount = std::max(
1239
0
                    nBandCount,
1240
0
                    CPLStringList(CSLTokenizeString2(
1241
0
                                      psOptions->osDataType.c_str(), ", ", 0))
1242
0
                        .size());
1243
0
            }
1244
0
            if (!psOptions->osNodata.empty())
1245
0
            {
1246
0
                nBandCount = std::max(
1247
0
                    nBandCount,
1248
0
                    CPLStringList(CSLTokenizeString2(
1249
0
                                      psOptions->osNodata.c_str(), ", ", 0))
1250
0
                        .size());
1251
0
            }
1252
0
            if (!psOptions->osColorInterp.empty())
1253
0
            {
1254
0
                nBandCount =
1255
0
                    std::max(nBandCount,
1256
0
                             CPLStringList(
1257
0
                                 CSLTokenizeString2(
1258
0
                                     psOptions->osColorInterp.c_str(), ", ", 0))
1259
0
                                 .size());
1260
0
            }
1261
0
        }
1262
1263
0
        for (int i = 0; i < nBandCount; ++i)
1264
0
        {
1265
0
            auto psBand = CPLCreateXMLNode(psRoot.get(), CXT_Element, "Band");
1266
0
            CPLAddXMLAttributeAndValue(psBand, "band", CPLSPrintf("%d", i + 1));
1267
0
            if (!psOptions->osDataType.empty())
1268
0
            {
1269
0
                const CPLStringList aosTokens(
1270
0
                    CSLTokenizeString2(psOptions->osDataType.c_str(), ", ", 0));
1271
0
                if (aosTokens.size() == 1)
1272
0
                    CPLAddXMLAttributeAndValue(psBand, "dataType",
1273
0
                                               aosTokens[0]);
1274
0
                else if (i < aosTokens.size())
1275
0
                    CPLAddXMLAttributeAndValue(psBand, "dataType",
1276
0
                                               aosTokens[i]);
1277
0
            }
1278
0
            if (!psOptions->osNodata.empty())
1279
0
            {
1280
0
                const CPLStringList aosTokens(
1281
0
                    CSLTokenizeString2(psOptions->osNodata.c_str(), ", ", 0));
1282
0
                if (aosTokens.size() == 1)
1283
0
                    CPLCreateXMLElementAndValue(psBand, "NoDataValue",
1284
0
                                                aosTokens[0]);
1285
0
                else if (i < aosTokens.size())
1286
0
                    CPLCreateXMLElementAndValue(psBand, "NoDataValue",
1287
0
                                                aosTokens[i]);
1288
0
            }
1289
0
            if (!psOptions->osColorInterp.empty())
1290
0
            {
1291
0
                const CPLStringList aosTokens(CSLTokenizeString2(
1292
0
                    psOptions->osColorInterp.c_str(), ", ", 0));
1293
0
                if (aosTokens.size() == 1)
1294
0
                    CPLCreateXMLElementAndValue(psBand, "ColorInterp",
1295
0
                                                aosTokens[0]);
1296
0
                else if (i < aosTokens.size())
1297
0
                    CPLCreateXMLElementAndValue(psBand, "ColorInterp",
1298
0
                                                aosTokens[i]);
1299
0
            }
1300
0
        }
1301
1302
0
        if (psOptions->bMaskBand)
1303
0
        {
1304
0
            CPLCreateXMLElementAndValue(psRoot.get(), "MaskBand", "true");
1305
0
        }
1306
0
    }
1307
0
    else
1308
0
    {
1309
0
        if (!bIsSTACGeoParquet)
1310
0
        {
1311
0
            poLayer->SetMetadataItem("LOCATION_FIELD",
1312
0
                                     psOptions->osLocationField.c_str());
1313
0
        }
1314
0
        if (!std::isnan(psOptions->xres))
1315
0
        {
1316
0
            poLayer->SetMetadataItem("RESX",
1317
0
                                     CPLSPrintf("%.18g", psOptions->xres));
1318
0
            poLayer->SetMetadataItem("RESY",
1319
0
                                     CPLSPrintf("%.18g", psOptions->yres));
1320
0
        }
1321
0
        if (!std::isnan(psOptions->xmin))
1322
0
        {
1323
0
            poLayer->SetMetadataItem("MINX",
1324
0
                                     CPLSPrintf("%.18g", psOptions->xmin));
1325
0
            poLayer->SetMetadataItem("MINY",
1326
0
                                     CPLSPrintf("%.18g", psOptions->ymin));
1327
0
            poLayer->SetMetadataItem("MAXX",
1328
0
                                     CPLSPrintf("%.18g", psOptions->xmax));
1329
0
            poLayer->SetMetadataItem("MAXY",
1330
0
                                     CPLSPrintf("%.18g", psOptions->ymax));
1331
0
        }
1332
0
        if (!psOptions->osBandCount.empty())
1333
0
        {
1334
0
            poLayer->SetMetadataItem("BAND_COUNT",
1335
0
                                     psOptions->osBandCount.c_str());
1336
0
        }
1337
0
        if (!psOptions->osDataType.empty())
1338
0
        {
1339
0
            poLayer->SetMetadataItem("DATA_TYPE",
1340
0
                                     psOptions->osDataType.c_str());
1341
0
        }
1342
0
        if (!psOptions->osNodata.empty())
1343
0
        {
1344
0
            poLayer->SetMetadataItem("NODATA", psOptions->osNodata.c_str());
1345
0
        }
1346
0
        if (!psOptions->osColorInterp.empty())
1347
0
        {
1348
0
            poLayer->SetMetadataItem("COLOR_INTERPRETATION",
1349
0
                                     psOptions->osColorInterp.c_str());
1350
0
        }
1351
0
        if (psOptions->bMaskBand)
1352
0
        {
1353
0
            poLayer->SetMetadataItem("MASK_BAND", "YES");
1354
0
        }
1355
0
        const CPLStringList aosMetadata(psOptions->aosMetadata);
1356
0
        for (const auto &[pszKey, pszValue] :
1357
0
             cpl::IterateNameValue(aosMetadata))
1358
0
        {
1359
0
            poLayer->SetMetadataItem(pszKey, pszValue);
1360
0
        }
1361
0
    }
1362
1363
0
    const int ti_field =
1364
0
        poLayerDefn->GetFieldIndex(psOptions->osLocationField.c_str());
1365
0
    if (!bIsSTACGeoParquet && ti_field < 0)
1366
0
    {
1367
0
        CPLError(CE_Failure, CPLE_AppDefined,
1368
0
                 "Unable to find field `%s' in file `%s'.",
1369
0
                 psOptions->osLocationField.c_str(), pszDest);
1370
0
        return nullptr;
1371
0
    }
1372
1373
0
    int i_SrcSRSName = -1;
1374
0
    if (!bIsSTACGeoParquet && !psOptions->osSrcSRSFieldName.empty())
1375
0
    {
1376
0
        i_SrcSRSName =
1377
0
            poLayerDefn->GetFieldIndex(psOptions->osSrcSRSFieldName.c_str());
1378
0
        if (i_SrcSRSName < 0)
1379
0
        {
1380
0
            CPLError(CE_Failure, CPLE_AppDefined,
1381
0
                     "Unable to find field `%s' in file `%s'.",
1382
0
                     psOptions->osSrcSRSFieldName.c_str(), pszDest);
1383
0
            return nullptr;
1384
0
        }
1385
0
    }
1386
1387
    // Load in memory existing file names in tile index.
1388
0
    std::set<std::string> oSetExistingFiles;
1389
0
    OGRSpatialReference oAlreadyExistingSRS;
1390
0
    if (bExistingLayer)
1391
0
    {
1392
0
        for (auto &&poFeature : poLayer)
1393
0
        {
1394
0
            if (poFeature->IsFieldSetAndNotNull(ti_field))
1395
0
            {
1396
0
                if (oSetExistingFiles.empty())
1397
0
                {
1398
0
                    auto poSrcDS =
1399
0
                        std::unique_ptr<GDALDataset>(GDALDataset::Open(
1400
0
                            poFeature->GetFieldAsString(ti_field),
1401
0
                            GDAL_OF_RASTER, nullptr, nullptr, nullptr));
1402
0
                    if (poSrcDS)
1403
0
                    {
1404
0
                        auto poSrcSRS = poSrcDS->GetSpatialRef();
1405
0
                        if (poSrcSRS)
1406
0
                            oAlreadyExistingSRS = *poSrcSRS;
1407
0
                    }
1408
0
                }
1409
0
                oSetExistingFiles.insert(poFeature->GetFieldAsString(ti_field));
1410
0
            }
1411
0
        }
1412
0
    }
1413
1414
0
    std::string osCurrentPath;
1415
0
    if (psOptions->bWriteAbsolutePath)
1416
0
    {
1417
0
        char *pszCurrentPath = CPLGetCurrentDir();
1418
0
        if (pszCurrentPath)
1419
0
        {
1420
0
            osCurrentPath = pszCurrentPath;
1421
0
        }
1422
0
        else
1423
0
        {
1424
0
            CPLError(CE_Warning, CPLE_AppDefined,
1425
0
                     "This system does not support the CPLGetCurrentDir call. "
1426
0
                     "The option -bWriteAbsolutePath will have no effect.");
1427
0
        }
1428
0
        CPLFree(pszCurrentPath);
1429
0
    }
1430
1431
0
    const bool bIsGTIContext =
1432
0
        !std::isnan(psOptions->xres) || !std::isnan(psOptions->xmin) ||
1433
0
        !psOptions->osBandCount.empty() || !psOptions->osNodata.empty() ||
1434
0
        !psOptions->osColorInterp.empty() || !psOptions->osDataType.empty() ||
1435
0
        psOptions->bMaskBand || !psOptions->aosMetadata.empty() ||
1436
0
        !psOptions->osGTIFilename.empty();
1437
1438
    /* -------------------------------------------------------------------- */
1439
    /*      loop over GDAL files, processing.                               */
1440
    /* -------------------------------------------------------------------- */
1441
0
    ArrowArray topArray{};
1442
1443
0
    struct TopArrayReleaser
1444
0
    {
1445
0
        ArrowArray *m_array = nullptr;
1446
1447
0
        explicit TopArrayReleaser(ArrowArray *array) : m_array(array)
1448
0
        {
1449
0
        }
1450
1451
0
        ~TopArrayReleaser()
1452
0
        {
1453
0
            if (m_array && m_array->release)
1454
0
                m_array->release(m_array);
1455
0
        }
1456
1457
0
        TopArrayReleaser(const TopArrayReleaser &) = delete;
1458
0
        TopArrayReleaser &operator=(const TopArrayReleaser &) = delete;
1459
0
    };
1460
1461
0
    TopArrayReleaser arrayReleaser(&topArray);
1462
1463
0
    ArrowArray **topArrays = nullptr;
1464
1465
0
    int iArray = 0;
1466
0
    const int iIdArray = iArray++;
1467
1468
0
    const int iStacExtensionsArray = iArray++;
1469
0
    ArrowArray *stacExtensionSubArray = nullptr;
1470
0
    uint32_t nStacExtensionSubArrayMaxAlloc = 0;
1471
1472
0
    const int iLinksArray = iArray++;
1473
0
    ArrowArray *linksItemArray = nullptr;
1474
1475
0
    const int iAssetsArray = iArray++;
1476
0
    ArrowArray *imageArray = nullptr;
1477
0
    uint32_t nImageHrefArrayMaxAlloc = 0;
1478
0
    ArrowArray *imageHrefArray = nullptr;
1479
0
    ArrowArray *imageRoleArray = nullptr;
1480
0
    ArrowArray *imageRoleItemArray = nullptr;
1481
0
    uint32_t nImageRoleItemArrayMaxAlloc = 0;
1482
0
    ArrowArray *imageTitleArray = nullptr;
1483
0
    uint32_t nImageTitleArrayMaxAlloc = 0;
1484
0
    ArrowArray *imageTypeArray = nullptr;
1485
0
    uint32_t nImageTypeArrayMaxAlloc = 0;
1486
1487
0
    const int iBandsArray = iArray++;
1488
0
    uint32_t nBandsItemCount = 0;
1489
0
    uint32_t nBandsItemAlloc = 0;
1490
0
    ArrowArray *bandsItemArray = nullptr;
1491
0
    ArrowArray *bandsNameArray = nullptr;
1492
0
    uint32_t nBandsNameArrayMaxAlloc = 0;
1493
0
    ArrowArray *bandsCommonNameArray = nullptr;
1494
0
    uint32_t nBandsCommonNameArrayMaxAlloc = 0;
1495
0
    ArrowArray *bandsCenterWavelengthArray = nullptr;
1496
0
    uint32_t nBandsCenterWavelengthArrayMaxAlloc = 0;
1497
0
    ArrowArray *bandsFWHMArray = nullptr;
1498
0
    uint32_t nBandsFWHMArrayMaxAlloc = 0;
1499
0
    ArrowArray *bandsNodataArray = nullptr;
1500
0
    uint32_t nBandsNodataArrayMaxAlloc = 0;
1501
0
    ArrowArray *bandsDataTypeArray = nullptr;
1502
0
    uint32_t nBandsDataTypeArrayMaxAlloc = 0;
1503
0
    ArrowArray *bandsUnitArray = nullptr;
1504
0
    uint32_t nBandsUnitArrayMaxAlloc = 0;
1505
1506
0
    const int iProjCode = iArray++;
1507
0
    uint32_t nProjCodeArrayMaxAlloc = 0;
1508
0
    const int iProjWKT2 = iArray++;
1509
0
    uint32_t nProjWKT2ArrayMaxAlloc = 0;
1510
0
    const int iProjPROJJSON = iArray++;
1511
0
    uint32_t nProjPROJJSONArrayMaxAlloc = 0;
1512
0
    const int iProjBBOX = iArray++;
1513
0
    ArrowArray *projBBOXItems = nullptr;
1514
0
    const int iProjShape = iArray++;
1515
0
    ArrowArray *projShapeItems = nullptr;
1516
0
    const int iProjTransform = iArray++;
1517
0
    ArrowArray *projTransformItems = nullptr;
1518
1519
0
    const int iWkbArray = iArray++;
1520
1521
0
    std::unique_ptr<OGRArrowArrayHelper> arrayHelper;
1522
1523
0
    const auto InitTopArray =
1524
0
        [iIdArray, iStacExtensionsArray, iLinksArray, iAssetsArray, iBandsArray,
1525
0
         iProjCode, iProjWKT2, iProjPROJJSON, iProjBBOX, iProjShape,
1526
0
         iProjTransform, iWkbArray, nMaxBatchSize, &arrayHelper, &topArray,
1527
0
         &topArrays, &topSchema, &nStacExtensionSubArrayMaxAlloc,
1528
0
         &stacExtensionSubArray, &linksItemArray, &imageArray,
1529
0
         &nImageHrefArrayMaxAlloc, &imageHrefArray, &imageRoleArray,
1530
0
         &imageRoleItemArray, &nImageRoleItemArrayMaxAlloc, &imageTitleArray,
1531
0
         &imageTypeArray, &nImageTitleArrayMaxAlloc, &nImageTypeArrayMaxAlloc,
1532
0
         &nBandsItemCount, &nBandsItemAlloc, &bandsItemArray, &bandsNameArray,
1533
0
         &nBandsNameArrayMaxAlloc, &bandsCommonNameArray,
1534
0
         &nBandsCommonNameArrayMaxAlloc, &nBandsCenterWavelengthArrayMaxAlloc,
1535
0
         &bandsCenterWavelengthArray, &nBandsFWHMArrayMaxAlloc, &bandsFWHMArray,
1536
0
         &bandsNodataArray, &nBandsNodataArrayMaxAlloc, &bandsDataTypeArray,
1537
0
         &nBandsDataTypeArrayMaxAlloc, &bandsUnitArray,
1538
0
         &nBandsUnitArrayMaxAlloc, &nProjCodeArrayMaxAlloc,
1539
0
         &nProjWKT2ArrayMaxAlloc, &nProjPROJJSONArrayMaxAlloc, &projBBOXItems,
1540
0
         &projShapeItems, &projTransformItems]()
1541
0
    {
1542
0
        const auto AllocArray = []()
1543
0
        {
1544
0
            auto array =
1545
0
                static_cast<ArrowArray *>(CPLCalloc(1, sizeof(ArrowArray)));
1546
0
            array->release = ReleaseArray;
1547
0
            return array;
1548
0
        };
1549
1550
0
        const auto AllocNBuffers = [](ArrowArray &array, int n_buffers)
1551
0
        {
1552
0
            array.n_buffers = n_buffers;
1553
0
            array.buffers = static_cast<const void **>(
1554
0
                CPLCalloc(n_buffers, sizeof(const void *)));
1555
0
        };
1556
1557
0
        const auto AllocNArrays = [](ArrowArray &array, int n_children)
1558
0
        {
1559
0
            array.n_children = n_children;
1560
0
            array.children = static_cast<ArrowArray **>(
1561
0
                CPLCalloc(n_children, sizeof(ArrowArray *)));
1562
0
        };
1563
1564
0
        const auto InitializePrimitiveArray =
1565
0
            [&AllocNBuffers](ArrowArray &sArray, size_t nEltSize,
1566
0
                             size_t nLength)
1567
0
        {
1568
0
            AllocNBuffers(sArray, 2);
1569
0
            sArray.buffers[ARROW_BUF_DATA] =
1570
0
                static_cast<const void *>(CPLCalloc(nLength, nEltSize));
1571
0
        };
1572
1573
0
        const auto InitializeStringOrBinaryArray =
1574
0
            [&AllocNBuffers](ArrowArray &sArray, size_t nLength)
1575
0
        {
1576
0
            AllocNBuffers(sArray, 3);
1577
            // +1 since the length of string of idx i is given by
1578
            // offset[i+1] - offset[i]
1579
0
            sArray.buffers[ARROW_BUF_DATA] = static_cast<const void *>(
1580
0
                CPLCalloc(nLength + 1, sizeof(uint32_t)));
1581
            // Allocate a minimum amount to not get a null pointer
1582
0
            sArray.buffers[ARROW_BUF_BYTES] =
1583
0
                static_cast<const void *>(CPLCalloc(1, 1));
1584
0
        };
1585
1586
0
        const auto InitializeListArray =
1587
0
            [&AllocNBuffers, &AllocNArrays](
1588
0
                ArrowArray &sArray, ArrowArray *subArray, size_t nLength)
1589
0
        {
1590
0
            AllocNBuffers(sArray, 2);
1591
0
            sArray.buffers[ARROW_BUF_DATA] = static_cast<const void *>(
1592
0
                CPLCalloc(nLength + 1, sizeof(uint32_t)));
1593
0
            AllocNArrays(sArray, 1);
1594
0
            sArray.children[0] = subArray;
1595
0
        };
1596
1597
0
        const auto InitializeFixedSizeListArray =
1598
0
            [&AllocNBuffers,
1599
0
             &AllocNArrays](ArrowArray &sArray, ArrowArray *subArray,
1600
0
                            size_t nItemSize, size_t nItemCount, size_t nLength)
1601
0
        {
1602
0
            AllocNArrays(sArray, 1);
1603
0
            AllocNBuffers(sArray, 1);
1604
0
            sArray.children[0] = subArray;
1605
0
            AllocNBuffers(*subArray, 2);
1606
0
            subArray->buffers[ARROW_BUF_DATA] = static_cast<const void *>(
1607
0
                CPLCalloc(nItemCount * nItemSize, nLength));
1608
0
        };
1609
1610
0
        const auto InitializeStructArray = [&AllocNBuffers](ArrowArray &sArray)
1611
0
        { AllocNBuffers(sArray, 1); };
1612
1613
0
        topArrays = static_cast<ArrowArray **>(CPLCalloc(
1614
0
            static_cast<int>(topSchema.n_children), sizeof(ArrowArray *)));
1615
0
        for (int i = 0; i < static_cast<int>(topSchema.n_children); ++i)
1616
0
            topArrays[i] = AllocArray();
1617
1618
0
        topArray = ArrowArray{};
1619
0
        topArray.release = ReleaseArray;
1620
0
        topArray.n_children = topSchema.n_children;
1621
0
        topArray.children = topArrays;
1622
0
        InitializeStructArray(topArray);
1623
1624
0
        InitializeStringOrBinaryArray(*topArrays[iIdArray], nMaxBatchSize);
1625
1626
0
        stacExtensionSubArray = AllocArray();
1627
0
        nStacExtensionSubArrayMaxAlloc = 0;
1628
0
        {
1629
0
            auto *array = topArrays[iStacExtensionsArray];
1630
0
            InitializeListArray(*array, stacExtensionSubArray, nMaxBatchSize);
1631
0
            InitializeStringOrBinaryArray(
1632
0
                *stacExtensionSubArray, COUNT_STAC_EXTENSIONS * nMaxBatchSize);
1633
0
        }
1634
1635
0
        linksItemArray = AllocArray();
1636
0
        {
1637
0
            auto *array = topArrays[iLinksArray];
1638
0
            InitializeListArray(*array, linksItemArray, nMaxBatchSize);
1639
0
            InitializeStructArray(*linksItemArray);
1640
1641
0
            AllocNArrays(*linksItemArray, 4);
1642
0
            ArrowArray *linksHrefArray = AllocArray();
1643
0
            ArrowArray *linksRelArray = AllocArray();
1644
0
            ArrowArray *linksTypeArray = AllocArray();
1645
0
            ArrowArray *linksTitleArray = AllocArray();
1646
0
            linksItemArray->children[0] = linksHrefArray;
1647
0
            linksItemArray->children[1] = linksRelArray;
1648
0
            linksItemArray->children[2] = linksTypeArray;
1649
0
            linksItemArray->children[3] = linksTitleArray;
1650
0
            InitializeStringOrBinaryArray(*linksHrefArray, nMaxBatchSize);
1651
0
            InitializeStringOrBinaryArray(*linksRelArray, nMaxBatchSize);
1652
0
            InitializeStringOrBinaryArray(*linksTypeArray, nMaxBatchSize);
1653
0
            InitializeStringOrBinaryArray(*linksTitleArray, nMaxBatchSize);
1654
0
        }
1655
1656
0
        imageArray = AllocArray();
1657
0
        nImageHrefArrayMaxAlloc = 0;
1658
0
        imageHrefArray = AllocArray();
1659
0
        imageRoleArray = AllocArray();
1660
0
        imageRoleItemArray = AllocArray();
1661
0
        nImageHrefArrayMaxAlloc = 0;
1662
0
        nImageRoleItemArrayMaxAlloc = 0;
1663
0
        imageTitleArray = AllocArray();
1664
0
        nImageTitleArrayMaxAlloc = 0;
1665
0
        imageTypeArray = AllocArray();
1666
0
        nImageTypeArrayMaxAlloc = 0;
1667
0
        {
1668
0
            auto *assets = topArrays[iAssetsArray];
1669
0
            InitializeStructArray(*assets);
1670
0
            AllocNArrays(*assets, 1);
1671
0
            assets->children[0] = imageArray;
1672
1673
0
            InitializeStructArray(*imageArray);
1674
0
            AllocNArrays(*imageArray, 4);
1675
0
            imageArray->children[0] = imageHrefArray;
1676
0
            imageArray->children[1] = imageRoleArray;
1677
0
            imageArray->children[2] = imageTitleArray;
1678
0
            imageArray->children[3] = imageTypeArray;
1679
1680
0
            InitializeStringOrBinaryArray(*imageHrefArray, nMaxBatchSize);
1681
0
            InitializeStringOrBinaryArray(*imageTitleArray, nMaxBatchSize);
1682
0
            InitializeStringOrBinaryArray(*imageTypeArray, nMaxBatchSize);
1683
0
            InitializeListArray(*imageRoleArray, imageRoleItemArray,
1684
0
                                nMaxBatchSize);
1685
0
            InitializeStringOrBinaryArray(*imageRoleItemArray, nMaxBatchSize);
1686
0
        }
1687
1688
        // "bands" related initialization
1689
0
        {
1690
0
            nBandsItemCount = 0;
1691
0
            nBandsItemAlloc = 0;
1692
0
            bandsItemArray = AllocArray();
1693
0
            InitializeListArray(*(topArrays[iBandsArray]), bandsItemArray,
1694
0
                                nMaxBatchSize);
1695
0
            InitializeStructArray(*bandsItemArray);
1696
1697
0
            bandsNameArray = AllocArray();
1698
0
            InitializeStringOrBinaryArray(*bandsNameArray, 0);
1699
0
            nBandsNameArrayMaxAlloc = 0;
1700
1701
0
            bandsCommonNameArray = AllocArray();
1702
0
            InitializeStringOrBinaryArray(*bandsCommonNameArray, 0);
1703
0
            nBandsCommonNameArrayMaxAlloc = 0;
1704
1705
0
            bandsCenterWavelengthArray = AllocArray();
1706
0
            InitializePrimitiveArray(*bandsCenterWavelengthArray, sizeof(float),
1707
0
                                     1);
1708
0
            nBandsCenterWavelengthArrayMaxAlloc = 0;
1709
1710
0
            bandsFWHMArray = AllocArray();
1711
0
            InitializePrimitiveArray(*bandsFWHMArray, sizeof(float), 1);
1712
0
            nBandsFWHMArrayMaxAlloc = 0;
1713
1714
0
            bandsNodataArray = AllocArray();
1715
0
            InitializeStringOrBinaryArray(*bandsNodataArray, 0);
1716
0
            nBandsNodataArrayMaxAlloc = 0;
1717
1718
0
            bandsDataTypeArray = AllocArray();
1719
0
            InitializeStringOrBinaryArray(*bandsDataTypeArray, 0);
1720
0
            nBandsDataTypeArrayMaxAlloc = 0;
1721
1722
0
            bandsUnitArray = AllocArray();
1723
0
            InitializeStringOrBinaryArray(*bandsUnitArray, 0);
1724
0
            nBandsUnitArrayMaxAlloc = 0;
1725
1726
0
            AllocNArrays(*bandsItemArray, 7);
1727
0
            bandsItemArray->children[0] = bandsNameArray;
1728
0
            bandsItemArray->children[1] = bandsCommonNameArray;
1729
0
            bandsItemArray->children[2] = bandsCenterWavelengthArray;
1730
0
            bandsItemArray->children[3] = bandsFWHMArray;
1731
0
            bandsItemArray->children[4] = bandsNodataArray;
1732
0
            bandsItemArray->children[5] = bandsDataTypeArray;
1733
0
            bandsItemArray->children[6] = bandsUnitArray;
1734
0
        }
1735
1736
        // proj:xxxx related initializations
1737
0
        {
1738
0
            InitializeStringOrBinaryArray(*topArrays[iProjCode], nMaxBatchSize);
1739
0
            nProjCodeArrayMaxAlloc = 0;
1740
0
            InitializeStringOrBinaryArray(*topArrays[iProjWKT2], nMaxBatchSize);
1741
0
            nProjWKT2ArrayMaxAlloc = 0;
1742
0
            InitializeStringOrBinaryArray(*topArrays[iProjPROJJSON],
1743
0
                                          nMaxBatchSize);
1744
0
            nProjPROJJSONArrayMaxAlloc = 0;
1745
1746
0
            projBBOXItems = AllocArray();
1747
0
            InitializeFixedSizeListArray(*(topArrays[iProjBBOX]), projBBOXItems,
1748
0
                                         sizeof(double), NUM_ITEMS_PROJ_BBOX,
1749
0
                                         nMaxBatchSize);
1750
1751
0
            projShapeItems = AllocArray();
1752
0
            InitializeFixedSizeListArray(*(topArrays[iProjShape]),
1753
0
                                         projShapeItems, sizeof(int32_t),
1754
0
                                         NUM_ITEMS_PROJ_SHAPE, nMaxBatchSize);
1755
1756
0
            projTransformItems = AllocArray();
1757
0
            InitializeFixedSizeListArray(
1758
0
                *(topArrays[iProjTransform]), projTransformItems,
1759
0
                sizeof(double), NUM_ITEMS_PROJ_TRANSFORM, nMaxBatchSize);
1760
0
        }
1761
1762
0
        InitializeStringOrBinaryArray(*topArrays[iWkbArray], nMaxBatchSize);
1763
1764
0
        arrayHelper =
1765
0
            std::make_unique<OGRArrowArrayHelper>(&topArray, nMaxBatchSize);
1766
0
    };
1767
1768
0
    int nBatchSize = 0;
1769
1770
0
    const auto FlushArrays = [poLayer, &topArray, &linksItemArray, &imageArray,
1771
0
                              &topSchema, &nBatchSize, &arrayHelper]()
1772
0
    {
1773
0
        topArray.length = nBatchSize;
1774
0
        linksItemArray->length = nBatchSize;
1775
0
        imageArray->length = nBatchSize;
1776
0
        for (int i = 0; i < static_cast<int>(topArray.n_children); ++i)
1777
0
            topArray.children[i]->length = nBatchSize;
1778
0
        const bool ret = poLayer->WriteArrowBatch(&topSchema, &topArray);
1779
0
        if (topArray.release)
1780
0
        {
1781
0
            topArray.release(&topArray);
1782
0
        }
1783
0
        memset(&topArray, 0, sizeof(topArray));
1784
0
        nBatchSize = 0;
1785
0
        arrayHelper.reset();
1786
0
        return ret;
1787
0
    };
1788
1789
0
    int iCur = 0;
1790
0
    int nTotal = nSrcCount + 1;
1791
0
    int nBandInterleavedCount = 0;
1792
0
    int nPixelInterleavedCount = 0;
1793
0
    while (true)
1794
0
    {
1795
0
        const std::string osSrcFilename = oGDALTileIndexTileIterator.next();
1796
0
        if (osSrcFilename.empty())
1797
0
            break;
1798
0
        if (bSkipFirstTile)
1799
0
        {
1800
0
            bSkipFirstTile = false;
1801
0
            continue;
1802
0
        }
1803
1804
0
        std::string osFileNameToWrite;
1805
0
        VSIStatBuf sStatBuf;
1806
1807
        // Make sure it is a file before building absolute path name.
1808
0
        if (!osCurrentPath.empty() &&
1809
0
            CPLIsFilenameRelative(osSrcFilename.c_str()) &&
1810
0
            VSIStat(osSrcFilename.c_str(), &sStatBuf) == 0)
1811
0
        {
1812
0
            osFileNameToWrite = CPLProjectRelativeFilenameSafe(
1813
0
                osCurrentPath.c_str(), osSrcFilename.c_str());
1814
0
        }
1815
0
        else
1816
0
        {
1817
0
            osFileNameToWrite = osSrcFilename.c_str();
1818
0
        }
1819
1820
        // Checks that file is not already in tileindex.
1821
0
        if (oSetExistingFiles.find(osFileNameToWrite) !=
1822
0
            oSetExistingFiles.end())
1823
0
        {
1824
0
            CPLError(CE_Warning, CPLE_AppDefined,
1825
0
                     "File %s is already in tileindex. Skipping it.",
1826
0
                     osFileNameToWrite.c_str());
1827
0
            continue;
1828
0
        }
1829
1830
0
        std::unique_ptr<GDALDataset> poSrcDS;
1831
0
        {
1832
0
            std::unique_ptr<CPLTurnFailureIntoWarningBackuper>
1833
0
                poFailureIntoWarning;
1834
0
            if (!bFailOnErrors)
1835
0
                poFailureIntoWarning =
1836
0
                    std::make_unique<CPLTurnFailureIntoWarningBackuper>();
1837
0
            CPL_IGNORE_RET_VAL(poFailureIntoWarning);
1838
1839
0
            poSrcDS.reset(GDALDataset::Open(
1840
0
                osSrcFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
1841
0
                nullptr, nullptr, nullptr));
1842
0
            if (poSrcDS == nullptr)
1843
0
            {
1844
0
                CPLError(bFailOnErrors ? CE_Failure : CE_Warning,
1845
0
                         CPLE_AppDefined, "Unable to open %s%s.",
1846
0
                         osSrcFilename.c_str(),
1847
0
                         bFailOnErrors ? "" : ", skipping");
1848
0
                if (bFailOnErrors)
1849
0
                    return nullptr;
1850
0
                continue;
1851
0
            }
1852
0
        }
1853
1854
0
        if (poSrcDS->GetRasterCount() > 1)
1855
0
        {
1856
0
            const char *pszInterleaving =
1857
0
                poSrcDS->GetMetadataItem("INTERLEAVE", "IMAGE_STRUCTURE");
1858
0
            if (pszInterleaving)
1859
0
            {
1860
0
                if (EQUAL(pszInterleaving, "BAND"))
1861
0
                    ++nBandInterleavedCount;
1862
0
                else if (EQUAL(pszInterleaving, "PIXEL"))
1863
0
                    ++nPixelInterleavedCount;
1864
0
            }
1865
0
        }
1866
1867
0
        GDALGeoTransform gt;
1868
0
        if (poSrcDS->GetGeoTransform(gt) != CE_None)
1869
0
        {
1870
0
            CPLError(bFailOnErrors ? CE_Failure : CE_Warning, CPLE_AppDefined,
1871
0
                     "It appears no georeferencing is available for\n"
1872
0
                     "`%s'%s.",
1873
0
                     osSrcFilename.c_str(), bFailOnErrors ? "" : ", skipping");
1874
0
            if (bFailOnErrors)
1875
0
                return nullptr;
1876
0
            continue;
1877
0
        }
1878
1879
0
        auto poSrcSRS = poSrcDS->GetSpatialRef();
1880
        // If not set target srs, test that the current file uses same
1881
        // projection as others.
1882
0
        if (oTargetSRS.IsEmpty())
1883
0
        {
1884
0
            if (!oAlreadyExistingSRS.IsEmpty())
1885
0
            {
1886
0
                if (poSrcSRS == nullptr ||
1887
0
                    !poSrcSRS->IsSame(&oAlreadyExistingSRS))
1888
0
                {
1889
0
                    CPLError(
1890
0
                        CE_Warning, CPLE_AppDefined,
1891
0
                        "%s is not using the same projection system "
1892
0
                        "as other files in the tileindex.\n"
1893
0
                        "This may cause problems when using it in MapServer "
1894
0
                        "for example.\n"
1895
0
                        "Use -t_srs option to set target projection system. %s",
1896
0
                        osSrcFilename.c_str(),
1897
0
                        psOptions->bSkipDifferentProjection
1898
0
                            ? "Skipping this file."
1899
0
                            : "");
1900
0
                    if (psOptions->bSkipDifferentProjection)
1901
0
                    {
1902
0
                        continue;
1903
0
                    }
1904
0
                }
1905
0
            }
1906
0
            else
1907
0
            {
1908
0
                if (poSrcSRS)
1909
0
                    oAlreadyExistingSRS = *poSrcSRS;
1910
0
            }
1911
0
        }
1912
1913
0
        const int nXSize = poSrcDS->GetRasterXSize();
1914
0
        const int nYSize = poSrcDS->GetRasterYSize();
1915
0
        if (nXSize == 0 || nYSize == 0)
1916
0
        {
1917
0
            CPLError(bFailOnErrors ? CE_Failure : CE_Warning, CPLE_AppDefined,
1918
0
                     "%s has 0 width or height%s", osSrcFilename.c_str(),
1919
0
                     bFailOnErrors ? "" : ", skipping");
1920
0
            if (bFailOnErrors)
1921
0
                return nullptr;
1922
0
            continue;
1923
0
        }
1924
1925
0
        double adfX[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
1926
0
        double adfY[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
1927
0
        adfX[0] = gt.xorig + 0 * gt.xscale + 0 * gt.xrot;
1928
0
        adfY[0] = gt.yorig + 0 * gt.yrot + 0 * gt.yscale;
1929
1930
0
        adfX[1] = gt.xorig + nXSize * gt.xscale + 0 * gt.xrot;
1931
0
        adfY[1] = gt.yorig + nXSize * gt.yrot + 0 * gt.yscale;
1932
1933
0
        adfX[2] = gt.xorig + nXSize * gt.xscale + nYSize * gt.xrot;
1934
0
        adfY[2] = gt.yorig + nXSize * gt.yrot + nYSize * gt.yscale;
1935
1936
0
        adfX[3] = gt.xorig + 0 * gt.xscale + nYSize * gt.xrot;
1937
0
        adfY[3] = gt.yorig + 0 * gt.yrot + nYSize * gt.yscale;
1938
1939
0
        adfX[4] = gt.xorig + 0 * gt.xscale + 0 * gt.xrot;
1940
0
        adfY[4] = gt.yorig + 0 * gt.yrot + 0 * gt.yscale;
1941
1942
0
        const double dfMinXBeforeReproj =
1943
0
            std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3]));
1944
0
        const double dfMinYBeforeReproj =
1945
0
            std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
1946
0
        const double dfMaxXBeforeReproj =
1947
0
            std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3]));
1948
0
        const double dfMaxYBeforeReproj =
1949
0
            std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
1950
1951
        // If set target srs, compute the reprojected extent.
1952
        // Use GDALWarp() with VRT output so the stored bounding box matches
1953
        // what the GTI reader computes in its Pass 1 (GetSourceDesc).
1954
        // A plain 4-corner transform or standalone GDALSuggestedWarpOutput2()
1955
        // can underestimate the extent because GDALWarp() may adjust the
1956
        // resolution after calling GDALSuggestedWarpOutput2(), producing a
1957
        // different pixel count and thus a different extent.
1958
0
        if (!oTargetSRS.IsEmpty() && poSrcSRS)
1959
0
        {
1960
0
            if (!poSrcSRS->IsSame(&oTargetSRS))
1961
0
            {
1962
0
                char *pszDstWKT = nullptr;
1963
0
                oTargetSRS.exportToWkt(&pszDstWKT);
1964
1965
0
                CPLStringList aosWarpArgs;
1966
0
                aosWarpArgs.AddString("-of");
1967
0
                aosWarpArgs.AddString("VRT");
1968
0
                aosWarpArgs.AddString("-t_srs");
1969
0
                aosWarpArgs.AddString(pszDstWKT);
1970
0
                CPLFree(pszDstWKT);
1971
1972
0
                GDALWarpAppOptions *psWarpOptions =
1973
0
                    GDALWarpAppOptionsNew(aosWarpArgs.List(), nullptr);
1974
0
                GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS.get());
1975
0
                GDALDatasetH ahSrcDS[] = {hSrcDS};
1976
0
                int bUsageError = false;
1977
0
                auto poWarpDS = std::unique_ptr<GDALDataset>(
1978
0
                    GDALDataset::FromHandle(GDALWarp(
1979
0
                        "", nullptr, 1, ahSrcDS, psWarpOptions, &bUsageError)));
1980
0
                GDALWarpAppOptionsFree(psWarpOptions);
1981
1982
0
                if (!poWarpDS)
1983
0
                {
1984
0
                    CPLError(bFailOnErrors ? CE_Failure : CE_Warning,
1985
0
                             CPLE_AppDefined,
1986
0
                             "unable to compute reprojected extent from "
1987
0
                             "source SRS `%s' to target SRS `%s' for "
1988
0
                             "file `%s'%s",
1989
0
                             poSrcDS->GetProjectionRef(),
1990
0
                             psOptions->osTargetSRS.c_str(),
1991
0
                             osFileNameToWrite.c_str(),
1992
0
                             bFailOnErrors ? "" : ", skipping");
1993
0
                    if (bFailOnErrors)
1994
0
                        return nullptr;
1995
0
                    continue;
1996
0
                }
1997
1998
0
                GDALGeoTransform warpGT;
1999
0
                poWarpDS->GetGeoTransform(warpGT);
2000
0
                const int nDstPixels = poWarpDS->GetRasterXSize();
2001
0
                const int nDstLines = poWarpDS->GetRasterYSize();
2002
2003
0
                const double dfDstMinX = warpGT.xorig;
2004
0
                const double dfDstMaxY = warpGT.yorig;
2005
0
                const double dfDstMaxX =
2006
0
                    warpGT.xorig + nDstPixels * warpGT.xscale;
2007
0
                const double dfDstMinY =
2008
0
                    warpGT.yorig + nDstLines * warpGT.yscale;
2009
2010
0
                adfX[0] = dfDstMinX;
2011
0
                adfY[0] = dfDstMaxY;
2012
0
                adfX[1] = dfDstMaxX;
2013
0
                adfY[1] = dfDstMaxY;
2014
0
                adfX[2] = dfDstMaxX;
2015
0
                adfY[2] = dfDstMinY;
2016
0
                adfX[3] = dfDstMinX;
2017
0
                adfY[3] = dfDstMinY;
2018
0
                adfX[4] = dfDstMinX;
2019
0
                adfY[4] = dfDstMaxY;
2020
0
            }
2021
0
        }
2022
0
        else if (bIsGTIContext && !oAlreadyExistingSRS.IsEmpty() &&
2023
0
                 (poSrcSRS == nullptr ||
2024
0
                  !poSrcSRS->IsSame(&oAlreadyExistingSRS)))
2025
0
        {
2026
0
            CPLError(
2027
0
                CE_Failure, CPLE_AppDefined,
2028
0
                "%s is not using the same projection system "
2029
0
                "as other files in the tileindex. This is not compatible of "
2030
0
                "GTI use. Use -t_srs option to reproject tile extents "
2031
0
                "to a common SRS.",
2032
0
                osSrcFilename.c_str());
2033
0
            return nullptr;
2034
0
        }
2035
2036
0
        const double dfMinX =
2037
0
            std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3]));
2038
0
        const double dfMinY =
2039
0
            std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
2040
0
        const double dfMaxX =
2041
0
            std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3]));
2042
0
        const double dfMaxY =
2043
0
            std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
2044
0
        const double dfRes =
2045
0
            sqrt((dfMaxX - dfMinX) * (dfMaxY - dfMinY) / nXSize / nYSize);
2046
0
        if (!std::isnan(psOptions->dfMinPixelSize) &&
2047
0
            dfRes < psOptions->dfMinPixelSize)
2048
0
        {
2049
0
            CPLError(CE_Warning, CPLE_AppDefined,
2050
0
                     "%s has %f as pixel size (< %f). Skipping",
2051
0
                     osSrcFilename.c_str(), dfRes, psOptions->dfMinPixelSize);
2052
0
            continue;
2053
0
        }
2054
0
        if (!std::isnan(psOptions->dfMaxPixelSize) &&
2055
0
            dfRes > psOptions->dfMaxPixelSize)
2056
0
        {
2057
0
            CPLError(CE_Warning, CPLE_AppDefined,
2058
0
                     "%s has %f as pixel size (> %f). Skipping",
2059
0
                     osSrcFilename.c_str(), dfRes, psOptions->dfMaxPixelSize);
2060
0
            continue;
2061
0
        }
2062
2063
0
        auto poPoly = std::make_unique<OGRPolygon>();
2064
0
        auto poRing = std::make_unique<OGRLinearRing>();
2065
0
        for (int k = 0; k < 5; k++)
2066
0
            poRing->addPoint(adfX[k], adfY[k]);
2067
0
        poPoly->addRing(std::move(poRing));
2068
2069
0
        if (bIsSTACGeoParquet)
2070
0
        {
2071
0
            const char *pszDriverName = poSrcDS->GetDriverName();
2072
0
            if (pszDriverName && EQUAL(pszDriverName, "MEM"))
2073
0
            {
2074
0
                CPLError(CE_Failure, CPLE_AppDefined,
2075
0
                         "Memory datasets cannot be referenced in a "
2076
0
                         "STAC-GeoParquet catalog");
2077
0
                return nullptr;
2078
0
            }
2079
2080
0
            if (!arrayHelper)
2081
0
            {
2082
0
                InitTopArray();
2083
0
            }
2084
2085
            // Write "id"
2086
0
            {
2087
0
                std::string osId(CPLGetFilename(osFileNameToWrite.c_str()));
2088
2089
0
                if (psOptions->osIdMethod == "md5")
2090
0
                {
2091
0
                    const std::string osFilename =
2092
0
                        VSIFileManager::GetHandler(osFileNameToWrite.c_str())
2093
0
                            ->GetStreamingFilename(osFileNameToWrite);
2094
0
                    auto fp = VSIFilesystemHandler::OpenStatic(
2095
0
                        osFilename.c_str(), "rb");
2096
0
                    if (!fp)
2097
0
                    {
2098
0
                        CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
2099
0
                                 osFileNameToWrite.c_str());
2100
0
                        return nullptr;
2101
0
                    }
2102
0
                    CPLMD5Context md5Context;
2103
0
                    CPLMD5Init(&md5Context);
2104
0
                    constexpr size_t CHUNK_SIZE = 1024 * 1024;
2105
0
                    std::vector<GByte> buffer(CHUNK_SIZE, 0);
2106
0
                    while (true)
2107
0
                    {
2108
0
                        const size_t nRead =
2109
0
                            fp->Read(buffer.data(), 1, buffer.size());
2110
0
                        CPLMD5Update(&md5Context, buffer.data(), nRead);
2111
0
                        if (nRead < buffer.size())
2112
0
                        {
2113
0
                            if (fp->Error())
2114
0
                            {
2115
0
                                CPLError(CE_Failure, CPLE_FileIO,
2116
0
                                         "Error while reading %s",
2117
0
                                         osFileNameToWrite.c_str());
2118
0
                                return nullptr;
2119
0
                            }
2120
0
                            break;
2121
0
                        }
2122
0
                    }
2123
0
                    unsigned char digest[16] = {0};
2124
0
                    CPLMD5Final(digest, &md5Context);
2125
0
                    char *pszMD5 = CPLBinaryToHex(16, digest);
2126
0
                    osId = pszMD5;
2127
0
                    CPLFree(pszMD5);
2128
0
                    osId += '-';
2129
0
                    osId += CPLGetFilename(osFileNameToWrite.c_str());
2130
0
                }
2131
0
                else if (psOptions->osIdMethod == "metadata-item")
2132
0
                {
2133
0
                    const char *pszId = poSrcDS->GetMetadataItem(
2134
0
                        psOptions->osIdMetadataItem.c_str());
2135
0
                    if (!pszId)
2136
0
                    {
2137
0
                        CPLError(CE_Failure, CPLE_AppDefined,
2138
0
                                 "No metadata item '%s' in dataset %s",
2139
0
                                 psOptions->osIdMetadataItem.c_str(),
2140
0
                                 osFileNameToWrite.c_str());
2141
0
                        return nullptr;
2142
0
                    }
2143
0
                    osId = pszId;
2144
0
                }
2145
0
                else if (psOptions->osIdMethod != "filename")
2146
0
                {
2147
                    // shouldn't happen
2148
0
                    CPLError(CE_Failure, CPLE_NotSupported,
2149
0
                             "Unhandled id method '%s'",
2150
0
                             psOptions->osIdMethod.c_str());
2151
0
                    return nullptr;
2152
0
                }
2153
2154
0
                void *ptr = arrayHelper->GetPtrForStringOrBinary(
2155
0
                    iIdArray, nBatchSize, osId.size(), false);
2156
0
                if (!ptr)
2157
0
                    return nullptr;
2158
0
                memcpy(ptr, osId.data(), osId.size());
2159
0
            }
2160
2161
            // Write "stac_extensions"
2162
0
            {
2163
0
                uint32_t *panOffsets = static_cast<uint32_t *>(
2164
0
                    const_cast<void *>(topArrays[iStacExtensionsArray]
2165
0
                                           ->buffers[ARROW_BUF_DATA]));
2166
0
                panOffsets[nBatchSize + 1] =
2167
0
                    panOffsets[nBatchSize] + COUNT_STAC_EXTENSIONS;
2168
2169
0
                {
2170
0
                    constexpr const char extension[] =
2171
0
                        "https://stac-extensions.github.io/projection/v2.0.0/"
2172
0
                        "schema.json";
2173
0
                    constexpr size_t nStrLen = sizeof(extension) - 1;
2174
0
                    void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2175
0
                        stacExtensionSubArray,
2176
0
                        COUNT_STAC_EXTENSIONS * nBatchSize + 0, nStrLen,
2177
0
                        nStacExtensionSubArrayMaxAlloc, false);
2178
0
                    if (!ptr)
2179
0
                        return nullptr;
2180
0
                    memcpy(ptr, extension, nStrLen);
2181
0
                    stacExtensionSubArray->length++;
2182
0
                }
2183
2184
0
                {
2185
0
                    constexpr const char extension[] =
2186
0
                        "https://stac-extensions.github.io/eo/v2.0.0/"
2187
0
                        "schema.json";
2188
0
                    constexpr size_t nStrLen = sizeof(extension) - 1;
2189
0
                    void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2190
0
                        stacExtensionSubArray,
2191
0
                        COUNT_STAC_EXTENSIONS * nBatchSize + 1, nStrLen,
2192
0
                        nStacExtensionSubArrayMaxAlloc, false);
2193
0
                    if (!ptr)
2194
0
                        return nullptr;
2195
0
                    memcpy(ptr, extension, nStrLen);
2196
0
                    stacExtensionSubArray->length++;
2197
0
                }
2198
0
            }
2199
2200
            // Write "assets.image.href"
2201
0
            {
2202
0
                std::string osHref = osFileNameToWrite;
2203
0
                CPL_IGNORE_RET_VAL(osFileNameToWrite);
2204
0
                if (!psOptions->osBaseURL.empty())
2205
0
                {
2206
0
                    osHref = CPLFormFilenameSafe(psOptions->osBaseURL.c_str(),
2207
0
                                                 CPLGetFilename(osHref.c_str()),
2208
0
                                                 nullptr);
2209
0
                }
2210
0
                else if (VSIIsLocal(osHref.c_str()))
2211
0
                {
2212
0
                    if (!CPLIsFilenameRelative(osHref.c_str()))
2213
0
                    {
2214
0
                        osHref = "file://" + osHref;
2215
0
                    }
2216
0
                }
2217
0
                else if (STARTS_WITH(osHref.c_str(), "/vsicurl/"))
2218
0
                {
2219
0
                    osHref = osHref.substr(strlen("/vsicurl/"));
2220
0
                }
2221
0
                else if (STARTS_WITH(osHref.c_str(), "/vsis3/"))
2222
0
                {
2223
0
                    osHref = "s3://" + osHref.substr(strlen("/vsis3/"));
2224
0
                }
2225
0
                else if (STARTS_WITH(osHref.c_str(), "/vsigs/"))
2226
0
                {
2227
0
                    osHref = "gcs://" + osHref.substr(strlen("/vsigs/"));
2228
0
                }
2229
0
                else if (STARTS_WITH(osHref.c_str(), "/vsiaz/"))
2230
0
                {
2231
0
                    osHref = "azure://" + osHref.substr(strlen("/vsiaz/"));
2232
0
                }
2233
0
                const size_t nHrefLen = osHref.size();
2234
0
                void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2235
0
                    imageHrefArray, nBatchSize, nHrefLen,
2236
0
                    nImageHrefArrayMaxAlloc, false);
2237
0
                if (!ptr)
2238
0
                    return nullptr;
2239
0
                memcpy(ptr, osHref.data(), nHrefLen);
2240
0
                imageHrefArray->length++;
2241
0
            }
2242
2243
            // Write "assets.image.roles"
2244
0
            {
2245
0
                uint32_t *panRolesOffsets =
2246
0
                    static_cast<uint32_t *>(const_cast<void *>(
2247
0
                        imageRoleArray->buffers[ARROW_BUF_DATA]));
2248
0
                panRolesOffsets[nBatchSize + 1] =
2249
0
                    panRolesOffsets[nBatchSize] + 1;
2250
2251
0
                constexpr const char ROLE_DATA[] = "data";
2252
0
                constexpr size_t nStrLen = sizeof(ROLE_DATA) - 1;
2253
0
                void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2254
0
                    imageRoleItemArray, nBatchSize, nStrLen,
2255
0
                    nImageRoleItemArrayMaxAlloc, false);
2256
0
                if (!ptr)
2257
0
                    return nullptr;
2258
0
                memcpy(ptr, ROLE_DATA, nStrLen);
2259
0
                imageRoleItemArray->length++;
2260
0
            }
2261
2262
            // Write "assets.image.type"
2263
0
            if (pszDriverName && EQUAL(pszDriverName, "GTiff"))
2264
0
            {
2265
0
                const char *pszLayout =
2266
0
                    poSrcDS->GetMetadataItem("LAYOUT", "IMAGE_STRUCTURE");
2267
0
                if (pszLayout && EQUAL(pszLayout, "COG"))
2268
0
                {
2269
0
                    constexpr const char TYPE[] =
2270
0
                        "image/tiff; application=geotiff; "
2271
0
                        "profile=cloud-optimized";
2272
0
                    constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
2273
0
                    void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2274
0
                        imageTypeArray, nBatchSize, TYPE_SIZE,
2275
0
                        nImageTypeArrayMaxAlloc, false);
2276
0
                    if (!ptr)
2277
0
                        return nullptr;
2278
0
                    memcpy(ptr, TYPE, TYPE_SIZE);
2279
0
                }
2280
0
                else
2281
0
                {
2282
0
                    constexpr const char TYPE[] =
2283
0
                        "image/tiff; application=geotiff";
2284
0
                    constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
2285
0
                    void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2286
0
                        imageTypeArray, nBatchSize, TYPE_SIZE,
2287
0
                        nImageTypeArrayMaxAlloc, false);
2288
0
                    if (!ptr)
2289
0
                        return nullptr;
2290
0
                    memcpy(ptr, TYPE, TYPE_SIZE);
2291
0
                }
2292
0
            }
2293
0
            else if (pszDriverName && EQUAL(pszDriverName, "PNG"))
2294
0
            {
2295
0
                constexpr const char TYPE[] = "image/png";
2296
0
                constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
2297
0
                void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2298
0
                    imageTypeArray, nBatchSize, TYPE_SIZE,
2299
0
                    nImageTypeArrayMaxAlloc, false);
2300
0
                if (!ptr)
2301
0
                    return nullptr;
2302
0
                memcpy(ptr, TYPE, TYPE_SIZE);
2303
0
            }
2304
0
            else if (pszDriverName && EQUAL(pszDriverName, "JPEG"))
2305
0
            {
2306
0
                constexpr const char TYPE[] = "image/jpeg";
2307
0
                constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
2308
0
                void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2309
0
                    imageTypeArray, nBatchSize, TYPE_SIZE,
2310
0
                    nImageTypeArrayMaxAlloc, false);
2311
0
                if (!ptr)
2312
0
                    return nullptr;
2313
0
                memcpy(ptr, TYPE, TYPE_SIZE);
2314
0
            }
2315
0
            else if (pszDriverName && (EQUAL(pszDriverName, "JP2KAK") ||
2316
0
                                       EQUAL(pszDriverName, "JP2OpenJPEG") ||
2317
0
                                       EQUAL(pszDriverName, "JP2ECW") ||
2318
0
                                       EQUAL(pszDriverName, "JP2MrSID")))
2319
0
            {
2320
0
                constexpr const char TYPE[] = "image/jp2";
2321
0
                constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
2322
0
                void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2323
0
                    imageTypeArray, nBatchSize, TYPE_SIZE,
2324
0
                    nImageTypeArrayMaxAlloc, false);
2325
0
                if (!ptr)
2326
0
                    return nullptr;
2327
0
                memcpy(ptr, TYPE, TYPE_SIZE);
2328
0
            }
2329
0
            else
2330
0
            {
2331
0
                OGRArrowArrayHelper::SetNull(imageTypeArray, nBatchSize,
2332
0
                                             nMaxBatchSize, false);
2333
0
                OGRArrowArrayHelper::SetEmptyStringOrBinary(imageTypeArray,
2334
0
                                                            nBatchSize);
2335
0
            }
2336
0
            imageTypeArray->length++;
2337
2338
            // Write "assets.image.title"
2339
0
            {
2340
0
                OGRArrowArrayHelper::SetNull(imageTitleArray, nBatchSize,
2341
0
                                             nMaxBatchSize, false);
2342
0
                OGRArrowArrayHelper::SetEmptyStringOrBinary(imageTitleArray,
2343
0
                                                            nBatchSize);
2344
0
                imageTitleArray->length++;
2345
0
            }
2346
2347
            // Write "bands"
2348
0
            {
2349
0
                const int nThisBands = poSrcDS->GetRasterCount();
2350
0
                if (nThisBands + nBandsItemCount > nBandsItemAlloc)
2351
0
                {
2352
0
                    const auto nOldAlloc = nBandsItemAlloc;
2353
0
                    if (nBandsItemAlloc >
2354
0
                        std::numeric_limits<uint32_t>::max() / 2)
2355
0
                    {
2356
0
                        CPLError(CE_Failure, CPLE_AppDefined, "Too many bands");
2357
0
                        return nullptr;
2358
0
                    }
2359
0
                    nBandsItemAlloc = std::max(2 * nBandsItemAlloc,
2360
0
                                               nThisBands + nBandsItemCount);
2361
2362
0
                    auto ReallocArray = [nOldAlloc, nBandsItemAlloc](
2363
0
                                            ArrowArray *array, size_t nItemSize)
2364
0
                    {
2365
0
                        if (array->buffers[ARROW_BUF_VALIDITY])
2366
0
                        {
2367
                            // Bitmap
2368
0
                            const uint32_t nNewSizeBytes =
2369
0
                                (nBandsItemAlloc + 7) / 8;
2370
0
                            char *newPtr =
2371
0
                                static_cast<char *>(VSI_REALLOC_VERBOSE(
2372
0
                                    const_cast<void *>(
2373
0
                                        array->buffers[ARROW_BUF_VALIDITY]),
2374
0
                                    nNewSizeBytes));
2375
0
                            if (!newPtr)
2376
0
                                return false;
2377
0
                            array->buffers[ARROW_BUF_VALIDITY] =
2378
0
                                static_cast<const void *>(
2379
0
                                    const_cast<const char *>(newPtr));
2380
0
                            const uint32_t nOldSizeBytes = (nOldAlloc + 7) / 8;
2381
0
                            if (nNewSizeBytes > nOldSizeBytes)
2382
0
                            {
2383
                                // Initialize new allocated bytes as valid
2384
                                // They are set invalid explicitly with SetNull()
2385
0
                                memset(newPtr + nOldSizeBytes, 0xFF,
2386
0
                                       nNewSizeBytes - nOldSizeBytes);
2387
0
                            }
2388
0
                        }
2389
0
                        char *newPtr = static_cast<char *>(VSI_REALLOC_VERBOSE(
2390
0
                            const_cast<void *>(array->buffers[ARROW_BUF_DATA]),
2391
0
                            (nBandsItemAlloc + 1) * nItemSize));
2392
0
                        if (!newPtr)
2393
0
                            return false;
2394
0
                        array->buffers[ARROW_BUF_DATA] =
2395
0
                            static_cast<const void *>(
2396
0
                                const_cast<const char *>(newPtr));
2397
0
                        memset(newPtr + (nOldAlloc + 1) * nItemSize, 0,
2398
0
                               (nBandsItemAlloc - nOldAlloc) * nItemSize);
2399
0
                        return true;
2400
0
                    };
2401
2402
0
                    if (!ReallocArray(bandsNameArray, sizeof(uint32_t)) ||
2403
0
                        !ReallocArray(bandsCommonNameArray, sizeof(uint32_t)) ||
2404
0
                        !ReallocArray(bandsCenterWavelengthArray,
2405
0
                                      sizeof(float)) ||
2406
0
                        !ReallocArray(bandsFWHMArray, sizeof(float)) ||
2407
0
                        !ReallocArray(bandsNodataArray, sizeof(uint32_t)) ||
2408
0
                        !ReallocArray(bandsDataTypeArray, sizeof(uint32_t)) ||
2409
0
                        !ReallocArray(bandsUnitArray, sizeof(uint32_t)))
2410
0
                    {
2411
0
                        return nullptr;
2412
0
                    }
2413
0
                }
2414
2415
0
                uint32_t *panBandsOffsets =
2416
0
                    static_cast<uint32_t *>(const_cast<void *>(
2417
0
                        topArrays[iBandsArray]->buffers[ARROW_BUF_DATA]));
2418
0
                panBandsOffsets[nBatchSize + 1] =
2419
0
                    panBandsOffsets[nBatchSize] + nThisBands;
2420
2421
0
                for (int i = 0; i < nThisBands; ++i, ++nBandsItemCount)
2422
0
                {
2423
0
                    bandsItemArray->length++;
2424
2425
0
                    const auto poBand = poSrcDS->GetRasterBand(i + 1);
2426
0
                    {
2427
0
                        std::string osBandName = poBand->GetDescription();
2428
0
                        if (osBandName.empty())
2429
0
                            osBandName = "Band " + std::to_string(i + 1);
2430
0
                        void *ptr =
2431
0
                            OGRArrowArrayHelper::GetPtrForStringOrBinary(
2432
0
                                bandsNameArray, nBandsItemCount,
2433
0
                                osBandName.size(), nBandsNameArrayMaxAlloc,
2434
0
                                false);
2435
0
                        if (!ptr)
2436
0
                            return nullptr;
2437
0
                        memcpy(ptr, osBandName.data(), osBandName.size());
2438
0
                        bandsNameArray->length++;
2439
0
                    }
2440
2441
0
                    const char *pszCommonName =
2442
0
                        GDALGetSTACCommonNameFromColorInterp(
2443
0
                            poBand->GetColorInterpretation());
2444
0
                    if (pszCommonName)
2445
0
                    {
2446
0
                        const size_t nLen = strlen(pszCommonName);
2447
0
                        void *ptr =
2448
0
                            OGRArrowArrayHelper::GetPtrForStringOrBinary(
2449
0
                                bandsCommonNameArray, nBandsItemCount, nLen,
2450
0
                                nBandsCommonNameArrayMaxAlloc, false);
2451
0
                        if (!ptr)
2452
0
                            return nullptr;
2453
0
                        memcpy(ptr, pszCommonName, nLen);
2454
0
                    }
2455
0
                    else
2456
0
                    {
2457
0
                        OGRArrowArrayHelper::SetNull(bandsCommonNameArray,
2458
0
                                                     nBandsItemCount,
2459
0
                                                     nBandsItemAlloc, false);
2460
0
                        OGRArrowArrayHelper::SetEmptyStringOrBinary(
2461
0
                            bandsCommonNameArray, nBandsItemCount);
2462
0
                    }
2463
0
                    bandsCommonNameArray->length++;
2464
2465
0
                    if (const char *pszCenterWavelength =
2466
0
                            poBand->GetMetadataItem("CENTRAL_WAVELENGTH_UM",
2467
0
                                                    "IMAGERY"))
2468
0
                    {
2469
0
                        float *values = static_cast<float *>(
2470
0
                            const_cast<void *>(bandsCenterWavelengthArray
2471
0
                                                   ->buffers[ARROW_BUF_DATA]));
2472
0
                        values[nBandsItemCount] =
2473
0
                            static_cast<float>(CPLAtof(pszCenterWavelength));
2474
0
                    }
2475
0
                    else
2476
0
                    {
2477
0
                        OGRArrowArrayHelper::SetNull(bandsCenterWavelengthArray,
2478
0
                                                     nBandsItemCount,
2479
0
                                                     nBandsItemAlloc, false);
2480
0
                    }
2481
0
                    bandsCenterWavelengthArray->length++;
2482
2483
0
                    if (const char *pszFWHM =
2484
0
                            poBand->GetMetadataItem("FWHM_UM", "IMAGERY"))
2485
0
                    {
2486
0
                        float *values = static_cast<float *>(const_cast<void *>(
2487
0
                            bandsFWHMArray->buffers[ARROW_BUF_DATA]));
2488
0
                        values[nBandsItemCount] =
2489
0
                            static_cast<float>(CPLAtof(pszFWHM));
2490
0
                    }
2491
0
                    else
2492
0
                    {
2493
0
                        OGRArrowArrayHelper::SetNull(bandsFWHMArray,
2494
0
                                                     nBandsItemCount,
2495
0
                                                     nBandsItemAlloc, false);
2496
0
                    }
2497
0
                    bandsFWHMArray->length++;
2498
2499
0
                    int bHasNoData = false;
2500
0
                    const double dfNoDataValue =
2501
0
                        poBand->GetNoDataValue(&bHasNoData);
2502
0
                    if (bHasNoData)
2503
0
                    {
2504
0
                        const std::string osNodata =
2505
0
                            std::isnan(dfNoDataValue) ? "nan"
2506
0
                            : std::isinf(dfNoDataValue)
2507
0
                                ? (dfNoDataValue > 0 ? "inf" : "-inf")
2508
0
                                : CPLSPrintf("%.17g", dfNoDataValue);
2509
0
                        void *ptr =
2510
0
                            OGRArrowArrayHelper::GetPtrForStringOrBinary(
2511
0
                                bandsNodataArray, nBandsItemCount,
2512
0
                                osNodata.size(), nBandsNodataArrayMaxAlloc,
2513
0
                                false);
2514
0
                        if (!ptr)
2515
0
                            return nullptr;
2516
0
                        memcpy(ptr, osNodata.data(), osNodata.size());
2517
0
                    }
2518
0
                    else
2519
0
                    {
2520
0
                        OGRArrowArrayHelper::SetNull(bandsNodataArray,
2521
0
                                                     nBandsItemCount,
2522
0
                                                     nBandsItemAlloc, false);
2523
0
                    }
2524
0
                    bandsNodataArray->length++;
2525
2526
0
                    {
2527
0
                        const char *pszDT = "other";
2528
                        // clang-format off
2529
0
                        switch (poBand->GetRasterDataType())
2530
0
                        {
2531
0
                            case GDT_Int8:     pszDT = "int8";     break;
2532
0
                            case GDT_UInt8:    pszDT = "uint8";    break;
2533
0
                            case GDT_Int16:    pszDT = "int16";    break;
2534
0
                            case GDT_UInt16:   pszDT = "uint16";   break;
2535
0
                            case GDT_Int32:    pszDT = "int32";    break;
2536
0
                            case GDT_UInt32:   pszDT = "uint32";   break;
2537
0
                            case GDT_Int64:    pszDT = "int64";    break;
2538
0
                            case GDT_UInt64:   pszDT = "uint64";   break;
2539
0
                            case GDT_Float16:  pszDT = "float16";  break;
2540
0
                            case GDT_Float32:  pszDT = "float32";  break;
2541
0
                            case GDT_Float64:  pszDT = "float64";  break;
2542
0
                            case GDT_CInt16:   pszDT = "cint16";   break;
2543
0
                            case GDT_CInt32:   pszDT = "cint32";   break;
2544
0
                            case GDT_CFloat16: pszDT = "cfloat16"; break;
2545
0
                            case GDT_CFloat32: pszDT = "cfloat32"; break;
2546
0
                            case GDT_CFloat64: pszDT = "cfloat64"; break;
2547
0
                            case GDT_Unknown:                      break;
2548
0
                            case GDT_TypeCount:                    break;
2549
0
                        }
2550
                        // clang-format on
2551
0
                        const size_t nLen = strlen(pszDT);
2552
0
                        void *ptr =
2553
0
                            OGRArrowArrayHelper::GetPtrForStringOrBinary(
2554
0
                                bandsDataTypeArray, nBandsItemCount, nLen,
2555
0
                                nBandsDataTypeArrayMaxAlloc, false);
2556
0
                        if (!ptr)
2557
0
                            return nullptr;
2558
0
                        memcpy(ptr, pszDT, nLen);
2559
2560
0
                        bandsDataTypeArray->length++;
2561
0
                    }
2562
2563
0
                    const char *pszUnits = poBand->GetUnitType();
2564
0
                    if (pszUnits && pszUnits[0])
2565
0
                    {
2566
0
                        const size_t nLen = strlen(pszUnits);
2567
0
                        void *ptr =
2568
0
                            OGRArrowArrayHelper::GetPtrForStringOrBinary(
2569
0
                                bandsUnitArray, nBandsItemCount, nLen,
2570
0
                                nBandsUnitArrayMaxAlloc, false);
2571
0
                        if (!ptr)
2572
0
                            return nullptr;
2573
0
                        memcpy(ptr, pszUnits, nLen);
2574
0
                    }
2575
0
                    else
2576
0
                    {
2577
0
                        OGRArrowArrayHelper::SetNull(bandsUnitArray,
2578
0
                                                     nBandsItemCount,
2579
0
                                                     nBandsItemAlloc, false);
2580
0
                    }
2581
0
                    bandsUnitArray->length++;
2582
0
                }
2583
0
            }
2584
2585
            // Write "proj:code"
2586
0
            bool bHasProjCode = false;
2587
0
            {
2588
0
                auto psArray = topArrays[iProjCode];
2589
0
                const char *pszSRSAuthName =
2590
0
                    poSrcSRS ? poSrcSRS->GetAuthorityName(nullptr) : nullptr;
2591
0
                const char *pszSRSAuthCode =
2592
0
                    poSrcSRS ? poSrcSRS->GetAuthorityCode(nullptr) : nullptr;
2593
0
                if (pszSRSAuthName && pszSRSAuthCode)
2594
0
                {
2595
0
                    std::string osCode(pszSRSAuthName);
2596
0
                    osCode += ':';
2597
0
                    osCode += pszSRSAuthCode;
2598
0
                    void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2599
0
                        psArray, nBatchSize, osCode.size(),
2600
0
                        nProjCodeArrayMaxAlloc, false);
2601
0
                    if (!ptr)
2602
0
                        return nullptr;
2603
0
                    memcpy(ptr, osCode.data(), osCode.size());
2604
0
                    bHasProjCode = true;
2605
0
                }
2606
0
                else
2607
0
                {
2608
0
                    OGRArrowArrayHelper::SetNull(psArray, nBatchSize,
2609
0
                                                 nMaxBatchSize, false);
2610
0
                    OGRArrowArrayHelper::SetEmptyStringOrBinary(psArray,
2611
0
                                                                nBatchSize);
2612
0
                }
2613
0
            }
2614
2615
            // Write "proj:wkt2"
2616
0
            {
2617
0
                auto psArray = topArrays[iProjWKT2];
2618
0
                std::string osWKT2;
2619
0
                if (poSrcSRS && !bHasProjCode)
2620
0
                {
2621
0
                    const char *const apszOptions[] = {"FORMAT=WKT2_2019",
2622
0
                                                       nullptr};
2623
0
                    osWKT2 = poSrcSRS->exportToWkt(apszOptions);
2624
0
                }
2625
0
                if (!osWKT2.empty())
2626
0
                {
2627
0
                    void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2628
0
                        psArray, nBatchSize, osWKT2.size(),
2629
0
                        nProjWKT2ArrayMaxAlloc, false);
2630
0
                    if (!ptr)
2631
0
                        return nullptr;
2632
0
                    memcpy(ptr, osWKT2.data(), osWKT2.size());
2633
0
                }
2634
0
                else
2635
0
                {
2636
0
                    OGRArrowArrayHelper::SetNull(psArray, nBatchSize,
2637
0
                                                 nMaxBatchSize, false);
2638
0
                    OGRArrowArrayHelper::SetEmptyStringOrBinary(psArray,
2639
0
                                                                nBatchSize);
2640
0
                }
2641
0
            }
2642
2643
            // Write "proj:projjson"
2644
0
            {
2645
0
                auto psArray = topArrays[iProjPROJJSON];
2646
0
                std::string osPROJJSON;
2647
0
                if (poSrcSRS && !bHasProjCode)
2648
0
                {
2649
0
                    char *pszPROJJSON = nullptr;
2650
0
                    poSrcSRS->exportToPROJJSON(&pszPROJJSON, nullptr);
2651
0
                    if (pszPROJJSON)
2652
0
                        osPROJJSON = pszPROJJSON;
2653
0
                    CPLFree(pszPROJJSON);
2654
0
                }
2655
0
                if (!osPROJJSON.empty())
2656
0
                {
2657
0
                    void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2658
0
                        psArray, nBatchSize, osPROJJSON.size(),
2659
0
                        nProjPROJJSONArrayMaxAlloc, false);
2660
0
                    if (!ptr)
2661
0
                        return nullptr;
2662
0
                    memcpy(ptr, osPROJJSON.data(), osPROJJSON.size());
2663
0
                }
2664
0
                else
2665
0
                {
2666
0
                    OGRArrowArrayHelper::SetNull(psArray, nBatchSize,
2667
0
                                                 nMaxBatchSize, false);
2668
0
                    OGRArrowArrayHelper::SetEmptyStringOrBinary(psArray,
2669
0
                                                                nBatchSize);
2670
0
                }
2671
0
            }
2672
2673
            // Write proj:bbox
2674
0
            {
2675
0
                double *values = static_cast<double *>(
2676
0
                    const_cast<void *>(projBBOXItems->buffers[ARROW_BUF_DATA]));
2677
0
                auto ptr = values + nBatchSize * NUM_ITEMS_PROJ_BBOX;
2678
0
                ptr[0] = dfMinXBeforeReproj;
2679
0
                ptr[1] = dfMinYBeforeReproj;
2680
0
                ptr[2] = dfMaxXBeforeReproj;
2681
0
                ptr[3] = dfMaxYBeforeReproj;
2682
0
            }
2683
2684
            // Write proj:shape
2685
0
            {
2686
0
                int32_t *values = static_cast<int32_t *>(const_cast<void *>(
2687
0
                    projShapeItems->buffers[ARROW_BUF_DATA]));
2688
0
                auto ptr = values + nBatchSize * NUM_ITEMS_PROJ_SHAPE;
2689
0
                ptr[0] = poSrcDS->GetRasterYSize();
2690
0
                ptr[1] = poSrcDS->GetRasterXSize();
2691
0
            }
2692
2693
            // Write proj:transform
2694
0
            {
2695
0
                double *values = static_cast<double *>(const_cast<void *>(
2696
0
                    projTransformItems->buffers[ARROW_BUF_DATA]));
2697
0
                auto ptr = values + nBatchSize * NUM_ITEMS_PROJ_TRANSFORM;
2698
0
                ptr[0] = gt.xscale;
2699
0
                ptr[1] = gt.xrot;
2700
0
                ptr[2] = gt.xorig;
2701
0
                ptr[3] = gt.yrot;
2702
0
                ptr[4] = gt.yscale;
2703
0
                ptr[5] = gt.yorig;
2704
0
                ptr[6] = 0;
2705
0
                ptr[7] = 0;
2706
0
                ptr[8] = 1;
2707
0
            }
2708
2709
            // Write geometry
2710
0
            {
2711
0
                const size_t nWKBSize = poPoly->WkbSize();
2712
0
                void *ptr = arrayHelper->GetPtrForStringOrBinary(
2713
0
                    iWkbArray, nBatchSize, nWKBSize, false);
2714
0
                if (!ptr)
2715
0
                    return nullptr;
2716
0
                OGRwkbExportOptions sExportOptions;
2717
0
                sExportOptions.eWkbVariant = wkbVariantIso;
2718
0
                if (poPoly->exportToWkb(static_cast<unsigned char *>(ptr),
2719
0
                                        &sExportOptions) != OGRERR_NONE)
2720
0
                    return nullptr;
2721
0
            }
2722
2723
0
            nBatchSize++;
2724
0
            if (nBatchSize == nMaxBatchSize && !FlushArrays())
2725
0
            {
2726
0
                return nullptr;
2727
0
            }
2728
0
        }
2729
0
        else
2730
0
        {
2731
0
            auto poFeature = std::make_unique<OGRFeature>(poLayerDefn);
2732
0
            poFeature->SetField(ti_field, osFileNameToWrite.c_str());
2733
2734
0
            if (i_SrcSRSName >= 0 && poSrcSRS)
2735
0
            {
2736
0
                const char *pszAuthorityCode =
2737
0
                    poSrcSRS->GetAuthorityCode(nullptr);
2738
0
                const char *pszAuthorityName =
2739
0
                    poSrcSRS->GetAuthorityName(nullptr);
2740
0
                if (psOptions->eSrcSRSFormat == FORMAT_AUTO)
2741
0
                {
2742
0
                    if (pszAuthorityName != nullptr &&
2743
0
                        pszAuthorityCode != nullptr)
2744
0
                    {
2745
0
                        poFeature->SetField(
2746
0
                            i_SrcSRSName, CPLSPrintf("%s:%s", pszAuthorityName,
2747
0
                                                     pszAuthorityCode));
2748
0
                    }
2749
0
                    else if (nMaxFieldSize == 0 ||
2750
0
                             strlen(poSrcDS->GetProjectionRef()) <=
2751
0
                                 static_cast<size_t>(nMaxFieldSize))
2752
0
                    {
2753
0
                        poFeature->SetField(i_SrcSRSName,
2754
0
                                            poSrcDS->GetProjectionRef());
2755
0
                    }
2756
0
                    else
2757
0
                    {
2758
0
                        char *pszProj4 = nullptr;
2759
0
                        if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
2760
0
                        {
2761
0
                            poFeature->SetField(i_SrcSRSName, pszProj4);
2762
0
                        }
2763
0
                        else
2764
0
                        {
2765
0
                            poFeature->SetField(i_SrcSRSName,
2766
0
                                                poSrcDS->GetProjectionRef());
2767
0
                        }
2768
0
                        CPLFree(pszProj4);
2769
0
                    }
2770
0
                }
2771
0
                else if (psOptions->eSrcSRSFormat == FORMAT_WKT)
2772
0
                {
2773
0
                    if (nMaxFieldSize == 0 ||
2774
0
                        strlen(poSrcDS->GetProjectionRef()) <=
2775
0
                            static_cast<size_t>(nMaxFieldSize))
2776
0
                    {
2777
0
                        poFeature->SetField(i_SrcSRSName,
2778
0
                                            poSrcDS->GetProjectionRef());
2779
0
                    }
2780
0
                    else
2781
0
                    {
2782
0
                        CPLError(
2783
0
                            CE_Warning, CPLE_AppDefined,
2784
0
                            "Cannot write WKT for file %s as it is too long!",
2785
0
                            osFileNameToWrite.c_str());
2786
0
                    }
2787
0
                }
2788
0
                else if (psOptions->eSrcSRSFormat == FORMAT_PROJ)
2789
0
                {
2790
0
                    char *pszProj4 = nullptr;
2791
0
                    if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
2792
0
                    {
2793
0
                        poFeature->SetField(i_SrcSRSName, pszProj4);
2794
0
                    }
2795
0
                    CPLFree(pszProj4);
2796
0
                }
2797
0
                else if (psOptions->eSrcSRSFormat == FORMAT_EPSG)
2798
0
                {
2799
0
                    if (pszAuthorityName != nullptr &&
2800
0
                        pszAuthorityCode != nullptr)
2801
0
                        poFeature->SetField(
2802
0
                            i_SrcSRSName, CPLSPrintf("%s:%s", pszAuthorityName,
2803
0
                                                     pszAuthorityCode));
2804
0
                }
2805
0
            }
2806
2807
0
            for (const auto &oFetchMD : psOptions->aoFetchMD)
2808
0
            {
2809
0
                if (EQUAL(oFetchMD.osRasterItemName.c_str(), "{PIXEL_SIZE}"))
2810
0
                {
2811
0
                    poFeature->SetField(oFetchMD.osFieldName.c_str(), dfRes);
2812
0
                    continue;
2813
0
                }
2814
2815
0
                const char *pszMD =
2816
0
                    poSrcDS->GetMetadataItem(oFetchMD.osRasterItemName.c_str());
2817
0
                if (pszMD)
2818
0
                {
2819
0
                    if (EQUAL(oFetchMD.osRasterItemName.c_str(),
2820
0
                              "TIFFTAG_DATETIME"))
2821
0
                    {
2822
0
                        int nYear, nMonth, nDay, nHour, nMin, nSec;
2823
0
                        if (sscanf(pszMD, "%04d:%02d:%02d %02d:%02d:%02d",
2824
0
                                   &nYear, &nMonth, &nDay, &nHour, &nMin,
2825
0
                                   &nSec) == 6)
2826
0
                        {
2827
0
                            poFeature->SetField(
2828
0
                                oFetchMD.osFieldName.c_str(),
2829
0
                                CPLSPrintf("%04d/%02d/%02d %02d:%02d:%02d",
2830
0
                                           nYear, nMonth, nDay, nHour, nMin,
2831
0
                                           nSec));
2832
0
                            continue;
2833
0
                        }
2834
0
                    }
2835
0
                    poFeature->SetField(oFetchMD.osFieldName.c_str(), pszMD);
2836
0
                }
2837
0
            }
2838
2839
0
            poFeature->SetGeometryDirectly(poPoly.release());
2840
2841
0
            if (poLayer->CreateFeature(poFeature.get()) != OGRERR_NONE)
2842
0
            {
2843
0
                CPLError(CE_Failure, CPLE_AppDefined,
2844
0
                         "Failed to create feature in tile index.");
2845
0
                return nullptr;
2846
0
            }
2847
0
        }
2848
2849
0
        ++iCur;
2850
0
        if (psOptions->pfnProgress &&
2851
0
            !psOptions->pfnProgress(static_cast<double>(iCur) / nTotal, "",
2852
0
                                    psOptions->pProgressData))
2853
0
        {
2854
0
            return nullptr;
2855
0
        }
2856
0
        if (iCur >= nSrcCount)
2857
0
            ++nTotal;
2858
0
    }
2859
0
    if (psOptions->pfnProgress)
2860
0
        psOptions->pfnProgress(1.0, "", psOptions->pProgressData);
2861
2862
0
    if (nBandInterleavedCount > 0 &&
2863
0
        nBandInterleavedCount > nPixelInterleavedCount)
2864
0
    {
2865
0
        if (psRoot)
2866
0
            CPLCreateXMLElementAndValue(psRoot.get(), "Interleave", "Band");
2867
0
        else
2868
0
            poLayer->SetMetadataItem("INTERLEAVE", "BAND");
2869
0
    }
2870
0
    else if (nPixelInterleavedCount > 0)
2871
0
    {
2872
0
        if (psRoot)
2873
0
            CPLCreateXMLElementAndValue(psRoot.get(), "Interleave", "Pixel");
2874
0
        else
2875
0
            poLayer->SetMetadataItem("INTERLEAVE", "PIXEL");
2876
0
    }
2877
2878
0
    if (!psOptions->osGTIFilename.empty())
2879
0
    {
2880
0
        if (!CPLSerializeXMLTreeToFile(psRoot.get(),
2881
0
                                       psOptions->osGTIFilename.c_str()))
2882
0
            return nullptr;
2883
0
    }
2884
2885
0
    if (bIsSTACGeoParquet && nBatchSize != 0 && !FlushArrays())
2886
0
    {
2887
0
        return nullptr;
2888
0
    }
2889
2890
0
    if (poTileIndexDSUnique)
2891
0
        return GDALDataset::ToHandle(poTileIndexDSUnique.release());
2892
0
    else
2893
0
        return GDALDataset::ToHandle(poTileIndexDS);
2894
0
}
2895
2896
/************************************************************************/
2897
/*                             SanitizeSRS                              */
2898
/************************************************************************/
2899
2900
static char *SanitizeSRS(const char *pszUserInput)
2901
2902
0
{
2903
0
    OGRSpatialReferenceH hSRS;
2904
0
    char *pszResult = nullptr;
2905
2906
0
    CPLErrorReset();
2907
2908
0
    hSRS = OSRNewSpatialReference(nullptr);
2909
0
    if (OSRSetFromUserInput(hSRS, pszUserInput) == OGRERR_NONE)
2910
0
        OSRExportToWkt(hSRS, &pszResult);
2911
0
    else
2912
0
    {
2913
0
        CPLError(CE_Failure, CPLE_AppDefined, "Translating SRS failed:\n%s",
2914
0
                 pszUserInput);
2915
0
    }
2916
2917
0
    OSRDestroySpatialReference(hSRS);
2918
2919
0
    return pszResult;
2920
0
}
2921
2922
/************************************************************************/
2923
/*                      GDALTileIndexOptionsNew()                       */
2924
/************************************************************************/
2925
2926
/**
2927
 * Allocates a GDALTileIndexOptions struct.
2928
 *
2929
 * @param papszArgv NULL terminated list of options (potentially including
2930
 * filename and open options too), or NULL. The accepted options are the ones of
2931
 * the <a href="/programs/gdaltindex.html">gdaltindex</a> utility.
2932
 * @param psOptionsForBinary (output) may be NULL (and should generally be
2933
 * NULL), otherwise (gdaltindex_bin.cpp use case) must be allocated with
2934
 * GDALTileIndexOptionsForBinaryNew() prior to this function. Will be filled
2935
 * with potentially present filename, open options,...
2936
 * @return pointer to the allocated GDALTileIndexOptions struct. Must be freed
2937
 * with GDALTileIndexOptionsFree().
2938
 *
2939
 * @since GDAL 3.9
2940
 */
2941
2942
GDALTileIndexOptions *
2943
GDALTileIndexOptionsNew(char **papszArgv,
2944
                        GDALTileIndexOptionsForBinary *psOptionsForBinary)
2945
0
{
2946
0
    auto psOptions = std::make_unique<GDALTileIndexOptions>();
2947
2948
    /* -------------------------------------------------------------------- */
2949
    /*      Parse arguments.                                                */
2950
    /* -------------------------------------------------------------------- */
2951
2952
0
    CPLStringList aosArgv;
2953
2954
0
    if (papszArgv)
2955
0
    {
2956
0
        const int nArgc = CSLCount(papszArgv);
2957
0
        for (int i = 0; i < nArgc; i++)
2958
0
        {
2959
0
            aosArgv.AddString(papszArgv[i]);
2960
0
        }
2961
0
    }
2962
2963
0
    try
2964
0
    {
2965
0
        auto argParser = GDALTileIndexAppOptionsGetParser(psOptions.get(),
2966
0
                                                          psOptionsForBinary);
2967
0
        argParser->parse_args_without_binary_name(aosArgv.List());
2968
2969
        // Check all no store_into args
2970
0
        if (auto oTr = argParser->present<std::vector<double>>("-tr"))
2971
0
        {
2972
0
            psOptions->xres = (*oTr)[0];
2973
0
            psOptions->yres = (*oTr)[1];
2974
0
        }
2975
2976
0
        if (auto oTargetExtent = argParser->present<std::vector<double>>("-te"))
2977
0
        {
2978
0
            psOptions->xmin = (*oTargetExtent)[0];
2979
0
            psOptions->ymin = (*oTargetExtent)[1];
2980
0
            psOptions->xmax = (*oTargetExtent)[2];
2981
0
            psOptions->ymax = (*oTargetExtent)[3];
2982
0
        }
2983
2984
0
        if (auto fetchMd =
2985
0
                argParser->present<std::vector<std::string>>("-fetch_md"))
2986
0
        {
2987
2988
0
            CPLAssert(fetchMd->size() % 3 == 0);
2989
2990
            // Loop
2991
0
            for (size_t i = 0; i < fetchMd->size(); i += 3)
2992
0
            {
2993
0
                OGRFieldType type;
2994
0
                const auto &typeName{fetchMd->at(i + 2)};
2995
0
                if (typeName == "String")
2996
0
                {
2997
0
                    type = OFTString;
2998
0
                }
2999
0
                else if (typeName == "Integer")
3000
0
                {
3001
0
                    type = OFTInteger;
3002
0
                }
3003
0
                else if (typeName == "Integer64")
3004
0
                {
3005
0
                    type = OFTInteger64;
3006
0
                }
3007
0
                else if (typeName == "Real")
3008
0
                {
3009
0
                    type = OFTReal;
3010
0
                }
3011
0
                else if (typeName == "Date")
3012
0
                {
3013
0
                    type = OFTDate;
3014
0
                }
3015
0
                else if (typeName == "DateTime")
3016
0
                {
3017
0
                    type = OFTDateTime;
3018
0
                }
3019
0
                else
3020
0
                {
3021
0
                    CPLError(CE_Failure, CPLE_AppDefined,
3022
0
                             "-fetch_md requires a valid type name as third "
3023
0
                             "argument: %s was given.",
3024
0
                             fetchMd->at(i).c_str());
3025
0
                    return nullptr;
3026
0
                }
3027
3028
0
                const GDALTileIndexRasterMetadata oMD{type, fetchMd->at(i + 1),
3029
0
                                                      fetchMd->at(i)};
3030
0
                psOptions->aoFetchMD.push_back(std::move(oMD));
3031
0
            }
3032
0
        }
3033
3034
        // Check -t_srs
3035
0
        if (!psOptions->osTargetSRS.empty())
3036
0
        {
3037
0
            auto sanitized{SanitizeSRS(psOptions->osTargetSRS.c_str())};
3038
0
            if (sanitized)
3039
0
            {
3040
0
                psOptions->osTargetSRS = sanitized;
3041
0
                CPLFree(sanitized);
3042
0
            }
3043
0
            else
3044
0
            {
3045
                // Error was already reported by SanitizeSRS, just return nullptr
3046
0
                psOptions->osTargetSRS.clear();
3047
0
                return nullptr;
3048
0
            }
3049
0
        }
3050
0
    }
3051
0
    catch (const std::exception &error)
3052
0
    {
3053
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
3054
0
        return nullptr;
3055
0
    }
3056
3057
0
    return psOptions.release();
3058
0
}
3059
3060
/************************************************************************/
3061
/*                      GDALTileIndexOptionsFree()                      */
3062
/************************************************************************/
3063
3064
/**
3065
 * Frees the GDALTileIndexOptions struct.
3066
 *
3067
 * @param psOptions the options struct for GDALTileIndex().
3068
 *
3069
 * @since GDAL 3.9
3070
 */
3071
3072
void GDALTileIndexOptionsFree(GDALTileIndexOptions *psOptions)
3073
0
{
3074
0
    delete psOptions;
3075
0
}
3076
3077
/************************************************************************/
3078
/*                  GDALTileIndexOptionsSetProgress()                   */
3079
/************************************************************************/
3080
3081
/**
3082
 * Set a progress function.
3083
 *
3084
 * @param psOptions the options struct for GDALTileIndex().
3085
 * @param pfnProgress the progress callback.
3086
 * @param pProgressData the user data for the progress callback.
3087
 *
3088
 * @since GDAL 3.11
3089
 */
3090
3091
void GDALTileIndexOptionsSetProgress(GDALTileIndexOptions *psOptions,
3092
                                     GDALProgressFunc pfnProgress,
3093
                                     void *pProgressData)
3094
0
{
3095
0
    psOptions->pfnProgress = pfnProgress;
3096
0
    psOptions->pProgressData = pProgressData;
3097
0
}
3098
3099
#undef CHECK_HAS_ENOUGH_ADDITIONAL_ARGS