Coverage Report

Created: 2026-02-14 06:52

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
    if (!psOptions->osGTIFilename.empty())
1195
0
    {
1196
0
        if (!psOptions->aosMetadata.empty())
1197
0
        {
1198
0
            CPLError(CE_Failure, CPLE_NotSupported,
1199
0
                     "-mo is not supported when -gti_filename is used");
1200
0
            return nullptr;
1201
0
        }
1202
0
        CPLXMLNode *psRoot =
1203
0
            CPLCreateXMLNode(nullptr, CXT_Element, "GDALTileIndexDataset");
1204
0
        CPLCreateXMLElementAndValue(psRoot, "IndexDataset", pszDest);
1205
0
        CPLCreateXMLElementAndValue(psRoot, "IndexLayer", poLayer->GetName());
1206
0
        CPLCreateXMLElementAndValue(psRoot, "LocationField",
1207
0
                                    psOptions->osLocationField.c_str());
1208
0
        if (!std::isnan(psOptions->xres))
1209
0
        {
1210
0
            CPLCreateXMLElementAndValue(psRoot, "ResX",
1211
0
                                        CPLSPrintf("%.18g", psOptions->xres));
1212
0
            CPLCreateXMLElementAndValue(psRoot, "ResY",
1213
0
                                        CPLSPrintf("%.18g", psOptions->yres));
1214
0
        }
1215
0
        if (!std::isnan(psOptions->xmin))
1216
0
        {
1217
0
            CPLCreateXMLElementAndValue(psRoot, "MinX",
1218
0
                                        CPLSPrintf("%.18g", psOptions->xmin));
1219
0
            CPLCreateXMLElementAndValue(psRoot, "MinY",
1220
0
                                        CPLSPrintf("%.18g", psOptions->ymin));
1221
0
            CPLCreateXMLElementAndValue(psRoot, "MaxX",
1222
0
                                        CPLSPrintf("%.18g", psOptions->xmax));
1223
0
            CPLCreateXMLElementAndValue(psRoot, "MaxY",
1224
0
                                        CPLSPrintf("%.18g", psOptions->ymax));
1225
0
        }
1226
1227
0
        int nBandCount = 0;
1228
0
        if (!psOptions->osBandCount.empty())
1229
0
        {
1230
0
            nBandCount = atoi(psOptions->osBandCount.c_str());
1231
0
        }
1232
0
        else
1233
0
        {
1234
0
            if (!psOptions->osDataType.empty())
1235
0
            {
1236
0
                nBandCount = std::max(
1237
0
                    nBandCount,
1238
0
                    CPLStringList(CSLTokenizeString2(
1239
0
                                      psOptions->osDataType.c_str(), ", ", 0))
1240
0
                        .size());
1241
0
            }
1242
0
            if (!psOptions->osNodata.empty())
1243
0
            {
1244
0
                nBandCount = std::max(
1245
0
                    nBandCount,
1246
0
                    CPLStringList(CSLTokenizeString2(
1247
0
                                      psOptions->osNodata.c_str(), ", ", 0))
1248
0
                        .size());
1249
0
            }
1250
0
            if (!psOptions->osColorInterp.empty())
1251
0
            {
1252
0
                nBandCount =
1253
0
                    std::max(nBandCount,
1254
0
                             CPLStringList(
1255
0
                                 CSLTokenizeString2(
1256
0
                                     psOptions->osColorInterp.c_str(), ", ", 0))
1257
0
                                 .size());
1258
0
            }
1259
0
        }
1260
1261
0
        for (int i = 0; i < nBandCount; ++i)
1262
0
        {
1263
0
            auto psBand = CPLCreateXMLNode(psRoot, CXT_Element, "Band");
1264
0
            CPLAddXMLAttributeAndValue(psBand, "band", CPLSPrintf("%d", i + 1));
1265
0
            if (!psOptions->osDataType.empty())
1266
0
            {
1267
0
                const CPLStringList aosTokens(
1268
0
                    CSLTokenizeString2(psOptions->osDataType.c_str(), ", ", 0));
1269
0
                if (aosTokens.size() == 1)
1270
0
                    CPLAddXMLAttributeAndValue(psBand, "dataType",
1271
0
                                               aosTokens[0]);
1272
0
                else if (i < aosTokens.size())
1273
0
                    CPLAddXMLAttributeAndValue(psBand, "dataType",
1274
0
                                               aosTokens[i]);
1275
0
            }
1276
0
            if (!psOptions->osNodata.empty())
1277
0
            {
1278
0
                const CPLStringList aosTokens(
1279
0
                    CSLTokenizeString2(psOptions->osNodata.c_str(), ", ", 0));
1280
0
                if (aosTokens.size() == 1)
1281
0
                    CPLCreateXMLElementAndValue(psBand, "NoDataValue",
1282
0
                                                aosTokens[0]);
1283
0
                else if (i < aosTokens.size())
1284
0
                    CPLCreateXMLElementAndValue(psBand, "NoDataValue",
1285
0
                                                aosTokens[i]);
1286
0
            }
1287
0
            if (!psOptions->osColorInterp.empty())
1288
0
            {
1289
0
                const CPLStringList aosTokens(CSLTokenizeString2(
1290
0
                    psOptions->osColorInterp.c_str(), ", ", 0));
1291
0
                if (aosTokens.size() == 1)
1292
0
                    CPLCreateXMLElementAndValue(psBand, "ColorInterp",
1293
0
                                                aosTokens[0]);
1294
0
                else if (i < aosTokens.size())
1295
0
                    CPLCreateXMLElementAndValue(psBand, "ColorInterp",
1296
0
                                                aosTokens[i]);
1297
0
            }
1298
0
        }
1299
1300
0
        if (psOptions->bMaskBand)
1301
0
        {
1302
0
            CPLCreateXMLElementAndValue(psRoot, "MaskBand", "true");
1303
0
        }
1304
0
        int res =
1305
0
            CPLSerializeXMLTreeToFile(psRoot, psOptions->osGTIFilename.c_str());
1306
0
        CPLDestroyXMLNode(psRoot);
1307
0
        if (!res)
1308
0
            return nullptr;
1309
0
    }
1310
0
    else
1311
0
    {
1312
0
        if (!bIsSTACGeoParquet)
1313
0
        {
1314
0
            poLayer->SetMetadataItem("LOCATION_FIELD",
1315
0
                                     psOptions->osLocationField.c_str());
1316
0
        }
1317
0
        if (!std::isnan(psOptions->xres))
1318
0
        {
1319
0
            poLayer->SetMetadataItem("RESX",
1320
0
                                     CPLSPrintf("%.18g", psOptions->xres));
1321
0
            poLayer->SetMetadataItem("RESY",
1322
0
                                     CPLSPrintf("%.18g", psOptions->yres));
1323
0
        }
1324
0
        if (!std::isnan(psOptions->xmin))
1325
0
        {
1326
0
            poLayer->SetMetadataItem("MINX",
1327
0
                                     CPLSPrintf("%.18g", psOptions->xmin));
1328
0
            poLayer->SetMetadataItem("MINY",
1329
0
                                     CPLSPrintf("%.18g", psOptions->ymin));
1330
0
            poLayer->SetMetadataItem("MAXX",
1331
0
                                     CPLSPrintf("%.18g", psOptions->xmax));
1332
0
            poLayer->SetMetadataItem("MAXY",
1333
0
                                     CPLSPrintf("%.18g", psOptions->ymax));
1334
0
        }
1335
0
        if (!psOptions->osBandCount.empty())
1336
0
        {
1337
0
            poLayer->SetMetadataItem("BAND_COUNT",
1338
0
                                     psOptions->osBandCount.c_str());
1339
0
        }
1340
0
        if (!psOptions->osDataType.empty())
1341
0
        {
1342
0
            poLayer->SetMetadataItem("DATA_TYPE",
1343
0
                                     psOptions->osDataType.c_str());
1344
0
        }
1345
0
        if (!psOptions->osNodata.empty())
1346
0
        {
1347
0
            poLayer->SetMetadataItem("NODATA", psOptions->osNodata.c_str());
1348
0
        }
1349
0
        if (!psOptions->osColorInterp.empty())
1350
0
        {
1351
0
            poLayer->SetMetadataItem("COLOR_INTERPRETATION",
1352
0
                                     psOptions->osColorInterp.c_str());
1353
0
        }
1354
0
        if (psOptions->bMaskBand)
1355
0
        {
1356
0
            poLayer->SetMetadataItem("MASK_BAND", "YES");
1357
0
        }
1358
0
        const CPLStringList aosMetadata(psOptions->aosMetadata);
1359
0
        for (const auto &[pszKey, pszValue] :
1360
0
             cpl::IterateNameValue(aosMetadata))
1361
0
        {
1362
0
            poLayer->SetMetadataItem(pszKey, pszValue);
1363
0
        }
1364
0
    }
1365
1366
0
    const int ti_field =
1367
0
        poLayerDefn->GetFieldIndex(psOptions->osLocationField.c_str());
1368
0
    if (!bIsSTACGeoParquet && ti_field < 0)
1369
0
    {
1370
0
        CPLError(CE_Failure, CPLE_AppDefined,
1371
0
                 "Unable to find field `%s' in file `%s'.",
1372
0
                 psOptions->osLocationField.c_str(), pszDest);
1373
0
        return nullptr;
1374
0
    }
1375
1376
0
    int i_SrcSRSName = -1;
1377
0
    if (!bIsSTACGeoParquet && !psOptions->osSrcSRSFieldName.empty())
1378
0
    {
1379
0
        i_SrcSRSName =
1380
0
            poLayerDefn->GetFieldIndex(psOptions->osSrcSRSFieldName.c_str());
1381
0
        if (i_SrcSRSName < 0)
1382
0
        {
1383
0
            CPLError(CE_Failure, CPLE_AppDefined,
1384
0
                     "Unable to find field `%s' in file `%s'.",
1385
0
                     psOptions->osSrcSRSFieldName.c_str(), pszDest);
1386
0
            return nullptr;
1387
0
        }
1388
0
    }
1389
1390
    // Load in memory existing file names in tile index.
1391
0
    std::set<std::string> oSetExistingFiles;
1392
0
    OGRSpatialReference oAlreadyExistingSRS;
1393
0
    if (bExistingLayer)
1394
0
    {
1395
0
        for (auto &&poFeature : poLayer)
1396
0
        {
1397
0
            if (poFeature->IsFieldSetAndNotNull(ti_field))
1398
0
            {
1399
0
                if (oSetExistingFiles.empty())
1400
0
                {
1401
0
                    auto poSrcDS =
1402
0
                        std::unique_ptr<GDALDataset>(GDALDataset::Open(
1403
0
                            poFeature->GetFieldAsString(ti_field),
1404
0
                            GDAL_OF_RASTER, nullptr, nullptr, nullptr));
1405
0
                    if (poSrcDS)
1406
0
                    {
1407
0
                        auto poSrcSRS = poSrcDS->GetSpatialRef();
1408
0
                        if (poSrcSRS)
1409
0
                            oAlreadyExistingSRS = *poSrcSRS;
1410
0
                    }
1411
0
                }
1412
0
                oSetExistingFiles.insert(poFeature->GetFieldAsString(ti_field));
1413
0
            }
1414
0
        }
1415
0
    }
