LCOV - code coverage report
Current view: top level - apps - gdaltindex_lib.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1307 1546 84.5 %
Date: 2025-12-01 18:11:08 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        1053 :     std::string next()
     423             :     {
     424             :         while (true)
     425             :         {
     426        1053 :             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         905 :             auto psEntry = VSIGetNextDirEntry(psDir);
     462         905 :             if (!psEntry)
     463             :             {
     464           5 :                 VSICloseDir(psDir);
     465           5 :                 psDir = nullptr;
     466           5 :                 continue;
     467             :             }
     468             : 
     469         900 :             if (!psOptions->oSetFilenameFilters.empty())
     470             :             {
     471         890 :                 bool bMatchFound = false;
     472             :                 const std::string osFilenameOnly =
     473         890 :                     CPLGetFilename(psEntry->pszName);
     474        1773 :                 for (const auto &osFilter : psOptions->oSetFilenameFilters)
     475             :                 {
     476         890 :                     if (GDALPatternMatch(osFilenameOnly.c_str(),
     477             :                                          osFilter.c_str()))
     478             :                     {
     479           7 :                         bMatchFound = true;
     480           7 :                         break;
     481             :                     }
     482             :                 }
     483         890 :                 if (!bMatchFound)
     484         883 :                     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         888 :         }
     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[0] + 0 * gt[1] + 0 * gt[2];
    1916          79 :         adfY[0] = gt[3] + 0 * gt[4] + 0 * gt[5];
    1917             : 
    1918          79 :         adfX[1] = gt[0] + nXSize * gt[1] + 0 * gt[2];
    1919          79 :         adfY[1] = gt[3] + nXSize * gt[4] + 0 * gt[5];
    1920             : 
    1921          79 :         adfX[2] = gt[0] + nXSize * gt[1] + nYSize * gt[2];
    1922          79 :         adfY[2] = gt[3] + nXSize * gt[4] + nYSize * gt[5];
    1923             : 
    1924          79 :         adfX[3] = gt[0] + 0 * gt[1] + nYSize * gt[2];
    1925          79 :         adfY[3] = gt[3] + 0 * gt[4] + nYSize * gt[5];
    1926             : 
    1927          79 :         adfX[4] = gt[0] + 0 * gt[1] + 0 * gt[2];
    1928          79 :         adfY[4] = gt[3] + 0 * gt[4] + 0 * gt[5];
    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, do the forward transformation of all points.
    1940          79 :         if (!oTargetSRS.IsEmpty() && poSrcSRS)
    1941             :         {
    1942          19 :             if (!poSrcSRS->IsSame(&oTargetSRS))
    1943             :             {
    1944             :                 auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
    1945          13 :                     OGRCreateCoordinateTransformation(poSrcSRS, &oTargetSRS));
    1946          13 :                 if (!poCT || !poCT->Transform(5, adfX, adfY, nullptr))
    1947             :                 {
    1948           0 :                     CPLError(bFailOnErrors ? CE_Failure : CE_Warning,
    1949             :                              CPLE_AppDefined,
    1950             :                              "unable to transform points from source "
    1951             :                              "SRS `%s' to target SRS `%s' for file `%s'%s",
    1952             :                              poSrcDS->GetProjectionRef(),
    1953           0 :                              psOptions->osTargetSRS.c_str(),
    1954             :                              osFileNameToWrite.c_str(),
    1955             :                              bFailOnErrors ? "" : ", skipping");
    1956           0 :                     if (bFailOnErrors)
    1957           0 :                         return nullptr;
    1958           0 :                     continue;
    1959             :                 }
    1960             :             }
    1961             :         }
    1962          72 :         else if (bIsGTIContext && !oAlreadyExistingSRS.IsEmpty() &&
    1963          12 :                  (poSrcSRS == nullptr ||
    1964          12 :                   !poSrcSRS->IsSame(&oAlreadyExistingSRS)))
    1965             :         {
    1966           0 :             CPLError(
    1967             :                 CE_Failure, CPLE_AppDefined,
    1968             :                 "%s is not using the same projection system "
    1969             :                 "as other files in the tileindex. This is not compatible of "
    1970             :                 "GTI use. Use -t_srs option to reproject tile extents "
    1971             :                 "to a common SRS.",
    1972             :                 osSrcFilename.c_str());
    1973           0 :             return nullptr;
    1974             :         }
    1975             : 
    1976             :         const double dfMinX =
    1977          79 :             std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3]));
    1978             :         const double dfMinY =
    1979          79 :             std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
    1980             :         const double dfMaxX =
    1981          79 :             std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3]));
    1982             :         const double dfMaxY =
    1983          79 :             std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
    1984             :         const double dfRes =
    1985          79 :             sqrt((dfMaxX - dfMinX) * (dfMaxY - dfMinY) / nXSize / nYSize);
    1986          89 :         if (!std::isnan(psOptions->dfMinPixelSize) &&
    1987          10 :             dfRes < psOptions->dfMinPixelSize)
    1988             :         {
    1989           5 :             CPLError(CE_Warning, CPLE_AppDefined,
    1990             :                      "%s has %f as pixel size (< %f). Skipping",
    1991           5 :                      osSrcFilename.c_str(), dfRes, psOptions->dfMinPixelSize);
    1992           5 :             continue;
    1993             :         }
    1994          82 :         if (!std::isnan(psOptions->dfMaxPixelSize) &&
    1995           8 :             dfRes > psOptions->dfMaxPixelSize)
    1996             :         {
    1997           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    1998             :                      "%s has %f as pixel size (> %f). Skipping",
    1999           4 :                      osSrcFilename.c_str(), dfRes, psOptions->dfMaxPixelSize);
    2000           4 :             continue;
    2001             :         }
    2002             : 
    2003          70 :         auto poPoly = std::make_unique<OGRPolygon>();
    2004          70 :         auto poRing = std::make_unique<OGRLinearRing>();
    2005         420 :         for (int k = 0; k < 5; k++)
    2006         350 :             poRing->addPoint(adfX[k], adfY[k]);
    2007          70 :         poPoly->addRing(std::move(poRing));
    2008             : 
    2009          70 :         if (bIsSTACGeoParquet)
    2010             :         {
    2011           8 :             const char *pszDriverName = poSrcDS->GetDriverName();
    2012           8 :             if (pszDriverName && EQUAL(pszDriverName, "MEM"))
    2013             :             {
    2014           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2015             :                          "Memory datasets cannot be referenced in a "
    2016             :                          "STAC-GeoParquet catalog");
    2017           0 :                 return nullptr;
    2018             :             }
    2019             : 
    2020           8 :             if (!arrayHelper)
    2021             :             {
    2022           8 :                 InitTopArray();
    2023             :             }
    2024             : 
    2025             :             // Write "id"
    2026             :             {
    2027           8 :                 std::string osId(CPLGetFilename(osFileNameToWrite.c_str()));
    2028             : 
    2029           8 :                 if (psOptions->osIdMethod == "md5")
    2030             :                 {
    2031             :                     const std::string osFilename =
    2032           1 :                         VSIFileManager::GetHandler(osFileNameToWrite.c_str())
    2033           1 :                             ->GetStreamingFilename(osFileNameToWrite);
    2034             :                     auto fp = VSIFilesystemHandler::OpenStatic(
    2035           1 :                         osFilename.c_str(), "rb");
    2036           1 :                     if (!fp)
    2037             :                     {
    2038           0 :                         CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
    2039             :                                  osFileNameToWrite.c_str());
    2040           0 :                         return nullptr;
    2041             :                     }
    2042             :                     CPLMD5Context md5Context;
    2043           1 :                     CPLMD5Init(&md5Context);
    2044           1 :                     constexpr size_t CHUNK_SIZE = 1024 * 1024;
    2045           1 :                     std::vector<GByte> buffer(CHUNK_SIZE, 0);
    2046             :                     while (true)
    2047             :                     {
    2048             :                         const size_t nRead =
    2049           1 :                             fp->Read(buffer.data(), 1, buffer.size());
    2050           1 :                         CPLMD5Update(&md5Context, buffer.data(), nRead);
    2051           1 :                         if (nRead < buffer.size())
    2052             :                         {
    2053           1 :                             if (fp->Error())
    2054             :                             {
    2055           0 :                                 CPLError(CE_Failure, CPLE_FileIO,
    2056             :                                          "Error while reading %s",
    2057             :                                          osFileNameToWrite.c_str());
    2058           0 :                                 return nullptr;
    2059             :                             }
    2060           1 :                             break;
    2061             :                         }
    2062           0 :                     }
    2063           1 :                     unsigned char digest[16] = {0};
    2064           1 :                     CPLMD5Final(digest, &md5Context);
    2065           1 :                     char *pszMD5 = CPLBinaryToHex(16, digest);
    2066           1 :                     osId = pszMD5;
    2067           1 :                     CPLFree(pszMD5);
    2068           1 :                     osId += '-';
    2069           1 :                     osId += CPLGetFilename(osFileNameToWrite.c_str());
    2070             :                 }
    2071           7 :                 else if (psOptions->osIdMethod == "metadata-item")
    2072             :                 {
    2073           2 :                     const char *pszId = poSrcDS->GetMetadataItem(
    2074           1 :                         psOptions->osIdMetadataItem.c_str());
    2075           1 :                     if (!pszId)
    2076             :                     {
    2077           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
    2078             :                                  "No metadata item '%s' in dataset %s",
    2079           0 :                                  psOptions->osIdMetadataItem.c_str(),
    2080             :                                  osFileNameToWrite.c_str());
    2081           0 :                         return nullptr;
    2082             :                     }
    2083           1 :                     osId = pszId;
    2084             :                 }
    2085           6 :                 else if (psOptions->osIdMethod != "filename")
    2086             :                 {
    2087             :                     // shouldn't happen
    2088           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
    2089             :                              "Unhandled id method '%s'",
    2090           0 :                              psOptions->osIdMethod.c_str());
    2091           0 :                     return nullptr;
    2092             :                 }
    2093             : 
    2094           8 :                 void *ptr = arrayHelper->GetPtrForStringOrBinary(
    2095             :                     iIdArray, nBatchSize, osId.size(), false);
    2096           8 :                 if (!ptr)
    2097           0 :                     return nullptr;
    2098           8 :                 memcpy(ptr, osId.data(), osId.size());
    2099             :             }
    2100             : 
    2101             :             // Write "stac_extensions"
    2102             :             {
    2103           8 :                 uint32_t *panOffsets = static_cast<uint32_t *>(
    2104           8 :                     const_cast<void *>(topArrays[iStacExtensionsArray]
    2105           8 :                                            ->buffers[ARROW_BUF_DATA]));
    2106           8 :                 panOffsets[nBatchSize + 1] =
    2107           8 :                     panOffsets[nBatchSize] + COUNT_STAC_EXTENSIONS;
    2108             : 
    2109             :                 {
    2110           8 :                     constexpr const char extension[] =
    2111             :                         "https://stac-extensions.github.io/projection/v2.0.0/"
    2112             :                         "schema.json";
    2113           8 :                     constexpr size_t nStrLen = sizeof(extension) - 1;
    2114           8 :                     void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2115             :                         stacExtensionSubArray,
    2116             :                         COUNT_STAC_EXTENSIONS * nBatchSize + 0, nStrLen,
    2117             :                         nStacExtensionSubArrayMaxAlloc, false);
    2118           8 :                     if (!ptr)
    2119           0 :                         return nullptr;
    2120           8 :                     memcpy(ptr, extension, nStrLen);
    2121           8 :                     stacExtensionSubArray->length++;
    2122             :                 }
    2123             : 
    2124             :                 {
    2125           8 :                     constexpr const char extension[] =
    2126             :                         "https://stac-extensions.github.io/eo/v2.0.0/"
    2127             :                         "schema.json";
    2128           8 :                     constexpr size_t nStrLen = sizeof(extension) - 1;
    2129          16 :                     void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2130             :                         stacExtensionSubArray,
    2131           8 :                         COUNT_STAC_EXTENSIONS * nBatchSize + 1, nStrLen,
    2132             :                         nStacExtensionSubArrayMaxAlloc, false);
    2133           8 :                     if (!ptr)
    2134           0 :                         return nullptr;
    2135           8 :                     memcpy(ptr, extension, nStrLen);
    2136           8 :                     stacExtensionSubArray->length++;
    2137             :                 }
    2138             :             }
    2139             : 
    2140             :             // Write "assets.image.href"
    2141             :             {
    2142           8 :                 std::string osHref = osFileNameToWrite;
    2143           8 :                 CPL_IGNORE_RET_VAL(osFileNameToWrite);
    2144           8 :                 if (!psOptions->osBaseURL.empty())
    2145             :                 {
    2146           2 :                     osHref = CPLFormFilenameSafe(psOptions->osBaseURL.c_str(),
    2147             :                                                  CPLGetFilename(osHref.c_str()),
    2148           1 :                                                  nullptr);
    2149             :                 }
    2150           7 :                 else if (VSIIsLocal(osHref.c_str()))
    2151             :                 {
    2152           7 :                     if (!CPLIsFilenameRelative(osHref.c_str()))
    2153             :                     {
    2154           4 :                         osHref = "file://" + osHref;
    2155             :                     }
    2156             :                 }
    2157           0 :                 else if (STARTS_WITH(osHref.c_str(), "/vsicurl/"))
    2158             :                 {
    2159           0 :                     osHref = osHref.substr(strlen("/vsicurl/"));
    2160             :                 }
    2161           0 :                 else if (STARTS_WITH(osHref.c_str(), "/vsis3/"))
    2162             :                 {
    2163           0 :                     osHref = "s3://" + osHref.substr(strlen("/vsis3/"));
    2164             :                 }
    2165           0 :                 else if (STARTS_WITH(osHref.c_str(), "/vsigs/"))
    2166             :                 {
    2167           0 :                     osHref = "gcs://" + osHref.substr(strlen("/vsigs/"));
    2168             :                 }
    2169           0 :                 else if (STARTS_WITH(osHref.c_str(), "/vsiaz/"))
    2170             :                 {
    2171           0 :                     osHref = "azure://" + osHref.substr(strlen("/vsiaz/"));
    2172             :                 }
    2173           8 :                 const size_t nHrefLen = osHref.size();
    2174           8 :                 void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2175             :                     imageHrefArray, nBatchSize, nHrefLen,
    2176             :                     nImageHrefArrayMaxAlloc, false);
    2177           8 :                 if (!ptr)
    2178           0 :                     return nullptr;
    2179           8 :                 memcpy(ptr, osHref.data(), nHrefLen);
    2180           8 :                 imageHrefArray->length++;
    2181             :             }
    2182             : 
    2183             :             // Write "assets.image.roles"
    2184             :             {
    2185           8 :                 uint32_t *panRolesOffsets =
    2186             :                     static_cast<uint32_t *>(const_cast<void *>(
    2187           8 :                         imageRoleArray->buffers[ARROW_BUF_DATA]));
    2188           8 :                 panRolesOffsets[nBatchSize + 1] =
    2189           8 :                     panRolesOffsets[nBatchSize] + 1;
    2190             : 
    2191           8 :                 constexpr const char ROLE_DATA[] = "data";
    2192           8 :                 constexpr size_t nStrLen = sizeof(ROLE_DATA) - 1;
    2193           8 :                 void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2194             :                     imageRoleItemArray, nBatchSize, nStrLen,
    2195             :                     nImageRoleItemArrayMaxAlloc, false);
    2196           8 :                 if (!ptr)
    2197           0 :                     return nullptr;
    2198           8 :                 memcpy(ptr, ROLE_DATA, nStrLen);
    2199           8 :                 imageRoleItemArray->length++;
    2200             :             }
    2201             : 
    2202             :             // Write "assets.image.type"
    2203           8 :             if (pszDriverName && EQUAL(pszDriverName, "GTiff"))
    2204             :             {
    2205             :                 const char *pszLayout =
    2206           8 :                     poSrcDS->GetMetadataItem("LAYOUT", "IMAGE_STRUCTURE");
    2207           8 :                 if (pszLayout && EQUAL(pszLayout, "COG"))
    2208             :                 {
    2209           0 :                     constexpr const char TYPE[] =
    2210             :                         "image/tiff; application=geotiff; "
    2211             :                         "profile=cloud-optimized";
    2212           0 :                     constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
    2213           0 :                     void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2214             :                         imageTypeArray, nBatchSize, TYPE_SIZE,
    2215             :                         nImageTypeArrayMaxAlloc, false);
    2216           0 :                     if (!ptr)
    2217           0 :                         return nullptr;
    2218           0 :                     memcpy(ptr, TYPE, TYPE_SIZE);
    2219             :                 }
    2220             :                 else
    2221             :                 {
    2222           8 :                     constexpr const char TYPE[] =
    2223             :                         "image/tiff; application=geotiff";
    2224           8 :                     constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
    2225           8 :                     void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2226             :                         imageTypeArray, nBatchSize, TYPE_SIZE,
    2227             :                         nImageTypeArrayMaxAlloc, false);
    2228           8 :                     if (!ptr)
    2229           0 :                         return nullptr;
    2230           8 :                     memcpy(ptr, TYPE, TYPE_SIZE);
    2231           8 :                 }
    2232             :             }
    2233           0 :             else if (pszDriverName && EQUAL(pszDriverName, "PNG"))
    2234             :             {
    2235           0 :                 constexpr const char TYPE[] = "image/png";
    2236           0 :                 constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
    2237           0 :                 void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2238             :                     imageTypeArray, nBatchSize, TYPE_SIZE,
    2239             :                     nImageTypeArrayMaxAlloc, false);
    2240           0 :                 if (!ptr)
    2241           0 :                     return nullptr;
    2242           0 :                 memcpy(ptr, TYPE, TYPE_SIZE);
    2243             :             }
    2244           0 :             else if (pszDriverName && EQUAL(pszDriverName, "JPEG"))
    2245             :             {
    2246           0 :                 constexpr const char TYPE[] = "image/jpeg";
    2247           0 :                 constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
    2248           0 :                 void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2249             :                     imageTypeArray, nBatchSize, TYPE_SIZE,
    2250             :                     nImageTypeArrayMaxAlloc, false);
    2251           0 :                 if (!ptr)
    2252           0 :                     return nullptr;
    2253           0 :                 memcpy(ptr, TYPE, TYPE_SIZE);
    2254             :             }
    2255           0 :             else if (pszDriverName && (EQUAL(pszDriverName, "JP2KAK") ||
    2256           0 :                                        EQUAL(pszDriverName, "JP2OpenJPEG") ||
    2257           0 :                                        EQUAL(pszDriverName, "JP2ECW") ||
    2258           0 :                                        EQUAL(pszDriverName, "JP2MrSID")))
    2259             :             {
    2260           0 :                 constexpr const char TYPE[] = "image/jp2";
    2261           0 :                 constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
    2262           0 :                 void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2263             :                     imageTypeArray, nBatchSize, TYPE_SIZE,
    2264             :                     nImageTypeArrayMaxAlloc, false);
    2265           0 :                 if (!ptr)
    2266           0 :                     return nullptr;
    2267           0 :                 memcpy(ptr, TYPE, TYPE_SIZE);
    2268             :             }
    2269             :             else
    2270             :             {
    2271           0 :                 OGRArrowArrayHelper::SetNull(imageTypeArray, nBatchSize,
    2272             :                                              nMaxBatchSize, false);
    2273           0 :                 OGRArrowArrayHelper::SetEmptyStringOrBinary(imageTypeArray,
    2274             :                                                             nBatchSize);
    2275             :             }
    2276           8 :             imageTypeArray->length++;
    2277             : 
    2278             :             // Write "assets.image.title"
    2279             :             {
    2280           8 :                 OGRArrowArrayHelper::SetNull(imageTitleArray, nBatchSize,
    2281             :                                              nMaxBatchSize, false);
    2282           8 :                 OGRArrowArrayHelper::SetEmptyStringOrBinary(imageTitleArray,
    2283             :                                                             nBatchSize);
    2284           8 :                 imageTitleArray->length++;
    2285             :             }
    2286             : 
    2287             :             // Write "bands"
    2288             :             {
    2289           8 :                 const int nThisBands = poSrcDS->GetRasterCount();
    2290           8 :                 if (nThisBands + nBandsItemCount > nBandsItemAlloc)
    2291             :                 {
    2292           8 :                     const auto nOldAlloc = nBandsItemAlloc;
    2293           8 :                     if (nBandsItemAlloc >
    2294           8 :                         std::numeric_limits<uint32_t>::max() / 2)
    2295             :                     {
    2296           0 :                         CPLError(CE_Failure, CPLE_AppDefined, "Too many bands");
    2297           0 :                         return nullptr;
    2298             :                     }
    2299          16 :                     nBandsItemAlloc = std::max(2 * nBandsItemAlloc,
    2300           8 :                                                nThisBands + nBandsItemCount);
    2301             : 
    2302          56 :                     auto ReallocArray = [nOldAlloc, nBandsItemAlloc](
    2303         112 :                                             ArrowArray *array, size_t nItemSize)
    2304             :                     {
    2305          56 :                         if (array->buffers[ARROW_BUF_VALIDITY])
    2306             :                         {
    2307             :                             // Bitmap
    2308           0 :                             const uint32_t nNewSizeBytes =
    2309           0 :                                 (nBandsItemAlloc + 7) / 8;
    2310             :                             char *newPtr =
    2311           0 :                                 static_cast<char *>(VSI_REALLOC_VERBOSE(
    2312             :                                     const_cast<void *>(
    2313             :                                         array->buffers[ARROW_BUF_VALIDITY]),
    2314             :                                     nNewSizeBytes));
    2315           0 :                             if (!newPtr)
    2316           0 :                                 return false;
    2317           0 :                             array->buffers[ARROW_BUF_VALIDITY] =
    2318             :                                 static_cast<const void *>(
    2319             :                                     const_cast<const char *>(newPtr));
    2320           0 :                             const uint32_t nOldSizeBytes = (nOldAlloc + 7) / 8;
    2321           0 :                             if (nNewSizeBytes > nOldSizeBytes)
    2322             :                             {
    2323             :                                 // Initialize new allocated bytes as valid
    2324             :                                 // They are set invalid explicitly with SetNull()
    2325           0 :                                 memset(newPtr + nOldSizeBytes, 0xFF,
    2326           0 :                                        nNewSizeBytes - nOldSizeBytes);
    2327             :                             }
    2328             :                         }
    2329          56 :                         char *newPtr = static_cast<char *>(VSI_REALLOC_VERBOSE(
    2330             :                             const_cast<void *>(array->buffers[ARROW_BUF_DATA]),
    2331             :                             (nBandsItemAlloc + 1) * nItemSize));
    2332          56 :                         if (!newPtr)
    2333           0 :                             return false;
    2334          56 :                         array->buffers[ARROW_BUF_DATA] =
    2335             :                             static_cast<const void *>(
    2336             :                                 const_cast<const char *>(newPtr));
    2337          56 :                         memset(newPtr + (nOldAlloc + 1) * nItemSize, 0,
    2338          56 :                                (nBandsItemAlloc - nOldAlloc) * nItemSize);
    2339          56 :                         return true;
    2340           8 :                     };
    2341             : 
    2342           8 :                     if (!ReallocArray(bandsNameArray, sizeof(uint32_t)) ||
    2343           8 :                         !ReallocArray(bandsCommonNameArray, sizeof(uint32_t)) ||
    2344           8 :                         !ReallocArray(bandsCenterWavelengthArray,
    2345           8 :                                       sizeof(float)) ||
    2346           8 :                         !ReallocArray(bandsFWHMArray, sizeof(float)) ||
    2347           8 :                         !ReallocArray(bandsNodataArray, sizeof(uint32_t)) ||
    2348          24 :                         !ReallocArray(bandsDataTypeArray, sizeof(uint32_t)) ||
    2349           8 :                         !ReallocArray(bandsUnitArray, sizeof(uint32_t)))
    2350             :                     {
    2351           0 :                         return nullptr;
    2352             :                     }
    2353             :                 }
    2354             : 
    2355           8 :                 uint32_t *panBandsOffsets =
    2356             :                     static_cast<uint32_t *>(const_cast<void *>(
    2357           8 :                         topArrays[iBandsArray]->buffers[ARROW_BUF_DATA]));
    2358           8 :                 panBandsOffsets[nBatchSize + 1] =
    2359           8 :                     panBandsOffsets[nBatchSize] + nThisBands;
    2360             : 
    2361          18 :                 for (int i = 0; i < nThisBands; ++i, ++nBandsItemCount)
    2362             :                 {
    2363          10 :                     bandsItemArray->length++;
    2364             : 
    2365          10 :                     const auto poBand = poSrcDS->GetRasterBand(i + 1);
    2366             :                     {
    2367          10 :                         std::string osBandName = poBand->GetDescription();
    2368          10 :                         if (osBandName.empty())
    2369           9 :                             osBandName = "Band " + std::to_string(i + 1);
    2370             :                         void *ptr =
    2371          10 :                             OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2372             :                                 bandsNameArray, nBandsItemCount,
    2373             :                                 osBandName.size(), nBandsNameArrayMaxAlloc,
    2374             :                                 false);
    2375          10 :                         if (!ptr)
    2376           0 :                             return nullptr;
    2377          10 :                         memcpy(ptr, osBandName.data(), osBandName.size());
    2378          10 :                         bandsNameArray->length++;
    2379             :                     }
    2380             : 
    2381             :                     const char *pszCommonName =
    2382          10 :                         GDALGetSTACCommonNameFromColorInterp(
    2383          10 :                             poBand->GetColorInterpretation());
    2384          10 :                     if (pszCommonName)
    2385             :                     {
    2386           3 :                         const size_t nLen = strlen(pszCommonName);
    2387             :                         void *ptr =
    2388           3 :                             OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2389             :                                 bandsCommonNameArray, nBandsItemCount, nLen,
    2390             :                                 nBandsCommonNameArrayMaxAlloc, false);
    2391           3 :                         if (!ptr)
    2392           0 :                             return nullptr;
    2393           3 :                         memcpy(ptr, pszCommonName, nLen);
    2394             :                     }
    2395             :                     else
    2396             :                     {
    2397           7 :                         OGRArrowArrayHelper::SetNull(bandsCommonNameArray,
    2398             :                                                      nBandsItemCount,
    2399             :                                                      nBandsItemAlloc, false);
    2400           7 :                         OGRArrowArrayHelper::SetEmptyStringOrBinary(
    2401             :                             bandsCommonNameArray, nBandsItemCount);
    2402             :                     }
    2403          10 :                     bandsCommonNameArray->length++;
    2404             : 
    2405          10 :                     if (const char *pszCenterWavelength =
    2406          10 :                             poBand->GetMetadataItem("CENTRAL_WAVELENGTH_UM",
    2407          10 :                                                     "IMAGERY"))
    2408             :                     {
    2409           1 :                         float *values = static_cast<float *>(
    2410             :                             const_cast<void *>(bandsCenterWavelengthArray
    2411           1 :                                                    ->buffers[ARROW_BUF_DATA]));
    2412           1 :                         values[nBandsItemCount] =
    2413           1 :                             static_cast<float>(CPLAtof(pszCenterWavelength));
    2414             :                     }
    2415             :                     else
    2416             :                     {
    2417           9 :                         OGRArrowArrayHelper::SetNull(bandsCenterWavelengthArray,
    2418             :                                                      nBandsItemCount,
    2419             :                                                      nBandsItemAlloc, false);
    2420             :                     }
    2421          10 :                     bandsCenterWavelengthArray->length++;
    2422             : 
    2423          10 :                     if (const char *pszFWHM =
    2424          10 :                             poBand->GetMetadataItem("FWHM_UM", "IMAGERY"))
    2425             :                     {
    2426           1 :                         float *values = static_cast<float *>(const_cast<void *>(
    2427           1 :                             bandsFWHMArray->buffers[ARROW_BUF_DATA]));
    2428           1 :                         values[nBandsItemCount] =
    2429           1 :                             static_cast<float>(CPLAtof(pszFWHM));
    2430             :                     }
    2431             :                     else
    2432             :                     {
    2433           9 :                         OGRArrowArrayHelper::SetNull(bandsFWHMArray,
    2434             :                                                      nBandsItemCount,
    2435             :                                                      nBandsItemAlloc, false);
    2436             :                     }
    2437          10 :                     bandsFWHMArray->length++;
    2438             : 
    2439          10 :                     int bHasNoData = false;
    2440             :                     const double dfNoDataValue =
    2441          10 :                         poBand->GetNoDataValue(&bHasNoData);
    2442          10 :                     if (bHasNoData)
    2443             :                     {
    2444             :                         const std::string osNodata =
    2445           1 :                             std::isnan(dfNoDataValue) ? "nan"
    2446           1 :                             : std::isinf(dfNoDataValue)
    2447           1 :                                 ? (dfNoDataValue > 0 ? "inf" : "-inf")
    2448           3 :                                 : CPLSPrintf("%.17g", dfNoDataValue);
    2449             :                         void *ptr =
    2450           1 :                             OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2451             :                                 bandsNodataArray, nBandsItemCount,
    2452             :                                 osNodata.size(), nBandsNodataArrayMaxAlloc,
    2453             :                                 false);
    2454           1 :                         if (!ptr)
    2455           0 :                             return nullptr;
    2456           1 :                         memcpy(ptr, osNodata.data(), osNodata.size());
    2457             :                     }
    2458             :                     else
    2459             :                     {
    2460           9 :                         OGRArrowArrayHelper::SetNull(bandsNodataArray,
    2461             :                                                      nBandsItemCount,
    2462             :                                                      nBandsItemAlloc, false);
    2463             :                     }
    2464          10 :                     bandsNodataArray->length++;
    2465             : 
    2466             :                     {
    2467          10 :                         const char *pszDT = "other";
    2468             :                         // clang-format off
    2469          10 :                         switch (poBand->GetRasterDataType())
    2470             :                         {
    2471           0 :                             case GDT_Int8:     pszDT = "int8";     break;
    2472           9 :                             case GDT_UInt8:    pszDT = "uint8";    break;
    2473           0 :                             case GDT_Int16:    pszDT = "int16";    break;
    2474           1 :                             case GDT_UInt16:   pszDT = "uint16";   break;
    2475           0 :                             case GDT_Int32:    pszDT = "int32";    break;
    2476           0 :                             case GDT_UInt32:   pszDT = "uint32";   break;
    2477           0 :                             case GDT_Int64:    pszDT = "int64";    break;
    2478           0 :                             case GDT_UInt64:   pszDT = "uint64";   break;
    2479           0 :                             case GDT_Float16:  pszDT = "float16";  break;
    2480           0 :                             case GDT_Float32:  pszDT = "float32";  break;
    2481           0 :                             case GDT_Float64:  pszDT = "float64";  break;
    2482           0 :                             case GDT_CInt16:   pszDT = "cint16";   break;
    2483           0 :                             case GDT_CInt32:   pszDT = "cint32";   break;
    2484           0 :                             case GDT_CFloat16: pszDT = "cfloat16"; break;
    2485           0 :                             case GDT_CFloat32: pszDT = "cfloat32"; break;
    2486           0 :                             case GDT_CFloat64: pszDT = "cfloat64"; break;
    2487           0 :                             case GDT_Unknown:                      break;
    2488           0 :                             case GDT_TypeCount:                    break;
    2489             :                         }
    2490             :                         // clang-format on
    2491          10 :                         const size_t nLen = strlen(pszDT);
    2492             :                         void *ptr =
    2493          10 :                             OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2494             :                                 bandsDataTypeArray, nBandsItemCount, nLen,
    2495             :                                 nBandsDataTypeArrayMaxAlloc, false);
    2496          10 :                         if (!ptr)
    2497           0 :                             return nullptr;
    2498          10 :                         memcpy(ptr, pszDT, nLen);
    2499             : 
    2500          10 :                         bandsDataTypeArray->length++;
    2501             :                     }
    2502             : 
    2503          10 :                     const char *pszUnits = poBand->GetUnitType();
    2504          10 :                     if (pszUnits && pszUnits[0])
    2505             :                     {
    2506           1 :                         const size_t nLen = strlen(pszUnits);
    2507             :                         void *ptr =
    2508           1 :                             OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2509             :                                 bandsUnitArray, nBandsItemCount, nLen,
    2510             :                                 nBandsUnitArrayMaxAlloc, false);
    2511           1 :                         if (!ptr)
    2512           0 :                             return nullptr;
    2513           1 :                         memcpy(ptr, pszUnits, nLen);
    2514             :                     }
    2515             :                     else
    2516             :                     {
    2517           9 :                         OGRArrowArrayHelper::SetNull(bandsUnitArray,
    2518             :                                                      nBandsItemCount,
    2519             :                                                      nBandsItemAlloc, false);
    2520             :                     }
    2521          10 :                     bandsUnitArray->length++;
    2522             :                 }
    2523             :             }
    2524             : 
    2525             :             // Write "proj:code"
    2526           8 :             bool bHasProjCode = false;
    2527             :             {
    2528           8 :                 auto psArray = topArrays[iProjCode];
    2529             :                 const char *pszSRSAuthName =
    2530           8 :                     poSrcSRS ? poSrcSRS->GetAuthorityName(nullptr) : nullptr;
    2531             :                 const char *pszSRSAuthCode =
    2532           8 :                     poSrcSRS ? poSrcSRS->GetAuthorityCode(nullptr) : nullptr;
    2533           8 :                 if (pszSRSAuthName && pszSRSAuthCode)
    2534             :                 {
    2535           6 :                     std::string osCode(pszSRSAuthName);
    2536           6 :                     osCode += ':';
    2537           6 :                     osCode += pszSRSAuthCode;
    2538           6 :                     void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2539             :                         psArray, nBatchSize, osCode.size(),
    2540             :                         nProjCodeArrayMaxAlloc, false);
    2541           6 :                     if (!ptr)
    2542           0 :                         return nullptr;
    2543           6 :                     memcpy(ptr, osCode.data(), osCode.size());
    2544          12 :                     bHasProjCode = true;
    2545             :                 }
    2546             :                 else
    2547             :                 {
    2548           2 :                     OGRArrowArrayHelper::SetNull(psArray, nBatchSize,
    2549             :                                                  nMaxBatchSize, false);
    2550           2 :                     OGRArrowArrayHelper::SetEmptyStringOrBinary(psArray,
    2551             :                                                                 nBatchSize);
    2552             :                 }
    2553             :             }
    2554             : 
    2555             :             // Write "proj:wkt2"
    2556             :             {
    2557           8 :                 auto psArray = topArrays[iProjWKT2];
    2558           8 :                 std::string osWKT2;
    2559           8 :                 if (poSrcSRS && !bHasProjCode)
    2560             :                 {
    2561           1 :                     const char *const apszOptions[] = {"FORMAT=WKT2_2019",
    2562             :                                                        nullptr};
    2563           1 :                     osWKT2 = poSrcSRS->exportToWkt(apszOptions);
    2564             :                 }
    2565           8 :                 if (!osWKT2.empty())
    2566             :                 {
    2567           1 :                     void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2568             :                         psArray, nBatchSize, osWKT2.size(),
    2569             :                         nProjWKT2ArrayMaxAlloc, false);
    2570           1 :                     if (!ptr)
    2571           0 :                         return nullptr;
    2572           1 :                     memcpy(ptr, osWKT2.data(), osWKT2.size());
    2573             :                 }
    2574             :                 else
    2575             :                 {
    2576           7 :                     OGRArrowArrayHelper::SetNull(psArray, nBatchSize,
    2577             :                                                  nMaxBatchSize, false);
    2578           7 :                     OGRArrowArrayHelper::SetEmptyStringOrBinary(psArray,
    2579             :                                                                 nBatchSize);
    2580             :                 }
    2581             :             }
    2582             : 
    2583             :             // Write "proj:projjson"
    2584             :             {
    2585           8 :                 auto psArray = topArrays[iProjPROJJSON];
    2586           8 :                 std::string osPROJJSON;
    2587           8 :                 if (poSrcSRS && !bHasProjCode)
    2588             :                 {
    2589           1 :                     char *pszPROJJSON = nullptr;
    2590           1 :                     poSrcSRS->exportToPROJJSON(&pszPROJJSON, nullptr);
    2591           1 :                     if (pszPROJJSON)
    2592           1 :                         osPROJJSON = pszPROJJSON;
    2593           1 :                     CPLFree(pszPROJJSON);
    2594             :                 }
    2595           8 :                 if (!osPROJJSON.empty())
    2596             :                 {
    2597           1 :                     void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2598             :                         psArray, nBatchSize, osPROJJSON.size(),
    2599             :                         nProjPROJJSONArrayMaxAlloc, false);
    2600           1 :                     if (!ptr)
    2601           0 :                         return nullptr;
    2602           1 :                     memcpy(ptr, osPROJJSON.data(), osPROJJSON.size());
    2603             :                 }
    2604             :                 else
    2605             :                 {
    2606           7 :                     OGRArrowArrayHelper::SetNull(psArray, nBatchSize,
    2607             :                                                  nMaxBatchSize, false);
    2608           7 :                     OGRArrowArrayHelper::SetEmptyStringOrBinary(psArray,
    2609             :                                                                 nBatchSize);
    2610             :                 }
    2611             :             }
    2612             : 
    2613             :             // Write proj:bbox
    2614             :             {
    2615           8 :                 double *values = static_cast<double *>(
    2616           8 :                     const_cast<void *>(projBBOXItems->buffers[ARROW_BUF_DATA]));
    2617           8 :                 auto ptr = values + nBatchSize * NUM_ITEMS_PROJ_BBOX;
    2618           8 :                 ptr[0] = dfMinXBeforeReproj;
    2619           8 :                 ptr[1] = dfMinYBeforeReproj;
    2620           8 :                 ptr[2] = dfMaxXBeforeReproj;
    2621           8 :                 ptr[3] = dfMaxYBeforeReproj;
    2622             :             }
    2623             : 
    2624             :             // Write proj:shape
    2625             :             {
    2626           8 :                 int32_t *values = static_cast<int32_t *>(const_cast<void *>(
    2627           8 :                     projShapeItems->buffers[ARROW_BUF_DATA]));
    2628           8 :                 auto ptr = values + nBatchSize * NUM_ITEMS_PROJ_SHAPE;
    2629           8 :                 ptr[0] = poSrcDS->GetRasterYSize();
    2630           8 :                 ptr[1] = poSrcDS->GetRasterXSize();
    2631             :             }
    2632             : 
    2633             :             // Write proj:transform
    2634             :             {
    2635           8 :                 double *values = static_cast<double *>(const_cast<void *>(
    2636           8 :                     projTransformItems->buffers[ARROW_BUF_DATA]));
    2637           8 :                 auto ptr = values + nBatchSize * NUM_ITEMS_PROJ_TRANSFORM;
    2638           8 :                 ptr[0] = gt[1];
    2639           8 :                 ptr[1] = gt[2];
    2640           8 :                 ptr[2] = gt[0];
    2641           8 :                 ptr[3] = gt[4];
    2642           8 :                 ptr[4] = gt[5];
    2643           8 :                 ptr[5] = gt[3];
    2644           8 :                 ptr[6] = 0;
    2645           8 :                 ptr[7] = 0;
    2646           8 :                 ptr[8] = 1;
    2647             :             }
    2648             : 
    2649             :             // Write geometry
    2650             :             {
    2651           8 :                 const size_t nWKBSize = poPoly->WkbSize();
    2652           8 :                 void *ptr = arrayHelper->GetPtrForStringOrBinary(
    2653             :                     iWkbArray, nBatchSize, nWKBSize, false);
    2654           8 :                 if (!ptr)
    2655           0 :                     return nullptr;
    2656           8 :                 OGRwkbExportOptions sExportOptions;
    2657           8 :                 sExportOptions.eWkbVariant = wkbVariantIso;
    2658           8 :                 if (poPoly->exportToWkb(static_cast<unsigned char *>(ptr),
    2659           8 :                                         &sExportOptions) != OGRERR_NONE)
    2660           0 :                     return nullptr;
    2661             :             }
    2662             : 
    2663           8 :             nBatchSize++;
    2664           8 :             if (nBatchSize == nMaxBatchSize && !FlushArrays())
    2665             :             {
    2666           0 :                 return nullptr;
    2667             :             }
    2668             :         }
    2669             :         else
    2670             :         {
    2671          62 :             auto poFeature = std::make_unique<OGRFeature>(poLayerDefn);
    2672          62 :             poFeature->SetField(ti_field, osFileNameToWrite.c_str());
    2673             : 
    2674          62 :             if (i_SrcSRSName >= 0 && poSrcSRS)
    2675             :             {
    2676             :                 const char *pszAuthorityCode =
    2677          11 :                     poSrcSRS->GetAuthorityCode(nullptr);
    2678             :                 const char *pszAuthorityName =
    2679          11 :                     poSrcSRS->GetAuthorityName(nullptr);
    2680          11 :                 if (psOptions->eSrcSRSFormat == FORMAT_AUTO)
    2681             :                 {
    2682           5 :                     if (pszAuthorityName != nullptr &&
    2683             :                         pszAuthorityCode != nullptr)
    2684             :                     {
    2685           5 :                         poFeature->SetField(
    2686             :                             i_SrcSRSName, CPLSPrintf("%s:%s", pszAuthorityName,
    2687             :                                                      pszAuthorityCode));
    2688             :                     }
    2689           0 :                     else if (nMaxFieldSize == 0 ||
    2690           0 :                              strlen(poSrcDS->GetProjectionRef()) <=
    2691           0 :                                  static_cast<size_t>(nMaxFieldSize))
    2692             :                     {
    2693           0 :                         poFeature->SetField(i_SrcSRSName,
    2694             :                                             poSrcDS->GetProjectionRef());
    2695             :                     }
    2696             :                     else
    2697             :                     {
    2698           0 :                         char *pszProj4 = nullptr;
    2699           0 :                         if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
    2700             :                         {
    2701           0 :                             poFeature->SetField(i_SrcSRSName, pszProj4);
    2702             :                         }
    2703             :                         else
    2704             :                         {
    2705           0 :                             poFeature->SetField(i_SrcSRSName,
    2706             :                                                 poSrcDS->GetProjectionRef());
    2707             :                         }
    2708           0 :                         CPLFree(pszProj4);
    2709             :                     }
    2710             :                 }
    2711           6 :                 else if (psOptions->eSrcSRSFormat == FORMAT_WKT)
    2712             :                 {
    2713           4 :                     if (nMaxFieldSize == 0 ||
    2714           2 :                         strlen(poSrcDS->GetProjectionRef()) <=
    2715           2 :                             static_cast<size_t>(nMaxFieldSize))
    2716             :                     {
    2717           0 :                         poFeature->SetField(i_SrcSRSName,
    2718             :                                             poSrcDS->GetProjectionRef());
    2719             :                     }
    2720             :                     else
    2721             :                     {
    2722           2 :                         CPLError(
    2723             :                             CE_Warning, CPLE_AppDefined,
    2724             :                             "Cannot write WKT for file %s as it is too long!",
    2725             :                             osFileNameToWrite.c_str());
    2726             :                     }
    2727             :                 }
    2728           4 :                 else if (psOptions->eSrcSRSFormat == FORMAT_PROJ)
    2729             :                 {
    2730           2 :                     char *pszProj4 = nullptr;
    2731           2 :                     if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
    2732             :                     {
    2733           2 :                         poFeature->SetField(i_SrcSRSName, pszProj4);
    2734             :                     }
    2735           2 :                     CPLFree(pszProj4);
    2736             :                 }
    2737           2 :                 else if (psOptions->eSrcSRSFormat == FORMAT_EPSG)
    2738             :                 {
    2739           2 :                     if (pszAuthorityName != nullptr &&
    2740             :                         pszAuthorityCode != nullptr)
    2741           2 :                         poFeature->SetField(
    2742             :                             i_SrcSRSName, CPLSPrintf("%s:%s", pszAuthorityName,
    2743             :                                                      pszAuthorityCode));
    2744             :                 }
    2745             :             }
    2746             : 
    2747          67 :             for (const auto &oFetchMD : psOptions->aoFetchMD)
    2748             :             {
    2749           5 :                 if (EQUAL(oFetchMD.osRasterItemName.c_str(), "{PIXEL_SIZE}"))
    2750             :                 {
    2751           1 :                     poFeature->SetField(oFetchMD.osFieldName.c_str(), dfRes);
    2752           1 :                     continue;
    2753             :                 }
    2754             : 
    2755             :                 const char *pszMD =
    2756           4 :                     poSrcDS->GetMetadataItem(oFetchMD.osRasterItemName.c_str());
    2757           4 :                 if (pszMD)
    2758             :                 {
    2759           4 :                     if (EQUAL(oFetchMD.osRasterItemName.c_str(),
    2760             :                               "TIFFTAG_DATETIME"))
    2761             :                     {
    2762             :                         int nYear, nMonth, nDay, nHour, nMin, nSec;
    2763           2 :                         if (sscanf(pszMD, "%04d:%02d:%02d %02d:%02d:%02d",
    2764             :                                    &nYear, &nMonth, &nDay, &nHour, &nMin,
    2765           1 :                                    &nSec) == 6)
    2766             :                         {
    2767           1 :                             poFeature->SetField(
    2768             :                                 oFetchMD.osFieldName.c_str(),
    2769             :                                 CPLSPrintf("%04d/%02d/%02d %02d:%02d:%02d",
    2770             :                                            nYear, nMonth, nDay, nHour, nMin,
    2771             :                                            nSec));
    2772           1 :                             continue;
    2773             :                         }
    2774             :                     }
    2775           3 :                     poFeature->SetField(oFetchMD.osFieldName.c_str(), pszMD);
    2776             :                 }
    2777             :             }
    2778             : 
    2779          62 :             poFeature->SetGeometryDirectly(poPoly.release());
    2780             : 
    2781          62 :             if (poLayer->CreateFeature(poFeature.get()) != OGRERR_NONE)
    2782             :             {
    2783           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2784             :                          "Failed to create feature in tile index.");
    2785           0 :                 return nullptr;
    2786             :             }
    2787             :         }
    2788             : 
    2789          70 :         ++iCur;
    2790          85 :         if (psOptions->pfnProgress &&
    2791          30 :             !psOptions->pfnProgress(static_cast<double>(iCur) / nTotal, "",
    2792          15 :                                     psOptions->pProgressData))
    2793             :         {
    2794           0 :             return nullptr;
    2795             :         }
    2796          70 :         if (iCur >= nSrcCount)
    2797          46 :             ++nTotal;
    2798          86 :     }
    2799          50 :     if (psOptions->pfnProgress)
    2800           7 :         psOptions->pfnProgress(1.0, "", psOptions->pProgressData);
    2801             : 
    2802          50 :     if (bIsSTACGeoParquet && nBatchSize != 0 && !FlushArrays())
    2803             :     {
    2804           0 :         return nullptr;
    2805             :     }
    2806             : 
    2807          50 :     if (poTileIndexDSUnique)
    2808          28 :         return GDALDataset::ToHandle(poTileIndexDSUnique.release());
    2809             :     else
    2810          22 :         return GDALDataset::ToHandle(poTileIndexDS);
    2811             : }
    2812             : 
    2813             : /************************************************************************/
    2814             : /*                             SanitizeSRS                              */
    2815             : /************************************************************************/
    2816             : 
    2817          16 : static char *SanitizeSRS(const char *pszUserInput)
    2818             : 
    2819             : {
    2820             :     OGRSpatialReferenceH hSRS;
    2821          16 :     char *pszResult = nullptr;
    2822             : 
    2823          16 :     CPLErrorReset();
    2824             : 
    2825          16 :     hSRS = OSRNewSpatialReference(nullptr);
    2826          16 :     if (OSRSetFromUserInput(hSRS, pszUserInput) == OGRERR_NONE)
    2827          16 :         OSRExportToWkt(hSRS, &pszResult);
    2828             :     else
    2829             :     {
    2830           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Translating SRS failed:\n%s",
    2831             :                  pszUserInput);
    2832             :     }
    2833             : 
    2834          16 :     OSRDestroySpatialReference(hSRS);
    2835             : 
    2836          16 :     return pszResult;
    2837             : }
    2838             : 
    2839             : /************************************************************************/
    2840             : /*                          GDALTileIndexOptionsNew()                   */
    2841             : /************************************************************************/
    2842             : 
    2843             : /**
    2844             :  * Allocates a GDALTileIndexOptions struct.
    2845             :  *
    2846             :  * @param papszArgv NULL terminated list of options (potentially including
    2847             :  * filename and open options too), or NULL. The accepted options are the ones of
    2848             :  * the <a href="/programs/gdaltindex.html">gdaltindex</a> utility.
    2849             :  * @param psOptionsForBinary (output) may be NULL (and should generally be
    2850             :  * NULL), otherwise (gdaltindex_bin.cpp use case) must be allocated with
    2851             :  * GDALTileIndexOptionsForBinaryNew() prior to this function. Will be filled
    2852             :  * with potentially present filename, open options,...
    2853             :  * @return pointer to the allocated GDALTileIndexOptions struct. Must be freed
    2854             :  * with GDALTileIndexOptionsFree().
    2855             :  *
    2856             :  * @since GDAL 3.9
    2857             :  */
    2858             : 
    2859             : GDALTileIndexOptions *
    2860          52 : GDALTileIndexOptionsNew(char **papszArgv,
    2861             :                         GDALTileIndexOptionsForBinary *psOptionsForBinary)
    2862             : {
    2863         104 :     auto psOptions = std::make_unique<GDALTileIndexOptions>();
    2864             : 
    2865             :     /* -------------------------------------------------------------------- */
    2866             :     /*      Parse arguments.                                                */
    2867             :     /* -------------------------------------------------------------------- */
    2868             : 
    2869         104 :     CPLStringList aosArgv;
    2870             : 
    2871          52 :     if (papszArgv)
    2872             :     {
    2873          52 :         const int nArgc = CSLCount(papszArgv);
    2874         460 :         for (int i = 0; i < nArgc; i++)
    2875             :         {
    2876         408 :             aosArgv.AddString(papszArgv[i]);
    2877             :         }
    2878             :     }
    2879             : 
    2880             :     try
    2881             :     {
    2882             :         auto argParser = GDALTileIndexAppOptionsGetParser(psOptions.get(),
    2883          52 :                                                           psOptionsForBinary);
    2884          52 :         argParser->parse_args_without_binary_name(aosArgv.List());
    2885             : 
    2886             :         // Check all no store_into args
    2887          55 :         if (auto oTr = argParser->present<std::vector<double>>("-tr"))
    2888             :         {
    2889           3 :             psOptions->xres = (*oTr)[0];
    2890           3 :             psOptions->yres = (*oTr)[1];
    2891             :         }
    2892             : 
    2893          55 :         if (auto oTargetExtent = argParser->present<std::vector<double>>("-te"))
    2894             :         {
    2895           3 :             psOptions->xmin = (*oTargetExtent)[0];
    2896           3 :             psOptions->ymin = (*oTargetExtent)[1];
    2897           3 :             psOptions->xmax = (*oTargetExtent)[2];
    2898           3 :             psOptions->ymax = (*oTargetExtent)[3];
    2899             :         }
    2900             : 
    2901          52 :         if (auto fetchMd =
    2902          52 :                 argParser->present<std::vector<std::string>>("-fetch_md"))
    2903             :         {
    2904             : 
    2905           3 :             CPLAssert(fetchMd->size() % 3 == 0);
    2906             : 
    2907             :             // Loop
    2908           8 :             for (size_t i = 0; i < fetchMd->size(); i += 3)
    2909             :             {
    2910             :                 OGRFieldType type;
    2911           5 :                 const auto &typeName{fetchMd->at(i + 2)};
    2912           5 :                 if (typeName == "String")
    2913             :                 {
    2914           3 :                     type = OFTString;
    2915             :                 }
    2916           2 :                 else if (typeName == "Integer")
    2917             :                 {
    2918           0 :                     type = OFTInteger;
    2919             :                 }
    2920           2 :                 else if (typeName == "Integer64")
    2921             :                 {
    2922           0 :                     type = OFTInteger64;
    2923             :                 }
    2924           2 :                 else if (typeName == "Real")
    2925             :                 {
    2926           1 :                     type = OFTReal;
    2927             :                 }
    2928           1 :                 else if (typeName == "Date")
    2929             :                 {
    2930           0 :                     type = OFTDate;
    2931             :                 }
    2932           1 :                 else if (typeName == "DateTime")
    2933             :                 {
    2934           1 :                     type = OFTDateTime;
    2935             :                 }
    2936             :                 else
    2937             :                 {
    2938           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    2939             :                              "-fetch_md requires a valid type name as third "
    2940             :                              "argument: %s was given.",
    2941           0 :                              fetchMd->at(i).c_str());
    2942           0 :                     return nullptr;
    2943             :                 }
    2944             : 
    2945           5 :                 const GDALTileIndexRasterMetadata oMD{type, fetchMd->at(i + 1),
    2946          15 :                                                       fetchMd->at(i)};
    2947           5 :                 psOptions->aoFetchMD.push_back(std::move(oMD));
    2948             :             }
    2949             :         }
    2950             : 
    2951             :         // Check -t_srs
    2952          52 :         if (!psOptions->osTargetSRS.empty())
    2953             :         {
    2954          16 :             auto sanitized{SanitizeSRS(psOptions->osTargetSRS.c_str())};
    2955          16 :             if (sanitized)
    2956             :             {
    2957          16 :                 psOptions->osTargetSRS = sanitized;
    2958          16 :                 CPLFree(sanitized);
    2959             :             }
    2960             :             else
    2961             :             {
    2962             :                 // Error was already reported by SanitizeSRS, just return nullptr
    2963           0 :                 psOptions->osTargetSRS.clear();
    2964           0 :                 return nullptr;
    2965             :             }
    2966             :         }
    2967             :     }
    2968           0 :     catch (const std::exception &error)
    2969             :     {
    2970           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
    2971           0 :         return nullptr;
    2972             :     }
    2973             : 
    2974          52 :     return psOptions.release();
    2975             : }
    2976             : 
    2977             : /************************************************************************/
    2978             : /*                        GDALTileIndexOptionsFree()                    */
    2979             : /************************************************************************/
    2980             : 
    2981             : /**
    2982             :  * Frees the GDALTileIndexOptions struct.
    2983             :  *
    2984             :  * @param psOptions the options struct for GDALTileIndex().
    2985             :  *
    2986             :  * @since GDAL 3.9
    2987             :  */
    2988             : 
    2989          52 : void GDALTileIndexOptionsFree(GDALTileIndexOptions *psOptions)
    2990             : {
    2991          52 :     delete psOptions;
    2992          52 : }
    2993             : 
    2994             : /************************************************************************/
    2995             : /*                 GDALTileIndexOptionsSetProgress()                    */
    2996             : /************************************************************************/
    2997             : 
    2998             : /**
    2999             :  * Set a progress function.
    3000             :  *
    3001             :  * @param psOptions the options struct for GDALTileIndex().
    3002             :  * @param pfnProgress the progress callback.
    3003             :  * @param pProgressData the user data for the progress callback.
    3004             :  *
    3005             :  * @since GDAL 3.11
    3006             :  */
    3007             : 
    3008          29 : void GDALTileIndexOptionsSetProgress(GDALTileIndexOptions *psOptions,
    3009             :                                      GDALProgressFunc pfnProgress,
    3010             :                                      void *pProgressData)
    3011             : {
    3012          29 :     psOptions->pfnProgress = pfnProgress;
    3013          29 :     psOptions->pProgressData = pProgressData;
    3014          29 : }
    3015             : 
    3016             : #undef CHECK_HAS_ENOUGH_ADDITIONAL_ARGS

Generated by: LCOV version 1.14