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

Generated by: LCOV version 1.14