1416
1417
0
    std::string osCurrentPath;
1418
0
    if (psOptions->bWriteAbsolutePath)
1419
0
    {
1420
0
        char *pszCurrentPath = CPLGetCurrentDir();
1421
0
        if (pszCurrentPath)
1422
0
        {
1423
0
            osCurrentPath = pszCurrentPath;
1424
0
        }
1425
0
        else
1426
0
        {
1427
0
            CPLError(CE_Warning, CPLE_AppDefined,
1428
0
                     "This system does not support the CPLGetCurrentDir call. "
1429
0
                     "The option -bWriteAbsolutePath will have no effect.");
1430
0
        }
1431
0
        CPLFree(pszCurrentPath);
1432
0
    }
1433
1434
0
    const bool bIsGTIContext =
1435
0
        !std::isnan(psOptions->xres) || !std::isnan(psOptions->xmin) ||
1436
0
        !psOptions->osBandCount.empty() || !psOptions->osNodata.empty() ||
1437
0
        !psOptions->osColorInterp.empty() || !psOptions->osDataType.empty() ||
1438
0
        psOptions->bMaskBand || !psOptions->aosMetadata.empty() ||
1439
0
        !psOptions->osGTIFilename.empty();
1440
1441
    /* -------------------------------------------------------------------- */
1442
    /*      loop over GDAL files, processing.                               */
1443
    /* -------------------------------------------------------------------- */
1444
0
    ArrowArray topArray{};
1445
1446
0
    struct TopArrayReleaser
1447
0
    {
1448
0
        ArrowArray *m_array = nullptr;
1449
1450
0
        explicit TopArrayReleaser(ArrowArray *array) : m_array(array)
1451
0
        {
1452
0
        }
1453
1454
0
        ~TopArrayReleaser()
1455
0
        {
1456
0
            if (m_array && m_array->release)
1457
0
                m_array->release(m_array);
1458
0
        }
1459
1460
0
        TopArrayReleaser(const TopArrayReleaser &) = delete;
1461
0
        TopArrayReleaser &operator=(const TopArrayReleaser &) = delete;
1462
0
    };
1463
1464
0
    TopArrayReleaser arrayReleaser(&topArray);
1465
1466
0
    ArrowArray **topArrays = nullptr;
1467
1468
0
    int iArray = 0;
1469
0
    const int iIdArray = iArray++;
1470
1471
0
    const int iStacExtensionsArray = iArray++;
1472
0
    ArrowArray *stacExtensionSubArray = nullptr;
1473
0
    uint32_t nStacExtensionSubArrayMaxAlloc = 0;
1474
1475
0
    const int iLinksArray = iArray++;
1476
0
    ArrowArray *linksItemArray = nullptr;
1477
1478
0
    const int iAssetsArray = iArray++;
1479
0
    ArrowArray *imageArray = nullptr;
1480
0
    uint32_t nImageHrefArrayMaxAlloc = 0;
1481
0
    ArrowArray *imageHrefArray = nullptr;
1482
0
    ArrowArray *imageRoleArray = nullptr;
1483
0
    ArrowArray *imageRoleItemArray = nullptr;
1484
0
    uint32_t nImageRoleItemArrayMaxAlloc = 0;
1485
0
    ArrowArray *imageTitleArray = nullptr;
1486
0
    uint32_t nImageTitleArrayMaxAlloc = 0;
1487
0
    ArrowArray *imageTypeArray = nullptr;
1488
0
    uint32_t nImageTypeArrayMaxAlloc = 0;
1489
1490
0
    const int iBandsArray = iArray++;
1491
0
    uint32_t nBandsItemCount = 0;
1492
0
    uint32_t nBandsItemAlloc = 0;
1493
0
    ArrowArray *bandsItemArray = nullptr;
1494
0
    ArrowArray *bandsNameArray = nullptr;
1495
0
    uint32_t nBandsNameArrayMaxAlloc = 0;
1496
0
    ArrowArray *bandsCommonNameArray = nullptr;
1497
0
    uint32_t nBandsCommonNameArrayMaxAlloc = 0;
1498
0
    ArrowArray *bandsCenterWavelengthArray = nullptr;
1499
0
    uint32_t nBandsCenterWavelengthArrayMaxAlloc = 0;
1500
0
    ArrowArray *bandsFWHMArray = nullptr;
1501
0
    uint32_t nBandsFWHMArrayMaxAlloc = 0;
1502
0
    ArrowArray *bandsNodataArray = nullptr;
1503
0
    uint32_t nBandsNodataArrayMaxAlloc = 0;
1504
0
    ArrowArray *bandsDataTypeArray = nullptr;
1505
0
    uint32_t nBandsDataTypeArrayMaxAlloc = 0;
1506
0
    ArrowArray *bandsUnitArray = nullptr;
1507
0
    uint32_t nBandsUnitArrayMaxAlloc = 0;
1508
1509
0
    const int iProjCode = iArray++;
1510
0
    uint32_t nProjCodeArrayMaxAlloc = 0;
1511
0
    const int iProjWKT2 = iArray++;
1512
0
    uint32_t nProjWKT2ArrayMaxAlloc = 0;
1513
0
    const int iProjPROJJSON = iArray++;
1514
0
    uint32_t nProjPROJJSONArrayMaxAlloc = 0;
1515
0
    const int iProjBBOX = iArray++;
1516
0
    ArrowArray *projBBOXItems = nullptr;
1517
0
    const int iProjShape = iArray++;
1518
0
    ArrowArray *projShapeItems = nullptr;
1519
0
    const int iProjTransform = iArray++;
1520
0
    ArrowArray *projTransformItems = nullptr;
1521
1522
0
    const int iWkbArray = iArray++;
1523
1524
0
    std::unique_ptr<OGRArrowArrayHelper> arrayHelper;
1525
1526
0
    const auto InitTopArray =
1527
0
        [iIdArray, iStacExtensionsArray, iLinksArray, iAssetsArray, iBandsArray,
1528
0
         iProjCode, iProjWKT2, iProjPROJJSON, iProjBBOX, iProjShape,
1529
0
         iProjTransform, iWkbArray, nMaxBatchSize, &arrayHelper, &topArray,
1530
0
         &topArrays, &topSchema, &nStacExtensionSubArrayMaxAlloc,
1531
0
         &stacExtensionSubArray, &linksItemArray, &imageArray,
1532
0
         &nImageHrefArrayMaxAlloc, &imageHrefArray, &imageRoleArray,
1533
0
         &imageRoleItemArray, &nImageRoleItemArrayMaxAlloc, &imageTitleArray,
1534
0
         &imageTypeArray, &nImageTitleArrayMaxAlloc, &nImageTypeArrayMaxAlloc,
1535
0
         &nBandsItemCount, &nBandsItemAlloc, &bandsItemArray, &bandsNameArray,
1536
0
         &nBandsNameArrayMaxAlloc, &bandsCommonNameArray,
1537
0
         &nBandsCommonNameArrayMaxAlloc, &nBandsCenterWavelengthArrayMaxAlloc,
1538
0
         &bandsCenterWavelengthArray, &nBandsFWHMArrayMaxAlloc, &bandsFWHMArray,
1539
0
         &bandsNodataArray, &nBandsNodataArrayMaxAlloc, &bandsDataTypeArray,
1540
0
         &nBandsDataTypeArrayMaxAlloc, &bandsUnitArray,
1541
0
         &nBandsUnitArrayMaxAlloc, &nProjCodeArrayMaxAlloc,
1542
0
         &nProjWKT2ArrayMaxAlloc, &nProjPROJJSONArrayMaxAlloc, &projBBOXItems,
1543
0
         &projShapeItems, &projTransformItems]()
