LCOV - code coverage report
Current view: top level - apps - gdaltindex_lib.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1340 1579 84.9 %
Date: 2026-03-07 23:28:44 Functions: 28 30 93.3 %

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

Generated by: LCOV version 1.14