LCOV - code coverage report
Current view: top level - frmts/stacta - stactadataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 612 711 86.1 %
Date: 2024-05-14 13:00:50 Functions: 21 24 87.5 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  STACTA (Spatio-Temporal Asset Catalog Tiled Assets) driver
       5             :  * Author:   Even Rouault, <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2020, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * Permission is hereby granted, free of charge, to any person obtaining a
      11             :  * copy of this software and associated documentation files (the "Software"),
      12             :  * to deal in the Software without restriction, including without limitation
      13             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      14             :  * and/or sell copies of the Software, and to permit persons to whom the
      15             :  * Software is furnished to do so, subject to the following conditions:
      16             :  *
      17             :  * The above copyright notice and this permission notice shall be included
      18             :  * in all copies or substantial portions of the Software.
      19             :  *
      20             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      21             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      22             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      23             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      24             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      25             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      26             :  * DEALINGS IN THE SOFTWARE.
      27             :  ****************************************************************************/
      28             : 
      29             : #include "cpl_json.h"
      30             : #include "cpl_mem_cache.h"
      31             : #include "cpl_string.h"
      32             : #include "gdal_pam.h"
      33             : #include "gdal_utils.h"
      34             : #include "memdataset.h"
      35             : #include "tilematrixset.hpp"
      36             : #include "stactadataset.h"
      37             : 
      38             : #include <algorithm>
      39             : #include <array>
      40             : #include <limits>
      41             : #include <map>
      42             : #include <memory>
      43             : #include <vector>
      44             : 
      45             : extern "C" void GDALRegister_STACTA();
      46             : 
      47             : // Implements a driver for
      48             : // https://github.com/stac-extensions/tiled-assets
      49             : 
      50             : /************************************************************************/
      51             : /*                         STACTARasterBand()                           */
      52             : /************************************************************************/
      53             : 
      54          57 : STACTARasterBand::STACTARasterBand(STACTADataset *poDSIn, int nBandIn,
      55          57 :                                    GDALRasterBand *poProtoBand)
      56          57 :     : m_eColorInterp(poProtoBand->GetColorInterpretation())
      57             : {
      58          57 :     poDS = poDSIn;
      59          57 :     nBand = nBandIn;
      60          57 :     eDataType = poProtoBand->GetRasterDataType();
      61          57 :     poProtoBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
      62          57 :     nRasterXSize = poDSIn->GetRasterXSize();
      63          57 :     nRasterYSize = poDSIn->GetRasterYSize();
      64          57 :     m_dfNoData = poProtoBand->GetNoDataValue(&m_bHasNoDataValue);
      65          57 : }
      66             : 
      67             : /************************************************************************/
      68             : /*                           IReadBlock()                               */
      69             : /************************************************************************/
      70             : 
      71           0 : CPLErr STACTARasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
      72             :                                     void *pImage)
      73             : {
      74           0 :     auto poGDS = cpl::down_cast<STACTADataset *>(poDS);
      75           0 :     return poGDS->m_poDS->GetRasterBand(nBand)->ReadBlock(nBlockXOff,
      76           0 :                                                           nBlockYOff, pImage);
      77             : }
      78             : 
      79             : /************************************************************************/
      80             : /*                           IRasterIO()                                */
      81             : /************************************************************************/
      82             : 
      83           4 : CPLErr STACTARasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
      84             :                                    int nXSize, int nYSize, void *pData,
      85             :                                    int nBufXSize, int nBufYSize,
      86             :                                    GDALDataType eBufType, GSpacing nPixelSpace,
      87             :                                    GSpacing nLineSpace,
      88             :                                    GDALRasterIOExtraArg *psExtraArg)
      89             : {
      90           4 :     auto poGDS = cpl::down_cast<STACTADataset *>(poDS);
      91           4 :     if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
      92           8 :         poGDS->m_apoOverviewDS.size() >= 1 && eRWFlag == GF_Read)
      93             :     {
      94             :         int bTried;
      95           1 :         CPLErr eErr = TryOverviewRasterIO(
      96             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
      97             :             eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried);
      98           1 :         if (bTried)
      99           1 :             return eErr;
     100             :     }
     101             : 
     102           3 :     return poGDS->m_poDS->GetRasterBand(nBand)->RasterIO(
     103             :         eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
     104           3 :         eBufType, nPixelSpace, nLineSpace, psExtraArg);
     105             : }
     106             : 
     107             : /************************************************************************/
     108             : /*                             IRasterIO()                              */
     109             : /************************************************************************/
     110             : 
     111          12 : CPLErr STACTADataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
     112             :                                 int nXSize, int nYSize, void *pData,
     113             :                                 int nBufXSize, int nBufYSize,
     114             :                                 GDALDataType eBufType, int nBandCount,
     115             :                                 int *panBandMap, GSpacing nPixelSpace,
     116             :                                 GSpacing nLineSpace, GSpacing nBandSpace,
     117             :                                 GDALRasterIOExtraArg *psExtraArg)
     118             : {
     119          12 :     if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
     120          24 :         m_apoOverviewDS.size() >= 1 && eRWFlag == GF_Read)
     121             :     {
     122             :         int bTried;
     123           6 :         CPLErr eErr = TryOverviewRasterIO(
     124             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
     125             :             eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
     126             :             nBandSpace, psExtraArg, &bTried);
     127           6 :         if (bTried)
     128           2 :             return eErr;
     129             :     }
     130             : 
     131          10 :     return m_poDS->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
     132             :                             nBufXSize, nBufYSize, eBufType, nBandCount,
     133             :                             panBandMap, nPixelSpace, nLineSpace, nBandSpace,
     134          10 :                             psExtraArg);
     135             : }
     136             : 
     137             : /************************************************************************/
     138             : /*                          GetOverviewCount()                          */
     139             : /************************************************************************/
     140             : 
     141          31 : int STACTARasterBand::GetOverviewCount()
     142             : {
     143          31 :     STACTADataset *poGDS = cpl::down_cast<STACTADataset *>(poDS);
     144          31 :     return static_cast<int>(poGDS->m_apoOverviewDS.size());
     145             : }
     146             : 
     147             : /************************************************************************/
     148             : /*                             GetOverview()                            */
     149             : /************************************************************************/
     150             : 
     151          21 : GDALRasterBand *STACTARasterBand::GetOverview(int nIdx)
     152             : {
     153          21 :     STACTADataset *poGDS = cpl::down_cast<STACTADataset *>(poDS);
     154          21 :     if (nIdx < 0 || nIdx >= GetOverviewCount())
     155           0 :         return nullptr;
     156          21 :     return poGDS->m_apoOverviewDS[nIdx]->GetRasterBand(nBand);
     157             : }
     158             : 
     159             : /************************************************************************/
     160             : /*                           GetNoDataValue()                           */
     161             : /************************************************************************/
     162             : 
     163          20 : double STACTARasterBand::GetNoDataValue(int *pbHasNoData)
     164             : {
     165          20 :     if (pbHasNoData)
     166          20 :         *pbHasNoData = m_bHasNoDataValue;
     167          20 :     return m_dfNoData;
     168             : }
     169             : 
     170             : /************************************************************************/
     171             : /*                        STACTARawRasterBand()                         */
     172             : /************************************************************************/
     173             : 
     174          81 : STACTARawRasterBand::STACTARawRasterBand(STACTARawDataset *poDSIn, int nBandIn,
     175          81 :                                          GDALRasterBand *poProtoBand)
     176          81 :     : m_eColorInterp(poProtoBand->GetColorInterpretation())
     177             : {
     178          81 :     poDS = poDSIn;
     179          81 :     nBand = nBandIn;
     180          81 :     eDataType = poProtoBand->GetRasterDataType();
     181          81 :     nBlockXSize = 256;
     182          81 :     nBlockYSize = 256;
     183             :     int nProtoBlockXSize;
     184             :     int nProtoBlockYSize;
     185             :     // Use tile block size if it divides the metatile dimension.
     186          81 :     poProtoBand->GetBlockSize(&nProtoBlockXSize, &nProtoBlockYSize);
     187          81 :     if ((poDSIn->m_nMetaTileWidth % nProtoBlockXSize) == 0 &&
     188          81 :         (poDSIn->m_nMetaTileHeight % nProtoBlockYSize) == 0)
     189             :     {
     190          81 :         nBlockXSize = nProtoBlockXSize;
     191          81 :         nBlockYSize = nProtoBlockYSize;
     192             :     }
     193          81 :     nRasterXSize = poDSIn->GetRasterXSize();
     194          81 :     nRasterYSize = poDSIn->GetRasterYSize();
     195          81 :     m_dfNoData = poProtoBand->GetNoDataValue(&m_bHasNoDataValue);
     196          81 : }
     197             : 
     198             : /************************************************************************/
     199             : /*                        STACTARawRasterBand()                         */
     200             : /************************************************************************/
     201             : 
     202          90 : STACTARawRasterBand::STACTARawRasterBand(STACTARawDataset *poDSIn, int nBandIn,
     203             :                                          GDALDataType eDT, bool bSetNoData,
     204          90 :                                          double dfNoData)
     205             : {
     206          90 :     poDS = poDSIn;
     207          90 :     nBand = nBandIn;
     208          90 :     eDataType = eDT;
     209          90 :     nBlockXSize = 256;
     210          90 :     nBlockYSize = 256;
     211          90 :     nRasterXSize = poDSIn->GetRasterXSize();
     212          90 :     nRasterYSize = poDSIn->GetRasterYSize();
     213          90 :     m_bHasNoDataValue = bSetNoData;
     214          90 :     m_dfNoData = dfNoData;
     215          90 : }
     216             : 
     217             : /************************************************************************/
     218             : /*                           GetNoDataValue()                           */
     219             : /************************************************************************/
     220             : 
     221          81 : double STACTARawRasterBand::GetNoDataValue(int *pbHasNoData)
     222             : {
     223          81 :     if (pbHasNoData)
     224          75 :         *pbHasNoData = m_bHasNoDataValue;
     225          81 :     return m_dfNoData;
     226             : }
     227             : 
     228             : /************************************************************************/
     229             : /*                           IReadBlock()                               */
     230             : /************************************************************************/
     231             : 
     232           0 : CPLErr STACTARawRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
     233             :                                        void *pImage)
     234             : {
     235           0 :     const int nXOff = nBlockXOff * nBlockXSize;
     236           0 :     const int nYOff = nBlockYOff * nBlockYSize;
     237           0 :     const int nXSize = std::min(nBlockXSize, nRasterXSize - nXOff);
     238           0 :     const int nYSize = std::min(nBlockYSize, nRasterYSize - nYOff);
     239             :     GDALRasterIOExtraArg sExtraArgs;
     240           0 :     INIT_RASTERIO_EXTRA_ARG(sExtraArgs);
     241           0 :     const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
     242           0 :     return IRasterIO(GF_Read, nXOff, nYOff, nXSize, nYSize, pImage, nBlockXSize,
     243             :                      nBlockYSize, eDataType, nDTSize,
     244           0 :                      static_cast<GSpacing>(nDTSize) * nBlockXSize, &sExtraArgs);
     245             : }
     246             : 
     247             : /************************************************************************/
     248             : /*                           IRasterIO()                                */
     249             : /************************************************************************/
     250             : 
     251           7 : CPLErr STACTARawRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
     252             :                                       int nXSize, int nYSize, void *pData,
     253             :                                       int nBufXSize, int nBufYSize,
     254             :                                       GDALDataType eBufType,
     255             :                                       GSpacing nPixelSpace, GSpacing nLineSpace,
     256             :                                       GDALRasterIOExtraArg *psExtraArg)
     257             : {
     258           7 :     CPLDebugOnly("STACTA", "Band %d RasterIO: %d,%d,%d,%d->%d,%d", nBand, nXOff,
     259             :                  nYOff, nXSize, nYSize, nBufXSize, nBufYSize);
     260           7 :     auto poGDS = cpl::down_cast<STACTARawDataset *>(poDS);
     261             : 
     262           7 :     const int nKernelRadius = 3;  // up to 3 for Lanczos
     263             :     const int nRadiusX =
     264           7 :         nKernelRadius * static_cast<int>(std::ceil(nXSize / nBufXSize));
     265             :     const int nRadiusY =
     266           7 :         nKernelRadius * static_cast<int>(std::ceil(nYSize / nBufYSize));
     267           7 :     const int nXOffMod = std::max(0, nXOff - nRadiusX);
     268           7 :     const int nYOffMod = std::max(0, nYOff - nRadiusY);
     269           7 :     const int nXSizeMod = static_cast<int>(std::min(
     270          14 :                               nXOff + nXSize + static_cast<GIntBig>(nRadiusX),
     271           7 :                               static_cast<GIntBig>(nRasterXSize))) -
     272           7 :                           nXOffMod;
     273           7 :     const int nYSizeMod = static_cast<int>(std::min(
     274          14 :                               nYOff + nYSize + static_cast<GIntBig>(nRadiusY),
     275           7 :                               static_cast<GIntBig>(nRasterYSize))) -
     276           7 :                           nYOffMod;
     277             : 
     278           7 :     const bool bRequestFitsInSingleMetaTile =
     279           7 :         nXOffMod / poGDS->m_nMetaTileWidth ==
     280          11 :             (nXOffMod + nXSizeMod - 1) / poGDS->m_nMetaTileWidth &&
     281           4 :         nYOffMod / poGDS->m_nMetaTileHeight ==
     282           4 :             (nYOffMod + nYSizeMod - 1) / poGDS->m_nMetaTileHeight;
     283             : 
     284           7 :     if (eRWFlag != GF_Read || ((nXSize != nBufXSize || nYSize != nBufYSize) &&
     285           4 :                                !bRequestFitsInSingleMetaTile))
     286             :     {
     287           0 :         if (!(eRWFlag == GF_Read && nXSizeMod <= 4096 && nYSizeMod <= 4096))
     288             :         {
     289             :             // If not reading at nominal resolution, fallback to default block
     290             :             // reading
     291           0 :             return GDALRasterBand::IRasterIO(
     292             :                 eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize,
     293           0 :                 nBufYSize, eBufType, nPixelSpace, nLineSpace, psExtraArg);
     294             :         }
     295             :     }
     296             : 
     297             :     // Use optimized dataset level RasterIO()
     298           7 :     return poGDS->IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
     299             :                             nBufXSize, nBufYSize, eBufType, 1, &nBand,
     300           7 :                             nPixelSpace, nLineSpace, 0, psExtraArg);
     301             : }
     302             : 
     303             : /************************************************************************/
     304             : /*                             IRasterIO()                              */
     305             : /************************************************************************/
     306             : 
     307          20 : CPLErr STACTARawDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
     308             :                                    int nXSize, int nYSize, void *pData,
     309             :                                    int nBufXSize, int nBufYSize,
     310             :                                    GDALDataType eBufType, int nBandCount,
     311             :                                    int *panBandMap, GSpacing nPixelSpace,
     312             :                                    GSpacing nLineSpace, GSpacing nBandSpace,
     313             :                                    GDALRasterIOExtraArg *psExtraArg)
     314             : {
     315          20 :     CPLDebugOnly("STACTA", "Dataset RasterIO: %d,%d,%d,%d->%d,%d", nXOff, nYOff,
     316             :                  nXSize, nYSize, nBufXSize, nBufYSize);
     317          20 :     const int nMinBlockX = nXOff / m_nMetaTileWidth;
     318          20 :     const int nMaxBlockX = (nXOff + nXSize - 1) / m_nMetaTileWidth;
     319          20 :     const int nMinBlockY = nYOff / m_nMetaTileHeight;
     320          20 :     const int nMaxBlockY = (nYOff + nYSize - 1) / m_nMetaTileHeight;
     321             : 
     322          20 :     const int nKernelRadius = 3;  // up to 3 for Lanczos
     323             :     const int nRadiusX =
     324          20 :         nKernelRadius * static_cast<int>(std::ceil(nXSize / nBufXSize));
     325             :     const int nRadiusY =
     326          20 :         nKernelRadius * static_cast<int>(std::ceil(nYSize / nBufYSize));
     327          20 :     const int nXOffMod = std::max(0, nXOff - nRadiusX);
     328          20 :     const int nYOffMod = std::max(0, nYOff - nRadiusY);
     329          20 :     const int nXSizeMod = static_cast<int>(std::min(
     330          40 :                               nXOff + nXSize + static_cast<GIntBig>(nRadiusX),
     331          20 :                               static_cast<GIntBig>(nRasterXSize))) -
     332          20 :                           nXOffMod;
     333          20 :     const int nYSizeMod = static_cast<int>(std::min(
     334          40 :                               nYOff + nYSize + static_cast<GIntBig>(nRadiusY),
     335          20 :                               static_cast<GIntBig>(nRasterYSize))) -
     336          20 :                           nYOffMod;
     337             : 
     338          20 :     const bool bRequestFitsInSingleMetaTile =
     339          20 :         nXOffMod / m_nMetaTileWidth ==
     340          27 :             (nXOffMod + nXSizeMod - 1) / m_nMetaTileWidth &&
     341           7 :         nYOffMod / m_nMetaTileHeight ==
     342           7 :             (nYOffMod + nYSizeMod - 1) / m_nMetaTileHeight;
     343          20 :     const auto eBandDT = GetRasterBand(1)->GetRasterDataType();
     344          20 :     const int nDTSize = GDALGetDataTypeSizeBytes(eBandDT);
     345             : 
     346          20 :     if (eRWFlag != GF_Read || ((nXSize != nBufXSize || nYSize != nBufYSize) &&
     347           9 :                                !bRequestFitsInSingleMetaTile))
     348             :     {
     349           2 :         if (eRWFlag == GF_Read && nXSizeMod <= 4096 && nYSizeMod <= 4096 &&
     350             :             nBandCount <= 10)
     351             :         {
     352             :             // If extracting from a small enough window, do a RasterIO()
     353             :             // at full resolution into a MEM dataset, and then proceeding to
     354             :             // resampling on it. This will avoid  to fallback on block based
     355             :             // approach.
     356             :             GDALRasterIOExtraArg sExtraArgs;
     357           2 :             INIT_RASTERIO_EXTRA_ARG(sExtraArgs);
     358           2 :             const size_t nXSizeModeMulYSizeModMulDTSize =
     359           2 :                 static_cast<size_t>(nXSizeMod) * nYSizeMod * nDTSize;
     360             :             std::vector<GByte> abyBuf(nXSizeModeMulYSizeModMulDTSize *
     361           4 :                                       nBandCount);
     362           2 :             if (IRasterIO(GF_Read, nXOffMod, nYOffMod, nXSizeMod, nYSizeMod,
     363           2 :                           &abyBuf[0], nXSizeMod, nYSizeMod, eBandDT, nBandCount,
     364             :                           panBandMap, nDTSize,
     365           2 :                           static_cast<GSpacing>(nDTSize) * nXSizeMod,
     366           2 :                           static_cast<GSpacing>(nDTSize) * nXSizeMod *
     367           2 :                               nYSizeMod,
     368           2 :                           &sExtraArgs) != CE_None)
     369             :             {
     370           0 :                 return CE_Failure;
     371             :             }
     372             : 
     373             :             auto poMEMDS = std::unique_ptr<MEMDataset>(MEMDataset::Create(
     374           4 :                 "", nXSizeMod, nYSizeMod, 0, eBandDT, nullptr));
     375           8 :             for (int i = 0; i < nBandCount; i++)
     376             :             {
     377           6 :                 auto hBand = MEMCreateRasterBandEx(
     378           6 :                     poMEMDS.get(), i + 1,
     379           6 :                     &abyBuf[0] + i * nXSizeModeMulYSizeModMulDTSize, eBandDT, 0,
     380             :                     0, false);
     381           6 :                 poMEMDS->AddMEMBand(hBand);
     382             :             }
     383             : 
     384           2 :             sExtraArgs.eResampleAlg = psExtraArg->eResampleAlg;
     385           2 :             if (psExtraArg->bFloatingPointWindowValidity)
     386             :             {
     387           0 :                 sExtraArgs.bFloatingPointWindowValidity = true;
     388           0 :                 sExtraArgs.dfXOff = psExtraArg->dfXOff - nXOffMod;
     389           0 :                 sExtraArgs.dfYOff = psExtraArg->dfYOff - nYOffMod;
     390           0 :                 sExtraArgs.dfXSize = psExtraArg->dfXSize;
     391           0 :                 sExtraArgs.dfYSize = psExtraArg->dfYSize;
     392             :             }
     393           2 :             return poMEMDS->RasterIO(
     394             :                 GF_Read, nXOff - nXOffMod, nYOff - nYOffMod, nXSize, nYSize,
     395             :                 pData, nBufXSize, nBufYSize, eBufType, nBandCount, nullptr,
     396           2 :                 nPixelSpace, nLineSpace, nBandSpace, &sExtraArgs);
     397             :         }
     398             : 
     399             :         // If not reading at nominal resolution, fallback to default block
     400             :         // reading
     401           0 :         return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
     402             :                                       pData, nBufXSize, nBufYSize, eBufType,
     403             :                                       nBandCount, panBandMap, nPixelSpace,
     404           0 :                                       nLineSpace, nBandSpace, psExtraArg);
     405             :     }
     406             : 
     407          18 :     int nBufYOff = 0;
     408             : 
     409             :     // If the (uncompressed) size of a metatile is small enough, then download
     410             :     // it entirely to minimize the number of network requests
     411          18 :     const bool bDownloadWholeMetaTile =
     412          33 :         m_poMasterDS->m_bDownloadWholeMetaTile ||
     413          15 :         (static_cast<GIntBig>(m_nMetaTileWidth) * m_nMetaTileHeight * nBands *
     414          15 :              nDTSize <
     415             :          128 * 1024);
     416             : 
     417             :     // Split the request on each metatile that it intersects
     418          35 :     for (int iY = nMinBlockY; iY <= nMaxBlockY; iY++)
     419             :     {
     420          18 :         const int nTileYOff = std::max(0, nYOff - iY * m_nMetaTileHeight);
     421             :         const int nTileYSize =
     422          18 :             std::min((iY + 1) * m_nMetaTileHeight, nYOff + nYSize) -
     423          18 :             std::max(nYOff, iY * m_nMetaTileHeight);
     424             : 
     425          18 :         int nBufXOff = 0;
     426          46 :         for (int iX = nMinBlockX; iX <= nMaxBlockX; iX++)
     427             :         {
     428          29 :             CPLString osURL(m_osURLTemplate);
     429             :             osURL.replaceAll("{TileRow}",
     430          29 :                              CPLSPrintf("%d", iY + m_nMinMetaTileRow));
     431             :             osURL.replaceAll("{TileCol}",
     432          29 :                              CPLSPrintf("%d", iX + m_nMinMetaTileCol));
     433             : 
     434          29 :             const int nTileXOff = std::max(0, nXOff - iX * m_nMetaTileWidth);
     435             :             const int nTileXSize =
     436          29 :                 std::min((iX + 1) * m_nMetaTileWidth, nXOff + nXSize) -
     437          29 :                 std::max(nXOff, iX * m_nMetaTileWidth);
     438             : 
     439          29 :             const int nBufXSizeEffective =
     440          29 :                 bRequestFitsInSingleMetaTile ? nBufXSize : nTileXSize;
     441          29 :             const int nBufYSizeEffective =
     442          29 :                 bRequestFitsInSingleMetaTile ? nBufYSize : nTileYSize;
     443             : 
     444          29 :             bool bMissingTile = false;
     445             :             do
     446             :             {
     447             :                 std::unique_ptr<GDALDataset> *ppoTileDS =
     448          29 :                     m_poMasterDS->m_oCacheTileDS.getPtr(osURL);
     449          29 :                 if (ppoTileDS == nullptr)
     450             :                 {
     451             : 
     452             :                     // Avoid probing side car files
     453             :                     CPLConfigOptionSetter oSetter(
     454             :                         "GDAL_DISABLE_READDIR_ON_OPEN", "EMPTY_DIR",
     455          15 :                         /* bSetOnlyIfUndefined = */ true);
     456             : 
     457          15 :                     CPLStringList aosAllowedDrivers;
     458          15 :                     aosAllowedDrivers.AddString("GTiff");
     459          15 :                     aosAllowedDrivers.AddString("PNG");
     460          15 :                     aosAllowedDrivers.AddString("JPEG");
     461          15 :                     aosAllowedDrivers.AddString("JP2KAK");
     462          15 :                     aosAllowedDrivers.AddString("JP2ECW");
     463          15 :                     aosAllowedDrivers.AddString("JP2MrSID");
     464          15 :                     aosAllowedDrivers.AddString("JP2OpenJPEG");
     465           0 :                     std::unique_ptr<GDALDataset> poTileDS;
     466          15 :                     if (bDownloadWholeMetaTile && !VSIIsLocal(osURL.c_str()))
     467             :                     {
     468           0 :                         if (m_poMasterDS->m_bSkipMissingMetaTile)
     469           0 :                             CPLPushErrorHandler(CPLQuietErrorHandler);
     470           0 :                         VSILFILE *fp = VSIFOpenL(osURL, "rb");
     471           0 :                         if (m_poMasterDS->m_bSkipMissingMetaTile)
     472           0 :                             CPLPopErrorHandler();
     473           0 :                         if (fp == nullptr)
     474             :                         {
     475           0 :                             if (m_poMasterDS->m_bSkipMissingMetaTile)
     476             :                             {
     477           0 :                                 m_poMasterDS->m_oCacheTileDS.insert(osURL,
     478           0 :                                                                     nullptr);
     479           0 :                                 bMissingTile = true;
     480           0 :                                 break;
     481             :                             }
     482           0 :                             CPLError(CE_Failure, CPLE_OpenFailed,
     483             :                                      "Cannot open %s", osURL.c_str());
     484           0 :                             return CE_Failure;
     485             :                         }
     486           0 :                         GByte *pabyBuf = nullptr;
     487           0 :                         vsi_l_offset nSize = 0;
     488           0 :                         if (!VSIIngestFile(fp, nullptr, &pabyBuf, &nSize, -1))
     489             :                         {
     490           0 :                             VSIFCloseL(fp);
     491           0 :                             return CE_Failure;
     492             :                         }
     493           0 :                         VSIFCloseL(fp);
     494           0 :                         const CPLString osMEMFilename("/vsimem/stacta/" +
     495           0 :                                                       osURL);
     496           0 :                         VSIFCloseL(VSIFileFromMemBuffer(osMEMFilename, pabyBuf,
     497             :                                                         nSize, TRUE));
     498           0 :                         poTileDS = std::unique_ptr<GDALDataset>(
     499             :                             GDALDataset::Open(osMEMFilename,
     500             :                                               GDAL_OF_INTERNAL | GDAL_OF_RASTER,
     501           0 :                                               aosAllowedDrivers.List()));
     502           0 :                         if (poTileDS)
     503           0 :                             poTileDS->MarkSuppressOnClose();
     504             :                         else
     505           0 :                             VSIUnlink(osMEMFilename);
     506             :                     }
     507          29 :                     else if (bDownloadWholeMetaTile ||
     508          14 :                              (!STARTS_WITH(osURL, "http://") &&
     509          12 :                               !STARTS_WITH(osURL, "https://")))
     510             :                     {
     511          13 :                         aosAllowedDrivers.AddString("HTTP");
     512          13 :                         if (m_poMasterDS->m_bSkipMissingMetaTile)
     513           4 :                             CPLPushErrorHandler(CPLQuietErrorHandler);
     514             :                         poTileDS =
     515          26 :                             std::unique_ptr<GDALDataset>(GDALDataset::Open(
     516             :                                 osURL, GDAL_OF_INTERNAL | GDAL_OF_RASTER,
     517          26 :                                 aosAllowedDrivers.List()));
     518          13 :                         if (m_poMasterDS->m_bSkipMissingMetaTile)
     519           4 :                             CPLPopErrorHandler();
     520             :                     }
     521             :                     else
     522             :                     {
     523           2 :                         if (m_poMasterDS->m_bSkipMissingMetaTile)
     524           0 :                             CPLPushErrorHandler(CPLQuietErrorHandler);
     525           4 :                         poTileDS = std::unique_ptr<GDALDataset>(
     526           4 :                             GDALDataset::Open(("/vsicurl/" + osURL).c_str(),
     527             :                                               GDAL_OF_INTERNAL | GDAL_OF_RASTER,
     528           4 :                                               aosAllowedDrivers.List()));
     529           2 :                         if (m_poMasterDS->m_bSkipMissingMetaTile)
     530           0 :                             CPLPopErrorHandler();
     531             :                     }
     532          15 :                     if (poTileDS == nullptr)
     533             :                     {
     534           3 :                         if (m_poMasterDS->m_bSkipMissingMetaTile)
     535             :                         {
     536           2 :                             m_poMasterDS->m_oCacheTileDS.insert(
     537           2 :                                 osURL, std::move(poTileDS));
     538           2 :                             bMissingTile = true;
     539           2 :                             break;
     540             :                         }
     541           1 :                         CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s",
     542             :                                  osURL.c_str());
     543           1 :                         return CE_Failure;
     544             :                     }
     545          12 :                     ppoTileDS = &m_poMasterDS->m_oCacheTileDS.insert(
     546          12 :                         osURL, std::move(poTileDS));
     547             :                 }
     548          26 :                 std::unique_ptr<GDALDataset> &poTileDS = *ppoTileDS;
     549          26 :                 if (poTileDS == nullptr)
     550             :                 {
     551           0 :                     bMissingTile = true;
     552           0 :                     break;
     553             :                 }
     554             : 
     555             :                 GDALRasterIOExtraArg sExtraArgs;
     556          26 :                 INIT_RASTERIO_EXTRA_ARG(sExtraArgs);
     557          26 :                 if (bRequestFitsInSingleMetaTile)
     558             :                 {
     559           7 :                     sExtraArgs.eResampleAlg = psExtraArg->eResampleAlg;
     560           7 :                     if (psExtraArg->bFloatingPointWindowValidity)
     561             :                     {
     562           5 :                         sExtraArgs.bFloatingPointWindowValidity = true;
     563           5 :                         sExtraArgs.dfXOff =
     564           5 :                             psExtraArg->dfXOff - iX * m_nMetaTileWidth;
     565           5 :                         sExtraArgs.dfYOff =
     566           5 :                             psExtraArg->dfYOff - iY * m_nMetaTileHeight;
     567           5 :                         sExtraArgs.dfXSize = psExtraArg->dfXSize;
     568           5 :                         sExtraArgs.dfYSize = psExtraArg->dfYSize;
     569             :                     }
     570             :                 }
     571          26 :                 CPLDebugOnly("STACTA", "Reading %d,%d,%d,%d in %s", nTileXOff,
     572             :                              nTileYOff, nTileXSize, nTileYSize, osURL.c_str());
     573          26 :                 if (poTileDS->RasterIO(
     574             :                         GF_Read, nTileXOff, nTileYOff, nTileXSize, nTileYSize,
     575          26 :                         static_cast<GByte *>(pData) + nBufXOff * nPixelSpace +
     576          26 :                             nBufYOff * nLineSpace,
     577             :                         nBufXSizeEffective, nBufYSizeEffective, eBufType,
     578             :                         nBandCount, panBandMap, nPixelSpace, nLineSpace,
     579          26 :                         nBandSpace, &sExtraArgs) != CE_None)
     580             :                 {
     581           0 :                     return CE_Failure;
     582             :                 }
     583             :             } while (false);
     584             : 
     585          28 :             if (bMissingTile)
     586             :             {
     587           2 :                 CPLDebugOnly("STACTA", "Missing metatile %s", osURL.c_str());
     588           8 :                 for (int iBand = 0; iBand < nBandCount; iBand++)
     589             :                 {
     590           6 :                     int bHasNoData = FALSE;
     591           6 :                     double dfNodata = GetRasterBand(panBandMap[iBand])
     592           6 :                                           ->GetNoDataValue(&bHasNoData);
     593           6 :                     if (!bHasNoData)
     594           0 :                         dfNodata = 0;
     595        6150 :                     for (int nYBufOff = 0; nYBufOff < nBufYSizeEffective;
     596             :                          nYBufOff++)
     597             :                     {
     598        6144 :                         GByte *pabyDest = static_cast<GByte *>(pData) +
     599        6144 :                                           iBand * nBandSpace +
     600        6144 :                                           nBufXOff * nPixelSpace +
     601        6144 :                                           (nBufYOff + nYBufOff) * nLineSpace;
     602        6144 :                         GDALCopyWords(&dfNodata, GDT_Float64, 0, pabyDest,
     603             :                                       eBufType, static_cast<int>(nPixelSpace),
     604             :                                       nBufXSizeEffective);
     605             :                     }
     606             :                 }
     607             :             }
     608             : 
     609          28 :             if (iX == nMinBlockX)
     610             :             {
     611          36 :                 nBufXOff = m_nMetaTileWidth -
     612          18 :                            std::max(0, nXOff - nMinBlockX * m_nMetaTileWidth);
     613             :             }
     614             :             else
     615             :             {
     616          10 :                 nBufXOff += m_nMetaTileWidth;
     617             :             }
     618             :         }
     619             : 
     620          17 :         if (iY == nMinBlockY)
     621             :         {
     622          34 :             nBufYOff = m_nMetaTileHeight -
     623          17 :                        std::max(0, nYOff - nMinBlockY * m_nMetaTileHeight);
     624             :         }
     625             :         else
     626             :         {
     627           0 :             nBufYOff += m_nMetaTileHeight;
     628             :         }
     629             :     }
     630             : 
     631          17 :     return CE_None;
     632             : }
     633             : 
     634             : /************************************************************************/
     635             : /*                           GetGeoTransform()                          */
     636             : /************************************************************************/
     637             : 
     638           4 : CPLErr STACTARawDataset::GetGeoTransform(double *padfGeoTransform)
     639             : {
     640           4 :     memcpy(padfGeoTransform, &m_adfGeoTransform[0], 6 * sizeof(double));
     641           4 :     return CE_None;
     642             : }
     643             : 
     644             : /************************************************************************/
     645             : /*                             Identify()                               */
     646             : /************************************************************************/
     647             : 
     648       47854 : int STACTADataset::Identify(GDALOpenInfo *poOpenInfo)
     649             : {
     650       47854 :     if (STARTS_WITH(poOpenInfo->pszFilename, "STACTA:"))
     651             :     {
     652           6 :         return true;
     653             :     }
     654             : 
     655       47843 :     if (
     656             : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
     657       48245 :         !EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "json") ||
     658             : #endif
     659         397 :         poOpenInfo->nHeaderBytes == 0)
     660             :     {
     661       47724 :         return false;
     662             :     }
     663             : 
     664         291 :     for (int i = 0; i < 2; i++)
     665             :     {
     666             :         // TryToIngest() may reallocate pabyHeader, so do not move this
     667             :         // before the loop.
     668         206 :         const char *pszHeader =
     669             :             reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
     670         206 :         if (strstr(pszHeader, "\"stac_extensions\"") != nullptr &&
     671          44 :             (strstr(pszHeader, "\"tiled-assets\"") != nullptr ||
     672          28 :              strstr(pszHeader,
     673             :                     "https://stac-extensions.github.io/tiled-assets/") !=
     674             :                  nullptr))
     675             :         {
     676          36 :             return true;
     677             :         }
     678             : 
     679         170 :         if (i == 0)
     680             :         {
     681             :             // Should be enough for a STACTA .json file
     682          85 :             poOpenInfo->TryToIngest(32768);
     683             :         }
     684             :     }
     685             : 
     686          85 :     return false;
     687             : }
     688             : 
     689             : /************************************************************************/
     690             : /*                               Open()                                 */
     691             : /************************************************************************/
     692             : 
     693          21 : bool STACTADataset::Open(GDALOpenInfo *poOpenInfo)
     694             : {
     695          42 :     CPLString osFilename(poOpenInfo->pszFilename);
     696          42 :     CPLString osAssetName;
     697          42 :     CPLString osTMS;
     698          21 :     if (STARTS_WITH(poOpenInfo->pszFilename, "STACTA:"))
     699             :     {
     700             :         const CPLStringList aosTokens(CSLTokenizeString2(
     701           3 :             poOpenInfo->pszFilename, ":", CSLT_HONOURSTRINGS));
     702           4 :         if (aosTokens.size() != 2 && aosTokens.size() != 3 &&
     703           1 :             aosTokens.size() != 4)
     704           0 :             return false;
     705           3 :         osFilename = aosTokens[1];
     706           3 :         if (aosTokens.size() >= 3)
     707           3 :             osAssetName = aosTokens[2];
     708           3 :         if (aosTokens.size() == 4)
     709           1 :             osTMS = aosTokens[3];
     710             :     }
     711             : 
     712          42 :     CPLJSONDocument oDoc;
     713          42 :     if (STARTS_WITH(osFilename, "http://") ||
     714          21 :         STARTS_WITH(osFilename, "https://"))
     715             :     {
     716           0 :         if (!oDoc.LoadUrl(osFilename, nullptr))
     717           0 :             return false;
     718             :     }
     719             :     else
     720             :     {
     721          21 :         if (!oDoc.Load(osFilename))
     722           0 :             return false;
     723             :     }
     724          42 :     const auto oRoot = oDoc.GetRoot();
     725          63 :     const auto oProperties = oRoot["properties"];
     726          42 :     if (!oProperties.IsValid() ||
     727          21 :         oProperties.GetType() != CPLJSONObject::Type::Object)
     728             :     {
     729           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing properties");
     730           0 :         return false;
     731             :     }
     732             : 
     733          63 :     const auto oAssetTemplates = oRoot["asset_templates"];
     734          42 :     if (!oAssetTemplates.IsValid() ||
     735          21 :         oAssetTemplates.GetType() != CPLJSONObject::Type::Object)
     736             :     {
     737           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing asset_templates");
     738           0 :         return false;
     739             :     }
     740             : 
     741          42 :     const auto aoAssetTemplates = oAssetTemplates.GetChildren();
     742          21 :     if (aoAssetTemplates.size() == 0)
     743             :     {
     744           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Empty asset_templates");
     745           0 :         return false;
     746             :     }
     747             : 
     748          63 :     const auto oTMSs = oProperties.GetObj("tiles:tile_matrix_sets");
     749          21 :     if (!oTMSs.IsValid() || oTMSs.GetType() != CPLJSONObject::Type::Object)
     750             :     {
     751           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     752             :                  "Missing properties[\"tiles:tile_matrix_sets\"]");
     753           0 :         return false;
     754             :     }
     755          42 :     const auto aoTMSs = oTMSs.GetChildren();
     756          21 :     if (aoTMSs.empty())
     757             :     {
     758           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     759             :                  "Empty properties[\"tiles:tile_matrix_sets\"]");
     760           0 :         return false;
     761             :     }
     762             : 
     763          44 :     if ((aoAssetTemplates.size() >= 2 || aoTMSs.size() >= 2) &&
     764          44 :         osAssetName.empty() && osTMS.empty())
     765             :     {
     766           2 :         int nSDSCount = 0;
     767           5 :         for (const auto &oAssetTemplate : aoAssetTemplates)
     768             :         {
     769           6 :             const CPLString osAssetNameSubDS = oAssetTemplate.GetName();
     770           3 :             const char *pszAssetNameSubDS = osAssetNameSubDS.c_str();
     771           3 :             if (aoTMSs.size() >= 2)
     772             :             {
     773           3 :                 for (const auto &oTMS : aoTMSs)
     774             :                 {
     775           2 :                     const CPLString osTMSSubDS = oTMS.GetName();
     776           2 :                     const char *pszTMSSubDS = osTMSSubDS.c_str();
     777           2 :                     GDALDataset::SetMetadataItem(
     778             :                         CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
     779             :                         CPLSPrintf("STACTA:\"%s\":%s:%s", osFilename.c_str(),
     780             :                                    pszAssetNameSubDS, pszTMSSubDS),
     781             :                         "SUBDATASETS");
     782           2 :                     GDALDataset::SetMetadataItem(
     783             :                         CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
     784             :                         CPLSPrintf("Asset %s, tile matrix set %s",
     785             :                                    pszAssetNameSubDS, pszTMSSubDS),
     786             :                         "SUBDATASETS");
     787           2 :                     nSDSCount++;
     788             :                 }
     789             :             }
     790             :             else
     791             :             {
     792           2 :                 GDALDataset::SetMetadataItem(
     793             :                     CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
     794             :                     CPLSPrintf("STACTA:\"%s\":%s", osFilename.c_str(),
     795             :                                pszAssetNameSubDS),
     796             :                     "SUBDATASETS");
     797           2 :                 GDALDataset::SetMetadataItem(
     798             :                     CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
     799             :                     CPLSPrintf("Asset %s", pszAssetNameSubDS), "SUBDATASETS");
     800           2 :                 nSDSCount++;
     801             :             }
     802             :         }
     803           2 :         return true;
     804             :     }
     805             : 
     806          19 :     if (osAssetName.empty())
     807             :     {
     808          16 :         osAssetName = aoAssetTemplates[0].GetName();
     809             :     }
     810          38 :     const auto oAssetTemplate = oAssetTemplates.GetObj(osAssetName);
     811          38 :     if (!oAssetTemplate.IsValid() ||
     812          19 :         oAssetTemplate.GetType() != CPLJSONObject::Type::Object)
     813             :     {
     814           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     815             :                  "Cannot find asset_templates[\"%s\"]", osAssetName.c_str());
     816           0 :         return false;
     817             :     }
     818             : 
     819          19 :     if (osTMS.empty())
     820             :     {
     821          18 :         osTMS = aoTMSs[0].GetName();
     822             :     }
     823          38 :     const auto oTMS = oTMSs.GetObj(osTMS);
     824          19 :     if (!oTMS.IsValid() || oTMS.GetType() != CPLJSONObject::Type::Object)
     825             :     {
     826           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     827             :                  "Cannot find properties[\"tiles:tile_matrix_sets\"][\"%s\"]",
     828             :                  osTMS.c_str());
     829           0 :         return false;
     830             :     }
     831             : 
     832             :     auto poTMS = gdal::TileMatrixSet::parse(
     833          38 :         oTMS.Format(CPLJSONObject::PrettyFormat::Plain).c_str());
     834          19 :     if (poTMS == nullptr)
     835           0 :         return false;
     836             : 
     837          57 :     CPLString osURLTemplate = oAssetTemplate.GetString("href");
     838          19 :     if (osURLTemplate.empty())
     839             :     {
     840           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     841             :                  "Cannot find asset_templates[\"%s\"][\"href\"]",
     842             :                  osAssetName.c_str());
     843             :     }
     844          19 :     osURLTemplate.replaceAll("{TileMatrixSet}", osTMS);
     845          19 :     if (STARTS_WITH(osURLTemplate, "file://"))
     846             :     {
     847           0 :         osURLTemplate = osURLTemplate.substr(strlen("file://"));
     848             :     }
     849          19 :     else if (STARTS_WITH(osURLTemplate, "s3://"))
     850             :     {
     851           0 :         osURLTemplate = "/vsis3/" + osURLTemplate.substr(strlen("s3://"));
     852             :     }
     853             : 
     854          37 :     if (!STARTS_WITH(osURLTemplate, "http://") &&
     855          18 :         !STARTS_WITH(osURLTemplate, "https://"))
     856             :     {
     857          18 :         if (STARTS_WITH(osURLTemplate, "./"))
     858          18 :             osURLTemplate = osURLTemplate.substr(2);
     859             :         osURLTemplate = CPLProjectRelativeFilename(CPLGetDirname(osFilename),
     860          18 :                                                    osURLTemplate);
     861             :     }
     862             : 
     863             :     // Parse optional tile matrix set limits
     864          38 :     std::map<CPLString, Limits> oMapLimits;
     865          57 :     const auto oTMLinks = oProperties.GetObj("tiles:tile_matrix_links");
     866          19 :     if (oTMLinks.IsValid())
     867             :     {
     868          16 :         if (oTMLinks.GetType() != CPLJSONObject::Type::Object)
     869             :         {
     870           0 :             CPLError(
     871             :                 CE_Failure, CPLE_AppDefined,
     872             :                 "Invalid type for properties[\"tiles:tile_matrix_links\"]");
     873           0 :             return false;
     874             :         }
     875             : 
     876          48 :         auto oLimits = oTMLinks[osTMS]["limits"];
     877          32 :         if (oLimits.IsValid() &&
     878          16 :             oLimits.GetType() == CPLJSONObject::Type::Object)
     879             :         {
     880          64 :             for (const auto &oLimit : oLimits.GetChildren())
     881             :             {
     882          48 :                 Limits limits;
     883          48 :                 limits.min_tile_col = oLimit.GetInteger("min_tile_col");
     884          48 :                 limits.max_tile_col = oLimit.GetInteger("max_tile_col");
     885          48 :                 limits.min_tile_row = oLimit.GetInteger("min_tile_row");
     886          48 :                 limits.max_tile_row = oLimit.GetInteger("max_tile_row");
     887          48 :                 oMapLimits[oLimit.GetName()] = limits;
     888             :             }
     889             :         }
     890             :     }
     891          19 :     const auto &tmsList = poTMS->tileMatrixList();
     892          19 :     if (tmsList.empty())
     893           0 :         return false;
     894             : 
     895          38 :     m_bSkipMissingMetaTile = CPLTestBool(CSLFetchNameValueDef(
     896          19 :         poOpenInfo->papszOpenOptions, "SKIP_MISSING_METATILE",
     897             :         CPLGetConfigOption("GDAL_STACTA_SKIP_MISSING_METATILE", "NO")));
     898             : 
     899             :     // STAC 1.1 uses bands instead of eo:bands and raster:bands
     900          57 :     const auto oBands = oAssetTemplate.GetArray("bands");
     901             : 
     902             :     // Check if there are both eo:bands and raster:bands extension
     903             :     // If so, we don't need to fetch a prototype metatile to derive the
     904             :     // information we need (number of bands, data type and nodata value)
     905             :     const auto oEoBands =
     906          56 :         oBands.IsValid() ? oBands : oAssetTemplate.GetArray("eo:bands");
     907             :     const auto oRasterBands =
     908          56 :         oBands.IsValid() ? oBands : oAssetTemplate.GetArray("raster:bands");
     909             : 
     910          38 :     std::vector<GDALDataType> aeDT;
     911          38 :     std::vector<double> adfNoData;
     912          38 :     std::vector<bool> abSetNoData;
     913          19 :     int nExpectedBandCount = 0;
     914          19 :     if (oRasterBands.IsValid())
     915             :     {
     916           9 :         if (oEoBands.IsValid() && oEoBands.Size() != oRasterBands.Size())
     917             :         {
     918           1 :             CPLError(CE_Warning, CPLE_AppDefined,
     919             :                      "Number of bands in eo:bands and raster:bands is not "
     920             :                      "identical. Ignoring the later");
     921             :         }
     922             :         else
     923             :         {
     924           8 :             nExpectedBandCount = oRasterBands.Size();
     925             : 
     926             :             const struct
     927             :             {
     928             :                 const char *pszStacDataType;
     929             :                 GDALDataType eGDALDataType;
     930           8 :             } aDataTypeMapping[] = {
     931             :                 {"int8", GDT_Int8},
     932             :                 {"int16", GDT_Int16},
     933             :                 {"int32", GDT_Int32},
     934             :                 {"int64", GDT_Int64},
     935             :                 {"uint8", GDT_Byte},
     936             :                 {"uint16", GDT_UInt16},
     937             :                 {"uint32", GDT_UInt32},
     938             :                 {"uint64", GDT_UInt64},
     939             :                 // float16: 16-bit float; unhandled
     940             :                 {"float32", GDT_Float32},
     941             :                 {"float64", GDT_Float64},
     942             :                 {"cint16", GDT_CInt16},
     943             :                 {"cint32", GDT_CInt32},
     944             :                 {"cfloat32", GDT_CFloat32},
     945             :                 {"cfloat64", GDT_CFloat64},
     946             :             };
     947             : 
     948          38 :             for (int i = 0; i < nExpectedBandCount; ++i)
     949             :             {
     950          33 :                 if (oRasterBands[i].GetType() != CPLJSONObject::Type::Object)
     951             :                 {
     952           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     953             :                              "Wrong raster:bands[%d]", i);
     954           3 :                     return false;
     955             :                 }
     956             :                 const std::string osDataType =
     957          64 :                     oRasterBands[i].GetString("data_type");
     958          32 :                 GDALDataType eDT = GDT_Unknown;
     959         280 :                 for (const auto &oTuple : aDataTypeMapping)
     960             :                 {
     961         278 :                     if (osDataType == oTuple.pszStacDataType)
     962             :                     {
     963          30 :                         eDT = oTuple.eGDALDataType;
     964          30 :                         break;
     965             :                     }
     966             :                 }
     967          32 :                 if (eDT == GDT_Unknown)
     968             :                 {
     969           2 :                     CPLError(CE_Failure, CPLE_AppDefined,
     970             :                              "Wrong raster:bands[%d].data_type = %s", i,
     971             :                              osDataType.c_str());
     972           2 :                     return false;
     973             :                 }
     974          30 :                 aeDT.push_back(eDT);
     975             : 
     976          90 :                 const auto oNoData = oRasterBands[i].GetObj("nodata");
     977          30 :                 if (oNoData.GetType() == CPLJSONObject::Type::String)
     978             :                 {
     979          48 :                     const std::string osNoData = oNoData.ToString();
     980          16 :                     if (osNoData == "inf")
     981             :                     {
     982           5 :                         abSetNoData.push_back(true);
     983           5 :                         adfNoData.push_back(
     984           5 :                             std::numeric_limits<double>::infinity());
     985             :                     }
     986          11 :                     else if (osNoData == "-inf")
     987             :                     {
     988           5 :                         abSetNoData.push_back(true);
     989           5 :                         adfNoData.push_back(
     990           5 :                             -std::numeric_limits<double>::infinity());
     991             :                     }
     992           6 :                     else if (osNoData == "nan")
     993             :                     {
     994           5 :                         abSetNoData.push_back(true);
     995           5 :                         adfNoData.push_back(
     996           5 :                             std::numeric_limits<double>::quiet_NaN());
     997             :                     }
     998             :                     else
     999             :                     {
    1000           1 :                         CPLError(CE_Warning, CPLE_AppDefined,
    1001             :                                  "Invalid raster:bands[%d].nodata = %s", i,
    1002             :                                  osNoData.c_str());
    1003           1 :                         abSetNoData.push_back(false);
    1004           1 :                         adfNoData.push_back(
    1005           1 :                             std::numeric_limits<double>::quiet_NaN());
    1006             :                     }
    1007             :                 }
    1008          14 :                 else if (oNoData.GetType() == CPLJSONObject::Type::Integer ||
    1009          25 :                          oNoData.GetType() == CPLJSONObject::Type::Long ||
    1010          11 :                          oNoData.GetType() == CPLJSONObject::Type::Double)
    1011             :                 {
    1012           8 :                     abSetNoData.push_back(true);
    1013           8 :                     adfNoData.push_back(oNoData.ToDouble());
    1014             :                 }
    1015           6 :                 else if (!oNoData.IsValid())
    1016             :                 {
    1017           5 :                     abSetNoData.push_back(false);
    1018           5 :                     adfNoData.push_back(
    1019           5 :                         std::numeric_limits<double>::quiet_NaN());
    1020             :                 }
    1021             :                 else
    1022             :                 {
    1023           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1024             :                              "Invalid raster:bands[%d].nodata", i);
    1025           1 :                     abSetNoData.push_back(false);
    1026           1 :                     adfNoData.push_back(
    1027           1 :                         std::numeric_limits<double>::quiet_NaN());
    1028             :                 }
    1029             :             }
    1030             : 
    1031           5 :             CPLAssert(aeDT.size() == abSetNoData.size());
    1032           5 :             CPLAssert(adfNoData.size() == abSetNoData.size());
    1033             :         }
    1034             :     }
    1035             : 
    1036          16 :     std::unique_ptr<GDALDataset> poProtoDS;
    1037          16 :     if (aeDT.empty())
    1038             :     {
    1039          11 :         for (int i = 0; i < static_cast<int>(tmsList.size()); i++)
    1040             :         {
    1041             :             // Open a metatile to get mostly its band data type
    1042          11 :             int nProtoTileCol = 0;
    1043          11 :             int nProtoTileRow = 0;
    1044          11 :             auto oIterLimit = oMapLimits.find(tmsList[i].mId);
    1045          11 :             if (oIterLimit != oMapLimits.end())
    1046             :             {
    1047           8 :                 nProtoTileCol = oIterLimit->second.min_tile_col;
    1048           8 :                 nProtoTileRow = oIterLimit->second.min_tile_row;
    1049             :             }
    1050             :             const CPLString osURL =
    1051          11 :                 CPLString(osURLTemplate)
    1052          22 :                     .replaceAll("{TileMatrix}", tmsList[i].mId)
    1053          22 :                     .replaceAll("{TileRow}", CPLSPrintf("%d", nProtoTileRow))
    1054          22 :                     .replaceAll("{TileCol}", CPLSPrintf("%d", nProtoTileCol));
    1055          32 :             CPLString osProtoDSName = (STARTS_WITH(osURL, "http://") ||
    1056          10 :                                        STARTS_WITH(osURL, "https://"))
    1057          22 :                                           ? CPLString("/vsicurl/" + osURL)
    1058          22 :                                           : osURL;
    1059             :             CPLConfigOptionSetter oSetter("GDAL_DISABLE_READDIR_ON_OPEN",
    1060             :                                           "EMPTY_DIR",
    1061          11 :                                           /* bSetOnlyIfUndefined = */ true);
    1062          11 :             if (m_bSkipMissingMetaTile)
    1063           2 :                 CPLPushErrorHandler(CPLQuietErrorHandler);
    1064          11 :             poProtoDS.reset(GDALDataset::Open(osProtoDSName.c_str()));
    1065          11 :             if (m_bSkipMissingMetaTile)
    1066           2 :                 CPLPopErrorHandler();
    1067          11 :             if (poProtoDS != nullptr)
    1068             :             {
    1069           9 :                 break;
    1070             :             }
    1071           2 :             if (!m_bSkipMissingMetaTile)
    1072             :             {
    1073           2 :                 CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s",
    1074             :                          osURL.c_str());
    1075           2 :                 return false;
    1076             :             }
    1077             :         }
    1078           9 :         if (poProtoDS == nullptr)
    1079             :         {
    1080           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1081             :                      "Cannot find prototype dataset");
    1082           0 :             return false;
    1083             :         }
    1084           9 :         nExpectedBandCount = poProtoDS->GetRasterCount();
    1085             :     }
    1086             : 
    1087             :     // Iterate over tile matrices to create corresponding STACTARawDataset
    1088             :     // objects
    1089          56 :     for (int i = static_cast<int>(tmsList.size() - 1); i >= 0; i--)
    1090             :     {
    1091          42 :         const auto &oTM = tmsList[i];
    1092          42 :         int nMatrixWidth = oTM.mMatrixWidth;
    1093          42 :         int nMatrixHeight = oTM.mMatrixHeight;
    1094          42 :         auto oIterLimit = oMapLimits.find(tmsList[i].mId);
    1095          42 :         if (oIterLimit != oMapLimits.end())
    1096             :         {
    1097          33 :             nMatrixWidth = oIterLimit->second.max_tile_col -
    1098          33 :                            oIterLimit->second.min_tile_col + 1;
    1099          33 :             nMatrixHeight = oIterLimit->second.max_tile_row -
    1100          33 :                             oIterLimit->second.min_tile_row + 1;
    1101             :         }
    1102          42 :         if (nMatrixWidth <= 0 || oTM.mTileWidth > INT_MAX / nMatrixWidth ||
    1103          42 :             nMatrixHeight <= 0 || oTM.mTileHeight > INT_MAX / nMatrixHeight)
    1104             :         {
    1105           0 :             continue;
    1106             :         }
    1107          42 :         auto poRawDS = std::make_unique<STACTARawDataset>();
    1108          84 :         if (!poRawDS->InitRaster(poProtoDS.get(), aeDT, abSetNoData, adfNoData,
    1109          42 :                                  poTMS.get(), tmsList[i].mId, oTM, oMapLimits))
    1110             :         {
    1111           0 :             return false;
    1112             :         }
    1113          42 :         poRawDS->m_osURLTemplate = osURLTemplate;
    1114          42 :         poRawDS->m_osURLTemplate.replaceAll("{TileMatrix}", tmsList[i].mId);
    1115          42 :         poRawDS->m_poMasterDS = this;
    1116             : 
    1117          42 :         if (m_poDS == nullptr)
    1118             :         {
    1119          14 :             nRasterXSize = poRawDS->GetRasterXSize();
    1120          14 :             nRasterYSize = poRawDS->GetRasterYSize();
    1121          14 :             m_oSRS = poRawDS->m_oSRS;
    1122          14 :             memcpy(&m_adfGeoTransform[0], &poRawDS->m_adfGeoTransform[0],
    1123             :                    6 * sizeof(double));
    1124          14 :             m_poDS = std::move(poRawDS);
    1125             :         }
    1126             :         else
    1127             :         {
    1128          28 :             const double dfMinX = m_adfGeoTransform[0];
    1129             :             const double dfMaxX =
    1130          28 :                 m_adfGeoTransform[0] + GetRasterXSize() * m_adfGeoTransform[1];
    1131          28 :             const double dfMaxY = m_adfGeoTransform[3];
    1132             :             const double dfMinY =
    1133          28 :                 m_adfGeoTransform[3] + GetRasterYSize() * m_adfGeoTransform[5];
    1134             : 
    1135          28 :             const double dfOvrMinX = poRawDS->m_adfGeoTransform[0];
    1136             :             const double dfOvrMaxX =
    1137          28 :                 poRawDS->m_adfGeoTransform[0] +
    1138          28 :                 poRawDS->GetRasterXSize() * poRawDS->m_adfGeoTransform[1];
    1139          28 :             const double dfOvrMaxY = poRawDS->m_adfGeoTransform[3];
    1140             :             const double dfOvrMinY =
    1141          28 :                 poRawDS->m_adfGeoTransform[3] +
    1142          28 :                 poRawDS->GetRasterYSize() * poRawDS->m_adfGeoTransform[5];
    1143             : 
    1144          28 :             if (fabs(dfMinX - dfOvrMinX) < 1e-10 * fabs(dfMinX) &&
    1145          26 :                 fabs(dfMinY - dfOvrMinY) < 1e-10 * fabs(dfMinY) &&
    1146          26 :                 fabs(dfMaxX - dfOvrMaxX) < 1e-10 * fabs(dfMaxX) &&
    1147          26 :                 fabs(dfMaxY - dfOvrMaxY) < 1e-10 * fabs(dfMaxY))
    1148             :             {
    1149          26 :                 m_apoOverviewDS.emplace_back(std::move(poRawDS));
    1150             :             }
    1151             :             else
    1152             :             {
    1153             :                 // If this zoom level doesn't share the same origin and extent
    1154             :                 // as the most resoluted one, then subset it
    1155           2 :                 CPLStringList aosOptions;
    1156           2 :                 aosOptions.AddString("-of");
    1157           2 :                 aosOptions.AddString("VRT");
    1158           2 :                 aosOptions.AddString("-projwin");
    1159           2 :                 aosOptions.AddString(CPLSPrintf("%.18g", dfMinX));
    1160           2 :                 aosOptions.AddString(CPLSPrintf("%.18g", dfMaxY));
    1161           2 :                 aosOptions.AddString(CPLSPrintf("%.18g", dfMaxX));
    1162           2 :                 aosOptions.AddString(CPLSPrintf("%.18g", dfMinY));
    1163             :                 auto psOptions =
    1164           2 :                     GDALTranslateOptionsNew(aosOptions.List(), nullptr);
    1165             :                 auto hDS =
    1166           2 :                     GDALTranslate("", GDALDataset::ToHandle(poRawDS.get()),
    1167             :                                   psOptions, nullptr);
    1168           2 :                 GDALTranslateOptionsFree(psOptions);
    1169           2 :                 if (hDS == nullptr)
    1170           0 :                     continue;
    1171           2 :                 m_apoIntermediaryDS.emplace_back(std::move(poRawDS));
    1172           2 :                 m_apoOverviewDS.emplace_back(GDALDataset::FromHandle(hDS));
    1173             :             }
    1174             :         }
    1175             :     }
    1176          14 :     if (m_poDS == nullptr)
    1177             :     {
    1178           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find valid tile matrix");
    1179           0 :         return false;
    1180             :     }
    1181             : 
    1182             :     // Create main bands
    1183          71 :     for (int i = 0; i < m_poDS->GetRasterCount(); i++)
    1184             :     {
    1185          57 :         auto poSrcBand = m_poDS->GetRasterBand(i + 1);
    1186          57 :         auto poBand = new STACTARasterBand(this, i + 1, poSrcBand);
    1187          57 :         if (oEoBands.IsValid() && oEoBands.Size() == nExpectedBandCount)
    1188             :         {
    1189             :             // Set band metadata
    1190          42 :             if (oEoBands[i].GetType() == CPLJSONObject::Type::Object)
    1191             :             {
    1192         118 :                 for (const auto &oItem : oEoBands[i].GetChildren())
    1193             :                 {
    1194          76 :                     if (oBands.IsValid())
    1195             :                     {
    1196             :                         // STAC 1.1
    1197          22 :                         if (STARTS_WITH(oItem.GetName().c_str(), "eo:"))
    1198             :                         {
    1199           2 :                             poBand->GDALRasterBand::SetMetadataItem(
    1200           2 :                                 oItem.GetName().c_str() + strlen("eo:"),
    1201           2 :                                 oItem.ToString().c_str());
    1202             :                         }
    1203          36 :                         else if (oItem.GetName() != "data_type" &&
    1204          46 :                                  oItem.GetName() != "nodata" &&
    1205          40 :                                  oItem.GetName() != "unit" &&
    1206          38 :                                  oItem.GetName() != "raster:scale" &&
    1207          65 :                                  oItem.GetName() != "raster:offset" &&
    1208          28 :                                  oItem.GetName() != "raster:bits_per_sample")
    1209             :                         {
    1210          12 :                             poBand->GDALRasterBand::SetMetadataItem(
    1211          12 :                                 oItem.GetName().c_str(),
    1212          12 :                                 oItem.ToString().c_str());
    1213             :                         }
    1214             :                     }
    1215             :                     else
    1216             :                     {
    1217             :                         // STAC 1.0
    1218         108 :                         poBand->GDALRasterBand::SetMetadataItem(
    1219         162 :                             oItem.GetName().c_str(), oItem.ToString().c_str());
    1220             :                     }
    1221             :                 }
    1222             :             }
    1223             :         }
    1224         201 :         if (oRasterBands.IsValid() &&
    1225          87 :             oRasterBands.Size() == nExpectedBandCount &&
    1226          87 :             oRasterBands[i].GetType() == CPLJSONObject::Type::Object)
    1227             :         {
    1228          30 :             poBand->m_osUnit = oRasterBands[i].GetString("unit");
    1229          60 :             const double dfScale = oRasterBands[i].GetDouble(
    1230          30 :                 oBands.IsValid() ? "raster:scale" : "scale");
    1231          30 :             if (dfScale != 0)
    1232           5 :                 poBand->m_dfScale = dfScale;
    1233          60 :             poBand->m_dfOffset = oRasterBands[i].GetDouble(
    1234          30 :                 oBands.IsValid() ? "raster:offset" : "offset");
    1235          60 :             const int nBitsPerSample = oRasterBands[i].GetInteger(
    1236          30 :                 oBands.IsValid() ? "raster:bits_per_sample"
    1237             :                                  : "bits_per_sample");
    1238           5 :             if (((nBitsPerSample >= 1 && nBitsPerSample <= 7) &&
    1239          35 :                  poBand->GetRasterDataType() == GDT_Byte) ||
    1240           0 :                 ((nBitsPerSample >= 9 && nBitsPerSample <= 15) &&
    1241           0 :                  poBand->GetRasterDataType() == GDT_UInt16))
    1242             :             {
    1243           5 :                 poBand->GDALRasterBand::SetMetadataItem(
    1244             :                     "NBITS", CPLSPrintf("%d", nBitsPerSample),
    1245             :                     "IMAGE_STRUCTURE");
    1246             :             }
    1247             :         }
    1248          57 :         SetBand(i + 1, poBand);
    1249             :     }
    1250             : 
    1251             :     // Set dataset metadata
    1252          95 :     for (const auto &oItem : oProperties.GetChildren())
    1253             :     {
    1254         162 :         const auto osName = oItem.GetName();
    1255         151 :         if (osName != "tiles:tile_matrix_links" &&
    1256          70 :             osName != "tiles:tile_matrix_sets")
    1257             :         {
    1258          56 :             GDALDataset::SetMetadataItem(osName.c_str(),
    1259         112 :                                          oItem.ToString().c_str());
    1260             :         }
    1261             :     }
    1262             : 
    1263          14 :     if (poProtoDS)
    1264             :     {
    1265             :         const char *pszInterleave =
    1266           9 :             poProtoDS->GetMetadataItem("INTERLEAVE", "IMAGE_STRUCTURE");
    1267           9 :         GDALDataset::SetMetadataItem("INTERLEAVE",
    1268             :                                      pszInterleave ? pszInterleave : "PIXEL",
    1269             :                                      "IMAGE_STRUCTURE");
    1270             :     }
    1271             :     else
    1272             :     {
    1273             :         // A bit bold to assume that, but that should be a reasonable
    1274             :         // setting
    1275           5 :         GDALDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    1276             :     }
    1277             : 
    1278          14 :     m_bDownloadWholeMetaTile = CPLTestBool(CSLFetchNameValueDef(
    1279          14 :         poOpenInfo->papszOpenOptions, "WHOLE_METATILE", "NO"));
    1280             : 
    1281          14 :     return true;
    1282             : }
    1283             : 
    1284             : /************************************************************************/
    1285             : /*                          ~STACTADataset()                            */
    1286             : /************************************************************************/
    1287             : 
    1288          42 : STACTADataset::~STACTADataset()
    1289             : {
    1290          21 :     m_poDS.reset();
    1291          21 :     m_apoOverviewDS.clear();
    1292          21 :     m_apoIntermediaryDS.clear();
    1293          42 : }
    1294             : 
    1295             : /************************************************************************/
    1296             : /*                          FlushCache()                                */
    1297             : /************************************************************************/
    1298             : 
    1299           0 : CPLErr STACTADataset::FlushCache(bool bAtClosing)
    1300             : {
    1301           0 :     m_oCacheTileDS.clear();
    1302           0 :     return GDALDataset::FlushCache(bAtClosing);
    1303             : }
    1304             : 
    1305             : /************************************************************************/
    1306             : /*                            InitRaster()                              */
    1307             : /************************************************************************/
    1308             : 
    1309          42 : bool STACTARawDataset::InitRaster(GDALDataset *poProtoDS,
    1310             :                                   const std::vector<GDALDataType> &aeDT,
    1311             :                                   const std::vector<bool> &abSetNoData,
    1312             :                                   const std::vector<double> &adfNoData,
    1313             :                                   const gdal::TileMatrixSet *poTMS,
    1314             :                                   const std::string &osTMId,
    1315             :                                   const gdal::TileMatrixSet::TileMatrix &oTM,
    1316             :                                   const std::map<CPLString, Limits> &oMapLimits)
    1317             : {
    1318          42 :     int nMatrixWidth = oTM.mMatrixWidth;
    1319          42 :     int nMatrixHeight = oTM.mMatrixHeight;
    1320          42 :     auto oIterLimit = oMapLimits.find(osTMId);
    1321          42 :     if (oIterLimit != oMapLimits.end())
    1322             :     {
    1323          33 :         m_nMinMetaTileCol = oIterLimit->second.min_tile_col;
    1324          33 :         m_nMinMetaTileRow = oIterLimit->second.min_tile_row;
    1325          33 :         nMatrixWidth = oIterLimit->second.max_tile_col - m_nMinMetaTileCol + 1;
    1326          33 :         nMatrixHeight = oIterLimit->second.max_tile_row - m_nMinMetaTileRow + 1;
    1327             :     }
    1328          42 :     m_nMetaTileWidth = oTM.mTileWidth;
    1329          42 :     m_nMetaTileHeight = oTM.mTileHeight;
    1330          42 :     nRasterXSize = nMatrixWidth * m_nMetaTileWidth;
    1331          42 :     nRasterYSize = nMatrixHeight * m_nMetaTileHeight;
    1332             : 
    1333          42 :     if (poProtoDS)
    1334             :     {
    1335         108 :         for (int i = 0; i < poProtoDS->GetRasterCount(); i++)
    1336             :         {
    1337          81 :             auto poProtoBand = poProtoDS->GetRasterBand(i + 1);
    1338          81 :             auto poBand = new STACTARawRasterBand(this, i + 1, poProtoBand);
    1339          81 :             SetBand(i + 1, poBand);
    1340             :         }
    1341             :     }
    1342             :     else
    1343             :     {
    1344         105 :         for (int i = 0; i < static_cast<int>(aeDT.size()); i++)
    1345             :         {
    1346          90 :             auto poBand = new STACTARawRasterBand(this, i + 1, aeDT[i],
    1347          90 :                                                   abSetNoData[i], adfNoData[i]);
    1348          90 :             SetBand(i + 1, poBand);
    1349             :         }
    1350             :     }
    1351             : 
    1352          84 :     CPLString osCRS = poTMS->crs().c_str();
    1353          42 :     if (osCRS == "http://www.opengis.net/def/crs/OGC/1.3/CRS84")
    1354          42 :         osCRS = "EPSG:4326";
    1355          42 :     if (m_oSRS.SetFromUserInput(osCRS) != OGRERR_NONE)
    1356             :     {
    1357           0 :         return false;
    1358             :     }
    1359          42 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1360          84 :     m_adfGeoTransform[0] =
    1361          42 :         oTM.mTopLeftX + m_nMinMetaTileCol * m_nMetaTileWidth * oTM.mResX;
    1362          42 :     m_adfGeoTransform[1] = oTM.mResX;
    1363          84 :     m_adfGeoTransform[3] =
    1364          42 :         oTM.mTopLeftY - m_nMinMetaTileRow * m_nMetaTileHeight * oTM.mResY;
    1365          42 :     m_adfGeoTransform[5] = -oTM.mResY;
    1366          42 :     SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    1367             : 
    1368          42 :     return true;
    1369             : }
    1370             : 
    1371             : /************************************************************************/
    1372             : /*                            GetSpatialRef ()                          */
    1373             : /************************************************************************/
    1374             : 
    1375           3 : const OGRSpatialReference *STACTADataset::GetSpatialRef() const
    1376             : {
    1377           3 :     return nBands == 0 ? nullptr : &m_oSRS;
    1378             : }
    1379             : 
    1380             : /************************************************************************/
    1381             : /*                           GetGeoTransform()                          */
    1382             : /************************************************************************/
    1383             : 
    1384           3 : CPLErr STACTADataset::GetGeoTransform(double *padfGeoTransform)
    1385             : {
    1386           3 :     memcpy(padfGeoTransform, &m_adfGeoTransform[0], 6 * sizeof(double));
    1387           3 :     return nBands == 0 ? CE_Failure : CE_None;
    1388             : }
    1389             : 
    1390             : /************************************************************************/
    1391             : /*                            OpenStatic()                              */
    1392             : /************************************************************************/
    1393             : 
    1394          21 : GDALDataset *STACTADataset::OpenStatic(GDALOpenInfo *poOpenInfo)
    1395             : {
    1396          21 :     if (!Identify(poOpenInfo))
    1397           0 :         return nullptr;
    1398          42 :     auto poDS = std::make_unique<STACTADataset>();
    1399          21 :     if (!poDS->Open(poOpenInfo))
    1400           5 :         return nullptr;
    1401          16 :     return poDS.release();
    1402             : }
    1403             : 
    1404             : /************************************************************************/
    1405             : /*                       GDALRegister_STACTA()                          */
    1406             : /************************************************************************/
    1407             : 
    1408        1522 : void GDALRegister_STACTA()
    1409             : 
    1410             : {
    1411        1522 :     if (GDALGetDriverByName("STACTA") != nullptr)
    1412         301 :         return;
    1413             : 
    1414        1221 :     GDALDriver *poDriver = new GDALDriver();
    1415             : 
    1416        1221 :     poDriver->SetDescription("STACTA");
    1417        1221 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1418        1221 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
    1419        1221 :                               "Spatio-Temporal Asset Catalog Tiled Assets");
    1420        1221 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/stacta.html");
    1421        1221 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "json");
    1422        1221 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1423        1221 :     poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
    1424        1221 :     poDriver->SetMetadataItem(
    1425             :         GDAL_DMD_OPENOPTIONLIST,
    1426             :         "<OpenOptionList>"
    1427             :         "   <Option name='WHOLE_METATILE' type='boolean' "
    1428             :         "description='Whether to download whole metatiles'/>"
    1429             :         "   <Option name='SKIP_MISSING_METATILE' type='boolean' "
    1430             :         "description='Whether to gracefully skip missing metatiles'/>"
    1431        1221 :         "</OpenOptionList>");
    1432             : 
    1433        1221 :     poDriver->pfnOpen = STACTADataset::OpenStatic;
    1434        1221 :     poDriver->pfnIdentify = STACTADataset::Identify;
    1435             : 
    1436        1221 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1437             : }

Generated by: LCOV version 1.14