1544
0
    {
1545
0
        const auto AllocArray = []()
1546
0
        {
1547
0
            auto array =
1548
0
                static_cast<ArrowArray *>(CPLCalloc(1, sizeof(ArrowArray)));
1549
0
            array->release = ReleaseArray;
1550
0
            return array;
1551
0
        };
1552
1553
0
        const auto AllocNBuffers = [](ArrowArray &array, int n_buffers)
1554
0
        {
1555
0
            array.n_buffers = n_buffers;
1556
0
            array.buffers = static_cast<const void **>(
1557
0
                CPLCalloc(n_buffers, sizeof(const void *)));
1558
0
        };
1559
1560
0
        const auto AllocNArrays = [](ArrowArray &array, int n_children)
1561
0
        {
1562
0
            array.n_children = n_children;
1563
0
            array.children = static_cast<ArrowArray **>(
1564
0
                CPLCalloc(n_children, sizeof(ArrowArray *)));
1565
0
        };
1566
1567
0
        const auto InitializePrimitiveArray =
1568
0
            [&AllocNBuffers](ArrowArray &sArray, size_t nEltSize,
1569
0
                             size_t nLength)
1570
0
        {
1571
0
            AllocNBuffers(sArray, 2);
1572
0
            sArray.buffers[ARROW_BUF_DATA] =
1573
0
                static_cast<const void *>(CPLCalloc(nLength, nEltSize));
1574
0
        };
1575
1576
0
        const auto InitializeStringOrBinaryArray =
1577
0
            [&AllocNBuffers](ArrowArray &sArray, size_t nLength)
1578
0
        {
1579
0
            AllocNBuffers(sArray, 3);
1580
            // +1 since the length of string of idx i is given by
1581
            // offset[i+1] - offset[i]
1582
0
            sArray.buffers[ARROW_BUF_DATA] = static_cast<const void *>(
1583
0
                CPLCalloc(nLength + 1, sizeof(uint32_t)));
1584
            // Allocate a minimum amount to not get a null pointer
1585
0
            sArray.buffers[ARROW_BUF_BYTES] =
1586
0
                static_cast<const void *>(CPLCalloc(1, 1));
1587
0
        };
1588
1589
0
        const auto InitializeListArray =
1590
0
            [&AllocNBuffers, &AllocNArrays](
1591
0
                ArrowArray &sArray, ArrowArray *subArray, size_t nLength)
1592
0
        {
1593
0
            AllocNBuffers(sArray, 2);
1594
0
            sArray.buffers[ARROW_BUF_DATA] = static_cast<const void *>(
1595
0
                CPLCalloc(nLength + 1, sizeof(uint32_t)));
1596
0
            AllocNArrays(sArray, 1);
1597
0
            sArray.children[0] = subArray;
1598
0
        };
1599
1600
0
        const auto InitializeFixedSizeListArray =
1601
0
            [&AllocNBuffers,
1602
0
             &AllocNArrays](ArrowArray &sArray, ArrowArray *subArray,
1603
0
                            size_t nItemSize, size_t nItemCount, size_t nLength)
1604
0
        {
1605
0
            AllocNArrays(sArray, 1);
1606
0
            AllocNBuffers(sArray, 1);
1607
0
            sArray.children[0] = subArray;
1608
0
            AllocNBuffers(*subArray, 2);
1609
0
            subArray->buffers[ARROW_BUF_DATA] = static_cast<const void *>(
1610
0
                CPLCalloc(nItemCount * nItemSize, nLength));
1611
0
        };
1612
1613
0
        const auto InitializeStructArray = [&AllocNBuffers](ArrowArray &sArray)
1614
0
        { AllocNBuffers(sArray, 1); };
1615
1616
0
        topArrays = static_cast<ArrowArray **>(CPLCalloc(
1617
0
            static_cast<int>(topSchema.n_children), sizeof(ArrowArray *)));
1618
0
        for (int i = 0; i < static_cast<int>(topSchema.n_children); ++i)
1619
0
            topArrays[i] = AllocArray();
1620
1621
0
        topArray = ArrowArray{};
1622
0
        topArray.release = ReleaseArray;
1623
0
        topArray.n_children = topSchema.n_children;
1624
0
        topArray.children = topArrays;
1625
0
        InitializeStructArray(topArray);
1626
1627
0
        InitializeStringOrBinaryArray(*topArrays[iIdArray], nMaxBatchSize);
1628
1629
0
        stacExtensionSubArray = AllocArray();
1630
0
        nStacExtensionSubArrayMaxAlloc = 0;
1631
0
        {
1632
0
            auto *array = topArrays[iStacExtensionsArray];
1633
0
            InitializeListArray(*array, stacExtensionSubArray, nMaxBatchSize);
1634
0
            InitializeStringOrBinaryArray(
1635
0
                *stacExtensionSubArray, COUNT_STAC_EXTENSIONS * nMaxBatchSize);
1636
0
        }
1637
1638
0
        linksItemArray = AllocArray();
1639
0
        {
1640
0
            auto *array = topArrays[iLinksArray];
1641
0
            InitializeListArray(*array, linksItemArray, nMaxBatchSize);
1642
0
            InitializeStructArray(*linksItemArray);
1643
1644
0
            AllocNArrays(*linksItemArray, 4);
1645
0
            ArrowArray *linksHrefArray = AllocArray();
1646
0
            ArrowArray *linksRelArray = AllocArray();
1647
0
            ArrowArray *linksTypeArray = AllocArray();
1648
0
            ArrowArray *linksTitleArray = AllocArray();
1649
0
            linksItemArray->children[0] = linksHrefArray;
1650
0
            linksItemArray->children[1] = linksRelArray;
1651
0
            linksItemArray->children[2] = linksTypeArray;
1652
0
            linksItemArray->children[3] = linksTitleArray;
1653
0
            InitializeStringOrBinaryArray(*linksHrefArray, nMaxBatchSize);
1654
0
            InitializeStringOrBinaryArray(*linksRelArray, nMaxBatchSize);
1655
0
            InitializeStringOrBinaryArray(*linksTypeArray, nMaxBatchSize);
1656
0
            InitializeStringOrBinaryArray(*linksTitleArray, nMaxBatchSize);
1657
0
        }
1658
1659
0
        imageArray = AllocArray();
1660
0
        nImageHrefArrayMaxAlloc = 0;
1661
0
        imageHrefArray = AllocArray();
1662
0
        imageRoleArray = AllocArray();
1663
0
        imageRoleItemArray = AllocArray();
1664
0
        nImageHrefArrayMaxAlloc = 0;
1665
0
        nImageRoleItemArrayMaxAlloc = 0;
1666
0
        imageTitleArray = AllocArray();
1667
0
        nImageTitleArrayMaxAlloc = 0;
1668
0
        imageTypeArray = AllocArray();
1669
0
        nImageTypeArrayMaxAlloc = 0;
1670
0
        {
1671
0
            auto *assets = topArrays[iAssetsArray];
1672
0
            InitializeStructArray(*assets);
1673
0
            AllocNArrays(*assets, 1);
1674
0
            assets->children[0] = imageArray;
1675
1676
0
            InitializeStructArray(*imageArray);
1677
0
            AllocNArrays(*imageArray, 4);
1678
0
            imageArray->children[0] = imageHrefArray;
1679
0
            imageArray->children[1] = imageRoleArray;
1680
0
            imageArray->children[2] = imageTitleArray;
1681
0
            imageArray->children[3] = imageTypeArray;
1682
1683
0
            InitializeStringOrBinaryArray(*imageHrefArray, nMaxBatchSize);
1684
0
            InitializeStringOrBinaryArray(*imageTitleArray, nMaxBatchSize);
1685
0
            InitializeStringOrBinaryArray(*imageTypeArray, nMaxBatchSize);
1686
0
            InitializeListArray(*imageRoleArray, imageRoleItemArray,
1687
0
                                nMaxBatchSize);
1688
0
            InitializeStringOrBinaryArray(*imageRoleItemArray, nMaxBatchSize);
1689
0
        }
1690
1691
        // "bands" related initialization
1692
0
        {
1693
0
            nBandsItemCount = 0;
1694
0
            nBandsItemAlloc = 0;
1695
0
            bandsItemArray = AllocArray();
1696
0
            InitializeListArray(*(topArrays[iBandsArray]), bandsItemArray,
1697
0
                                nMaxBatchSize);
1698
0
            InitializeStructArray(*bandsItemArray);
1699
1700
0
            bandsNameArray = AllocArray();
1701
0
            InitializeStringOrBinaryArray(*bandsNameArray, 0);
1702
0
            nBandsNameArrayMaxAlloc = 0;
1703
1704
0
            bandsCommonNameArray = AllocArray();
1705
0
            InitializeStringOrBinaryArray(*bandsCommonNameArray, 0);
1706
0
            nBandsCommonNameArrayMaxAlloc = 0;
1707
1708
0
            bandsCenterWavelengthArray = AllocArray();
1709
0
            InitializePrimitiveArray(*bandsCenterWavelengthArray, sizeof(float),
1710
0
                                     1);
1711
0
            nBandsCenterWavelengthArrayMaxAlloc = 0;
1712
1713
0
            bandsFWHMArray = AllocArray();
1714
0
            InitializePrimitiveArray(*bandsFWHMArray, sizeof(float), 1);
1715
0
            nBandsFWHMArrayMaxAlloc = 0;
1716
1717
0
            bandsNodataArray = AllocArray();
1718
0
            InitializeStringOrBinaryArray(*bandsNodataArray, 0);
1719
0
            nBandsNodataArrayMaxAlloc = 0;
1720
1721
0
            bandsDataTypeArray = AllocArray();
1722
0
            InitializeStringOrBinaryArray(*bandsDataTypeArray, 0);
1723
0
            nBandsDataTypeArrayMaxAlloc = 0;
1724
1725
0
            bandsUnitArray = AllocArray();
1726
0
            InitializeStringOrBinaryArray(*bandsUnitArray, 0);
1727
0
            nBandsUnitArrayMaxAlloc = 0;
1728
1729
0
            AllocNArrays(*bandsItemArray, 7);
1730
0
            bandsItemArray->children[0] = bandsNameArray;
1731
0
            bandsItemArray->children[1] = bandsCommonNameArray;
1732
0
            bandsItemArray->children[2] = bandsCenterWavelengthArray;
1733
0
            bandsItemArray->children[3] = bandsFWHMArray;
1734
0
            bandsItemArray->children[4] = bandsNodataArray;
1735
0
            bandsItemArray->children[5] = bandsDataTypeArray;
1736
0
            bandsItemArray->children[6] = bandsUnitArray;
1737
0
        }
1738
1739
        // proj:xxxx related initializations
1740
0
        {
1741
0
            InitializeStringOrBinaryArray(*topArrays[iProjCode], nMaxBatchSize);
1742
0
            nProjCodeArrayMaxAlloc = 0;
1743
0
            InitializeStringOrBinaryArray(*topArrays[iProjWKT2], nMaxBatchSize);
1744
0
            nProjWKT2ArrayMaxAlloc = 0;
1745
0
            InitializeStringOrBinaryArray(*topArrays[iProjPROJJSON],
1746
0
                                          nMaxBatchSize);
1747
0
            nProjPROJJSONArrayMaxAlloc = 0;
1748
1749
0
            projBBOXItems = AllocArray();
1750
0
            InitializeFixedSizeListArray(*(topArrays[iProjBBOX]), projBBOXItems,
1751
0
                                         sizeof(double), NUM_ITEMS_PROJ_BBOX,
1752
0
                                         nMaxBatchSize);
1753
1754
0
            projShapeItems = AllocArray();
1755
0
            InitializeFixedSizeListArray(*(topArrays[iProjShape]),
1756
0
                                         projShapeItems, sizeof(int32_t),
1757
0
                                         NUM_ITEMS_PROJ_SHAPE, nMaxBatchSize);
1758
1759
0
            projTransformItems = AllocArray();
1760
0
            InitializeFixedSizeListArray(
1761
0
                *(topArrays[iProjTransform]), projTransformItems,
1762
0
                sizeof(double), NUM_ITEMS_PROJ_TRANSFORM, nMaxBatchSize);
1763
0
        }
1764
1765
0
        InitializeStringOrBinaryArray(*topArrays[iWkbArray], nMaxBatchSize);
1766
1767
0
        arrayHelper =
1768
0
            std::make_unique<OGRArrowArrayHelper>(&topArray, nMaxBatchSize);
1769
0
    };
1770
1771
0
    int nBatchSize = 0;
1772
1773
0
    const auto FlushArrays = [poLayer, &topArray, &linksItemArray, &imageArray,
1774
0
                              &topSchema, &nBatchSize, &arrayHelper]()
1775
0
    {
1776
0
        topArray.length = nBatchSize;
1777
0
        linksItemArray->length = nBatchSize;
1778
0
        imageArray->length = nBatchSize;
1779
0
        for (int i = 0; i < static_cast<int>(topArray.n_children); ++i)
1780
0
            topArray.children[i]->length = nBatchSize;
1781
0
        const bool ret = poLayer->WriteArrowBatch(&topSchema, &topArray);
1782
0
        if (topArray.release)
1783
0
        {
1784
0
            topArray.release(&topArray);
1785
0
        }
1786
0
        memset(&topArray, 0, sizeof(topArray));
1787
0
        nBatchSize = 0;
1788
0
        arrayHelper.reset();
1789
0
        return ret;
1790
0
    };
1791
1792
0
    int iCur = 0;
1793
0
    int nTotal = nSrcCount + 1;
1794
0
    while (true)
