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

Generated by: LCOV version 1.14