LCOV - code coverage report
Current view: top level - apps - gdaltindex_lib.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1353 1598 84.7 %
Date: 2026-04-03 14:38:35 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          54 : static std::unique_ptr<GDALArgumentParser> GDALTileIndexAppOptionsGetParser(
     153             :     GDALTileIndexOptions *psOptions,
     154             :     GDALTileIndexOptionsForBinary *psOptionsForBinary)
     155             : {
     156             :     auto argParser = std::make_unique<GDALArgumentParser>(
     157          54 :         "gdaltindex", /* bForBinary=*/psOptionsForBinary != nullptr);
     158             : 
     159          54 :     argParser->add_description(
     160          54 :         _("Build a tile index from a list of datasets."));
     161             : 
     162          54 :     argParser->add_epilog(
     163             :         _("For more details, see the full documentation for gdaltindex at\n"
     164          54 :           "https://gdal.org/programs/gdaltindex.html"));
     165             : 
     166             :     // Hidden as used by gdal raster index
     167          54 :     argParser->add_argument("--invoked-from-gdal-raster-index")
     168          54 :         .store_into(psOptions->bInvokedFromGdalRasterIndex)
     169          54 :         .hidden();
     170             : 
     171             :     // Hidden as used by gdal raster index
     172          54 :     argParser->add_argument("-skip_errors")
     173          54 :         .store_into(psOptions->bSkipErrors)
     174          54 :         .hidden();
     175             : 
     176             :     // Hidden as used by gdal raster index
     177          54 :     argParser->add_argument("-profile")
     178          54 :         .store_into(psOptions->osProfile)
     179          54 :         .hidden();
     180             : 
     181             :     // Hidden as used by gdal raster index
     182          54 :     argParser->add_argument("--base-url")
     183          54 :         .store_into(psOptions->osBaseURL)
     184          54 :         .hidden();
     185             : 
     186             :     // Hidden as used by gdal raster index
     187          54 :     argParser->add_argument("--id-method")
     188          54 :         .store_into(psOptions->osIdMethod)
     189          54 :         .hidden();
     190             : 
     191             :     // Hidden as used by gdal raster index
     192          54 :     argParser->add_argument("--id-metadata-item")
     193          54 :         .store_into(psOptions->osIdMetadataItem)
     194          54 :         .hidden();
     195             : 
     196          54 :     argParser->add_argument("-overwrite")
     197          54 :         .flag()
     198          54 :         .store_into(psOptions->bOverwrite)
     199          54 :         .help(_("Overwrite the output tile index file if it already exists."));
     200             : 
     201          54 :     argParser->add_argument("-recursive")
     202          54 :         .flag()
     203          54 :         .store_into(psOptions->bRecursive)
     204             :         .help(_("Whether directories specified in <file_or_dir> should be "
     205          54 :                 "explored recursively."));
     206             : 
     207          54 :     argParser->add_argument("-filename_filter")
     208         108 :         .metavar("<val>")
     209          54 :         .append()
     210          54 :         .store_into(psOptions->oSetFilenameFilters)
     211             :         .help(_("Pattern that the filenames contained in directories pointed "
     212          54 :                 "by <file_or_dir> should follow."));
     213             : 
     214          54 :     argParser->add_argument("-min_pixel_size")
     215         108 :         .metavar("<val>")
     216          54 :         .store_into(psOptions->dfMinPixelSize)
     217             :         .help(_("Minimum pixel size in term of geospatial extent per pixel "
     218          54 :                 "(resolution) that a raster should have to be selected."));
     219             : 
     220          54 :     argParser->add_argument("-max_pixel_size")
     221         108 :         .metavar("<val>")
     222          54 :         .store_into(psOptions->dfMaxPixelSize)
     223             :         .help(_("Maximum pixel size in term of geospatial extent per pixel "
     224          54 :                 "(resolution) that a raster should have to be selected."));
     225             : 
     226          54 :     argParser->add_output_format_argument(psOptions->osFormat);
     227             : 
     228          54 :     argParser->add_argument("-tileindex")
     229         108 :         .metavar("<field_name>")
     230          54 :         .store_into(psOptions->osLocationField)
     231          54 :         .help(_("Name of the layer in the tile index file."));
     232             : 
     233          54 :     argParser->add_argument("-write_absolute_path")
     234          54 :         .flag()
     235          54 :         .store_into(psOptions->bWriteAbsolutePath)
     236             :         .help(_("Write the absolute path of the raster files in the tile index "
     237          54 :                 "file."));
     238             : 
     239          54 :     argParser->add_argument("-skip_different_projection")
     240          54 :         .flag()
     241          54 :         .store_into(psOptions->bSkipDifferentProjection)
     242             :         .help(_(
     243             :             "Only files with the same projection as files already inserted in "
     244          54 :             "the tile index will be inserted (unless -t_srs is specified)."));
     245             : 
     246          54 :     argParser->add_argument("-t_srs")
     247         108 :         .metavar("<srs_def>")
     248          54 :         .store_into(psOptions->osTargetSRS)
     249             :         .help(_("Geometries of input files will be transformed to the desired "
     250          54 :                 "target coordinate reference system."));
     251             : 
     252          54 :     argParser->add_argument("-src_srs_name")
     253         108 :         .metavar("<field_name>")
     254          54 :         .store_into(psOptions->osSrcSRSFieldName)
     255             :         .help(_("Name of the field in the tile index file where the source SRS "
     256          54 :                 "will be stored."));
     257             : 
     258          54 :     argParser->add_argument("-src_srs_format")
     259         108 :         .metavar("{AUTO|WKT|EPSG|PROJ}")
     260          54 :         .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          54 :             })
     273          54 :         .help(_("Format of the source SRS to store in the tile index file."));
     274             : 
     275          54 :     argParser->add_argument("-lyr_name")
     276         108 :         .metavar("<name>")
     277          54 :         .store_into(psOptions->osIndexLayerName)
     278          54 :         .help(_("Name of the layer in the tile index file."));
     279             : 
     280          54 :     argParser->add_layer_creation_options_argument(psOptions->aosLCO);
     281             : 
     282             :     // GTI driver options
     283             : 
     284          54 :     argParser->add_argument("-gti_filename")
     285         108 :         .metavar("<filename>")
     286          54 :         .store_into(psOptions->osGTIFilename)
     287          54 :         .help(_("Filename of the XML Virtual Tile Index file to generate."));
     288             : 
     289             :     // NOTE: no store_into
     290          54 :     argParser->add_argument("-tr")
     291         108 :         .metavar("<xres> <yres>")
     292          54 :         .nargs(2)
     293          54 :         .scan<'g', double>()
     294          54 :         .help(_("Set target resolution."));
     295             : 
     296             :     // NOTE: no store_into
     297          54 :     argParser->add_argument("-te")
     298         108 :         .metavar("<xmin> <ymin> <xmax> <ymax>")
     299          54 :         .nargs(4)
     300          54 :         .scan<'g', double>()
     301          54 :         .help(_("Set target extent in SRS unit."));
     302             : 
     303          54 :     argParser->add_argument("-ot")
     304         108 :         .metavar("<datatype>")
     305          54 :         .store_into(psOptions->osDataType)
     306          54 :         .help(_("Output data type."));
     307             : 
     308          54 :     argParser->add_argument("-bandcount")
     309         108 :         .metavar("<val>")
     310          54 :         .store_into(psOptions->osBandCount)
     311          54 :         .help(_("Number of bands of the tiles of the tile index."));
     312             : 
     313          54 :     argParser->add_argument("-nodata")
     314         108 :         .metavar("<val>")
     315          54 :         .append()
     316          54 :         .store_into(psOptions->osNodata)
     317          54 :         .help(_("Nodata value of the tiles of the tile index."));
     318             : 
     319             :     // Should we use choices here?
     320          54 :     argParser->add_argument("-colorinterp")
     321         108 :         .metavar("<val>")
     322          54 :         .append()
     323          54 :         .store_into(psOptions->osColorInterp)
     324             :         .help(_("Color interpretation of of the tiles of the tile index: red, "
     325          54 :                 "green, blue, alpha, gray, undefined."));
     326             : 
     327          54 :     argParser->add_argument("-mask")
     328          54 :         .flag()
     329          54 :         .store_into(psOptions->bMaskBand)
     330          54 :         .help(_("Add a mask band to the tiles of the tile index."));
     331             : 
     332          54 :     argParser->add_argument("-mo")
     333         108 :         .metavar("<name>=<value>")
     334          54 :         .append()
     335          54 :         .store_into(psOptions->aosMetadata)
     336             :         .help(_("Write an arbitrary layer metadata item, for formats that "
     337          54 :                 "support layer metadata."));
     338             : 
     339             :     // NOTE: no store_into
     340          54 :     argParser->add_argument("-fetch_md")
     341          54 :         .nargs(3)
     342         108 :         .metavar("<gdal_md_name> <fld_name> <fld_type>")
     343          54 :         .append()
     344             :         .help("Fetch a metadata item from the raster tile and write it as a "
     345          54 :               "field in the tile index.");
     346             : 
     347          54 :     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          54 :     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          54 :     GDALTileIndexTileIterator(const GDALTileIndexOptions *psOptionsIn,
     407             :                               int nSrcCountIn,
     408             :                               const char *const *papszSrcDSNamesIn)
     409          54 :         : psOptions(psOptionsIn), nSrcCount(nSrcCountIn),
     410          54 :           papszSrcDSNames(papszSrcDSNamesIn)
     411             :     {
     412          54 :     }
     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         862 :     std::string next()
     423             :     {
     424             :         while (true)
     425             :         {
     426         862 :             if (!psDir)
     427             :             {
     428         161 :                 if (iCurSrc == nSrcCount)
     429             :                 {
     430          53 :                     break;
     431             :                 }
     432             : 
     433             :                 VSIStatBufL sStatBuf;
     434         108 :                 const char *pszCurName = papszSrcDSNames[iCurSrc++];
     435         212 :                 if (VSIStatL(pszCurName, &sStatBuf) == 0 &&
     436         104 :                     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          99 :                     return pszCurName;
     458             :                 }
     459             :             }
     460             : 
     461         710 :             auto psEntry = VSIGetNextDirEntry(psDir);
     462         710 :             if (!psEntry)
     463             :             {
     464           5 :                 VSICloseDir(psDir);
     465           5 :                 psDir = nullptr;
     466           5 :                 continue;
     467             :             }
     468             : 
     469         705 :             if (!psOptions->oSetFilenameFilters.empty())
     470             :             {
     471         695 :                 bool bMatchFound = false;
     472             :                 const std::string osFilenameOnly =
     473         695 :                     CPLGetFilename(psEntry->pszName);
     474        1383 :                 for (const auto &osFilter : psOptions->oSetFilenameFilters)
     475             :                 {
     476         695 :                     if (GDALPatternMatch(osFilenameOnly.c_str(),
     477             :                                          osFilter.c_str()))
     478             :                     {
     479           7 :                         bMatchFound = true;
     480           7 :                         break;
     481             :                     }
     482             :                 }
     483         695 :                 if (!bMatchFound)
     484         688 :                     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         693 :         }
     503          53 :         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          54 : 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          54 :     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         108 :                          : std::make_unique<GDALTileIndexOptions>();
     563             : 
     564             :     GDALTileIndexTileIterator oGDALTileIndexTileIterator(
     565         108 :         psOptions.get(), nSrcCount, papszSrcDSNames);
     566             : 
     567             :     /* -------------------------------------------------------------------- */
     568             :     /*      Create and validate target SRS if given.                        */
     569             :     /* -------------------------------------------------------------------- */
     570         108 :     OGRSpatialReference oTargetSRS;
     571          54 :     if (!psOptions->osTargetSRS.empty())
     572             :     {
     573          18 :         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          18 :         oTargetSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     580             :         // coverity[tainted_data]
     581          18 :         oTargetSRS.SetFromUserInput(psOptions->osTargetSRS.c_str());
     582             :     }
     583             : 
     584             :     /* -------------------------------------------------------------------- */
     585             :     /*      Open or create the target datasource                            */
     586             :     /* -------------------------------------------------------------------- */
     587             : 
     588          54 :     std::unique_ptr<GDALDataset> poTileIndexDSUnique;
     589          54 :     GDALDataset *poTileIndexDS = GDALDataset::FromHandle(hTileIndexDS);
     590          54 :     OGRLayer *poLayer = OGRLayer::FromHandle(hLayer);
     591          54 :     bool bExistingLayer = false;
     592         108 :     std::string osFormat;
     593             : 
     594          54 :     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          54 :         EQUAL(psOptions->osProfile.c_str(), "STAC-GeoParquet");
     688             : 
     689          54 :     auto poOutDrv = poTileIndexDS->GetDriver();
     690          54 :     if (osFormat.empty() && poOutDrv)
     691          25 :         osFormat = poOutDrv->GetDescription();
     692             : 
     693             :     const char *pszVal =
     694          54 :         poOutDrv ? poOutDrv->GetMetadataItem(GDAL_DMD_MAX_STRING_LENGTH)
     695          54 :                  : nullptr;
     696          54 :     const int nMaxFieldSize = pszVal ? atoi(pszVal) : 0;
     697             : 
     698             :     const bool bFailOnErrors =
     699          54 :         psOptions->bInvokedFromGdalRasterIndex && !psOptions->bSkipErrors;
     700          54 :     bool bSkipFirstTile = false;
     701             : 
     702             :     // Configurable mostly/only for autotest purposes.
     703             :     const int nMaxBatchSize = std::max(
     704          54 :         1, atoi(CPLGetConfigOption("GDAL_RASTER_INDEX_BATCH_SIZE", "65536")));
     705             : 
     706         108 :     std::vector<ArrowSchema> topSchemas;
     707         108 :     std::vector<ArrowSchema *> topSchemasPointers;
     708         108 :     std::vector<std::unique_ptr<ArrowSchema>> auxSchemas;
     709         108 :     std::vector<ArrowSchema *> stacExtensionsSchemaChildren,
     710         108 :         linksSchemaChildren, linksItemSchemaChildren, assetsSchemaChildren,
     711         108 :         imageAssetSchemaChildren, imageAssetRolesSchemaChildren,
     712         108 :         bandsSchemaChildren, bandsItemSchemaChildren, projBboxSchemaChildren,
     713         108 :         projShapeSchemaChildren, projTransformSchemaChildren;
     714          54 :     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          54 :     };
     722             : 
     723          54 :     if (poLayer)
     724             :     {
     725           9 :         bExistingLayer = true;
     726             :     }
     727             :     else
     728             :     {
     729          45 :         std::string osLayerName;
     730          45 :         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          26 :             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          26 :             osLayerName = psOptions->osIndexLayerName;
     765             :         }
     766             : 
     767             :         /* get spatial reference for output file from target SRS (if set) */
     768             :         /* or from first input file */
     769          45 :         OGRSpatialReference oSRS;
     770          45 :         if (!oTargetSRS.IsEmpty())
     771             :         {
     772          17 :             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          44 :         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          44 :         poLayer = poTileIndexDS->CreateLayer(
     830          44 :             osLayerName.c_str(), oSRS.IsEmpty() ? nullptr : &oSRS, wkbPolygon,
     831          44 :             psOptions->aosLCO.List());
     832          44 :         if (!poLayer)
     833           0 :             return nullptr;
     834             : 
     835          44 :         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          37 :             OGRFieldDefn oLocationField(psOptions->osLocationField.c_str(),
    1144          37 :                                         OFTString);
    1145          37 :             oLocationField.SetWidth(nMaxFieldSize);
    1146          37 :             if (poLayer->CreateField(&oLocationField) != OGRERR_NONE)
    1147           0 :                 return nullptr;
    1148             :         }
    1149             : 
    1150          44 :         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          53 :     auto poLayerDefn = poLayer->GetLayerDefn();
    1161             : 
    1162          53 :     if (!bIsSTACGeoParquet)
    1163             :     {
    1164          51 :         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          53 :     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         106 :     CPLXMLTreeCloser psRoot(nullptr);
    1195          53 :     if (!psOptions->osGTIFilename.empty())
    1196             :     {
    1197           2 :         if (!psOptions->aosMetadata.empty())
    1198             :         {
    1199           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1200             :                      "-mo is not supported when -gti_filename is used");
    1201           0 :             return nullptr;
    1202             :         }
    1203           2 :         psRoot.reset(
    1204             :             CPLCreateXMLNode(nullptr, CXT_Element, "GDALTileIndexDataset"));
    1205           2 :         CPLCreateXMLElementAndValue(psRoot.get(), "IndexDataset", pszDest);
    1206           2 :         CPLCreateXMLElementAndValue(psRoot.get(), "IndexLayer",
    1207           2 :                                     poLayer->GetName());
    1208           2 :         CPLCreateXMLElementAndValue(psRoot.get(), "LocationField",
    1209           2 :                                     psOptions->osLocationField.c_str());
    1210           2 :         if (!std::isnan(psOptions->xres))
    1211             :         {
    1212           1 :             CPLCreateXMLElementAndValue(psRoot.get(), "ResX",
    1213           1 :                                         CPLSPrintf("%.18g", psOptions->xres));
    1214           1 :             CPLCreateXMLElementAndValue(psRoot.get(), "ResY",
    1215           1 :                                         CPLSPrintf("%.18g", psOptions->yres));
    1216             :         }
    1217           2 :         if (!std::isnan(psOptions->xmin))
    1218             :         {
    1219           1 :             CPLCreateXMLElementAndValue(psRoot.get(), "MinX",
    1220           1 :                                         CPLSPrintf("%.18g", psOptions->xmin));
    1221           1 :             CPLCreateXMLElementAndValue(psRoot.get(), "MinY",
    1222           1 :                                         CPLSPrintf("%.18g", psOptions->ymin));
    1223           1 :             CPLCreateXMLElementAndValue(psRoot.get(), "MaxX",
    1224           1 :                                         CPLSPrintf("%.18g", psOptions->xmax));
    1225           1 :             CPLCreateXMLElementAndValue(psRoot.get(), "MaxY",
    1226           1 :                                         CPLSPrintf("%.18g", psOptions->ymax));
    1227             :         }
    1228             : 
    1229           2 :         int nBandCount = 0;
    1230           2 :         if (!psOptions->osBandCount.empty())
    1231             :         {
    1232           0 :             nBandCount = atoi(psOptions->osBandCount.c_str());
    1233             :         }
    1234             :         else
    1235             :         {
    1236           2 :             if (!psOptions->osDataType.empty())
    1237             :             {
    1238           0 :                 nBandCount = std::max(
    1239             :                     nBandCount,
    1240           0 :                     CPLStringList(CSLTokenizeString2(
    1241           0 :                                       psOptions->osDataType.c_str(), ", ", 0))
    1242           0 :                         .size());
    1243             :             }
    1244           2 :             if (!psOptions->osNodata.empty())
    1245             :             {
    1246           1 :                 nBandCount = std::max(
    1247             :                     nBandCount,
    1248           2 :                     CPLStringList(CSLTokenizeString2(
    1249           1 :                                       psOptions->osNodata.c_str(), ", ", 0))
    1250           1 :                         .size());
    1251             :             }
    1252           2 :             if (!psOptions->osColorInterp.empty())
    1253             :             {
    1254           1 :                 nBandCount =
    1255           1 :                     std::max(nBandCount,
    1256           2 :                              CPLStringList(
    1257             :                                  CSLTokenizeString2(
    1258           1 :                                      psOptions->osColorInterp.c_str(), ", ", 0))
    1259           1 :                                  .size());
    1260             :             }
    1261             :         }
    1262             : 
    1263           3 :         for (int i = 0; i < nBandCount; ++i)
    1264             :         {
    1265           1 :             auto psBand = CPLCreateXMLNode(psRoot.get(), CXT_Element, "Band");
    1266           1 :             CPLAddXMLAttributeAndValue(psBand, "band", CPLSPrintf("%d", i + 1));
    1267           1 :             if (!psOptions->osDataType.empty())
    1268             :             {
    1269             :                 const CPLStringList aosTokens(
    1270           0 :                     CSLTokenizeString2(psOptions->osDataType.c_str(), ", ", 0));
    1271           0 :                 if (aosTokens.size() == 1)
    1272           0 :                     CPLAddXMLAttributeAndValue(psBand, "dataType",
    1273             :                                                aosTokens[0]);
    1274           0 :                 else if (i < aosTokens.size())
    1275           0 :                     CPLAddXMLAttributeAndValue(psBand, "dataType",
    1276             :                                                aosTokens[i]);
    1277             :             }
    1278           1 :             if (!psOptions->osNodata.empty())
    1279             :             {
    1280             :                 const CPLStringList aosTokens(
    1281           2 :                     CSLTokenizeString2(psOptions->osNodata.c_str(), ", ", 0));
    1282           1 :                 if (aosTokens.size() == 1)
    1283           1 :                     CPLCreateXMLElementAndValue(psBand, "NoDataValue",
    1284             :                                                 aosTokens[0]);
    1285           0 :                 else if (i < aosTokens.size())
    1286           0 :                     CPLCreateXMLElementAndValue(psBand, "NoDataValue",
    1287             :                                                 aosTokens[i]);
    1288             :             }
    1289           1 :             if (!psOptions->osColorInterp.empty())
    1290             :             {
    1291             :                 const CPLStringList aosTokens(CSLTokenizeString2(
    1292           2 :                     psOptions->osColorInterp.c_str(), ", ", 0));
    1293           1 :                 if (aosTokens.size() == 1)
    1294           1 :                     CPLCreateXMLElementAndValue(psBand, "ColorInterp",
    1295             :                                                 aosTokens[0]);
    1296           0 :                 else if (i < aosTokens.size())
    1297           0 :                     CPLCreateXMLElementAndValue(psBand, "ColorInterp",
    1298             :                                                 aosTokens[i]);
    1299             :             }
    1300             :         }
    1301             : 
    1302           2 :         if (psOptions->bMaskBand)
    1303             :         {
    1304           1 :             CPLCreateXMLElementAndValue(psRoot.get(), "MaskBand", "true");
    1305             :         }
    1306             :     }
    1307             :     else
    1308             :     {
    1309          51 :         if (!bIsSTACGeoParquet)
    1310             :         {
    1311          44 :             poLayer->SetMetadataItem("LOCATION_FIELD",
    1312          44 :                                      psOptions->osLocationField.c_str());
    1313             :         }
    1314          51 :         if (!std::isnan(psOptions->xres))
    1315             :         {
    1316           2 :             poLayer->SetMetadataItem("RESX",
    1317           2 :                                      CPLSPrintf("%.18g", psOptions->xres));
    1318           2 :             poLayer->SetMetadataItem("RESY",
    1319           2 :                                      CPLSPrintf("%.18g", psOptions->yres));
    1320             :         }
    1321          51 :         if (!std::isnan(psOptions->xmin))
    1322             :         {
    1323           2 :             poLayer->SetMetadataItem("MINX",
    1324           2 :                                      CPLSPrintf("%.18g", psOptions->xmin));
    1325           2 :             poLayer->SetMetadataItem("MINY",
    1326           2 :                                      CPLSPrintf("%.18g", psOptions->ymin));
    1327           2 :             poLayer->SetMetadataItem("MAXX",
    1328           2 :                                      CPLSPrintf("%.18g", psOptions->xmax));
    1329           2 :             poLayer->SetMetadataItem("MAXY",
    1330           2 :                                      CPLSPrintf("%.18g", psOptions->ymax));
    1331             :         }
    1332          51 :         if (!psOptions->osBandCount.empty())
    1333             :         {
    1334           2 :             poLayer->SetMetadataItem("BAND_COUNT",
    1335           2 :                                      psOptions->osBandCount.c_str());
    1336             :         }
    1337          51 :         if (!psOptions->osDataType.empty())
    1338             :         {
    1339           2 :             poLayer->SetMetadataItem("DATA_TYPE",
    1340           2 :                                      psOptions->osDataType.c_str());
    1341             :         }
    1342          51 :         if (!psOptions->osNodata.empty())
    1343             :         {
    1344           2 :             poLayer->SetMetadataItem("NODATA", psOptions->osNodata.c_str());
    1345             :         }
    1346          51 :         if (!psOptions->osColorInterp.empty())
    1347             :         {
    1348           2 :             poLayer->SetMetadataItem("COLOR_INTERPRETATION",
    1349           2 :                                      psOptions->osColorInterp.c_str());
    1350             :         }
    1351          51 :         if (psOptions->bMaskBand)
    1352             :         {
    1353           2 :             poLayer->SetMetadataItem("MASK_BAND", "YES");
    1354             :         }
    1355         102 :         const CPLStringList aosMetadata(psOptions->aosMetadata);
    1356           4 :         for (const auto &[pszKey, pszValue] :
    1357          55 :              cpl::IterateNameValue(aosMetadata))
    1358             :         {
    1359           2 :             poLayer->SetMetadataItem(pszKey, pszValue);
    1360             :         }
    1361             :     }
    1362             : 
    1363             :     const int ti_field =
    1364          53 :         poLayerDefn->GetFieldIndex(psOptions->osLocationField.c_str());
    1365          53 :     if (!bIsSTACGeoParquet && ti_field < 0)
    1366             :     {
    1367           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1368             :                  "Unable to find field `%s' in file `%s'.",
    1369           0 :                  psOptions->osLocationField.c_str(), pszDest);
    1370           0 :         return nullptr;
    1371             :     }
    1372             : 
    1373          53 :     int i_SrcSRSName = -1;
    1374          53 :     if (!bIsSTACGeoParquet && !psOptions->osSrcSRSFieldName.empty())
    1375             :     {
    1376             :         i_SrcSRSName =
    1377           6 :             poLayerDefn->GetFieldIndex(psOptions->osSrcSRSFieldName.c_str());
    1378           6 :         if (i_SrcSRSName < 0)
    1379             :         {
    1380           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1381             :                      "Unable to find field `%s' in file `%s'.",
    1382           0 :                      psOptions->osSrcSRSFieldName.c_str(), pszDest);
    1383           0 :             return nullptr;
    1384             :         }
    1385             :     }
    1386             : 
    1387             :     // Load in memory existing file names in tile index.
    1388         106 :     std::set<std::string> oSetExistingFiles;
    1389         106 :     OGRSpatialReference oAlreadyExistingSRS;
    1390          53 :     if (bExistingLayer)
    1391             :     {
    1392          31 :         for (auto &&poFeature : poLayer)
    1393             :         {
    1394          22 :             if (poFeature->IsFieldSetAndNotNull(ti_field))
    1395             :             {
    1396          22 :                 if (oSetExistingFiles.empty())
    1397             :                 {
    1398             :                     auto poSrcDS =
    1399             :                         std::unique_ptr<GDALDataset>(GDALDataset::Open(
    1400             :                             poFeature->GetFieldAsString(ti_field),
    1401          18 :                             GDAL_OF_RASTER, nullptr, nullptr, nullptr));
    1402           9 :                     if (poSrcDS)
    1403             :                     {
    1404           9 :                         auto poSrcSRS = poSrcDS->GetSpatialRef();
    1405           9 :                         if (poSrcSRS)
    1406           9 :                             oAlreadyExistingSRS = *poSrcSRS;
    1407             :                     }
    1408             :                 }
    1409          22 :                 oSetExistingFiles.insert(poFeature->GetFieldAsString(ti_field));
    1410             :             }
    1411             :         }
    1412             :     }
    1413             : 
    1414         106 :     std::string osCurrentPath;
    1415          53 :     if (psOptions->bWriteAbsolutePath)
    1416             :     {
    1417           3 :         char *pszCurrentPath = CPLGetCurrentDir();
    1418           3 :         if (pszCurrentPath)
    1419             :         {
    1420           3 :             osCurrentPath = pszCurrentPath;
    1421             :         }
    1422             :         else
    1423             :         {
    1424           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1425             :                      "This system does not support the CPLGetCurrentDir call. "
    1426             :                      "The option -bWriteAbsolutePath will have no effect.");
    1427             :         }
    1428           3 :         CPLFree(pszCurrentPath);
    1429             :     }
    1430             : 
    1431             :     const bool bIsGTIContext =
    1432         103 :         !std::isnan(psOptions->xres) || !std::isnan(psOptions->xmin) ||
    1433          50 :         !psOptions->osBandCount.empty() || !psOptions->osNodata.empty() ||
    1434          50 :         !psOptions->osColorInterp.empty() || !psOptions->osDataType.empty() ||
    1435         151 :         psOptions->bMaskBand || !psOptions->aosMetadata.empty() ||
    1436          48 :         !psOptions->osGTIFilename.empty();
    1437             : 
    1438             :     /* -------------------------------------------------------------------- */
    1439             :     /*      loop over GDAL files, processing.                               */
    1440             :     /* -------------------------------------------------------------------- */
    1441          53 :     ArrowArray topArray{};
    1442             : 
    1443             :     struct TopArrayReleaser
    1444             :     {
    1445             :         ArrowArray *m_array = nullptr;
    1446             : 
    1447          53 :         explicit TopArrayReleaser(ArrowArray *array) : m_array(array)
    1448             :         {
    1449          53 :         }
    1450             : 
    1451          53 :         ~TopArrayReleaser()
    1452          53 :         {
    1453          53 :             if (m_array && m_array->release)
    1454           0 :                 m_array->release(m_array);
    1455          53 :         }
    1456             : 
    1457             :         TopArrayReleaser(const TopArrayReleaser &) = delete;
    1458             :         TopArrayReleaser &operator=(const TopArrayReleaser &) = delete;
    1459             :     };
    1460             : 
    1461         106 :     TopArrayReleaser arrayReleaser(&topArray);
    1462             : 
    1463          53 :     ArrowArray **topArrays = nullptr;
    1464             : 
    1465          53 :     int iArray = 0;
    1466          53 :     const int iIdArray = iArray++;
    1467             : 
    1468          53 :     const int iStacExtensionsArray = iArray++;
    1469          53 :     ArrowArray *stacExtensionSubArray = nullptr;
    1470          53 :     uint32_t nStacExtensionSubArrayMaxAlloc = 0;
    1471             : 
    1472          53 :     const int iLinksArray = iArray++;
    1473          53 :     ArrowArray *linksItemArray = nullptr;
    1474             : 
    1475          53 :     const int iAssetsArray = iArray++;
    1476          53 :     ArrowArray *imageArray = nullptr;
    1477          53 :     uint32_t nImageHrefArrayMaxAlloc = 0;
    1478          53 :     ArrowArray *imageHrefArray = nullptr;
    1479          53 :     ArrowArray *imageRoleArray = nullptr;
    1480          53 :     ArrowArray *imageRoleItemArray = nullptr;
    1481          53 :     uint32_t nImageRoleItemArrayMaxAlloc = 0;
    1482          53 :     ArrowArray *imageTitleArray = nullptr;
    1483          53 :     uint32_t nImageTitleArrayMaxAlloc = 0;
    1484          53 :     ArrowArray *imageTypeArray = nullptr;
    1485          53 :     uint32_t nImageTypeArrayMaxAlloc = 0;
    1486             : 
    1487          53 :     const int iBandsArray = iArray++;
    1488          53 :     uint32_t nBandsItemCount = 0;
    1489          53 :     uint32_t nBandsItemAlloc = 0;
    1490          53 :     ArrowArray *bandsItemArray = nullptr;
    1491          53 :     ArrowArray *bandsNameArray = nullptr;
    1492          53 :     uint32_t nBandsNameArrayMaxAlloc = 0;
    1493          53 :     ArrowArray *bandsCommonNameArray = nullptr;
    1494          53 :     uint32_t nBandsCommonNameArrayMaxAlloc = 0;
    1495          53 :     ArrowArray *bandsCenterWavelengthArray = nullptr;
    1496          53 :     uint32_t nBandsCenterWavelengthArrayMaxAlloc = 0;
    1497          53 :     ArrowArray *bandsFWHMArray = nullptr;
    1498          53 :     uint32_t nBandsFWHMArrayMaxAlloc = 0;
    1499          53 :     ArrowArray *bandsNodataArray = nullptr;
    1500          53 :     uint32_t nBandsNodataArrayMaxAlloc = 0;
    1501          53 :     ArrowArray *bandsDataTypeArray = nullptr;
    1502          53 :     uint32_t nBandsDataTypeArrayMaxAlloc = 0;
    1503          53 :     ArrowArray *bandsUnitArray = nullptr;
    1504          53 :     uint32_t nBandsUnitArrayMaxAlloc = 0;
    1505             : 
    1506          53 :     const int iProjCode = iArray++;
    1507          53 :     uint32_t nProjCodeArrayMaxAlloc = 0;
    1508          53 :     const int iProjWKT2 = iArray++;
    1509          53 :     uint32_t nProjWKT2ArrayMaxAlloc = 0;
    1510          53 :     const int iProjPROJJSON = iArray++;
    1511          53 :     uint32_t nProjPROJJSONArrayMaxAlloc = 0;
    1512          53 :     const int iProjBBOX = iArray++;
    1513          53 :     ArrowArray *projBBOXItems = nullptr;
    1514          53 :     const int iProjShape = iArray++;
    1515          53 :     ArrowArray *projShapeItems = nullptr;
    1516          53 :     const int iProjTransform = iArray++;
    1517          53 :     ArrowArray *projTransformItems = nullptr;
    1518             : 
    1519          53 :     const int iWkbArray = iArray++;
    1520             : 
    1521          53 :     std::unique_ptr<OGRArrowArrayHelper> arrayHelper;
    1522             : 
    1523             :     const auto InitTopArray =
    1524           8 :         [iIdArray, iStacExtensionsArray, iLinksArray, iAssetsArray, iBandsArray,
    1525             :          iProjCode, iProjWKT2, iProjPROJJSON, iProjBBOX, iProjShape,
    1526             :          iProjTransform, iWkbArray, nMaxBatchSize, &arrayHelper, &topArray,
    1527             :          &topArrays, &topSchema, &nStacExtensionSubArrayMaxAlloc,
    1528             :          &stacExtensionSubArray, &linksItemArray, &imageArray,
    1529             :          &nImageHrefArrayMaxAlloc, &imageHrefArray, &imageRoleArray,
    1530             :          &imageRoleItemArray, &nImageRoleItemArrayMaxAlloc, &imageTitleArray,
    1531             :          &imageTypeArray, &nImageTitleArrayMaxAlloc, &nImageTypeArrayMaxAlloc,
    1532             :          &nBandsItemCount, &nBandsItemAlloc, &bandsItemArray, &bandsNameArray,
    1533             :          &nBandsNameArrayMaxAlloc, &bandsCommonNameArray,
    1534             :          &nBandsCommonNameArrayMaxAlloc, &nBandsCenterWavelengthArrayMaxAlloc,
    1535             :          &bandsCenterWavelengthArray, &nBandsFWHMArrayMaxAlloc, &bandsFWHMArray,
    1536             :          &bandsNodataArray, &nBandsNodataArrayMaxAlloc, &bandsDataTypeArray,
    1537             :          &nBandsDataTypeArrayMaxAlloc, &bandsUnitArray,
    1538             :          &nBandsUnitArrayMaxAlloc, &nProjCodeArrayMaxAlloc,
    1539             :          &nProjWKT2ArrayMaxAlloc, &nProjPROJJSONArrayMaxAlloc, &projBBOXItems,
    1540         664 :          &projShapeItems, &projTransformItems]()
    1541             :     {
    1542         280 :         const auto AllocArray = []()
    1543             :         {
    1544             :             auto array =
    1545         280 :                 static_cast<ArrowArray *>(CPLCalloc(1, sizeof(ArrowArray)));
    1546         280 :             array->release = ReleaseArray;
    1547         280 :             return array;
    1548             :         };
    1549             : 
    1550         288 :         const auto AllocNBuffers = [](ArrowArray &array, int n_buffers)
    1551             :         {
    1552         288 :             array.n_buffers = n_buffers;
    1553         288 :             array.buffers = static_cast<const void **>(
    1554         288 :                 CPLCalloc(n_buffers, sizeof(const void *)));
    1555         288 :         };
    1556             : 
    1557          88 :         const auto AllocNArrays = [](ArrowArray &array, int n_children)
    1558             :         {
    1559          88 :             array.n_children = n_children;
    1560          88 :             array.children = static_cast<ArrowArray **>(
    1561          88 :                 CPLCalloc(n_children, sizeof(ArrowArray *)));
    1562          88 :         };
    1563             : 
    1564             :         const auto InitializePrimitiveArray =
    1565          16 :             [&AllocNBuffers](ArrowArray &sArray, size_t nEltSize,
    1566          16 :                              size_t nLength)
    1567             :         {
    1568          16 :             AllocNBuffers(sArray, 2);
    1569          32 :             sArray.buffers[ARROW_BUF_DATA] =
    1570          16 :                 static_cast<const void *>(CPLCalloc(nLength, nEltSize));
    1571          24 :         };
    1572             : 
    1573             :         const auto InitializeStringOrBinaryArray =
    1574         152 :             [&AllocNBuffers](ArrowArray &sArray, size_t nLength)
    1575             :         {
    1576         152 :             AllocNBuffers(sArray, 3);
    1577             :             // +1 since the length of string of idx i is given by
    1578             :             // offset[i+1] - offset[i]
    1579         304 :             sArray.buffers[ARROW_BUF_DATA] = static_cast<const void *>(
    1580         152 :                 CPLCalloc(nLength + 1, sizeof(uint32_t)));
    1581             :             // Allocate a minimum amount to not get a null pointer
    1582         304 :             sArray.buffers[ARROW_BUF_BYTES] =
    1583         152 :                 static_cast<const void *>(CPLCalloc(1, 1));
    1584         160 :         };
    1585             : 
    1586             :         const auto InitializeListArray =
    1587          32 :             [&AllocNBuffers, &AllocNArrays](
    1588          64 :                 ArrowArray &sArray, ArrowArray *subArray, size_t nLength)
    1589             :         {
    1590          32 :             AllocNBuffers(sArray, 2);
    1591          64 :             sArray.buffers[ARROW_BUF_DATA] = static_cast<const void *>(
    1592          32 :                 CPLCalloc(nLength + 1, sizeof(uint32_t)));
    1593          32 :             AllocNArrays(sArray, 1);
    1594          32 :             sArray.children[0] = subArray;
    1595          40 :         };
    1596             : 
    1597             :         const auto InitializeFixedSizeListArray =
    1598          24 :             [&AllocNBuffers,
    1599             :              &AllocNArrays](ArrowArray &sArray, ArrowArray *subArray,
    1600          72 :                             size_t nItemSize, size_t nItemCount, size_t nLength)
    1601             :         {
    1602          24 :             AllocNArrays(sArray, 1);
    1603          24 :             AllocNBuffers(sArray, 1);
    1604          24 :             sArray.children[0] = subArray;
    1605          24 :             AllocNBuffers(*subArray, 2);
    1606          48 :             subArray->buffers[ARROW_BUF_DATA] = static_cast<const void *>(
    1607          24 :                 CPLCalloc(nItemCount * nItemSize, nLength));
    1608          32 :         };
    1609             : 
    1610          40 :         const auto InitializeStructArray = [&AllocNBuffers](ArrowArray &sArray)
    1611          40 :         { AllocNBuffers(sArray, 1); };
    1612             : 
    1613          16 :         topArrays = static_cast<ArrowArray **>(CPLCalloc(
    1614           8 :             static_cast<int>(topSchema.n_children), sizeof(ArrowArray *)));
    1615         104 :         for (int i = 0; i < static_cast<int>(topSchema.n_children); ++i)
    1616          96 :             topArrays[i] = AllocArray();
    1617             : 
    1618           8 :         topArray = ArrowArray{};
    1619           8 :         topArray.release = ReleaseArray;
    1620           8 :         topArray.n_children = topSchema.n_children;
    1621           8 :         topArray.children = topArrays;
    1622           8 :         InitializeStructArray(topArray);
    1623             : 
    1624           8 :         InitializeStringOrBinaryArray(*topArrays[iIdArray], nMaxBatchSize);
    1625             : 
    1626           8 :         stacExtensionSubArray = AllocArray();
    1627           8 :         nStacExtensionSubArrayMaxAlloc = 0;
    1628             :         {
    1629           8 :             auto *array = topArrays[iStacExtensionsArray];
    1630           8 :             InitializeListArray(*array, stacExtensionSubArray, nMaxBatchSize);
    1631           8 :             InitializeStringOrBinaryArray(
    1632           8 :                 *stacExtensionSubArray, COUNT_STAC_EXTENSIONS * nMaxBatchSize);
    1633             :         }
    1634             : 
    1635           8 :         linksItemArray = AllocArray();
    1636             :         {
    1637           8 :             auto *array = topArrays[iLinksArray];
    1638           8 :             InitializeListArray(*array, linksItemArray, nMaxBatchSize);
    1639           8 :             InitializeStructArray(*linksItemArray);
    1640             : 
    1641           8 :             AllocNArrays(*linksItemArray, 4);
    1642           8 :             ArrowArray *linksHrefArray = AllocArray();
    1643           8 :             ArrowArray *linksRelArray = AllocArray();
    1644           8 :             ArrowArray *linksTypeArray = AllocArray();
    1645           8 :             ArrowArray *linksTitleArray = AllocArray();
    1646           8 :             linksItemArray->children[0] = linksHrefArray;
    1647           8 :             linksItemArray->children[1] = linksRelArray;
    1648           8 :             linksItemArray->children[2] = linksTypeArray;
    1649           8 :             linksItemArray->children[3] = linksTitleArray;
    1650           8 :             InitializeStringOrBinaryArray(*linksHrefArray, nMaxBatchSize);
    1651           8 :             InitializeStringOrBinaryArray(*linksRelArray, nMaxBatchSize);
    1652           8 :             InitializeStringOrBinaryArray(*linksTypeArray, nMaxBatchSize);
    1653           8 :             InitializeStringOrBinaryArray(*linksTitleArray, nMaxBatchSize);
    1654             :         }
    1655             : 
    1656           8 :         imageArray = AllocArray();
    1657           8 :         nImageHrefArrayMaxAlloc = 0;
    1658           8 :         imageHrefArray = AllocArray();
    1659           8 :         imageRoleArray = AllocArray();
    1660           8 :         imageRoleItemArray = AllocArray();
    1661           8 :         nImageHrefArrayMaxAlloc = 0;
    1662           8 :         nImageRoleItemArrayMaxAlloc = 0;
    1663           8 :         imageTitleArray = AllocArray();
    1664           8 :         nImageTitleArrayMaxAlloc = 0;
    1665           8 :         imageTypeArray = AllocArray();
    1666           8 :         nImageTypeArrayMaxAlloc = 0;
    1667             :         {
    1668           8 :             auto *assets = topArrays[iAssetsArray];
    1669           8 :             InitializeStructArray(*assets);
    1670           8 :             AllocNArrays(*assets, 1);
    1671           8 :             assets->children[0] = imageArray;
    1672             : 
    1673           8 :             InitializeStructArray(*imageArray);
    1674           8 :             AllocNArrays(*imageArray, 4);
    1675           8 :             imageArray->children[0] = imageHrefArray;
    1676           8 :             imageArray->children[1] = imageRoleArray;
    1677           8 :             imageArray->children[2] = imageTitleArray;
    1678           8 :             imageArray->children[3] = imageTypeArray;
    1679             : 
    1680           8 :             InitializeStringOrBinaryArray(*imageHrefArray, nMaxBatchSize);
    1681           8 :             InitializeStringOrBinaryArray(*imageTitleArray, nMaxBatchSize);
    1682           8 :             InitializeStringOrBinaryArray(*imageTypeArray, nMaxBatchSize);
    1683           8 :             InitializeListArray(*imageRoleArray, imageRoleItemArray,
    1684             :                                 nMaxBatchSize);
    1685           8 :             InitializeStringOrBinaryArray(*imageRoleItemArray, nMaxBatchSize);
    1686             :         }
    1687             : 
    1688             :         // "bands" related initialization
    1689             :         {
    1690           8 :             nBandsItemCount = 0;
    1691           8 :             nBandsItemAlloc = 0;
    1692           8 :             bandsItemArray = AllocArray();
    1693           8 :             InitializeListArray(*(topArrays[iBandsArray]), bandsItemArray,
    1694             :                                 nMaxBatchSize);
    1695           8 :             InitializeStructArray(*bandsItemArray);
    1696             : 
    1697           8 :             bandsNameArray = AllocArray();
    1698           8 :             InitializeStringOrBinaryArray(*bandsNameArray, 0);
    1699           8 :             nBandsNameArrayMaxAlloc = 0;
    1700             : 
    1701           8 :             bandsCommonNameArray = AllocArray();
    1702           8 :             InitializeStringOrBinaryArray(*bandsCommonNameArray, 0);
    1703           8 :             nBandsCommonNameArrayMaxAlloc = 0;
    1704             : 
    1705           8 :             bandsCenterWavelengthArray = AllocArray();
    1706           8 :             InitializePrimitiveArray(*bandsCenterWavelengthArray, sizeof(float),
    1707             :                                      1);
    1708           8 :             nBandsCenterWavelengthArrayMaxAlloc = 0;
    1709             : 
    1710           8 :             bandsFWHMArray = AllocArray();
    1711           8 :             InitializePrimitiveArray(*bandsFWHMArray, sizeof(float), 1);
    1712           8 :             nBandsFWHMArrayMaxAlloc = 0;
    1713             : 
    1714           8 :             bandsNodataArray = AllocArray();
    1715           8 :             InitializeStringOrBinaryArray(*bandsNodataArray, 0);
    1716           8 :             nBandsNodataArrayMaxAlloc = 0;
    1717             : 
    1718           8 :             bandsDataTypeArray = AllocArray();
    1719           8 :             InitializeStringOrBinaryArray(*bandsDataTypeArray, 0);
    1720           8 :             nBandsDataTypeArrayMaxAlloc = 0;
    1721             : 
    1722           8 :             bandsUnitArray = AllocArray();
    1723           8 :             InitializeStringOrBinaryArray(*bandsUnitArray, 0);
    1724           8 :             nBandsUnitArrayMaxAlloc = 0;
    1725             : 
    1726           8 :             AllocNArrays(*bandsItemArray, 7);
    1727           8 :             bandsItemArray->children[0] = bandsNameArray;
    1728           8 :             bandsItemArray->children[1] = bandsCommonNameArray;
    1729           8 :             bandsItemArray->children[2] = bandsCenterWavelengthArray;
    1730           8 :             bandsItemArray->children[3] = bandsFWHMArray;
    1731           8 :             bandsItemArray->children[4] = bandsNodataArray;
    1732           8 :             bandsItemArray->children[5] = bandsDataTypeArray;
    1733           8 :             bandsItemArray->children[6] = bandsUnitArray;
    1734             :         }
    1735             : 
    1736             :         // proj:xxxx related initializations
    1737             :         {
    1738           8 :             InitializeStringOrBinaryArray(*topArrays[iProjCode], nMaxBatchSize);
    1739           8 :             nProjCodeArrayMaxAlloc = 0;
    1740           8 :             InitializeStringOrBinaryArray(*topArrays[iProjWKT2], nMaxBatchSize);
    1741           8 :             nProjWKT2ArrayMaxAlloc = 0;
    1742           8 :             InitializeStringOrBinaryArray(*topArrays[iProjPROJJSON],
    1743             :                                           nMaxBatchSize);
    1744           8 :             nProjPROJJSONArrayMaxAlloc = 0;
    1745             : 
    1746           8 :             projBBOXItems = AllocArray();
    1747           8 :             InitializeFixedSizeListArray(*(topArrays[iProjBBOX]), projBBOXItems,
    1748             :                                          sizeof(double), NUM_ITEMS_PROJ_BBOX,
    1749             :                                          nMaxBatchSize);
    1750             : 
    1751           8 :             projShapeItems = AllocArray();
    1752           8 :             InitializeFixedSizeListArray(*(topArrays[iProjShape]),
    1753             :                                          projShapeItems, sizeof(int32_t),
    1754             :                                          NUM_ITEMS_PROJ_SHAPE, nMaxBatchSize);
    1755             : 
    1756           8 :             projTransformItems = AllocArray();
    1757           8 :             InitializeFixedSizeListArray(
    1758           8 :                 *(topArrays[iProjTransform]), projTransformItems,
    1759             :                 sizeof(double), NUM_ITEMS_PROJ_TRANSFORM, nMaxBatchSize);
    1760             :         }
    1761             : 
    1762           8 :         InitializeStringOrBinaryArray(*topArrays[iWkbArray], nMaxBatchSize);
    1763             : 
    1764             :         arrayHelper =
    1765           8 :             std::make_unique<OGRArrowArrayHelper>(&topArray, nMaxBatchSize);
    1766           8 :     };
    1767             : 
    1768          53 :     int nBatchSize = 0;
    1769             : 
    1770           8 :     const auto FlushArrays = [poLayer, &topArray, &linksItemArray, &imageArray,
    1771         232 :                               &topSchema, &nBatchSize, &arrayHelper]()
    1772             :     {
    1773           8 :         topArray.length = nBatchSize;
    1774           8 :         linksItemArray->length = nBatchSize;
    1775           8 :         imageArray->length = nBatchSize;
    1776         104 :         for (int i = 0; i < static_cast<int>(topArray.n_children); ++i)
    1777          96 :             topArray.children[i]->length = nBatchSize;
    1778           8 :         const bool ret = poLayer->WriteArrowBatch(&topSchema, &topArray);
    1779           8 :         if (topArray.release)
    1780             :         {
    1781           0 :             topArray.release(&topArray);
    1782             :         }
    1783           8 :         memset(&topArray, 0, sizeof(topArray));
    1784           8 :         nBatchSize = 0;
    1785           8 :         arrayHelper.reset();
    1786           8 :         return ret;
    1787          53 :     };
    1788             : 
    1789          53 :     int iCur = 0;
    1790          53 :     int nTotal = nSrcCount + 1;
    1791          53 :     int nBandInterleavedCount = 0;
    1792          53 :     int nPixelInterleavedCount = 0;
    1793             :     while (true)
    1794             :     {
    1795         141 :         const std::string osSrcFilename = oGDALTileIndexTileIterator.next();
    1796         141 :         if (osSrcFilename.empty())
    1797          52 :             break;
    1798          89 :         if (bSkipFirstTile)
    1799             :         {
    1800           1 :             bSkipFirstTile = false;
    1801           1 :             continue;
    1802             :         }
    1803             : 
    1804          88 :         std::string osFileNameToWrite;
    1805             :         VSIStatBuf sStatBuf;
    1806             : 
    1807             :         // Make sure it is a file before building absolute path name.
    1808          88 :         if (!osCurrentPath.empty() &&
    1809          92 :             CPLIsFilenameRelative(osSrcFilename.c_str()) &&
    1810           4 :             VSIStat(osSrcFilename.c_str(), &sStatBuf) == 0)
    1811             :         {
    1812           8 :             osFileNameToWrite = CPLProjectRelativeFilenameSafe(
    1813           4 :                 osCurrentPath.c_str(), osSrcFilename.c_str());
    1814             :         }
    1815             :         else
    1816             :         {
    1817          84 :             osFileNameToWrite = osSrcFilename.c_str();
    1818             :         }
    1819             : 
    1820             :         // Checks that file is not already in tileindex.
    1821          88 :         if (oSetExistingFiles.find(osFileNameToWrite) !=
    1822         176 :             oSetExistingFiles.end())
    1823             :         {
    1824           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    1825             :                      "File %s is already in tileindex. Skipping it.",
    1826             :                      osFileNameToWrite.c_str());
    1827           4 :             continue;
    1828             :         }
    1829             : 
    1830           0 :         std::unique_ptr<GDALDataset> poSrcDS;
    1831             :         {
    1832             :             std::unique_ptr<CPLTurnFailureIntoWarningBackuper>
    1833           0 :                 poFailureIntoWarning;
    1834          84 :             if (!bFailOnErrors)
    1835             :                 poFailureIntoWarning =
    1836          60 :                     std::make_unique<CPLTurnFailureIntoWarningBackuper>();
    1837          84 :             CPL_IGNORE_RET_VAL(poFailureIntoWarning);
    1838             : 
    1839          84 :             poSrcDS.reset(GDALDataset::Open(
    1840             :                 osSrcFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
    1841             :                 nullptr, nullptr, nullptr));
    1842          84 :             if (poSrcDS == nullptr)
    1843             :             {
    1844           2 :                 CPLError(bFailOnErrors ? CE_Failure : CE_Warning,
    1845             :                          CPLE_AppDefined, "Unable to open %s%s.",
    1846             :                          osSrcFilename.c_str(),
    1847             :                          bFailOnErrors ? "" : ", skipping");
    1848           2 :                 if (bFailOnErrors)
    1849           1 :                     return nullptr;
    1850           1 :                 continue;
    1851             :             }
    1852             :         }
    1853             : 
    1854          82 :         if (poSrcDS->GetRasterCount() > 1)
    1855             :         {
    1856             :             const char *pszInterleaving =
    1857           3 :                 poSrcDS->GetMetadataItem("INTERLEAVE", "IMAGE_STRUCTURE");
    1858           3 :             if (pszInterleaving)
    1859             :             {
    1860           3 :                 if (EQUAL(pszInterleaving, "BAND"))
    1861           3 :                     ++nBandInterleavedCount;
    1862           0 :                 else if (EQUAL(pszInterleaving, "PIXEL"))
    1863           0 :                     ++nPixelInterleavedCount;
    1864             :             }
    1865             :         }
    1866             : 
    1867          82 :         GDALGeoTransform gt;
    1868          82 :         if (poSrcDS->GetGeoTransform(gt) != CE_None)
    1869             :         {
    1870           0 :             CPLError(bFailOnErrors ? CE_Failure : CE_Warning, CPLE_AppDefined,
    1871             :                      "It appears no georeferencing is available for\n"
    1872             :                      "`%s'%s.",
    1873             :                      osSrcFilename.c_str(), bFailOnErrors ? "" : ", skipping");
    1874           0 :             if (bFailOnErrors)
    1875           0 :                 return nullptr;
    1876           0 :             continue;
    1877             :         }
    1878             : 
    1879          82 :         auto poSrcSRS = poSrcDS->GetSpatialRef();
    1880             :         // If not set target srs, test that the current file uses same
    1881             :         // projection as others.
    1882          82 :         if (oTargetSRS.IsEmpty())
    1883             :         {
    1884          60 :             if (!oAlreadyExistingSRS.IsEmpty())
    1885             :             {
    1886          68 :                 if (poSrcSRS == nullptr ||
    1887          34 :                     !poSrcSRS->IsSame(&oAlreadyExistingSRS))
    1888             :                 {
    1889           1 :                     CPLError(
    1890             :                         CE_Warning, CPLE_AppDefined,
    1891             :                         "%s is not using the same projection system "
    1892             :                         "as other files in the tileindex.\n"
    1893             :                         "This may cause problems when using it in MapServer "
    1894             :                         "for example.\n"
    1895             :                         "Use -t_srs option to set target projection system. %s",
    1896             :                         osSrcFilename.c_str(),
    1897           1 :                         psOptions->bSkipDifferentProjection
    1898             :                             ? "Skipping this file."
    1899             :                             : "");
    1900           1 :                     if (psOptions->bSkipDifferentProjection)
    1901             :                     {
    1902           1 :                         continue;
    1903             :                     }
    1904             :                 }
    1905             :             }
    1906             :             else
    1907             :             {
    1908          26 :                 if (poSrcSRS)
    1909          26 :                     oAlreadyExistingSRS = *poSrcSRS;
    1910             :             }
    1911             :         }
    1912             : 
    1913          81 :         const int nXSize = poSrcDS->GetRasterXSize();
    1914          81 :         const int nYSize = poSrcDS->GetRasterYSize();
    1915          81 :         if (nXSize == 0 || nYSize == 0)
    1916             :         {
    1917           0 :             CPLError(bFailOnErrors ? CE_Failure : CE_Warning, CPLE_AppDefined,
    1918             :                      "%s has 0 width or height%s", osSrcFilename.c_str(),
    1919             :                      bFailOnErrors ? "" : ", skipping");
    1920           0 :             if (bFailOnErrors)
    1921           0 :                 return nullptr;
    1922           0 :             continue;
    1923             :         }
    1924             : 
    1925          81 :         double adfX[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
    1926          81 :         double adfY[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
    1927          81 :         adfX[0] = gt.xorig + 0 * gt.xscale + 0 * gt.xrot;
    1928          81 :         adfY[0] = gt.yorig + 0 * gt.yrot + 0 * gt.yscale;
    1929             : 
    1930          81 :         adfX[1] = gt.xorig + nXSize * gt.xscale + 0 * gt.xrot;
    1931          81 :         adfY[1] = gt.yorig + nXSize * gt.yrot + 0 * gt.yscale;
    1932             : 
    1933          81 :         adfX[2] = gt.xorig + nXSize * gt.xscale + nYSize * gt.xrot;
    1934          81 :         adfY[2] = gt.yorig + nXSize * gt.yrot + nYSize * gt.yscale;
    1935             : 
    1936          81 :         adfX[3] = gt.xorig + 0 * gt.xscale + nYSize * gt.xrot;
    1937          81 :         adfY[3] = gt.yorig + 0 * gt.yrot + nYSize * gt.yscale;
    1938             : 
    1939          81 :         adfX[4] = gt.xorig + 0 * gt.xscale + 0 * gt.xrot;
    1940          81 :         adfY[4] = gt.yorig + 0 * gt.yrot + 0 * gt.yscale;
    1941             : 
    1942             :         const double dfMinXBeforeReproj =
    1943          81 :             std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3]));
    1944             :         const double dfMinYBeforeReproj =
    1945          81 :             std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
    1946             :         const double dfMaxXBeforeReproj =
    1947          81 :             std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3]));
    1948             :         const double dfMaxYBeforeReproj =
    1949          81 :             std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
    1950             : 
    1951             :         // If set target srs, compute the reprojected extent.
    1952             :         // Use GDALWarp() with VRT output so the stored bounding box matches
    1953             :         // what the GTI reader computes in its Pass 1 (GetSourceDesc).
    1954             :         // A plain 4-corner transform or standalone GDALSuggestedWarpOutput2()
    1955             :         // can underestimate the extent because GDALWarp() may adjust the
    1956             :         // resolution after calling GDALSuggestedWarpOutput2(), producing a
    1957             :         // different pixel count and thus a different extent.
    1958          81 :         if (!oTargetSRS.IsEmpty() && poSrcSRS)
    1959             :         {
    1960          21 :             if (!poSrcSRS->IsSame(&oTargetSRS))
    1961             :             {
    1962          15 :                 char *pszDstWKT = nullptr;
    1963          15 :                 oTargetSRS.exportToWkt(&pszDstWKT);
    1964             : 
    1965          15 :                 CPLStringList aosWarpArgs;
    1966          15 :                 aosWarpArgs.AddString("-of");
    1967          15 :                 aosWarpArgs.AddString("VRT");
    1968          15 :                 aosWarpArgs.AddString("-t_srs");
    1969          15 :                 aosWarpArgs.AddString(pszDstWKT);
    1970          15 :                 CPLFree(pszDstWKT);
    1971             : 
    1972             :                 GDALWarpAppOptions *psWarpOptions =
    1973          15 :                     GDALWarpAppOptionsNew(aosWarpArgs.List(), nullptr);
    1974          15 :                 GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS.get());
    1975          15 :                 GDALDatasetH ahSrcDS[] = {hSrcDS};
    1976          15 :                 int bUsageError = false;
    1977             :                 auto poWarpDS = std::unique_ptr<GDALDataset>(
    1978             :                     GDALDataset::FromHandle(GDALWarp(
    1979          15 :                         "", nullptr, 1, ahSrcDS, psWarpOptions, &bUsageError)));
    1980          15 :                 GDALWarpAppOptionsFree(psWarpOptions);
    1981             : 
    1982          15 :                 if (!poWarpDS)
    1983             :                 {
    1984           0 :                     CPLError(bFailOnErrors ? CE_Failure : CE_Warning,
    1985             :                              CPLE_AppDefined,
    1986             :                              "unable to compute reprojected extent from "
    1987             :                              "source SRS `%s' to target SRS `%s' for "
    1988             :                              "file `%s'%s",
    1989             :                              poSrcDS->GetProjectionRef(),
    1990           0 :                              psOptions->osTargetSRS.c_str(),
    1991             :                              osFileNameToWrite.c_str(),
    1992             :                              bFailOnErrors ? "" : ", skipping");
    1993           0 :                     if (bFailOnErrors)
    1994           0 :                         return nullptr;
    1995           0 :                     continue;
    1996             :                 }
    1997             : 
    1998          15 :                 GDALGeoTransform warpGT;
    1999          15 :                 poWarpDS->GetGeoTransform(warpGT);
    2000          15 :                 const int nDstPixels = poWarpDS->GetRasterXSize();
    2001          15 :                 const int nDstLines = poWarpDS->GetRasterYSize();
    2002             : 
    2003          15 :                 const double dfDstMinX = warpGT.xorig;
    2004          15 :                 const double dfDstMaxY = warpGT.yorig;
    2005          15 :                 const double dfDstMaxX =
    2006          15 :                     warpGT.xorig + nDstPixels * warpGT.xscale;
    2007          15 :                 const double dfDstMinY =
    2008          15 :                     warpGT.yorig + nDstLines * warpGT.yscale;
    2009             : 
    2010          15 :                 adfX[0] = dfDstMinX;
    2011          15 :                 adfY[0] = dfDstMaxY;
    2012          15 :                 adfX[1] = dfDstMaxX;
    2013          15 :                 adfY[1] = dfDstMaxY;
    2014          15 :                 adfX[2] = dfDstMaxX;
    2015          15 :                 adfY[2] = dfDstMinY;
    2016          15 :                 adfX[3] = dfDstMinX;
    2017          15 :                 adfY[3] = dfDstMinY;
    2018          15 :                 adfX[4] = dfDstMinX;
    2019          15 :                 adfY[4] = dfDstMaxY;
    2020             :             }
    2021             :         }
    2022          72 :         else if (bIsGTIContext && !oAlreadyExistingSRS.IsEmpty() &&
    2023          12 :                  (poSrcSRS == nullptr ||
    2024          12 :                   !poSrcSRS->IsSame(&oAlreadyExistingSRS)))
    2025             :         {
    2026           0 :             CPLError(
    2027             :                 CE_Failure, CPLE_AppDefined,
    2028             :                 "%s is not using the same projection system "
    2029             :                 "as other files in the tileindex. This is not compatible of "
    2030             :                 "GTI use. Use -t_srs option to reproject tile extents "
    2031             :                 "to a common SRS.",
    2032             :                 osSrcFilename.c_str());
    2033           0 :             return nullptr;
    2034             :         }
    2035             : 
    2036             :         const double dfMinX =
    2037          81 :             std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3]));
    2038             :         const double dfMinY =
    2039          81 :             std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
    2040             :         const double dfMaxX =
    2041          81 :             std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3]));
    2042             :         const double dfMaxY =
    2043          81 :             std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
    2044             :         const double dfRes =
    2045          81 :             sqrt((dfMaxX - dfMinX) * (dfMaxY - dfMinY) / nXSize / nYSize);
    2046          91 :         if (!std::isnan(psOptions->dfMinPixelSize) &&
    2047          10 :             dfRes < psOptions->dfMinPixelSize)
    2048             :         {
    2049           5 :             CPLError(CE_Warning, CPLE_AppDefined,
    2050             :                      "%s has %f as pixel size (< %f). Skipping",
    2051           5 :                      osSrcFilename.c_str(), dfRes, psOptions->dfMinPixelSize);
    2052           5 :             continue;
    2053             :         }
    2054          84 :         if (!std::isnan(psOptions->dfMaxPixelSize) &&
    2055           8 :             dfRes > psOptions->dfMaxPixelSize)
    2056             :         {
    2057           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    2058             :                      "%s has %f as pixel size (> %f). Skipping",
    2059           4 :                      osSrcFilename.c_str(), dfRes, psOptions->dfMaxPixelSize);
    2060           4 :             continue;
    2061             :         }
    2062             : 
    2063          72 :         auto poPoly = std::make_unique<OGRPolygon>();
    2064          72 :         auto poRing = std::make_unique<OGRLinearRing>();
    2065         432 :         for (int k = 0; k < 5; k++)
    2066         360 :             poRing->addPoint(adfX[k], adfY[k]);
    2067          72 :         poPoly->addRing(std::move(poRing));
    2068             : 
    2069          72 :         if (bIsSTACGeoParquet)
    2070             :         {
    2071           8 :             const char *pszDriverName = poSrcDS->GetDriverName();
    2072           8 :             if (pszDriverName && EQUAL(pszDriverName, "MEM"))
    2073             :             {
    2074           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2075             :                          "Memory datasets cannot be referenced in a "
    2076             :                          "STAC-GeoParquet catalog");
    2077           0 :                 return nullptr;
    2078             :             }
    2079             : 
    2080           8 :             if (!arrayHelper)
    2081             :             {
    2082           8 :                 InitTopArray();
    2083             :             }
    2084             : 
    2085             :             // Write "id"
    2086             :             {
    2087           8 :                 std::string osId(CPLGetFilename(osFileNameToWrite.c_str()));
    2088             : 
    2089           8 :                 if (psOptions->osIdMethod == "md5")
    2090             :                 {
    2091             :                     const std::string osFilename =
    2092           1 :                         VSIFileManager::GetHandler(osFileNameToWrite.c_str())
    2093           1 :                             ->GetStreamingFilename(osFileNameToWrite);
    2094             :                     auto fp = VSIFilesystemHandler::OpenStatic(
    2095           1 :                         osFilename.c_str(), "rb");
    2096           1 :                     if (!fp)
    2097             :                     {
    2098           0 :                         CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
    2099             :                                  osFileNameToWrite.c_str());
    2100           0 :                         return nullptr;
    2101             :                     }
    2102             :                     CPLMD5Context md5Context;
    2103           1 :                     CPLMD5Init(&md5Context);
    2104           1 :                     constexpr size_t CHUNK_SIZE = 1024 * 1024;
    2105           1 :                     std::vector<GByte> buffer(CHUNK_SIZE, 0);
    2106             :                     while (true)
    2107             :                     {
    2108             :                         const size_t nRead =
    2109           1 :                             fp->Read(buffer.data(), 1, buffer.size());
    2110           1 :                         CPLMD5Update(&md5Context, buffer.data(), nRead);
    2111           1 :                         if (nRead < buffer.size())
    2112             :                         {
    2113           1 :                             if (fp->Error())
    2114             :                             {
    2115           0 :                                 CPLError(CE_Failure, CPLE_FileIO,
    2116             :                                          "Error while reading %s",
    2117             :                                          osFileNameToWrite.c_str());
    2118           0 :                                 return nullptr;
    2119             :                             }
    2120           1 :                             break;
    2121             :                         }
    2122           0 :                     }
    2123           1 :                     unsigned char digest[16] = {0};
    2124           1 :                     CPLMD5Final(digest, &md5Context);
    2125           1 :                     char *pszMD5 = CPLBinaryToHex(16, digest);
    2126           1 :                     osId = pszMD5;
    2127           1 :                     CPLFree(pszMD5);
    2128           1 :                     osId += '-';
    2129           1 :                     osId += CPLGetFilename(osFileNameToWrite.c_str());
    2130             :                 }
    2131           7 :                 else if (psOptions->osIdMethod == "metadata-item")
    2132             :                 {
    2133           2 :                     const char *pszId = poSrcDS->GetMetadataItem(
    2134           1 :                         psOptions->osIdMetadataItem.c_str());
    2135           1 :                     if (!pszId)
    2136             :                     {
    2137           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
    2138             :                                  "No metadata item '%s' in dataset %s",
    2139           0 :                                  psOptions->osIdMetadataItem.c_str(),
    2140             :                                  osFileNameToWrite.c_str());
    2141           0 :                         return nullptr;
    2142             :                     }
    2143           1 :                     osId = pszId;
    2144             :                 }
    2145           6 :                 else if (psOptions->osIdMethod != "filename")
    2146             :                 {
    2147             :                     // shouldn't happen
    2148           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
    2149             :                              "Unhandled id method '%s'",
    2150           0 :                              psOptions->osIdMethod.c_str());
    2151           0 :                     return nullptr;
    2152             :                 }
    2153             : 
    2154           8 :                 void *ptr = arrayHelper->GetPtrForStringOrBinary(
    2155             :                     iIdArray, nBatchSize, osId.size(), false);
    2156           8 :                 if (!ptr)
    2157           0 :                     return nullptr;
    2158           8 :                 memcpy(ptr, osId.data(), osId.size());
    2159             :             }
    2160             : 
    2161             :             // Write "stac_extensions"
    2162             :             {
    2163           8 :                 uint32_t *panOffsets = static_cast<uint32_t *>(
    2164           8 :                     const_cast<void *>(topArrays[iStacExtensionsArray]
    2165           8 :                                            ->buffers[ARROW_BUF_DATA]));
    2166           8 :                 panOffsets[nBatchSize + 1] =
    2167           8 :                     panOffsets[nBatchSize] + COUNT_STAC_EXTENSIONS;
    2168             : 
    2169             :                 {
    2170           8 :                     constexpr const char extension[] =
    2171             :                         "https://stac-extensions.github.io/projection/v2.0.0/"
    2172             :                         "schema.json";
    2173           8 :                     constexpr size_t nStrLen = sizeof(extension) - 1;
    2174           8 :                     void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2175             :                         stacExtensionSubArray,
    2176             :                         COUNT_STAC_EXTENSIONS * nBatchSize + 0, nStrLen,
    2177             :                         nStacExtensionSubArrayMaxAlloc, false);
    2178           8 :                     if (!ptr)
    2179           0 :                         return nullptr;
    2180           8 :                     memcpy(ptr, extension, nStrLen);
    2181           8 :                     stacExtensionSubArray->length++;
    2182             :                 }
    2183             : 
    2184             :                 {
    2185           8 :                     constexpr const char extension[] =
    2186             :                         "https://stac-extensions.github.io/eo/v2.0.0/"
    2187             :                         "schema.json";
    2188           8 :                     constexpr size_t nStrLen = sizeof(extension) - 1;
    2189          16 :                     void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2190             :                         stacExtensionSubArray,
    2191           8 :                         COUNT_STAC_EXTENSIONS * nBatchSize + 1, nStrLen,
    2192             :                         nStacExtensionSubArrayMaxAlloc, false);
    2193           8 :                     if (!ptr)
    2194           0 :                         return nullptr;
    2195           8 :                     memcpy(ptr, extension, nStrLen);
    2196           8 :                     stacExtensionSubArray->length++;
    2197             :                 }
    2198             :             }
    2199             : 
    2200             :             // Write "assets.image.href"
    2201             :             {
    2202           8 :                 std::string osHref = osFileNameToWrite;
    2203           8 :                 CPL_IGNORE_RET_VAL(osFileNameToWrite);
    2204           8 :                 if (!psOptions->osBaseURL.empty())
    2205             :                 {
    2206           2 :                     osHref = CPLFormFilenameSafe(psOptions->osBaseURL.c_str(),
    2207             :                                                  CPLGetFilename(osHref.c_str()),
    2208           1 :                                                  nullptr);
    2209             :                 }
    2210           7 :                 else if (VSIIsLocal(osHref.c_str()))
    2211             :                 {
    2212           7 :                     if (!CPLIsFilenameRelative(osHref.c_str()))
    2213             :                     {
    2214           4 :                         osHref = "file://" + osHref;
    2215             :                     }
    2216             :                 }
    2217           0 :                 else if (STARTS_WITH(osHref.c_str(), "/vsicurl/"))
    2218             :                 {
    2219           0 :                     osHref = osHref.substr(strlen("/vsicurl/"));
    2220             :                 }
    2221           0 :                 else if (STARTS_WITH(osHref.c_str(), "/vsis3/"))
    2222             :                 {
    2223           0 :                     osHref = "s3://" + osHref.substr(strlen("/vsis3/"));
    2224             :                 }
    2225           0 :                 else if (STARTS_WITH(osHref.c_str(), "/vsigs/"))
    2226             :                 {
    2227           0 :                     osHref = "gcs://" + osHref.substr(strlen("/vsigs/"));
    2228             :                 }
    2229           0 :                 else if (STARTS_WITH(osHref.c_str(), "/vsiaz/"))
    2230             :                 {
    2231           0 :                     osHref = "azure://" + osHref.substr(strlen("/vsiaz/"));
    2232             :                 }
    2233           8 :                 const size_t nHrefLen = osHref.size();
    2234           8 :                 void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2235             :                     imageHrefArray, nBatchSize, nHrefLen,
    2236             :                     nImageHrefArrayMaxAlloc, false);
    2237           8 :                 if (!ptr)
    2238           0 :                     return nullptr;
    2239           8 :                 memcpy(ptr, osHref.data(), nHrefLen);
    2240           8 :                 imageHrefArray->length++;
    2241             :             }
    2242             : 
    2243             :             // Write "assets.image.roles"
    2244             :             {
    2245           8 :                 uint32_t *panRolesOffsets =
    2246             :                     static_cast<uint32_t *>(const_cast<void *>(
    2247           8 :                         imageRoleArray->buffers[ARROW_BUF_DATA]));
    2248           8 :                 panRolesOffsets[nBatchSize + 1] =
    2249           8 :                     panRolesOffsets[nBatchSize] + 1;
    2250             : 
    2251           8 :                 constexpr const char ROLE_DATA[] = "data";
    2252           8 :                 constexpr size_t nStrLen = sizeof(ROLE_DATA) - 1;
    2253           8 :                 void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2254             :                     imageRoleItemArray, nBatchSize, nStrLen,
    2255             :                     nImageRoleItemArrayMaxAlloc, false);
    2256           8 :                 if (!ptr)
    2257           0 :                     return nullptr;
    2258           8 :                 memcpy(ptr, ROLE_DATA, nStrLen);
    2259           8 :                 imageRoleItemArray->length++;
    2260             :             }
    2261             : 
    2262             :             // Write "assets.image.type"
    2263           8 :             if (pszDriverName && EQUAL(pszDriverName, "GTiff"))
    2264             :             {
    2265             :                 const char *pszLayout =
    2266           8 :                     poSrcDS->GetMetadataItem("LAYOUT", "IMAGE_STRUCTURE");
    2267           8 :                 if (pszLayout && EQUAL(pszLayout, "COG"))
    2268             :                 {
    2269           0 :                     constexpr const char TYPE[] =
    2270             :                         "image/tiff; application=geotiff; "
    2271             :                         "profile=cloud-optimized";
    2272           0 :                     constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
    2273           0 :                     void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2274             :                         imageTypeArray, nBatchSize, TYPE_SIZE,
    2275             :                         nImageTypeArrayMaxAlloc, false);
    2276           0 :                     if (!ptr)
    2277           0 :                         return nullptr;
    2278           0 :                     memcpy(ptr, TYPE, TYPE_SIZE);
    2279             :                 }
    2280             :                 else
    2281             :                 {
    2282           8 :                     constexpr const char TYPE[] =
    2283             :                         "image/tiff; application=geotiff";
    2284           8 :                     constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
    2285           8 :                     void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2286             :                         imageTypeArray, nBatchSize, TYPE_SIZE,
    2287             :                         nImageTypeArrayMaxAlloc, false);
    2288           8 :                     if (!ptr)
    2289           0 :                         return nullptr;
    2290           8 :                     memcpy(ptr, TYPE, TYPE_SIZE);
    2291           8 :                 }
    2292             :             }
    2293           0 :             else if (pszDriverName && EQUAL(pszDriverName, "PNG"))
    2294             :             {
    2295           0 :                 constexpr const char TYPE[] = "image/png";
    2296           0 :                 constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
    2297           0 :                 void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2298             :                     imageTypeArray, nBatchSize, TYPE_SIZE,
    2299             :                     nImageTypeArrayMaxAlloc, false);
    2300           0 :                 if (!ptr)
    2301           0 :                     return nullptr;
    2302           0 :                 memcpy(ptr, TYPE, TYPE_SIZE);
    2303             :             }
    2304           0 :             else if (pszDriverName && EQUAL(pszDriverName, "JPEG"))
    2305             :             {
    2306           0 :                 constexpr const char TYPE[] = "image/jpeg";
    2307           0 :                 constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
    2308           0 :                 void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2309             :                     imageTypeArray, nBatchSize, TYPE_SIZE,
    2310             :                     nImageTypeArrayMaxAlloc, false);
    2311           0 :                 if (!ptr)
    2312           0 :                     return nullptr;
    2313           0 :                 memcpy(ptr, TYPE, TYPE_SIZE);
    2314             :             }
    2315           0 :             else if (pszDriverName && (EQUAL(pszDriverName, "JP2KAK") ||
    2316           0 :                                        EQUAL(pszDriverName, "JP2OpenJPEG") ||
    2317           0 :                                        EQUAL(pszDriverName, "JP2ECW") ||
    2318           0 :                                        EQUAL(pszDriverName, "JP2MrSID")))
    2319             :             {
    2320           0 :                 constexpr const char TYPE[] = "image/jp2";
    2321           0 :                 constexpr size_t TYPE_SIZE = sizeof(TYPE) - 1;
    2322           0 :                 void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2323             :                     imageTypeArray, nBatchSize, TYPE_SIZE,
    2324             :                     nImageTypeArrayMaxAlloc, false);
    2325           0 :                 if (!ptr)
    2326           0 :                     return nullptr;
    2327           0 :                 memcpy(ptr, TYPE, TYPE_SIZE);
    2328             :             }
    2329             :             else
    2330             :             {
    2331           0 :                 OGRArrowArrayHelper::SetNull(imageTypeArray, nBatchSize,
    2332             :                                              nMaxBatchSize, false);
    2333           0 :                 OGRArrowArrayHelper::SetEmptyStringOrBinary(imageTypeArray,
    2334             :                                                             nBatchSize);
    2335             :             }
    2336           8 :             imageTypeArray->length++;
    2337             : 
    2338             :             // Write "assets.image.title"
    2339             :             {
    2340           8 :                 OGRArrowArrayHelper::SetNull(imageTitleArray, nBatchSize,
    2341             :                                              nMaxBatchSize, false);
    2342           8 :                 OGRArrowArrayHelper::SetEmptyStringOrBinary(imageTitleArray,
    2343             :                                                             nBatchSize);
    2344           8 :                 imageTitleArray->length++;
    2345             :             }
    2346             : 
    2347             :             // Write "bands"
    2348             :             {
    2349           8 :                 const int nThisBands = poSrcDS->GetRasterCount();
    2350           8 :                 if (nThisBands + nBandsItemCount > nBandsItemAlloc)
    2351             :                 {
    2352           8 :                     const auto nOldAlloc = nBandsItemAlloc;
    2353           8 :                     if (nBandsItemAlloc >
    2354           8 :                         std::numeric_limits<uint32_t>::max() / 2)
    2355             :                     {
    2356           0 :                         CPLError(CE_Failure, CPLE_AppDefined, "Too many bands");
    2357           0 :                         return nullptr;
    2358             :                     }
    2359          16 :                     nBandsItemAlloc = std::max(2 * nBandsItemAlloc,
    2360           8 :                                                nThisBands + nBandsItemCount);
    2361             : 
    2362          56 :                     auto ReallocArray = [nOldAlloc, nBandsItemAlloc](
    2363         112 :                                             ArrowArray *array, size_t nItemSize)
    2364             :                     {
    2365          56 :                         if (array->buffers[ARROW_BUF_VALIDITY])
    2366             :                         {
    2367             :                             // Bitmap
    2368           0 :                             const uint32_t nNewSizeBytes =
    2369           0 :                                 (nBandsItemAlloc + 7) / 8;
    2370             :                             char *newPtr =
    2371           0 :                                 static_cast<char *>(VSI_REALLOC_VERBOSE(
    2372             :                                     const_cast<void *>(
    2373             :                                         array->buffers[ARROW_BUF_VALIDITY]),
    2374             :                                     nNewSizeBytes));
    2375           0 :                             if (!newPtr)
    2376           0 :                                 return false;
    2377           0 :                             array->buffers[ARROW_BUF_VALIDITY] =
    2378             :                                 static_cast<const void *>(
    2379             :                                     const_cast<const char *>(newPtr));
    2380           0 :                             const uint32_t nOldSizeBytes = (nOldAlloc + 7) / 8;
    2381           0 :                             if (nNewSizeBytes > nOldSizeBytes)
    2382             :                             {
    2383             :                                 // Initialize new allocated bytes as valid
    2384             :                                 // They are set invalid explicitly with SetNull()
    2385           0 :                                 memset(newPtr + nOldSizeBytes, 0xFF,
    2386           0 :                                        nNewSizeBytes - nOldSizeBytes);
    2387             :                             }
    2388             :                         }
    2389          56 :                         char *newPtr = static_cast<char *>(VSI_REALLOC_VERBOSE(
    2390             :                             const_cast<void *>(array->buffers[ARROW_BUF_DATA]),
    2391             :                             (nBandsItemAlloc + 1) * nItemSize));
    2392          56 :                         if (!newPtr)
    2393           0 :                             return false;
    2394          56 :                         array->buffers[ARROW_BUF_DATA] =
    2395             :                             static_cast<const void *>(
    2396             :                                 const_cast<const char *>(newPtr));
    2397          56 :                         memset(newPtr + (nOldAlloc + 1) * nItemSize, 0,
    2398          56 :                                (nBandsItemAlloc - nOldAlloc) * nItemSize);
    2399          56 :                         return true;
    2400           8 :                     };
    2401             : 
    2402           8 :                     if (!ReallocArray(bandsNameArray, sizeof(uint32_t)) ||
    2403           8 :                         !ReallocArray(bandsCommonNameArray, sizeof(uint32_t)) ||
    2404           8 :                         !ReallocArray(bandsCenterWavelengthArray,
    2405           8 :                                       sizeof(float)) ||
    2406           8 :                         !ReallocArray(bandsFWHMArray, sizeof(float)) ||
    2407           8 :                         !ReallocArray(bandsNodataArray, sizeof(uint32_t)) ||
    2408          24 :                         !ReallocArray(bandsDataTypeArray, sizeof(uint32_t)) ||
    2409           8 :                         !ReallocArray(bandsUnitArray, sizeof(uint32_t)))
    2410             :                     {
    2411           0 :                         return nullptr;
    2412             :                     }
    2413             :                 }
    2414             : 
    2415           8 :                 uint32_t *panBandsOffsets =
    2416             :                     static_cast<uint32_t *>(const_cast<void *>(
    2417           8 :                         topArrays[iBandsArray]->buffers[ARROW_BUF_DATA]));
    2418           8 :                 panBandsOffsets[nBatchSize + 1] =
    2419           8 :                     panBandsOffsets[nBatchSize] + nThisBands;
    2420             : 
    2421          18 :                 for (int i = 0; i < nThisBands; ++i, ++nBandsItemCount)
    2422             :                 {
    2423          10 :                     bandsItemArray->length++;
    2424             : 
    2425          10 :                     const auto poBand = poSrcDS->GetRasterBand(i + 1);
    2426             :                     {
    2427          10 :                         std::string osBandName = poBand->GetDescription();
    2428          10 :                         if (osBandName.empty())
    2429           9 :                             osBandName = "Band " + std::to_string(i + 1);
    2430             :                         void *ptr =
    2431          10 :                             OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2432             :                                 bandsNameArray, nBandsItemCount,
    2433             :                                 osBandName.size(), nBandsNameArrayMaxAlloc,
    2434             :                                 false);
    2435          10 :                         if (!ptr)
    2436           0 :                             return nullptr;
    2437          10 :                         memcpy(ptr, osBandName.data(), osBandName.size());
    2438          10 :                         bandsNameArray->length++;
    2439             :                     }
    2440             : 
    2441             :                     const char *pszCommonName =
    2442          10 :                         GDALGetSTACCommonNameFromColorInterp(
    2443          10 :                             poBand->GetColorInterpretation());
    2444          10 :                     if (pszCommonName)
    2445             :                     {
    2446           3 :                         const size_t nLen = strlen(pszCommonName);
    2447             :                         void *ptr =
    2448           3 :                             OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2449             :                                 bandsCommonNameArray, nBandsItemCount, nLen,
    2450             :                                 nBandsCommonNameArrayMaxAlloc, false);
    2451           3 :                         if (!ptr)
    2452           0 :                             return nullptr;
    2453           3 :                         memcpy(ptr, pszCommonName, nLen);
    2454             :                     }
    2455             :                     else
    2456             :                     {
    2457           7 :                         OGRArrowArrayHelper::SetNull(bandsCommonNameArray,
    2458             :                                                      nBandsItemCount,
    2459             :                                                      nBandsItemAlloc, false);
    2460           7 :                         OGRArrowArrayHelper::SetEmptyStringOrBinary(
    2461             :                             bandsCommonNameArray, nBandsItemCount);
    2462             :                     }
    2463          10 :                     bandsCommonNameArray->length++;
    2464             : 
    2465          10 :                     if (const char *pszCenterWavelength =
    2466          10 :                             poBand->GetMetadataItem("CENTRAL_WAVELENGTH_UM",
    2467          10 :                                                     "IMAGERY"))
    2468             :                     {
    2469           1 :                         float *values = static_cast<float *>(
    2470             :                             const_cast<void *>(bandsCenterWavelengthArray
    2471           1 :                                                    ->buffers[ARROW_BUF_DATA]));
    2472           1 :                         values[nBandsItemCount] =
    2473           1 :                             static_cast<float>(CPLAtof(pszCenterWavelength));
    2474             :                     }
    2475             :                     else
    2476             :                     {
    2477           9 :                         OGRArrowArrayHelper::SetNull(bandsCenterWavelengthArray,
    2478             :                                                      nBandsItemCount,
    2479             :                                                      nBandsItemAlloc, false);
    2480             :                     }
    2481          10 :                     bandsCenterWavelengthArray->length++;
    2482             : 
    2483          10 :                     if (const char *pszFWHM =
    2484          10 :                             poBand->GetMetadataItem("FWHM_UM", "IMAGERY"))
    2485             :                     {
    2486           1 :                         float *values = static_cast<float *>(const_cast<void *>(
    2487           1 :                             bandsFWHMArray->buffers[ARROW_BUF_DATA]));
    2488           1 :                         values[nBandsItemCount] =
    2489           1 :                             static_cast<float>(CPLAtof(pszFWHM));
    2490             :                     }
    2491             :                     else
    2492             :                     {
    2493           9 :                         OGRArrowArrayHelper::SetNull(bandsFWHMArray,
    2494             :                                                      nBandsItemCount,
    2495             :                                                      nBandsItemAlloc, false);
    2496             :                     }
    2497          10 :                     bandsFWHMArray->length++;
    2498             : 
    2499          10 :                     int bHasNoData = false;
    2500             :                     const double dfNoDataValue =
    2501          10 :                         poBand->GetNoDataValue(&bHasNoData);
    2502          10 :                     if (bHasNoData)
    2503             :                     {
    2504             :                         const std::string osNodata =
    2505           1 :                             std::isnan(dfNoDataValue) ? "nan"
    2506           1 :                             : std::isinf(dfNoDataValue)
    2507           1 :                                 ? (dfNoDataValue > 0 ? "inf" : "-inf")
    2508           3 :                                 : CPLSPrintf("%.17g", dfNoDataValue);
    2509             :                         void *ptr =
    2510           1 :                             OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2511             :                                 bandsNodataArray, nBandsItemCount,
    2512             :                                 osNodata.size(), nBandsNodataArrayMaxAlloc,
    2513             :                                 false);
    2514           1 :                         if (!ptr)
    2515           0 :                             return nullptr;
    2516           1 :                         memcpy(ptr, osNodata.data(), osNodata.size());
    2517             :                     }
    2518             :                     else
    2519             :                     {
    2520           9 :                         OGRArrowArrayHelper::SetNull(bandsNodataArray,
    2521             :                                                      nBandsItemCount,
    2522             :                                                      nBandsItemAlloc, false);
    2523             :                     }
    2524          10 :                     bandsNodataArray->length++;
    2525             : 
    2526             :                     {
    2527          10 :                         const char *pszDT = "other";
    2528             :                         // clang-format off
    2529          10 :                         switch (poBand->GetRasterDataType())
    2530             :                         {
    2531           0 :                             case GDT_Int8:     pszDT = "int8";     break;
    2532           9 :                             case GDT_UInt8:    pszDT = "uint8";    break;
    2533           0 :                             case GDT_Int16:    pszDT = "int16";    break;
    2534           1 :                             case GDT_UInt16:   pszDT = "uint16";   break;
    2535           0 :                             case GDT_Int32:    pszDT = "int32";    break;
    2536           0 :                             case GDT_UInt32:   pszDT = "uint32";   break;
    2537           0 :                             case GDT_Int64:    pszDT = "int64";    break;
    2538           0 :                             case GDT_UInt64:   pszDT = "uint64";   break;
    2539           0 :                             case GDT_Float16:  pszDT = "float16";  break;
    2540           0 :                             case GDT_Float32:  pszDT = "float32";  break;
    2541           0 :                             case GDT_Float64:  pszDT = "float64";  break;
    2542           0 :                             case GDT_CInt16:   pszDT = "cint16";   break;
    2543           0 :                             case GDT_CInt32:   pszDT = "cint32";   break;
    2544           0 :                             case GDT_CFloat16: pszDT = "cfloat16"; break;
    2545           0 :                             case GDT_CFloat32: pszDT = "cfloat32"; break;
    2546           0 :                             case GDT_CFloat64: pszDT = "cfloat64"; break;
    2547           0 :                             case GDT_Unknown:                      break;
    2548           0 :                             case GDT_TypeCount:                    break;
    2549             :                         }
    2550             :                         // clang-format on
    2551          10 :                         const size_t nLen = strlen(pszDT);
    2552             :                         void *ptr =
    2553          10 :                             OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2554             :                                 bandsDataTypeArray, nBandsItemCount, nLen,
    2555             :                                 nBandsDataTypeArrayMaxAlloc, false);
    2556          10 :                         if (!ptr)
    2557           0 :                             return nullptr;
    2558          10 :                         memcpy(ptr, pszDT, nLen);
    2559             : 
    2560          10 :                         bandsDataTypeArray->length++;
    2561             :                     }
    2562             : 
    2563          10 :                     const char *pszUnits = poBand->GetUnitType();
    2564          10 :                     if (pszUnits && pszUnits[0])
    2565             :                     {
    2566           1 :                         const size_t nLen = strlen(pszUnits);
    2567             :                         void *ptr =
    2568           1 :                             OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2569             :                                 bandsUnitArray, nBandsItemCount, nLen,
    2570             :                                 nBandsUnitArrayMaxAlloc, false);
    2571           1 :                         if (!ptr)
    2572           0 :                             return nullptr;
    2573           1 :                         memcpy(ptr, pszUnits, nLen);
    2574             :                     }
    2575             :                     else
    2576             :                     {
    2577           9 :                         OGRArrowArrayHelper::SetNull(bandsUnitArray,
    2578             :                                                      nBandsItemCount,
    2579             :                                                      nBandsItemAlloc, false);
    2580             :                     }
    2581          10 :                     bandsUnitArray->length++;
    2582             :                 }
    2583             :             }
    2584             : 
    2585             :             // Write "proj:code"
    2586           8 :             bool bHasProjCode = false;
    2587             :             {
    2588           8 :                 auto psArray = topArrays[iProjCode];
    2589             :                 const char *pszSRSAuthName =
    2590           8 :                     poSrcSRS ? poSrcSRS->GetAuthorityName(nullptr) : nullptr;
    2591             :                 const char *pszSRSAuthCode =
    2592           8 :                     poSrcSRS ? poSrcSRS->GetAuthorityCode(nullptr) : nullptr;
    2593           8 :                 if (pszSRSAuthName && pszSRSAuthCode)
    2594             :                 {
    2595           6 :                     std::string osCode(pszSRSAuthName);
    2596           6 :                     osCode += ':';
    2597           6 :                     osCode += pszSRSAuthCode;
    2598           6 :                     void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2599             :                         psArray, nBatchSize, osCode.size(),
    2600             :                         nProjCodeArrayMaxAlloc, false);
    2601           6 :                     if (!ptr)
    2602           0 :                         return nullptr;
    2603           6 :                     memcpy(ptr, osCode.data(), osCode.size());
    2604          12 :                     bHasProjCode = true;
    2605             :                 }
    2606             :                 else
    2607             :                 {
    2608           2 :                     OGRArrowArrayHelper::SetNull(psArray, nBatchSize,
    2609             :                                                  nMaxBatchSize, false);
    2610           2 :                     OGRArrowArrayHelper::SetEmptyStringOrBinary(psArray,
    2611             :                                                                 nBatchSize);
    2612             :                 }
    2613             :             }
    2614             : 
    2615             :             // Write "proj:wkt2"
    2616             :             {
    2617           8 :                 auto psArray = topArrays[iProjWKT2];
    2618           8 :                 std::string osWKT2;
    2619           8 :                 if (poSrcSRS && !bHasProjCode)
    2620             :                 {
    2621           1 :                     const char *const apszOptions[] = {"FORMAT=WKT2_2019",
    2622             :                                                        nullptr};
    2623           1 :                     osWKT2 = poSrcSRS->exportToWkt(apszOptions);
    2624             :                 }
    2625           8 :                 if (!osWKT2.empty())
    2626             :                 {
    2627           1 :                     void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2628             :                         psArray, nBatchSize, osWKT2.size(),
    2629             :                         nProjWKT2ArrayMaxAlloc, false);
    2630           1 :                     if (!ptr)
    2631           0 :                         return nullptr;
    2632           1 :                     memcpy(ptr, osWKT2.data(), osWKT2.size());
    2633             :                 }
    2634             :                 else
    2635             :                 {
    2636           7 :                     OGRArrowArrayHelper::SetNull(psArray, nBatchSize,
    2637             :                                                  nMaxBatchSize, false);
    2638           7 :                     OGRArrowArrayHelper::SetEmptyStringOrBinary(psArray,
    2639             :                                                                 nBatchSize);
    2640             :                 }
    2641             :             }
    2642             : 
    2643             :             // Write "proj:projjson"
    2644             :             {
    2645           8 :                 auto psArray = topArrays[iProjPROJJSON];
    2646           8 :                 std::string osPROJJSON;
    2647           8 :                 if (poSrcSRS && !bHasProjCode)
    2648             :                 {
    2649           1 :                     char *pszPROJJSON = nullptr;
    2650           1 :                     poSrcSRS->exportToPROJJSON(&pszPROJJSON, nullptr);
    2651           1 :                     if (pszPROJJSON)
    2652           1 :                         osPROJJSON = pszPROJJSON;
    2653           1 :                     CPLFree(pszPROJJSON);
    2654             :                 }
    2655           8 :                 if (!osPROJJSON.empty())
    2656             :                 {
    2657           1 :                     void *ptr = OGRArrowArrayHelper::GetPtrForStringOrBinary(
    2658             :                         psArray, nBatchSize, osPROJJSON.size(),
    2659             :                         nProjPROJJSONArrayMaxAlloc, false);
    2660           1 :                     if (!ptr)
    2661           0 :                         return nullptr;
    2662           1 :                     memcpy(ptr, osPROJJSON.data(), osPROJJSON.size());
    2663             :                 }
    2664             :                 else
    2665             :                 {
    2666           7 :                     OGRArrowArrayHelper::SetNull(psArray, nBatchSize,
    2667             :                                                  nMaxBatchSize, false);
    2668           7 :                     OGRArrowArrayHelper::SetEmptyStringOrBinary(psArray,
    2669             :                                                                 nBatchSize);
    2670             :                 }
    2671             :             }
    2672             : 
    2673             :             // Write proj:bbox
    2674             :             {
    2675           8 :                 double *values = static_cast<double *>(
    2676           8 :                     const_cast<void *>(projBBOXItems->buffers[ARROW_BUF_DATA]));
    2677           8 :                 auto ptr = values + nBatchSize * NUM_ITEMS_PROJ_BBOX;
    2678           8 :                 ptr[0] = dfMinXBeforeReproj;
    2679           8 :                 ptr[1] = dfMinYBeforeReproj;
    2680           8 :                 ptr[2] = dfMaxXBeforeReproj;
    2681           8 :                 ptr[3] = dfMaxYBeforeReproj;
    2682             :             }
    2683             : 
    2684             :             // Write proj:shape
    2685             :             {
    2686           8 :                 int32_t *values = static_cast<int32_t *>(const_cast<void *>(
    2687           8 :                     projShapeItems->buffers[ARROW_BUF_DATA]));
    2688           8 :                 auto ptr = values + nBatchSize * NUM_ITEMS_PROJ_SHAPE;
    2689           8 :                 ptr[0] = poSrcDS->GetRasterYSize();
    2690           8 :                 ptr[1] = poSrcDS->GetRasterXSize();
    2691             :             }
    2692             : 
    2693             :             // Write proj:transform
    2694             :             {
    2695           8 :                 double *values = static_cast<double *>(const_cast<void *>(
    2696           8 :                     projTransformItems->buffers[ARROW_BUF_DATA]));
    2697           8 :                 auto ptr = values + nBatchSize * NUM_ITEMS_PROJ_TRANSFORM;
    2698           8 :                 ptr[0] = gt.xscale;
    2699           8 :                 ptr[1] = gt.xrot;
    2700           8 :                 ptr[2] = gt.xorig;
    2701           8 :                 ptr[3] = gt.yrot;
    2702           8 :                 ptr[4] = gt.yscale;
    2703           8 :                 ptr[5] = gt.yorig;
    2704           8 :                 ptr[6] = 0;
    2705           8 :                 ptr[7] = 0;
    2706           8 :                 ptr[8] = 1;
    2707             :             }
    2708             : 
    2709             :             // Write geometry
    2710             :             {
    2711           8 :                 const size_t nWKBSize = poPoly->WkbSize();
    2712           8 :                 void *ptr = arrayHelper->GetPtrForStringOrBinary(
    2713             :                     iWkbArray, nBatchSize, nWKBSize, false);
    2714           8 :                 if (!ptr)
    2715           0 :                     return nullptr;
    2716           8 :                 OGRwkbExportOptions sExportOptions;
    2717           8 :                 sExportOptions.eWkbVariant = wkbVariantIso;
    2718           8 :                 if (poPoly->exportToWkb(static_cast<unsigned char *>(ptr),
    2719           8 :                                         &sExportOptions) != OGRERR_NONE)
    2720           0 :                     return nullptr;
    2721             :             }
    2722             : 
    2723           8 :             nBatchSize++;
    2724           8 :             if (nBatchSize == nMaxBatchSize && !FlushArrays())
    2725             :             {
    2726           0 :                 return nullptr;
    2727             :             }
    2728             :         }
    2729             :         else
    2730             :         {
    2731          64 :             auto poFeature = std::make_unique<OGRFeature>(poLayerDefn);
    2732          64 :             poFeature->SetField(ti_field, osFileNameToWrite.c_str());
    2733             : 
    2734          64 :             if (i_SrcSRSName >= 0 && poSrcSRS)
    2735             :             {
    2736             :                 const char *pszAuthorityCode =
    2737          11 :                     poSrcSRS->GetAuthorityCode(nullptr);
    2738             :                 const char *pszAuthorityName =
    2739          11 :                     poSrcSRS->GetAuthorityName(nullptr);
    2740          11 :                 if (psOptions->eSrcSRSFormat == FORMAT_AUTO)
    2741             :                 {
    2742           5 :                     if (pszAuthorityName != nullptr &&
    2743             :                         pszAuthorityCode != nullptr)
    2744             :                     {
    2745           5 :                         poFeature->SetField(
    2746             :                             i_SrcSRSName, CPLSPrintf("%s:%s", pszAuthorityName,
    2747             :                                                      pszAuthorityCode));
    2748             :                     }
    2749           0 :                     else if (nMaxFieldSize == 0 ||
    2750           0 :                              strlen(poSrcDS->GetProjectionRef()) <=
    2751           0 :                                  static_cast<size_t>(nMaxFieldSize))
    2752             :                     {
    2753           0 :                         poFeature->SetField(i_SrcSRSName,
    2754             :                                             poSrcDS->GetProjectionRef());
    2755             :                     }
    2756             :                     else
    2757             :                     {
    2758           0 :                         char *pszProj4 = nullptr;
    2759           0 :                         if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
    2760             :                         {
    2761           0 :                             poFeature->SetField(i_SrcSRSName, pszProj4);
    2762             :                         }
    2763             :                         else
    2764             :                         {
    2765           0 :                             poFeature->SetField(i_SrcSRSName,
    2766             :                                                 poSrcDS->GetProjectionRef());
    2767             :                         }
    2768           0 :                         CPLFree(pszProj4);
    2769             :                     }
    2770             :                 }
    2771           6 :                 else if (psOptions->eSrcSRSFormat == FORMAT_WKT)
    2772             :                 {
    2773           4 :                     if (nMaxFieldSize == 0 ||
    2774           2 :                         strlen(poSrcDS->GetProjectionRef()) <=
    2775           2 :                             static_cast<size_t>(nMaxFieldSize))
    2776             :                     {
    2777           0 :                         poFeature->SetField(i_SrcSRSName,
    2778             :                                             poSrcDS->GetProjectionRef());
    2779             :                     }
    2780             :                     else
    2781             :                     {
    2782           2 :                         CPLError(
    2783             :                             CE_Warning, CPLE_AppDefined,
    2784             :                             "Cannot write WKT for file %s as it is too long!",
    2785             :                             osFileNameToWrite.c_str());
    2786             :                     }
    2787             :                 }
    2788           4 :                 else if (psOptions->eSrcSRSFormat == FORMAT_PROJ)
    2789             :                 {
    2790           2 :                     char *pszProj4 = nullptr;
    2791           2 :                     if (poSrcSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
    2792             :                     {
    2793           2 :                         poFeature->SetField(i_SrcSRSName, pszProj4);
    2794             :                     }
    2795           2 :                     CPLFree(pszProj4);
    2796             :                 }
    2797           2 :                 else if (psOptions->eSrcSRSFormat == FORMAT_EPSG)
    2798             :                 {
    2799           2 :                     if (pszAuthorityName != nullptr &&
    2800             :                         pszAuthorityCode != nullptr)
    2801           2 :                         poFeature->SetField(
    2802             :                             i_SrcSRSName, CPLSPrintf("%s:%s", pszAuthorityName,
    2803             :                                                      pszAuthorityCode));
    2804             :                 }
    2805             :             }
    2806             : 
    2807          69 :             for (const auto &oFetchMD : psOptions->aoFetchMD)
    2808             :             {
    2809           5 :                 if (EQUAL(oFetchMD.osRasterItemName.c_str(), "{PIXEL_SIZE}"))
    2810             :                 {
    2811           1 :                     poFeature->SetField(oFetchMD.osFieldName.c_str(), dfRes);
    2812           1 :                     continue;
    2813             :                 }
    2814             : 
    2815             :                 const char *pszMD =
    2816           4 :                     poSrcDS->GetMetadataItem(oFetchMD.osRasterItemName.c_str());
    2817           4 :                 if (pszMD)
    2818             :                 {
    2819           4 :                     if (EQUAL(oFetchMD.osRasterItemName.c_str(),
    2820             :                               "TIFFTAG_DATETIME"))
    2821             :                     {
    2822             :                         int nYear, nMonth, nDay, nHour, nMin, nSec;
    2823           2 :                         if (sscanf(pszMD, "%04d:%02d:%02d %02d:%02d:%02d",
    2824             :                                    &nYear, &nMonth, &nDay, &nHour, &nMin,
    2825           1 :                                    &nSec) == 6)
    2826             :                         {
    2827           1 :                             poFeature->SetField(
    2828             :                                 oFetchMD.osFieldName.c_str(),
    2829             :                                 CPLSPrintf("%04d/%02d/%02d %02d:%02d:%02d",
    2830             :                                            nYear, nMonth, nDay, nHour, nMin,
    2831             :                                            nSec));
    2832           1 :                             continue;
    2833             :                         }
    2834             :                     }
    2835           3 :                     poFeature->SetField(oFetchMD.osFieldName.c_str(), pszMD);
    2836             :                 }
    2837             :             }
    2838             : 
    2839          64 :             poFeature->SetGeometryDirectly(poPoly.release());
    2840             : 
    2841          64 :             if (poLayer->CreateFeature(poFeature.get()) != OGRERR_NONE)
    2842             :             {
    2843           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2844             :                          "Failed to create feature in tile index.");
    2845           0 :                 return nullptr;
    2846             :             }
    2847             :         }
    2848             : 
    2849          72 :         ++iCur;
    2850          87 :         if (psOptions->pfnProgress &&
    2851          30 :             !psOptions->pfnProgress(static_cast<double>(iCur) / nTotal, "",
    2852          15 :                                     psOptions->pProgressData))
    2853             :         {
    2854           0 :             return nullptr;
    2855             :         }
    2856          72 :         if (iCur >= nSrcCount)
    2857          48 :             ++nTotal;
    2858          88 :     }
    2859          52 :     if (psOptions->pfnProgress)
    2860           7 :         psOptions->pfnProgress(1.0, "", psOptions->pProgressData);
    2861             : 
    2862          52 :     if (nBandInterleavedCount > 0 &&
    2863             :         nBandInterleavedCount > nPixelInterleavedCount)
    2864             :     {
    2865           3 :         if (psRoot)
    2866           0 :             CPLCreateXMLElementAndValue(psRoot.get(), "Interleave", "Band");
    2867             :         else
    2868           3 :             poLayer->SetMetadataItem("INTERLEAVE", "BAND");
    2869             :     }
    2870          49 :     else if (nPixelInterleavedCount > 0)
    2871             :     {
    2872           0 :         if (psRoot)
    2873           0 :             CPLCreateXMLElementAndValue(psRoot.get(), "Interleave", "Pixel");
    2874             :         else
    2875           0 :             poLayer->SetMetadataItem("INTERLEAVE", "PIXEL");
    2876             :     }
    2877             : 
    2878          52 :     if (!psOptions->osGTIFilename.empty())
    2879             :     {
    2880           2 :         if (!CPLSerializeXMLTreeToFile(psRoot.get(),
    2881           2 :                                        psOptions->osGTIFilename.c_str()))
    2882           0 :             return nullptr;
    2883             :     }
    2884             : 
    2885          52 :     if (bIsSTACGeoParquet && nBatchSize != 0 && !FlushArrays())
    2886             :     {
    2887           0 :         return nullptr;
    2888             :     }
    2889             : 
    2890          52 :     if (poTileIndexDSUnique)
    2891          28 :         return GDALDataset::ToHandle(poTileIndexDSUnique.release());
    2892             :     else
    2893          24 :         return GDALDataset::ToHandle(poTileIndexDS);
    2894             : }
    2895             : 
    2896             : /************************************************************************/
    2897             : /*                             SanitizeSRS                              */
    2898             : /************************************************************************/
    2899             : 
    2900          18 : static char *SanitizeSRS(const char *pszUserInput)
    2901             : 
    2902             : {
    2903             :     OGRSpatialReferenceH hSRS;
    2904          18 :     char *pszResult = nullptr;
    2905             : 
    2906          18 :     CPLErrorReset();
    2907             : 
    2908          18 :     hSRS = OSRNewSpatialReference(nullptr);
    2909          18 :     if (OSRSetFromUserInput(hSRS, pszUserInput) == OGRERR_NONE)
    2910          18 :         OSRExportToWkt(hSRS, &pszResult);
    2911             :     else
    2912             :     {
    2913           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Translating SRS failed:\n%s",
    2914             :                  pszUserInput);
    2915             :     }
    2916             : 
    2917          18 :     OSRDestroySpatialReference(hSRS);
    2918             : 
    2919          18 :     return pszResult;
    2920             : }
    2921             : 
    2922             : /************************************************************************/
    2923             : /*                      GDALTileIndexOptionsNew()                       */
    2924             : /************************************************************************/
    2925             : 
    2926             : /**
    2927             :  * Allocates a GDALTileIndexOptions struct.
    2928             :  *
    2929             :  * @param papszArgv NULL terminated list of options (potentially including
    2930             :  * filename and open options too), or NULL. The accepted options are the ones of
    2931             :  * the <a href="/programs/gdaltindex.html">gdaltindex</a> utility.
    2932             :  * @param psOptionsForBinary (output) may be NULL (and should generally be
    2933             :  * NULL), otherwise (gdaltindex_bin.cpp use case) must be allocated with
    2934             :  * GDALTileIndexOptionsForBinaryNew() prior to this function. Will be filled
    2935             :  * with potentially present filename, open options,...
    2936             :  * @return pointer to the allocated GDALTileIndexOptions struct. Must be freed
    2937             :  * with GDALTileIndexOptionsFree().
    2938             :  *
    2939             :  * @since GDAL 3.9
    2940             :  */
    2941             : 
    2942             : GDALTileIndexOptions *
    2943          54 : GDALTileIndexOptionsNew(char **papszArgv,
    2944             :                         GDALTileIndexOptionsForBinary *psOptionsForBinary)
    2945             : {
    2946         108 :     auto psOptions = std::make_unique<GDALTileIndexOptions>();
    2947             : 
    2948             :     /* -------------------------------------------------------------------- */
    2949             :     /*      Parse arguments.                                                */
    2950             :     /* -------------------------------------------------------------------- */
    2951             : 
    2952         108 :     CPLStringList aosArgv;
    2953             : 
    2954          54 :     if (papszArgv)
    2955             :     {
    2956          54 :         const int nArgc = CSLCount(papszArgv);
    2957         476 :         for (int i = 0; i < nArgc; i++)
    2958             :         {
    2959         422 :             aosArgv.AddString(papszArgv[i]);
    2960             :         }
    2961             :     }
    2962             : 
    2963             :     try
    2964             :     {
    2965             :         auto argParser = GDALTileIndexAppOptionsGetParser(psOptions.get(),
    2966          54 :                                                           psOptionsForBinary);
    2967          54 :         argParser->parse_args_without_binary_name(aosArgv.List());
    2968             : 
    2969             :         // Check all no store_into args
    2970          57 :         if (auto oTr = argParser->present<std::vector<double>>("-tr"))
    2971             :         {
    2972           3 :             psOptions->xres = (*oTr)[0];
    2973           3 :             psOptions->yres = (*oTr)[1];
    2974             :         }
    2975             : 
    2976          57 :         if (auto oTargetExtent = argParser->present<std::vector<double>>("-te"))
    2977             :         {
    2978           3 :             psOptions->xmin = (*oTargetExtent)[0];
    2979           3 :             psOptions->ymin = (*oTargetExtent)[1];
    2980           3 :             psOptions->xmax = (*oTargetExtent)[2];
    2981           3 :             psOptions->ymax = (*oTargetExtent)[3];
    2982             :         }
    2983             : 
    2984          54 :         if (auto fetchMd =
    2985          54 :                 argParser->present<std::vector<std::string>>("-fetch_md"))
    2986             :         {
    2987             : 
    2988           3 :             CPLAssert(fetchMd->size() % 3 == 0);
    2989             : 
    2990             :             // Loop
    2991           8 :             for (size_t i = 0; i < fetchMd->size(); i += 3)
    2992             :             {
    2993             :                 OGRFieldType type;
    2994           5 :                 const auto &typeName{fetchMd->at(i + 2)};
    2995           5 :                 if (typeName == "String")
    2996             :                 {
    2997           3 :                     type = OFTString;
    2998             :                 }
    2999           2 :                 else if (typeName == "Integer")
    3000             :                 {
    3001           0 :                     type = OFTInteger;
    3002             :                 }
    3003           2 :                 else if (typeName == "Integer64")
    3004             :                 {
    3005           0 :                     type = OFTInteger64;
    3006             :                 }
    3007           2 :                 else if (typeName == "Real")
    3008             :                 {
    3009           1 :                     type = OFTReal;
    3010             :                 }
    3011           1 :                 else if (typeName == "Date")
    3012             :                 {
    3013           0 :                     type = OFTDate;
    3014             :                 }
    3015           1 :                 else if (typeName == "DateTime")
    3016             :                 {
    3017           1 :                     type = OFTDateTime;
    3018             :                 }
    3019             :                 else
    3020             :                 {
    3021           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    3022             :                              "-fetch_md requires a valid type name as third "
    3023             :                              "argument: %s was given.",
    3024           0 :                              fetchMd->at(i).c_str());
    3025           0 :                     return nullptr;
    3026             :                 }
    3027             : 
    3028           5 :                 const GDALTileIndexRasterMetadata oMD{type, fetchMd->at(i + 1),
    3029          15 :                                                       fetchMd->at(i)};
    3030           5 :                 psOptions->aoFetchMD.push_back(std::move(oMD));
    3031             :             }
    3032             :         }
    3033             : 
    3034             :         // Check -t_srs
    3035          54 :         if (!psOptions->osTargetSRS.empty())
    3036             :         {
    3037          18 :             auto sanitized{SanitizeSRS(psOptions->osTargetSRS.c_str())};
    3038          18 :             if (sanitized)
    3039             :             {
    3040          18 :                 psOptions->osTargetSRS = sanitized;
    3041          18 :                 CPLFree(sanitized);
    3042             :             }
    3043             :             else
    3044             :             {
    3045             :                 // Error was already reported by SanitizeSRS, just return nullptr
    3046           0 :                 psOptions->osTargetSRS.clear();
    3047           0 :                 return nullptr;
    3048             :             }
    3049             :         }
    3050             :     }
    3051           0 :     catch (const std::exception &error)
    3052             :     {
    3053           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
    3054           0 :         return nullptr;
    3055             :     }
    3056             : 
    3057          54 :     return psOptions.release();
    3058             : }
    3059             : 
    3060             : /************************************************************************/
    3061             : /*                      GDALTileIndexOptionsFree()                      */
    3062             : /************************************************************************/
    3063             : 
    3064             : /**
    3065             :  * Frees the GDALTileIndexOptions struct.
    3066             :  *
    3067             :  * @param psOptions the options struct for GDALTileIndex().
    3068             :  *
    3069             :  * @since GDAL 3.9
    3070             :  */
    3071             : 
    3072          54 : void GDALTileIndexOptionsFree(GDALTileIndexOptions *psOptions)
    3073             : {
    3074          54 :     delete psOptions;
    3075          54 : }
    3076             : 
    3077             : /************************************************************************/
    3078             : /*                  GDALTileIndexOptionsSetProgress()                   */
    3079             : /************************************************************************/
    3080             : 
    3081             : /**
    3082             :  * Set a progress function.
    3083             :  *
    3084             :  * @param psOptions the options struct for GDALTileIndex().
    3085             :  * @param pfnProgress the progress callback.
    3086             :  * @param pProgressData the user data for the progress callback.
    3087             :  *
    3088             :  * @since GDAL 3.11
    3089             :  */
    3090             : 
    3091          31 : void GDALTileIndexOptionsSetProgress(GDALTileIndexOptions *psOptions,
    3092             :                                      GDALProgressFunc pfnProgress,
    3093             :                                      void *pProgressData)
    3094             : {
    3095          31 :     psOptions->pfnProgress = pfnProgress;
    3096          31 :     psOptions->pProgressData = pProgressData;
    3097          31 : }
    3098             : 
    3099             : #undef CHECK_HAS_ENOUGH_ADDITIONAL_ARGS

Generated by: LCOV version 1.14