1795
0
    {
1796
0
        const std::string osSrcFilename = oGDALTileIndexTileIterator.next();
1797
0
        if (osSrcFilename.empty())
1798
0
            break;
1799
0
        if (bSkipFirstTile)
1800
0
        {
1801
0
            bSkipFirstTile = false;
1802
0
            continue;
1803
0
        }
1804
1805
0
        std::string osFileNameToWrite;
1806
0
        VSIStatBuf sStatBuf;
1807
1808
        // Make sure it is a file before building absolute path name.
1809
0
        if (!osCurrentPath.empty() &&
1810
0
            CPLIsFilenameRelative(osSrcFilename.c_str()) &&
1811
0
            VSIStat(osSrcFilename.c_str(), &sStatBuf) == 0)
1812
0
        {
1813
0
            osFileNameToWrite = CPLProjectRelativeFilenameSafe(
1814
0
                osCurrentPath.c_str(), osSrcFilename.c_str());
1815
0
        }
1816
0
        else
1817
0
        {
1818
0
            osFileNameToWrite = osSrcFilename.c_str();
1819
0
        }
1820
1821
        // Checks that file is not already in tileindex.
1822
0
        if (oSetExistingFiles.find(osFileNameToWrite) !=
1823
0
            oSetExistingFiles.end())
1824
0
        {
1825
0
            CPLError(CE_Warning, CPLE_AppDefined,
1826
0
                     "File %s is already in tileindex. Skipping it.",
1827
0
                     osFileNameToWrite.c_str());
1828
0
            continue;
1829
0
        }
1830
1831
0
        std::unique_ptr<GDALDataset> poSrcDS;
1832
0
        {
1833
0
            std::unique_ptr<CPLTurnFailureIntoWarningBackuper>
1834
0
                poFailureIntoWarning;
1835
0
            if (!bFailOnErrors)
1836
0
                poFailureIntoWarning =
1837
0
                    std::make_unique<CPLTurnFailureIntoWarningBackuper>();
1838
0
            CPL_IGNORE_RET_VAL(poFailureIntoWarning);
1839
1840
0
            poSrcDS.reset(GDALDataset::Open(
1841
0
                osSrcFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
1842
0
                nullptr, nullptr, nullptr));
1843
0
            if (poSrcDS == nullptr)
1844
0
            {
1845
0
                CPLError(bFailOnErrors ? CE_Failure : CE_Warning,
1846
0
                         CPLE_AppDefined, "Unable to open %s%s.",
1847
0
                         osSrcFilename.c_str(),
1848
0
                         bFailOnErrors ? "" : ", skipping");
1849
0
                if (bFailOnErrors)
1850
0
                    return nullptr;
1851
0
                continue;
1852
0
            }
1853
0
        }
1854
1855
0
        GDALGeoTransform gt;
1856
0
        if (poSrcDS->GetGeoTransform(gt) != CE_None)
1857
0
        {
1858
0
            CPLError(bFailOnErrors ? CE_Failure : CE_Warning, CPLE_AppDefined,
1859
0
                     "It appears no georeferencing is available for\n"
1860
0
                     "`%s'%s.",
1861
0
                     osSrcFilename.c_str(), bFailOnErrors ? "" : ", skipping");
1862
0
            if (bFailOnErrors)
1863
0
                return nullptr;
1864
0
            continue;
1865
0
        }
1866
1867
0
        auto poSrcSRS = poSrcDS->GetSpatialRef();
1868
        // If not set target srs, test that the current file uses same
1869
        // projection as others.
1870
0
        if (oTargetSRS.IsEmpty())
1871
0
        {
1872
0
            if (!oAlreadyExistingSRS.IsEmpty())
1873
0
            {
1874
0
                if (poSrcSRS == nullptr ||
1875
0
                    !poSrcSRS->IsSame(&oAlreadyExistingSRS))
1876
0
                {
1877
0
                    CPLError(
1878
0
                        CE_Warning, CPLE_AppDefined,
1879
0
                        "%s is not using the same projection system "
1880
0
                        "as other files in the tileindex.\n"
1881
0
                        "This may cause problems when using it in MapServer "
1882
0
                        "for example.\n"
1883
0
                        "Use -t_srs option to set target projection system. %s",
1884
0
                        osSrcFilename.c_str(),
1885
0
                        psOptions->bSkipDifferentProjection
1886
0
                            ? "Skipping this file."
1887
0
                            : "");
1888
0
                    if (psOptions->bSkipDifferentProjection)
1889
0
                    {
1890
0
                        continue;
1891
0
                    }
1892
0
                }
1893
0
            }
1894
0
            else
1895
0
            {
1896
0
                if (poSrcSRS)
1897
0
                    oAlreadyExistingSRS = *poSrcSRS;
1898
0
            }
1899
0
        }
1900
1901
0
        const int nXSize = poSrcDS->GetRasterXSize();
1902
0
        const int nYSize = poSrcDS->GetRasterYSize();
1903
0
        if (nXSize == 0 || nYSize == 0)
1904
0
        {
1905
0
            CPLError(bFailOnErrors ? CE_Failure : CE_Warning, CPLE_AppDefined,
1906
0
                     "%s has 0 width or height%s", osSrcFilename.c_str(),
1907
0
                     bFailOnErrors ? "" : ", skipping");
1908
0
            if (bFailOnErrors)
1909
0
                return nullptr;
1910
0
            continue;
1911
0
        }
1912
1913
0
        double adfX[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
1914
0
        double adfY[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
1915
0
        adfX[0] = gt.xorig + 0 * gt.xscale + 0 * gt.xrot;
1916
0
        adfY[0] = gt.yorig + 0 * gt.yrot + 0 * gt.yscale;
1917
1918
0
        adfX[1] = gt.xorig + nXSize * gt.xscale + 0 * gt.xrot;
1919
0
        adfY[1] = gt.yorig + nXSize * gt.yrot + 0 * gt.yscale;
1920
1921
0
        adfX[2] = gt.xorig + nXSize * gt.xscale + nYSize * gt.xrot;
1922
0
        adfY[2] = gt.yorig + nXSize * gt.yrot + nYSize * gt.yscale;
1923
1924
0
        adfX[3] = gt.xorig + 0 * gt.xscale + nYSize * gt.xrot;
1925
0
        adfY[3] = gt.yorig + 0 * gt.yrot + nYSize * gt.yscale;
1926
1927
0
        adfX[4] = gt.xorig + 0 * gt.xscale + 0 * gt.xrot;
1928
0
        adfY[4] = gt.yorig + 0 * gt.yrot + 0 * gt.yscale;
1929
1930
0
        const double dfMinXBeforeReproj =
1931
0
            std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3]));
1932
0
        const double dfMinYBeforeReproj =
1933
0
            std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
1934
0
        const double dfMaxXBeforeReproj =
1935
0
            std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3]));
1936
0
        const double dfMaxYBeforeReproj =
1937
0
            std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
1938
1939
        // If set target srs, do the forward transformation of all points.
1940
0
        if (!oTargetSRS.IsEmpty() && poSrcSRS)
1941
0
        {
1942
0
            if (!poSrcSRS->IsSame(&oTargetSRS))
1943
0
            {
1944
0
                auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
1945
0
                    OGRCreateCoordinateTransformation(poSrcSRS, &oTargetSRS));
1946
0
                if (!poCT || !poCT->Transform(5, adfX, adfY, nullptr))
1947
0
                {
1948
0
                    CPLError(bFailOnErrors ? CE_Failure : CE_Warning,
1949
0
                             CPLE_AppDefined,
1950
0
                             "unable to transform points from source "
1951
0
                             "SRS `%s' to target SRS `%s' for file `%s'%s",
1952
0
                             poSrcDS->GetProjectionRef(),
1953
0
                             psOptions->osTargetSRS.c_str(),
1954
0
                             osFileNameToWrite.c_str(),
1955
0
                             bFailOnErrors ? "" : ", skipping");
1956
0
                    if (bFailOnErrors)
1957
0
                        return nullptr;
1958
0
                    continue;
1959
0
                }
1960
0
            }
1961
0
        }
1962
0
        else if (bIsGTIContext && !oAlreadyExistingSRS.IsEmpty() &&
1963
0
                 (poSrcSRS == nullptr ||
1964
0
                  !poSrcSRS->IsSame(&oAlreadyExistingSRS)))
1965
0
        {
1966
0
            CPLError(
1967
0
                CE_Failure, CPLE_AppDefined,
1968
0
                "%s is not using the same projection system "
1969
0
                "as other files in the tileindex. This is not compatible of "
1970
0
                "GTI use. Use -t_srs option to reproject tile extents "
1971
0
                "to a common SRS.",
1972
0
                osSrcFilename.c_str());
1973
0
            return nullptr;
1974
0
        }
1975
1976
0
        const double dfMinX =
1977
0
            std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3]));
1978
0
        const double dfMinY =
1979
0
            std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
1980
0
        const double dfMaxX =
1981
0
            std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3]));
1982
0
        const double dfMaxY =
1983
0
            std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
1984
0
        const double dfRes =
1985
0
            sqrt((dfMaxX - dfMinX) * (dfMaxY - dfMinY) / nXSize / nYSize);
1986
0
        if (!std::isnan(psOptions->dfMinPixelSize) &&
1987
0
            dfRes < psOptions->dfMinPixelSize)
1988
0
        {
1989
0
            CPLError(CE_Warning, CPLE_AppDefined,
1990
0
                     "%s has %f as pixel size (< %f). Skipping",
1991
0
                     osSrcFilename.c_str(), dfRes, psOptions->dfMinPixelSize);
1992
0
            continue;
1993
0
        }
1994
0
        if (!std::isnan(psOptions->dfMaxPixelSize) &&
1995
0
            dfRes > psOptions->dfMaxPixelSize)
1996
0
        {
1997
0
            CPLError(CE_Warning, CPLE_AppDefined,
1998
0
                     "%s has %f as pixel size (> %f). Skipping",
1999
0
                     osSrcFilename.c_str(), dfRes, psOptions->dfMaxPixelSize);
2000
0
            continue;
2001
0
        }
2002
2003
0
        auto poPoly = std::make_unique<OGRPolygon>();
2004
0
        auto poRing = std::make_unique<OGRLinearRing>();
2005
0
        for (int k = 0; k < 5; k++)
2006
0
            poRing->addPoint(adfX[k], adfY[k]);
2007
0
        poPoly->addRing(std::move(poRing));
2008
2009
0
        if (bIsSTACGeoParquet)
2010
0
        {
2011
0
            const char *pszDriverName = poSrcDS->GetDriverName();
2012
0
            if (pszDriverName && EQUAL(pszDriverName, "MEM"))
2013
0
            {
2014
0
                CPLError(CE_Failure, CPLE_AppDefined,
2015
0
                         "Memory datasets cannot be referenced in a "
2016
0
                         "STAC-GeoParquet catalog");
2017
0
                return nullptr;
2018
0
            }
2019
2020
0
            if (!arrayHelper)
2021
0
            {
2022
0
                InitTopArray();
2023
0
            }
2024
2025
            // Write "id"
2026
0
            {
2027
0
                std::string osId(CPLGetFilename(osFileNameToWrite.c_str()));
2028
2029
0
                if (psOptions->osIdMethod == "md5")
2030
0
                {
2031
0
                    const std::string osFilename =
2032
0
                        VSIFileManager::GetHandler(osFileNameToWrite.c_str())
2033
0
                            ->GetStreamingFilename(osFileNameToWrite);
2034
0
                    auto fp = VSIFilesystemHandler::OpenStatic(
2035
0
                        osFilename.c_str(), "rb");
2036
0
                    if (!fp)
2037
0
                    {
2038
0
                        CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
2039
0
                                 osFileNameToWrite.c_str());
2040
0
                        return nullptr;
2041
0
                    }
2042
0
                    CPLMD5Context md5Context;
2043
0
                    CPLMD5Init(&md5Context);
2044
0
                    constexpr size_t CHUNK_SIZE = 1024 * 1024;
2045
0
                    std::vector<GByte> buffer(CHUNK_SIZE, 0);
2046
0
                    while (true)
2047
0
                    {
2048
0
                        const size_t nRead =
2049
0
                            fp->Read(buffer.data(), 1, buffer.size());
2050
0
                        CPLMD5Update(&md5Context, buffer.data(), nRead);
2051
0
                        if (nRead < buffer.size())
2052
0
                        {
2053
0
                            if (fp->Error())
2054
0
                            {
2055
0
                                CPLError(CE_Failure, CPLE_FileIO,
2056
0
                                         "Error while reading %s",
2057
0
                                         osFileNameToWrite.c_str());
2058
0
                                return nullptr;
2059
0
                            }
2060
0
                            break;
2061
0
                        }
2062
0
                    }
2063
0
                    unsigned char digest[16] = {0};
2064
0
                    CPLMD5Final(digest, &md5Context);
2065
0
                    char *pszMD5 = CPLBinaryToHex(16, digest);
2066
0
                    osId = pszMD5;
2067
0
                    CPLFree(pszMD5);
2068
0
                    osId += '-';
2069
0
                    osId += CPLGetFilename(osFileNameToWrite.c_str());
2070
0
                }
2071
0
                else if (psOptions->osIdMethod == "metadata-item")
2072
0
                {
2073
0
                    const char *pszId = poSrcDS->GetMetadataItem(
2074
0
                        psOptions->osIdMetadataItem.c_str());
2075
0
                    if (!pszId)
2076
0
                    {
2077
0
                        CPLError(CE_Failure, CPLE_AppDefined,
2078
0
                                 "No metadata item '%s' in dataset %s",
2079
0
                                 psOptions->osIdMetadataItem.c_str(),
2080
0
                                 osFileNameToWrite.c_str());
2081
0
                        return nullptr;
2082
0
                    }
2083
0
                    osId = pszId;
2084
0
                }
2085
0
                else if (psOptions->osIdMethod != "filename")
2086
0
                {
2087
                    // shouldn't happen
2088
0
                    CPLError(CE_Failure, CPLE_NotSupported,
2089
0
                             "Unhandled id method '%s'",
2090
0
                             psOptions->osIdMethod.c_str());
2091
0
                    return nullptr;
2092
0
                }
2093
2094
0
                void *ptr = arrayHelper->GetPtrForStringOrBinary(
2095
0
                    iIdArray, nBatchSize, osId.size(), false);
2096
0
                if (!ptr)
2097
0
                    return nullptr;
2098
0
                memcpy(ptr, osId.data(), osId.size());
2099
0
            }
