LCOV - code coverage report
Current view: top level - frmts/gti - gdaltileindexdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2433 2680 90.8 %
Date: 2026-06-11 20:55:18 Functions: 72 72 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Virtual GDAL Datasets
       4             :  * Purpose:  Tile index based VRT
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2023, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : /*! @cond Doxygen_Suppress */
      14             : 
      15             : #include <array>
      16             : #include <algorithm>
      17             : #include <atomic>
      18             : #include <cmath>
      19             : #include <limits>
      20             : #include <mutex>
      21             : #include <set>
      22             : #include <tuple>
      23             : #include <utility>
      24             : #include <vector>
      25             : 
      26             : #include "cpl_port.h"
      27             : #include "cpl_error_internal.h"
      28             : #include "cpl_json.h"
      29             : #include "cpl_mem_cache.h"
      30             : #include "cpl_minixml.h"
      31             : #include "cpl_quad_tree.h"
      32             : #include "cpl_worker_thread_pool.h"
      33             : #include "vrtdataset.h"
      34             : #include "vrt_priv.h"
      35             : #include "ogrsf_frmts.h"
      36             : #include "ogrwarpedlayer.h"
      37             : #include "gdal_frmts.h"
      38             : #include "gdal_proxy.h"
      39             : #include "gdalsubdatasetinfo.h"
      40             : #include "gdal_thread_pool.h"
      41             : #include "gdal_utils.h"
      42             : 
      43             : #include "gdalalg_raster_index.h"
      44             : 
      45             : #ifdef USE_NEON_OPTIMIZATIONS
      46             : #define USE_SSE2_OPTIM
      47             : #define USE_SSE41_OPTIM
      48             : #include "include_sse2neon.h"
      49             : #elif defined(__SSE2__) || defined(_M_X64)
      50             : #define USE_SSE2_OPTIM
      51             : #include <emmintrin.h>
      52             : // MSVC doesn't define __SSE4_1__, but if -arch:AVX2 is enabled, we do have SSE4.1
      53             : #if defined(__SSE4_1__) || defined(__AVX2__)
      54             : #define USE_SSE41_OPTIM
      55             : #include <smmintrin.h>
      56             : #endif
      57             : #endif
      58             : 
      59             : #ifndef _
      60             : #define _(x) (x)
      61             : #endif
      62             : 
      63             : constexpr const char *GTI_PREFIX = "GTI:";
      64             : 
      65             : constexpr const char *MD_DS_TILE_INDEX_LAYER = "TILE_INDEX_LAYER";
      66             : constexpr const char *MD_DS_TILE_INDEX_SQL = "TILE_INDEX_SQL";
      67             : constexpr const char *MD_DS_TILE_INDEX_SPATIAL_SQL = "TILE_INDEX_SPATIAL_SQL";
      68             : 
      69             : constexpr const char *MD_RESX = "RESX";
      70             : constexpr const char *MD_RESY = "RESY";
      71             : constexpr const char *MD_BAND_COUNT = "BAND_COUNT";
      72             : constexpr const char *MD_DATA_TYPE = "DATA_TYPE";
      73             : constexpr const char *MD_NODATA = "NODATA";
      74             : constexpr const char *MD_MINX = "MINX";
      75             : constexpr const char *MD_MINY = "MINY";
      76             : constexpr const char *MD_MAXX = "MAXX";
      77             : constexpr const char *MD_MAXY = "MAXY";
      78             : constexpr const char *MD_GEOTRANSFORM = "GEOTRANSFORM";
      79             : constexpr const char *MD_XSIZE = "XSIZE";
      80             : constexpr const char *MD_YSIZE = "YSIZE";
      81             : constexpr const char *MD_COLOR_INTERPRETATION = "COLOR_INTERPRETATION";
      82             : constexpr const char *MD_SRS = "SRS";
      83             : constexpr const char *MD_SRS_BEHAVIOR = "SRS_BEHAVIOR";
      84             : constexpr const char *MD_LOCATION_FIELD = "LOCATION_FIELD";
      85             : constexpr const char *MD_SORT_FIELD = "SORT_FIELD";
      86             : constexpr const char *MD_SORT_FIELD_ASC = "SORT_FIELD_ASC";
      87             : constexpr const char *MD_BLOCK_X_SIZE = "BLOCKXSIZE";
      88             : constexpr const char *MD_BLOCK_Y_SIZE = "BLOCKYSIZE";
      89             : constexpr const char *MD_MASK_BAND = "MASK_BAND";
      90             : constexpr const char *MD_RESAMPLING = "RESAMPLING";
      91             : constexpr const char *MD_INTERLEAVE = GDALMD_INTERLEAVE;
      92             : 
      93             : constexpr const char *const apszTIOptions[] = {MD_RESX,
      94             :                                                MD_RESY,
      95             :                                                MD_BAND_COUNT,
      96             :                                                MD_DATA_TYPE,
      97             :                                                MD_NODATA,
      98             :                                                MD_MINX,
      99             :                                                MD_MINY,
     100             :                                                MD_MAXX,
     101             :                                                MD_MAXY,
     102             :                                                MD_GEOTRANSFORM,
     103             :                                                MD_XSIZE,
     104             :                                                MD_YSIZE,
     105             :                                                MD_COLOR_INTERPRETATION,
     106             :                                                MD_SRS,
     107             :                                                MD_SRS_BEHAVIOR,
     108             :                                                MD_LOCATION_FIELD,
     109             :                                                MD_SORT_FIELD,
     110             :                                                MD_SORT_FIELD_ASC,
     111             :                                                MD_BLOCK_X_SIZE,
     112             :                                                MD_BLOCK_Y_SIZE,
     113             :                                                MD_MASK_BAND,
     114             :                                                MD_RESAMPLING,
     115             :                                                MD_INTERLEAVE};
     116             : 
     117             : constexpr const char *const MD_BAND_OFFSET = "OFFSET";
     118             : constexpr const char *const MD_BAND_SCALE = "SCALE";
     119             : constexpr const char *const MD_BAND_UNITTYPE = "UNITTYPE";
     120             : constexpr const char *const apszReservedBandItems[] = {
     121             :     MD_BAND_OFFSET, MD_BAND_SCALE, MD_BAND_UNITTYPE};
     122             : 
     123             : constexpr const char *GTI_XML_BANDCOUNT = "BandCount";
     124             : constexpr const char *GTI_XML_DATATYPE = "DataType";
     125             : constexpr const char *GTI_XML_NODATAVALUE = "NoDataValue";
     126             : constexpr const char *GTI_XML_COLORINTERP = "ColorInterp";
     127             : constexpr const char *GTI_XML_LOCATIONFIELD = "LocationField";
     128             : constexpr const char *GTI_XML_SORTFIELD = "SortField";
     129             : constexpr const char *GTI_XML_SORTFIELDASC = "SortFieldAsc";
     130             : constexpr const char *GTI_XML_MASKBAND = "MaskBand";
     131             : constexpr const char *GTI_XML_OVERVIEW_ELEMENT = "Overview";
     132             : constexpr const char *GTI_XML_OVERVIEW_DATASET = "Dataset";
     133             : constexpr const char *GTI_XML_OVERVIEW_LAYER = "Layer";
     134             : constexpr const char *GTI_XML_OVERVIEW_FACTOR = "Factor";
     135             : 
     136             : constexpr const char *GTI_XML_BAND_ELEMENT = "Band";
     137             : constexpr const char *GTI_XML_BAND_NUMBER = "band";
     138             : constexpr const char *GTI_XML_BAND_DATATYPE = "dataType";
     139             : constexpr const char *GTI_XML_BAND_DESCRIPTION = "Description";
     140             : constexpr const char *GTI_XML_BAND_OFFSET = "Offset";
     141             : constexpr const char *GTI_XML_BAND_SCALE = "Scale";
     142             : constexpr const char *GTI_XML_BAND_NODATAVALUE = "NoDataValue";
     143             : constexpr const char *GTI_XML_BAND_UNITTYPE = "UnitType";
     144             : constexpr const char *GTI_XML_BAND_COLORINTERP = "ColorInterp";
     145             : constexpr const char *GTI_XML_CATEGORYNAMES = "CategoryNames";
     146             : constexpr const char *GTI_XML_COLORTABLE = "ColorTable";
     147             : constexpr const char *GTI_XML_RAT = "GDALRasterAttributeTable";
     148             : 
     149             : /************************************************************************/
     150             : /*                            ENDS_WITH_CI()                            */
     151             : /************************************************************************/
     152             : 
     153       68735 : static inline bool ENDS_WITH_CI(const char *a, const char *b)
     154             : {
     155       68735 :     return strlen(a) >= strlen(b) && EQUAL(a + strlen(a) - strlen(b), b);
     156             : }
     157             : 
     158             : /************************************************************************/
     159             : /*                          GTISharedSourceKey                          */
     160             : /************************************************************************/
     161             : 
     162             : struct GTISharedSourceKey
     163             : {
     164             :     std::string osTileName{};
     165             :     std::vector<int> anBands{};
     166             : 
     167         392 :     bool operator==(const GTISharedSourceKey &other) const
     168             :     {
     169         392 :         return osTileName == other.osTileName && anBands == other.anBands;
     170             :     }
     171             : 
     172             :     CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW
     173        1585 :     size_t getHash() const noexcept
     174             :     {
     175        1585 :         size_t h = std::hash<std::string>{}(osTileName);
     176        1632 :         for (int b : anBands)
     177             :         {
     178             :             // Cf https://www.boost.org/doc/libs/1_36_0/doc/html/hash/reference.html#boost.hash_combine
     179          47 :             h ^= std::hash<int>{}(b) + 0x9e3779b9 + (h << 6) + (h >> 2);
     180             :         }
     181        1585 :         return h;
     182             :     }
     183             : };
     184             : 
     185             : namespace std
     186             : {
     187             : template <> struct hash<GTISharedSourceKey>
     188             : {
     189        1585 :     size_t operator()(const GTISharedSourceKey &val) const noexcept
     190             :     {
     191        1585 :         return val.getHash();
     192             :     }
     193             : };
     194             : }  // namespace std
     195             : 
     196             : /************************************************************************/
     197             : /*                         GDALTileIndexDataset                         */
     198             : /************************************************************************/
     199             : 
     200             : class GDALTileIndexBand;
     201             : 
     202             : class GDALTileIndexDataset final : public GDALPamDataset
     203             : {
     204             :   public:
     205             :     GDALTileIndexDataset();
     206             :     ~GDALTileIndexDataset() override;
     207             : 
     208             :     bool Open(GDALOpenInfo *poOpenInfo);
     209             : 
     210             :     CPLErr FlushCache(bool bAtClosing) override;
     211             : 
     212             :     CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
     213             :     const OGRSpatialReference *GetSpatialRef() const override;
     214             : 
     215             :     CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
     216             :                      int nYSize, void *pData, int nBufXSize, int nBufYSize,
     217             :                      GDALDataType eBufType, int nBandCount,
     218             :                      BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
     219             :                      GSpacing nLineSpace, GSpacing nBandSpace,
     220             :                      GDALRasterIOExtraArg *psExtraArg) override;
     221             : 
     222             :     const char *GetMetadataItem(const char *pszName,
     223             :                                 const char *pszDomain) override;
     224             :     CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
     225             :                            const char *pszDomain) override;
     226             :     CPLErr SetMetadata(CSLConstList papszMD, const char *pszDomain) override;
     227             : 
     228             :     void LoadOverviews();
     229             : 
     230             :     std::vector<GTISourceDesc> GetSourcesMoreRecentThan(int64_t mTime);
     231             : 
     232             :   private:
     233             :     friend class GDALTileIndexBand;
     234             : 
     235             :     /** Directory where the main (.xml / .gti.gpkg) file is located.
     236             :      * Used for relative filenames resolution.
     237             :      */
     238             :     std::string m_osBaseDir{};
     239             : 
     240             :     //! Optional GTI XML
     241             :     CPLXMLTreeCloser m_psXMLTree{nullptr};
     242             : 
     243             :     //! Whether the GTI XML might be modified (by SetMetadata/SetMetadataItem)
     244             :     bool m_bXMLUpdatable = false;
     245             : 
     246             :     //! Whether the GTI XML has been modified (by SetMetadata/SetMetadataItem)
     247             :     bool m_bXMLModified = false;
     248             : 
     249             :     //! Allowed raster drivers
     250             :     CPLStringList m_aosAllowedRasterDrivers{};
     251             : 
     252             :     //! Unique string (without the process) for this tile index. Passed to
     253             :     //! GDALProxyPoolDataset to ensure that sources are unique for a given owner
     254             :     const std::string m_osUniqueHandle;
     255             : 
     256             :     //! Vector dataset with the sources
     257             :     std::unique_ptr<GDALDataset> m_poVectorDS{};
     258             : 
     259             :     //! Generic SQL request to return features. May be empty.
     260             :     std::string m_osSQL{};
     261             : 
     262             :     //! SQL request to return features with placeholders for spatial filtering. May be empty
     263             :     std::string m_osSpatialSQL{};
     264             : 
     265             :     //! Vector layer with the sources
     266             :     OGRLayer *m_poLayer = nullptr;
     267             : 
     268             :     //! Non-null when m_poLayer was created with ExecuteSQL() and must be freed
     269             :     //! with m_poVectorDS->ReleaseResultSet()
     270             :     OGRLayer *m_poLayerToRelease = nullptr;
     271             : 
     272             :     //! When the SRS of m_poLayer is not the one we expose
     273             :     std::unique_ptr<OGRWarpedLayer> m_poWarpedLayerKeeper{};
     274             : 
     275             :     //! Geotransform matrix of the tile index
     276             :     GDALGeoTransform m_gt{};
     277             : 
     278             :     //! Index of the "location" (or alternate name given by user) field
     279             :     //! (within m_poLayer->GetLayerDefn()), that contain source dataset names.
     280             :     int m_nLocationFieldIndex = -1;
     281             : 
     282             :     //! SRS of the tile index.
     283             :     OGRSpatialReference m_oSRS{};
     284             : 
     285             :     struct SharedDataset
     286             :     {
     287             :         //! Source dataset (possibly warped).
     288             :         std::shared_ptr<GDALDataset> poDS{};
     289             : 
     290             :         //! Source dataset, raw/unwarped
     291             :         GDALDataset *poUnreprojectedDS = nullptr;
     292             : 
     293             :         //! Whether (nBandCount, panBandMap) is taken into account by poDS
     294             :         bool bBandMapTakenIntoAccount = false;
     295             :     };
     296             : 
     297             :     //! Cache from dataset name to dataset handle.
     298             :     //! Note that the dataset objects are ultimately GDALProxyPoolDataset,
     299             :     //! and that the GDALProxyPoolDataset limits the number of simultaneously
     300             :     //! opened real datasets (controlled by GDAL_MAX_DATASET_POOL_SIZE). Hence 500 is not too big.
     301             :     lru11::Cache<GTISharedSourceKey, std::shared_ptr<SharedDataset>>
     302             :         m_oMapSharedSources{500};
     303             : 
     304             :     //! Mask band (e.g. for JPEG compressed + mask band)
     305             :     std::unique_ptr<GDALTileIndexBand> m_poMaskBand{};
     306             : 
     307             :     //! Whether all bands of the tile index have the same data type.
     308             :     bool m_bSameDataType = true;
     309             : 
     310             :     //! Whether all bands of the tile index have the same nodata value.
     311             :     bool m_bSameNoData = true;
     312             : 
     313             :     //! Whether a band interleave must be exposed.
     314             :     bool m_bBandInterleave = false;
     315             : 
     316             :     //! Minimum X of the current pixel request, in georeferenced units.
     317             :     double m_dfLastMinXFilter = std::numeric_limits<double>::quiet_NaN();
     318             : 
     319             :     //! Minimum Y of the current pixel request, in georeferenced units.
     320             :     double m_dfLastMinYFilter = std::numeric_limits<double>::quiet_NaN();
     321             : 
     322             :     //! Maximum X of the current pixel request, in georeferenced units.
     323             :     double m_dfLastMaxXFilter = std::numeric_limits<double>::quiet_NaN();
     324             : 
     325             :     //! Maximum Y of the current pixel request, in georeferenced units.
     326             :     double m_dfLastMaxYFilter = std::numeric_limits<double>::quiet_NaN();
     327             : 
     328             :     //! Bands for which m_aoSourceDesc is valid (only if m_bBandInterleave)
     329             :     std::vector<int> m_anLastBands{};
     330             : 
     331             :     //! Index of the field (within m_poLayer->GetLayerDefn()) used to sort, or -1 if none.
     332             :     int m_nSortFieldIndex = -1;
     333             : 
     334             :     //! Whether sorting must be ascending (true) or descending (false).
     335             :     bool m_bSortFieldAsc = true;
     336             : 
     337             :     //! Resampling method by default for warping or when a source has not
     338             :     //! the same resolution as the tile index.
     339             :     std::string m_osResampling = "near";
     340             :     GDALRIOResampleAlg m_eResampling = GRIORA_NearestNeighbour;
     341             : 
     342             :     //! WKT2 representation of the tile index SRS (if needed, typically for on-the-fly warping).
     343             :     std::string m_osWKT{};
     344             : 
     345             :     //! Whether we had to open of the sources at tile index opening.
     346             :     bool m_bScannedOneFeatureAtOpening = false;
     347             : 
     348             :     //! Array of overview descriptors.
     349             :     //! Each descriptor is a tuple (dataset_name, concatenated_open_options, layer_name, overview_factor).
     350             :     std::vector<std::tuple<std::string, CPLStringList, std::string, double>>
     351             :         m_aoOverviewDescriptor{};
     352             : 
     353             :     //! Array of overview datasets.
     354             :     std::vector<std::unique_ptr<GDALDataset, GDALDatasetUniquePtrReleaser>>
     355             :         m_apoOverviews{};
     356             : 
     357             :     //! Cache of buffers used by VRTComplexSource to avoid memory reallocation.
     358             :     VRTSource::WorkingState m_oWorkingState{};
     359             : 
     360             :     //! Used by IRasterIO() when using multi-threading
     361             :     struct QueueWorkingStates
     362             :     {
     363             :         std::mutex oMutex{};
     364             :         std::vector<std::unique_ptr<VRTSource::WorkingState>> oStates{};
     365             :     };
     366             : 
     367             :     //! Used by IRasterIO() when using multi-threading
     368             :     QueueWorkingStates m_oQueueWorkingStates{};
     369             : 
     370             :     //! Structure describing one of the source raster in the tile index.
     371             :     struct SourceDesc
     372             :     {
     373             :         //! Source dataset name.
     374             :         std::string osName{};
     375             : 
     376             :         //! Source dataset (possibly warped).
     377             :         std::shared_ptr<GDALDataset> poDS{};
     378             : 
     379             :         //! Source dataset, raw/unwarped
     380             :         GDALDataset *poUnreprojectedDS = nullptr;
     381             : 
     382             :         //! Whether (nBandCount, panBandMap) is taken into account by poDS
     383             :         bool bBandMapTakenIntoAccount = false;
     384             : 
     385             :         //! VRTSimpleSource or VRTComplexSource for the source.
     386             :         std::unique_ptr<VRTSimpleSource> poSource{};
     387             : 
     388             :         //! OGRFeature corresponding to the source in the tile index.
     389             :         std::unique_ptr<OGRFeature> poFeature{};
     390             : 
     391             :         //! Work buffer containing the value of the mask band for the current pixel query.
     392             :         mutable std::vector<GByte> abyMask{};
     393             : 
     394             :         //! Whether the source covers the whole area of interest of the current pixel query.
     395             :         bool bCoversWholeAOI = false;
     396             : 
     397             :         //! Whether the source has a nodata value at least in one of its band.
     398             :         bool bHasNoData = false;
     399             : 
     400             :         //! Whether all bands of the source have the same nodata value.
     401             :         bool bSameNoData = false;
     402             : 
     403             :         //! Nodata value of all bands (when bSameNoData == true).
     404             :         double dfSameNoData = 0;
     405             : 
     406             :         //! Mask band of the source.
     407             :         GDALRasterBand *poMaskBand = nullptr;
     408             :     };
     409             : 
     410             :     //! Array of sources participating to the current pixel query.
     411             :     std::vector<SourceDesc> m_aoSourceDesc{};
     412             : 
     413             :     //! Maximum number of threads. Updated by CollectSources().
     414             :     int m_nNumThreads = -1;
     415             : 
     416             :     //! Whereas the multi-threading rendering code path must be used. Updated by CollectSources().
     417             :     bool m_bLastMustUseMultiThreading = false;
     418             : 
     419             :     //! Whether the GTI file is a STAC collection
     420             :     bool m_bSTACCollection = false;
     421             : 
     422             :     std::string m_osWarpMemory{};
     423             : 
     424             :     //! From a source dataset name, return its SourceDesc description structure.
     425             :     bool GetSourceDesc(const std::string &osTileName, SourceDesc &oSourceDesc,
     426             :                        std::mutex *pMutex, int nBandCount,
     427             :                        const int *panBandMap);
     428             : 
     429             :     //! Collect sources corresponding to the georeferenced window of interest,
     430             :     //! and store them in m_aoSourceDesc[].
     431             :     bool CollectSources(double dfXOff, double dfYOff, double dfXSize,
     432             :                         double dfYSize, int nBandCount, const int *panBandMap,
     433             :                         bool bMultiThreadAllowed);
     434             : 
     435             :     //! Sort sources according to m_nSortFieldIndex.
     436             :     void SortSourceDesc();
     437             : 
     438             :     //! Whether the output buffer needs to be nodata initialized, or if
     439             :     //! sources are fully covering it.
     440             :     bool NeedInitBuffer(int nBandCount, const int *panBandMap) const;
     441             : 
     442             :     //! Nodata initialize the output buffer.
     443             :     void InitBuffer(void *pData, int nBufXSize, int nBufYSize,
     444             :                     GDALDataType eBufType, int nBandCount,
     445             :                     const int *panBandMap, GSpacing nPixelSpace,
     446             :                     GSpacing nLineSpace, GSpacing nBandSpace) const;
     447             : 
     448             :     //! Render one source. Used by IRasterIO()
     449             :     CPLErr RenderSource(const SourceDesc &oSourceDesc, bool bNeedInitBuffer,
     450             :                         int nBandNrMax, int nXOff, int nYOff, int nXSize,
     451             :                         int nYSize, double dfXOff, double dfYOff,
     452             :                         double dfXSize, double dfYSize, int nBufXSize,
     453             :                         int nBufYSize, void *pData, GDALDataType eBufType,
     454             :                         int nBandCount, BANDMAP_TYPE panBandMap,
     455             :                         GSpacing nPixelSpace, GSpacing nLineSpace,
     456             :                         GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg,
     457             :                         VRTSource::WorkingState &oWorkingState) const;
     458             : 
     459             :     //! Whether m_poVectorDS supports SetMetadata()/SetMetadataItem()
     460             :     bool TileIndexSupportsEditingLayerMetadata() const;
     461             : 
     462             :     //! Return number of threads that can be used
     463             :     int GetNumThreads() const;
     464             : 
     465             :     /** Structure used to declare a threaded job to satisfy IRasterIO()
     466             :      * on a given source.
     467             :      */
     468             :     struct RasterIOJob
     469             :     {
     470             :         std::atomic<int> *pnCompletedJobs = nullptr;
     471             :         std::atomic<bool> *pbSuccess = nullptr;
     472             :         CPLErrorAccumulator *poErrorAccumulator = nullptr;
     473             :         GDALTileIndexDataset *poDS = nullptr;
     474             :         GDALTileIndexDataset::QueueWorkingStates *poQueueWorkingStates =
     475             :             nullptr;
     476             :         int nBandNrMax = 0;
     477             :         bool bSTACCollection = false;
     478             : 
     479             :         int nXOff = 0;
     480             :         int nYOff = 0;
     481             :         int nXSize = 0;
     482             :         int nYSize = 0;
     483             :         void *pData = nullptr;
     484             :         int nBufXSize = 0;
     485             :         int nBufYSize = 0;
     486             :         int nBandCount = 0;
     487             :         BANDMAP_TYPE panBandMap = nullptr;
     488             :         GDALDataType eBufType = GDT_Unknown;
     489             :         GSpacing nPixelSpace = 0;
     490             :         GSpacing nLineSpace = 0;
     491             :         GSpacing nBandSpace = 0;
     492             :         GDALRasterIOExtraArg *psExtraArg = nullptr;
     493             : 
     494             :         std::string osTileName{};
     495             : 
     496             :         static void Func(void *pData);
     497             :     };
     498             : 
     499             :     CPL_DISALLOW_COPY_ASSIGN(GDALTileIndexDataset)
     500             : };
     501             : 
     502             : /************************************************************************/
     503             : /*                          GDALTileIndexBand                           */
     504             : /************************************************************************/
     505             : 
     506             : class GDALTileIndexBand final : public GDALPamRasterBand
     507             : {
     508             :   public:
     509             :     GDALTileIndexBand(GDALTileIndexDataset *poDSIn, int nBandIn,
     510             :                       GDALDataType eDT, int nBlockXSizeIn, int nBlockYSizeIn);
     511             : 
     512         151 :     double GetNoDataValue(int *pbHasNoData) override
     513             :     {
     514         151 :         if (pbHasNoData)
     515         148 :             *pbHasNoData = m_bNoDataValueSet;
     516         151 :         return m_dfNoDataValue;
     517             :     }
     518             : 
     519          79 :     GDALColorInterp GetColorInterpretation() override
     520             :     {
     521          79 :         return m_eColorInterp;
     522             :     }
     523             : 
     524             :     CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pData) override;
     525             : 
     526             :     CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
     527             :                      int nYSize, void *pData, int nBufXSize, int nBufYSize,
     528             :                      GDALDataType eBufType, GSpacing nPixelSpace,
     529             :                      GSpacing nLineSpace,
     530             :                      GDALRasterIOExtraArg *psExtraArg) override;
     531             : 
     532             :     int IGetDataCoverageStatus(int nXOff, int nYOff, int nXSize, int nYSize,
     533             :                                int nMaskFlagStop, double *pdfDataPct) override;
     534             : 
     535          39 :     int GetMaskFlags() override
     536             :     {
     537          39 :         if (m_poDS->m_poMaskBand && m_poDS->m_poMaskBand.get() != this)
     538           5 :             return GMF_PER_DATASET;
     539          34 :         return GDALPamRasterBand::GetMaskFlags();
     540             :     }
     541             : 
     542          39 :     GDALRasterBand *GetMaskBand() override
     543             :     {
     544          39 :         if (m_poDS->m_poMaskBand && m_poDS->m_poMaskBand.get() != this)
     545           9 :             return m_poDS->m_poMaskBand.get();
     546          30 :         return GDALPamRasterBand::GetMaskBand();
     547             :     }
     548             : 
     549          13 :     double GetOffset(int *pbHasValue) override
     550             :     {
     551          13 :         int bHasValue = FALSE;
     552          13 :         double dfVal = GDALPamRasterBand::GetOffset(&bHasValue);
     553          13 :         if (bHasValue)
     554             :         {
     555           0 :             if (pbHasValue)
     556           0 :                 *pbHasValue = true;
     557           0 :             return dfVal;
     558             :         }
     559          13 :         if (pbHasValue)
     560          10 :             *pbHasValue = !std::isnan(m_dfOffset);
     561          13 :         return std::isnan(m_dfOffset) ? 0.0 : m_dfOffset;
     562             :     }
     563             : 
     564          13 :     double GetScale(int *pbHasValue) override
     565             :     {
     566          13 :         int bHasValue = FALSE;
     567          13 :         double dfVal = GDALPamRasterBand::GetScale(&bHasValue);
     568          13 :         if (bHasValue)
     569             :         {
     570           0 :             if (pbHasValue)
     571           0 :                 *pbHasValue = true;
     572           0 :             return dfVal;
     573             :         }
     574          13 :         if (pbHasValue)
     575          10 :             *pbHasValue = !std::isnan(m_dfScale);
     576          13 :         return std::isnan(m_dfScale) ? 1.0 : m_dfScale;
     577             :     }
     578             : 
     579           9 :     const char *GetUnitType() override
     580             :     {
     581           9 :         const char *pszVal = GDALPamRasterBand::GetUnitType();
     582           9 :         if (pszVal && *pszVal)
     583           0 :             return pszVal;
     584           9 :         return m_osUnit.c_str();
     585             :     }
     586             : 
     587           5 :     char **GetCategoryNames() override
     588             :     {
     589           5 :         return m_aosCategoryNames.List();
     590             :     }
     591             : 
     592          11 :     GDALColorTable *GetColorTable() override
     593             :     {
     594          11 :         return m_poColorTable.get();
     595             :     }
     596             : 
     597           5 :     GDALRasterAttributeTable *GetDefaultRAT() override
     598             :     {
     599           5 :         return m_poRAT.get();
     600             :     }
     601             : 
     602             :     int GetOverviewCount() override;
     603             :     GDALRasterBand *GetOverview(int iOvr) override;
     604             : 
     605             :     char **GetMetadataDomainList() override;
     606             :     const char *GetMetadataItem(const char *pszName,
     607             :                                 const char *pszDomain) override;
     608             :     CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
     609             :                            const char *pszDomain) override;
     610             :     CPLErr SetMetadata(CSLConstList papszMD, const char *pszDomain) override;
     611             : 
     612             :   private:
     613             :     friend class GDALTileIndexDataset;
     614             : 
     615             :     //! Dataset that owns this band.
     616             :     GDALTileIndexDataset *m_poDS = nullptr;
     617             : 
     618             :     //! Whether a nodata value is set to this band.
     619             :     bool m_bNoDataValueSet = false;
     620             : 
     621             :     //! Nodata value.
     622             :     double m_dfNoDataValue = 0;
     623             : 
     624             :     //! Color interpretation.
     625             :     GDALColorInterp m_eColorInterp = GCI_Undefined;
     626             : 
     627             :     //! Cached value for GetMetadataItem("Pixel_X_Y", "LocationInfo").
     628             :     std::string m_osLastLocationInfo{};
     629             : 
     630             :     //! Scale value (returned by GetScale())
     631             :     double m_dfScale = std::numeric_limits<double>::quiet_NaN();
     632             : 
     633             :     //! Offset value (returned by GetOffset())
     634             :     double m_dfOffset = std::numeric_limits<double>::quiet_NaN();
     635             : 
     636             :     //! Unit type (returned by GetUnitType()).
     637             :     std::string m_osUnit{};
     638             : 
     639             :     //! Category names (returned by GetCategoryNames()).
     640             :     CPLStringList m_aosCategoryNames{};
     641             : 
     642             :     //! Color table (returned by GetColorTable()).
     643             :     std::unique_ptr<GDALColorTable> m_poColorTable{};
     644             : 
     645             :     //! Raster attribute table (returned by GetDefaultRAT()).
     646             :     std::unique_ptr<GDALRasterAttributeTable> m_poRAT{};
     647             : 
     648             :     CPL_DISALLOW_COPY_ASSIGN(GDALTileIndexBand)
     649             : };
     650             : 
     651             : /************************************************************************/
     652             : /*                           IsSameNaNAware()                           */
     653             : /************************************************************************/
     654             : 
     655         466 : static inline bool IsSameNaNAware(double a, double b)
     656             : {
     657         466 :     return a == b || (std::isnan(a) && std::isnan(b));
     658             : }
     659             : 
     660             : /************************************************************************/
     661             : /*                        GDALTileIndexDataset()                        */
     662             : /************************************************************************/
     663             : 
     664         351 : GDALTileIndexDataset::GDALTileIndexDataset()
     665         351 :     : m_osUniqueHandle(CPLSPrintf("%p", this))
     666             : {
     667         351 : }
     668             : 
     669             : /************************************************************************/
     670             : /*                        GetAbsoluteFileName()                         */
     671             : /************************************************************************/
     672             : 
     673         693 : static std::string GetAbsoluteFileName(const char *pszTileName,
     674             :                                        const char *pszVRTName,
     675             :                                        bool bIsStacCollection)
     676             : {
     677             :     std::string osRet =
     678        2079 :         bIsStacCollection ? VSIURIToVSIPath(pszTileName) : pszTileName;
     679         693 :     if (osRet != pszTileName)
     680           6 :         return osRet;
     681             : 
     682         687 :     if (CPLIsFilenameRelative(pszTileName) &&
     683         705 :         !STARTS_WITH(pszTileName, "<VRTDataset") &&
     684          18 :         !STARTS_WITH(pszVRTName, "<GDALTileIndexDataset"))
     685             :     {
     686          18 :         const auto oSubDSInfo(GDALGetSubdatasetInfo(pszTileName));
     687          18 :         if (oSubDSInfo && !oSubDSInfo->GetPathComponent().empty())
     688             :         {
     689           4 :             const std::string osPath(oSubDSInfo->GetPathComponent());
     690           2 :             osRet = CPLIsFilenameRelative(osPath.c_str())
     691           5 :                         ? oSubDSInfo->ModifyPathComponent(
     692           4 :                               CPLProjectRelativeFilenameSafe(
     693           3 :                                   CPLGetPathSafe(pszVRTName).c_str(),
     694             :                                   osPath.c_str()))
     695           2 :                         : std::string(pszTileName);
     696           2 :             GDALDestroySubdatasetInfo(oSubDSInfo);
     697           2 :             return osRet;
     698             :         }
     699             : 
     700             :         std::string osRelativeMadeAbsolute = CPLProjectRelativeFilenameSafe(
     701          16 :             CPLGetPathSafe(pszVRTName).c_str(), pszTileName);
     702             :         VSIStatBufL sStat;
     703          16 :         if (VSIStatL(osRelativeMadeAbsolute.c_str(), &sStat) == 0)
     704           9 :             return osRelativeMadeAbsolute;
     705             :     }
     706         676 :     return pszTileName;
     707             : }
     708             : 
     709             : /************************************************************************/
     710             : /*                           IsGrayOrGrayA()                            */
     711             : /************************************************************************/
     712             : 
     713         525 : static bool IsGrayOrGrayA(GDALDataset *poDS)
     714             : {
     715         525 :     return (poDS->GetRasterCount() == 1 &&
     716         614 :             poDS->GetRasterBand(1)->GetRasterDataType() == GDT_Byte) ||
     717          89 :            (poDS->GetRasterCount() == 2 &&
     718         538 :             poDS->GetRasterBand(2)->GetColorInterpretation() == GCI_AlphaBand);
     719             : }
     720             : 
     721             : /************************************************************************/
     722             : /*                            IsRGBOrRGBA()                             */
     723             : /************************************************************************/
     724             : 
     725         525 : static bool IsRGBOrRGBA(GDALDataset *poDS)
     726             : {
     727         593 :     return poDS->GetRasterCount() >= 3 && poDS->GetRasterCount() <= 4 &&
     728          68 :            poDS->GetRasterBand(1)->GetColorInterpretation() == GCI_RedBand &&
     729          66 :            poDS->GetRasterBand(2)->GetColorInterpretation() == GCI_GreenBand &&
     730         659 :            poDS->GetRasterBand(3)->GetColorInterpretation() == GCI_BlueBand &&
     731          66 :            (poDS->GetRasterCount() == 3 ||
     732         547 :             poDS->GetRasterBand(4)->GetColorInterpretation() == GCI_AlphaBand);
     733             : }
     734             : 
     735             : /************************************************************************/
     736             : /*                         GTIAdjustBandCount()                         */
     737             : /************************************************************************/
     738             : 
     739             : // Do palette -> RGB(A) expansion, G(A)/RGB(A) -> G(A)/RGB(A)
     740             : 
     741         559 : static bool GTIAdjustBandCount(std::shared_ptr<GDALDataset> &poTileDS,
     742             :                                int nBandCount, GDALDataset *poGTIDS)
     743             : {
     744         559 :     if (nBandCount == poTileDS->GetRasterCount())
     745         295 :         return true;
     746             : 
     747         528 :     CPLStringList aosOptions;
     748             : 
     749         490 :     if (poTileDS->GetRasterCount() == 1 &&
     750         492 :         (nBandCount == 3 || nBandCount == 4) &&
     751           4 :         poTileDS->GetRasterBand(1)->GetColorTable() != nullptr)
     752             :     {
     753           4 :         aosOptions.AddString("-of");
     754           4 :         aosOptions.AddString("VRT");
     755             : 
     756           4 :         aosOptions.AddString("-expand");
     757           4 :         aosOptions.AddString(nBandCount == 3 ? "rgb" : "rgba");
     758             :     }
     759             : 
     760             :     // G(A) -> G(A)
     761         260 :     else if (IsGrayOrGrayA(poTileDS.get()) && poGTIDS && IsGrayOrGrayA(poGTIDS))
     762             :     {
     763           0 :         aosOptions.AddString("-of");
     764           0 :         aosOptions.AddString("VRT");
     765             : 
     766           0 :         aosOptions.AddString("-b");
     767           0 :         aosOptions.AddString("1");
     768             : 
     769           0 :         if (nBandCount == 2)
     770             :         {
     771           0 :             aosOptions.AddString("-b");
     772           0 :             aosOptions.AddString("mask");
     773             :         }
     774             :     }
     775             : 
     776             :     // G(A) -> RGB(A)
     777         260 :     else if (IsGrayOrGrayA(poTileDS.get()) && poGTIDS && IsRGBOrRGBA(poGTIDS))
     778             :     {
     779           0 :         aosOptions.AddString("-of");
     780           0 :         aosOptions.AddString("VRT");
     781             : 
     782           0 :         aosOptions.AddString("-b");
     783           0 :         aosOptions.AddString("1");
     784             : 
     785           0 :         aosOptions.AddString("-b");
     786           0 :         aosOptions.AddString("1");
     787             : 
     788           0 :         aosOptions.AddString("-b");
     789           0 :         aosOptions.AddString("1");
     790             : 
     791           0 :         if (nBandCount == 4)
     792             :         {
     793           0 :             aosOptions.AddString("-b");
     794           0 :             aosOptions.AddString("mask");
     795             :         }
     796             :     }
     797             : 
     798             :     // RGB(A) -> RGB(A)
     799         260 :     else if (IsRGBOrRGBA(poTileDS.get()) && poGTIDS && IsRGBOrRGBA(poGTIDS))
     800             :     {
     801           2 :         aosOptions.AddString("-of");
     802           2 :         aosOptions.AddString("VRT");
     803             : 
     804           2 :         aosOptions.AddString("-b");
     805           2 :         aosOptions.AddString("1");
     806             : 
     807           2 :         aosOptions.AddString("-b");
     808           2 :         aosOptions.AddString("2");
     809             : 
     810           2 :         aosOptions.AddString("-b");
     811           2 :         aosOptions.AddString("3");
     812             : 
     813           2 :         if (nBandCount == 4)
     814             :         {
     815           2 :             aosOptions.AddString("-b");
     816           2 :             aosOptions.AddString("mask");
     817             :         }
     818             :     }
     819             : 
     820             :     // GrGrGr(A) -> G(A)
     821         258 :     else if (IsRGBOrRGBA(poTileDS.get()) && poGTIDS && IsGrayOrGrayA(poGTIDS))
     822             :     {
     823           3 :         aosOptions.AddString("-of");
     824           3 :         aosOptions.AddString("VRT");
     825             : 
     826             :         // We assume that the R,G,B channels actually contain the same value
     827             :         // i.e. a gray scale image expanded as R,G,B
     828           3 :         aosOptions.AddString("-b");
     829           3 :         aosOptions.AddString("1");
     830             : 
     831           3 :         if (nBandCount == 2)
     832             :         {
     833           3 :             aosOptions.AddString("-b");
     834           3 :             aosOptions.AddString("mask");
     835             :         }
     836             :     }
     837             : 
     838             :     else
     839             :     {
     840         255 :         return true;
     841             :     }
     842             : 
     843             :     GDALTranslateOptions *psOptions =
     844           9 :         GDALTranslateOptionsNew(aosOptions.List(), nullptr);
     845           9 :     int bUsageError = false;
     846             :     auto poNewTileDS = std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(
     847             :         GDALTranslate("", GDALDataset::ToHandle(poTileDS.get()), psOptions,
     848           9 :                       &bUsageError)));
     849           9 :     GDALTranslateOptionsFree(psOptions);
     850           9 :     const bool bRet = poNewTileDS != nullptr;
     851           9 :     poTileDS = std::move(poNewTileDS);
     852           9 :     return bRet;
     853             : }
     854             : 
     855             : /************************************************************************/
     856             : /*                                Open()                                */
     857             : /************************************************************************/
     858             : 
     859         351 : bool GDALTileIndexDataset::Open(GDALOpenInfo *poOpenInfo)
     860             : {
     861         351 :     eAccess = poOpenInfo->eAccess;
     862             : 
     863         702 :     m_aosAllowedRasterDrivers = CPLStringList(
     864         351 :         CSLTokenizeString2(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
     865             :                                                 "ALLOWED_RASTER_DRIVERS", ""),
     866         351 :                            ",", 0));
     867             : 
     868         351 :     CPLXMLNode *psRoot = nullptr;
     869         702 :     std::string osIndexDataset(poOpenInfo->pszFilename);
     870             : 
     871         351 :     if (cpl::starts_with(osIndexDataset, GTI_PREFIX))
     872             :     {
     873          17 :         osIndexDataset = osIndexDataset.substr(strlen(GTI_PREFIX));
     874          17 :         m_osBaseDir = CPLGetPathSafe(osIndexDataset.c_str());
     875             :     }
     876         334 :     else if (cpl::starts_with(osIndexDataset, "<GDALTileIndexDataset"))
     877             :     {
     878             :         // CPLParseXMLString() emits an error in case of failure
     879          26 :         m_psXMLTree.reset(CPLParseXMLString(poOpenInfo->pszFilename));
     880          26 :         if (m_psXMLTree == nullptr)
     881           1 :             return false;
     882             :     }
     883         308 :     else if (poOpenInfo->nHeaderBytes > 0 &&
     884         308 :              strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     885             :                     "<GDALTileIndexDataset"))
     886             :     {
     887           8 :         if (CPLTestBool(CPLGetConfigOption("GDAL_XML_VALIDATION", "YES")))
     888             :         {
     889           8 :             const char *pszXSD = CPLFindFile("gdal", "gdaltileindex.xsd");
     890           8 :             if (pszXSD != nullptr)
     891             :             {
     892          16 :                 CPLErrorAccumulator oAccumulator;
     893             :                 int bRet;
     894             :                 {
     895           8 :                     auto oContext = oAccumulator.InstallForCurrentScope();
     896           8 :                     CPL_IGNORE_RET_VAL(oContext);
     897           8 :                     bRet = CPLValidateXML(poOpenInfo->pszFilename, pszXSD,
     898             :                                           nullptr);
     899             :                 }
     900           8 :                 if (!bRet)
     901             :                 {
     902           3 :                     const auto &aoErrors = oAccumulator.GetErrors();
     903           5 :                     if (!aoErrors.empty() &&
     904           2 :                         aoErrors[0].msg.find("missing libxml2 support") ==
     905             :                             std::string::npos)
     906             :                     {
     907           4 :                         for (size_t i = 0; i < aoErrors.size(); i++)
     908             :                         {
     909           2 :                             CPLError(CE_Warning, CPLE_AppDefined, "%s",
     910           2 :                                      aoErrors[i].msg.c_str());
     911             :                         }
     912             :                     }
     913             :                 }
     914             :             }
     915             :         }
     916             :         // CPLParseXMLFile() emits an error in case of failure
     917           8 :         m_psXMLTree.reset(CPLParseXMLFile(poOpenInfo->pszFilename));
     918           8 :         if (m_psXMLTree == nullptr)
     919           1 :             return false;
     920           7 :         m_bXMLUpdatable = (poOpenInfo->eAccess == GA_Update);
     921           7 :         m_osBaseDir = CPLGetPathSafe(osIndexDataset.c_str());
     922             :     }
     923             :     else
     924             :     {
     925         300 :         m_osBaseDir = CPLGetPathSafe(osIndexDataset.c_str());
     926             :     }
     927             : 
     928         349 :     if (m_psXMLTree)
     929             :     {
     930          32 :         psRoot = CPLGetXMLNode(m_psXMLTree.get(), "=GDALTileIndexDataset");
     931          32 :         if (psRoot == nullptr)
     932             :         {
     933           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     934             :                      "Missing GDALTileIndexDataset root element.");
     935           1 :             return false;
     936             :         }
     937             : 
     938             :         const char *pszIndexDataset =
     939          31 :             CPLGetXMLValue(psRoot, "IndexDataset", nullptr);
     940          31 :         if (!pszIndexDataset)
     941             :         {
     942           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     943             :                      "Missing IndexDataset element.");
     944           1 :             return false;
     945             :         }
     946             : 
     947          30 :         osIndexDataset = pszIndexDataset;
     948          30 :         if (cpl::starts_with(osIndexDataset, GTI_PREFIX))
     949           1 :             osIndexDataset = osIndexDataset.substr(strlen(GTI_PREFIX));
     950             : 
     951          36 :         if (!m_osBaseDir.empty() &&
     952           6 :             CPLIsFilenameRelative(osIndexDataset.c_str()))
     953             :         {
     954           4 :             osIndexDataset = CPLFormFilenameSafe(
     955           2 :                 m_osBaseDir.c_str(), osIndexDataset.c_str(), nullptr);
     956             :         }
     957             :     }
     958             : 
     959         347 :     if (ENDS_WITH_CI(osIndexDataset.c_str(), ".gti.gpkg") &&
     960         652 :         poOpenInfo->nHeaderBytes >= 100 &&
     961         305 :         STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     962             :                     "SQLite format 3"))
     963             :     {
     964         299 :         const char *const apszAllowedDrivers[] = {"GPKG", nullptr};
     965         299 :         m_poVectorDS.reset(GDALDataset::Open(
     966         598 :             std::string("GPKG:\"").append(osIndexDataset).append("\"").c_str(),
     967         299 :             GDAL_OF_VECTOR | GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR |
     968         299 :                 ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) ? GDAL_OF_UPDATE
     969             :                                                            : GDAL_OF_READONLY),
     970             :             apszAllowedDrivers));
     971         299 :         if (!m_poVectorDS)
     972             :         {
     973           1 :             return false;
     974             :         }
     975         301 :         if (m_poVectorDS->GetLayerCount() == 0 &&
     976           2 :             (m_poVectorDS->GetRasterCount() != 0 ||
     977           1 :              m_poVectorDS->GetMetadata(GDAL_MDD_SUBDATASETS) != nullptr))
     978             :         {
     979           1 :             return false;
     980             :         }
     981             :     }
     982             :     else
     983             :     {
     984          48 :         m_poVectorDS.reset(
     985             :             GDALDataset::Open(osIndexDataset.c_str(),
     986          48 :                               GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR |
     987          48 :                                   ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE)
     988          48 :                                        ? GDAL_OF_UPDATE
     989             :                                        : GDAL_OF_READONLY)));
     990          48 :         if (!m_poVectorDS)
     991             :         {
     992           1 :             return false;
     993             :         }
     994             :     }
     995             : 
     996         345 :     if (m_poVectorDS->GetLayerCount() == 0)
     997             :     {
     998           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s has no vector layer",
     999             :                  osIndexDataset.c_str());
    1000           1 :         return false;
    1001             :     }
    1002             : 
    1003         344 :     double dfOvrFactor = 1.0;
    1004         344 :     if (const char *pszFactor =
    1005         344 :             CSLFetchNameValue(poOpenInfo->papszOpenOptions, "FACTOR"))
    1006             :     {
    1007           5 :         dfOvrFactor = CPLAtof(pszFactor);
    1008             :         // Written that way to catch NaN
    1009           5 :         if (!(dfOvrFactor >= 1.0))
    1010             :         {
    1011           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Wrong overview factor");
    1012           1 :             return false;
    1013             :         }
    1014             :     }
    1015             : 
    1016         343 :     m_osSQL = CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "SQL", "");
    1017         343 :     if (m_osSQL.empty())
    1018             :     {
    1019         340 :         if (!psRoot)
    1020             :         {
    1021         311 :             if (const char *pszVal =
    1022         311 :                     m_poVectorDS->GetMetadataItem(MD_DS_TILE_INDEX_SQL))
    1023           0 :                 m_osSQL = pszVal;
    1024             :         }
    1025             :         else
    1026          29 :             m_osSQL = CPLGetXMLValue(psRoot, "SQL", "");
    1027             :     }
    1028             : 
    1029         343 :     if (!m_osSQL.empty())
    1030             :     {
    1031           5 :         m_osSpatialSQL = CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
    1032           5 :                                               "SPATIAL_SQL", "");
    1033           5 :         if (m_osSpatialSQL.empty())
    1034             :         {
    1035           3 :             if (!psRoot)
    1036             :             {
    1037           2 :                 if (const char *pszVal = m_poVectorDS->GetMetadataItem(
    1038           1 :                         MD_DS_TILE_INDEX_SPATIAL_SQL))
    1039           0 :                     m_osSpatialSQL = pszVal;
    1040             :             }
    1041             :             else
    1042           2 :                 m_osSpatialSQL = CPLGetXMLValue(psRoot, "SpatialSQL", "");
    1043             :         }
    1044             :     }
    1045             : 
    1046             :     const char *pszLayerName;
    1047             : 
    1048         343 :     if ((pszLayerName = CSLFetchNameValue(poOpenInfo->papszOpenOptions,
    1049         343 :                                           "LAYER")) != nullptr)
    1050             :     {
    1051           6 :         m_poLayer = m_poVectorDS->GetLayerByName(pszLayerName);
    1052           6 :         if (!m_poLayer)
    1053             :         {
    1054           2 :             CPLError(CE_Failure, CPLE_AppDefined, "Layer %s does not exist",
    1055             :                      pszLayerName);
    1056           2 :             return false;
    1057             :         }
    1058             :     }
    1059         337 :     else if (psRoot && (pszLayerName = CPLGetXMLValue(psRoot, "IndexLayer",
    1060             :                                                       nullptr)) != nullptr)
    1061             :     {
    1062           8 :         m_poLayer = m_poVectorDS->GetLayerByName(pszLayerName);
    1063           8 :         if (!m_poLayer)
    1064             :         {
    1065           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Layer %s does not exist",
    1066             :                      pszLayerName);
    1067           1 :             return false;
    1068             :         }
    1069             :     }
    1070         641 :     else if (!psRoot && (pszLayerName = m_poVectorDS->GetMetadataItem(
    1071         312 :                              MD_DS_TILE_INDEX_LAYER)) != nullptr)
    1072             :     {
    1073           2 :         m_poLayer = m_poVectorDS->GetLayerByName(pszLayerName);
    1074           2 :         if (!m_poLayer)
    1075             :         {
    1076           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Layer %s does not exist",
    1077             :                      pszLayerName);
    1078           1 :             return false;
    1079             :         }
    1080             :     }
    1081         327 :     else if (!m_osSQL.empty())
    1082             :     {
    1083           5 :         m_poLayer = m_poVectorDS->ExecuteSQL(m_osSQL.c_str(), nullptr, nullptr);
    1084           5 :         if (!m_poLayer)
    1085             :         {
    1086           1 :             CPLError(CE_Failure, CPLE_AppDefined, "SQL request %s failed",
    1087             :                      m_osSQL.c_str());
    1088           1 :             return false;
    1089             :         }
    1090           4 :         m_poLayerToRelease = m_poLayer;
    1091             :     }
    1092         322 :     else if (m_poVectorDS->GetLayerCount() == 1)
    1093             :     {
    1094         320 :         m_poLayer = m_poVectorDS->GetLayer(0);
    1095         320 :         if (!m_poLayer)
    1096             :         {
    1097           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot open layer 0");
    1098           0 :             return false;
    1099             :         }
    1100             :     }
    1101             :     else
    1102             :     {
    1103           2 :         if (STARTS_WITH(poOpenInfo->pszFilename, GTI_PREFIX))
    1104             :         {
    1105           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1106             :                      "%s has more than one layer. LAYER open option "
    1107             :                      "must be defined to specify which one to "
    1108             :                      "use as the tile index",
    1109             :                      osIndexDataset.c_str());
    1110             :         }
    1111           1 :         else if (psRoot)
    1112             :         {
    1113           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1114             :                      "%s has more than one layer. IndexLayer element must be "
    1115             :                      "defined to specify which one to "
    1116             :                      "use as the tile index",
    1117             :                      osIndexDataset.c_str());
    1118             :         }
    1119             :         else
    1120             :         {
    1121           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1122             :                      "%s has more than one layer. %s "
    1123             :                      "metadata item must be defined to specify which one to "
    1124             :                      "use as the tile index",
    1125             :                      osIndexDataset.c_str(), MD_DS_TILE_INDEX_LAYER);
    1126             :         }
    1127           2 :         return false;
    1128             :     }
    1129             : 
    1130             :     // Try to get the metadata from an embedded xml:GTI domain
    1131         336 :     if (!m_psXMLTree)
    1132             :     {
    1133         309 :         CSLConstList papszMD = m_poLayer->GetMetadata("xml:GTI");
    1134         309 :         if (papszMD && papszMD[0])
    1135             :         {
    1136           1 :             m_psXMLTree.reset(CPLParseXMLString(papszMD[0]));
    1137           1 :             if (m_psXMLTree == nullptr)
    1138           0 :                 return false;
    1139             : 
    1140           1 :             psRoot = CPLGetXMLNode(m_psXMLTree.get(), "=GDALTileIndexDataset");
    1141           1 :             if (psRoot == nullptr)
    1142             :             {
    1143           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1144             :                          "Missing GDALTileIndexDataset root element.");
    1145           0 :                 return false;
    1146             :             }
    1147             :         }
    1148             :     }
    1149             : 
    1150             :     // Get the value of an option.
    1151             :     // The order of lookup is the following one (first to last):
    1152             :     // - open options
    1153             :     // - XML file
    1154             :     // - Layer metadata items.
    1155       33691 :     const auto GetOption = [poOpenInfo, psRoot, this](const char *pszItem)
    1156             :     {
    1157             :         const char *pszVal =
    1158       10798 :             CSLFetchNameValue(poOpenInfo->papszOpenOptions, pszItem);
    1159       10798 :         if (pszVal)
    1160          18 :             return pszVal;
    1161             : 
    1162       10780 :         if (psRoot)
    1163             :         {
    1164         697 :             pszVal = CPLGetXMLValue(psRoot, pszItem, nullptr);
    1165         697 :             if (pszVal)
    1166          27 :                 return pszVal;
    1167             : 
    1168         670 :             if (EQUAL(pszItem, MD_BAND_COUNT))
    1169          25 :                 pszItem = GTI_XML_BANDCOUNT;
    1170         645 :             else if (EQUAL(pszItem, MD_DATA_TYPE))
    1171          28 :                 pszItem = GTI_XML_DATATYPE;
    1172         617 :             else if (EQUAL(pszItem, MD_NODATA))
    1173          23 :                 pszItem = GTI_XML_NODATAVALUE;
    1174         594 :             else if (EQUAL(pszItem, MD_COLOR_INTERPRETATION))
    1175          28 :                 pszItem = GTI_XML_COLORINTERP;
    1176         566 :             else if (EQUAL(pszItem, MD_LOCATION_FIELD))
    1177          28 :                 pszItem = GTI_XML_LOCATIONFIELD;
    1178         538 :             else if (EQUAL(pszItem, MD_SORT_FIELD))
    1179          28 :                 pszItem = GTI_XML_SORTFIELD;
    1180         510 :             else if (EQUAL(pszItem, MD_SORT_FIELD_ASC))
    1181           2 :                 pszItem = GTI_XML_SORTFIELDASC;
    1182         508 :             else if (EQUAL(pszItem, MD_MASK_BAND))
    1183          23 :                 pszItem = GTI_XML_MASKBAND;
    1184         670 :             pszVal = CPLGetXMLValue(psRoot, pszItem, nullptr);
    1185         670 :             if (pszVal)
    1186           7 :                 return pszVal;
    1187             :         }
    1188             : 
    1189       10746 :         return m_poLayer->GetMetadataItem(pszItem);
    1190         336 :     };
    1191             : 
    1192         336 :     const char *pszFilter = GetOption("Filter");
    1193         336 :     if (pszFilter)
    1194             :     {
    1195           1 :         if (m_poLayer->SetAttributeFilter(pszFilter) != OGRERR_NONE)
    1196           0 :             return false;
    1197             :     }
    1198             : 
    1199         336 :     const OGRFeatureDefn *poLayerDefn = m_poLayer->GetLayerDefn();
    1200             : 
    1201         672 :     std::string osLocationFieldName;
    1202             :     {
    1203         336 :         const char *pszLocationFieldName = GetOption(MD_LOCATION_FIELD);
    1204         336 :         if (pszLocationFieldName)
    1205             :         {
    1206          10 :             osLocationFieldName = pszLocationFieldName;
    1207             :         }
    1208             :         else
    1209             :         {
    1210             :             // Is this a https://stac-utils.github.io/stac-geoparquet/latest/spec/stac-geoparquet-spec ?
    1211         326 :             if (poLayerDefn->GetFieldIndex("assets.data.href") >= 0)
    1212             :             {
    1213           0 :                 m_bSTACCollection = true;
    1214           0 :                 osLocationFieldName = "assets.data.href";
    1215           0 :                 CPLDebug("GTI", "Using %s as location field",
    1216             :                          osLocationFieldName.c_str());
    1217             :             }
    1218         326 :             else if (poLayerDefn->GetFieldIndex("assets.image.href") >= 0)
    1219             :             {
    1220           3 :                 m_bSTACCollection = true;
    1221           3 :                 osLocationFieldName = "assets.image.href";
    1222           3 :                 CPLDebug("GTI", "Using %s as location field",
    1223             :                          osLocationFieldName.c_str());
    1224             :             }
    1225         642 :             else if (poLayerDefn->GetFieldIndex("stac_version") >= 0 ||
    1226         319 :                      poLayerDefn->GetFieldIndex("stac_extensions") >= 0)
    1227             :             {
    1228           4 :                 m_bSTACCollection = true;
    1229           4 :                 const int nFieldCount = poLayerDefn->GetFieldCount();
    1230             :                 // Look for "assets.xxxxx.href" fields
    1231           4 :                 int nAssetCount = 0;
    1232          60 :                 for (int i = 0; i < nFieldCount; ++i)
    1233             :                 {
    1234          56 :                     const auto poFDefn = poLayerDefn->GetFieldDefn(i);
    1235          56 :                     const char *pszFieldName = poFDefn->GetNameRef();
    1236          56 :                     if (STARTS_WITH(pszFieldName, "assets.") &&
    1237          44 :                         EQUAL(pszFieldName + strlen(pszFieldName) -
    1238             :                                   strlen(".href"),
    1239           4 :                               ".href") &&
    1240             :                         // Assets with "metadata" in them are very much likely
    1241             :                         // not rasters... We could potentially confirm that by
    1242             :                         // inspecting the value of the assets.XXX.type or
    1243             :                         // assets.XXX.roles fields of one feature
    1244           4 :                         !strstr(pszFieldName, "metadata"))
    1245             :                     {
    1246           4 :                         ++nAssetCount;
    1247           4 :                         if (!osLocationFieldName.empty())
    1248             :                         {
    1249           0 :                             osLocationFieldName += ", ";
    1250             :                         }
    1251           4 :                         osLocationFieldName += pszFieldName;
    1252             :                     }
    1253             :                 }
    1254           4 :                 if (nAssetCount > 1)
    1255             :                 {
    1256           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1257             :                              "Several potential STAC assets. Please select one "
    1258             :                              "among %s with the LOCATION_FIELD open option",
    1259             :                              osLocationFieldName.c_str());
    1260           0 :                     return false;
    1261             :                 }
    1262           4 :                 else if (nAssetCount == 0)
    1263             :                 {
    1264           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1265             :                              "File has stac_version or stac_extensions "
    1266             :                              "property but lacks assets");
    1267           0 :                     return false;
    1268             :                 }
    1269             :             }
    1270             :             else
    1271             :             {
    1272         319 :                 constexpr const char *DEFAULT_LOCATION_FIELD_NAME = "location";
    1273         319 :                 osLocationFieldName = DEFAULT_LOCATION_FIELD_NAME;
    1274             :             }
    1275             :         }
    1276             :     }
    1277             : 
    1278         336 :     m_nLocationFieldIndex =
    1279         336 :         poLayerDefn->GetFieldIndex(osLocationFieldName.c_str());
    1280         336 :     if (m_nLocationFieldIndex < 0)
    1281             :     {
    1282           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find field %s",
    1283             :                  osLocationFieldName.c_str());
    1284           1 :         return false;
    1285             :     }
    1286         335 :     if (poLayerDefn->GetFieldDefn(m_nLocationFieldIndex)->GetType() !=
    1287             :         OFTString)
    1288             :     {
    1289           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Field %s is not of type string",
    1290             :                  osLocationFieldName.c_str());
    1291           1 :         return false;
    1292             :     }
    1293             : 
    1294         334 :     const char *pszSortFieldName = GetOption(MD_SORT_FIELD);
    1295         334 :     if (pszSortFieldName)
    1296             :     {
    1297          96 :         m_nSortFieldIndex = poLayerDefn->GetFieldIndex(pszSortFieldName);
    1298          96 :         if (m_nSortFieldIndex < 0)
    1299             :         {
    1300           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find field %s",
    1301             :                      pszSortFieldName);
    1302           1 :             return false;
    1303             :         }
    1304             : 
    1305             :         const auto eFieldType =
    1306          95 :             poLayerDefn->GetFieldDefn(m_nSortFieldIndex)->GetType();
    1307          95 :         if (eFieldType != OFTString && eFieldType != OFTInteger &&
    1308          61 :             eFieldType != OFTInteger64 && eFieldType != OFTReal &&
    1309          19 :             eFieldType != OFTDate && eFieldType != OFTDateTime)
    1310             :         {
    1311           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1312             :                      "Unsupported type for field %s", pszSortFieldName);
    1313           1 :             return false;
    1314             :         }
    1315             : 
    1316          94 :         const char *pszSortFieldAsc = GetOption(MD_SORT_FIELD_ASC);
    1317          94 :         if (pszSortFieldAsc)
    1318             :         {
    1319           3 :             m_bSortFieldAsc = CPLTestBool(pszSortFieldAsc);
    1320             :         }
    1321             :     }
    1322             : 
    1323         332 :     const char *pszResX = GetOption(MD_RESX);
    1324         332 :     const char *pszResY = GetOption(MD_RESY);
    1325         332 :     if (pszResX && !pszResY)
    1326             :     {
    1327           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1328             :                  "%s metadata item defined, but not %s", MD_RESX, MD_RESY);
    1329           1 :         return false;
    1330             :     }
    1331         331 :     if (!pszResX && pszResY)
    1332             :     {
    1333           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1334             :                  "%s metadata item defined, but not %s", MD_RESY, MD_RESX);
    1335           1 :         return false;
    1336             :     }
    1337             : 
    1338         330 :     const char *pszResampling = GetOption(MD_RESAMPLING);
    1339         330 :     if (pszResampling)
    1340             :     {
    1341           8 :         const auto nErrorCountBefore = CPLGetErrorCounter();
    1342           8 :         m_eResampling = GDALRasterIOGetResampleAlg(pszResampling);
    1343           8 :         if (nErrorCountBefore != CPLGetErrorCounter())
    1344             :         {
    1345           0 :             return false;
    1346             :         }
    1347           8 :         m_osResampling = pszResampling;
    1348             :     }
    1349             : 
    1350         330 :     const char *pszMinX = GetOption(MD_MINX);
    1351         330 :     const char *pszMinY = GetOption(MD_MINY);
    1352         330 :     const char *pszMaxX = GetOption(MD_MAXX);
    1353         330 :     const char *pszMaxY = GetOption(MD_MAXY);
    1354         330 :     int nCountMinMaxXY = (pszMinX ? 1 : 0) + (pszMinY ? 1 : 0) +
    1355         330 :                          (pszMaxX ? 1 : 0) + (pszMaxY ? 1 : 0);
    1356         330 :     if (nCountMinMaxXY != 0 && nCountMinMaxXY != 4)
    1357             :     {
    1358           4 :         CPLError(CE_Failure, CPLE_AppDefined,
    1359             :                  "None or all of %s, %s, %s and %s must be specified", MD_MINX,
    1360             :                  MD_MINY, MD_MAXX, MD_MAXY);
    1361           4 :         return false;
    1362             :     }
    1363             : 
    1364         326 :     const char *pszXSize = GetOption(MD_XSIZE);
    1365         326 :     const char *pszYSize = GetOption(MD_YSIZE);
    1366         326 :     const char *pszGeoTransform = GetOption(MD_GEOTRANSFORM);
    1367         326 :     const int nCountXSizeYSizeGT =
    1368         326 :         (pszXSize ? 1 : 0) + (pszYSize ? 1 : 0) + (pszGeoTransform ? 1 : 0);
    1369         326 :     if (nCountXSizeYSizeGT != 0 && nCountXSizeYSizeGT != 3)
    1370             :     {
    1371           3 :         CPLError(CE_Failure, CPLE_AppDefined,
    1372             :                  "None or all of %s, %s, %s must be specified", MD_XSIZE,
    1373             :                  MD_YSIZE, MD_GEOTRANSFORM);
    1374           3 :         return false;
    1375             :     }
    1376             : 
    1377         323 :     const char *pszDataType = GetOption(MD_DATA_TYPE);
    1378         323 :     const char *pszColorInterp = GetOption(MD_COLOR_INTERPRETATION);
    1379         323 :     int nBandCount = 0;
    1380         646 :     std::vector<GDALDataType> aeDataTypes;
    1381         646 :     std::vector<std::pair<bool, double>> aNoData;
    1382         646 :     std::vector<GDALColorInterp> aeColorInterp;
    1383             : 
    1384         323 :     const char *pszSRS = GetOption(MD_SRS);
    1385         323 :     const char *pszSRSBehavior = GetOption(MD_SRS_BEHAVIOR);
    1386         323 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1387         323 :     if (pszSRS)
    1388             :     {
    1389           8 :         if (m_oSRS.SetFromUserInput(
    1390             :                 pszSRS,
    1391           8 :                 OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
    1392             :             OGRERR_NONE)
    1393             :         {
    1394           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid %s", MD_SRS);
    1395           1 :             return false;
    1396             :         }
    1397           7 :         if (pszSRSBehavior && !EQUAL(pszSRSBehavior, "OVERRIDE") &&
    1398           3 :             !EQUAL(pszSRSBehavior, "REPROJECT"))
    1399             :         {
    1400           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1401             :                      "Invalid value for %s: must be OVERRIDE or REPROJECT",
    1402             :                      MD_SRS_BEHAVIOR);
    1403           1 :             return false;
    1404             :         }
    1405             :     }
    1406         315 :     else if (const auto poSRS = m_poLayer->GetSpatialRef())
    1407             :     {
    1408             :         // Ignore GPKG "Undefined geographic SRS" and "Undefined Cartesian SRS"
    1409         147 :         if (!STARTS_WITH(poSRS->GetName(), "Undefined "))
    1410         147 :             m_oSRS = *poSRS;
    1411             :     }
    1412             : 
    1413         642 :     std::vector<const CPLXMLNode *> apoXMLNodeBands;
    1414         321 :     if (psRoot)
    1415             :     {
    1416          28 :         int nExpectedBandNumber = 1;
    1417         122 :         for (const CPLXMLNode *psIter = psRoot->psChild; psIter;
    1418          94 :              psIter = psIter->psNext)
    1419             :         {
    1420          97 :             if (psIter->eType == CXT_Element &&
    1421          97 :                 strcmp(psIter->pszValue, GTI_XML_BAND_ELEMENT) == 0)
    1422             :             {
    1423             :                 const char *pszBand =
    1424           8 :                     CPLGetXMLValue(psIter, GTI_XML_BAND_NUMBER, nullptr);
    1425           8 :                 if (!pszBand)
    1426             :                 {
    1427           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1428             :                              "%s attribute missing on %s element",
    1429             :                              GTI_XML_BAND_NUMBER, GTI_XML_BAND_ELEMENT);
    1430           3 :                     return false;
    1431             :                 }
    1432           7 :                 const int nBand = atoi(pszBand);
    1433           7 :                 if (nBand <= 0)
    1434             :                 {
    1435           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1436             :                              "Invalid band number");
    1437           1 :                     return false;
    1438             :                 }
    1439           6 :                 if (nBand != nExpectedBandNumber)
    1440             :                 {
    1441           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1442             :                              "Invalid band number: found %d, expected %d",
    1443             :                              nBand, nExpectedBandNumber);
    1444           1 :                     return false;
    1445             :                 }
    1446           5 :                 apoXMLNodeBands.push_back(psIter);
    1447           5 :                 ++nExpectedBandNumber;
    1448             :             }
    1449             :         }
    1450             :     }
    1451             : 
    1452         318 :     const char *pszBandCount = GetOption(MD_BAND_COUNT);
    1453         318 :     if (pszBandCount)
    1454          67 :         nBandCount = atoi(pszBandCount);
    1455             : 
    1456         318 :     if (!apoXMLNodeBands.empty())
    1457             :     {
    1458           5 :         if (!pszBandCount)
    1459           4 :             nBandCount = static_cast<int>(apoXMLNodeBands.size());
    1460           1 :         else if (nBandCount != static_cast<int>(apoXMLNodeBands.size()))
    1461             :         {
    1462           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1463             :                      "Inconsistent %s with actual number of %s elements",
    1464             :                      GTI_XML_BANDCOUNT, GTI_XML_BAND_ELEMENT);
    1465           1 :             return false;
    1466             :         }
    1467             :     }
    1468             : 
    1469             :     // Take into STAC GeoParquet proj:code / proj:epsg / proj:wkt2 / proj:projjson
    1470             :     // and proj:transform fields
    1471         317 :     std::unique_ptr<OGRFeature> poFeature;
    1472         634 :     std::string osResX, osResY, osMinX, osMinY, osMaxX, osMaxY;
    1473         317 :     int iProjCode = -1;
    1474         317 :     int iProjEPSG = -1;
    1475         317 :     int iProjWKT2 = -1;
    1476         317 :     int iProjPROJSON = -1;
    1477         317 :     int iProjTransform = -1;
    1478             : 
    1479             :     const bool bIsStacGeoParquet =
    1480         324 :         STARTS_WITH(osLocationFieldName.c_str(), "assets.") &&
    1481           7 :         EQUAL(osLocationFieldName.c_str() + osLocationFieldName.size() -
    1482             :                   strlen(".href"),
    1483             :               ".href");
    1484         634 :     std::string osAssetName;
    1485         317 :     if (bIsStacGeoParquet)
    1486             :     {
    1487           7 :         m_bSTACCollection = true;
    1488          14 :         osAssetName = osLocationFieldName.substr(
    1489             :             strlen("assets."),
    1490          14 :             osLocationFieldName.size() - strlen("assets.") - strlen(".href"));
    1491             :     }
    1492             : 
    1493             :     const auto GetAssetFieldIndex =
    1494          38 :         [poLayerDefn, &osAssetName](const char *pszFieldName)
    1495             :     {
    1496          21 :         const int idx = poLayerDefn->GetFieldIndex(
    1497          21 :             CPLSPrintf("assets.%s.%s", osAssetName.c_str(), pszFieldName));
    1498          21 :         if (idx >= 0)
    1499           4 :             return idx;
    1500          17 :         return poLayerDefn->GetFieldIndex(pszFieldName);
    1501         317 :     };
    1502             : 
    1503           7 :     if (bIsStacGeoParquet && !pszSRS && !pszResX && !pszResY && !pszMinX &&
    1504           7 :         !pszMinY && !pszMaxX && !pszMaxY &&
    1505           7 :         ((iProjCode = GetAssetFieldIndex("proj:code")) >= 0 ||
    1506           4 :          (iProjEPSG = GetAssetFieldIndex("proj:epsg")) >= 0 ||
    1507           2 :          (iProjWKT2 = GetAssetFieldIndex("proj:wkt2")) >= 0 ||
    1508         325 :          (iProjPROJSON = GetAssetFieldIndex("proj:projjson")) >= 0) &&
    1509           7 :         ((iProjTransform = GetAssetFieldIndex("proj:transform")) >= 0))
    1510             :     {
    1511           7 :         poFeature.reset(m_poLayer->GetNextFeature());
    1512             :         const auto poProjTransformField =
    1513           7 :             poLayerDefn->GetFieldDefn(iProjTransform);
    1514           7 :         if (poFeature &&
    1515           7 :             ((iProjCode >= 0 && poFeature->IsFieldSet(iProjCode)) ||
    1516           4 :              (iProjEPSG >= 0 && poFeature->IsFieldSet(iProjEPSG)) ||
    1517           2 :              (iProjWKT2 >= 0 && poFeature->IsFieldSet(iProjWKT2)) ||
    1518           8 :              (iProjPROJSON >= 0 && poFeature->IsFieldSet(iProjPROJSON))) &&
    1519          25 :             poFeature->IsFieldSet(iProjTransform) &&
    1520          11 :             (poProjTransformField->GetType() == OFTRealList ||
    1521           4 :              poProjTransformField->GetType() == OFTIntegerList ||
    1522           0 :              poProjTransformField->GetType() == OFTInteger64List))
    1523             :         {
    1524          14 :             OGRSpatialReference oSTACSRS;
    1525           7 :             oSTACSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1526             : 
    1527           7 :             if (iProjCode >= 0 && poFeature->IsFieldSet(iProjCode))
    1528           3 :                 oSTACSRS.SetFromUserInput(
    1529             :                     poFeature->GetFieldAsString(iProjCode),
    1530             :                     OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
    1531             : 
    1532           4 :             else if (iProjEPSG >= 0 && poFeature->IsFieldSet(iProjEPSG))
    1533           2 :                 oSTACSRS.importFromEPSG(
    1534             :                     poFeature->GetFieldAsInteger(iProjEPSG));
    1535             : 
    1536           2 :             else if (iProjWKT2 >= 0 && poFeature->IsFieldSet(iProjWKT2))
    1537           1 :                 oSTACSRS.SetFromUserInput(
    1538             :                     poFeature->GetFieldAsString(iProjWKT2),
    1539             :                     OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
    1540             : 
    1541           1 :             else if (iProjPROJSON >= 0 && poFeature->IsFieldSet(iProjPROJSON))
    1542           1 :                 oSTACSRS.SetFromUserInput(
    1543             :                     poFeature->GetFieldAsString(iProjPROJSON),
    1544             :                     OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
    1545             : 
    1546           7 :             if (!oSTACSRS.IsEmpty())
    1547             :             {
    1548           6 :                 int nTransformCount = 0;
    1549             :                 // Note: different coefficient ordering than GDAL geotransform
    1550           6 :                 double adfProjTransform[6] = {0, 0, 0, 0, 0, 0};
    1551           6 :                 if (poProjTransformField->GetType() == OFTRealList)
    1552             :                 {
    1553             :                     const auto padfFeatureTransform =
    1554           2 :                         poFeature->GetFieldAsDoubleList(iProjTransform,
    1555             :                                                         &nTransformCount);
    1556           2 :                     if (nTransformCount >= 6)
    1557           2 :                         memcpy(adfProjTransform, padfFeatureTransform,
    1558             :                                6 * sizeof(double));
    1559             :                 }
    1560           4 :                 else if (poProjTransformField->GetType() == OFTInteger64List)
    1561             :                 {
    1562             :                     const auto paFeatureTransform =
    1563           0 :                         poFeature->GetFieldAsInteger64List(iProjTransform,
    1564             :                                                            &nTransformCount);
    1565           0 :                     if (nTransformCount >= 6)
    1566             :                     {
    1567           0 :                         for (int i = 0; i < 6; ++i)
    1568           0 :                             adfProjTransform[i] =
    1569           0 :                                 static_cast<double>(paFeatureTransform[i]);
    1570             :                     }
    1571             :                 }
    1572           4 :                 else if (poProjTransformField->GetType() == OFTIntegerList)
    1573             :                 {
    1574             :                     const auto paFeatureTransform =
    1575           4 :                         poFeature->GetFieldAsIntegerList(iProjTransform,
    1576             :                                                          &nTransformCount);
    1577           4 :                     if (nTransformCount >= 6)
    1578             :                     {
    1579          28 :                         for (int i = 0; i < 6; ++i)
    1580          24 :                             adfProjTransform[i] = paFeatureTransform[i];
    1581             :                     }
    1582             :                 }
    1583           6 :                 OGREnvelope sEnvelope;
    1584          12 :                 if (nTransformCount >= 6 && m_poLayer->GetSpatialRef() &&
    1585           6 :                     m_poLayer->GetExtent(&sEnvelope, /* bForce = */ true) ==
    1586             :                         OGRERR_NONE)
    1587             :                 {
    1588           6 :                     const double dfResX = adfProjTransform[0];
    1589           6 :                     osResX = CPLSPrintf("%.17g", dfResX);
    1590           6 :                     const double dfResY = std::fabs(adfProjTransform[4]);
    1591           6 :                     osResY = CPLSPrintf("%.17g", dfResY);
    1592             : 
    1593             :                     auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
    1594             :                         OGRCreateCoordinateTransformation(
    1595          12 :                             m_poLayer->GetSpatialRef(), &oSTACSRS));
    1596             :                     auto poInvCT = std::unique_ptr<OGRCoordinateTransformation>(
    1597          12 :                         poCT ? poCT->GetInverse() : nullptr);
    1598           6 :                     double dfOutMinX = 0;
    1599           6 :                     double dfOutMinY = 0;
    1600           6 :                     double dfOutMaxX = 0;
    1601           6 :                     double dfOutMaxY = 0;
    1602          12 :                     if (dfResX > 0 && dfResY > 0 && poCT && poInvCT &&
    1603          12 :                         poCT->TransformBounds(sEnvelope.MinX, sEnvelope.MinY,
    1604             :                                               sEnvelope.MaxX, sEnvelope.MaxY,
    1605             :                                               &dfOutMinX, &dfOutMinY,
    1606           6 :                                               &dfOutMaxX, &dfOutMaxY, 21))
    1607             :                     {
    1608           6 :                         constexpr double EPSILON = 1e-3;
    1609             :                         const bool bTileAlignedOnRes =
    1610           6 :                             (fmod(std::fabs(adfProjTransform[3]), dfResX) <=
    1611          12 :                                  EPSILON * dfResX &&
    1612           6 :                              fmod(std::fabs(adfProjTransform[5]), dfResY) <=
    1613           6 :                                  EPSILON * dfResY);
    1614             : 
    1615             :                         osMinX = CPLSPrintf(
    1616             :                             "%.17g",
    1617             :                             !bTileAlignedOnRes
    1618             :                                 ? dfOutMinX
    1619           6 :                                 : std::floor(dfOutMinX / dfResX) * dfResX);
    1620             :                         osMinY = CPLSPrintf(
    1621             :                             "%.17g",
    1622             :                             !bTileAlignedOnRes
    1623             :                                 ? dfOutMinY
    1624           6 :                                 : std::floor(dfOutMinY / dfResY) * dfResY);
    1625             :                         osMaxX = CPLSPrintf(
    1626             :                             "%.17g",
    1627             :                             !bTileAlignedOnRes
    1628             :                                 ? dfOutMaxX
    1629           6 :                                 : std::ceil(dfOutMaxX / dfResX) * dfResX);
    1630             :                         osMaxY = CPLSPrintf(
    1631             :                             "%.17g",
    1632             :                             !bTileAlignedOnRes
    1633             :                                 ? dfOutMaxY
    1634           6 :                                 : std::ceil(dfOutMaxY / dfResY) * dfResY);
    1635             : 
    1636           6 :                         m_oSRS = std::move(oSTACSRS);
    1637           6 :                         pszResX = osResX.c_str();
    1638           6 :                         pszResY = osResY.c_str();
    1639           6 :                         pszMinX = osMinX.c_str();
    1640           6 :                         pszMinY = osMinY.c_str();
    1641           6 :                         pszMaxX = osMaxX.c_str();
    1642           6 :                         pszMaxY = osMaxY.c_str();
    1643           6 :                         nCountMinMaxXY = 4;
    1644             : 
    1645           6 :                         poFeature.reset();
    1646           6 :                         m_poLayer->ResetReading();
    1647             : 
    1648             :                         m_poWarpedLayerKeeper =
    1649           6 :                             std::make_unique<OGRWarpedLayer>(
    1650           0 :                                 m_poLayer, /* iGeomField = */ 0,
    1651           6 :                                 /* bTakeOwnership = */ false, std::move(poCT),
    1652          12 :                                 std::move(poInvCT));
    1653           6 :                         m_poLayer = m_poWarpedLayerKeeper.get();
    1654           6 :                         poLayerDefn = m_poLayer->GetLayerDefn();
    1655             :                     }
    1656             :                 }
    1657             :             }
    1658             :         }
    1659             :     }
    1660             : 
    1661             :     // When SRS was explicitly specified and differs from the layer's geometry
    1662             :     // SRS, apply the behavior requested via SRS_BEHAVIOR:
    1663             :     //   REPROJECT  - wrap the layer in an OGRWarpedLayer so that
    1664             :     //                SetSpatialFilterRect() calls during raster reads operate
    1665             :     //                in the output SRS and are correctly transformed back to
    1666             :     //                the layer's native SRS before being applied to the index.
    1667             :     //   OVERRIDE   - keep m_oSRS as-is without reprojecting the layer (the
    1668             :     //                caller asserts the SRS metadata was simply wrong/missing).
    1669             :     //   (unset)    - emit a warning asking the user to be explicit.
    1670             :     // If the layer has no SRS, we always use the specified SRS silently.
    1671         317 :     if (!m_poWarpedLayerKeeper && !m_oSRS.IsEmpty() && pszSRS)
    1672             :     {
    1673           6 :         const auto poLayerSRS = m_poLayer->GetSpatialRef();
    1674           6 :         if (poLayerSRS && !m_oSRS.IsSame(poLayerSRS))
    1675             :         {
    1676           5 :             if (pszSRSBehavior && EQUAL(pszSRSBehavior, "REPROJECT"))
    1677             :             {
    1678             :                 auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
    1679           4 :                     OGRCreateCoordinateTransformation(poLayerSRS, &m_oSRS));
    1680             :                 auto poInvCT = std::unique_ptr<OGRCoordinateTransformation>(
    1681           4 :                     poCT ? poCT->GetInverse() : nullptr);
    1682           2 :                 if (poCT && poInvCT)
    1683             :                 {
    1684           2 :                     m_poWarpedLayerKeeper = std::make_unique<OGRWarpedLayer>(
    1685           0 :                         m_poLayer, /* iGeomField = */ 0,
    1686           2 :                         /* bTakeOwnership = */ false, std::move(poCT),
    1687           4 :                         std::move(poInvCT));
    1688           2 :                     m_poLayer = m_poWarpedLayerKeeper.get();
    1689           2 :                     poLayerDefn = m_poLayer->GetLayerDefn();
    1690           2 :                 }
    1691             :             }
    1692           3 :             else if (!pszSRSBehavior || !EQUAL(pszSRSBehavior, "OVERRIDE"))
    1693             :             {
    1694           2 :                 CPLError(
    1695             :                     CE_Warning, CPLE_AppDefined,
    1696             :                     "%s is not consistent with the SRS of the vector layer. "
    1697             :                     "If you intend to override the dataset SRS without "
    1698             :                     "reprojection, specify %s=OVERRIDE. If you intend to "
    1699             :                     "reproject from the source layer SRS to the specified "
    1700             :                     "SRS, specify %s=REPROJECT.",
    1701             :                     MD_SRS, MD_SRS_BEHAVIOR, MD_SRS_BEHAVIOR);
    1702             :             }
    1703             :             // OVERRIDE: m_oSRS is already set; no warping needed.
    1704             :         }
    1705             :     }
    1706             : 
    1707         317 :     OGREnvelope sEnvelope;
    1708         317 :     if (nCountMinMaxXY == 4)
    1709             :     {
    1710          60 :         sEnvelope.MinX = CPLAtof(pszMinX);
    1711          60 :         sEnvelope.MinY = CPLAtof(pszMinY);
    1712          60 :         sEnvelope.MaxX = CPLAtof(pszMaxX);
    1713          60 :         sEnvelope.MaxY = CPLAtof(pszMaxY);
    1714          60 :         if (!(sEnvelope.MaxX > sEnvelope.MinX))
    1715             :         {
    1716           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1717             :                      "%s metadata item must be > %s", MD_MAXX, MD_MINX);
    1718           1 :             return false;
    1719             :         }
    1720          59 :         if (!(sEnvelope.MaxY > sEnvelope.MinY))
    1721             :         {
    1722           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1723             :                      "%s metadata item must be > %s", MD_MAXY, MD_MINY);
    1724           1 :             return false;
    1725             :         }
    1726             :     }
    1727             : 
    1728         315 :     bool bHasMaskBand = false;
    1729         315 :     std::unique_ptr<GDALColorTable> poSingleColorTable;
    1730         385 :     if ((!pszBandCount && apoXMLNodeBands.empty()) ||
    1731          70 :         (!(pszResX && pszResY) && nCountXSizeYSizeGT == 0))
    1732             :     {
    1733         259 :         CPLDebug("GTI", "Inspecting one feature due to missing metadata items");
    1734         259 :         m_bScannedOneFeatureAtOpening = true;
    1735             : 
    1736         259 :         if (!poFeature)
    1737         258 :             poFeature.reset(m_poLayer->GetNextFeature());
    1738         516 :         if (!poFeature ||
    1739         257 :             !poFeature->IsFieldSetAndNotNull(m_nLocationFieldIndex))
    1740             :         {
    1741           2 :             CPLError(
    1742             :                 CE_Failure, CPLE_AppDefined,
    1743             :                 "BAND_COUNT(+DATA_TYPE+COLOR_INTERPRETATION)+ (RESX+RESY or "
    1744             :                 "XSIZE+YSIZE+GEOTRANSFORM) metadata items "
    1745             :                 "missing");
    1746          10 :             return false;
    1747             :         }
    1748             : 
    1749             :         const char *pszTileName =
    1750         257 :             poFeature->GetFieldAsString(m_nLocationFieldIndex);
    1751             :         const std::string osTileName(GetAbsoluteFileName(
    1752         257 :             pszTileName, osIndexDataset.c_str(), m_bSTACCollection));
    1753         257 :         pszTileName = osTileName.c_str();
    1754             : 
    1755             :         auto poTileDS = std::shared_ptr<GDALDataset>(
    1756             :             GDALDataset::Open(pszTileName,
    1757             :                               GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
    1758         257 :                               m_aosAllowedRasterDrivers.List()),
    1759         257 :             GDALDatasetUniquePtrReleaser());
    1760         257 :         if (!poTileDS)
    1761             :         {
    1762           1 :             return false;
    1763             :         }
    1764             : 
    1765             :         // do palette -> RGB(A) expansion if needed
    1766         256 :         if (!GTIAdjustBandCount(poTileDS, nBandCount, nullptr))
    1767           0 :             return false;
    1768             : 
    1769         256 :         const int nTileBandCount = poTileDS->GetRasterCount();
    1770         586 :         for (int i = 0; i < nTileBandCount; ++i)
    1771             :         {
    1772         330 :             auto poTileBand = poTileDS->GetRasterBand(i + 1);
    1773         330 :             aeDataTypes.push_back(poTileBand->GetRasterDataType());
    1774         330 :             int bHasNoData = FALSE;
    1775         330 :             const double dfNoData = poTileBand->GetNoDataValue(&bHasNoData);
    1776         330 :             aNoData.emplace_back(CPL_TO_BOOL(bHasNoData), dfNoData);
    1777         330 :             aeColorInterp.push_back(poTileBand->GetColorInterpretation());
    1778         330 :             if (nTileBandCount == 1)
    1779             :             {
    1780         221 :                 if (auto poCT = poTileBand->GetColorTable())
    1781             :                 {
    1782             :                     // We assume that this will apply to all tiles...
    1783             :                     // TODO: detect if that it is really the case, and warn
    1784             :                     // if not, or do approximate palette matching like
    1785             :                     // done in GDALRasterBand::GetIndexColorTranslationTo()
    1786           0 :                     poSingleColorTable.reset(poCT->Clone());
    1787             :                 }
    1788             :             }
    1789             : 
    1790         330 :             if (poTileBand->GetMaskFlags() == GMF_PER_DATASET)
    1791           2 :                 bHasMaskBand = true;
    1792             :         }
    1793         256 :         if (!pszBandCount && nBandCount == 0)
    1794         242 :             nBandCount = nTileBandCount;
    1795             : 
    1796         256 :         auto poTileSRS = poTileDS->GetSpatialRef();
    1797         256 :         if (!m_oSRS.IsEmpty() && poTileSRS && !m_oSRS.IsSame(poTileSRS))
    1798             :         {
    1799          16 :             CPLStringList aosOptions;
    1800          16 :             aosOptions.AddString("-of");
    1801          16 :             aosOptions.AddString("VRT");
    1802             : 
    1803          16 :             char *pszWKT = nullptr;
    1804          16 :             const char *const apszWKTOptions[] = {"FORMAT=WKT2_2019", nullptr};
    1805          16 :             m_oSRS.exportToWkt(&pszWKT, apszWKTOptions);
    1806          16 :             if (pszWKT)
    1807          16 :                 m_osWKT = pszWKT;
    1808          16 :             CPLFree(pszWKT);
    1809             : 
    1810          16 :             if (m_osWKT.empty())
    1811             :             {
    1812           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1813             :                          "Cannot export VRT SRS to WKT2");
    1814           0 :                 return false;
    1815             :             }
    1816             : 
    1817          16 :             aosOptions.AddString("-t_srs");
    1818          16 :             aosOptions.AddString(m_osWKT.c_str());
    1819             : 
    1820             :             GDALWarpAppOptions *psWarpOptions =
    1821          16 :                 GDALWarpAppOptionsNew(aosOptions.List(), nullptr);
    1822          16 :             GDALDatasetH ahSrcDS[] = {GDALDataset::ToHandle(poTileDS.get())};
    1823          16 :             int bUsageError = false;
    1824             :             auto poWarpDS =
    1825             :                 std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(GDALWarp(
    1826          16 :                     "", nullptr, 1, ahSrcDS, psWarpOptions, &bUsageError)));
    1827          16 :             GDALWarpAppOptionsFree(psWarpOptions);
    1828          16 :             if (!poWarpDS)
    1829             :             {
    1830           0 :                 return false;
    1831             :             }
    1832             : 
    1833          16 :             poTileDS = std::move(poWarpDS);
    1834          16 :             poTileSRS = poTileDS->GetSpatialRef();
    1835          16 :             CPL_IGNORE_RET_VAL(poTileSRS);
    1836             :         }
    1837             : 
    1838         256 :         GDALGeoTransform gtTile;
    1839         256 :         if (poTileDS->GetGeoTransform(gtTile) != CE_None)
    1840             :         {
    1841           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1842             :                      "Cannot find geotransform on %s", pszTileName);
    1843           1 :             return false;
    1844             :         }
    1845         255 :         if (!(gtTile.xrot == 0))
    1846             :         {
    1847           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1848             :                      "3rd value of GeoTransform of %s must be 0", pszTileName);
    1849           1 :             return false;
    1850             :         }
    1851         254 :         if (!(gtTile.yrot == 0))
    1852             :         {
    1853           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1854             :                      "5th value of GeoTransform of %s must be 0", pszTileName);
    1855           1 :             return false;
    1856             :         }
    1857             : 
    1858         253 :         const double dfResX = gtTile.xscale;
    1859         253 :         const double dfResY = gtTile.yscale;
    1860         253 :         if (!(dfResX > 0))
    1861             :         {
    1862           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1863             :                      "2nd value of GeoTransform of %s must be > 0",
    1864             :                      pszTileName);
    1865           1 :             return false;
    1866             :         }
    1867         252 :         if (!(dfResY != 0))
    1868             :         {
    1869           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1870             :                      "6th value of GeoTransform of %s must be != 0",
    1871             :                      pszTileName);
    1872           0 :             return false;
    1873             :         }
    1874             : 
    1875         492 :         if (!sEnvelope.IsInit() &&
    1876         240 :             m_poLayer->GetExtent(&sEnvelope, /* bForce = */ false) ==
    1877             :                 OGRERR_FAILURE)
    1878             :         {
    1879           2 :             if (m_poLayer->GetExtent(&sEnvelope, /* bForce = */ true) ==
    1880             :                 OGRERR_FAILURE)
    1881             :             {
    1882           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1883             :                          "Cannot get layer extent");
    1884           1 :                 return false;
    1885             :             }
    1886           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1887             :                      "Could get layer extent, but using a slower method");
    1888             :         }
    1889             : 
    1890         251 :         const double dfXSize = (sEnvelope.MaxX - sEnvelope.MinX) / dfResX;
    1891         251 :         if (!(dfXSize >= 0 && dfXSize < INT_MAX))
    1892             :         {
    1893           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1894             :                      "Too small %s, or wrong layer extent", MD_RESX);
    1895           1 :             return false;
    1896             :         }
    1897             : 
    1898         250 :         const double dfYSize =
    1899         250 :             (sEnvelope.MaxY - sEnvelope.MinY) / std::fabs(dfResY);
    1900         250 :         if (!(dfYSize >= 0 && dfYSize < INT_MAX))
    1901             :         {
    1902           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1903             :                      "Too small %s, or wrong layer extent", MD_RESY);
    1904           1 :             return false;
    1905             :         }
    1906             : 
    1907         249 :         m_gt.xorig = sEnvelope.MinX;
    1908         249 :         m_gt.xscale = dfResX;
    1909         249 :         m_gt.xrot = 0;
    1910         249 :         m_gt.yorig = sEnvelope.MaxY;
    1911         249 :         m_gt.yrot = 0;
    1912         249 :         m_gt.yscale = -std::fabs(dfResY);
    1913             : 
    1914         249 :         nRasterXSize = static_cast<int>(std::ceil(dfXSize));
    1915         249 :         nRasterYSize = static_cast<int>(std::ceil(dfYSize));
    1916             :     }
    1917             : 
    1918         305 :     if (pszXSize && pszYSize && pszGeoTransform)
    1919             :     {
    1920          12 :         const int nXSize = atoi(pszXSize);
    1921          12 :         if (nXSize <= 0)
    1922             :         {
    1923           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1924             :                      "%s metadata item must be > 0", MD_XSIZE);
    1925           6 :             return false;
    1926             :         }
    1927             : 
    1928          11 :         const int nYSize = atoi(pszYSize);
    1929          11 :         if (nYSize <= 0)
    1930             :         {
    1931           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1932             :                      "%s metadata item must be > 0", MD_YSIZE);
    1933           1 :             return false;
    1934             :         }
    1935             : 
    1936             :         const CPLStringList aosTokens(
    1937          10 :             CSLTokenizeString2(pszGeoTransform, ",", 0));
    1938          10 :         if (aosTokens.size() != 6)
    1939             :         {
    1940           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1941             :                      "%s metadata item must be 6 numeric values "
    1942             :                      "separated with comma",
    1943             :                      MD_GEOTRANSFORM);
    1944           1 :             return false;
    1945             :         }
    1946          63 :         for (int i = 0; i < 6; ++i)
    1947             :         {
    1948          54 :             m_gt[i] = CPLAtof(aosTokens[i]);
    1949             :         }
    1950           9 :         if (!(m_gt.xscale > 0))
    1951             :         {
    1952           0 :             CPLError(CE_Failure, CPLE_AppDefined, "2nd value of %s must be > 0",
    1953             :                      MD_GEOTRANSFORM);
    1954           0 :             return false;
    1955             :         }
    1956           9 :         if (!(m_gt.xrot == 0))
    1957             :         {
    1958           1 :             CPLError(CE_Failure, CPLE_AppDefined, "3rd value of %s must be 0",
    1959             :                      MD_GEOTRANSFORM);
    1960           1 :             return false;
    1961             :         }
    1962           8 :         if (!(m_gt.yrot == 0))
    1963             :         {
    1964           1 :             CPLError(CE_Failure, CPLE_AppDefined, "5th value of %s must be 0",
    1965             :                      MD_GEOTRANSFORM);
    1966           1 :             return false;
    1967             :         }
    1968           7 :         if (!(m_gt.yscale < 0))
    1969             :         {
    1970           1 :             CPLError(CE_Failure, CPLE_AppDefined, "6th value of %s must be < 0",
    1971             :                      MD_GEOTRANSFORM);
    1972           1 :             return false;
    1973             :         }
    1974           6 :         nRasterXSize = nXSize;
    1975          12 :         nRasterYSize = nYSize;
    1976             :     }
    1977         293 :     else if (pszResX && pszResY)
    1978             :     {
    1979          65 :         const double dfResX = CPLAtof(pszResX);
    1980          65 :         if (!(dfResX > 0))
    1981             :         {
    1982           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1983             :                      "RESX metadata item must be > 0");
    1984           1 :             return false;
    1985             :         }
    1986          64 :         const double dfResY = CPLAtof(pszResY);
    1987          64 :         if (!(dfResY > 0))
    1988             :         {
    1989           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1990             :                      "RESY metadata item must be > 0");
    1991           1 :             return false;
    1992             :         }
    1993             : 
    1994          63 :         if (nCountMinMaxXY == 4)
    1995             :         {
    1996          54 :             if (pszXSize || pszYSize || pszGeoTransform)
    1997             :             {
    1998           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1999             :                          "Ignoring %s, %s and %s when %s, "
    2000             :                          "%s, %s and %s are specified",
    2001             :                          MD_XSIZE, MD_YSIZE, MD_GEOTRANSFORM, MD_MINX, MD_MINY,
    2002             :                          MD_MAXX, MD_MAXY);
    2003             :             }
    2004             :         }
    2005          13 :         else if (!sEnvelope.IsInit() &&
    2006           4 :                  m_poLayer->GetExtent(&sEnvelope, /* bForce = */ false) ==
    2007             :                      OGRERR_FAILURE)
    2008             :         {
    2009           0 :             if (m_poLayer->GetExtent(&sEnvelope, /* bForce = */ true) ==
    2010             :                 OGRERR_FAILURE)
    2011             :             {
    2012           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2013             :                          "Cannot get layer extent");
    2014           0 :                 return false;
    2015             :             }
    2016           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    2017             :                      "Could get layer extent, but using a slower method");
    2018             :         }
    2019             : 
    2020          63 :         const double dfXSize = (sEnvelope.MaxX - sEnvelope.MinX) / dfResX;
    2021          63 :         if (!(dfXSize >= 0 && dfXSize < INT_MAX))
    2022             :         {
    2023           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    2024             :                      "Too small %s, or wrong layer extent", MD_RESX);
    2025           1 :             return false;
    2026             :         }
    2027             : 
    2028          62 :         const double dfYSize = (sEnvelope.MaxY - sEnvelope.MinY) / dfResY;
    2029          62 :         if (!(dfYSize >= 0 && dfYSize < INT_MAX))
    2030             :         {
    2031           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    2032             :                      "Too small %s, or wrong layer extent", MD_RESY);
    2033           1 :             return false;
    2034             :         }
    2035             : 
    2036          61 :         m_gt.xorig = sEnvelope.MinX;
    2037          61 :         m_gt.xscale = dfResX;
    2038          61 :         m_gt.xrot = 0;
    2039          61 :         m_gt.yorig = sEnvelope.MaxY;
    2040          61 :         m_gt.yrot = 0;
    2041          61 :         m_gt.yscale = -dfResY;
    2042          61 :         nRasterXSize = static_cast<int>(std::ceil(dfXSize));
    2043          61 :         nRasterYSize = static_cast<int>(std::ceil(dfYSize));
    2044             :     }
    2045             : 
    2046         295 :     if (nBandCount == 0 && !pszBandCount)
    2047             :     {
    2048           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s metadata item missing",
    2049             :                  MD_BAND_COUNT);
    2050           0 :         return false;
    2051             :     }
    2052             : 
    2053         295 :     if (!GDALCheckBandCount(nBandCount, false))
    2054           1 :         return false;
    2055             : 
    2056         294 :     if (aeDataTypes.empty() && !pszDataType)
    2057             :     {
    2058          25 :         aeDataTypes.resize(nBandCount, GDT_UInt8);
    2059             :     }
    2060         269 :     else if (pszDataType)
    2061             :     {
    2062          37 :         aeDataTypes.clear();
    2063          37 :         const CPLStringList aosTokens(CSLTokenizeString2(pszDataType, ", ", 0));
    2064          37 :         if (aosTokens.size() == 1)
    2065             :         {
    2066          35 :             const auto eDataType = GDALGetDataTypeByName(aosTokens[0]);
    2067          35 :             if (eDataType == GDT_Unknown)
    2068             :             {
    2069           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for %s",
    2070             :                          MD_DATA_TYPE);
    2071           1 :                 return false;
    2072             :             }
    2073          34 :             aeDataTypes.resize(nBandCount, eDataType);
    2074             :         }
    2075           2 :         else if (aosTokens.size() == nBandCount)
    2076             :         {
    2077           2 :             for (int i = 0; i < nBandCount; ++i)
    2078             :             {
    2079           2 :                 const auto eDataType = GDALGetDataTypeByName(aosTokens[i]);
    2080           2 :                 if (eDataType == GDT_Unknown)
    2081             :                 {
    2082           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    2083             :                              "Invalid value for %s", MD_DATA_TYPE);
    2084           1 :                     return false;
    2085             :                 }
    2086           1 :                 aeDataTypes.push_back(eDataType);
    2087             :             }
    2088             :         }
    2089             :         else
    2090             :         {
    2091           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    2092             :                      "Number of values in %s must be 1 or %s", MD_DATA_TYPE,
    2093             :                      MD_BAND_COUNT);
    2094           1 :             return false;
    2095             :         }
    2096             :     }
    2097             : 
    2098         291 :     const char *pszNoData = GetOption(MD_NODATA);
    2099         291 :     if (pszNoData)
    2100             :     {
    2101          22 :         const auto IsValidNoDataStr = [](const char *pszStr)
    2102             :         {
    2103          22 :             if (EQUAL(pszStr, "inf") || EQUAL(pszStr, "-inf") ||
    2104          18 :                 EQUAL(pszStr, "nan"))
    2105           6 :                 return true;
    2106          16 :             const auto eType = CPLGetValueType(pszStr);
    2107          16 :             return eType == CPL_VALUE_INTEGER || eType == CPL_VALUE_REAL;
    2108             :         };
    2109             : 
    2110          20 :         aNoData.clear();
    2111          20 :         const CPLStringList aosTokens(CSLTokenizeString2(pszNoData, ", ", 0));
    2112          20 :         if (aosTokens.size() == 1)
    2113             :         {
    2114          16 :             if (!EQUAL(aosTokens[0], "NONE"))
    2115             :             {
    2116          13 :                 if (!IsValidNoDataStr(aosTokens[0]))
    2117             :                 {
    2118           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    2119             :                              "Invalid value for %s", MD_NODATA);
    2120           1 :                     return false;
    2121             :                 }
    2122          12 :                 aNoData.resize(nBandCount,
    2123          24 :                                std::pair(true, CPLAtof(aosTokens[0])));
    2124             :             }
    2125             :         }
    2126           4 :         else if (aosTokens.size() == nBandCount)
    2127             :         {
    2128          12 :             for (int i = 0; i < nBandCount; ++i)
    2129             :             {
    2130          10 :                 if (EQUAL(aosTokens[i], "NONE"))
    2131             :                 {
    2132           1 :                     aNoData.emplace_back(false, 0);
    2133             :                 }
    2134           9 :                 else if (IsValidNoDataStr(aosTokens[i]))
    2135             :                 {
    2136           8 :                     aNoData.emplace_back(true, CPLAtof(aosTokens[i]));
    2137             :                 }
    2138             :                 else
    2139             :                 {
    2140           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    2141             :                              "Invalid value for %s", MD_NODATA);
    2142           1 :                     return false;
    2143             :                 }
    2144             :             }
    2145             :         }
    2146             :         else
    2147             :         {
    2148           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    2149             :                      "Number of values in %s must be 1 or %s", MD_NODATA,
    2150             :                      MD_BAND_COUNT);
    2151           1 :             return false;
    2152             :         }
    2153             :     }
    2154             : 
    2155         288 :     if (pszColorInterp)
    2156             :     {
    2157          54 :         aeColorInterp.clear();
    2158             :         const CPLStringList aosTokens(
    2159          54 :             CSLTokenizeString2(pszColorInterp, ", ", 0));
    2160          54 :         if (aosTokens.size() == 1)
    2161             :         {
    2162          11 :             const auto eInterp = GDALGetColorInterpretationByName(aosTokens[0]);
    2163          16 :             if (eInterp == GCI_Undefined &&
    2164           5 :                 !EQUAL(aosTokens[0],
    2165             :                        GDALGetColorInterpretationName(GCI_Undefined)))
    2166             :             {
    2167           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for %s",
    2168             :                          MD_COLOR_INTERPRETATION);
    2169           1 :                 return false;
    2170             :             }
    2171          10 :             aeColorInterp.resize(nBandCount, eInterp);
    2172             :         }
    2173          43 :         else if (aosTokens.size() == nBandCount)
    2174             :         {
    2175         178 :             for (int i = 0; i < nBandCount; ++i)
    2176             :             {
    2177             :                 const auto eInterp =
    2178         137 :                     GDALGetColorInterpretationByName(aosTokens[i]);
    2179         139 :                 if (eInterp == GCI_Undefined &&
    2180           2 :                     !EQUAL(aosTokens[i],
    2181             :                            GDALGetColorInterpretationName(GCI_Undefined)))
    2182             :                 {
    2183           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    2184             :                              "Invalid value for %s", MD_COLOR_INTERPRETATION);
    2185           1 :                     return false;
    2186             :                 }
    2187         136 :                 aeColorInterp.emplace_back(eInterp);
    2188             :             }
    2189             :         }
    2190             :         else
    2191             :         {
    2192           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    2193             :                      "Number of values in %s must be 1 or "
    2194             :                      "%s",
    2195             :                      MD_COLOR_INTERPRETATION, MD_BAND_COUNT);
    2196           1 :             return false;
    2197             :         }
    2198             :     }
    2199             : 
    2200             :     /* -------------------------------------------------------------------- */
    2201             :     /*      Create bands.                                                   */
    2202             :     /* -------------------------------------------------------------------- */
    2203         285 :     if (aeDataTypes.size() != static_cast<size_t>(nBandCount))
    2204             :     {
    2205           1 :         CPLError(
    2206             :             CE_Failure, CPLE_AppDefined,
    2207             :             "Number of data types values found not matching number of bands");
    2208           1 :         return false;
    2209             :     }
    2210         284 :     if (!aNoData.empty() && aNoData.size() != static_cast<size_t>(nBandCount))
    2211             :     {
    2212           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    2213             :                  "Number of nodata values found not matching number of bands");
    2214           1 :         return false;
    2215             :     }
    2216         556 :     if (!aeColorInterp.empty() &&
    2217         273 :         aeColorInterp.size() != static_cast<size_t>(nBandCount))
    2218             :     {
    2219           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    2220             :                  "Number of color interpretation values found not matching "
    2221             :                  "number of bands");
    2222           1 :         return false;
    2223             :     }
    2224             : 
    2225         282 :     int nBlockXSize = 256;
    2226         282 :     const char *pszBlockXSize = GetOption(MD_BLOCK_X_SIZE);
    2227         282 :     if (pszBlockXSize)
    2228             :     {
    2229           3 :         nBlockXSize = atoi(pszBlockXSize);
    2230           3 :         if (nBlockXSize <= 0)
    2231             :         {
    2232           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid %s",
    2233             :                      MD_BLOCK_X_SIZE);
    2234           1 :             return false;
    2235             :         }
    2236             :     }
    2237             : 
    2238         281 :     int nBlockYSize = 256;
    2239         281 :     const char *pszBlockYSize = GetOption(MD_BLOCK_Y_SIZE);
    2240         281 :     if (pszBlockYSize)
    2241             :     {
    2242           3 :         nBlockYSize = atoi(pszBlockYSize);
    2243           3 :         if (nBlockYSize <= 0)
    2244             :         {
    2245           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid %s",
    2246             :                      MD_BLOCK_Y_SIZE);
    2247           1 :             return false;
    2248             :         }
    2249             :     }
    2250             : 
    2251         280 :     if (nBlockXSize > INT_MAX / nBlockYSize)
    2252             :     {
    2253           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Too big %s * %s",
    2254             :                  MD_BLOCK_X_SIZE, MD_BLOCK_Y_SIZE);
    2255           1 :         return false;
    2256             :     }
    2257             : 
    2258         279 :     if (dfOvrFactor > 1.0)
    2259             :     {
    2260           4 :         m_gt.xscale *= dfOvrFactor;
    2261           4 :         m_gt.yscale *= dfOvrFactor;
    2262           4 :         nRasterXSize = static_cast<int>(std::ceil(nRasterXSize / dfOvrFactor));
    2263           4 :         nRasterYSize = static_cast<int>(std::ceil(nRasterYSize / dfOvrFactor));
    2264             :     }
    2265             : 
    2266         558 :     std::vector<std::string> aosDescriptions;
    2267         558 :     std::vector<double> adfCenterWavelength;
    2268         558 :     std::vector<double> adfFullWidthHalfMax;
    2269         558 :     std::vector<double> adfScale;
    2270         558 :     std::vector<double> adfOffset;
    2271         279 :     if (bIsStacGeoParquet && poFeature)
    2272             :     {
    2273           7 :         int nBandsIdx = poLayerDefn->GetFieldIndex(
    2274           7 :             CPLSPrintf("assets.%s.eo:bands", osAssetName.c_str()));
    2275           7 :         if (nBandsIdx < 0)
    2276           2 :             nBandsIdx = poLayerDefn->GetFieldIndex("bands");
    2277           7 :         if (nBandsIdx >= 0 &&
    2278          14 :             poLayerDefn->GetFieldDefn(nBandsIdx)->GetSubType() == OFSTJSON &&
    2279           7 :             poFeature->IsFieldSet(nBandsIdx))
    2280             :         {
    2281           7 :             const char *pszStr = poFeature->GetFieldAsString(nBandsIdx);
    2282          14 :             CPLJSONDocument oDoc;
    2283          21 :             if (oDoc.LoadMemory(pszStr) &&
    2284          14 :                 oDoc.GetRoot().GetType() == CPLJSONObject::Type::Array)
    2285             :             {
    2286          14 :                 const auto oArray = oDoc.GetRoot().ToArray();
    2287           7 :                 if (oArray.Size() == nBandCount)
    2288             :                 {
    2289           7 :                     int i = 0;
    2290           7 :                     aosDescriptions.resize(nBandCount);
    2291           7 :                     adfCenterWavelength.resize(nBandCount);
    2292           7 :                     adfFullWidthHalfMax.resize(nBandCount);
    2293          19 :                     for (const auto &oObj : oArray)
    2294             :                     {
    2295          12 :                         if (oObj.GetType() == CPLJSONObject::Type::Object)
    2296             :                         {
    2297          36 :                             auto osCommonName = oObj.GetString("common_name");
    2298          12 :                             if (osCommonName.empty())
    2299           4 :                                 osCommonName = oObj.GetString("eo:common_name");
    2300             :                             const auto eInterp =
    2301          12 :                                 GDALGetColorInterpFromSTACCommonName(
    2302             :                                     osCommonName.c_str());
    2303          12 :                             if (eInterp != GCI_Undefined)
    2304          11 :                                 aeColorInterp[i] = eInterp;
    2305             : 
    2306          12 :                             aosDescriptions[i] = oObj.GetString("name");
    2307             : 
    2308             :                             std::string osDescription =
    2309          36 :                                 oObj.GetString("description");
    2310          12 :                             if (!osDescription.empty())
    2311             :                             {
    2312           1 :                                 if (aosDescriptions[i].empty())
    2313           0 :                                     aosDescriptions[i] =
    2314           0 :                                         std::move(osDescription);
    2315             :                                 else
    2316           1 :                                     aosDescriptions[i]
    2317           1 :                                         .append(" (")
    2318           1 :                                         .append(osDescription)
    2319           1 :                                         .append(")");
    2320             :                             }
    2321             : 
    2322          24 :                             adfCenterWavelength[i] =
    2323          12 :                                 oObj.GetDouble("center_wavelength");
    2324          12 :                             if (adfCenterWavelength[i] == 0)
    2325          16 :                                 adfCenterWavelength[i] =
    2326           8 :                                     oObj.GetDouble("eo:center_wavelength");
    2327          24 :                             adfFullWidthHalfMax[i] =
    2328          12 :                                 oObj.GetDouble("full_width_half_max");
    2329          12 :                             if (adfFullWidthHalfMax[i] == 0)
    2330          16 :                                 adfFullWidthHalfMax[i] =
    2331           8 :                                     oObj.GetDouble("eo:full_width_half_max");
    2332             :                         }
    2333          12 :                         ++i;
    2334             :                     }
    2335             :                 }
    2336             :             }
    2337             :         }
    2338             : 
    2339           7 :         int nRasterBandsIdx = poLayerDefn->GetFieldIndex(
    2340           7 :             CPLSPrintf("assets.%s.raster:bands", osAssetName.c_str()));
    2341           7 :         if (nRasterBandsIdx < 0)
    2342           3 :             nRasterBandsIdx = poLayerDefn->GetFieldIndex("bands");
    2343           6 :         if (nRasterBandsIdx >= 0 &&
    2344           6 :             poLayerDefn->GetFieldDefn(nRasterBandsIdx)->GetSubType() ==
    2345          13 :                 OFSTJSON &&
    2346           6 :             poFeature->IsFieldSet(nRasterBandsIdx))
    2347             :         {
    2348           6 :             const char *pszStr = poFeature->GetFieldAsString(nRasterBandsIdx);
    2349          12 :             CPLJSONDocument oDoc;
    2350          18 :             if (oDoc.LoadMemory(pszStr) &&
    2351          12 :                 oDoc.GetRoot().GetType() == CPLJSONObject::Type::Array)
    2352             :             {
    2353          12 :                 const auto oArray = oDoc.GetRoot().ToArray();
    2354           6 :                 if (oArray.Size() == nBandCount)
    2355             :                 {
    2356           6 :                     int i = 0;
    2357           6 :                     adfScale.resize(nBandCount,
    2358           6 :                                     std::numeric_limits<double>::quiet_NaN());
    2359           6 :                     adfOffset.resize(nBandCount,
    2360           6 :                                      std::numeric_limits<double>::quiet_NaN());
    2361          14 :                     for (const auto &oObj : oArray)
    2362             :                     {
    2363           8 :                         if (oObj.GetType() == CPLJSONObject::Type::Object)
    2364             :                         {
    2365           8 :                             adfScale[i] = oObj.GetDouble(
    2366             :                                 "scale",
    2367             :                                 std::numeric_limits<double>::quiet_NaN());
    2368           8 :                             adfOffset[i] = oObj.GetDouble(
    2369             :                                 "offset",
    2370             :                                 std::numeric_limits<double>::quiet_NaN());
    2371             :                         }
    2372           8 :                         ++i;
    2373             :                     }
    2374             :                 }
    2375             :             }
    2376             :         }
    2377             :     }
    2378             : 
    2379         279 :     GDALTileIndexBand *poFirstBand = nullptr;
    2380         729 :     for (int i = 0; i < nBandCount; ++i)
    2381             :     {
    2382         450 :         GDALDataType eDataType = aeDataTypes[i];
    2383         450 :         if (!apoXMLNodeBands.empty())
    2384             :         {
    2385           4 :             const char *pszVal = CPLGetXMLValue(apoXMLNodeBands[i],
    2386             :                                                 GTI_XML_BAND_DATATYPE, nullptr);
    2387           4 :             if (pszVal)
    2388             :             {
    2389           3 :                 eDataType = GDALGetDataTypeByName(pszVal);
    2390           3 :                 if (eDataType == GDT_Unknown)
    2391           0 :                     return false;
    2392             :             }
    2393             :         }
    2394             :         auto poBandUniquePtr = std::make_unique<GDALTileIndexBand>(
    2395         900 :             this, i + 1, eDataType, nBlockXSize, nBlockYSize);
    2396         450 :         auto poBand = poBandUniquePtr.get();
    2397         450 :         SetBand(i + 1, poBandUniquePtr.release());
    2398         450 :         if (!poFirstBand)
    2399         279 :             poFirstBand = poBand;
    2400         450 :         if (poBand->GetRasterDataType() != poFirstBand->GetRasterDataType())
    2401             :         {
    2402           0 :             m_bSameDataType = false;
    2403             :         }
    2404             : 
    2405         450 :         if (!aosDescriptions.empty() && !aosDescriptions[i].empty())
    2406             :         {
    2407          12 :             poBand->GDALRasterBand::SetDescription(aosDescriptions[i].c_str());
    2408             :         }
    2409         450 :         if (!apoXMLNodeBands.empty())
    2410             :         {
    2411           4 :             const char *pszVal = CPLGetXMLValue(
    2412           4 :                 apoXMLNodeBands[i], GTI_XML_BAND_DESCRIPTION, nullptr);
    2413           4 :             if (pszVal)
    2414             :             {
    2415           2 :                 poBand->GDALRasterBand::SetDescription(pszVal);
    2416             :             }
    2417             :         }
    2418             : 
    2419         450 :         if (!aNoData.empty() && aNoData[i].first)
    2420             :         {
    2421          31 :             poBand->m_bNoDataValueSet = true;
    2422          31 :             poBand->m_dfNoDataValue = aNoData[i].second;
    2423             :         }
    2424         450 :         if (!apoXMLNodeBands.empty())
    2425             :         {
    2426           4 :             const char *pszVal = CPLGetXMLValue(
    2427           4 :                 apoXMLNodeBands[i], GTI_XML_BAND_NODATAVALUE, nullptr);
    2428           4 :             if (pszVal)
    2429             :             {
    2430           3 :                 poBand->m_bNoDataValueSet = true;
    2431           3 :                 poBand->m_dfNoDataValue = CPLAtof(pszVal);
    2432             :             }
    2433             :         }
    2434         899 :         if (poBand->m_bNoDataValueSet != poFirstBand->m_bNoDataValueSet ||
    2435         449 :             !IsSameNaNAware(poBand->m_dfNoDataValue,
    2436             :                             poFirstBand->m_dfNoDataValue))
    2437             :         {
    2438           6 :             m_bSameNoData = false;
    2439             :         }
    2440             : 
    2441         450 :         if (!aeColorInterp.empty())
    2442             :         {
    2443         437 :             poBand->m_eColorInterp = aeColorInterp[i];
    2444             :         }
    2445         450 :         if (!apoXMLNodeBands.empty())
    2446             :         {
    2447           4 :             const char *pszVal = CPLGetXMLValue(
    2448           4 :                 apoXMLNodeBands[i], GTI_XML_BAND_COLORINTERP, nullptr);
    2449           4 :             if (pszVal)
    2450             :             {
    2451           4 :                 poBand->m_eColorInterp =
    2452           4 :                     GDALGetColorInterpretationByName(pszVal);
    2453             :             }
    2454             :         }
    2455             : 
    2456         458 :         if (static_cast<int>(adfScale.size()) == nBandCount &&
    2457           8 :             !std::isnan(adfScale[i]))
    2458             :         {
    2459           4 :             poBand->m_dfScale = adfScale[i];
    2460             :         }
    2461         450 :         if (const char *pszScale =
    2462         450 :                 GetOption(CPLSPrintf("BAND_%d_%s", i + 1, MD_BAND_SCALE)))
    2463             :         {
    2464           6 :             poBand->m_dfScale = CPLAtof(pszScale);
    2465             :         }
    2466         450 :         if (!apoXMLNodeBands.empty())
    2467             :         {
    2468             :             const char *pszVal =
    2469           4 :                 CPLGetXMLValue(apoXMLNodeBands[i], GTI_XML_BAND_SCALE, nullptr);
    2470           4 :             if (pszVal)
    2471             :             {
    2472           2 :                 poBand->m_dfScale = CPLAtof(pszVal);
    2473             :             }
    2474             :         }
    2475             : 
    2476         458 :         if (static_cast<int>(adfOffset.size()) == nBandCount &&
    2477           8 :             !std::isnan(adfOffset[i]))
    2478             :         {
    2479           4 :             poBand->m_dfOffset = adfOffset[i];
    2480             :         }
    2481         450 :         if (const char *pszOffset =
    2482         450 :                 GetOption(CPLSPrintf("BAND_%d_%s", i + 1, MD_BAND_OFFSET)))
    2483             :         {
    2484           6 :             poBand->m_dfOffset = CPLAtof(pszOffset);
    2485             :         }
    2486         450 :         if (!apoXMLNodeBands.empty())
    2487             :         {
    2488           4 :             const char *pszVal = CPLGetXMLValue(apoXMLNodeBands[i],
    2489             :                                                 GTI_XML_BAND_OFFSET, nullptr);
    2490           4 :             if (pszVal)
    2491             :             {
    2492           2 :                 poBand->m_dfOffset = CPLAtof(pszVal);
    2493             :             }
    2494             :         }
    2495             : 
    2496         450 :         if (const char *pszUnit =
    2497         450 :                 GetOption(CPLSPrintf("BAND_%d_%s", i + 1, MD_BAND_UNITTYPE)))
    2498             :         {
    2499           6 :             poBand->m_osUnit = pszUnit;
    2500             :         }
    2501         450 :         if (!apoXMLNodeBands.empty())
    2502             :         {
    2503           4 :             const char *pszVal = CPLGetXMLValue(apoXMLNodeBands[i],
    2504             :                                                 GTI_XML_BAND_UNITTYPE, nullptr);
    2505           4 :             if (pszVal)
    2506             :             {
    2507           2 :                 poBand->m_osUnit = pszVal;
    2508             :             }
    2509             :         }
    2510             : 
    2511         450 :         if (!apoXMLNodeBands.empty())
    2512             :         {
    2513           4 :             const CPLXMLNode *psBandNode = apoXMLNodeBands[i];
    2514           4 :             poBand->oMDMD.XMLInit(psBandNode, TRUE);
    2515             : 
    2516           4 :             if (const CPLXMLNode *psCategoryNames =
    2517           4 :                     CPLGetXMLNode(psBandNode, GTI_XML_CATEGORYNAMES))
    2518             :             {
    2519             :                 poBand->m_aosCategoryNames =
    2520           2 :                     VRTParseCategoryNames(psCategoryNames);
    2521             :             }
    2522             : 
    2523           4 :             if (const CPLXMLNode *psColorTable =
    2524           4 :                     CPLGetXMLNode(psBandNode, GTI_XML_COLORTABLE))
    2525             :             {
    2526           2 :                 poBand->m_poColorTable = VRTParseColorTable(psColorTable);
    2527             :             }
    2528             : 
    2529           4 :             if (const CPLXMLNode *psRAT =
    2530           4 :                     CPLGetXMLNode(psBandNode, GTI_XML_RAT))
    2531             :             {
    2532             :                 poBand->m_poRAT =
    2533           2 :                     std::make_unique<GDALDefaultRasterAttributeTable>();
    2534           2 :                 poBand->m_poRAT->XMLInit(psRAT, "");
    2535             :             }
    2536             :         }
    2537             : 
    2538         462 :         if (static_cast<int>(adfCenterWavelength.size()) == nBandCount &&
    2539          12 :             adfCenterWavelength[i] != 0)
    2540             :         {
    2541           5 :             poBand->GDALRasterBand::SetMetadataItem(
    2542             :                 GDALMD_CENTRAL_WAVELENGTH_UM,
    2543           5 :                 CPLSPrintf("%g", adfCenterWavelength[i]), GDAL_MDD_IMAGERY);
    2544             :         }
    2545             : 
    2546         462 :         if (static_cast<int>(adfFullWidthHalfMax.size()) == nBandCount &&
    2547          12 :             adfFullWidthHalfMax[i] != 0)
    2548             :         {
    2549           5 :             poBand->GDALRasterBand::SetMetadataItem(
    2550           5 :                 GDALMD_FWHM_UM, CPLSPrintf("%g", adfFullWidthHalfMax[i]),
    2551             :                 GDAL_MDD_IMAGERY);
    2552             :         }
    2553             :     }
    2554             : 
    2555         279 :     if (nBandCount == 1 && poFirstBand && poSingleColorTable &&
    2556           0 :         !poFirstBand->m_poColorTable)
    2557           0 :         poFirstBand->m_poColorTable = std::move(poSingleColorTable);
    2558             : 
    2559         279 :     const char *pszMaskBand = GetOption(MD_MASK_BAND);
    2560         279 :     if (pszMaskBand)
    2561           7 :         bHasMaskBand = CPLTestBool(pszMaskBand);
    2562         279 :     if (bHasMaskBand)
    2563             :     {
    2564           9 :         m_poMaskBand = std::make_unique<GDALTileIndexBand>(
    2565          18 :             this, 0, GDT_UInt8, nBlockXSize, nBlockYSize);
    2566             :     }
    2567             : 
    2568         279 :     if (dfOvrFactor == 1.0)
    2569             :     {
    2570         275 :         if (psRoot)
    2571             :         {
    2572             :             // Return the number of child elements of psNode whose name is pszElt
    2573             :             const auto CountChildElements =
    2574          11 :                 [](const CPLXMLNode *psNode, const char *pszElt)
    2575             :             {
    2576          11 :                 int nCount = 0;
    2577          24 :                 for (const CPLXMLNode *psIter = psNode->psChild; psIter;
    2578          13 :                      psIter = psIter->psNext)
    2579             :                 {
    2580          13 :                     if (strcmp(psIter->pszValue, pszElt) == 0)
    2581           3 :                         ++nCount;
    2582             :                 }
    2583          11 :                 return nCount;
    2584             :             };
    2585             : 
    2586          92 :             for (const CPLXMLNode *psIter = psRoot->psChild; psIter;
    2587          71 :                  psIter = psIter->psNext)
    2588             :             {
    2589          73 :                 if (psIter->eType == CXT_Element &&
    2590          73 :                     strcmp(psIter->pszValue, GTI_XML_OVERVIEW_ELEMENT) == 0)
    2591             :                 {
    2592          12 :                     const char *pszDataset = CPLGetXMLValue(
    2593             :                         psIter, GTI_XML_OVERVIEW_DATASET, nullptr);
    2594             :                     const char *pszLayer =
    2595          12 :                         CPLGetXMLValue(psIter, GTI_XML_OVERVIEW_LAYER, nullptr);
    2596          12 :                     const char *pszFactor = CPLGetXMLValue(
    2597             :                         psIter, GTI_XML_OVERVIEW_FACTOR, nullptr);
    2598          12 :                     if (!pszDataset && !pszLayer && !pszFactor)
    2599             :                     {
    2600           1 :                         CPLError(
    2601             :                             CE_Failure, CPLE_AppDefined,
    2602             :                             "At least one of %s, %s or %s element "
    2603             :                             "must be present as an %s child",
    2604             :                             GTI_XML_OVERVIEW_DATASET, GTI_XML_OVERVIEW_LAYER,
    2605             :                             GTI_XML_OVERVIEW_FACTOR, GTI_XML_OVERVIEW_ELEMENT);
    2606           2 :                         return false;
    2607             :                     }
    2608             : 
    2609          11 :                     if (CountChildElements(psIter, GTI_XML_OVERVIEW_FACTOR) > 1)
    2610             :                     {
    2611           1 :                         CPLError(CE_Failure, CPLE_AppDefined,
    2612             :                                  "At most one of %s element "
    2613             :                                  "is allowed per %s child.",
    2614             :                                  GTI_XML_OVERVIEW_FACTOR,
    2615             :                                  GTI_XML_OVERVIEW_ELEMENT);
    2616           1 :                         return false;
    2617             :                     }
    2618             : 
    2619             :                     m_aoOverviewDescriptor.emplace_back(
    2620          20 :                         std::string(pszDataset ? pszDataset : ""),
    2621          20 :                         CPLStringList(
    2622             :                             GDALDeserializeOpenOptionsFromXML(psIter)),
    2623          20 :                         std::string(pszLayer ? pszLayer : ""),
    2624          30 :                         pszFactor ? CPLAtof(pszFactor) : 0.0);
    2625             :                 }
    2626             :             }
    2627             :         }
    2628             :         else
    2629             :         {
    2630         254 :             for (int iOvr = 0;; ++iOvr)
    2631             :             {
    2632             :                 const char *pszOvrDSName =
    2633         509 :                     GetOption(CPLSPrintf("OVERVIEW_%d_DATASET", iOvr));
    2634             :                 const char *pszOpenOptions =
    2635         509 :                     GetOption(CPLSPrintf("OVERVIEW_%d_OPEN_OPTIONS", iOvr));
    2636             :                 const char *pszOvrLayer =
    2637         509 :                     GetOption(CPLSPrintf("OVERVIEW_%d_LAYER", iOvr));
    2638             :                 const char *pszOvrFactor =
    2639         509 :                     GetOption(CPLSPrintf("OVERVIEW_%d_FACTOR", iOvr));
    2640         509 :                 if (!pszOvrDSName && !pszOvrLayer && !pszOvrFactor)
    2641             :                 {
    2642             :                     // Before GDAL 3.9.2, we started the iteration at 1.
    2643         496 :                     if (iOvr == 0)
    2644         242 :                         continue;
    2645         254 :                     break;
    2646             :                 }
    2647             :                 m_aoOverviewDescriptor.emplace_back(
    2648          26 :                     std::string(pszOvrDSName ? pszOvrDSName : ""),
    2649          26 :                     pszOpenOptions ? CPLStringList(CSLTokenizeString2(
    2650             :                                          pszOpenOptions, ",", 0))
    2651             :                                    : CPLStringList(),
    2652          26 :                     std::string(pszOvrLayer ? pszOvrLayer : ""),
    2653          39 :                     pszOvrFactor ? CPLAtof(pszOvrFactor) : 0.0);
    2654         255 :             }
    2655             :         }
    2656             :     }
    2657             : 
    2658         277 :     if (psRoot)
    2659             :     {
    2660          21 :         oMDMD.XMLInit(psRoot, TRUE);
    2661             :     }
    2662             :     else
    2663             :     {
    2664             :         // Set on the dataset all metadata items from the index layer which are
    2665             :         // not "reserved" keywords.
    2666         256 :         CSLConstList papszLayerMD = m_poLayer->GetMetadata();
    2667        1304 :         for (const auto &[pszKey, pszValue] :
    2668        1560 :              cpl::IterateNameValue(papszLayerMD))
    2669             :         {
    2670         652 :             if (STARTS_WITH_CI(pszKey, "OVERVIEW_"))
    2671             :             {
    2672          18 :                 continue;
    2673             :             }
    2674             : 
    2675         634 :             bool bIsVRTItem = false;
    2676        6286 :             for (const char *pszTest : apszTIOptions)
    2677             :             {
    2678        6226 :                 if (EQUAL(pszKey, pszTest))
    2679             :                 {
    2680         574 :                     bIsVRTItem = true;
    2681         574 :                     break;
    2682             :                 }
    2683             :             }
    2684         634 :             if (!bIsVRTItem)
    2685             :             {
    2686          60 :                 if (STARTS_WITH_CI(pszKey, "BAND_"))
    2687             :                 {
    2688          52 :                     const int nBandNr = atoi(pszKey + strlen("BAND_"));
    2689             :                     const char *pszNextUnderscore =
    2690          52 :                         strchr(pszKey + strlen("BAND_"), '_');
    2691          52 :                     if (pszNextUnderscore && nBandNr >= 1 && nBandNr <= nBands)
    2692             :                     {
    2693          42 :                         const char *pszKeyWithoutBand = pszNextUnderscore + 1;
    2694          42 :                         bool bIsReservedBandItem = false;
    2695         132 :                         for (const char *pszItem : apszReservedBandItems)
    2696             :                         {
    2697         108 :                             if (EQUAL(pszKeyWithoutBand, pszItem))
    2698             :                             {
    2699          18 :                                 bIsReservedBandItem = true;
    2700          18 :                                 break;
    2701             :                             }
    2702             :                         }
    2703          42 :                         if (!bIsReservedBandItem)
    2704             :                         {
    2705          24 :                             GetRasterBand(nBandNr)
    2706          24 :                                 ->GDALRasterBand::SetMetadataItem(
    2707             :                                     pszKeyWithoutBand, pszValue);
    2708             :                         }
    2709             :                     }
    2710             :                 }
    2711             :                 else
    2712             :                 {
    2713           8 :                     GDALDataset::SetMetadataItem(pszKey, pszValue);
    2714             :                 }
    2715             :             }
    2716             :         }
    2717             :     }
    2718             : 
    2719         277 :     const char *pszInterleave = GetOption(MD_INTERLEAVE);
    2720         277 :     if (pszInterleave)
    2721             :     {
    2722           5 :         GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, pszInterleave,
    2723             :                                      GDAL_MDD_IMAGE_STRUCTURE);
    2724           5 :         m_bBandInterleave = EQUAL(pszInterleave, "BAND");
    2725             :     }
    2726         272 :     else if (nBandCount > 1 && !GetMetadata(GDAL_MDD_IMAGE_STRUCTURE))
    2727             :     {
    2728          73 :         GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
    2729             :                                      GDAL_MDD_IMAGE_STRUCTURE);
    2730             :     }
    2731             : 
    2732             :     /* -------------------------------------------------------------------- */
    2733             :     /*      Initialize any PAM information.                                 */
    2734             :     /* -------------------------------------------------------------------- */
    2735         277 :     SetDescription(poOpenInfo->pszFilename);
    2736         277 :     TryLoadXML();
    2737             : 
    2738             :     /* -------------------------------------------------------------------- */
    2739             :     /*      Check for overviews.                                            */
    2740             :     /* -------------------------------------------------------------------- */
    2741         277 :     oOvManager.Initialize(this, osIndexDataset.c_str());
    2742             : 
    2743         277 :     m_osWarpMemory = CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
    2744         277 :                                           "WARPING_MEMORY_SIZE", "");
    2745             : 
    2746         277 :     return true;
    2747             : }
    2748             : 
    2749             : /************************************************************************/
    2750             : /*                          GetMetadataItem()                           */
    2751             : /************************************************************************/
    2752             : 
    2753         120 : const char *GDALTileIndexDataset::GetMetadataItem(const char *pszName,
    2754             :                                                   const char *pszDomain)
    2755             : {
    2756         120 :     if (pszName && pszDomain && EQUAL(pszDomain, "__DEBUG__"))
    2757             :     {
    2758          20 :         if (EQUAL(pszName, "SCANNED_ONE_FEATURE_AT_OPENING"))
    2759             :         {
    2760           4 :             return m_bScannedOneFeatureAtOpening ? "YES" : "NO";
    2761             :         }
    2762          16 :         else if (EQUAL(pszName, "NUMBER_OF_CONTRIBUTING_SOURCES"))
    2763             :         {
    2764           5 :             return CPLSPrintf("%d", static_cast<int>(m_aoSourceDesc.size()));
    2765             :         }
    2766          11 :         else if (EQUAL(pszName, "MULTI_THREADED_RASTERIO_LAST_USED"))
    2767             :         {
    2768          11 :             return m_bLastMustUseMultiThreading ? "1" : "0";
    2769             :         }
    2770             :     }
    2771         100 :     return GDALPamDataset::GetMetadataItem(pszName, pszDomain);
    2772             : }
    2773             : 
    2774             : /************************************************************************/
    2775             : /*               TileIndexSupportsEditingLayerMetadata()                */
    2776             : /************************************************************************/
    2777             : 
    2778          17 : bool GDALTileIndexDataset::TileIndexSupportsEditingLayerMetadata() const
    2779             : {
    2780          27 :     return eAccess == GA_Update && m_poVectorDS->GetDriver() &&
    2781          27 :            EQUAL(m_poVectorDS->GetDriver()->GetDescription(), "GPKG");
    2782             : }
    2783             : 
    2784             : /************************************************************************/
    2785             : /*                          SetMetadataItem()                           */
    2786             : /************************************************************************/
    2787             : 
    2788           3 : CPLErr GDALTileIndexDataset::SetMetadataItem(const char *pszName,
    2789             :                                              const char *pszValue,
    2790             :                                              const char *pszDomain)
    2791             : {
    2792           3 :     if (m_bXMLUpdatable)
    2793             :     {
    2794           1 :         m_bXMLModified = true;
    2795           1 :         return GDALDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    2796             :     }
    2797           2 :     else if (TileIndexSupportsEditingLayerMetadata())
    2798             :     {
    2799           1 :         m_poLayer->SetMetadataItem(pszName, pszValue, pszDomain);
    2800           1 :         return GDALDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    2801             :     }
    2802             :     else
    2803             :     {
    2804           1 :         return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    2805             :     }
    2806             : }
    2807             : 
    2808             : /************************************************************************/
    2809             : /*                            SetMetadata()                             */
    2810             : /************************************************************************/
    2811             : 
    2812           3 : CPLErr GDALTileIndexDataset::SetMetadata(CSLConstList papszMD,
    2813             :                                          const char *pszDomain)
    2814             : {
    2815           3 :     if (m_bXMLUpdatable)
    2816             :     {
    2817           1 :         m_bXMLModified = true;
    2818           1 :         return GDALDataset::SetMetadata(papszMD, pszDomain);
    2819             :     }
    2820           2 :     else if (TileIndexSupportsEditingLayerMetadata())
    2821             :     {
    2822           2 :         if (!pszDomain || pszDomain[0] == 0)
    2823             :         {
    2824           4 :             CPLStringList aosMD(CSLDuplicate(papszMD));
    2825             : 
    2826             :             // Reinject dataset reserved items
    2827          48 :             for (const char *pszItem : apszTIOptions)
    2828             :             {
    2829          46 :                 if (!aosMD.FetchNameValue(pszItem))
    2830             :                 {
    2831          46 :                     const char *pszValue = m_poLayer->GetMetadataItem(pszItem);
    2832          46 :                     if (pszValue)
    2833             :                     {
    2834           2 :                         aosMD.SetNameValue(pszItem, pszValue);
    2835             :                     }
    2836             :                 }
    2837             :             }
    2838             : 
    2839             :             // Reinject band metadata
    2840           2 :             CSLConstList papszExistingLayerMD = m_poLayer->GetMetadata();
    2841          17 :             for (int i = 0; papszExistingLayerMD && papszExistingLayerMD[i];
    2842             :                  ++i)
    2843             :             {
    2844          15 :                 if (STARTS_WITH_CI(papszExistingLayerMD[i], "BAND_"))
    2845             :                 {
    2846          12 :                     aosMD.AddString(papszExistingLayerMD[i]);
    2847             :                 }
    2848             :             }
    2849             : 
    2850           4 :             m_poLayer->SetMetadata(aosMD.List(), pszDomain);
    2851             :         }
    2852             :         else
    2853             :         {
    2854           0 :             m_poLayer->SetMetadata(papszMD, pszDomain);
    2855             :         }
    2856           2 :         return GDALDataset::SetMetadata(papszMD, pszDomain);
    2857             :     }
    2858             :     else
    2859             :     {
    2860           0 :         return GDALPamDataset::SetMetadata(papszMD, pszDomain);
    2861             :     }
    2862             : }
    2863             : 
    2864             : /************************************************************************/
    2865             : /*                    GDALTileIndexDatasetIdentify()                    */
    2866             : /************************************************************************/
    2867             : 
    2868       94571 : static int GDALTileIndexDatasetIdentify(GDALOpenInfo *poOpenInfo)
    2869             : {
    2870       94571 :     if (STARTS_WITH(poOpenInfo->pszFilename, GTI_PREFIX))
    2871          34 :         return true;
    2872             : 
    2873       94537 :     if (STARTS_WITH(poOpenInfo->pszFilename, "<GDALTileIndexDataset"))
    2874          52 :         return true;
    2875             : 
    2876       94485 :     if (poOpenInfo->nHeaderBytes >= 100 &&
    2877       33718 :         STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    2878             :                     "SQLite format 3"))
    2879             :     {
    2880        1098 :         if (ENDS_WITH_CI(poOpenInfo->pszFilename, ".gti.gpkg"))
    2881             :         {
    2882             :             // Most likely handled by GTI driver, but we can't be sure
    2883         629 :             return GDAL_IDENTIFY_UNKNOWN;
    2884             :         }
    2885         471 :         else if (poOpenInfo->IsSingleAllowedDriver("GTI") &&
    2886           2 :                  poOpenInfo->IsExtensionEqualToCI("gpkg"))
    2887             :         {
    2888           2 :             return true;
    2889             :         }
    2890             :     }
    2891             : 
    2892       93854 :     if (poOpenInfo->nHeaderBytes > 0 &&
    2893       33661 :         (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0)
    2894             :     {
    2895       67322 :         if (strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    2896       33645 :                    "<GDALTileIndexDataset") ||
    2897       67306 :             ENDS_WITH_CI(poOpenInfo->pszFilename, ".gti.fgb") ||
    2898       33645 :             ENDS_WITH_CI(poOpenInfo->pszFilename, ".gti.parquet"))
    2899             :         {
    2900          16 :             return true;
    2901             :         }
    2902       33645 :         else if (poOpenInfo->IsSingleAllowedDriver("GTI") &&
    2903           0 :                  (poOpenInfo->IsExtensionEqualToCI("fgb") ||
    2904           0 :                   poOpenInfo->IsExtensionEqualToCI("parquet")))
    2905             :         {
    2906           0 :             return true;
    2907             :         }
    2908             :     }
    2909             : 
    2910       93838 :     return false;
    2911             : }
    2912             : 
    2913             : /************************************************************************/
    2914             : /*                      GDALTileIndexDatasetOpen()                      */
    2915             : /************************************************************************/
    2916             : 
    2917         351 : static GDALDataset *GDALTileIndexDatasetOpen(GDALOpenInfo *poOpenInfo)
    2918             : {
    2919         351 :     if (GDALTileIndexDatasetIdentify(poOpenInfo) == GDAL_IDENTIFY_FALSE)
    2920           0 :         return nullptr;
    2921         702 :     auto poDS = std::make_unique<GDALTileIndexDataset>();
    2922         351 :     if (!poDS->Open(poOpenInfo))
    2923          74 :         return nullptr;
    2924         277 :     return poDS.release();
    2925             : }
    2926             : 
    2927             : /************************************************************************/
    2928             : /*                       ~GDALTileIndexDataset()                        */
    2929             : /************************************************************************/
    2930             : 
    2931         702 : GDALTileIndexDataset::~GDALTileIndexDataset()
    2932             : {
    2933         351 :     if (m_poVectorDS && m_poLayerToRelease)
    2934             :     {
    2935             :         // Reset the warped layer before releasing the SQL result layer, since
    2936             :         // the warped layer holds a reference to it.
    2937           4 :         m_poWarpedLayerKeeper.reset();
    2938           4 :         m_poVectorDS->ReleaseResultSet(m_poLayerToRelease);
    2939             :     }
    2940             : 
    2941         351 :     GDALTileIndexDataset::FlushCache(true);
    2942         702 : }
    2943             : 
    2944             : /************************************************************************/
    2945             : /*                             FlushCache()                             */
    2946             : /************************************************************************/
    2947             : 
    2948         352 : CPLErr GDALTileIndexDataset::FlushCache(bool bAtClosing)
    2949             : {
    2950         352 :     CPLErr eErr = CE_None;
    2951         352 :     if (bAtClosing && m_bXMLModified)
    2952             :     {
    2953             :         CPLXMLNode *psRoot =
    2954           1 :             CPLGetXMLNode(m_psXMLTree.get(), "=GDALTileIndexDataset");
    2955             : 
    2956             :         // Suppress existing dataset metadata
    2957             :         while (true)
    2958             :         {
    2959           1 :             CPLXMLNode *psExistingMetadata = CPLGetXMLNode(psRoot, "Metadata");
    2960           1 :             if (!psExistingMetadata)
    2961           1 :                 break;
    2962           0 :             CPLRemoveXMLChild(psRoot, psExistingMetadata);
    2963           0 :         }
    2964             : 
    2965             :         // Serialize new dataset metadata
    2966           1 :         if (CPLXMLNode *psMD = oMDMD.Serialize())
    2967           1 :             CPLAddXMLChild(psRoot, psMD);
    2968             : 
    2969             :         // Update existing band metadata
    2970           1 :         if (CPLGetXMLNode(psRoot, GTI_XML_BAND_ELEMENT))
    2971             :         {
    2972           0 :             for (CPLXMLNode *psIter = psRoot->psChild; psIter;
    2973           0 :                  psIter = psIter->psNext)
    2974             :             {
    2975           0 :                 if (psIter->eType == CXT_Element &&
    2976           0 :                     strcmp(psIter->pszValue, GTI_XML_BAND_ELEMENT))
    2977             :                 {
    2978             :                     const char *pszBand =
    2979           0 :                         CPLGetXMLValue(psIter, GTI_XML_BAND_NUMBER, nullptr);
    2980           0 :                     if (pszBand)
    2981             :                     {
    2982           0 :                         const int nBand = atoi(pszBand);
    2983           0 :                         if (nBand >= 1 && nBand <= nBands)
    2984             :                         {
    2985             :                             while (true)
    2986             :                             {
    2987             :                                 CPLXMLNode *psExistingMetadata =
    2988           0 :                                     CPLGetXMLNode(psIter, "Metadata");
    2989           0 :                                 if (!psExistingMetadata)
    2990           0 :                                     break;
    2991           0 :                                 CPLRemoveXMLChild(psIter, psExistingMetadata);
    2992           0 :                             }
    2993             : 
    2994           0 :                             auto poBand = cpl::down_cast<GDALTileIndexBand *>(
    2995           0 :                                 papoBands[nBand - 1]);
    2996           0 :                             if (CPLXMLNode *psMD = poBand->oMDMD.Serialize())
    2997           0 :                                 CPLAddXMLChild(psIter, psMD);
    2998             :                         }
    2999             :                     }
    3000             :                 }
    3001             :             }
    3002             :         }
    3003             :         else
    3004             :         {
    3005             :             // Create new band objects if they have metadata
    3006           2 :             std::vector<CPLXMLTreeCloser> aoBandXML;
    3007           1 :             bool bHasBandMD = false;
    3008           2 :             for (int i = 1; i <= nBands; ++i)
    3009             :             {
    3010             :                 auto poBand =
    3011           1 :                     cpl::down_cast<GDALTileIndexBand *>(papoBands[i - 1]);
    3012           1 :                 auto psMD = poBand->oMDMD.Serialize();
    3013           1 :                 if (psMD)
    3014           1 :                     bHasBandMD = true;
    3015           1 :                 aoBandXML.emplace_back(CPLXMLTreeCloser(psMD));
    3016             :             }
    3017           1 :             if (bHasBandMD)
    3018             :             {
    3019           2 :                 for (int i = 1; i <= nBands; ++i)
    3020             :                 {
    3021             :                     auto poBand =
    3022           1 :                         cpl::down_cast<GDALTileIndexBand *>(papoBands[i - 1]);
    3023             : 
    3024           1 :                     CPLXMLNode *psBand = CPLCreateXMLNode(psRoot, CXT_Element,
    3025             :                                                           GTI_XML_BAND_ELEMENT);
    3026           1 :                     CPLAddXMLAttributeAndValue(psBand, GTI_XML_BAND_NUMBER,
    3027             :                                                CPLSPrintf("%d", i));
    3028           1 :                     CPLAddXMLAttributeAndValue(
    3029             :                         psBand, GTI_XML_BAND_DATATYPE,
    3030             :                         GDALGetDataTypeName(poBand->GetRasterDataType()));
    3031             : 
    3032           1 :                     const char *pszDescription = poBand->GetDescription();
    3033           1 :                     if (pszDescription && pszDescription[0])
    3034           0 :                         CPLSetXMLValue(psBand, GTI_XML_BAND_DESCRIPTION,
    3035             :                                        pszDescription);
    3036             : 
    3037           1 :                     const auto eColorInterp = poBand->GetColorInterpretation();
    3038           1 :                     if (eColorInterp != GCI_Undefined)
    3039           1 :                         CPLSetXMLValue(
    3040             :                             psBand, GTI_XML_BAND_COLORINTERP,
    3041             :                             GDALGetColorInterpretationName(eColorInterp));
    3042             : 
    3043           1 :                     if (!std::isnan(poBand->m_dfOffset))
    3044           0 :                         CPLSetXMLValue(psBand, GTI_XML_BAND_OFFSET,
    3045             :                                        CPLSPrintf("%.16g", poBand->m_dfOffset));
    3046             : 
    3047           1 :                     if (!std::isnan(poBand->m_dfScale))
    3048           0 :                         CPLSetXMLValue(psBand, GTI_XML_BAND_SCALE,
    3049             :                                        CPLSPrintf("%.16g", poBand->m_dfScale));
    3050             : 
    3051           1 :                     if (!poBand->m_osUnit.empty())
    3052           0 :                         CPLSetXMLValue(psBand, GTI_XML_BAND_UNITTYPE,
    3053             :                                        poBand->m_osUnit.c_str());
    3054             : 
    3055           1 :                     if (poBand->m_bNoDataValueSet)
    3056             :                     {
    3057           0 :                         CPLSetXMLValue(
    3058             :                             psBand, GTI_XML_BAND_NODATAVALUE,
    3059           0 :                             VRTSerializeNoData(poBand->m_dfNoDataValue,
    3060             :                                                poBand->GetRasterDataType(), 18)
    3061             :                                 .c_str());
    3062             :                     }
    3063           1 :                     if (aoBandXML[i - 1])
    3064             :                     {
    3065           1 :                         CPLAddXMLChild(psBand, aoBandXML[i - 1].release());
    3066             :                     }
    3067             :                 }
    3068             :             }
    3069             :         }
    3070             : 
    3071           1 :         if (!CPLSerializeXMLTreeToFile(m_psXMLTree.get(), GetDescription()))
    3072           0 :             eErr = CE_Failure;
    3073             :     }
    3074             : 
    3075             :     // We also clear the cache of opened sources, in case the user would
    3076             :     // change the content of a source and would want the GTI dataset to see
    3077             :     // the refreshed content.
    3078         352 :     m_oMapSharedSources.clear();
    3079         352 :     m_dfLastMinXFilter = std::numeric_limits<double>::quiet_NaN();
    3080         352 :     m_dfLastMinYFilter = std::numeric_limits<double>::quiet_NaN();
    3081         352 :     m_dfLastMaxXFilter = std::numeric_limits<double>::quiet_NaN();
    3082         352 :     m_dfLastMaxYFilter = std::numeric_limits<double>::quiet_NaN();
    3083         352 :     m_anLastBands.clear();
    3084         352 :     m_aoSourceDesc.clear();
    3085         352 :     if (GDALPamDataset::FlushCache(bAtClosing) != CE_None)
    3086           0 :         eErr = CE_Failure;
    3087         352 :     return eErr;
    3088             : }
    3089             : 
    3090             : /************************************************************************/
    3091             : /*                           LoadOverviews()                            */
    3092             : /************************************************************************/
    3093             : 
    3094          76 : void GDALTileIndexDataset::LoadOverviews()
    3095             : {
    3096          76 :     if (m_apoOverviews.empty() && !m_aoOverviewDescriptor.empty())
    3097             :     {
    3098          44 :         for (const auto &[osDSName, aosOpenOptions, osLyrName, dfFactor] :
    3099          66 :              m_aoOverviewDescriptor)
    3100             :         {
    3101          44 :             CPLStringList aosNewOpenOptions(aosOpenOptions);
    3102          22 :             if (dfFactor != 0)
    3103             :             {
    3104             :                 aosNewOpenOptions.SetNameValue("@FACTOR",
    3105           6 :                                                CPLSPrintf("%.17g", dfFactor));
    3106             :             }
    3107          22 :             if (!osLyrName.empty())
    3108             :             {
    3109           5 :                 aosNewOpenOptions.SetNameValue("@LAYER", osLyrName.c_str());
    3110             :             }
    3111             : 
    3112          44 :             std::string osResolvedDSName(osDSName);
    3113          33 :             if (!m_osBaseDir.empty() && !osResolvedDSName.empty() &&
    3114          11 :                 (cpl::starts_with(osResolvedDSName, GTI_PREFIX)
    3115          11 :                      ? CPLIsFilenameRelative(osResolvedDSName.c_str() +
    3116             :                                              strlen(GTI_PREFIX))
    3117           9 :                      : CPLIsFilenameRelative(osResolvedDSName.c_str())))
    3118             :             {
    3119           3 :                 if (cpl::starts_with(osResolvedDSName, GTI_PREFIX))
    3120             :                 {
    3121             :                     osResolvedDSName =
    3122           2 :                         GTI_PREFIX +
    3123           2 :                         CPLFormFilenameSafe(m_osBaseDir.c_str(),
    3124           1 :                                             osResolvedDSName.c_str() +
    3125             :                                                 strlen(GTI_PREFIX),
    3126           1 :                                             nullptr);
    3127             :                 }
    3128             :                 else
    3129             :                 {
    3130           4 :                     osResolvedDSName = CPLFormFilenameSafe(
    3131           2 :                         m_osBaseDir.c_str(), osResolvedDSName.c_str(), nullptr);
    3132             :                 }
    3133             :             }
    3134             : 
    3135             :             std::unique_ptr<GDALDataset, GDALDatasetUniquePtrReleaser> poOvrDS(
    3136          22 :                 GDALDataset::Open(!osResolvedDSName.empty()
    3137          13 :                                       ? osResolvedDSName.c_str()
    3138           9 :                                       : GetDescription(),
    3139             :                                   GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
    3140          22 :                                   m_aosAllowedRasterDrivers.List(),
    3141          88 :                                   aosNewOpenOptions.List(), nullptr));
    3142             : 
    3143             :             // Make it possible to use the Factor option on a GeoTIFF for
    3144             :             // example and translate it to an overview level.
    3145          39 :             if (poOvrDS && dfFactor > 1 &&
    3146          43 :                 poOvrDS->GetRasterCount() == GetRasterCount() &&
    3147           4 :                 aosOpenOptions.FetchNameValue("OVERVIEW_LEVEL") == nullptr)
    3148             :             {
    3149           4 :                 auto poBand = poOvrDS->GetRasterBand(1);
    3150           5 :                 for (int iOvr = 0; iOvr < poBand->GetOverviewCount(); ++iOvr)
    3151             :                 {
    3152           1 :                     auto poOvrBand = poBand->GetOverview(iOvr);
    3153           1 :                     if (dfFactor * poOvrBand->GetXSize() <=
    3154           1 :                             poOvrDS->GetRasterXSize() ||
    3155           0 :                         dfFactor * poOvrBand->GetYSize() <=
    3156           0 :                             poOvrDS->GetRasterYSize())
    3157             :                     {
    3158           1 :                         GDALDataset *poNewOvrDS = GDALCreateOverviewDataset(
    3159             :                             poOvrDS.get(), iOvr, /*bThisLevelOnly = */ true);
    3160           1 :                         if (!poNewOvrDS)
    3161           0 :                             continue;
    3162             : 
    3163             :                         const auto RelativeDifferenceBelowThreshold =
    3164           2 :                             [](double a, double b, double dfRelativeThreshold)
    3165             :                         {
    3166           2 :                             return std::fabs(a - b) <=
    3167           2 :                                    std::fabs(a) * dfRelativeThreshold;
    3168             :                         };
    3169           1 :                         constexpr double RELATIVE_THRESHOLD = 0.01;
    3170           3 :                         if (RelativeDifferenceBelowThreshold(
    3171           1 :                                 dfFactor * poOvrBand->GetXSize(),
    3172           1 :                                 poOvrDS->GetRasterXSize(),
    3173           2 :                                 RELATIVE_THRESHOLD) &&
    3174           2 :                             RelativeDifferenceBelowThreshold(
    3175           1 :                                 dfFactor * poOvrBand->GetYSize(),
    3176           1 :                                 poOvrDS->GetRasterYSize(), RELATIVE_THRESHOLD))
    3177             :                         {
    3178           1 :                             CPLDebug("GTI",
    3179             :                                      "Using overview of size %dx%d as best "
    3180             :                                      "approximation for requested overview of "
    3181             :                                      "factor %f of %s",
    3182             :                                      poOvrBand->GetXSize(),
    3183             :                                      poOvrBand->GetYSize(), dfFactor,
    3184             :                                      osResolvedDSName.c_str());
    3185             :                         }
    3186             : 
    3187           1 :                         poOvrDS.reset(poNewOvrDS);
    3188             :                     }
    3189             :                 }
    3190             :             }
    3191             : 
    3192             :             const auto IsSmaller =
    3193          18 :                 [](const GDALDataset *a, const GDALDataset *b)
    3194             :             {
    3195          18 :                 return (a->GetRasterXSize() < b->GetRasterXSize() &&
    3196          19 :                         a->GetRasterYSize() <= b->GetRasterYSize()) ||
    3197           1 :                        (a->GetRasterYSize() < b->GetRasterYSize() &&
    3198          18 :                         a->GetRasterXSize() <= b->GetRasterXSize());
    3199             :             };
    3200             : 
    3201          56 :             if (poOvrDS &&
    3202          34 :                 ((m_apoOverviews.empty() && IsSmaller(poOvrDS.get(), this)) ||
    3203           1 :                  ((!m_apoOverviews.empty() &&
    3204          22 :                    IsSmaller(poOvrDS.get(), m_apoOverviews.back().get())))))
    3205             :             {
    3206          16 :                 if (poOvrDS->GetRasterCount() == GetRasterCount())
    3207             :                 {
    3208          32 :                     CPLDebug(
    3209             :                         "GTI", "Using overview of size %dx%d from %s",
    3210             :                         poOvrDS->GetRasterXSize(), poOvrDS->GetRasterYSize(),
    3211          21 :                         osResolvedDSName.empty() ? GetDescription()
    3212          11 :                                                  : osResolvedDSName.c_str());
    3213          16 :                     m_apoOverviews.emplace_back(std::move(poOvrDS));
    3214             :                     // Add the overviews of the overview, unless the OVERVIEW_LEVEL
    3215             :                     // option option or FACTOR is specified
    3216          16 :                     if (aosOpenOptions.FetchNameValue("OVERVIEW_LEVEL") ==
    3217          31 :                             nullptr &&
    3218          15 :                         dfFactor == 0)
    3219             :                     {
    3220          10 :                         const int nOverviewCount = m_apoOverviews.back()
    3221          10 :                                                        ->GetRasterBand(1)
    3222          10 :                                                        ->GetOverviewCount();
    3223          11 :                         for (int i = 0; i < nOverviewCount; ++i)
    3224             :                         {
    3225             :                             aosNewOpenOptions.SetNameValue("OVERVIEW_LEVEL",
    3226           1 :                                                            CPLSPrintf("%d", i));
    3227             :                             std::unique_ptr<GDALDataset,
    3228             :                                             GDALDatasetUniquePtrReleaser>
    3229             :                                 poOvrOfOvrDS(GDALDataset::Open(
    3230           1 :                                     !osResolvedDSName.empty()
    3231           1 :                                         ? osResolvedDSName.c_str()
    3232           0 :                                         : GetDescription(),
    3233             :                                     GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
    3234           1 :                                     m_aosAllowedRasterDrivers.List(),
    3235           4 :                                     aosNewOpenOptions.List(), nullptr));
    3236           2 :                             if (poOvrOfOvrDS &&
    3237           1 :                                 poOvrOfOvrDS->GetRasterCount() ==
    3238           3 :                                     GetRasterCount() &&
    3239           1 :                                 IsSmaller(poOvrOfOvrDS.get(),
    3240           1 :                                           m_apoOverviews.back().get()))
    3241             :                             {
    3242           2 :                                 CPLDebug("GTI",
    3243             :                                          "Using automatically overview of size "
    3244             :                                          "%dx%d from %s",
    3245             :                                          poOvrOfOvrDS->GetRasterXSize(),
    3246             :                                          poOvrOfOvrDS->GetRasterYSize(),
    3247           1 :                                          osResolvedDSName.empty()
    3248           0 :                                              ? GetDescription()
    3249           1 :                                              : osResolvedDSName.c_str());
    3250             :                                 m_apoOverviews.emplace_back(
    3251           1 :                                     std::move(poOvrOfOvrDS));
    3252             :                             }
    3253             :                         }
    3254             :                     }
    3255             :                 }
    3256             :                 else
    3257             :                 {
    3258           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    3259             :                              "%s has not the same number of bands as %s",
    3260           0 :                              poOvrDS->GetDescription(), GetDescription());
    3261             :                 }
    3262             :             }
    3263           6 :             else if (poOvrDS)
    3264             :             {
    3265           2 :                 CPLDebug("GTI",
    3266             :                          "Skipping overview of size %dx%d from %s as it is "
    3267             :                          "larger than a previously added overview",
    3268             :                          poOvrDS->GetRasterXSize(), poOvrDS->GetRasterYSize(),
    3269           2 :                          osResolvedDSName.empty() ? GetDescription()
    3270           0 :                                                   : osResolvedDSName.c_str());
    3271             :             }
    3272             :         }
    3273             :     }
    3274          76 : }
    3275             : 
    3276             : /************************************************************************/
    3277             : /*                          GetOverviewCount()                          */
    3278             : /************************************************************************/
    3279             : 
    3280         101 : int GDALTileIndexBand::GetOverviewCount()
    3281             : {
    3282         101 :     const int nPAMOverviews = GDALPamRasterBand::GetOverviewCount();
    3283         101 :     if (nPAMOverviews)
    3284          25 :         return nPAMOverviews;
    3285             : 
    3286          76 :     m_poDS->LoadOverviews();
    3287          76 :     return static_cast<int>(m_poDS->m_apoOverviews.size());
    3288             : }
    3289             : 
    3290             : /************************************************************************/
    3291             : /*                            GetOverview()                             */
    3292             : /************************************************************************/
    3293             : 
    3294          52 : GDALRasterBand *GDALTileIndexBand::GetOverview(int iOvr)
    3295             : {
    3296          52 :     if (iOvr < 0 || iOvr >= GetOverviewCount())
    3297           6 :         return nullptr;
    3298             : 
    3299          46 :     const int nPAMOverviews = GDALPamRasterBand::GetOverviewCount();
    3300          46 :     if (nPAMOverviews)
    3301          17 :         return GDALPamRasterBand::GetOverview(iOvr);
    3302             : 
    3303          29 :     if (nBand == 0)
    3304             :     {
    3305           4 :         auto poBand = m_poDS->m_apoOverviews[iOvr]->GetRasterBand(1);
    3306           4 :         if (!poBand)
    3307           0 :             return nullptr;
    3308           4 :         return poBand->GetMaskBand();
    3309             :     }
    3310             :     else
    3311             :     {
    3312          25 :         return m_poDS->m_apoOverviews[iOvr]->GetRasterBand(nBand);
    3313             :     }
    3314             : }
    3315             : 
    3316             : /************************************************************************/
    3317             : /*                          GetGeoTransform()                           */
    3318             : /************************************************************************/
    3319             : 
    3320          23 : CPLErr GDALTileIndexDataset::GetGeoTransform(GDALGeoTransform &gt) const
    3321             : {
    3322          23 :     gt = m_gt;
    3323          23 :     return CE_None;
    3324             : }
    3325             : 
    3326             : /************************************************************************/
    3327             : /*                           GetSpatialRef()                            */
    3328             : /************************************************************************/
    3329             : 
    3330          23 : const OGRSpatialReference *GDALTileIndexDataset::GetSpatialRef() const
    3331             : {
    3332          23 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3333             : }
    3334             : 
    3335             : /************************************************************************/
    3336             : /*                         GDALTileIndexBand()                          */
    3337             : /************************************************************************/
    3338             : 
    3339         459 : GDALTileIndexBand::GDALTileIndexBand(GDALTileIndexDataset *poDSIn, int nBandIn,
    3340             :                                      GDALDataType eDT, int nBlockXSizeIn,
    3341         459 :                                      int nBlockYSizeIn)
    3342             : {
    3343         459 :     m_poDS = poDSIn;
    3344         459 :     nBand = nBandIn;
    3345         459 :     eDataType = eDT;
    3346         459 :     nRasterXSize = poDSIn->GetRasterXSize();
    3347         459 :     nRasterYSize = poDSIn->GetRasterYSize();
    3348         459 :     nBlockXSize = nBlockXSizeIn;
    3349         459 :     nBlockYSize = nBlockYSizeIn;
    3350         459 : }
    3351             : 
    3352             : /************************************************************************/
    3353             : /*                             IReadBlock()                             */
    3354             : /************************************************************************/
    3355             : 
    3356          13 : CPLErr GDALTileIndexBand::IReadBlock(int nBlockXOff, int nBlockYOff,
    3357             :                                      void *pImage)
    3358             : 
    3359             : {
    3360          13 :     const int nPixelSize = GDALGetDataTypeSizeBytes(eDataType);
    3361             : 
    3362          13 :     int nReadXSize = nBlockXSize;
    3363          13 :     int nReadYSize = nBlockYSize;
    3364          13 :     GetActualBlockSize(nBlockXOff, nBlockYOff, &nReadXSize, &nReadYSize);
    3365             : 
    3366             :     GDALRasterIOExtraArg sExtraArg;
    3367          13 :     INIT_RASTERIO_EXTRA_ARG(sExtraArg);
    3368             : 
    3369          26 :     return IRasterIO(
    3370          13 :         GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize, nReadXSize,
    3371             :         nReadYSize, pImage, nReadXSize, nReadYSize, eDataType, nPixelSize,
    3372          26 :         static_cast<GSpacing>(nPixelSize) * nBlockXSize, &sExtraArg);
    3373             : }
    3374             : 
    3375             : /************************************************************************/
    3376             : /*                             IRasterIO()                              */
    3377             : /************************************************************************/
    3378             : 
    3379         208 : CPLErr GDALTileIndexBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
    3380             :                                     int nXSize, int nYSize, void *pData,
    3381             :                                     int nBufXSize, int nBufYSize,
    3382             :                                     GDALDataType eBufType, GSpacing nPixelSpace,
    3383             :                                     GSpacing nLineSpace,
    3384             :                                     GDALRasterIOExtraArg *psExtraArg)
    3385             : {
    3386         208 :     int anBand[] = {nBand};
    3387             : 
    3388         208 :     return m_poDS->IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
    3389             :                              nBufXSize, nBufYSize, eBufType, 1, anBand,
    3390         416 :                              nPixelSpace, nLineSpace, 0, psExtraArg);
    3391             : }
    3392             : 
    3393             : /************************************************************************/
    3394             : /*                       IGetDataCoverageStatus()                       */
    3395             : /************************************************************************/
    3396             : 
    3397             : #ifndef HAVE_GEOS
    3398             : int GDALTileIndexBand::IGetDataCoverageStatus(int /* nXOff */, int /* nYOff */,
    3399             :                                               int /* nXSize */,
    3400             :                                               int /* nYSize */,
    3401             :                                               int /* nMaskFlagStop */,
    3402             :                                               double *pdfDataPct)
    3403             : {
    3404             :     if (pdfDataPct != nullptr)
    3405             :         *pdfDataPct = -1.0;
    3406             :     return GDAL_DATA_COVERAGE_STATUS_UNIMPLEMENTED |
    3407             :            GDAL_DATA_COVERAGE_STATUS_DATA;
    3408             : }
    3409             : #else
    3410           9 : int GDALTileIndexBand::IGetDataCoverageStatus(int nXOff, int nYOff, int nXSize,
    3411             :                                               int nYSize, int nMaskFlagStop,
    3412             :                                               double *pdfDataPct)
    3413             : {
    3414           9 :     if (pdfDataPct != nullptr)
    3415           9 :         *pdfDataPct = -1.0;
    3416             : 
    3417           9 :     const double dfMinX = m_poDS->m_gt.xorig + nXOff * m_poDS->m_gt.xscale;
    3418           9 :     const double dfMaxX = dfMinX + nXSize * m_poDS->m_gt.xscale;
    3419           9 :     const double dfMaxY = m_poDS->m_gt.yorig + nYOff * m_poDS->m_gt.yscale;
    3420           9 :     const double dfMinY = dfMaxY + nYSize * m_poDS->m_gt.yscale;
    3421             : 
    3422           9 :     OGRLayer *poSQLLayer = nullptr;
    3423           9 :     if (!m_poDS->m_osSpatialSQL.empty())
    3424             :     {
    3425             :         const std::string osSQL =
    3426           2 :             CPLString(m_poDS->m_osSpatialSQL)
    3427           4 :                 .replaceAll("{XMIN}", CPLSPrintf("%.17g", dfMinX))
    3428           4 :                 .replaceAll("{YMIN}", CPLSPrintf("%.17g", dfMinY))
    3429           4 :                 .replaceAll("{XMAX}", CPLSPrintf("%.17g", dfMaxX))
    3430           4 :                 .replaceAll("{YMAX}", CPLSPrintf("%.17g", dfMaxY));
    3431             :         poSQLLayer =
    3432           2 :             m_poDS->m_poVectorDS->ExecuteSQL(osSQL.c_str(), nullptr, nullptr);
    3433           2 :         if (!poSQLLayer)
    3434           1 :             return 0;
    3435             :     }
    3436             :     else
    3437             :     {
    3438           7 :         m_poDS->m_poLayer->SetSpatialFilterRect(dfMinX, dfMinY, dfMaxX, dfMaxY);
    3439           7 :         m_poDS->m_poLayer->ResetReading();
    3440             :     }
    3441             : 
    3442           8 :     OGRLayer *const poLayer = poSQLLayer ? poSQLLayer : m_poDS->m_poLayer;
    3443             : 
    3444           8 :     int nStatus = 0;
    3445             : 
    3446          16 :     auto poPolyNonCoveredBySources = std::make_unique<OGRPolygon>();
    3447             :     {
    3448          16 :         auto poLR = std::make_unique<OGRLinearRing>();
    3449           8 :         poLR->addPoint(nXOff, nYOff);
    3450           8 :         poLR->addPoint(nXOff, nYOff + nYSize);
    3451           8 :         poLR->addPoint(nXOff + nXSize, nYOff + nYSize);
    3452           8 :         poLR->addPoint(nXOff + nXSize, nYOff);
    3453           8 :         poLR->addPoint(nXOff, nYOff);
    3454           8 :         poPolyNonCoveredBySources->addRingDirectly(poLR.release());
    3455             :     }
    3456             :     while (true)
    3457             :     {
    3458          12 :         auto poFeature = std::unique_ptr<OGRFeature>(poLayer->GetNextFeature());
    3459          12 :         if (!poFeature)
    3460           2 :             break;
    3461          10 :         if (!poFeature->IsFieldSetAndNotNull(m_poDS->m_nLocationFieldIndex))
    3462             :         {
    3463           0 :             continue;
    3464             :         }
    3465             : 
    3466          10 :         const auto poGeom = poFeature->GetGeometryRef();
    3467          10 :         if (!poGeom || poGeom->IsEmpty())
    3468           0 :             continue;
    3469             : 
    3470          10 :         OGREnvelope sSourceEnvelope;
    3471          10 :         poGeom->getEnvelope(&sSourceEnvelope);
    3472             : 
    3473             :         const double dfDstXOff = std::max<double>(
    3474             :             nXOff,
    3475          10 :             (sSourceEnvelope.MinX - m_poDS->m_gt.xorig) / m_poDS->m_gt.xscale);
    3476             :         const double dfDstXOff2 = std::min<double>(
    3477          10 :             nXOff + nXSize,
    3478          10 :             (sSourceEnvelope.MaxX - m_poDS->m_gt.xorig) / m_poDS->m_gt.xscale);
    3479             :         const double dfDstYOff = std::max<double>(
    3480             :             nYOff,
    3481          10 :             (sSourceEnvelope.MaxY - m_poDS->m_gt.yorig) / m_poDS->m_gt.yscale);
    3482             :         const double dfDstYOff2 = std::min<double>(
    3483          10 :             nYOff + nYSize,
    3484          10 :             (sSourceEnvelope.MinY - m_poDS->m_gt.yorig) / m_poDS->m_gt.yscale);
    3485             : 
    3486             :         // CPLDebug("GTI", "dfDstXOff=%f, dfDstXOff2=%f, dfDstYOff=%f, dfDstYOff2=%f",
    3487             :         //         dfDstXOff, dfDstXOff2, dfDstYOff, dfDstXOff2);
    3488             : 
    3489             :         // Check if the AOI is fully inside the source
    3490          10 :         if (nXOff >= dfDstXOff && nYOff >= dfDstYOff &&
    3491           7 :             nXOff + nXSize <= dfDstXOff2 && nYOff + nYSize <= dfDstYOff2)
    3492             :         {
    3493           4 :             if (pdfDataPct)
    3494           4 :                 *pdfDataPct = 100.0;
    3495           4 :             return GDAL_DATA_COVERAGE_STATUS_DATA;
    3496             :         }
    3497             : 
    3498             :         // Check intersection of bounding boxes.
    3499           6 :         if (dfDstXOff2 > nXOff && dfDstYOff2 > nYOff &&
    3500           6 :             dfDstXOff < nXOff + nXSize && dfDstYOff < nYOff + nYSize)
    3501             :         {
    3502           6 :             nStatus |= GDAL_DATA_COVERAGE_STATUS_DATA;
    3503           6 :             if (poPolyNonCoveredBySources)
    3504             :             {
    3505           6 :                 OGRPolygon oPolySource;
    3506           6 :                 auto poLR = std::make_unique<OGRLinearRing>();
    3507           6 :                 poLR->addPoint(dfDstXOff, dfDstYOff);
    3508           6 :                 poLR->addPoint(dfDstXOff, dfDstYOff2);
    3509           6 :                 poLR->addPoint(dfDstXOff2, dfDstYOff2);
    3510           6 :                 poLR->addPoint(dfDstXOff2, dfDstYOff);
    3511           6 :                 poLR->addPoint(dfDstXOff, dfDstYOff);
    3512           6 :                 oPolySource.addRingDirectly(poLR.release());
    3513             :                 auto poRes = std::unique_ptr<OGRGeometry>(
    3514           6 :                     poPolyNonCoveredBySources->Difference(&oPolySource));
    3515           6 :                 if (poRes && poRes->IsEmpty())
    3516             :                 {
    3517           2 :                     if (pdfDataPct)
    3518           2 :                         *pdfDataPct = 100.0;
    3519           2 :                     return GDAL_DATA_COVERAGE_STATUS_DATA;
    3520             :                 }
    3521           4 :                 else if (poRes && poRes->getGeometryType() == wkbPolygon)
    3522             :                 {
    3523           4 :                     poPolyNonCoveredBySources.reset(
    3524             :                         poRes.release()->toPolygon());
    3525             :                 }
    3526             :                 else
    3527             :                 {
    3528           0 :                     poPolyNonCoveredBySources.reset();
    3529             :                 }
    3530             :             }
    3531             :         }
    3532           4 :         if (nMaskFlagStop != 0 && (nStatus & nMaskFlagStop) != 0)
    3533             :         {
    3534           0 :             if (poSQLLayer)
    3535           0 :                 m_poDS->ReleaseResultSet(poSQLLayer);
    3536           0 :             return nStatus;
    3537             :         }
    3538           4 :     }
    3539             : 
    3540           2 :     if (poSQLLayer)
    3541           0 :         m_poDS->ReleaseResultSet(poSQLLayer);
    3542             : 
    3543           2 :     if (poPolyNonCoveredBySources)
    3544             :     {
    3545           2 :         if (!poPolyNonCoveredBySources->IsEmpty())
    3546           2 :             nStatus |= GDAL_DATA_COVERAGE_STATUS_EMPTY;
    3547           2 :         if (pdfDataPct)
    3548           2 :             *pdfDataPct = 100.0 * (1.0 - poPolyNonCoveredBySources->get_Area() /
    3549           2 :                                              nXSize / nYSize);
    3550             :     }
    3551           2 :     return nStatus;
    3552             : }
    3553             : #endif  // HAVE_GEOS
    3554             : 
    3555             : /************************************************************************/
    3556             : /*                       GetMetadataDomainList()                        */
    3557             : /************************************************************************/
    3558             : 
    3559           1 : char **GDALTileIndexBand::GetMetadataDomainList()
    3560             : {
    3561           1 :     return CSLAddString(GDALRasterBand::GetMetadataDomainList(),
    3562           1 :                         "LocationInfo");
    3563             : }
    3564             : 
    3565             : /************************************************************************/
    3566             : /*                          GetMetadataItem()                           */
    3567             : /************************************************************************/
    3568             : 
    3569          44 : const char *GDALTileIndexBand::GetMetadataItem(const char *pszName,
    3570             :                                                const char *pszDomain)
    3571             : 
    3572             : {
    3573             :     /* ==================================================================== */
    3574             :     /*      LocationInfo handling.                                          */
    3575             :     /* ==================================================================== */
    3576          44 :     if (pszDomain != nullptr && EQUAL(pszDomain, "LocationInfo") &&
    3577          19 :         (STARTS_WITH_CI(pszName, "Pixel_") ||
    3578           6 :          STARTS_WITH_CI(pszName, "GeoPixel_")))
    3579             :     {
    3580             :         // What pixel are we aiming at?
    3581          18 :         int iPixel = 0;
    3582          18 :         int iLine = 0;
    3583             : 
    3584          18 :         if (STARTS_WITH_CI(pszName, "Pixel_"))
    3585             :         {
    3586          13 :             pszName += strlen("Pixel_");
    3587          13 :             iPixel = atoi(pszName);
    3588          13 :             const char *const pszUnderscore = strchr(pszName, '_');
    3589          13 :             if (!pszUnderscore)
    3590           2 :                 return nullptr;
    3591          11 :             iLine = atoi(pszUnderscore + 1);
    3592             :         }
    3593           5 :         else if (STARTS_WITH_CI(pszName, "GeoPixel_"))
    3594             :         {
    3595           5 :             pszName += strlen("GeoPixel_");
    3596           5 :             const double dfGeoX = CPLAtof(pszName);
    3597           5 :             const char *const pszUnderscore = strchr(pszName, '_');
    3598           5 :             if (!pszUnderscore)
    3599           2 :                 return nullptr;
    3600           3 :             const double dfGeoY = CPLAtof(pszUnderscore + 1);
    3601             : 
    3602           3 :             double adfInvGeoTransform[6] = {0.0};
    3603           3 :             if (!GDALInvGeoTransform(m_poDS->m_gt.data(), adfInvGeoTransform))
    3604           0 :                 return nullptr;
    3605             : 
    3606           3 :             iPixel = static_cast<int>(floor(adfInvGeoTransform[0] +
    3607           3 :                                             adfInvGeoTransform[1] * dfGeoX +
    3608           3 :                                             adfInvGeoTransform[2] * dfGeoY));
    3609           3 :             iLine = static_cast<int>(floor(adfInvGeoTransform[3] +
    3610           3 :                                            adfInvGeoTransform[4] * dfGeoX +
    3611           3 :                                            adfInvGeoTransform[5] * dfGeoY));
    3612             :         }
    3613             :         else
    3614             :         {
    3615           0 :             return nullptr;
    3616             :         }
    3617             : 
    3618          23 :         if (iPixel < 0 || iLine < 0 || iPixel >= GetXSize() ||
    3619           9 :             iLine >= GetYSize())
    3620           6 :             return nullptr;
    3621             : 
    3622           8 :         const int anBand[] = {nBand};
    3623           8 :         if (!m_poDS->CollectSources(iPixel, iLine, 1, 1, 1, anBand,
    3624             :                                     /* bMultiThreadAllowed = */ false))
    3625           0 :             return nullptr;
    3626             : 
    3627             :         // Format into XML.
    3628           8 :         m_osLastLocationInfo = "<LocationInfo>";
    3629             : 
    3630           8 :         if (!m_poDS->m_aoSourceDesc.empty())
    3631             :         {
    3632             :             const auto AddSource =
    3633           6 :                 [&](const GDALTileIndexDataset::SourceDesc &oSourceDesc)
    3634             :             {
    3635           6 :                 m_osLastLocationInfo += "<File>";
    3636             :                 char *const pszXMLEscaped =
    3637           6 :                     CPLEscapeString(oSourceDesc.osName.c_str(), -1, CPLES_XML);
    3638           6 :                 m_osLastLocationInfo += pszXMLEscaped;
    3639           6 :                 CPLFree(pszXMLEscaped);
    3640           6 :                 m_osLastLocationInfo += "</File>";
    3641          11 :             };
    3642             : 
    3643           5 :             if (!m_poDS->NeedInitBuffer(1, anBand))
    3644             :             {
    3645           4 :                 AddSource(m_poDS->m_aoSourceDesc.back());
    3646             :             }
    3647             :             else
    3648             :             {
    3649           3 :                 for (const auto &oSourceDesc : m_poDS->m_aoSourceDesc)
    3650             :                 {
    3651           2 :                     if (oSourceDesc.poDS)
    3652           2 :                         AddSource(oSourceDesc);
    3653             :                 }
    3654             :             }
    3655             :         }
    3656             : 
    3657           8 :         m_osLastLocationInfo += "</LocationInfo>";
    3658             : 
    3659           8 :         return m_osLastLocationInfo.c_str();
    3660             :     }
    3661             : 
    3662          26 :     return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
    3663             : }
    3664             : 
    3665             : /************************************************************************/
    3666             : /*                          SetMetadataItem()                           */
    3667             : /************************************************************************/
    3668             : 
    3669          13 : CPLErr GDALTileIndexBand::SetMetadataItem(const char *pszName,
    3670             :                                           const char *pszValue,
    3671             :                                           const char *pszDomain)
    3672             : {
    3673          13 :     if (nBand > 0 && m_poDS->m_bXMLUpdatable)
    3674             :     {
    3675           1 :         m_poDS->m_bXMLModified = true;
    3676           1 :         return GDALRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
    3677             :     }
    3678          12 :     else if (nBand > 0 && m_poDS->TileIndexSupportsEditingLayerMetadata())
    3679             :     {
    3680           6 :         m_poDS->m_poLayer->SetMetadataItem(
    3681           6 :             CPLSPrintf("BAND_%d_%s", nBand, pszName), pszValue, pszDomain);
    3682           6 :         return GDALRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
    3683             :     }
    3684             :     else
    3685             :     {
    3686           6 :         return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
    3687             :     }
    3688             : }
    3689             : 
    3690             : /************************************************************************/
    3691             : /*                            SetMetadata()                             */
    3692             : /************************************************************************/
    3693             : 
    3694           2 : CPLErr GDALTileIndexBand::SetMetadata(CSLConstList papszMD,
    3695             :                                       const char *pszDomain)
    3696             : {
    3697           2 :     if (nBand > 0 && m_poDS->m_bXMLUpdatable)
    3698             :     {
    3699           1 :         m_poDS->m_bXMLModified = true;
    3700           1 :         return GDALRasterBand::SetMetadata(papszMD, pszDomain);
    3701             :     }
    3702           1 :     else if (nBand > 0 && m_poDS->TileIndexSupportsEditingLayerMetadata())
    3703             :     {
    3704           2 :         CPLStringList aosMD;
    3705             : 
    3706           1 :         if (!pszDomain || pszDomain[0] == 0)
    3707             :         {
    3708             :             // Reinject dataset metadata
    3709             :             CSLConstList papszLayerMD =
    3710           1 :                 m_poDS->m_poLayer->GetMetadata(pszDomain);
    3711          14 :             for (const char *const *papszIter = papszLayerMD;
    3712          14 :                  papszIter && *papszIter; ++papszIter)
    3713             :             {
    3714          13 :                 if (!STARTS_WITH(*papszIter, "BAND_") ||
    3715          12 :                     STARTS_WITH(*papszIter, MD_BAND_COUNT))
    3716           1 :                     aosMD.AddString(*papszIter);
    3717             :             }
    3718             :         }
    3719             : 
    3720           8 :         for (int i = 0; papszMD && papszMD[i]; ++i)
    3721             :         {
    3722           7 :             aosMD.AddString(CPLSPrintf("BAND_%d_%s", nBand, papszMD[i]));
    3723             :         }
    3724             : 
    3725           1 :         if (!pszDomain || pszDomain[0] == 0)
    3726             :         {
    3727           4 :             for (const char *pszItem : apszReservedBandItems)
    3728             :             {
    3729           3 :                 const char *pszKey = CPLSPrintf("BAND_%d_%s", nBand, pszItem);
    3730           3 :                 if (!aosMD.FetchNameValue(pszKey))
    3731             :                 {
    3732           3 :                     if (const char *pszVal =
    3733           3 :                             m_poDS->m_poLayer->GetMetadataItem(pszKey))
    3734             :                     {
    3735           3 :                         aosMD.SetNameValue(pszKey, pszVal);
    3736             :                     }
    3737             :                 }
    3738             :             }
    3739             :         }
    3740             : 
    3741           1 :         m_poDS->m_poLayer->SetMetadata(aosMD.List(), pszDomain);
    3742           1 :         return GDALRasterBand::SetMetadata(papszMD, pszDomain);
    3743             :     }
    3744             :     else
    3745             :     {
    3746           0 :         return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
    3747             :     }
    3748             : }
    3749             : 
    3750             : /************************************************************************/
    3751             : /*                            GetSrcDstWin()                            */
    3752             : /************************************************************************/
    3753             : 
    3754         429 : static bool GetSrcDstWin(const GDALGeoTransform &tileGT, int nTileXSize,
    3755             :                          int nTileYSize, const GDALGeoTransform &vrtGT,
    3756             :                          int nVRTXSize, int nVRTYSize, double *pdfSrcXOff,
    3757             :                          double *pdfSrcYOff, double *pdfSrcXSize,
    3758             :                          double *pdfSrcYSize, double *pdfDstXOff,
    3759             :                          double *pdfDstYOff, double *pdfDstXSize,
    3760             :                          double *pdfDstYSize)
    3761             : {
    3762         429 :     const double minX = vrtGT.xorig;
    3763         429 :     const double we_res = vrtGT.xscale;
    3764         429 :     const double maxX = minX + nVRTXSize * we_res;
    3765         429 :     const double maxY = vrtGT.yorig;
    3766         429 :     const double ns_res = vrtGT.yscale;
    3767         429 :     const double minY = maxY + nVRTYSize * ns_res;
    3768             : 
    3769             :     /* Check that the destination bounding box intersects the source bounding
    3770             :      * box */
    3771         429 :     if (tileGT.xorig + nTileXSize * tileGT.xscale <= minX)
    3772           0 :         return false;
    3773         429 :     if (tileGT.xorig >= maxX)
    3774           1 :         return false;
    3775         428 :     if (tileGT.yorig + nTileYSize * tileGT.yscale >= maxY)
    3776           0 :         return false;
    3777         428 :     if (tileGT.yorig <= minY)
    3778           0 :         return false;
    3779             : 
    3780         428 :     if (tileGT.xorig < minX)
    3781             :     {
    3782           1 :         *pdfSrcXOff = (minX - tileGT.xorig) / tileGT.xscale;
    3783           1 :         *pdfDstXOff = 0.0;
    3784             :     }
    3785             :     else
    3786             :     {
    3787         427 :         *pdfSrcXOff = 0.0;
    3788         427 :         *pdfDstXOff = ((tileGT.xorig - minX) / we_res);
    3789             :     }
    3790         428 :     if (maxY < tileGT.yorig)
    3791             :     {
    3792           1 :         *pdfSrcYOff = (tileGT.yorig - maxY) / -tileGT.yscale;
    3793           1 :         *pdfDstYOff = 0.0;
    3794             :     }
    3795             :     else
    3796             :     {
    3797         427 :         *pdfSrcYOff = 0.0;
    3798         427 :         *pdfDstYOff = ((maxY - tileGT.yorig) / -ns_res);
    3799             :     }
    3800             : 
    3801         428 :     *pdfSrcXSize = nTileXSize;
    3802         428 :     *pdfSrcYSize = nTileYSize;
    3803         428 :     if (*pdfSrcXOff > 0)
    3804           1 :         *pdfSrcXSize -= *pdfSrcXOff;
    3805         428 :     if (*pdfSrcYOff > 0)
    3806           1 :         *pdfSrcYSize -= *pdfSrcYOff;
    3807             : 
    3808         428 :     const double dfSrcToDstXSize = tileGT.xscale / we_res;
    3809         428 :     *pdfDstXSize = *pdfSrcXSize * dfSrcToDstXSize;
    3810         428 :     const double dfSrcToDstYSize = tileGT.yscale / ns_res;
    3811         428 :     *pdfDstYSize = *pdfSrcYSize * dfSrcToDstYSize;
    3812             : 
    3813         428 :     if (*pdfDstXOff + *pdfDstXSize > nVRTXSize)
    3814             :     {
    3815           3 :         *pdfDstXSize = nVRTXSize - *pdfDstXOff;
    3816           3 :         *pdfSrcXSize = *pdfDstXSize / dfSrcToDstXSize;
    3817             :     }
    3818             : 
    3819         428 :     if (*pdfDstYOff + *pdfDstYSize > nVRTYSize)
    3820             :     {
    3821           1 :         *pdfDstYSize = nVRTYSize - *pdfDstYOff;
    3822           1 :         *pdfSrcYSize = *pdfDstYSize / dfSrcToDstYSize;
    3823             :     }
    3824             : 
    3825         856 :     return *pdfSrcXSize > 0 && *pdfDstXSize > 0 && *pdfSrcYSize > 0 &&
    3826         856 :            *pdfDstYSize > 0;
    3827             : }
    3828             : 
    3829             : /************************************************************************/
    3830             : /*                    GDALDatasetCastToGTIDataset()                     */
    3831             : /************************************************************************/
    3832             : 
    3833           3 : GDALTileIndexDataset *GDALDatasetCastToGTIDataset(GDALDataset *poDS)
    3834             : {
    3835           3 :     return dynamic_cast<GDALTileIndexDataset *>(poDS);
    3836             : }
    3837             : 
    3838             : /************************************************************************/
    3839             : /*                    GTIGetSourcesMoreRecentThan()                     */
    3840             : /************************************************************************/
    3841             : 
    3842             : std::vector<GTISourceDesc>
    3843           2 : GTIGetSourcesMoreRecentThan(GDALTileIndexDataset *poDS, int64_t mTime)
    3844             : {
    3845           2 :     return poDS->GetSourcesMoreRecentThan(mTime);
    3846             : }
    3847             : 
    3848             : /************************************************************************/
    3849             : /*                      GetSourcesMoreRecentThan()                      */
    3850             : /************************************************************************/
    3851             : 
    3852             : std::vector<GTISourceDesc>
    3853           2 : GDALTileIndexDataset::GetSourcesMoreRecentThan(int64_t mTime)
    3854             : {
    3855           2 :     std::vector<GTISourceDesc> oRes;
    3856             : 
    3857           2 :     m_poLayer->SetSpatialFilter(nullptr);
    3858           6 :     for (auto &&poFeature : m_poLayer)
    3859             :     {
    3860           4 :         if (!poFeature->IsFieldSetAndNotNull(m_nLocationFieldIndex))
    3861             :         {
    3862           2 :             continue;
    3863             :         }
    3864             : 
    3865           4 :         auto poGeom = poFeature->GetGeometryRef();
    3866           4 :         if (!poGeom || poGeom->IsEmpty())
    3867           0 :             continue;
    3868             : 
    3869           4 :         OGREnvelope sEnvelope;
    3870           4 :         poGeom->getEnvelope(&sEnvelope);
    3871             : 
    3872           4 :         double dfXOff = (sEnvelope.MinX - m_gt.xorig) / m_gt.xscale;
    3873           4 :         if (dfXOff >= nRasterXSize)
    3874           0 :             continue;
    3875             : 
    3876           4 :         double dfYOff = (sEnvelope.MaxY - m_gt.yorig) / m_gt.yscale;
    3877           4 :         if (dfYOff >= nRasterYSize)
    3878           0 :             continue;
    3879             : 
    3880           4 :         double dfXSize = (sEnvelope.MaxX - sEnvelope.MinX) / m_gt.xscale;
    3881           4 :         if (dfXOff < 0)
    3882             :         {
    3883           0 :             dfXSize += dfXOff;
    3884           0 :             dfXOff = 0;
    3885           0 :             if (dfXSize <= 0)
    3886           0 :                 continue;
    3887             :         }
    3888             : 
    3889           4 :         double dfYSize =
    3890           4 :             (sEnvelope.MaxY - sEnvelope.MinY) / std::fabs(m_gt.yscale);
    3891           4 :         if (dfYOff < 0)
    3892             :         {
    3893           0 :             dfYSize += dfYOff;
    3894           0 :             dfYOff = 0;
    3895           0 :             if (dfYSize <= 0)
    3896           0 :                 continue;
    3897             :         }
    3898             : 
    3899             :         const char *pszTileName =
    3900           4 :             poFeature->GetFieldAsString(m_nLocationFieldIndex);
    3901             :         std::string osTileName(GetAbsoluteFileName(
    3902           4 :             pszTileName, GetDescription(), m_bSTACCollection));
    3903             :         VSIStatBufL sStatSource;
    3904           8 :         if (VSIStatL(osTileName.c_str(), &sStatSource) != 0 ||
    3905           4 :             sStatSource.st_mtime <= mTime)
    3906             :         {
    3907           2 :             continue;
    3908             :         }
    3909             : 
    3910           2 :         constexpr double EPS = 1e-8;
    3911           4 :         GTISourceDesc oSourceDesc;
    3912           2 :         oSourceDesc.osFilename = std::move(osTileName);
    3913           2 :         oSourceDesc.nDstXOff = static_cast<int>(dfXOff + EPS);
    3914           2 :         oSourceDesc.nDstYOff = static_cast<int>(dfYOff + EPS);
    3915           2 :         oSourceDesc.nDstXSize = static_cast<int>(dfXSize + 0.5);
    3916           2 :         oSourceDesc.nDstYSize = static_cast<int>(dfYSize + 0.5);
    3917           2 :         oRes.emplace_back(std::move(oSourceDesc));
    3918             :     }
    3919             : 
    3920           2 :     return oRes;
    3921             : }
    3922             : 
    3923             : /************************************************************************/
    3924             : /*                           GetSourceDesc()                            */
    3925             : /************************************************************************/
    3926             : 
    3927         432 : bool GDALTileIndexDataset::GetSourceDesc(const std::string &osTileName,
    3928             :                                          SourceDesc &oSourceDesc,
    3929             :                                          std::mutex *pMutex, int nBandCount,
    3930             :                                          const int *panBandMap)
    3931             : {
    3932             : 
    3933         432 :     if (pMutex)
    3934         138 :         pMutex->lock();
    3935         432 :     std::shared_ptr<SharedDataset> sharedDS;
    3936         864 :     GTISharedSourceKey key;
    3937         432 :     key.osTileName = osTileName;
    3938         432 :     if (m_bBandInterleave)
    3939           0 :         key.anBands.insert(key.anBands.end(), panBandMap,
    3940           9 :                            panBandMap + nBandCount);
    3941         432 :     m_oMapSharedSources.tryGet(key, sharedDS);
    3942         432 :     if (pMutex)
    3943         138 :         pMutex->unlock();
    3944             : 
    3945         432 :     std::shared_ptr<GDALDataset> poTileDS;
    3946         432 :     GDALDataset *poUnreprojectedDS = nullptr;
    3947         432 :     bool bBandMapTakenIntoAccount = false;
    3948             : 
    3949         432 :     if (sharedDS)
    3950             :     {
    3951         126 :         poTileDS = sharedDS->poDS;
    3952         126 :         poUnreprojectedDS = sharedDS->poUnreprojectedDS;
    3953         126 :         bBandMapTakenIntoAccount = sharedDS->bBandMapTakenIntoAccount;
    3954             :     }
    3955             :     else
    3956             :     {
    3957         612 :         poTileDS = std::shared_ptr<GDALDataset>(
    3958             :             GDALProxyPoolDataset::Create(
    3959             :                 osTileName.c_str(), nullptr, GA_ReadOnly,
    3960             :                 /* bShared = */ true, m_osUniqueHandle.c_str(),
    3961         306 :                 m_aosAllowedRasterDrivers.List()),
    3962         306 :             GDALDatasetUniquePtrReleaser());
    3963         306 :         if (!poTileDS)
    3964             :         {
    3965           3 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot open source %s",
    3966             :                      osTileName.c_str());
    3967           3 :             return false;
    3968             :         }
    3969         303 :         poUnreprojectedDS = poTileDS.get();
    3970         303 :         if (poTileDS->GetRasterCount() == 0)
    3971             :         {
    3972           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3973             :                      "Source %s has no raster bands", osTileName.c_str());
    3974           0 :             return false;
    3975             :         }
    3976             : 
    3977             :         // do palette -> RGB(A) expansion if needed
    3978         303 :         if (!GTIAdjustBandCount(poTileDS, nBands, this))
    3979           0 :             return false;
    3980             : 
    3981         303 :         bool bWarpVRT = false;
    3982         303 :         bool bExportSRS = false;
    3983         303 :         bool bAddAlphaToVRT = false;
    3984         303 :         const OGRSpatialReference *poTileSRS = poTileDS->GetSpatialRef();
    3985         303 :         GDALGeoTransform tileGT;
    3986         499 :         if (!m_oSRS.IsEmpty() && poTileSRS != nullptr &&
    3987         196 :             !m_oSRS.IsSame(poTileSRS))
    3988             :         {
    3989          15 :             CPLDebug("GTI",
    3990             :                      "Tile %s has not the same SRS as the VRT. "
    3991             :                      "Proceed to on-the-fly warping",
    3992             :                      osTileName.c_str());
    3993          15 :             bWarpVRT = true;
    3994          15 :             bExportSRS = true;
    3995          15 :             if (poTileDS->GetRasterBand(poTileDS->GetRasterCount())
    3996          25 :                         ->GetColorInterpretation() != GCI_AlphaBand &&
    3997          10 :                 GetRasterBand(nBands)->GetColorInterpretation() ==
    3998             :                     GCI_AlphaBand)
    3999             :             {
    4000           0 :                 bAddAlphaToVRT = true;
    4001             :             }
    4002             :         }
    4003         288 :         else if (poTileDS->GetGeoTransform(tileGT) == CE_None &&
    4004         289 :                  tileGT.yscale > 0 &&
    4005           1 :                  ((m_oSRS.IsEmpty() && poTileSRS == nullptr) ||
    4006           0 :                   (!m_oSRS.IsEmpty() && poTileSRS && m_oSRS.IsSame(poTileSRS))))
    4007             : 
    4008             :         {
    4009           1 :             CPLDebug("GTI",
    4010             :                      "Tile %s is south-up oriented. "
    4011             :                      "Proceed to on-the-fly warping",
    4012             :                      osTileName.c_str());
    4013           1 :             bWarpVRT = true;
    4014             :         }
    4015             : 
    4016         303 :         if (bWarpVRT)
    4017             :         {
    4018          16 :             CPLStringList aosOptions;
    4019          16 :             aosOptions.AddString("-of");
    4020          16 :             aosOptions.AddString("VRT");
    4021             : 
    4022          16 :             if ((poTileDS->GetRasterBand(1)->GetColorTable() == nullptr &&
    4023          16 :                  poTileDS->GetRasterBand(1)->GetCategoryNames() == nullptr) ||
    4024           0 :                 m_eResampling == GRIORA_Mode)
    4025             :             {
    4026          16 :                 aosOptions.AddString("-r");
    4027          16 :                 aosOptions.AddString(m_osResampling.c_str());
    4028             :             }
    4029             : 
    4030          16 :             if (bExportSRS)
    4031             :             {
    4032          15 :                 if (m_osWKT.empty())
    4033             :                 {
    4034           2 :                     char *pszWKT = nullptr;
    4035           2 :                     const char *const apszWKTOptions[] = {"FORMAT=WKT2_2019",
    4036             :                                                           nullptr};
    4037           2 :                     m_oSRS.exportToWkt(&pszWKT, apszWKTOptions);
    4038           2 :                     if (pszWKT)
    4039           2 :                         m_osWKT = pszWKT;
    4040           2 :                     CPLFree(pszWKT);
    4041             : 
    4042           2 :                     if (m_osWKT.empty())
    4043             :                     {
    4044           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
    4045             :                                  "Cannot export VRT SRS to WKT2");
    4046           0 :                         return false;
    4047             :                     }
    4048             :                 }
    4049             : 
    4050          15 :                 aosOptions.AddString("-t_srs");
    4051          15 :                 aosOptions.AddString(m_osWKT.c_str());
    4052             :             }
    4053             : 
    4054             :             // First pass to get the extent of the tile in the
    4055             :             // target VRT SRS
    4056             :             GDALWarpAppOptions *psWarpOptions =
    4057          16 :                 GDALWarpAppOptionsNew(aosOptions.List(), nullptr);
    4058          16 :             GDALDatasetH ahSrcDS[] = {GDALDataset::ToHandle(poTileDS.get())};
    4059          16 :             int bUsageError = false;
    4060             :             auto poWarpDS =
    4061             :                 std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(GDALWarp(
    4062          16 :                     "", nullptr, 1, ahSrcDS, psWarpOptions, &bUsageError)));
    4063          16 :             GDALWarpAppOptionsFree(psWarpOptions);
    4064          16 :             if (!poWarpDS)
    4065             :             {
    4066           0 :                 return false;
    4067             :             }
    4068             : 
    4069             :             // Second pass to create a warped source VRT whose
    4070             :             // extent is aligned on the one of the target GTI
    4071          16 :             GDALGeoTransform warpDSGT;
    4072          16 :             const auto eErr = poWarpDS->GetGeoTransform(warpDSGT);
    4073          16 :             CPL_IGNORE_RET_VAL(eErr);
    4074          16 :             CPLAssert(eErr == CE_None);
    4075          16 :             const double dfGTIMinX = m_gt.xorig;
    4076          16 :             const double dfGTIResX = m_gt.xscale;
    4077          16 :             const double dfGTIMaxY = m_gt.yorig;
    4078          16 :             const double dfGTIResYAbs = -m_gt.yscale;
    4079          16 :             const double dfWarpMinX =
    4080          16 :                 std::floor((warpDSGT.xorig - dfGTIMinX) / dfGTIResX) *
    4081             :                     dfGTIResX +
    4082             :                 dfGTIMinX;
    4083             :             const double dfWarpMaxX =
    4084          32 :                 std::ceil((warpDSGT.xorig +
    4085          16 :                            warpDSGT.xscale * poWarpDS->GetRasterXSize() -
    4086             :                            dfGTIMinX) /
    4087          16 :                           dfGTIResX) *
    4088             :                     dfGTIResX +
    4089          16 :                 dfGTIMinX;
    4090          16 :             const double dfWarpMaxY =
    4091             :                 dfGTIMaxY -
    4092          16 :                 std::floor((dfGTIMaxY - warpDSGT.yorig) / dfGTIResYAbs) *
    4093             :                     dfGTIResYAbs;
    4094             :             const double dfWarpMinY =
    4095             :                 dfGTIMaxY -
    4096          16 :                 std::ceil((dfGTIMaxY -
    4097          32 :                            (warpDSGT.yorig +
    4098          16 :                             warpDSGT.yscale * poWarpDS->GetRasterYSize())) /
    4099          16 :                           dfGTIResYAbs) *
    4100          16 :                     dfGTIResYAbs;
    4101             : 
    4102          16 :             aosOptions.AddString("-te");
    4103          16 :             aosOptions.AddString(CPLSPrintf("%.17g", dfWarpMinX));
    4104          16 :             aosOptions.AddString(CPLSPrintf("%.17g", dfWarpMinY));
    4105          16 :             aosOptions.AddString(CPLSPrintf("%.17g", dfWarpMaxX));
    4106          16 :             aosOptions.AddString(CPLSPrintf("%.17g", dfWarpMaxY));
    4107             : 
    4108          16 :             aosOptions.AddString("-tr");
    4109          16 :             aosOptions.AddString(CPLSPrintf("%.17g", dfGTIResX));
    4110          16 :             aosOptions.AddString(CPLSPrintf("%.17g", dfGTIResYAbs));
    4111             : 
    4112          16 :             if (m_bBandInterleave)
    4113             :             {
    4114           9 :                 bBandMapTakenIntoAccount = true;
    4115          23 :                 for (int i = 0; i < nBandCount; ++i)
    4116             :                 {
    4117          14 :                     aosOptions.AddString("-b");
    4118          14 :                     aosOptions.AddString(CPLSPrintf("%d", panBandMap[i]));
    4119             :                 }
    4120             :             }
    4121             : 
    4122          16 :             if (bAddAlphaToVRT)
    4123           0 :                 aosOptions.AddString("-dstalpha");
    4124             : 
    4125          16 :             if (!m_osWarpMemory.empty())
    4126             :             {
    4127           0 :                 aosOptions.AddString("-wm");
    4128           0 :                 aosOptions.AddString(m_osWarpMemory.c_str());
    4129             :             }
    4130             : 
    4131          16 :             psWarpOptions = GDALWarpAppOptionsNew(aosOptions.List(), nullptr);
    4132          16 :             poWarpDS.reset(GDALDataset::FromHandle(GDALWarp(
    4133             :                 "", nullptr, 1, ahSrcDS, psWarpOptions, &bUsageError)));
    4134          16 :             GDALWarpAppOptionsFree(psWarpOptions);
    4135          16 :             if (!poWarpDS)
    4136             :             {
    4137           0 :                 return false;
    4138             :             }
    4139             : 
    4140          16 :             poTileDS = std::move(poWarpDS);
    4141             :         }
    4142             : 
    4143         303 :         sharedDS = std::make_shared<SharedDataset>();
    4144         303 :         sharedDS->poDS = poTileDS;
    4145         303 :         sharedDS->poUnreprojectedDS = poUnreprojectedDS;
    4146         303 :         sharedDS->bBandMapTakenIntoAccount = bBandMapTakenIntoAccount;
    4147             : 
    4148         303 :         if (pMutex)
    4149          70 :             pMutex->lock();
    4150         303 :         m_oMapSharedSources.insert(key, sharedDS);
    4151         303 :         if (pMutex)
    4152          70 :             pMutex->unlock();
    4153             :     }
    4154             : 
    4155         429 :     GDALGeoTransform gtTile;
    4156         429 :     bool bHasGT = poTileDS->GetGeoTransform(gtTile) == CE_None;
    4157             : 
    4158             :     // A bit of a hack to synthetize the geotransform of a PMTiles .png/.jpg/.webp
    4159             :     // from the /vsipmtiles/my.pmtiles/{z}/{x}/{y}.ext filename, using GoogleMaps
    4160             :     // tiling scheme.
    4161          27 :     if (!bHasGT && cpl::starts_with(osTileName, "/vsipmtiles/") &&
    4162         456 :         poTileDS->GetRasterXSize() > 0 && poTileDS->GetRasterYSize() > 0)
    4163             :     {
    4164          27 :         constexpr double SPHERICAL_RADIUS = 6378137.0;
    4165          27 :         constexpr double MAX_GM =
    4166             :             SPHERICAL_RADIUS * M_PI;  // 20037508.342789244
    4167          27 :         const auto nPos = CPLString(osTileName).ifind(".pmtiles/");
    4168          27 :         if (nPos != std::string::npos)
    4169             :         {
    4170             :             const char *pszPMTiles =
    4171          27 :                 osTileName.c_str() + nPos + strlen(".pmtiles/");
    4172             :             const CPLStringList aosTokens(
    4173          54 :                 CSLTokenizeString2(pszPMTiles, "/", 0));
    4174          27 :             if (aosTokens.size() == 3)
    4175             :             {
    4176          27 :                 const unsigned nZ = atoi(aosTokens[0]);
    4177          27 :                 const unsigned nX = atoi(aosTokens[1]);
    4178          27 :                 const unsigned nY = atoi(aosTokens[2]);
    4179          27 :                 if (nZ <= 31)
    4180             :                 {
    4181          27 :                     bHasGT = true;
    4182          27 :                     const double dfTileDim = 2 * MAX_GM / (1U << nZ);
    4183          27 :                     gtTile.xorig = -MAX_GM + nX * dfTileDim;
    4184          27 :                     gtTile.xscale = dfTileDim / poTileDS->GetRasterXSize();
    4185          27 :                     gtTile.yorig = MAX_GM - nY * dfTileDim;
    4186          27 :                     gtTile.yscale = -dfTileDim / poTileDS->GetRasterYSize();
    4187             :                 }
    4188             :             }
    4189             :         }
    4190             :     }
    4191         429 :     if (!bHasGT)
    4192             :     {
    4193           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s lacks geotransform",
    4194             :                  osTileName.c_str());
    4195           0 :         return false;
    4196             :     }
    4197             : 
    4198         429 :     bool bHasNoData = false;
    4199         429 :     bool bSameNoData = true;
    4200         429 :     double dfNoDataValue = 0;
    4201         429 :     GDALRasterBand *poMaskBand = nullptr;
    4202         429 :     const int nTileBandCount = poTileDS->GetRasterCount();
    4203        1477 :     for (int iBand = 0; iBand < nTileBandCount; ++iBand)
    4204             :     {
    4205        1048 :         auto poTileBand = poTileDS->GetRasterBand(iBand + 1);
    4206        1048 :         int bThisBandHasNoData = false;
    4207             :         const double dfThisBandNoDataValue =
    4208        1048 :             poTileBand->GetNoDataValue(&bThisBandHasNoData);
    4209        1048 :         if (bThisBandHasNoData)
    4210             :         {
    4211          24 :             bHasNoData = true;
    4212          24 :             dfNoDataValue = dfThisBandNoDataValue;
    4213             :         }
    4214        1667 :         if (iBand > 0 &&
    4215         619 :             (static_cast<int>(bThisBandHasNoData) !=
    4216         619 :                  static_cast<int>(bHasNoData) ||
    4217          12 :              (bHasNoData &&
    4218          12 :               !IsSameNaNAware(dfNoDataValue, dfThisBandNoDataValue))))
    4219             :         {
    4220           0 :             bSameNoData = false;
    4221             :         }
    4222             : 
    4223        1048 :         if (poTileBand->GetMaskFlags() == GMF_PER_DATASET)
    4224           3 :             poMaskBand = poTileBand->GetMaskBand();
    4225        1045 :         else if (poTileBand->GetColorInterpretation() == GCI_AlphaBand)
    4226          64 :             poMaskBand = poTileBand;
    4227             :     }
    4228             : 
    4229           0 :     std::unique_ptr<VRTSimpleSource> poSource;
    4230         429 :     if (!bHasNoData)
    4231             :     {
    4232         417 :         poSource = std::make_unique<VRTSimpleSource>();
    4233             :     }
    4234             :     else
    4235             :     {
    4236          24 :         auto poComplexSource = std::make_unique<VRTComplexSource>();
    4237          12 :         poComplexSource->SetNoDataValue(dfNoDataValue);
    4238          12 :         poSource = std::move(poComplexSource);
    4239             :     }
    4240             : 
    4241         858 :     GetSrcDstWin(gtTile, poTileDS->GetRasterXSize(), poTileDS->GetRasterYSize(),
    4242         429 :                  m_gt, GetRasterXSize(), GetRasterYSize(),
    4243         429 :                  &poSource->m_dfSrcXOff, &poSource->m_dfSrcYOff,
    4244         429 :                  &poSource->m_dfSrcXSize, &poSource->m_dfSrcYSize,
    4245         429 :                  &poSource->m_dfDstXOff, &poSource->m_dfDstYOff,
    4246         429 :                  &poSource->m_dfDstXSize, &poSource->m_dfDstYSize);
    4247             : 
    4248         429 :     oSourceDesc.osName = osTileName;
    4249         429 :     oSourceDesc.poDS = std::move(poTileDS);
    4250         429 :     oSourceDesc.poUnreprojectedDS = poUnreprojectedDS;
    4251         429 :     oSourceDesc.bBandMapTakenIntoAccount = bBandMapTakenIntoAccount;
    4252         429 :     oSourceDesc.poSource = std::move(poSource);
    4253         429 :     oSourceDesc.bHasNoData = bHasNoData;
    4254         429 :     oSourceDesc.bSameNoData = bSameNoData;
    4255         429 :     if (bSameNoData)
    4256         429 :         oSourceDesc.dfSameNoData = dfNoDataValue;
    4257         429 :     oSourceDesc.poMaskBand = poMaskBand;
    4258         429 :     return true;
    4259             : }
    4260             : 
    4261             : /************************************************************************/
    4262             : /*                           GetNumThreads()                            */
    4263             : /************************************************************************/
    4264             : 
    4265           8 : int GDALTileIndexDataset::GetNumThreads() const
    4266             : {
    4267             :     const char *pszNumThreads =
    4268           8 :         CSLFetchNameValueDef(GetOpenOptions(), "NUM_THREADS", nullptr);
    4269           8 :     if (!pszNumThreads)
    4270           8 :         pszNumThreads = CPLGetConfigOption("GTI_NUM_THREADS", nullptr);
    4271           8 :     return GDALGetNumThreads(pszNumThreads, GDALGetMaxDatasetPoolSize(),
    4272           8 :                              /* bDefaultAllCPUs = */ true);
    4273             : }
    4274             : 
    4275             : /************************************************************************/
    4276             : /*                           CollectSources()                           */
    4277             : /************************************************************************/
    4278             : 
    4279         285 : bool GDALTileIndexDataset::CollectSources(double dfXOff, double dfYOff,
    4280             :                                           double dfXSize, double dfYSize,
    4281             :                                           int nBandCount, const int *panBandMap,
    4282             :                                           bool bMultiThreadAllowed)
    4283             : {
    4284         285 :     const double dfMinX = m_gt.xorig + dfXOff * m_gt.xscale;
    4285         285 :     const double dfMaxX = dfMinX + dfXSize * m_gt.xscale;
    4286         285 :     const double dfMaxY = m_gt.yorig + dfYOff * m_gt.yscale;
    4287         285 :     const double dfMinY = dfMaxY + dfYSize * m_gt.yscale;
    4288             : 
    4289          99 :     if (dfMinX == m_dfLastMinXFilter && dfMinY == m_dfLastMinYFilter &&
    4290         468 :         dfMaxX == m_dfLastMaxXFilter && dfMaxY == m_dfLastMaxYFilter &&
    4291          84 :         (!m_bBandInterleave ||
    4292           6 :          (m_anLastBands ==
    4293         291 :           std::vector<int>(panBandMap, panBandMap + nBandCount))))
    4294             :     {
    4295          79 :         return true;
    4296             :     }
    4297             : 
    4298         206 :     m_dfLastMinXFilter = dfMinX;
    4299         206 :     m_dfLastMinYFilter = dfMinY;
    4300         206 :     m_dfLastMaxXFilter = dfMaxX;
    4301         206 :     m_dfLastMaxYFilter = dfMaxY;
    4302         206 :     if (m_bBandInterleave)
    4303           9 :         m_anLastBands = std::vector<int>(panBandMap, panBandMap + nBandCount);
    4304         206 :     m_bLastMustUseMultiThreading = false;
    4305             : 
    4306         206 :     OGRLayer *poSQLLayer = nullptr;
    4307         206 :     if (!m_osSpatialSQL.empty())
    4308             :     {
    4309             :         const std::string osSQL =
    4310           3 :             CPLString(m_osSpatialSQL)
    4311           6 :                 .replaceAll("{XMIN}", CPLSPrintf("%.17g", dfMinX))
    4312           6 :                 .replaceAll("{YMIN}", CPLSPrintf("%.17g", dfMinY))
    4313           6 :                 .replaceAll("{XMAX}", CPLSPrintf("%.17g", dfMaxX))
    4314           6 :                 .replaceAll("{YMAX}", CPLSPrintf("%.17g", dfMaxY));
    4315           3 :         poSQLLayer = m_poVectorDS->ExecuteSQL(osSQL.c_str(), nullptr, nullptr);
    4316           3 :         if (!poSQLLayer)
    4317           1 :             return 0;
    4318             :     }
    4319             :     else
    4320             :     {
    4321         203 :         m_poLayer->SetSpatialFilterRect(dfMinX, dfMinY, dfMaxX, dfMaxY);
    4322         203 :         m_poLayer->ResetReading();
    4323             :     }
    4324             : 
    4325         205 :     OGRLayer *const poLayer = poSQLLayer ? poSQLLayer : m_poLayer;
    4326             : 
    4327         205 :     m_aoSourceDesc.clear();
    4328             :     while (true)
    4329             :     {
    4330         603 :         auto poFeature = std::unique_ptr<OGRFeature>(poLayer->GetNextFeature());
    4331         603 :         if (!poFeature)
    4332         205 :             break;
    4333         398 :         if (!poFeature->IsFieldSetAndNotNull(m_nLocationFieldIndex))
    4334             :         {
    4335           1 :             continue;
    4336             :         }
    4337             : 
    4338         397 :         SourceDesc oSourceDesc;
    4339         397 :         oSourceDesc.poFeature = std::move(poFeature);
    4340         397 :         m_aoSourceDesc.emplace_back(std::move(oSourceDesc));
    4341             : 
    4342         397 :         if (m_aoSourceDesc.size() > 10 * 1000 * 1000)
    4343             :         {
    4344             :             // Safety belt...
    4345           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4346             :                      "More than 10 million contributing sources to a "
    4347             :                      "single RasterIO() request is not supported");
    4348           0 :             return false;
    4349             :         }
    4350         398 :     }
    4351             : 
    4352         205 :     if (poSQLLayer)
    4353           2 :         ReleaseResultSet(poSQLLayer);
    4354             : 
    4355         205 :     constexpr int MINIMUM_PIXEL_COUNT_FOR_THREADED_IO = 1000 * 1000;
    4356         274 :     if (bMultiThreadAllowed && m_aoSourceDesc.size() > 1 &&
    4357          69 :         dfXSize * dfYSize > MINIMUM_PIXEL_COUNT_FOR_THREADED_IO)
    4358             :     {
    4359           8 :         if (m_nNumThreads < 0)
    4360           8 :             m_nNumThreads = GetNumThreads();
    4361           8 :         bMultiThreadAllowed = m_nNumThreads > 1;
    4362             :     }
    4363             :     else
    4364             :     {
    4365         197 :         bMultiThreadAllowed = false;
    4366             :     }
    4367             : 
    4368         205 :     if (bMultiThreadAllowed)
    4369             :     {
    4370             :         CPLRectObj sGlobalBounds;
    4371           5 :         sGlobalBounds.minx = dfXOff;
    4372           5 :         sGlobalBounds.miny = dfYOff;
    4373           5 :         sGlobalBounds.maxx = dfXOff + dfXSize;
    4374           5 :         sGlobalBounds.maxy = dfYOff + dfYSize;
    4375           5 :         CPLQuadTree *hQuadTree = CPLQuadTreeCreate(&sGlobalBounds, nullptr);
    4376             : 
    4377           5 :         bool bCompatibleOfMultiThread = true;
    4378           5 :         std::set<std::string> oSetTileNames;
    4379          77 :         for (const auto &oSourceDesc : m_aoSourceDesc)
    4380             :         {
    4381             :             const char *pszTileName =
    4382          73 :                 oSourceDesc.poFeature->GetFieldAsString(m_nLocationFieldIndex);
    4383          73 :             if (oSetTileNames.find(pszTileName) != oSetTileNames.end())
    4384             :             {
    4385           0 :                 bCompatibleOfMultiThread = false;
    4386           1 :                 break;
    4387             :             }
    4388          73 :             oSetTileNames.insert(pszTileName);
    4389             : 
    4390          73 :             const auto poGeom = oSourceDesc.poFeature->GetGeometryRef();
    4391          73 :             if (!poGeom || poGeom->IsEmpty())
    4392           0 :                 continue;
    4393             : 
    4394          73 :             OGREnvelope sEnvelope;
    4395          73 :             poGeom->getEnvelope(&sEnvelope);
    4396             : 
    4397             :             CPLRectObj sSourceBounds;
    4398          73 :             sSourceBounds.minx = (sEnvelope.MinX - m_gt.xorig) / m_gt.xscale;
    4399          73 :             sSourceBounds.maxx = (sEnvelope.MaxX - m_gt.xorig) / m_gt.xscale;
    4400             :             // Yes use of MaxY to compute miny is intended given that MaxY is
    4401             :             // in georeferenced space whereas miny is in pixel space.
    4402          73 :             sSourceBounds.miny = (sEnvelope.MaxY - m_gt.yorig) / m_gt.yscale;
    4403             :             // Same here for maxy vs Miny
    4404          73 :             sSourceBounds.maxy = (sEnvelope.MinY - m_gt.yorig) / m_gt.yscale;
    4405             : 
    4406             :             // Clamp to global bounds and some epsilon to avoid adjacent tiles
    4407             :             // to be considered as overlapping
    4408          73 :             constexpr double EPSILON = 0.1;
    4409          73 :             sSourceBounds.minx =
    4410          73 :                 std::max(sGlobalBounds.minx, sSourceBounds.minx) + EPSILON;
    4411          73 :             sSourceBounds.maxx =
    4412          73 :                 std::min(sGlobalBounds.maxx, sSourceBounds.maxx) - EPSILON;
    4413          73 :             sSourceBounds.miny =
    4414          73 :                 std::max(sGlobalBounds.miny, sSourceBounds.miny) + EPSILON;
    4415          73 :             sSourceBounds.maxy =
    4416          73 :                 std::min(sGlobalBounds.maxy, sSourceBounds.maxy) - EPSILON;
    4417             : 
    4418             :             // Check that the new source doesn't overlap an existing one.
    4419          73 :             if (CPLQuadTreeHasMatch(hQuadTree, &sSourceBounds))
    4420             :             {
    4421           1 :                 bCompatibleOfMultiThread = false;
    4422           1 :                 break;
    4423             :             }
    4424             : 
    4425          72 :             CPLQuadTreeInsertWithBounds(
    4426             :                 hQuadTree,
    4427             :                 const_cast<void *>(static_cast<const void *>(&oSourceDesc)),
    4428             :                 &sSourceBounds);
    4429             :         }
    4430             : 
    4431           5 :         CPLQuadTreeDestroy(hQuadTree);
    4432             : 
    4433           5 :         if (bCompatibleOfMultiThread)
    4434             :         {
    4435           4 :             m_bLastMustUseMultiThreading = true;
    4436           4 :             return true;
    4437             :         }
    4438             :     }
    4439             : 
    4440         201 :     if (m_aoSourceDesc.size() > 1)
    4441             :     {
    4442          66 :         SortSourceDesc();
    4443             :     }
    4444             : 
    4445             :     // Try to find the last (most priority) fully opaque source covering
    4446             :     // the whole AOI. We only need to start rendering from it.
    4447         201 :     size_t i = m_aoSourceDesc.size();
    4448         371 :     while (i > 0)
    4449             :     {
    4450         294 :         --i;
    4451         294 :         auto &poFeature = m_aoSourceDesc[i].poFeature;
    4452             :         const char *pszTileName =
    4453         294 :             poFeature->GetFieldAsString(m_nLocationFieldIndex);
    4454             :         const std::string osTileName(GetAbsoluteFileName(
    4455         294 :             pszTileName, GetDescription(), m_bSTACCollection));
    4456             : 
    4457         294 :         SourceDesc oSourceDesc;
    4458         294 :         if (!GetSourceDesc(osTileName, oSourceDesc, nullptr, nBandCount,
    4459             :                            panBandMap))
    4460           2 :             return false;
    4461             : 
    4462             :         // Check consistency of bounding box in tile index vs actual
    4463             :         // extent of the tile.
    4464         292 :         GDALGeoTransform tileGT;
    4465         292 :         if (oSourceDesc.poDS->GetGeoTransform(tileGT) == CE_None &&
    4466         292 :             tileGT.xrot == 0 && tileGT.yrot == 0)
    4467             :         {
    4468         265 :             OGREnvelope sActualTileExtent;
    4469         265 :             sActualTileExtent.MinX = tileGT.xorig;
    4470         265 :             sActualTileExtent.MaxX =
    4471         530 :                 sActualTileExtent.MinX +
    4472         265 :                 oSourceDesc.poDS->GetRasterXSize() * tileGT.xscale;
    4473         265 :             sActualTileExtent.MaxY = tileGT.yorig;
    4474         265 :             sActualTileExtent.MinY =
    4475         530 :                 sActualTileExtent.MaxY +
    4476         265 :                 oSourceDesc.poDS->GetRasterYSize() * tileGT.yscale;
    4477         265 :             const auto poGeom = poFeature->GetGeometryRef();
    4478         265 :             if (poGeom && !poGeom->IsEmpty())
    4479             :             {
    4480         265 :                 OGREnvelope sGeomTileExtent;
    4481         265 :                 poGeom->getEnvelope(&sGeomTileExtent);
    4482         265 :                 sGeomTileExtent.MinX -= m_gt.xscale;
    4483         265 :                 sGeomTileExtent.MaxX += m_gt.xscale;
    4484         265 :                 sGeomTileExtent.MinY -= std::fabs(m_gt.yscale);
    4485         265 :                 sGeomTileExtent.MaxY += std::fabs(m_gt.yscale);
    4486         265 :                 if (!sGeomTileExtent.Contains(sActualTileExtent))
    4487             :                 {
    4488           3 :                     if (!sGeomTileExtent.Intersects(sActualTileExtent))
    4489             :                     {
    4490           1 :                         CPLError(CE_Warning, CPLE_AppDefined,
    4491             :                                  "Tile index is out of sync with actual "
    4492             :                                  "extent of %s. Bounding box from tile index "
    4493             :                                  "is (%.15g, %.15g, %.15g, %.15g) does not "
    4494             :                                  "intersect at all bounding box from tile "
    4495             :                                  "(%.15g, %.15g, %.15g, %.15g)",
    4496             :                                  osTileName.c_str(), sGeomTileExtent.MinX,
    4497             :                                  sGeomTileExtent.MinY, sGeomTileExtent.MaxX,
    4498             :                                  sGeomTileExtent.MaxY, sActualTileExtent.MinX,
    4499             :                                  sActualTileExtent.MinY, sActualTileExtent.MaxX,
    4500             :                                  sActualTileExtent.MaxY);
    4501           2 :                         continue;
    4502             :                     }
    4503             : 
    4504             :                     // The above test assumes, in the case of reprojection, that
    4505             :                     // the reprojected geometry in the index is computed the
    4506             :                     // same way as we do here, that is using GDALWarp()
    4507             :                     // Which wasn't the case for example before GDAL 3.12.3 when
    4508             :                     // using "gdal raster index", which uses a simple 4-corner
    4509             :                     // reprojection logic. So also test using that method,
    4510             :                     // before emitting any warning.
    4511           2 :                     if (oSourceDesc.poUnreprojectedDS != oSourceDesc.poDS.get())
    4512             :                     {
    4513             :                         const int nXSize =
    4514           1 :                             oSourceDesc.poUnreprojectedDS->GetRasterXSize();
    4515             :                         const int nYSize =
    4516           1 :                             oSourceDesc.poUnreprojectedDS->GetRasterYSize();
    4517           1 :                         GDALGeoTransform gt;
    4518             :                         const auto poSrcSRS =
    4519           1 :                             oSourceDesc.poUnreprojectedDS->GetSpatialRef();
    4520           2 :                         if (poSrcSRS && !m_oSRS.IsEmpty() &&
    4521           1 :                             oSourceDesc.poUnreprojectedDS->GetGeoTransform(
    4522           1 :                                 gt) == CE_None)
    4523             :                         {
    4524           1 :                             double adfX[4] = {0.0, 0.0, 0.0, 0.0};
    4525           1 :                             double adfY[4] = {0.0, 0.0, 0.0, 0.0};
    4526           1 :                             adfX[0] = gt.xorig + 0 * gt.xscale + 0 * gt.xrot;
    4527           1 :                             adfY[0] = gt.yorig + 0 * gt.yrot + 0 * gt.yscale;
    4528             : 
    4529           1 :                             adfX[1] =
    4530           1 :                                 gt.xorig + nXSize * gt.xscale + 0 * gt.xrot;
    4531           1 :                             adfY[1] =
    4532           1 :                                 gt.yorig + nXSize * gt.yrot + 0 * gt.yscale;
    4533             : 
    4534           1 :                             adfX[2] = gt.xorig + nXSize * gt.xscale +
    4535           1 :                                       nYSize * gt.xrot;
    4536           1 :                             adfY[2] = gt.yorig + nXSize * gt.yrot +
    4537           1 :                                       nYSize * gt.yscale;
    4538             : 
    4539           1 :                             adfX[3] =
    4540           1 :                                 gt.xorig + 0 * gt.xscale + nYSize * gt.xrot;
    4541           1 :                             adfY[3] =
    4542           1 :                                 gt.yorig + 0 * gt.yrot + nYSize * gt.yscale;
    4543             : 
    4544             :                             auto poCT =
    4545             :                                 std::unique_ptr<OGRCoordinateTransformation>(
    4546             :                                     OGRCreateCoordinateTransformation(poSrcSRS,
    4547           1 :                                                                       &m_oSRS));
    4548           1 :                             if (poCT && poCT->Transform(4, adfX, adfY, nullptr))
    4549             :                             {
    4550           1 :                                 OGREnvelope sActualTileExtent2;
    4551           0 :                                 sActualTileExtent2.MinX = std::min(
    4552           1 :                                     {adfX[0], adfX[1], adfX[2], adfX[3]});
    4553           0 :                                 sActualTileExtent2.MinY = std::min(
    4554           1 :                                     {adfY[0], adfY[1], adfY[2], adfY[3]});
    4555           0 :                                 sActualTileExtent2.MaxX = std::max(
    4556           1 :                                     {adfX[0], adfX[1], adfX[2], adfX[3]});
    4557           0 :                                 sActualTileExtent2.MaxY = std::max(
    4558           1 :                                     {adfY[0], adfY[1], adfY[2], adfY[3]});
    4559           1 :                                 if (sGeomTileExtent.Contains(
    4560           1 :                                         sActualTileExtent2))
    4561             :                                 {
    4562           1 :                                     continue;
    4563             :                                 }
    4564             :                             }
    4565             :                         }
    4566             :                     }
    4567             : 
    4568           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    4569             :                              "Tile index is out of sync with actual extent "
    4570             :                              "of %s. Bounding box from tile index is "
    4571             :                              "(%.15g, %.15g, %.15g, %.15g) does not fully "
    4572             :                              "contain bounding box from tile "
    4573             :                              "(%.15g, %.15g, %.15g, %.15g)",
    4574             :                              osTileName.c_str(), sGeomTileExtent.MinX,
    4575             :                              sGeomTileExtent.MinY, sGeomTileExtent.MaxX,
    4576             :                              sGeomTileExtent.MaxY, sActualTileExtent.MinX,
    4577             :                              sActualTileExtent.MinY, sActualTileExtent.MaxX,
    4578             :                              sActualTileExtent.MaxY);
    4579             :                 }
    4580             :             }
    4581             :         }
    4582             : 
    4583         290 :         const auto &poSource = oSourceDesc.poSource;
    4584         290 :         if (dfXOff >= poSource->m_dfDstXOff + poSource->m_dfDstXSize ||
    4585         290 :             dfYOff >= poSource->m_dfDstYOff + poSource->m_dfDstYSize ||
    4586         867 :             poSource->m_dfDstXOff >= dfXOff + dfXSize ||
    4587         287 :             poSource->m_dfDstYOff >= dfYOff + dfYSize)
    4588             :         {
    4589             :             // Can happen as some spatial filters select slightly more features
    4590             :             // than strictly needed.
    4591           3 :             continue;
    4592             :         }
    4593             : 
    4594             :         const bool bCoversWholeAOI =
    4595         287 :             (poSource->m_dfDstXOff <= dfXOff &&
    4596         210 :              poSource->m_dfDstYOff <= dfYOff &&
    4597         208 :              poSource->m_dfDstXOff + poSource->m_dfDstXSize >=
    4598         692 :                  dfXOff + dfXSize &&
    4599         195 :              poSource->m_dfDstYOff + poSource->m_dfDstYSize >=
    4600         195 :                  dfYOff + dfYSize);
    4601         287 :         oSourceDesc.bCoversWholeAOI = bCoversWholeAOI;
    4602             : 
    4603         287 :         m_aoSourceDesc[i] = std::move(oSourceDesc);
    4604             : 
    4605         287 :         if (m_aoSourceDesc[i].bCoversWholeAOI &&
    4606         287 :             !m_aoSourceDesc[i].bHasNoData && !m_aoSourceDesc[i].poMaskBand)
    4607             :         {
    4608         122 :             break;
    4609             :         }
    4610             :     }
    4611             : 
    4612         199 :     if (i > 0)
    4613             :     {
    4614             :         // Remove sources that will not be rendered
    4615          31 :         m_aoSourceDesc.erase(m_aoSourceDesc.begin(),
    4616          62 :                              m_aoSourceDesc.begin() + i);
    4617             :     }
    4618             : 
    4619             :     // Remove elements that have no dataset
    4620           0 :     m_aoSourceDesc.erase(std::remove_if(m_aoSourceDesc.begin(),
    4621             :                                         m_aoSourceDesc.end(),
    4622         292 :                                         [](const SourceDesc &desc)
    4623         491 :                                         { return desc.poDS == nullptr; }),
    4624         398 :                          m_aoSourceDesc.end());
    4625             : 
    4626         199 :     return true;
    4627             : }
    4628             : 
    4629             : /************************************************************************/
    4630             : /*                           SortSourceDesc()                           */
    4631             : /************************************************************************/
    4632             : 
    4633          66 : void GDALTileIndexDataset::SortSourceDesc()
    4634             : {
    4635          66 :     const auto eFieldType = m_nSortFieldIndex >= 0
    4636          66 :                                 ? m_poLayer->GetLayerDefn()
    4637          47 :                                       ->GetFieldDefn(m_nSortFieldIndex)
    4638          47 :                                       ->GetType()
    4639          66 :                                 : OFTMaxType;
    4640          66 :     std::sort(
    4641             :         m_aoSourceDesc.begin(), m_aoSourceDesc.end(),
    4642        1828 :         [this, eFieldType](const SourceDesc &a, const SourceDesc &b)
    4643             :         {
    4644         419 :             const auto &poFeatureA = (m_bSortFieldAsc ? a : b).poFeature;
    4645         419 :             const auto &poFeatureB = (m_bSortFieldAsc ? b : a).poFeature;
    4646         918 :             if (m_nSortFieldIndex >= 0 &&
    4647         499 :                 poFeatureA->IsFieldSetAndNotNull(m_nSortFieldIndex) &&
    4648          80 :                 poFeatureB->IsFieldSetAndNotNull(m_nSortFieldIndex))
    4649             :             {
    4650          80 :                 if (eFieldType == OFTString)
    4651             :                 {
    4652             :                     const int nCmp =
    4653           5 :                         strcmp(poFeatureA->GetFieldAsString(m_nSortFieldIndex),
    4654             :                                poFeatureB->GetFieldAsString(m_nSortFieldIndex));
    4655           5 :                     if (nCmp < 0)
    4656           1 :                         return true;
    4657           4 :                     if (nCmp > 0)
    4658           2 :                         return false;
    4659             :                 }
    4660          75 :                 else if (eFieldType == OFTInteger || eFieldType == OFTInteger64)
    4661             :                 {
    4662             :                     const auto nA =
    4663          45 :                         poFeatureA->GetFieldAsInteger64(m_nSortFieldIndex);
    4664             :                     const auto nB =
    4665          45 :                         poFeatureB->GetFieldAsInteger64(m_nSortFieldIndex);
    4666          45 :                     if (nA < nB)
    4667           3 :                         return true;
    4668          42 :                     if (nA > nB)
    4669          42 :                         return false;
    4670             :                 }
    4671          30 :                 else if (eFieldType == OFTReal)
    4672             :                 {
    4673             :                     const auto dfA =
    4674           3 :                         poFeatureA->GetFieldAsDouble(m_nSortFieldIndex);
    4675             :                     const auto dfB =
    4676           3 :                         poFeatureB->GetFieldAsDouble(m_nSortFieldIndex);
    4677           3 :                     if (dfA < dfB)
    4678           1 :                         return true;
    4679           2 :                     if (dfA > dfB)
    4680           2 :                         return false;
    4681             :                 }
    4682          27 :                 else if (eFieldType == OFTDate || eFieldType == OFTDateTime)
    4683             :                 {
    4684             :                     const auto poFieldA =
    4685          27 :                         poFeatureA->GetRawFieldRef(m_nSortFieldIndex);
    4686             :                     const auto poFieldB =
    4687          27 :                         poFeatureB->GetRawFieldRef(m_nSortFieldIndex);
    4688             : 
    4689             : #define COMPARE_DATE_COMPONENT(comp)                                           \
    4690             :     do                                                                         \
    4691             :     {                                                                          \
    4692             :         if (poFieldA->Date.comp < poFieldB->Date.comp)                         \
    4693             :             return true;                                                       \
    4694             :         if (poFieldA->Date.comp > poFieldB->Date.comp)                         \
    4695             :             return false;                                                      \
    4696             :     } while (0)
    4697             : 
    4698          27 :                     COMPARE_DATE_COMPONENT(Year);
    4699          21 :                     COMPARE_DATE_COMPONENT(Month);
    4700          15 :                     COMPARE_DATE_COMPONENT(Day);
    4701           9 :                     COMPARE_DATE_COMPONENT(Hour);
    4702           8 :                     COMPARE_DATE_COMPONENT(Minute);
    4703           7 :                     COMPARE_DATE_COMPONENT(Second);
    4704             :                 }
    4705             :                 else
    4706             :                 {
    4707           0 :                     CPLAssert(false);
    4708             :                 }
    4709             :             }
    4710         347 :             return poFeatureA->GetFID() < poFeatureB->GetFID();
    4711             :         });
    4712          66 : }
    4713             : 
    4714             : /************************************************************************/
    4715             : /*                    CompositeSrcWithMaskIntoDest()                    */
    4716             : /************************************************************************/
    4717             : 
    4718             : static void
    4719          91 : CompositeSrcWithMaskIntoDest(const int nOutXSize, const int nOutYSize,
    4720             :                              const GDALDataType eBufType,
    4721             :                              const int nBufTypeSize, const GSpacing nPixelSpace,
    4722             :                              const GSpacing nLineSpace, const GByte *pabySrc,
    4723             :                              const GByte *const pabyMask, GByte *const pabyDest)
    4724             : {
    4725          91 :     size_t iMaskIdx = 0;
    4726          91 :     if (eBufType == GDT_UInt8)
    4727             :     {
    4728             :         // Optimization for byte case
    4729         285 :         for (int iY = 0; iY < nOutYSize; iY++)
    4730             :         {
    4731         210 :             GByte *pabyDestLine =
    4732         210 :                 pabyDest + static_cast<GPtrDiff_t>(iY * nLineSpace);
    4733         210 :             int iX = 0;
    4734             : #ifdef USE_SSE2_OPTIM
    4735         210 :             if (nPixelSpace == 1)
    4736             :             {
    4737             :                 // SSE2 version up to 6 times faster than portable version
    4738         210 :                 const auto xmm_zero = _mm_setzero_si128();
    4739         210 :                 constexpr int SIZEOF_REG = static_cast<int>(sizeof(xmm_zero));
    4740         834 :                 for (; iX + SIZEOF_REG <= nOutXSize; iX += SIZEOF_REG)
    4741             :                 {
    4742        1248 :                     auto xmm_mask = _mm_loadu_si128(
    4743             :                         reinterpret_cast<__m128i const *>(pabyMask + iMaskIdx));
    4744         624 :                     const auto xmm_src = _mm_loadu_si128(
    4745             :                         reinterpret_cast<__m128i const *>(pabySrc));
    4746         624 :                     auto xmm_dst = _mm_loadu_si128(
    4747             :                         reinterpret_cast<__m128i const *>(pabyDestLine));
    4748             : #ifdef USE_SSE41_OPTIM
    4749             :                     xmm_dst = _mm_blendv_epi8(xmm_dst, xmm_src, xmm_mask);
    4750             : #else
    4751             :                     // mask[i] = 0 becomes 255, and mask[i] != 0 becomes 0
    4752         624 :                     xmm_mask = _mm_cmpeq_epi8(xmm_mask, xmm_zero);
    4753             :                     // dst_data[i] = (mask[i] & dst_data[i]) |
    4754             :                     //               (~mask[i] & src_data[i])
    4755             :                     // That is:
    4756             :                     // dst_data[i] = dst_data[i] when mask[i] = 255
    4757             :                     // dst_data[i] = src_data[i] when mask[i] = 0
    4758        1872 :                     xmm_dst = _mm_or_si128(_mm_and_si128(xmm_mask, xmm_dst),
    4759             :                                            _mm_andnot_si128(xmm_mask, xmm_src));
    4760             : #endif
    4761             :                     _mm_storeu_si128(reinterpret_cast<__m128i *>(pabyDestLine),
    4762             :                                      xmm_dst);
    4763         624 :                     pabyDestLine += SIZEOF_REG;
    4764         624 :                     pabySrc += SIZEOF_REG;
    4765         624 :                     iMaskIdx += SIZEOF_REG;
    4766             :                 }
    4767             :             }
    4768             : #endif
    4769         914 :             for (; iX < nOutXSize; iX++)
    4770             :             {
    4771         704 :                 if (pabyMask[iMaskIdx])
    4772             :                 {
    4773         458 :                     *pabyDestLine = *pabySrc;
    4774             :                 }
    4775         704 :                 pabyDestLine += static_cast<GPtrDiff_t>(nPixelSpace);
    4776         704 :                 pabySrc++;
    4777         704 :                 iMaskIdx++;
    4778             :             }
    4779             :         }
    4780             :     }
    4781             :     else
    4782             :     {
    4783          38 :         for (int iY = 0; iY < nOutYSize; iY++)
    4784             :         {
    4785          22 :             GByte *pabyDestLine =
    4786          22 :                 pabyDest + static_cast<GPtrDiff_t>(iY * nLineSpace);
    4787          54 :             for (int iX = 0; iX < nOutXSize; iX++)
    4788             :             {
    4789          32 :                 if (pabyMask[iMaskIdx])
    4790             :                 {
    4791          16 :                     memcpy(pabyDestLine, pabySrc, nBufTypeSize);
    4792             :                 }
    4793          32 :                 pabyDestLine += static_cast<GPtrDiff_t>(nPixelSpace);
    4794          32 :                 pabySrc += nBufTypeSize;
    4795          32 :                 iMaskIdx++;
    4796             :             }
    4797             :         }
    4798             :     }
    4799          91 : }
    4800             : 
    4801             : /************************************************************************/
    4802             : /*                           NeedInitBuffer()                           */
    4803             : /************************************************************************/
    4804             : 
    4805             : // Must be called after CollectSources()
    4806         273 : bool GDALTileIndexDataset::NeedInitBuffer(int nBandCount,
    4807             :                                           const int *panBandMap) const
    4808             : {
    4809         273 :     bool bNeedInitBuffer = true;
    4810             :     // If the last source (that is the most priority one) covers at least
    4811             :     // the window of interest and is fully opaque, then we don't need to
    4812             :     // initialize the buffer, and can directly render that source.
    4813         273 :     int bHasNoData = false;
    4814         542 :     if (!m_aoSourceDesc.empty() && m_aoSourceDesc.back().bCoversWholeAOI &&
    4815         256 :         (!m_aoSourceDesc.back().bHasNoData ||
    4816             :          // Also, if there's a single source and that the VRT bands and the
    4817             :          // source bands have the same nodata value, we can skip initialization.
    4818          12 :          (m_aoSourceDesc.size() == 1 && m_aoSourceDesc.back().bSameNoData &&
    4819          10 :           m_bSameNoData && m_bSameDataType &&
    4820           5 :           IsSameNaNAware(papoBands[0]->GetNoDataValue(&bHasNoData),
    4821           5 :                          m_aoSourceDesc.back().dfSameNoData) &&
    4822         547 :           bHasNoData)) &&
    4823         331 :         (!m_aoSourceDesc.back().poMaskBand ||
    4824             :          // Also, if there's a single source that has a mask band, and the VRT
    4825             :          // bands have no-nodata or a 0-nodata value, we can skip
    4826             :          // initialization.
    4827         134 :          (m_aoSourceDesc.size() == 1 && m_bSameDataType &&
    4828          50 :           !(nBandCount == 1 && panBandMap[0] == 0) && m_bSameNoData &&
    4829          49 :           papoBands[0]->GetNoDataValue(&bHasNoData) == 0)))
    4830             :     {
    4831         212 :         bNeedInitBuffer = false;
    4832             :     }
    4833         273 :     return bNeedInitBuffer;
    4834             : }
    4835             : 
    4836             : /************************************************************************/
    4837             : /*                             InitBuffer()                             */
    4838             : /************************************************************************/
    4839             : 
    4840          66 : void GDALTileIndexDataset::InitBuffer(void *pData, int nBufXSize, int nBufYSize,
    4841             :                                       GDALDataType eBufType, int nBandCount,
    4842             :                                       const int *panBandMap,
    4843             :                                       GSpacing nPixelSpace, GSpacing nLineSpace,
    4844             :                                       GSpacing nBandSpace) const
    4845             : {
    4846          66 :     const int nBufTypeSize = GDALGetDataTypeSizeBytes(eBufType);
    4847          66 :     if (m_bSameNoData && nBandCount > 1 &&
    4848          19 :         ((nPixelSpace == nBufTypeSize &&
    4849          19 :           nLineSpace == nBufXSize * nPixelSpace &&
    4850          19 :           nBandSpace == nBufYSize * nLineSpace) ||
    4851           0 :          (nBandSpace == nBufTypeSize &&
    4852           0 :           nPixelSpace == nBandCount * nBandSpace &&
    4853           0 :           nLineSpace == nBufXSize * nPixelSpace)))
    4854             :     {
    4855          19 :         const int nBandNr = panBandMap[0];
    4856             :         auto poVRTBand =
    4857             :             nBandNr == 0
    4858          19 :                 ? m_poMaskBand.get()
    4859          19 :                 : cpl::down_cast<GDALTileIndexBand *>(papoBands[nBandNr - 1]);
    4860          19 :         CPLAssert(poVRTBand);
    4861          19 :         const double dfNoData = poVRTBand->m_dfNoDataValue;
    4862          19 :         if (dfNoData == 0.0)
    4863             :         {
    4864          17 :             memset(pData, 0,
    4865          17 :                    static_cast<size_t>(nBufXSize) * nBufYSize * nBandCount *
    4866          17 :                        nBufTypeSize);
    4867             :         }
    4868             :         else
    4869             :         {
    4870           2 :             GDALCopyWords64(
    4871             :                 &dfNoData, GDT_Float64, 0, pData, eBufType, nBufTypeSize,
    4872           2 :                 static_cast<size_t>(nBufXSize) * nBufYSize * nBandCount);
    4873          19 :         }
    4874             :     }
    4875             :     else
    4876             :     {
    4877          95 :         for (int i = 0; i < nBandCount; ++i)
    4878             :         {
    4879          48 :             const int nBandNr = panBandMap[i];
    4880          48 :             auto poVRTBand = nBandNr == 0 ? m_poMaskBand.get()
    4881          45 :                                           : cpl::down_cast<GDALTileIndexBand *>(
    4882          45 :                                                 papoBands[nBandNr - 1]);
    4883          48 :             GByte *pabyBandData = static_cast<GByte *>(pData) + i * nBandSpace;
    4884          48 :             if (nPixelSpace == nBufTypeSize &&
    4885          48 :                 poVRTBand->m_dfNoDataValue == 0.0)
    4886             :             {
    4887          42 :                 if (nLineSpace == nBufXSize * nPixelSpace)
    4888             :                 {
    4889          42 :                     memset(pabyBandData, 0,
    4890          42 :                            static_cast<size_t>(nBufYSize * nLineSpace));
    4891             :                 }
    4892             :                 else
    4893             :                 {
    4894           0 :                     for (int iLine = 0; iLine < nBufYSize; iLine++)
    4895             :                     {
    4896           0 :                         memset(static_cast<GByte *>(pabyBandData) +
    4897           0 :                                    static_cast<GIntBig>(iLine) * nLineSpace,
    4898           0 :                                0, static_cast<size_t>(nBufXSize * nPixelSpace));
    4899             :                     }
    4900          42 :                 }
    4901             :             }
    4902             :             else
    4903             :             {
    4904           6 :                 double dfWriteValue = poVRTBand->m_dfNoDataValue;
    4905             : 
    4906        1014 :                 for (int iLine = 0; iLine < nBufYSize; iLine++)
    4907             :                 {
    4908        1008 :                     GDALCopyWords(&dfWriteValue, GDT_Float64, 0,
    4909        1008 :                                   static_cast<GByte *>(pabyBandData) +
    4910        1008 :                                       static_cast<GIntBig>(nLineSpace) * iLine,
    4911             :                                   eBufType, static_cast<int>(nPixelSpace),
    4912             :                                   nBufXSize);
    4913             :                 }
    4914             :             }
    4915             :         }
    4916             :     }
    4917          66 : }
    4918             : 
    4919             : /************************************************************************/
    4920             : /*                            RenderSource()                            */
    4921             : /************************************************************************/
    4922             : 
    4923         580 : CPLErr GDALTileIndexDataset::RenderSource(
    4924             :     const SourceDesc &oSourceDesc, bool bNeedInitBuffer, int nBandNrMax,
    4925             :     int nXOff, int nYOff, int nXSize, int nYSize, double dfXOff, double dfYOff,
    4926             :     double dfXSize, double dfYSize, int nBufXSize, int nBufYSize, void *pData,
    4927             :     GDALDataType eBufType, int nBandCount, BANDMAP_TYPE panBandMapIn,
    4928             :     GSpacing nPixelSpace, GSpacing nLineSpace, GSpacing nBandSpace,
    4929             :     GDALRasterIOExtraArg *psExtraArg,
    4930             :     VRTSource::WorkingState &oWorkingState) const
    4931             : {
    4932         580 :     auto &poTileDS = oSourceDesc.poDS;
    4933         580 :     auto &poSource = oSourceDesc.poSource;
    4934         580 :     auto poComplexSource = dynamic_cast<VRTComplexSource *>(poSource.get());
    4935         580 :     CPLErr eErr = CE_None;
    4936             : 
    4937        1004 :     const auto GetBandFromBandMap = [&oSourceDesc, panBandMapIn](int iBand)
    4938             :     {
    4939         507 :         return oSourceDesc.bBandMapTakenIntoAccount ? iBand + 1
    4940         507 :                                                     : panBandMapIn[iBand];
    4941         580 :     };
    4942             : 
    4943        1160 :     std::vector<int> anSerial;
    4944         580 :     if (oSourceDesc.bBandMapTakenIntoAccount)
    4945             :     {
    4946          25 :         for (int i = 0; i < nBandCount; ++i)
    4947          15 :             anSerial.push_back(i + 1);
    4948             :     }
    4949             :     BANDMAP_TYPE panBandMapForRasterIO =
    4950         580 :         oSourceDesc.bBandMapTakenIntoAccount ? anSerial.data() : panBandMapIn;
    4951             : 
    4952         580 :     if (poTileDS->GetRasterCount() + 1 == nBandNrMax &&
    4953         580 :         papoBands[nBandNrMax - 1]->GetColorInterpretation() == GCI_AlphaBand &&
    4954           0 :         papoBands[nBandNrMax - 1]->GetRasterDataType() == GDT_UInt8)
    4955             :     {
    4956             :         GDALRasterIOExtraArg sExtraArg;
    4957           0 :         INIT_RASTERIO_EXTRA_ARG(sExtraArg);
    4958           0 :         if (psExtraArg->eResampleAlg != GRIORA_NearestNeighbour)
    4959             :         {
    4960             :             // cppcheck-suppress redundantAssignment
    4961           0 :             sExtraArg.eResampleAlg = psExtraArg->eResampleAlg;
    4962             :         }
    4963             :         else
    4964             :         {
    4965             :             // cppcheck-suppress redundantAssignment
    4966           0 :             sExtraArg.eResampleAlg = m_eResampling;
    4967             :         }
    4968             : 
    4969             :         // Special case when there's typically a mix of RGB and RGBA source
    4970             :         // datasets and we read a RGB one.
    4971           0 :         for (int iBand = 0; iBand < nBandCount && eErr == CE_None; ++iBand)
    4972             :         {
    4973           0 :             const int nBandNr = GetBandFromBandMap(iBand);
    4974           0 :             if (nBandNr == nBandNrMax)
    4975             :             {
    4976             :                 // The window we will actually request from the source raster band.
    4977           0 :                 double dfReqXOff = 0.0;
    4978           0 :                 double dfReqYOff = 0.0;
    4979           0 :                 double dfReqXSize = 0.0;
    4980           0 :                 double dfReqYSize = 0.0;
    4981           0 :                 int nReqXOff = 0;
    4982           0 :                 int nReqYOff = 0;
    4983           0 :                 int nReqXSize = 0;
    4984           0 :                 int nReqYSize = 0;
    4985             : 
    4986             :                 // The window we will actual set _within_ the pData buffer.
    4987           0 :                 int nOutXOff = 0;
    4988           0 :                 int nOutYOff = 0;
    4989           0 :                 int nOutXSize = 0;
    4990           0 :                 int nOutYSize = 0;
    4991             : 
    4992           0 :                 bool bError = false;
    4993             : 
    4994           0 :                 auto poTileBand = poTileDS->GetRasterBand(1);
    4995           0 :                 poSource->SetRasterBand(poTileBand, false);
    4996           0 :                 if (poSource->GetSrcDstWindow(
    4997             :                         dfXOff, dfYOff, dfXSize, dfYSize, nBufXSize, nBufYSize,
    4998             :                         sExtraArg.eResampleAlg, &dfReqXOff, &dfReqYOff,
    4999             :                         &dfReqXSize, &dfReqYSize, &nReqXOff, &nReqYOff,
    5000             :                         &nReqXSize, &nReqYSize, &nOutXOff, &nOutYOff,
    5001           0 :                         &nOutXSize, &nOutYSize, bError))
    5002             :                 {
    5003           0 :                     GByte *pabyOut =
    5004             :                         static_cast<GByte *>(pData) +
    5005           0 :                         static_cast<GPtrDiff_t>(iBand * nBandSpace +
    5006           0 :                                                 nOutXOff * nPixelSpace +
    5007           0 :                                                 nOutYOff * nLineSpace);
    5008             : 
    5009           0 :                     constexpr GByte n255 = 255;
    5010           0 :                     for (int iY = 0; iY < nOutYSize; iY++)
    5011             :                     {
    5012           0 :                         GDALCopyWords(
    5013             :                             &n255, GDT_UInt8, 0,
    5014           0 :                             pabyOut + static_cast<GPtrDiff_t>(iY * nLineSpace),
    5015             :                             eBufType, static_cast<int>(nPixelSpace), nOutXSize);
    5016             :                     }
    5017             :                 }
    5018             :             }
    5019             :             else
    5020             :             {
    5021           0 :                 auto poTileBand = poTileDS->GetRasterBand(nBandNr);
    5022           0 :                 if (poComplexSource)
    5023             :                 {
    5024           0 :                     int bHasNoData = false;
    5025             :                     const double dfNoDataValue =
    5026           0 :                         poTileBand->GetNoDataValue(&bHasNoData);
    5027           0 :                     poComplexSource->SetNoDataValue(
    5028           0 :                         bHasNoData ? dfNoDataValue : VRT_NODATA_UNSET);
    5029             :                 }
    5030           0 :                 poSource->SetRasterBand(poTileBand, false);
    5031             : 
    5032           0 :                 GByte *pabyBandData =
    5033           0 :                     static_cast<GByte *>(pData) + iBand * nBandSpace;
    5034           0 :                 eErr = poSource->RasterIO(
    5035             :                     poTileBand->GetRasterDataType(), nXOff, nYOff, nXSize,
    5036             :                     nYSize, pabyBandData, nBufXSize, nBufYSize, eBufType,
    5037           0 :                     nPixelSpace, nLineSpace, &sExtraArg, oWorkingState);
    5038             :             }
    5039             :         }
    5040           0 :         return eErr;
    5041             :     }
    5042        1150 :     else if (!oSourceDesc.bBandMapTakenIntoAccount &&
    5043         570 :              poTileDS->GetRasterCount() < nBandNrMax)
    5044             :     {
    5045           2 :         CPLError(CE_Failure, CPLE_AppDefined, "%s has not enough bands.",
    5046             :                  oSourceDesc.osName.c_str());
    5047           2 :         return CE_Failure;
    5048             :     }
    5049             : 
    5050         578 :     if ((oSourceDesc.poMaskBand && bNeedInitBuffer) || nBandNrMax == 0)
    5051             :     {
    5052             :         // The window we will actually request from the source raster band.
    5053          71 :         double dfReqXOff = 0.0;
    5054          71 :         double dfReqYOff = 0.0;
    5055          71 :         double dfReqXSize = 0.0;
    5056          71 :         double dfReqYSize = 0.0;
    5057          71 :         int nReqXOff = 0;
    5058          71 :         int nReqYOff = 0;
    5059          71 :         int nReqXSize = 0;
    5060          71 :         int nReqYSize = 0;
    5061             : 
    5062             :         // The window we will actual set _within_ the pData buffer.
    5063          71 :         int nOutXOff = 0;
    5064          71 :         int nOutYOff = 0;
    5065          71 :         int nOutXSize = 0;
    5066          71 :         int nOutYSize = 0;
    5067             : 
    5068          71 :         bool bError = false;
    5069             : 
    5070          71 :         auto poFirstTileBand = poTileDS->GetRasterBand(1);
    5071          71 :         poSource->SetRasterBand(poFirstTileBand, false);
    5072             : 
    5073             :         GDALRasterIOExtraArg sExtraArg;
    5074          71 :         INIT_RASTERIO_EXTRA_ARG(sExtraArg);
    5075          71 :         CPL_IGNORE_RET_VAL(sExtraArg.bFloatingPointWindowValidity);
    5076          71 :         CPL_IGNORE_RET_VAL(sExtraArg.eResampleAlg);
    5077          71 :         if (psExtraArg->eResampleAlg != GRIORA_NearestNeighbour)
    5078             :         {
    5079           0 :             sExtraArg.eResampleAlg = psExtraArg->eResampleAlg;
    5080             :         }
    5081             :         else
    5082             :         {
    5083          71 :             sExtraArg.eResampleAlg = m_eResampling;
    5084             :         }
    5085             : 
    5086          71 :         if (poSource->GetSrcDstWindow(
    5087             :                 dfXOff, dfYOff, dfXSize, dfYSize, nBufXSize, nBufYSize,
    5088             :                 sExtraArg.eResampleAlg, &dfReqXOff, &dfReqYOff, &dfReqXSize,
    5089             :                 &dfReqYSize, &nReqXOff, &nReqYOff, &nReqXSize, &nReqYSize,
    5090          71 :                 &nOutXOff, &nOutYOff, &nOutXSize, &nOutYSize, bError))
    5091             :         {
    5092          71 :             int iMaskBandIdx = -1;
    5093          71 :             if (eBufType == GDT_UInt8 && nBandNrMax == 0)
    5094             :             {
    5095             :                 // when called from m_poMaskBand
    5096           5 :                 iMaskBandIdx = 0;
    5097             :             }
    5098          66 :             else if (oSourceDesc.poMaskBand)
    5099             :             {
    5100             :                 // If we request a Byte buffer and the mask band is actually
    5101             :                 // one of the queried bands of this request, we can save
    5102             :                 // requesting it separately.
    5103          66 :                 const int nMaskBandNr = oSourceDesc.poMaskBand->GetBand();
    5104          54 :                 if (eBufType == GDT_UInt8 && nMaskBandNr >= 1 &&
    5105         174 :                     nMaskBandNr <= poTileDS->GetRasterCount() &&
    5106          54 :                     poTileDS->GetRasterBand(nMaskBandNr) ==
    5107          54 :                         oSourceDesc.poMaskBand)
    5108             :                 {
    5109          94 :                     for (int iBand = 0; iBand < nBandCount; ++iBand)
    5110             :                     {
    5111          68 :                         if (panBandMapIn[iBand] == nMaskBandNr)
    5112             :                         {
    5113          26 :                             iMaskBandIdx = iBand;
    5114          26 :                             break;
    5115             :                         }
    5116             :                     }
    5117             :                 }
    5118             :             }
    5119             : 
    5120          71 :             sExtraArg.bFloatingPointWindowValidity = TRUE;
    5121          71 :             sExtraArg.dfXOff = dfReqXOff;
    5122          71 :             sExtraArg.dfYOff = dfReqYOff;
    5123          71 :             sExtraArg.dfXSize = dfReqXSize;
    5124          71 :             sExtraArg.dfYSize = dfReqYSize;
    5125             : 
    5126          95 :             if (iMaskBandIdx < 0 && oSourceDesc.abyMask.empty() &&
    5127          24 :                 oSourceDesc.poMaskBand)
    5128             :             {
    5129             :                 // Fetch the mask band
    5130             :                 try
    5131             :                 {
    5132          24 :                     oSourceDesc.abyMask.resize(static_cast<size_t>(nOutXSize) *
    5133          24 :                                                nOutYSize);
    5134             :                 }
    5135           0 :                 catch (const std::bad_alloc &)
    5136             :                 {
    5137           0 :                     CPLError(CE_Failure, CPLE_OutOfMemory,
    5138             :                              "Cannot allocate working buffer for mask");
    5139           0 :                     return CE_Failure;
    5140             :                 }
    5141             : 
    5142          24 :                 if (oSourceDesc.poMaskBand->RasterIO(
    5143             :                         GF_Read, nReqXOff, nReqYOff, nReqXSize, nReqYSize,
    5144          24 :                         oSourceDesc.abyMask.data(), nOutXSize, nOutYSize,
    5145          24 :                         GDT_UInt8, 0, 0, &sExtraArg) != CE_None)
    5146             :                 {
    5147           0 :                     oSourceDesc.abyMask.clear();
    5148           0 :                     return CE_Failure;
    5149             :                 }
    5150             :             }
    5151             : 
    5152             :             // Allocate a temporary contiguous buffer to receive pixel data
    5153          71 :             const int nBufTypeSize = GDALGetDataTypeSizeBytes(eBufType);
    5154          71 :             const size_t nWorkBufferBandSize =
    5155          71 :                 static_cast<size_t>(nOutXSize) * nOutYSize * nBufTypeSize;
    5156          71 :             std::vector<GByte> abyWorkBuffer;
    5157             :             try
    5158             :             {
    5159          71 :                 abyWorkBuffer.resize(nBandCount * nWorkBufferBandSize);
    5160             :             }
    5161           0 :             catch (const std::bad_alloc &)
    5162             :             {
    5163           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory,
    5164             :                          "Cannot allocate working buffer");
    5165           0 :                 return CE_Failure;
    5166             :             }
    5167             : 
    5168             :             const GByte *const pabyMask =
    5169             :                 iMaskBandIdx >= 0
    5170          31 :                     ? abyWorkBuffer.data() + iMaskBandIdx * nWorkBufferBandSize
    5171         102 :                     : oSourceDesc.abyMask.data();
    5172             : 
    5173          71 :             if (nBandNrMax == 0)
    5174             :             {
    5175             :                 // Special case when called from m_poMaskBand
    5176          14 :                 if (poTileDS->GetRasterBand(1)->GetMaskBand()->RasterIO(
    5177             :                         GF_Read, nReqXOff, nReqYOff, nReqXSize, nReqYSize,
    5178           7 :                         abyWorkBuffer.data(), nOutXSize, nOutYSize, eBufType, 0,
    5179           7 :                         0, &sExtraArg) != CE_None)
    5180             :                 {
    5181           0 :                     return CE_Failure;
    5182             :                 }
    5183             :             }
    5184         128 :             else if (poTileDS->RasterIO(GF_Read, nReqXOff, nReqYOff, nReqXSize,
    5185          64 :                                         nReqYSize, abyWorkBuffer.data(),
    5186             :                                         nOutXSize, nOutYSize, eBufType,
    5187             :                                         nBandCount, panBandMapForRasterIO, 0, 0,
    5188          64 :                                         0, &sExtraArg) != CE_None)
    5189             :             {
    5190           0 :                 return CE_Failure;
    5191             :             }
    5192             : 
    5193             :             // Compose the temporary contiguous buffer into the target
    5194             :             // buffer, taking into account the mask
    5195          71 :             GByte *pabyOut = static_cast<GByte *>(pData) +
    5196          71 :                              static_cast<GPtrDiff_t>(nOutXOff * nPixelSpace +
    5197          71 :                                                      nOutYOff * nLineSpace);
    5198             : 
    5199         162 :             for (int iBand = 0; iBand < nBandCount && eErr == CE_None; ++iBand)
    5200             :             {
    5201          91 :                 GByte *pabyDestBand =
    5202          91 :                     pabyOut + static_cast<GPtrDiff_t>(iBand * nBandSpace);
    5203             :                 const GByte *pabySrc =
    5204          91 :                     abyWorkBuffer.data() + iBand * nWorkBufferBandSize;
    5205             : 
    5206          91 :                 CompositeSrcWithMaskIntoDest(
    5207             :                     nOutXSize, nOutYSize, eBufType, nBufTypeSize, nPixelSpace,
    5208             :                     nLineSpace, pabySrc, pabyMask, pabyDestBand);
    5209             :             }
    5210          71 :         }
    5211             :     }
    5212         507 :     else if (m_bSameDataType && !bNeedInitBuffer && oSourceDesc.bHasNoData)
    5213             :     {
    5214             :         // We create a non-VRTComplexSource SimpleSource copy of poSource
    5215             :         // to be able to call DatasetRasterIO()
    5216           4 :         VRTSimpleSource oSimpleSource(poSource.get(), 1.0, 1.0);
    5217             : 
    5218             :         GDALRasterIOExtraArg sExtraArg;
    5219           4 :         INIT_RASTERIO_EXTRA_ARG(sExtraArg);
    5220           4 :         if (psExtraArg->eResampleAlg != GRIORA_NearestNeighbour)
    5221             :         {
    5222             :             // cppcheck-suppress redundantAssignment
    5223           0 :             sExtraArg.eResampleAlg = psExtraArg->eResampleAlg;
    5224             :         }
    5225             :         else
    5226             :         {
    5227             :             // cppcheck-suppress redundantAssignment
    5228           4 :             sExtraArg.eResampleAlg = m_eResampling;
    5229             :         }
    5230             : 
    5231           4 :         auto poTileBand = poTileDS->GetRasterBand(GetBandFromBandMap(0));
    5232           4 :         oSimpleSource.SetRasterBand(poTileBand, false);
    5233           4 :         eErr = oSimpleSource.DatasetRasterIO(
    5234           4 :             papoBands[0]->GetRasterDataType(), nXOff, nYOff, nXSize, nYSize,
    5235             :             pData, nBufXSize, nBufYSize, eBufType, nBandCount,
    5236             :             panBandMapForRasterIO, nPixelSpace, nLineSpace, nBandSpace,
    5237           4 :             &sExtraArg);
    5238             :     }
    5239         503 :     else if (m_bSameDataType && !poComplexSource)
    5240             :     {
    5241         494 :         auto poTileBand = poTileDS->GetRasterBand(GetBandFromBandMap(0));
    5242         494 :         poSource->SetRasterBand(poTileBand, false);
    5243             : 
    5244             :         GDALRasterIOExtraArg sExtraArg;
    5245         494 :         INIT_RASTERIO_EXTRA_ARG(sExtraArg);
    5246         494 :         if (poTileBand->GetColorTable())
    5247             :         {
    5248             :             // cppcheck-suppress redundantAssignment
    5249           0 :             sExtraArg.eResampleAlg = GRIORA_NearestNeighbour;
    5250             :         }
    5251         494 :         else if (psExtraArg->eResampleAlg != GRIORA_NearestNeighbour)
    5252             :         {
    5253             :             // cppcheck-suppress redundantAssignment
    5254           0 :             sExtraArg.eResampleAlg = psExtraArg->eResampleAlg;
    5255             :         }
    5256             :         else
    5257             :         {
    5258             :             // cppcheck-suppress redundantAssignment
    5259         494 :             sExtraArg.eResampleAlg = m_eResampling;
    5260             :         }
    5261             : 
    5262         988 :         eErr = poSource->DatasetRasterIO(
    5263         494 :             papoBands[0]->GetRasterDataType(), nXOff, nYOff, nXSize, nYSize,
    5264             :             pData, nBufXSize, nBufYSize, eBufType, nBandCount,
    5265             :             panBandMapForRasterIO, nPixelSpace, nLineSpace, nBandSpace,
    5266         494 :             &sExtraArg);
    5267             :     }
    5268             :     else
    5269             :     {
    5270          18 :         for (int i = 0; i < nBandCount && eErr == CE_None; ++i)
    5271             :         {
    5272           9 :             const int nBandNr = GetBandFromBandMap(i);
    5273           9 :             GByte *pabyBandData = static_cast<GByte *>(pData) + i * nBandSpace;
    5274           9 :             auto poTileBand = poTileDS->GetRasterBand(nBandNr);
    5275           9 :             if (poComplexSource)
    5276             :             {
    5277           9 :                 int bHasNoData = false;
    5278             :                 const double dfNoDataValue =
    5279           9 :                     poTileBand->GetNoDataValue(&bHasNoData);
    5280           9 :                 poComplexSource->SetNoDataValue(bHasNoData ? dfNoDataValue
    5281           9 :                                                            : VRT_NODATA_UNSET);
    5282             :             }
    5283           9 :             poSource->SetRasterBand(poTileBand, false);
    5284             : 
    5285             :             GDALRasterIOExtraArg sExtraArg;
    5286           9 :             INIT_RASTERIO_EXTRA_ARG(sExtraArg);
    5287           9 :             if (poTileBand->GetColorTable())
    5288             :             {
    5289             :                 // cppcheck-suppress redundantAssignment
    5290           0 :                 sExtraArg.eResampleAlg = GRIORA_NearestNeighbour;
    5291             :             }
    5292           9 :             else if (psExtraArg->eResampleAlg != GRIORA_NearestNeighbour)
    5293             :             {
    5294             :                 // cppcheck-suppress redundantAssignment
    5295           0 :                 sExtraArg.eResampleAlg = psExtraArg->eResampleAlg;
    5296             :             }
    5297             :             else
    5298             :             {
    5299             :                 // cppcheck-suppress redundantAssignment
    5300           9 :                 sExtraArg.eResampleAlg = m_eResampling;
    5301             :             }
    5302             : 
    5303          18 :             eErr = poSource->RasterIO(
    5304           9 :                 papoBands[nBandNr - 1]->GetRasterDataType(), nXOff, nYOff,
    5305             :                 nXSize, nYSize, pabyBandData, nBufXSize, nBufYSize, eBufType,
    5306           9 :                 nPixelSpace, nLineSpace, &sExtraArg, oWorkingState);
    5307             :         }
    5308             :     }
    5309         578 :     return eErr;
    5310             : }
    5311             : 
    5312             : /************************************************************************/
    5313             : /*                             IRasterIO()                              */
    5314             : /************************************************************************/
    5315             : 
    5316         281 : CPLErr GDALTileIndexDataset::IRasterIO(
    5317             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
    5318             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
    5319             :     int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
    5320             :     GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
    5321             : {
    5322         281 :     if (eRWFlag != GF_Read)
    5323           0 :         return CE_Failure;
    5324             : 
    5325         281 :     if (nBufXSize < nXSize && nBufYSize < nYSize && AreOverviewsEnabled())
    5326             :     {
    5327           4 :         int bTried = FALSE;
    5328           4 :         if (nBandCount == 1 && panBandMap[0] == 0)
    5329             :         {
    5330           1 :             if (m_poMaskBand)
    5331             :             {
    5332           1 :                 const CPLErr eErr = m_poMaskBand->TryOverviewRasterIO(
    5333             :                     eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize,
    5334             :                     nBufYSize, eBufType, nPixelSpace, nLineSpace, psExtraArg,
    5335             :                     &bTried);
    5336           1 :                 if (bTried)
    5337           4 :                     return eErr;
    5338           0 :             }
    5339             :         }
    5340             :         else
    5341             :         {
    5342           3 :             const CPLErr eErr = TryOverviewRasterIO(
    5343             :                 eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize,
    5344             :                 nBufYSize, eBufType, nBandCount, panBandMap, nPixelSpace,
    5345             :                 nLineSpace, nBandSpace, psExtraArg, &bTried);
    5346           3 :             if (bTried)
    5347           3 :                 return eErr;
    5348             :         }
    5349             :     }
    5350             : 
    5351         277 :     double dfXOff = nXOff;
    5352         277 :     double dfYOff = nYOff;
    5353         277 :     double dfXSize = nXSize;
    5354         277 :     double dfYSize = nYSize;
    5355         277 :     if (psExtraArg->bFloatingPointWindowValidity)
    5356             :     {
    5357           6 :         dfXOff = psExtraArg->dfXOff;
    5358           6 :         dfYOff = psExtraArg->dfYOff;
    5359           6 :         dfXSize = psExtraArg->dfXSize;
    5360           6 :         dfYSize = psExtraArg->dfYSize;
    5361             :     }
    5362             : 
    5363         277 :     if (!CollectSources(dfXOff, dfYOff, dfXSize, dfYSize, nBandCount,
    5364             :                         panBandMap,
    5365             :                         /* bMultiThreadAllowed = */ true))
    5366             :     {
    5367           3 :         return CE_Failure;
    5368             :     }
    5369             : 
    5370             :     // We might be called with nBandCount == 1 && panBandMap[0] == 0
    5371             :     // to mean m_poMaskBand
    5372         274 :     int nBandNrMax = 0;
    5373         604 :     for (int i = 0; i < nBandCount; ++i)
    5374             :     {
    5375         330 :         const int nBandNr = panBandMap[i];
    5376         330 :         nBandNrMax = std::max(nBandNrMax, nBandNr);
    5377             :     }
    5378             : 
    5379             :     const bool bNeedInitBuffer =
    5380         274 :         m_bLastMustUseMultiThreading || NeedInitBuffer(nBandCount, panBandMap);
    5381             : 
    5382         274 :     if (!bNeedInitBuffer)
    5383             :     {
    5384         208 :         return RenderSource(
    5385         208 :             m_aoSourceDesc.back(), bNeedInitBuffer, nBandNrMax, nXOff, nYOff,
    5386             :             nXSize, nYSize, dfXOff, dfYOff, dfXSize, dfYSize, nBufXSize,
    5387             :             nBufYSize, pData, eBufType, nBandCount, panBandMap, nPixelSpace,
    5388         416 :             nLineSpace, nBandSpace, psExtraArg, m_oWorkingState);
    5389             :     }
    5390             :     else
    5391             :     {
    5392          66 :         InitBuffer(pData, nBufXSize, nBufYSize, eBufType, nBandCount,
    5393             :                    panBandMap, nPixelSpace, nLineSpace, nBandSpace);
    5394             : 
    5395          66 :         if (m_bLastMustUseMultiThreading)
    5396             :         {
    5397          12 :             CPLErrorAccumulator oErrorAccumulator;
    5398           6 :             std::atomic<bool> bSuccess = true;
    5399             :             const int nContributingSources =
    5400           6 :                 static_cast<int>(m_aoSourceDesc.size());
    5401           6 :             CPLWorkerThreadPool *psThreadPool = GDALGetGlobalThreadPool(
    5402           6 :                 std::min(nContributingSources, m_nNumThreads));
    5403             :             const int nThreads =
    5404           6 :                 std::min(nContributingSources, psThreadPool->GetThreadCount());
    5405           6 :             CPLDebugOnly("GTI",
    5406             :                          "IRasterIO(): use optimized "
    5407             :                          "multi-threaded code path. "
    5408             :                          "Using %d threads",
    5409             :                          nThreads);
    5410             : 
    5411             :             {
    5412          12 :                 std::lock_guard oLock(m_oQueueWorkingStates.oMutex);
    5413           6 :                 if (m_oQueueWorkingStates.oStates.size() <
    5414           6 :                     static_cast<size_t>(nThreads))
    5415             :                 {
    5416           4 :                     m_oQueueWorkingStates.oStates.resize(nThreads);
    5417             :                 }
    5418          22 :                 for (int i = 0; i < nThreads; ++i)
    5419             :                 {
    5420          16 :                     if (!m_oQueueWorkingStates.oStates[i])
    5421          10 :                         m_oQueueWorkingStates.oStates[i] =
    5422          20 :                             std::make_unique<VRTSource::WorkingState>();
    5423             :                 }
    5424             :             }
    5425             : 
    5426           6 :             auto oQueue = psThreadPool->CreateJobQueue();
    5427           6 :             std::atomic<int> nCompletedJobs = 0;
    5428         144 :             for (auto &oSourceDesc : m_aoSourceDesc)
    5429             :             {
    5430         138 :                 auto psJob = new RasterIOJob();
    5431         138 :                 psJob->bSTACCollection = m_bSTACCollection;
    5432         138 :                 psJob->poDS = this;
    5433         138 :                 psJob->pbSuccess = &bSuccess;
    5434         138 :                 psJob->poErrorAccumulator = &oErrorAccumulator;
    5435         138 :                 psJob->pnCompletedJobs = &nCompletedJobs;
    5436         138 :                 psJob->poQueueWorkingStates = &m_oQueueWorkingStates;
    5437         138 :                 psJob->nBandNrMax = nBandNrMax;
    5438         138 :                 psJob->nXOff = nXOff;
    5439         138 :                 psJob->nYOff = nYOff;
    5440         138 :                 psJob->nXSize = nXSize;
    5441         138 :                 psJob->nYSize = nYSize;
    5442         138 :                 psJob->pData = pData;
    5443         138 :                 psJob->nBufXSize = nBufXSize;
    5444         138 :                 psJob->nBufYSize = nBufYSize;
    5445         138 :                 psJob->eBufType = eBufType;
    5446         138 :                 psJob->nBandCount = nBandCount;
    5447         138 :                 psJob->panBandMap = panBandMap;
    5448         138 :                 psJob->nPixelSpace = nPixelSpace;
    5449         138 :                 psJob->nLineSpace = nLineSpace;
    5450         138 :                 psJob->nBandSpace = nBandSpace;
    5451         138 :                 psJob->psExtraArg = psExtraArg;
    5452             : 
    5453             :                 psJob->osTileName = oSourceDesc.poFeature->GetFieldAsString(
    5454         138 :                     m_nLocationFieldIndex);
    5455             : 
    5456         138 :                 if (!oQueue->SubmitJob(RasterIOJob::Func, psJob))
    5457             :                 {
    5458           0 :                     delete psJob;
    5459           0 :                     bSuccess = false;
    5460           0 :                     break;
    5461             :                 }
    5462             :             }
    5463             : 
    5464          90 :             while (oQueue->WaitEvent())
    5465             :             {
    5466             :                 // Quite rough progress callback. We could do better by counting
    5467             :                 // the number of contributing pixels.
    5468          84 :                 if (psExtraArg->pfnProgress)
    5469             :                 {
    5470         164 :                     psExtraArg->pfnProgress(double(nCompletedJobs.load()) /
    5471             :                                                 nContributingSources,
    5472             :                                             "", psExtraArg->pProgressData);
    5473             :                 }
    5474             :             }
    5475             : 
    5476           6 :             oErrorAccumulator.ReplayErrors();
    5477             : 
    5478           6 :             if (bSuccess && psExtraArg->pfnProgress)
    5479             :             {
    5480           4 :                 psExtraArg->pfnProgress(1.0, "", psExtraArg->pProgressData);
    5481             :             }
    5482             : 
    5483           6 :             return bSuccess ? CE_None : CE_Failure;
    5484             :         }
    5485             :         else
    5486             :         {
    5487             :             // Now render from bottom of the stack to top.
    5488         295 :             for (auto &oSourceDesc : m_aoSourceDesc)
    5489             :             {
    5490         470 :                 if (oSourceDesc.poDS &&
    5491         235 :                     RenderSource(oSourceDesc, bNeedInitBuffer, nBandNrMax,
    5492             :                                  nXOff, nYOff, nXSize, nYSize, dfXOff, dfYOff,
    5493             :                                  dfXSize, dfYSize, nBufXSize, nBufYSize, pData,
    5494             :                                  eBufType, nBandCount, panBandMap, nPixelSpace,
    5495             :                                  nLineSpace, nBandSpace, psExtraArg,
    5496         470 :                                  m_oWorkingState) != CE_None)
    5497           0 :                     return CE_Failure;
    5498             :             }
    5499             : 
    5500          60 :             if (psExtraArg->pfnProgress)
    5501             :             {
    5502           4 :                 psExtraArg->pfnProgress(1.0, "", psExtraArg->pProgressData);
    5503             :             }
    5504             : 
    5505          60 :             return CE_None;
    5506             :         }
    5507             :     }
    5508             : }
    5509             : 
    5510             : /************************************************************************/
    5511             : /*              GDALTileIndexDataset::RasterIOJob::Func()               */
    5512             : /************************************************************************/
    5513             : 
    5514         138 : void GDALTileIndexDataset::RasterIOJob::Func(void *pData)
    5515             : {
    5516             :     auto psJob =
    5517         276 :         std::unique_ptr<RasterIOJob>(static_cast<RasterIOJob *>(pData));
    5518         138 :     if (*psJob->pbSuccess)
    5519             :     {
    5520             :         const std::string osTileName(GetAbsoluteFileName(
    5521         414 :             psJob->osTileName.c_str(), psJob->poDS->GetDescription(),
    5522         276 :             psJob->bSTACCollection));
    5523             : 
    5524         276 :         SourceDesc oSourceDesc;
    5525             : 
    5526         276 :         auto oAccumulator = psJob->poErrorAccumulator->InstallForCurrentScope();
    5527         138 :         CPL_IGNORE_RET_VAL(oAccumulator);
    5528             : 
    5529             :         const bool bCanOpenSource =
    5530         138 :             psJob->poDS->GetSourceDesc(osTileName, oSourceDesc,
    5531         138 :                                        &psJob->poQueueWorkingStates->oMutex,
    5532         413 :                                        psJob->nBandCount, psJob->panBandMap) &&
    5533         137 :             oSourceDesc.poDS;
    5534             : 
    5535         138 :         if (!bCanOpenSource)
    5536             :         {
    5537           1 :             *psJob->pbSuccess = false;
    5538             :         }
    5539             :         else
    5540             :         {
    5541         137 :             GDALRasterIOExtraArg sArg = *(psJob->psExtraArg);
    5542         137 :             sArg.pfnProgress = nullptr;
    5543         137 :             sArg.pProgressData = nullptr;
    5544             : 
    5545         137 :             std::unique_ptr<VRTSource::WorkingState> poWorkingState;
    5546             :             {
    5547         274 :                 std::lock_guard oLock(psJob->poQueueWorkingStates->oMutex);
    5548             :                 poWorkingState =
    5549         137 :                     std::move(psJob->poQueueWorkingStates->oStates.back());
    5550         137 :                 psJob->poQueueWorkingStates->oStates.pop_back();
    5551         137 :                 CPLAssert(poWorkingState.get());
    5552             :             }
    5553             : 
    5554         137 :             double dfXOff = psJob->nXOff;
    5555         137 :             double dfYOff = psJob->nYOff;
    5556         137 :             double dfXSize = psJob->nXSize;
    5557         137 :             double dfYSize = psJob->nYSize;
    5558         137 :             if (psJob->psExtraArg->bFloatingPointWindowValidity)
    5559             :             {
    5560           0 :                 dfXOff = psJob->psExtraArg->dfXOff;
    5561           0 :                 dfYOff = psJob->psExtraArg->dfYOff;
    5562           0 :                 dfXSize = psJob->psExtraArg->dfXSize;
    5563           0 :                 dfYSize = psJob->psExtraArg->dfYSize;
    5564             :             }
    5565             : 
    5566             :             const bool bRenderOK =
    5567         274 :                 psJob->poDS->RenderSource(
    5568         137 :                     oSourceDesc, /*bNeedInitBuffer = */ true, psJob->nBandNrMax,
    5569         137 :                     psJob->nXOff, psJob->nYOff, psJob->nXSize, psJob->nYSize,
    5570         137 :                     dfXOff, dfYOff, dfXSize, dfYSize, psJob->nBufXSize,
    5571         137 :                     psJob->nBufYSize, psJob->pData, psJob->eBufType,
    5572         137 :                     psJob->nBandCount, psJob->panBandMap, psJob->nPixelSpace,
    5573         137 :                     psJob->nLineSpace, psJob->nBandSpace, &sArg,
    5574         137 :                     *(poWorkingState.get())) == CE_None;
    5575             : 
    5576         137 :             if (!bRenderOK)
    5577             :             {
    5578           1 :                 *psJob->pbSuccess = false;
    5579             :             }
    5580             : 
    5581             :             {
    5582         274 :                 std::lock_guard oLock(psJob->poQueueWorkingStates->oMutex);
    5583         274 :                 psJob->poQueueWorkingStates->oStates.push_back(
    5584         137 :                     std::move(poWorkingState));
    5585             :             }
    5586             :         }
    5587             :     }
    5588             : 
    5589         138 :     ++(*psJob->pnCompletedJobs);
    5590         138 : }
    5591             : 
    5592             : #ifdef GDAL_ENABLE_ALGORITHMS
    5593             : 
    5594             : /************************************************************************/
    5595             : /*                        GDALGTICreateAlgorithm                        */
    5596             : /************************************************************************/
    5597             : 
    5598             : class GDALGTICreateAlgorithm final : public GDALRasterIndexAlgorithm
    5599             : {
    5600             :   public:
    5601             :     static constexpr const char *NAME = "create";
    5602             :     static constexpr const char *DESCRIPTION =
    5603             :         "Create an index of raster datasets compatible with the GDAL Tile "
    5604             :         "Index "
    5605             :         "(GTI) driver.";
    5606             :     static constexpr const char *HELP_URL =
    5607             :         "/programs/gdal_driver_gti_create.html";
    5608             : 
    5609             :     GDALGTICreateAlgorithm();
    5610             : 
    5611             :   protected:
    5612             :     bool AddExtraOptions(CPLStringList &aosOptions) override;
    5613             : 
    5614             :   private:
    5615             :     std::string m_xmlFilename{};
    5616             :     std::vector<double> m_resolution{};
    5617             :     std::vector<double> m_bbox{};
    5618             :     std::string m_dataType{};
    5619             :     int m_bandCount = 0;
    5620             :     std::vector<double> m_nodata{};
    5621             :     std::vector<std::string> m_colorInterpretation{};
    5622             :     bool m_mask = false;
    5623             :     std::vector<std::string> m_fetchedMetadata{};
    5624             : };
    5625             : 
    5626             : /************************************************************************/
    5627             : /*           GDALGTICreateAlgorithm::GDALGTICreateAlgorithm()           */
    5628             : /************************************************************************/
    5629             : 
    5630         146 : GDALGTICreateAlgorithm::GDALGTICreateAlgorithm()
    5631         146 :     : GDALRasterIndexAlgorithm(NAME, DESCRIPTION, HELP_URL)
    5632             : {
    5633         146 :     AddProgressArg();
    5634         146 :     AddInputDatasetArg(&m_inputDatasets, GDAL_OF_RASTER)
    5635         146 :         .SetAutoOpenDataset(false);
    5636         146 :     GDALVectorOutputAbstractAlgorithm::AddAllOutputArgs();
    5637             : 
    5638         146 :     AddCommonOptions();
    5639             : 
    5640             :     AddArg("xml-filename", 0,
    5641             :            _("Filename of the XML Virtual Tile Index file to generate, that "
    5642             :              "can be used as an input for the GDAL GTI / Virtual Raster Tile "
    5643             :              "Index driver"),
    5644         292 :            &m_xmlFilename)
    5645         146 :         .SetMinCharCount(1);
    5646             : 
    5647             :     AddArg("resolution", 0,
    5648             :            _("Resolution (in destination CRS units) of the virtual mosaic"),
    5649         292 :            &m_resolution)
    5650         146 :         .SetMinCount(2)
    5651         146 :         .SetMaxCount(2)
    5652         146 :         .SetMinValueExcluded(0)
    5653         146 :         .SetRepeatedArgAllowed(false)
    5654         146 :         .SetDisplayHintAboutRepetition(false)
    5655         146 :         .SetMetaVar("<xres>,<yres>");
    5656             : 
    5657             :     AddBBOXArg(
    5658             :         &m_bbox,
    5659         146 :         _("Bounding box (in destination CRS units) of the virtual mosaic"));
    5660         146 :     AddOutputDataTypeArg(&m_dataType, _("Datatype of the virtual mosaic"));
    5661             :     AddArg("band-count", 0, _("Number of bands of the virtual mosaic"),
    5662         292 :            &m_bandCount)
    5663         146 :         .SetMinValueIncluded(1);
    5664             :     AddArg("nodata", 0, _("Nodata value(s) of the bands of the virtual mosaic"),
    5665         146 :            &m_nodata);
    5666             :     AddArg("color-interpretation", 0,
    5667             :            _("Color interpretation(s) of the bands of the virtual mosaic"),
    5668         292 :            &m_colorInterpretation)
    5669         146 :         .SetChoices("red", "green", "blue", "alpha", "gray", "undefined");
    5670             :     AddArg("mask", 0, _("Defines that the virtual mosaic has a mask band"),
    5671         146 :            &m_mask);
    5672             :     AddArg("fetch-metadata", 0,
    5673             :            _("Fetch a metadata item from source rasters and write it as a "
    5674             :              "field in the index."),
    5675         292 :            &m_fetchedMetadata)
    5676         292 :         .SetMetaVar("<gdal-metadata-name>,<field-name>,<field-type>")
    5677         146 :         .SetPackedValuesAllowed(false)
    5678             :         .AddValidationAction(
    5679           6 :             [this]()
    5680             :             {
    5681           6 :                 for (const std::string &s : m_fetchedMetadata)
    5682             :                 {
    5683             :                     const CPLStringList aosTokens(
    5684           4 :                         CSLTokenizeString2(s.c_str(), ",", 0));
    5685           4 :                     if (aosTokens.size() != 3)
    5686             :                     {
    5687           1 :                         ReportError(
    5688             :                             CE_Failure, CPLE_IllegalArg,
    5689             :                             "'%s' is not of the form "
    5690             :                             "<gdal-metadata-name>,<field-name>,<field-type>",
    5691             :                             s.c_str());
    5692           1 :                         return false;
    5693             :                     }
    5694           3 :                     bool ok = false;
    5695          18 :                     for (const char *type : {"String", "Integer", "Integer64",
    5696          21 :                                              "Real", "Date", "DateTime"})
    5697             :                     {
    5698          18 :                         if (EQUAL(aosTokens[2], type))
    5699           2 :                             ok = true;
    5700             :                     }
    5701           3 :                     if (!ok)
    5702             :                     {
    5703           1 :                         ReportError(CE_Failure, CPLE_IllegalArg,
    5704             :                                     "'%s' has an invalid field type '%s'. It "
    5705             :                                     "should be one of 'String', 'Integer', "
    5706             :                                     "'Integer64', 'Real', 'Date', 'DateTime'.",
    5707             :                                     s.c_str(), aosTokens[2]);
    5708           1 :                         return false;
    5709             :                     }
    5710             :                 }
    5711           2 :                 return true;
    5712         146 :             });
    5713         146 : }
    5714             : 
    5715             : /************************************************************************/
    5716             : /*              GDALGTICreateAlgorithm::AddExtraOptions()               */
    5717             : /************************************************************************/
    5718             : 
    5719           6 : bool GDALGTICreateAlgorithm::AddExtraOptions(CPLStringList &aosOptions)
    5720             : {
    5721           6 :     if (!m_xmlFilename.empty())
    5722             :     {
    5723           1 :         aosOptions.push_back("-gti_filename");
    5724           1 :         aosOptions.push_back(m_xmlFilename);
    5725             :     }
    5726           6 :     if (!m_resolution.empty())
    5727             :     {
    5728           1 :         aosOptions.push_back("-tr");
    5729           1 :         aosOptions.push_back(CPLSPrintf("%.17g", m_resolution[0]));
    5730           1 :         aosOptions.push_back(CPLSPrintf("%.17g", m_resolution[1]));
    5731             :     }
    5732           6 :     if (!m_bbox.empty())
    5733             :     {
    5734           1 :         aosOptions.push_back("-te");
    5735           1 :         aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[0]));
    5736           1 :         aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[1]));
    5737           1 :         aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[2]));
    5738           1 :         aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[3]));
    5739             :     }
    5740           6 :     if (!m_dataType.empty())
    5741             :     {
    5742           1 :         aosOptions.push_back("-ot");
    5743           1 :         aosOptions.push_back(m_dataType);
    5744             :     }
    5745           6 :     if (m_bandCount > 0)
    5746             :     {
    5747           3 :         aosOptions.push_back("-bandcount");
    5748           3 :         aosOptions.push_back(CPLSPrintf("%d", m_bandCount));
    5749             : 
    5750           5 :         if (!m_nodata.empty() && m_nodata.size() != 1 &&
    5751           2 :             static_cast<int>(m_nodata.size()) != m_bandCount)
    5752             :         {
    5753           1 :             ReportError(CE_Failure, CPLE_IllegalArg,
    5754             :                         "%d nodata values whereas one or %d were expected",
    5755           1 :                         static_cast<int>(m_nodata.size()), m_bandCount);
    5756           1 :             return false;
    5757             :         }
    5758             : 
    5759           4 :         if (!m_colorInterpretation.empty() &&
    5760           4 :             m_colorInterpretation.size() != 1 &&
    5761           2 :             static_cast<int>(m_colorInterpretation.size()) != m_bandCount)
    5762             :         {
    5763           1 :             ReportError(
    5764             :                 CE_Failure, CPLE_IllegalArg,
    5765             :                 "%d color interpretations whereas one or %d were expected",
    5766           1 :                 static_cast<int>(m_colorInterpretation.size()), m_bandCount);
    5767           1 :             return false;
    5768             :         }
    5769             :     }
    5770           4 :     if (!m_nodata.empty())
    5771             :     {
    5772           2 :         std::string val;
    5773           3 :         for (double v : m_nodata)
    5774             :         {
    5775           2 :             if (!val.empty())
    5776           1 :                 val += ',';
    5777           2 :             val += CPLSPrintf("%.17g", v);
    5778             :         }
    5779           1 :         aosOptions.push_back("-nodata");
    5780           1 :         aosOptions.push_back(val);
    5781             :     }
    5782           4 :     if (!m_colorInterpretation.empty())
    5783             :     {
    5784           2 :         std::string val;
    5785           3 :         for (const std::string &s : m_colorInterpretation)
    5786             :         {
    5787           2 :             if (!val.empty())
    5788           1 :                 val += ',';
    5789           2 :             val += s;
    5790             :         }
    5791           1 :         aosOptions.push_back("-colorinterp");
    5792           1 :         aosOptions.push_back(val);
    5793             :     }
    5794           4 :     if (m_mask)
    5795           1 :         aosOptions.push_back("-mask");
    5796           5 :     for (const std::string &s : m_fetchedMetadata)
    5797             :     {
    5798           1 :         aosOptions.push_back("-fetch_md");
    5799           2 :         const CPLStringList aosTokens(CSLTokenizeString2(s.c_str(), ",", 0));
    5800           4 :         for (const char *token : aosTokens)
    5801             :         {
    5802           3 :             aosOptions.push_back(token);
    5803             :         }
    5804             :     }
    5805           4 :     return true;
    5806             : }
    5807             : 
    5808             : /************************************************************************/
    5809             : /*                 GDALTileIndexInstantiateAlgorithm()                  */
    5810             : /************************************************************************/
    5811             : 
    5812             : static GDALAlgorithm *
    5813         146 : GDALTileIndexInstantiateAlgorithm(const std::vector<std::string> &aosPath)
    5814             : {
    5815         146 :     if (aosPath.size() == 1 && aosPath[0] == "create")
    5816             :     {
    5817         146 :         return std::make_unique<GDALGTICreateAlgorithm>().release();
    5818             :     }
    5819             :     else
    5820             :     {
    5821           0 :         return nullptr;
    5822             :     }
    5823             : }
    5824             : 
    5825             : #endif
    5826             : 
    5827             : /************************************************************************/
    5828             : /*                          GDALRegister_GTI()                          */
    5829             : /************************************************************************/
    5830             : 
    5831        2129 : void GDALRegister_GTI()
    5832             : {
    5833        2129 :     if (GDALGetDriverByName("GTI") != nullptr)
    5834         263 :         return;
    5835             : 
    5836        3732 :     auto poDriver = std::make_unique<GDALDriver>();
    5837             : 
    5838        1866 :     poDriver->SetDescription("GTI");
    5839        1866 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    5840        1866 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "GDAL Raster Tile Index");
    5841        1866 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "gti.gpkg gti.fgb gti");
    5842        1866 :     poDriver->SetMetadataItem(GDAL_DMD_CONNECTION_PREFIX, GTI_PREFIX);
    5843        1866 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/gti.html");
    5844             : 
    5845        1866 :     poDriver->pfnOpen = GDALTileIndexDatasetOpen;
    5846        1866 :     poDriver->pfnIdentify = GDALTileIndexDatasetIdentify;
    5847             : 
    5848        1866 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    5849             : 
    5850        1866 :     poDriver->SetMetadataItem(
    5851             :         GDAL_DMD_OPENOPTIONLIST,
    5852             :         "<OpenOptionList>"
    5853             :         "  <Option name='LAYER' type='string'/>"
    5854             :         "  <Option name='SQL' type='string'/>"
    5855             :         "  <Option name='SPATIAL_SQL' type='string'/>"
    5856             :         "  <Option name='LOCATION_FIELD' type='string'/>"
    5857             :         "  <Option name='SORT_FIELD' type='string'/>"
    5858             :         "  <Option name='SORT_FIELD_ASC' type='boolean'/>"
    5859             :         "  <Option name='FILTER' type='string'/>"
    5860             :         "  <Option name='SRS' type='string'/>"
    5861             :         "  <Option name='SRS_BEHAVIOR' type='string-select' "
    5862             :         "description='How to handle a mismatch between SRS and the vector "
    5863             :         "layer SRS'>"
    5864             :         "    <Value>OVERRIDE</Value>"
    5865             :         "    <Value>REPROJECT</Value>"
    5866             :         "  </Option>"
    5867             :         "  <Option name='RESX' type='float'/>"
    5868             :         "  <Option name='RESY' type='float'/>"
    5869             :         "  <Option name='MINX' type='float'/>"
    5870             :         "  <Option name='MINY' type='float'/>"
    5871             :         "  <Option name='MAXX' type='float'/>"
    5872             :         "  <Option name='MAXY' type='float'/>"
    5873             :         "  <Option name='NUM_THREADS' type='string' description="
    5874             :         "'Number of worker threads for reading. Can be set to ALL_CPUS' "
    5875             :         "default='ALL_CPUS'/>"
    5876             :         "  <Option name='WARPING_MEMORY_SIZE' type='string' description="
    5877             :         "'Set the amount of memory that the warp API is allowed to use for "
    5878             :         "caching' default='64MB'/>"
    5879             :         "  <Option name='ALLOWED_RASTER_DRIVERS' type='string' description="
    5880             :         "'Comma-separated list of allowed raster driver names'/>"
    5881        1866 :         "</OpenOptionList>");
    5882             : 
    5883             : #ifdef GDAL_ENABLE_ALGORITHMS
    5884        3732 :     poDriver->DeclareAlgorithm({"create"});
    5885        1866 :     poDriver->pfnInstantiateAlgorithm = GDALTileIndexInstantiateAlgorithm;
    5886             : #endif
    5887             : 
    5888             : #ifdef BUILT_AS_PLUGIN
    5889             :     // Used by gdaladdo and test_gdaladdo.py
    5890             :     poDriver->SetMetadataItem("IS_PLUGIN", "YES");
    5891             : #endif
    5892             : 
    5893        1866 :     GetGDALDriverManager()->RegisterDriver(poDriver.release());
    5894             : }

Generated by: LCOV version 1.14