2100
2101
            // Write "stac_extensions"
2102
0
            {
2103
0
                uint32_t *panOffsets = static_cast<uint32_t *>(
2104
0
                    const_cast<void *>(topArrays[iStacExtensionsArray]
2105
0
                                           ->buffers[ARROW_BUF_DATA]));
2106
0
                panOffsets[nBatchSize + 1] =
2107
0
                    panOffsets[nBatchSize] + COUNT_STAC_EXTENSIONS;
2108
2109
0
                {
2110
0
                    constexpr const char extension[] =
2111
0
                        "https://stac-extensions.github.io/projection/v2.0.0/"
2112
0
                        "schema.json";
2113
0
                    constexpr size_t nStrLen = sizeof(extension) - 1;
2114
0
                    void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2115
0
                        stacExtensionSubArray,
2116
0
                        COUNT_STAC_EXTENSIONS * nBatchSize + 0, nStrLen,
2117
0
                        nStacExtensionSubArrayMaxAlloc, false);
2118
0
                    if (!ptr)
2119
0
                        return nullptr;
2120
0
                    memcpy(ptr, extension, nStrLen);
2121
0
                    stacExtensionSubArray->length++;
2122
0
                }
2123
2124
0
                {
2125
0
                    constexpr const char extension[] =
2126
0
                        "https://stac-extensions.github.io/eo/v2.0.0/"
2127
0
                        "schema.json";
2128
0
                    constexpr size_t nStrLen = sizeof(extension) - 1;
2129
0
                    void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2130
0
                        stacExtensionSubArray,
2131
0
                        COUNT_STAC_EXTENSIONS * nBatchSize + 1, nStrLen,
2132
0
                        nStacExtensionSubArrayMaxAlloc, false);
2133
0
                    if (!ptr)
2134
0
                        return nullptr;
2135
0
                    memcpy(ptr, extension, nStrLen);
2136
0
                    stacExtensionSubArray->length++;
2137
0
                }
2138
0
            }
2139
2140
            // Write "assets.image.href"
2141
0
            {
2142
0
                std::string osHref = osFileNameToWrite;
2143
0
                CPL_IGNORE_RET_VAL(osFileNameToWrite);
2144
0
                if (!psOptions->osBaseURL.empty())
2145
0
                {
2146
0
                    osHref = CPLFormFilenameSafe(psOptions->osBaseURL.c_str(),
2147
0
                                                 CPLGetFilename(osHref.c_str()),
2148
0
                                                 nullptr);
2149
0
                }
2150
0
                else if (VSIIsLocal(osHref.c_str()))
2151
0
                {
2152
0
                    if (!CPLIsFilenameRelative(osHref.c_str()))
2153
0
                    {
2154
0
                        osHref = "file://" + osHref;
2155
0
                    }
2156
0
                }
2157
0
                else if (STARTS_WITH(osHref.c_str(), "/vsicurl/"))
2158
0
                {
2159
0
                    osHref = osHref.substr(strlen("/vsicurl/"));
2160
0
                }
2161
0
                else if (STARTS_WITH(osHref.c_str(), "/vsis3/"))
2162
0
                {
2163
0
                    osHref = "s3://" + osHref.substr(strlen("/vsis3/"));
2164
0
                }
2165
0
                else if (STARTS_WITH(osHref.c_str(), "/vsigs/"))
2166
0
                {
2167
0
                    osHref = "gcs://" + osHref.substr(strlen("/vsigs/"));
2168
0
                }
2169
0
                else if (STARTS_WITH(osHref.c_str(), "/vsiaz/"))
2170
0
                {
2171
0
                    osHref = "azure://" + osHref.substr(strlen("/vsiaz/"));
2172
0
                }
2173
0
                const size_t nHrefLen = osHref.size();
2174
0
                void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2175
0
                    imageHrefArray, nBatchSize, nHrefLen,
2176
0
                    nImageHrefArrayMaxAlloc, false);
2177
0
                if (!ptr)
2178
0
                    return nullptr;
2179
0
                memcpy(ptr, osHref.data(), nHrefLen);
2180
0
                imageHrefArray->length++;
2181
0
            }
2182
2183
            // Write "assets.image.roles"
2184
0
            {
2185
0
                uint32_t *panRolesOffsets =
2186
0
                    static_cast<uint32_t *>(const_cast<void *>(
2187
0
                        imageRoleArray->buffers[ARROW_BUF_DATA]));
2188
0
                panRolesOffsets[nBatchSize + 1] =
2189
0
                    panRolesOffsets[nBatchSize] + 1;
2190
2191
0
                constexpr const char ROLE_DATA[] = "data";
2192
0
                constexpr size_t nStrLen = sizeof(ROLE_DATA) - 1;
2193
0
                void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2194
0
                    imageRoleItemArray, nBatchSize, nStrLen,
2195
0
                    nImageRoleItemArrayMaxAlloc, false);
2196
0
                if (!ptr)
2197
0
                    return nullptr;
2198
0
                memcpy(ptr, ROLE_DATA, nStrLen);
2199
0
                imageRoleItemArray->length++;
2200
0
            }
2201
2202
            // Write "assets.image.type"
2203
0
            if (pszDriverName && EQUAL(pszDriverName, "GTiff"))
2204
0
            {
2205
0
                const char *pszLayout =
2206
0
                    poSrcDS->GetMetadataItem("LAYOUT", "IMAGE_STRUCTURE");
2207
0
                if (pszLayout && EQUAL(pszLayout, "COG"))
2208
0
                {
2209
0
                    constexpr const char TYPE[] =
2210
0
                        "image/tiff; application=geotiff; "
2211
0
                        "profile=cloud-optimized";
2212
0
                    constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
2213
0
                    void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2214
0
                        imageTypeArray, nBatchSize, TYPE_SIZE,
2215
0
                        nImageTypeArrayMaxAlloc, false);
2216
0
                    if (!ptr)
2217
0
                        return nullptr;
2218
0
                    memcpy(ptr, TYPE, TYPE_SIZE);
2219
0
                }
2220
0
                else
2221
0
                {
2222
0
                    constexpr const char TYPE[] =
2223
0
                        "image/tiff; application=geotiff";
2224
0
                    constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
2225
0
                    void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2226
0
                        imageTypeArray, nBatchSize, TYPE_SIZE,
2227
0
                        nImageTypeArrayMaxAlloc, false);
2228
0
                    if (!ptr)
2229
0
                        return nullptr;
2230
0
                    memcpy(ptr, TYPE, TYPE_SIZE);
2231
0
                }
2232
0
            }
2233
0
            else if (pszDriverName && EQUAL(pszDriverName, "PNG"))
2234
0
            {
2235
0
                constexpr const char TYPE[] = "image/png";
2236
0
                constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
2237
0
                void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2238
0
                    imageTypeArray, nBatchSize, TYPE_SIZE,
2239
0
                    nImageTypeArrayMaxAlloc, false);
2240
0
                if (!ptr)
2241
0
                    return nullptr;
2242
0
                memcpy(ptr, TYPE, TYPE_SIZE);
2243
0
            }
2244
0
            else if (pszDriverName && EQUAL(pszDriverName, "JPEG"))
2245
0
            {
2246
0
                constexpr const char TYPE[] = "image/jpeg";
2247
0
                constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
2248
0
                void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2249
0
                    imageTypeArray, nBatchSize, TYPE_SIZE,
2250
0
                    nImageTypeArrayMaxAlloc, false);
2251
0
                if (!ptr)
2252
0
                    return nullptr;
2253
0
                memcpy(ptr, TYPE, TYPE_SIZE);
2254
0
            }
2255
0
            else if (pszDriverName && (EQUAL(pszDriverName, "JP2KAK") ||
2256
0
                                       EQUAL(pszDriverName, "JP2OpenJPEG") ||
2257
0
                                       EQUAL(pszDriverName, "JP2ECW") ||
2258
0
                                       EQUAL(pszDriverName, "JP2MrSID")))
2259
0
            {
2260
0
                constexpr const char TYPE[] = "image/jp2";
2261
0
                constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
2262
0
                void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2263
0
                    imageTypeArray, nBatchSize, TYPE_SIZE,
2264
0
                    nImageTypeArrayMaxAlloc, false);
2265
0
                if (!ptr)
2266
0
                    return nullptr;
2267
0
                memcpy(ptr, TYPE, TYPE_SIZE);
2268
0
            }
2269
0
            else
2270
0
            {
2271
0
                OGRArrowArrayHelper::SetNull(imageTypeArray, nBatchSize,
2272
0
                                             nMaxBatchSize, false);
2273
0
                OGRArrowArrayHelper::SetEmptyStringOrBinary(imageTypeArray,
2274
0
                                                            nBatchSize);
2275
0
            }
2276
0
            imageTypeArray->length++;
2277
2278
            // Write "assets.image.title"
2279
0
            {
2280
0
                OGRArrowArrayHelper::SetNull(imageTitleArray, nBatchSize,
2281
0
                                             nMaxBatchSize, false);
2282
0
                OGRArrowArrayHelper::SetEmptyStringOrBinary(imageTitleArray,
2283
0
                                                            nBatchSize);
2284
0
                imageTitleArray->length++;
2285
0
            }
2286
2287
            // Write "bands"
2288
0
            {
2289
0
                const int nThisBands = poSrcDS->GetRasterCount();
2290
0
                if (nThisBands + nBandsItemCount > nBandsItemAlloc)
2291
0
                {
2292
0
                    const auto nOldAlloc = nBandsItemAlloc;
2293
0
                    if (nBandsItemAlloc >
2294
0
                        std::numeric_limits<uint32_t>::max() / 2)
2295
0
                    {
2296
0
                        CPLError(CE_Failure, CPLE_AppDefined, "Too many bands");
2297
0
                        return nullptr;
2298
0
                    }
2299
0
                    nBandsItemAlloc = std::max(2 * nBandsItemAlloc,
2300
0
                                               nThisBands + nBandsItemCount);
2301
2302
0
                    auto ReallocArray = [nOldAlloc, nBandsItemAlloc](
2303
0
                                            ArrowArray *array, size_t nItemSize)
2304
0
                    {
2305
0
                        if (array->buffers[ARROW_BUF_VALIDITY])
2306
0
                        {
2307
                            // Bitmap
2308
0
                            const uint32_t nNewSizeBytes =
2309
0
                                (nBandsItemAlloc + 7) / 8;
2310
0
                            char *newPtr =
2311
0
                                static_cast<char *>(VSI_REALLOC_VERBOSE(
2312
0
                                    const_cast<void *>(
2313
0
                                        array->buffers[ARROW_BUF_VALIDITY]),
2314
0
                                    nNewSizeBytes));
2315
0
                            if (!newPtr)
2316
0
                                return false;
2317
0
                            array->buffers[ARROW_BUF_VALIDITY] =
2318
0
                                static_cast<const void *>(
2319
0
                                    const_cast<const char *>(newPtr));
2320
0
                            const uint32_t nOldSizeBytes = (nOldAlloc + 7) / 8;
2321
0
                            if (nNewSizeBytes > nOldSizeBytes)
2322
0
                            {
2323
                                // Initialize new allocated bytes as valid
2324
                                // They are set invalid explicitly with SetNull()
2325
0
                                memset(newPtr + nOldSizeBytes, 0xFF,
2326
0
                                       nNewSizeBytes - nOldSizeBytes);
2327
0
                            }
2328
0
                        }
2329
0
                        char *newPtr = static_cast<char *>(VSI_REALLOC_VERBOSE(
2330
0
                            const_cast<void *>(array->buffers[ARROW_BUF_DATA]),
2331
0
                            (nBandsItemAlloc + 1) * nItemSize));
2332
0
                        if (!newPtr)
2333
0
                            return false;
2334
0
                        array->buffers[ARROW_BUF_DATA] =
2335
0
                            static_cast<const void *>(
2336
0
                                const_cast<const char *>(newPtr));
2337
0
                        memset(newPtr + (nOldAlloc + 1) * nItemSize, 0,
2338
0
                               (nBandsItemAlloc - nOldAlloc) * nItemSize);
2339
0
                        return true;
2340
0
                    };
2341
2342
0
                    if (!ReallocArray(bandsNameArray, sizeof(uint32_t)) ||
2343
0
                        !ReallocArray(bandsCommonNameArray, sizeof(uint32_t)) ||
2344
0
                        !ReallocArray(bandsCenterWavelengthArray,
2345
0
                                      sizeof(float)) ||
2346
0
                        !ReallocArray(bandsFWHMArray, sizeof(float)) ||
2347
0
                        !ReallocArray(bandsNodataArray, sizeof(uint32_t)) ||
2348
0
                        !ReallocArray(bandsDataTypeArray, sizeof(uint32_t)) ||
2349
0
                        !ReallocArray(bandsUnitArray, sizeof(uint32_t)))
2350
0
                    {
2351
0
                        return nullptr;
2352
0
                    }
2353
0
                }
2354
2355
0
                uint32_t *panBandsOffsets =
2356
0
                    static_cast<uint32_t *>(const_cast<void *>(
2357
0
                        topArrays[iBandsArray]->buffers[ARROW_BUF_DATA]));
2358
0
                panBandsOffsets[nBatchSize + 1] =
2359
0
                    panBandsOffsets[nBatchSize] + nThisBands;
2360
2361
0
                for (int i = 0; i < nThisBands; ++i, ++nBandsItemCount)
2362
0
                {
2363
0
                    bandsItemArray->length++;
2364
2365
0
                    const auto poBand = poSrcDS->GetRasterBand(i + 1);
2366
0
                    {
2367
0
                        std::string osBandName = poBand->GetDescription();
2368
0
                        if (osBandName.empty())
2369
0
                            osBandName = "Band " + std::to_string(i + 1);
2370
0
                        void *ptr =
2371
0
                            OGRArrowArrayHelper::GetPtrForStringOrBinary(
2372
0
                                bandsNameArray, nBandsItemCount,
2373
0
                                osBandName.size(), nBandsNameArrayMaxAlloc,
2374
0
                                false);
2375
0
                        if (!ptr)
2376
0
                            return nullptr;
2377
0
                        memcpy(ptr, osBandName.data(), osBandName.size());
2378
0
                        bandsNameArray->length++;
2379
0
                    }
2380
2381
0
                    const char *pszCommonName =
2382
0
                        GDALGetSTACCommonNameFromColorInterp(
2383
0
                            poBand->GetColorInterpretation());
2384
0
                    if (pszCommonName)
2385
0
                    {
2386
0
                        const size_t nLen = strlen(pszCommonName);
2387
0
                        void *ptr =
2388
0
                            OGRArrowArrayHelper::GetPtrForStringOrBinary(
2389
0
                                bandsCommonNameArray, nBandsItemCount, nLen,
2390
0
                                nBandsCommonNameArrayMaxAlloc, false);
2391
0
                        if (!ptr)
2392
0
                            return nullptr;
2393
0
                        memcpy(ptr, pszCommonName, nLen);
2394
0
                    }
2395
0
                    else
2396
0
                    {
2397
0
                        OGRArrowArrayHelper::SetNull(bandsCommonNameArray,
2398
0
                                                     nBandsItemCount,
2399
0
                                                     nBandsItemAlloc, false);
2400
0
                        OGRArrowArrayHelper::SetEmptyStringOrBinary(
2401
0
                            bandsCommonNameArray, nBandsItemCount);
2402
0
                    }
2403
0
                    bandsCommonNameArray->length++;
2404
2405
0
                    if (const char *pszCenterWavelength =
2406
0
                            poBand->GetMetadataItem("CENTRAL_WAVELENGTH_UM",
2407
0
                                                    "IMAGERY"))
2408
0
                    {
2409
0
                        float *values = static_cast<float *>(
2410
0
                            const_cast<void *>(bandsCenterWavelengthArray
2411
0
                                                   ->buffers[ARROW_BUF_DATA]));
2412
0
                        values[nBandsItemCount] =
2413
0
                            static_cast<float>(CPLAtof(pszCenterWavelength));
2414
0
                    }
2415
0
                    else
2416
0
                    {
2417
0
                        OGRArrowArrayHelper::SetNull(bandsCenterWavelengthArray,
2418
0
                                                     nBandsItemCount,
2419
0
                                                     nBandsItemAlloc, false);
2420
0
                    }
2421
0
                    bandsCenterWavelengthArray->length++;
2422
2423
0
                    if (const char *pszFWHM =
2424
0
                            poBand->GetMetadataItem("FWHM_UM", "IMAGERY"))
2425
0
                    {
2426
0
                        float *values = static_cast<float *>(const_cast<void *>(
2427
0
                            bandsFWHMArray->buffers[ARROW_BUF_DATA]));
2428
0
                        values[nBandsItemCount] =
2429
0
                            static_cast<float>(CPLAtof(pszFWHM));
2430
0
                    }
2431
0
                    else
2432
0
                    {
2433
0
                        OGRArrowArrayHelper::SetNull(bandsFWHMArray,
2434
0
                                                     nBandsItemCount,
2435
0
                                                     nBandsItemAlloc, false);
2436
0
                    }
2437
0
                    bandsFWHMArray->length++;
2438
2439
0
                    int bHasNoData = false;
2440
0
                    const double dfNoDataValue =
2441
0
                        poBand->GetNoDataValue(&bHasNoData);
2442
0
                    if (bHasNoData)
2443
0
                    {
2444
0
                        const std::string osNodata =
2445
0
                            std::isnan(dfNoDataValue) ? "nan"
2446
0
                            : std::isinf(dfNoDataValue)
2447
0
                                ? (dfNoDataValue > 0 ? "inf" : "-inf")
2448
0
                                : CPLSPrintf("%.17g", dfNoDataValue);
2449
0
                        void *ptr =
2450
0
                            OGRArrowArrayHelper::GetPtrForStringOrBinary(
2451
0
                                bandsNodataArray, nBandsItemCount,
2452
0
                                osNodata.size(), nBandsNodataArrayMaxAlloc,
2453
0
                                false);
2454
0
                        if (!ptr)
2455
0
                            return nullptr;
2456
0
                        memcpy(ptr, osNodata.data(), osNodata.size());
2457
0
                    }
2458
0
                    else
2459
0
                    {
2460
0
                        OGRArrowArrayHelper::SetNull(bandsNodataArray,
2461
0
                                                     nBandsItemCount,
2462
0
                                                     nBandsItemAlloc, false);
2463
0
                    }
2464
0
                    bandsNodataArray->length++;
2465
2466
0
                    {
2467
0
                        const char *pszDT = "other";
2468
                        // clang-format off
2469
0
                        switch (poBand->GetRasterDataType())
2470
0
                        {
2471
0
                            case GDT_Int8:     pszDT = "int8";     break;
2472
0
                            case GDT_UInt8:    pszDT = "uint8";    break;
2473
0
                            case GDT_Int16:    pszDT = "int16";    break;
2474
0
                            case GDT_UInt16:   pszDT = "uint16";   break;
2475
0
                            case GDT_Int32:    pszDT = "int32";    break;
2476
0
                            case GDT_UInt32:   pszDT = "uint32";   break;
2477
0
                            case GDT_Int64:    pszDT = "int64";    break;
2478
0
                            case GDT_UInt64:   pszDT = "uint64";   break;
2479
0
                            case GDT_Float16:  pszDT = "float16";  break;
2480
0
                            case GDT_Float32:  pszDT = "float32";  break;
2481
0
                            case GDT_Float64:  pszDT = "float64";  break;
2482
0
                            case GDT_CInt16:   pszDT = "cint16";   break;
2483
0
                            case GDT_CInt32:   pszDT = "cint32";   break;
2484
0
                            case GDT_CFloat16: pszDT = "cfloat16"; break;
2485
0
                            case GDT_CFloat32: pszDT = "cfloat32"; break;
2486
0
                            case GDT_CFloat64: pszDT = "cfloat64"; break;
2487
0
                            case GDT_Unknown:                      break;
2488
0
                            case GDT_TypeCount:                    break;
2489
0
                        }
2490
                        // clang-format on
2491
0
                        const size_t nLen = strlen(pszDT);
2492
0
                        void *ptr =
2493
0
                            OGRArrowArrayHelper::GetPtrForStringOrBinary(
2494
0
                                bandsDataTypeArray, nBandsItemCount, nLen,
2495
0
                                nBandsDataTypeArrayMaxAlloc, false);
2496
0
                        if (!ptr)
2497
0
                            return nullptr;
2498
0
                        memcpy(ptr, pszDT, nLen);
2499
2500
0
                        bandsDataTypeArray->length++;
2501
0
                    }
2502
2503
0
                    const char *pszUnits = poBand->GetUnitType();
2504
0
                    if (pszUnits && pszUnits[0])
2505
0
                    {
2506
0
                        const size_t nLen = strlen(pszUnits);
2507
0
                        void *ptr =
2508
0
                            OGRArrowArrayHelper::GetPtrForStringOrBinary(
2509
0
                                bandsUnitArray, nBandsItemCount, nLen,
2510
0
                                nBandsUnitArrayMaxAlloc, false);
2511
0
                        if (!ptr)
2512
0
                            return nullptr;
2513
0
                        memcpy(ptr, pszUnits, nLen);
2514
0
                    }
2515
0
                    else
2516
0
                    {
2517
0
                        OGRArrowArrayHelper::SetNull(bandsUnitArray,
2518
0
                                                     nBandsItemCount,
2519
0
                                                     nBandsItemAlloc, false);
2520
0
                    }
2521
0
                    bandsUnitArray->length++;
2522
0
                }
2523
0
            }
2524
2525
            // Write "proj:code"
2526
0
            bool bHasProjCode = false;
2527
0
            {
2528
0
                auto psArray = topArrays[iProjCode];
2529
0
                const char *pszSRSAuthName =
2530
0
                    poSrcSRS ? poSrcSRS->GetAuthorityName(nullptr) : nullptr;
2531
0
                const char *pszSRSAuthCode =
2532
0
                    poSrcSRS ? poSrcSRS->GetAuthorityCode(nullptr) : nullptr;
2533
0
                if (pszSRSAuthName && pszSRSAuthCode)
2534
0
                {
2535
0
                    std::string osCode(pszSRSAuthName);
2536
0
                    osCode += ':';
2537
0
                    osCode += pszSRSAuthCode;
2538
0
                    void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2539
0
                        psArray, nBatchSize, osCode.size(),
2540
0
                        nProjCodeArrayMaxAlloc, false);
2541
0
                    if (!ptr)
2542
0
                        return nullptr;
2543
0
                    memcpy(ptr, osCode.data(), osCode.size());
2544
0
                    bHasProjCode = true;
2545
0
                }
2546
0
                else
2547
0
                {
2548
0
                    OGRArrowArrayHelper::SetNull(psArray, nBatchSize,
2549
0
                                                 nMaxBatchSize, false);
2550
0
                    OGRArrowArrayHelper::SetEmptyStringOrBinary(psArray,
2551
0
                                                                nBatchSize);
2552
0
                }
2553
0
            }
2554
2555
            // Write "proj:wkt2"
2556
0
            {
2557
0
                auto psArray = topArrays[iProjWKT2];
2558
0
                std::string osWKT2;
2559
0
                if (poSrcSRS && !bHasProjCode)
2560
0
                {
2561
0
                    const char *const apszOptions[] = {"FORMAT=WKT2_2019",
2562
0
                                                       nullptr};
2563
0
                    osWKT2 = poSrcSRS->exportToWkt(apszOptions);
2564
0
                }
2565
0
                if (!osWKT2.empty())
2566
0
                {
2567
0
                    void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2568
0
                        psArray, nBatchSize, osWKT2.size(),
2569
0
                        nProjWKT2ArrayMaxAlloc, false);
2570
0
                    if (!ptr)
2571
0
                        return nullptr;
2572
0
                    memcpy(ptr, osWKT2.data(), osWKT2.size());
2573
0
                }
2574
0
                else
2575
0
                {
2576
0
                    OGRArrowArrayHelper::SetNull(psArray, nBatchSize,
2577
0
                                                 nMaxBatchSize, false);
2578
0
                    OGRArrowArrayHelper::SetEmptyStringOrBinary(psArray,
2579
0
                                                                nBatchSize);
2580
0
                }
2581
0
            }
2582
2583
            // Write "proj:projjson"
2584
0
            {
2585
0
                auto psArray = topArrays[iProjPROJJSON];
2586
0
                std::string osPROJJSON;
2587
0
                if (poSrcSRS && !bHasProjCode)
2588
0
                {
2589
0
                    char *pszPROJJSON = nullptr;
2590
0
                    poSrcSRS->exportToPROJJSON(&pszPROJJSON, nullptr);
2591
0
                    if (pszPROJJSON)
2592
0
                        osPROJJSON = pszPROJJSON;
2593
0
                    CPLFree(pszPROJJSON);
2594
0
                }
2595
0
                if (!osPROJJSON.empty())
2596
0
                {
2597
0
                    void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
2598
0
                        psArray, nBatchSize, osPROJJSON.size(),
2599
0
                        nProjPROJJSONArrayMaxAlloc, false);
2600
0
                    if (!ptr)
2601
0
                        return nullptr;
2602
0
                    memcpy(ptr, osPROJJSON.data(), osPROJJSON.size());
2603
0
                }
2604
0
                else
2605
0
                {
2606
0
                    OGRArrowArrayHelper::SetNull(psArray, nBatchSize,
2607
0
                                                 nMaxBatchSize, false);
2608
0
                    OGRArrowArrayHelper::SetEmptyStringOrBinary(psArray,
2609
0
                                                                nBatchSize);
2610
0
                }
2611
0
            }
2612
2613
            // Write proj:bbox
2614
0
            {
2615
0
                double *values = static_cast<double *>(
2616
0
                    const_cast<void *>(projBBOXItems->buffers[ARROW_BUF_DATA]));
2617
0
                auto ptr = values + nBatchSize * NUM_ITEMS_PROJ_BBOX;
2618
0
                ptr[0] = dfMinXBeforeReproj;
2619
0
                ptr[1] = dfMinYBeforeReproj;
2620
0
                ptr[2] = dfMaxXBeforeReproj;
2621
0
                ptr[3] = dfMaxYBeforeReproj;
2622
0
            }
2623
2624
            // Write proj:shape
2625
0
            {
2626
0
                int32_t *values = static_cast<int32_t *>(const_cast<void *>(
2627
0
                    projShapeItems->buffers[ARROW_BUF_DATA]));
2628
0
                auto ptr = values + nBatchSize * NUM_ITEMS_PROJ_SHAPE;
2629
0
                ptr[0] = poSrcDS->GetRasterYSize();
2630
0
                ptr[1] = poSrcDS->GetRasterXSize();
2631
0
            }
2632
2633
            // Write proj:transform
2634
0
            {
2635
0
                double *values = static_cast<double *>(const_cast<void *>(
2636
0
                    projTransformItems->buffers[ARROW_BUF_DATA]));
2637
0
                auto ptr = values + nBatchSize * NUM_ITEMS_PROJ_TRANSFORM;
2638
0
                ptr[0] = gt.xscale;
2639
0
                ptr[1] = gt.xrot;
2640
0
                ptr[2] = gt.xorig;
2641
0
                ptr[3] = gt.yrot;
2642
0
                ptr[4] = gt.yscale;
2643
0
                ptr[5] = gt.yorig;
2644
0
                ptr[6] = 0;
2645
0
                ptr[7] = 0;
2646
0
                ptr[8] = 1;
2647
0
            }
2648
2649
            // Write geometry
2650
0
            {
2651
0
                const size_t nWKBSize = poPoly->WkbSize();
2652
0
                void *ptr = arrayHelper->GetPtrForStringOrBinary(
2653
0
                    iWkbArray, nBatchSize, nWKBSize, false);
2654
0
                if (!ptr)
2655
0
                    return nullptr;
2656
0
                OGRwkbExportOptions sExportOptions;
2657
0
                sExportOptions.eWkbVariant = wkbVariantIso;
2658
0
                if (poPoly->exportToWkb(static_cast<unsigned char *>(ptr),
2659
0
                                        &sExportOptions) != OGRERR_NONE)
2660
0
                    return nullptr;
2661
0
            }
2662
2663
0
            nBatchSize++;
2664
0
            if (nBatchSize == nMaxBatchSize && !FlushArrays())
2665
0
            {
2666
0
                return nullptr;
2667
0
            }
2668
0
        }
2669
0
        else
2670
0
        {
2671
0
            auto poFeature = std::make_unique<OGRFeature>(poLayerDefn);
2672
0
            poFeature->SetField(ti_field, osFileNameToWrite.c_str());
2673
2674
0
            if (i_SrcSRSName >= 0 && poSrcSRS)
2675
0
            {
2676
0
                const char *pszAuthorityCode =
2677
0
                    poSrcSRS->GetAuthorityCode(nullptr);
2678
0
                const char *pszAuthorityName =
2679
0
                    poSrcSRS->GetAuthorityName(nullptr);
2680
0
                if (psOptions->eSrcSRSFormat == FORMAT_AUTO)
2681
0
                {
2682
0
                    if (pszAuthorityName != nullptr &&
2683
0
                        pszAuthorityCode != nullptr)
2684
0
                    {
2685
0
                        poFeature->SetField(
2686
0
                            i_SrcSRSName, CPLSPrintf("%s:%s", pszAuthorityName,
2687
0
                                                     pszAuthorityCode));
2688
0
                    }
2689
0
                    else if (nMaxFieldSize == 0 ||
2690
0
                             strlen(poSrcDS->GetProjectionRef()) <=
2691
0
                                 static_cast<size_t>(nMaxFieldSize))
2692
0
                    {
2693
0
                        poFeature->SetField(i_SrcSRSName,
2694
0
                                            poSrcDS->GetProjectionRef());
2695
0
                    }
2696
0
                    else
2697
0
                    {
2698
0
                        char *pszProj4 = nullptr;
2699
0
                        if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
2700
0
                        {
2701
0
                            poFeature->SetField(i_SrcSRSName, pszProj4);
2702
0
                        }
2703
0
                        else
2704
0
                        {
2705
0
                            poFeature->SetField(i_SrcSRSName,
2706
0
                                                poSrcDS->GetProjectionRef());
2707
0
                        }
2708
0
                        CPLFree(pszProj4);
2709
0
                    }
2710
0
                }
2711
0
                else if (psOptions->eSrcSRSFormat == FORMAT_WKT)
2712
0
                {
2713
0
                    if (nMaxFieldSize == 0 ||
2714
0
                        strlen(poSrcDS->GetProjectionRef()) <=
2715
0
                            static_cast<size_t>(nMaxFieldSize))
2716
0
                    {
2717
0
                        poFeature->SetField(i_SrcSRSName,
2718
0
                                            poSrcDS->GetProjectionRef());
2719
0
                    }
2720
0
                    else
2721
0
                    {
2722
0
                        CPLError(
2723
0
                            CE_Warning, CPLE_AppDefined,
2724
0
                            "Cannot write WKT for file %s as it is too long!",
2725
0
                            osFileNameToWrite.c_str());
2726
0
                    }
2727
0
                }
2728
0
                else if (psOptions->eSrcSRSFormat == FORMAT_PROJ)
2729
0
                {
2730
0
                    char *pszProj4 = nullptr;
2731
0
                    if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
2732
0
                    {
2733
0
                        poFeature->SetField(i_SrcSRSName, pszProj4);
2734
0
                    }
2735
0
                    CPLFree(pszProj4);
2736
0
                }
2737
0
                else if (psOptions->eSrcSRSFormat == FORMAT_EPSG)
2738
0
                {
2739
0
                    if (pszAuthorityName != nullptr &&
2740
0
                        pszAuthorityCode != nullptr)
2741
0
                        poFeature->SetField(
2742
0
                            i_SrcSRSName, CPLSPrintf("%s:%s", pszAuthorityName,
2743
0
                                                     pszAuthorityCode));
2744
0
                }
2745
0
            }
2746
2747
0
            for (const auto &oFetchMD : psOptions->aoFetchMD)
2748
0
            {
2749
0
                if (EQUAL(oFetchMD.osRasterItemName.c_str(), "{PIXEL_SIZE}"))
2750
0
                {
2751
0
                    poFeature->SetField(oFetchMD.osFieldName.c_str(), dfRes);
2752
0
                    continue;
2753
0
                }
2754
2755
0
                const char *pszMD =
2756
0
                    poSrcDS->GetMetadataItem(oFetchMD.osRasterItemName.c_str());
2757
0
                if (pszMD)
2758
0
                {
2759
0
                    if (EQUAL(oFetchMD.osRasterItemName.c_str(),
2760
0
                              "TIFFTAG_DATETIME"))
2761
0
                    {
2762
0
                        int nYear, nMonth, nDay, nHour, nMin, nSec;
2763
0
                        if (sscanf(pszMD, "%04d:%02d:%02d %02d:%02d:%02d",
2764
0
                                   &nYear, &nMonth, &nDay, &nHour, &nMin,
2765
0
                                   &nSec) == 6)
2766
0
                        {
2767
0
                            poFeature->SetField(
2768
0
                                oFetchMD.osFieldName.c_str(),
2769
0
                                CPLSPrintf("%04d/%02d/%02d %02d:%02d:%02d",
2770
0
                                           nYear, nMonth, nDay, nHour, nMin,
2771
0
                                           nSec));
2772
0
                            continue;
2773
0
                        }
2774
0
                    }
2775
0
                    poFeature->SetField(oFetchMD.osFieldName.c_str(), pszMD);
2776
0
                }
2777
0
            }
2778
2779
0
            poFeature->SetGeometryDirectly(poPoly.release());
2780
2781
0
            if (poLayer->CreateFeature(poFeature.get()) != OGRERR_NONE)
2782
0
            {
2783
0
                CPLError(CE_Failure, CPLE_AppDefined,
2784
0
                         "Failed to create feature in tile index.");
2785
0
                return nullptr;
2786
0
            }
2787
0
        }
2788
2789
0
        ++iCur;
2790
0
        if (psOptions->pfnProgress &&
2791
0
            !psOptions->pfnProgress(static_cast<double>(iCur) / nTotal, "",
2792
0
                                    psOptions->pProgressData))
2793
0
        {
2794
0
            return nullptr;
2795
0
        }
2796
0
        if (iCur >= nSrcCount)
2797
0
            ++nTotal;
2798
0
    }
2799
0
    if (psOptions->pfnProgress)
2800
0
        psOptions->pfnProgress(1.0, "", psOptions->pProgressData);
2801
2802
0
    if (bIsSTACGeoParquet && nBatchSize != 0 && !FlushArrays())
2803
0
    {
2804
0
        return nullptr;
2805
0
    }
2806
2807
0
    if (poTileIndexDSUnique)
2808
0
        return GDALDataset::ToHandle(poTileIndexDSUnique.release());
2809
0
    else
2810
0
        return GDALDataset::ToHandle(poTileIndexDS);
2811
0
}
2812
2813
/************************************************************************/
2814
/*                             SanitizeSRS                              */
2815
/************************************************************************/
2816
2817
static char *SanitizeSRS(const char *pszUserInput)
2818
2819
0
{
2820
0
    OGRSpatialReferenceH hSRS;
2821
0
    char *pszResult = nullptr;
2822
2823
0
    CPLErrorReset();
2824
2825
0
    hSRS = OSRNewSpatialReference(nullptr);
2826
0
    if (OSRSetFromUserInput(hSRS, pszUserInput) == OGRERR_NONE)
2827
0
        OSRExportToWkt(hSRS, &pszResult);
2828
0
    else
2829
0
    {
2830
0
        CPLError(CE_Failure, CPLE_AppDefined, "Translating SRS failed:\n%s",
2831
0
                 pszUserInput);
2832
0
    }
2833
2834
0
    OSRDestroySpatialReference(hSRS);
2835
2836
0
    return pszResult;
2837
0
}
2838
2839
/************************************************************************/
2840
/*                      GDALTileIndexOptionsNew()                       */
2841
/************************************************************************/
2842
2843
/**
2844
 * Allocates a GDALTileIndexOptions struct.
2845
 *
2846
 * @param papszArgv NULL terminated list of options (potentially including
2847
 * filename and open options too), or NULL. The accepted options are the ones of
2848
 * the <a href="/programs/gdaltindex.html">gdaltindex</a> utility.
2849
 * @param psOptionsForBinary (output) may be NULL (and should generally be
2850
 * NULL), otherwise (gdaltindex_bin.cpp use case) must be allocated with
2851
 * GDALTileIndexOptionsForBinaryNew() prior to this function. Will be filled
2852
 * with potentially present filename, open options,...
2853
 * @return pointer to the allocated GDALTileIndexOptions struct. Must be freed
2854
 * with GDALTileIndexOptionsFree().
2855
 *
2856
 * @since GDAL 3.9
2857
 */
2858
2859
GDALTileIndexOptions *
2860
GDALTileIndexOptionsNew(char **papszArgv,
2861
                        GDALTileIndexOptionsForBinary *psOptionsForBinary)
2862
0
{
2863
0
    auto psOptions = std::make_unique<GDALTileIndexOptions>();
2864
2865
    /* -------------------------------------------------------------------- */
2866
    /*      Parse arguments.                                                */
2867
    /* -------------------------------------------------------------------- */
2868
2869
0
    CPLStringList aosArgv;
2870
2871
0
    if (papszArgv)
2872
0
    {
2873
0
        const int nArgc = CSLCount(papszArgv);
2874
0
        for (int i = 0; i < nArgc; i++)
2875
0
        {
2876
0
            aosArgv.AddString(papszArgv[i]);
2877
0
        }
2878
0
    }
2879
2880
0
    try
2881
0
    {
2882
0
        auto argParser = GDALTileIndexAppOptionsGetParser(psOptions.get(),
2883
0
                                                          psOptionsForBinary);
2884
0
        argParser->parse_args_without_binary_name(aosArgv.List());
2885
2886
        // Check all no store_into args
2887
0
        if (auto oTr = argParser->present<std::vector<double>>("-tr"))
2888
0
        {
2889
0
            psOptions->xres = (*oTr)[0];
2890
0
            psOptions->yres = (*oTr)[1];
2891
0
        }
2892
2893
0
        if (auto oTargetExtent = argParser->present<std::vector<double>>("-te"))
2894
0
        {
2895
0
            psOptions->xmin = (*oTargetExtent)[0];
2896
0
            psOptions->ymin = (*oTargetExtent)[1];
2897
0
            psOptions->xmax = (*oTargetExtent)[2];
2898
0
            psOptions->ymax = (*oTargetExtent)[3];
2899
0
        }
2900
2901
0
        if (auto fetchMd =
2902
0
                argParser->present<std::vector<std::string>>("-fetch_md"))
2903
0
        {
2904
2905
0
            CPLAssert(fetchMd->size() % 3 == 0);
2906
2907
            // Loop
2908
0
            for (size_t i = 0; i < fetchMd->size(); i += 3)
2909
0
            {
2910
0
                OGRFieldType type;
2911
0
                const auto &typeName{fetchMd->at(i + 2)};
2912
0
                if (typeName == "String")
2913
0
                {
2914
0
                    type = OFTString;
2915
0
                }
2916
0
                else if (typeName == "Integer")
2917
0
                {
2918
0
                    type = OFTInteger;
2919
0
                }
2920
0
                else if (typeName == "Integer64")
2921
0
                {
2922
0
                    type = OFTInteger64;
2923
0
                }
2924
0
                else if (typeName == "Real")
2925
0
                {
2926
0
                    type = OFTReal;
2927
0
                }
2928
0
                else if (typeName == "Date")
2929
0
                {
2930
0
                    type = OFTDate;
2931
0
                }
2932
0
                else if (typeName == "DateTime")
2933
0
                {
2934
0
                    type = OFTDateTime;
2935
0
                }
2936
0
                else
2937
0
                {
2938
0
                    CPLError(CE_Failure, CPLE_AppDefined,
2939
0
                             "-fetch_md requires a valid type name as third "
2940
0
                             "argument: %s was given.",
2941
0
                             fetchMd->at(i).c_str());
2942
0
                    return nullptr;
2943
0
                }
2944
2945
0
                const GDALTileIndexRasterMetadata oMD{type, fetchMd->at(i + 1),
2946
0
                                                      fetchMd->at(i)};
2947
0
                psOptions->aoFetchMD.push_back(std::move(oMD));
2948
0
            }
2949
0
        }
2950
2951
        // Check -t_srs
2952
0
        if (!psOptions->osTargetSRS.empty())
2953
0
        {
2954
0
            auto sanitized{SanitizeSRS(psOptions->osTargetSRS.c_str())};
2955
0
            if (sanitized)
2956
0
            {
2957
0
                psOptions->osTargetSRS = sanitized;
2958
0
                CPLFree(sanitized);
2959
0
            }
2960
0
            else
2961
0
            {
2962
                // Error was already reported by SanitizeSRS, just return nullptr
2963
0
                psOptions->osTargetSRS.clear();
2964
0
                return nullptr;
2965
0
            }
2966
0
        }
2967
0
    }
2968
0
    catch (const std::exception &error)
2969
0
    {
2970
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
2971
0
        return nullptr;
2972
0
    }
2973
2974
0
    return psOptions.release();
2975
0
}
2976
2977
/************************************************************************/
2978
/*                      GDALTileIndexOptionsFree()                      */
2979
/************************************************************************/
2980
2981
/**
2982
 * Frees the GDALTileIndexOptions struct.
2983
 *
2984
 * @param psOptions the options struct for GDALTileIndex().
2985
 *
2986
 * @since GDAL 3.9
2987
 */
2988
2989
void GDALTileIndexOptionsFree(GDALTileIndexOptions *psOptions)
2990
0
{
2991
0
    delete psOptions;
2992
0
}
2993
2994
/************************************************************************/
2995
/*                  GDALTileIndexOptionsSetProgress()                   */
2996
/************************************************************************/
2997
2998
/**
2999
 * Set a progress function.
3000
 *
3001
 * @param psOptions the options struct for GDALTileIndex().
3002
 * @param pfnProgress the progress callback.
3003
 * @param pProgressData the user data for the progress callback.
3004
 *
3005
 * @since GDAL 3.11
3006
 */
3007
3008
void GDALTileIndexOptionsSetProgress(GDALTileIndexOptions *psOptions,
3009
                                     GDALProgressFunc pfnProgress,
3010
                                     void *pProgressData)
3011
0
{
3012
0
    psOptions->pfnProgress = pfnProgress;
3013
0
    psOptions->pProgressData = pProgressData;
3014
0
}
3015
3016
#undef CHECK_HAS_ENOUGH_ADDITIONAL_ARGS