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: 2025-01-18 12:42:00 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       51063 : int STACTADataset::Identify(GDALOpenInfo *poOpenInfo)
     636             : {
     637       51063 :     if (STARTS_WITH(poOpenInfo->pszFilename, "STACTA:"))
     638             :     {
     639           6 :         return true;
     640             :     }
     641             : 
     642       51057 :     const bool bIsSingleDriver = poOpenInfo->IsSingleAllowedDriver("STACTA");
     643       51054 :     if (bIsSingleDriver && (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
     644           4 :                             STARTS_WITH(poOpenInfo->pszFilename, "https://")))
     645             :     {
     646           1 :         return true;
     647             :     }
     648             : 
     649       51053 :     if (
     650             : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
     651       51486 :         (!bIsSingleDriver && !poOpenInfo->IsExtensionEqualToCI("json")) ||
     652             : #endif
     653         433 :         poOpenInfo->nHeaderBytes == 0)
     654             :     {
     655       50902 :         return false;
     656             :     }
     657             : 
     658         357 :     for (int i = 0; i < 2; i++)
     659             :     {
     660             :         // TryToIngest() may reallocate pabyHeader, so do not move this
     661             :         // before the loop.
     662         252 :         const char *pszHeader =
     663             :             reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
     664         252 :         while (*pszHeader != 0 &&
     665         252 :                std::isspace(static_cast<unsigned char>(*pszHeader)))
     666           0 :             ++pszHeader;
     667         252 :         if (bIsSingleDriver)
     668             :         {
     669           4 :             return pszHeader[0] == '{';
     670             :         }
     671             : 
     672         248 :         if (strstr(pszHeader, "\"stac_extensions\"") != nullptr &&
     673          64 :             (strstr(pszHeader, "\"tiled-assets\"") != nullptr ||
     674          46 :              strstr(pszHeader,
     675             :                     "https://stac-extensions.github.io/tiled-assets/") !=
     676             :                  nullptr))
     677             :         {
     678          38 :             return true;
     679             :         }
     680             : 
     681         210 :         if (i == 0)
     682             :         {
     683             :             // Should be enough for a STACTA .json file
     684         105 :             poOpenInfo->TryToIngest(32768);
     685             :         }
     686             :     }
     687             : 
     688         105 :     return false;
     689             : }
     690             : 
     691             : /************************************************************************/
     692             : /*                               Open()                                 */
     693             : /************************************************************************/
     694             : 
     695          23 : bool STACTADataset::Open(GDALOpenInfo *poOpenInfo)
     696             : {
     697          46 :     CPLString osFilename(poOpenInfo->pszFilename);
     698          46 :     CPLString osAssetName;
     699          46 :     CPLString osTMS;
     700          23 :     if (STARTS_WITH(poOpenInfo->pszFilename, "STACTA:"))
     701             :     {
     702             :         const CPLStringList aosTokens(CSLTokenizeString2(
     703           3 :             poOpenInfo->pszFilename, ":", CSLT_HONOURSTRINGS));
     704           4 :         if (aosTokens.size() != 2 && aosTokens.size() != 3 &&
     705           1 :             aosTokens.size() != 4)
     706           0 :             return false;
     707           3 :         osFilename = aosTokens[1];
     708           3 :         if (aosTokens.size() >= 3)
     709           3 :             osAssetName = aosTokens[2];
     710           3 :         if (aosTokens.size() == 4)
     711           1 :             osTMS = aosTokens[3];
     712             :     }
     713             : 
     714          46 :     CPLJSONDocument oDoc;
     715          46 :     if (STARTS_WITH(osFilename, "http://") ||
     716          23 :         STARTS_WITH(osFilename, "https://"))
     717             :     {
     718           0 :         if (!oDoc.LoadUrl(osFilename, nullptr))
     719           0 :             return false;
     720             :     }
     721             :     else
     722             :     {
     723          23 :         if (!oDoc.Load(osFilename))
     724           0 :             return false;
     725             :     }
     726          46 :     const auto oRoot = oDoc.GetRoot();
     727          69 :     const auto oProperties = oRoot["properties"];
     728          46 :     if (!oProperties.IsValid() ||
     729          23 :         oProperties.GetType() != CPLJSONObject::Type::Object)
     730             :     {
     731           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing properties");
     732           0 :         return false;
     733             :     }
     734             : 
     735          69 :     const auto oAssetTemplates = oRoot["asset_templates"];
     736          46 :     if (!oAssetTemplates.IsValid() ||
     737          23 :         oAssetTemplates.GetType() != CPLJSONObject::Type::Object)
     738             :     {
     739           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing asset_templates");
     740           0 :         return false;
     741             :     }
     742             : 
     743          46 :     const auto aoAssetTemplates = oAssetTemplates.GetChildren();
     744          23 :     if (aoAssetTemplates.size() == 0)
     745             :     {
     746           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Empty asset_templates");
     747           0 :         return false;
     748             :     }
     749             : 
     750          69 :     const auto oTMSs = oProperties.GetObj("tiles:tile_matrix_sets");
     751          23 :     if (!oTMSs.IsValid() || oTMSs.GetType() != CPLJSONObject::Type::Object)
     752             :     {
     753           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     754             :                  "Missing properties[\"tiles:tile_matrix_sets\"]");
     755           0 :         return false;
     756             :     }
     757          46 :     const auto aoTMSs = oTMSs.GetChildren();
     758          23 :     if (aoTMSs.empty())
     759             :     {
     760           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     761             :                  "Empty properties[\"tiles:tile_matrix_sets\"]");
     762           0 :         return false;
     763             :     }
     764             : 
     765          48 :     if ((aoAssetTemplates.size() >= 2 || aoTMSs.size() >= 2) &&
     766          48 :         osAssetName.empty() && osTMS.empty())
     767             :     {
     768           2 :         int nSDSCount = 0;
     769           5 :         for (const auto &oAssetTemplate : aoAssetTemplates)
     770             :         {
     771           6 :             const CPLString osAssetNameSubDS = oAssetTemplate.GetName();
     772           3 :             const char *pszAssetNameSubDS = osAssetNameSubDS.c_str();
     773           3 :             if (aoTMSs.size() >= 2)
     774             :             {
     775           3 :                 for (const auto &oTMS : aoTMSs)
     776             :                 {
     777           2 :                     const CPLString osTMSSubDS = oTMS.GetName();
     778           2 :                     const char *pszTMSSubDS = osTMSSubDS.c_str();
     779           2 :                     GDALDataset::SetMetadataItem(
     780             :                         CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
     781             :                         CPLSPrintf("STACTA:\"%s\":%s:%s", osFilename.c_str(),
     782             :                                    pszAssetNameSubDS, pszTMSSubDS),
     783             :                         "SUBDATASETS");
     784           2 :                     GDALDataset::SetMetadataItem(
     785             :                         CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
     786             :                         CPLSPrintf("Asset %s, tile matrix set %s",
     787             :                                    pszAssetNameSubDS, pszTMSSubDS),
     788             :                         "SUBDATASETS");
     789           2 :                     nSDSCount++;
     790             :                 }
     791             :             }
     792             :             else
     793             :             {
     794           2 :                 GDALDataset::SetMetadataItem(
     795             :                     CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
     796             :                     CPLSPrintf("STACTA:\"%s\":%s", osFilename.c_str(),
     797             :                                pszAssetNameSubDS),
     798             :                     "SUBDATASETS");
     799           2 :                 GDALDataset::SetMetadataItem(
     800             :                     CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
     801             :                     CPLSPrintf("Asset %s", pszAssetNameSubDS), "SUBDATASETS");
     802           2 :                 nSDSCount++;
     803             :             }
     804             :         }
     805           2 :         return true;
     806             :     }
     807             : 
     808          21 :     if (osAssetName.empty())
     809             :     {
     810          18 :         osAssetName = aoAssetTemplates[0].GetName();
     811             :     }
     812          42 :     const auto oAssetTemplate = oAssetTemplates.GetObj(osAssetName);
     813          42 :     if (!oAssetTemplate.IsValid() ||
     814          21 :         oAssetTemplate.GetType() != CPLJSONObject::Type::Object)
     815             :     {
     816           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     817             :                  "Cannot find asset_templates[\"%s\"]", osAssetName.c_str());
     818           0 :         return false;
     819             :     }
     820             : 
     821          21 :     if (osTMS.empty())
     822             :     {
     823          20 :         osTMS = aoTMSs[0].GetName();
     824             :     }
     825          42 :     const auto oTMS = oTMSs.GetObj(osTMS);
     826          21 :     if (!oTMS.IsValid() || oTMS.GetType() != CPLJSONObject::Type::Object)
     827             :     {
     828           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     829             :                  "Cannot find properties[\"tiles:tile_matrix_sets\"][\"%s\"]",
     830             :                  osTMS.c_str());
     831           0 :         return false;
     832             :     }
     833             : 
     834             :     auto poTMS = gdal::TileMatrixSet::parse(
     835          42 :         oTMS.Format(CPLJSONObject::PrettyFormat::Plain).c_str());
     836          21 :     if (poTMS == nullptr)
     837           0 :         return false;
     838             : 
     839          63 :     CPLString osURLTemplate = oAssetTemplate.GetString("href");
     840          21 :     if (osURLTemplate.empty())
     841             :     {
     842           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     843             :                  "Cannot find asset_templates[\"%s\"][\"href\"]",
     844             :                  osAssetName.c_str());
     845             :     }
     846          21 :     osURLTemplate.replaceAll("{TileMatrixSet}", osTMS);
     847          21 :     if (STARTS_WITH(osURLTemplate, "file://"))
     848             :     {
     849           0 :         osURLTemplate = osURLTemplate.substr(strlen("file://"));
     850             :     }
     851          21 :     else if (STARTS_WITH(osURLTemplate, "s3://"))
     852             :     {
     853           0 :         osURLTemplate = "/vsis3/" + osURLTemplate.substr(strlen("s3://"));
     854             :     }
     855             : 
     856          41 :     if (!STARTS_WITH(osURLTemplate, "http://") &&
     857          20 :         !STARTS_WITH(osURLTemplate, "https://"))
     858             :     {
     859          20 :         if (STARTS_WITH(osURLTemplate, "./"))
     860          20 :             osURLTemplate = osURLTemplate.substr(2);
     861          40 :         osURLTemplate = CPLProjectRelativeFilenameSafe(
     862          40 :             CPLGetDirnameSafe(osFilename).c_str(), osURLTemplate);
     863             :     }
     864             : 
     865             :     // Parse optional tile matrix set limits
     866          42 :     std::map<CPLString, Limits> oMapLimits;
     867          63 :     const auto oTMLinks = oProperties.GetObj("tiles:tile_matrix_links");
     868          21 :     if (oTMLinks.IsValid())
     869             :     {
     870          18 :         if (oTMLinks.GetType() != CPLJSONObject::Type::Object)
     871             :         {
     872           0 :             CPLError(
     873             :                 CE_Failure, CPLE_AppDefined,
     874             :                 "Invalid type for properties[\"tiles:tile_matrix_links\"]");
     875           0 :             return false;
     876             :         }
     877             : 
     878          54 :         auto oLimits = oTMLinks[osTMS]["limits"];
     879          36 :         if (oLimits.IsValid() &&
     880          18 :             oLimits.GetType() == CPLJSONObject::Type::Object)
     881             :         {
     882          72 :             for (const auto &oLimit : oLimits.GetChildren())
     883             :             {
     884          54 :                 Limits limits;
     885          54 :                 limits.min_tile_col = oLimit.GetInteger("min_tile_col");
     886          54 :                 limits.max_tile_col = oLimit.GetInteger("max_tile_col");
     887          54 :                 limits.min_tile_row = oLimit.GetInteger("min_tile_row");
     888          54 :                 limits.max_tile_row = oLimit.GetInteger("max_tile_row");
     889          54 :                 oMapLimits[oLimit.GetName()] = limits;
     890             :             }
     891             :         }
     892             :     }
     893          21 :     const auto &tmsList = poTMS->tileMatrixList();
     894          21 :     if (tmsList.empty())
     895           0 :         return false;
     896             : 
     897          42 :     m_bSkipMissingMetaTile = CPLTestBool(CSLFetchNameValueDef(
     898          21 :         poOpenInfo->papszOpenOptions, "SKIP_MISSING_METATILE",
     899             :         CPLGetConfigOption("GDAL_STACTA_SKIP_MISSING_METATILE", "NO")));
     900             : 
     901             :     // STAC 1.1 uses bands instead of eo:bands and raster:bands
     902          63 :     const auto oBands = oAssetTemplate.GetArray("bands");
     903             : 
     904             :     // Check if there are both eo:bands and raster:bands extension
     905             :     // If so, we don't need to fetch a prototype metatile to derive the
     906             :     // information we need (number of bands, data type and nodata value)
     907             :     const auto oEoBands =
     908          62 :         oBands.IsValid() ? oBands : oAssetTemplate.GetArray("eo:bands");
     909             :     const auto oRasterBands =
     910          62 :         oBands.IsValid() ? oBands : oAssetTemplate.GetArray("raster:bands");
     911             : 
     912          42 :     std::vector<GDALDataType> aeDT;
     913          42 :     std::vector<double> adfNoData;
     914          42 :     std::vector<bool> abSetNoData;
     915          21 :     int nExpectedBandCount = 0;
     916          21 :     if (oRasterBands.IsValid())
     917             :     {
     918           9 :         if (oEoBands.IsValid() && oEoBands.Size() != oRasterBands.Size())
     919             :         {
     920           1 :             CPLError(CE_Warning, CPLE_AppDefined,
     921             :                      "Number of bands in eo:bands and raster:bands is not "
     922             :                      "identical. Ignoring the later");
     923             :         }
     924             :         else
     925             :         {
     926           8 :             nExpectedBandCount = oRasterBands.Size();
     927             : 
     928             :             const struct
     929             :             {
     930             :                 const char *pszStacDataType;
     931             :                 GDALDataType eGDALDataType;
     932           8 :             } aDataTypeMapping[] = {
     933             :                 {"int8", GDT_Int8},
     934             :                 {"int16", GDT_Int16},
     935             :                 {"int32", GDT_Int32},
     936             :                 {"int64", GDT_Int64},
     937             :                 {"uint8", GDT_Byte},
     938             :                 {"uint16", GDT_UInt16},
     939             :                 {"uint32", GDT_UInt32},
     940             :                 {"uint64", GDT_UInt64},
     941             :                 // float16: 16-bit float; unhandled
     942             :                 {"float32", GDT_Float32},
     943             :                 {"float64", GDT_Float64},
     944             :                 {"cint16", GDT_CInt16},
     945             :                 {"cint32", GDT_CInt32},
     946             :                 {"cfloat32", GDT_CFloat32},
     947             :                 {"cfloat64", GDT_CFloat64},
     948             :             };
     949             : 
     950          38 :             for (int i = 0; i < nExpectedBandCount; ++i)
     951             :             {
     952          33 :                 if (oRasterBands[i].GetType() != CPLJSONObject::Type::Object)
     953             :                 {
     954           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     955             :                              "Wrong raster:bands[%d]", i);
     956           3 :                     return false;
     957             :                 }
     958             :                 const std::string osDataType =
     959          64 :                     oRasterBands[i].GetString("data_type");
     960          32 :                 GDALDataType eDT = GDT_Unknown;
     961         280 :                 for (const auto &oTuple : aDataTypeMapping)
     962             :                 {
     963         278 :                     if (osDataType == oTuple.pszStacDataType)
     964             :                     {
     965          30 :                         eDT = oTuple.eGDALDataType;
     966          30 :                         break;
     967             :                     }
     968             :                 }
     969          32 :                 if (eDT == GDT_Unknown)
     970             :                 {
     971           2 :                     CPLError(CE_Failure, CPLE_AppDefined,
     972             :                              "Wrong raster:bands[%d].data_type = %s", i,
     973             :                              osDataType.c_str());
     974           2 :                     return false;
     975             :                 }
     976          30 :                 aeDT.push_back(eDT);
     977             : 
     978          90 :                 const auto oNoData = oRasterBands[i].GetObj("nodata");
     979          30 :                 if (oNoData.GetType() == CPLJSONObject::Type::String)
     980             :                 {
     981          48 :                     const std::string osNoData = oNoData.ToString();
     982          16 :                     if (osNoData == "inf")
     983             :                     {
     984           5 :                         abSetNoData.push_back(true);
     985           5 :                         adfNoData.push_back(
     986           5 :                             std::numeric_limits<double>::infinity());
     987             :                     }
     988          11 :                     else if (osNoData == "-inf")
     989             :                     {
     990           5 :                         abSetNoData.push_back(true);
     991           5 :                         adfNoData.push_back(
     992           5 :                             -std::numeric_limits<double>::infinity());
     993             :                     }
     994           6 :                     else if (osNoData == "nan")
     995             :                     {
     996           5 :                         abSetNoData.push_back(true);
     997           5 :                         adfNoData.push_back(
     998           5 :                             std::numeric_limits<double>::quiet_NaN());
     999             :                     }
    1000             :                     else
    1001             :                     {
    1002           1 :                         CPLError(CE_Warning, CPLE_AppDefined,
    1003             :                                  "Invalid raster:bands[%d].nodata = %s", i,
    1004             :                                  osNoData.c_str());
    1005           1 :                         abSetNoData.push_back(false);
    1006           1 :                         adfNoData.push_back(
    1007           1 :                             std::numeric_limits<double>::quiet_NaN());
    1008             :                     }
    1009             :                 }
    1010          14 :                 else if (oNoData.GetType() == CPLJSONObject::Type::Integer ||
    1011          25 :                          oNoData.GetType() == CPLJSONObject::Type::Long ||
    1012          11 :                          oNoData.GetType() == CPLJSONObject::Type::Double)
    1013             :                 {
    1014           8 :                     abSetNoData.push_back(true);
    1015           8 :                     adfNoData.push_back(oNoData.ToDouble());
    1016             :                 }
    1017           6 :                 else if (!oNoData.IsValid())
    1018             :                 {
    1019           5 :                     abSetNoData.push_back(false);
    1020           5 :                     adfNoData.push_back(
    1021           5 :                         std::numeric_limits<double>::quiet_NaN());
    1022             :                 }
    1023             :                 else
    1024             :                 {
    1025           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1026             :                              "Invalid raster:bands[%d].nodata", i);
    1027           1 :                     abSetNoData.push_back(false);
    1028           1 :                     adfNoData.push_back(
    1029           1 :                         std::numeric_limits<double>::quiet_NaN());
    1030             :                 }
    1031             :             }
    1032             : 
    1033           5 :             CPLAssert(aeDT.size() == abSetNoData.size());
    1034           5 :             CPLAssert(adfNoData.size() == abSetNoData.size());
    1035             :         }
    1036             :     }
    1037             : 
    1038          18 :     std::unique_ptr<GDALDataset> poProtoDS;
    1039          18 :     if (aeDT.empty())
    1040             :     {
    1041          13 :         for (int i = 0; i < static_cast<int>(tmsList.size()); i++)
    1042             :         {
    1043             :             // Open a metatile to get mostly its band data type
    1044          13 :             int nProtoTileCol = 0;
    1045          13 :             int nProtoTileRow = 0;
    1046          13 :             auto oIterLimit = oMapLimits.find(tmsList[i].mId);
    1047          13 :             if (oIterLimit != oMapLimits.end())
    1048             :             {
    1049          10 :                 nProtoTileCol = oIterLimit->second.min_tile_col;
    1050          10 :                 nProtoTileRow = oIterLimit->second.min_tile_row;
    1051             :             }
    1052             :             const CPLString osURL =
    1053          13 :                 CPLString(osURLTemplate)
    1054          26 :                     .replaceAll("{TileMatrix}", tmsList[i].mId)
    1055          26 :                     .replaceAll("{TileRow}", CPLSPrintf("%d", nProtoTileRow))
    1056          26 :                     .replaceAll("{TileCol}", CPLSPrintf("%d", nProtoTileCol));
    1057          38 :             CPLString osProtoDSName = (STARTS_WITH(osURL, "http://") ||
    1058          12 :                                        STARTS_WITH(osURL, "https://"))
    1059          26 :                                           ? CPLString("/vsicurl/" + osURL)
    1060          26 :                                           : osURL;
    1061             :             CPLConfigOptionSetter oSetter("GDAL_DISABLE_READDIR_ON_OPEN",
    1062             :                                           "EMPTY_DIR",
    1063          13 :                                           /* bSetOnlyIfUndefined = */ true);
    1064          13 :             if (m_bSkipMissingMetaTile)
    1065           2 :                 CPLPushErrorHandler(CPLQuietErrorHandler);
    1066          13 :             poProtoDS.reset(GDALDataset::Open(osProtoDSName.c_str()));
    1067          13 :             if (m_bSkipMissingMetaTile)
    1068           2 :                 CPLPopErrorHandler();
    1069          13 :             if (poProtoDS != nullptr)
    1070             :             {
    1071          11 :                 break;
    1072             :             }
    1073           2 :             if (!m_bSkipMissingMetaTile)
    1074             :             {
    1075           2 :                 CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s",
    1076             :                          osURL.c_str());
    1077           2 :                 return false;
    1078             :             }
    1079             :         }
    1080          11 :         if (poProtoDS == nullptr)
    1081             :         {
    1082           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1083             :                      "Cannot find prototype dataset");
    1084           0 :             return false;
    1085             :         }
    1086          11 :         nExpectedBandCount = poProtoDS->GetRasterCount();
    1087             :     }
    1088             : 
    1089             :     // Iterate over tile matrices to create corresponding STACTARawDataset
    1090             :     // objects
    1091          64 :     for (int i = static_cast<int>(tmsList.size() - 1); i >= 0; i--)
    1092             :     {
    1093          48 :         const auto &oTM = tmsList[i];
    1094          48 :         int nMatrixWidth = oTM.mMatrixWidth;
    1095          48 :         int nMatrixHeight = oTM.mMatrixHeight;
    1096          48 :         auto oIterLimit = oMapLimits.find(tmsList[i].mId);
    1097          48 :         if (oIterLimit != oMapLimits.end())
    1098             :         {
    1099          39 :             nMatrixWidth = oIterLimit->second.max_tile_col -
    1100          39 :                            oIterLimit->second.min_tile_col + 1;
    1101          39 :             nMatrixHeight = oIterLimit->second.max_tile_row -
    1102          39 :                             oIterLimit->second.min_tile_row + 1;
    1103             :         }
    1104          48 :         if (nMatrixWidth <= 0 || oTM.mTileWidth > INT_MAX / nMatrixWidth ||
    1105          48 :             nMatrixHeight <= 0 || oTM.mTileHeight > INT_MAX / nMatrixHeight)
    1106             :         {
    1107           0 :             continue;
    1108             :         }
    1109          48 :         auto poRawDS = std::make_unique<STACTARawDataset>();
    1110          96 :         if (!poRawDS->InitRaster(poProtoDS.get(), aeDT, abSetNoData, adfNoData,
    1111          48 :                                  poTMS.get(), tmsList[i].mId, oTM, oMapLimits))
    1112             :         {
    1113           0 :             return false;
    1114             :         }
    1115          48 :         poRawDS->m_osURLTemplate = osURLTemplate;
    1116          48 :         poRawDS->m_osURLTemplate.replaceAll("{TileMatrix}", tmsList[i].mId);
    1117          48 :         poRawDS->m_poMasterDS = this;
    1118             : 
    1119          48 :         if (m_poDS == nullptr)
    1120             :         {
    1121          16 :             nRasterXSize = poRawDS->GetRasterXSize();
    1122          16 :             nRasterYSize = poRawDS->GetRasterYSize();
    1123          16 :             m_oSRS = poRawDS->m_oSRS;
    1124          16 :             memcpy(&m_adfGeoTransform[0], &poRawDS->m_adfGeoTransform[0],
    1125             :                    6 * sizeof(double));
    1126          16 :             m_poDS = std::move(poRawDS);
    1127             :         }
    1128             :         else
    1129             :         {
    1130          32 :             const double dfMinX = m_adfGeoTransform[0];
    1131             :             const double dfMaxX =
    1132          32 :                 m_adfGeoTransform[0] + GetRasterXSize() * m_adfGeoTransform[1];
    1133          32 :             const double dfMaxY = m_adfGeoTransform[3];
    1134             :             const double dfMinY =
    1135          32 :                 m_adfGeoTransform[3] + GetRasterYSize() * m_adfGeoTransform[5];
    1136             : 
    1137          32 :             const double dfOvrMinX = poRawDS->m_adfGeoTransform[0];
    1138             :             const double dfOvrMaxX =
    1139          32 :                 poRawDS->m_adfGeoTransform[0] +
    1140          32 :                 poRawDS->GetRasterXSize() * poRawDS->m_adfGeoTransform[1];
    1141          32 :             const double dfOvrMaxY = poRawDS->m_adfGeoTransform[3];
    1142             :             const double dfOvrMinY =
    1143          32 :                 poRawDS->m_adfGeoTransform[3] +
    1144          32 :                 poRawDS->GetRasterYSize() * poRawDS->m_adfGeoTransform[5];
    1145             : 
    1146          32 :             if (fabs(dfMinX - dfOvrMinX) < 1e-10 * fabs(dfMinX) &&
    1147          30 :                 fabs(dfMinY - dfOvrMinY) < 1e-10 * fabs(dfMinY) &&
    1148          30 :                 fabs(dfMaxX - dfOvrMaxX) < 1e-10 * fabs(dfMaxX) &&
    1149          30 :                 fabs(dfMaxY - dfOvrMaxY) < 1e-10 * fabs(dfMaxY))
    1150             :             {
    1151          30 :                 m_apoOverviewDS.emplace_back(std::move(poRawDS));
    1152             :             }
    1153             :             else
    1154             :             {
    1155             :                 // If this zoom level doesn't share the same origin and extent
    1156             :                 // as the most resoluted one, then subset it
    1157           2 :                 CPLStringList aosOptions;
    1158           2 :                 aosOptions.AddString("-of");
    1159           2 :                 aosOptions.AddString("VRT");
    1160           2 :                 aosOptions.AddString("-projwin");
    1161           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", dfMinX));
    1162           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
    1163           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
    1164           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
    1165             :                 auto psOptions =
    1166           2 :                     GDALTranslateOptionsNew(aosOptions.List(), nullptr);
    1167             :                 auto hDS =
    1168           2 :                     GDALTranslate("", GDALDataset::ToHandle(poRawDS.get()),
    1169             :                                   psOptions, nullptr);
    1170           2 :                 GDALTranslateOptionsFree(psOptions);
    1171           2 :                 if (hDS == nullptr)
    1172           0 :                     continue;
    1173           2 :                 m_apoIntermediaryDS.emplace_back(std::move(poRawDS));
    1174           2 :                 m_apoOverviewDS.emplace_back(GDALDataset::FromHandle(hDS));
    1175             :             }
    1176             :         }
    1177             :     }
    1178          16 :     if (m_poDS == nullptr)
    1179             :     {
    1180           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find valid tile matrix");
    1181           0 :         return false;
    1182             :     }
    1183             : 
    1184             :     // Create main bands
    1185          79 :     for (int i = 0; i < m_poDS->GetRasterCount(); i++)
    1186             :     {
    1187          63 :         auto poSrcBand = m_poDS->GetRasterBand(i + 1);
    1188          63 :         auto poBand = new STACTARasterBand(this, i + 1, poSrcBand);
    1189          63 :         if (oEoBands.IsValid() && oEoBands.Size() == nExpectedBandCount)
    1190             :         {
    1191             :             // Set band metadata
    1192          48 :             if (oEoBands[i].GetType() == CPLJSONObject::Type::Object)
    1193             :             {
    1194         136 :                 for (const auto &oItem : oEoBands[i].GetChildren())
    1195             :                 {
    1196          88 :                     if (oBands.IsValid())
    1197             :                     {
    1198             :                         // STAC 1.1
    1199          22 :                         if (STARTS_WITH(oItem.GetName().c_str(), "eo:"))
    1200             :                         {
    1201           2 :                             poBand->GDALRasterBand::SetMetadataItem(
    1202           2 :                                 oItem.GetName().c_str() + strlen("eo:"),
    1203           2 :                                 oItem.ToString().c_str());
    1204             :                         }
    1205          36 :                         else if (oItem.GetName() != "data_type" &&
    1206          46 :                                  oItem.GetName() != "nodata" &&
    1207          40 :                                  oItem.GetName() != "unit" &&
    1208          38 :                                  oItem.GetName() != "raster:scale" &&
    1209          65 :                                  oItem.GetName() != "raster:offset" &&
    1210          28 :                                  oItem.GetName() != "raster:bits_per_sample")
    1211             :                         {
    1212          12 :                             poBand->GDALRasterBand::SetMetadataItem(
    1213          12 :                                 oItem.GetName().c_str(),
    1214          12 :                                 oItem.ToString().c_str());
    1215             :                         }
    1216             :                     }
    1217             :                     else
    1218             :                     {
    1219             :                         // STAC 1.0
    1220         132 :                         poBand->GDALRasterBand::SetMetadataItem(
    1221         198 :                             oItem.GetName().c_str(), oItem.ToString().c_str());
    1222             :                     }
    1223             :                 }
    1224             :             }
    1225             :         }
    1226         219 :         if (oRasterBands.IsValid() &&
    1227          93 :             oRasterBands.Size() == nExpectedBandCount &&
    1228          93 :             oRasterBands[i].GetType() == CPLJSONObject::Type::Object)
    1229             :         {
    1230          30 :             poBand->m_osUnit = oRasterBands[i].GetString("unit");
    1231          60 :             const double dfScale = oRasterBands[i].GetDouble(
    1232          30 :                 oBands.IsValid() ? "raster:scale" : "scale");
    1233          30 :             if (dfScale != 0)
    1234           5 :                 poBand->m_dfScale = dfScale;
    1235          60 :             poBand->m_dfOffset = oRasterBands[i].GetDouble(
    1236          30 :                 oBands.IsValid() ? "raster:offset" : "offset");
    1237          60 :             const int nBitsPerSample = oRasterBands[i].GetInteger(
    1238          30 :                 oBands.IsValid() ? "raster:bits_per_sample"
    1239             :                                  : "bits_per_sample");
    1240           5 :             if (((nBitsPerSample >= 1 && nBitsPerSample <= 7) &&
    1241          35 :                  poBand->GetRasterDataType() == GDT_Byte) ||
    1242           0 :                 ((nBitsPerSample >= 9 && nBitsPerSample <= 15) &&
    1243           0 :                  poBand->GetRasterDataType() == GDT_UInt16))
    1244             :             {
    1245           5 :                 poBand->GDALRasterBand::SetMetadataItem(
    1246             :                     "NBITS", CPLSPrintf("%d", nBitsPerSample),
    1247             :                     "IMAGE_STRUCTURE");
    1248             :             }
    1249             :         }
    1250          63 :         SetBand(i + 1, poBand);
    1251             :     }
    1252             : 
    1253             :     // Set dataset metadata
    1254         109 :     for (const auto &oItem : oProperties.GetChildren())
    1255             :     {
    1256         186 :         const auto osName = oItem.GetName();
    1257         173 :         if (osName != "tiles:tile_matrix_links" &&
    1258          80 :             osName != "tiles:tile_matrix_sets")
    1259             :         {
    1260          64 :             GDALDataset::SetMetadataItem(osName.c_str(),
    1261         128 :                                          oItem.ToString().c_str());
    1262             :         }
    1263             :     }
    1264             : 
    1265          16 :     if (poProtoDS)
    1266             :     {
    1267             :         const char *pszInterleave =
    1268          11 :             poProtoDS->GetMetadataItem("INTERLEAVE", "IMAGE_STRUCTURE");
    1269          11 :         GDALDataset::SetMetadataItem("INTERLEAVE",
    1270             :                                      pszInterleave ? pszInterleave : "PIXEL",
    1271             :                                      "IMAGE_STRUCTURE");
    1272             :     }
    1273             :     else
    1274             :     {
    1275             :         // A bit bold to assume that, but that should be a reasonable
    1276             :         // setting
    1277           5 :         GDALDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    1278             :     }
    1279             : 
    1280          16 :     m_bDownloadWholeMetaTile = CPLTestBool(CSLFetchNameValueDef(
    1281          16 :         poOpenInfo->papszOpenOptions, "WHOLE_METATILE", "NO"));
    1282             : 
    1283          16 :     return true;
    1284             : }
    1285             : 
    1286             : /************************************************************************/
    1287             : /*                          ~STACTADataset()                            */
    1288             : /************************************************************************/
    1289             : 
    1290          46 : STACTADataset::~STACTADataset()
    1291             : {
    1292          23 :     m_poDS.reset();
    1293          23 :     m_apoOverviewDS.clear();
    1294          23 :     m_apoIntermediaryDS.clear();
    1295          46 : }
    1296             : 
    1297             : /************************************************************************/
    1298             : /*                          FlushCache()                                */
    1299             : /************************************************************************/
    1300             : 
    1301           0 : CPLErr STACTADataset::FlushCache(bool bAtClosing)
    1302             : {
    1303           0 :     m_oCacheTileDS.clear();
    1304           0 :     return GDALDataset::FlushCache(bAtClosing);
    1305             : }
    1306             : 
    1307             : /************************************************************************/
    1308             : /*                            InitRaster()                              */
    1309             : /************************************************************************/
    1310             : 
    1311          48 : bool STACTARawDataset::InitRaster(GDALDataset *poProtoDS,
    1312             :                                   const std::vector<GDALDataType> &aeDT,
    1313             :                                   const std::vector<bool> &abSetNoData,
    1314             :                                   const std::vector<double> &adfNoData,
    1315             :                                   const gdal::TileMatrixSet *poTMS,
    1316             :                                   const std::string &osTMId,
    1317             :                                   const gdal::TileMatrixSet::TileMatrix &oTM,
    1318             :                                   const std::map<CPLString, Limits> &oMapLimits)
    1319             : {
    1320          48 :     int nMatrixWidth = oTM.mMatrixWidth;
    1321          48 :     int nMatrixHeight = oTM.mMatrixHeight;
    1322          48 :     auto oIterLimit = oMapLimits.find(osTMId);
    1323          48 :     if (oIterLimit != oMapLimits.end())
    1324             :     {
    1325          39 :         m_nMinMetaTileCol = oIterLimit->second.min_tile_col;
    1326          39 :         m_nMinMetaTileRow = oIterLimit->second.min_tile_row;
    1327          39 :         nMatrixWidth = oIterLimit->second.max_tile_col - m_nMinMetaTileCol + 1;
    1328          39 :         nMatrixHeight = oIterLimit->second.max_tile_row - m_nMinMetaTileRow + 1;
    1329             :     }
    1330          48 :     m_nMetaTileWidth = oTM.mTileWidth;
    1331          48 :     m_nMetaTileHeight = oTM.mTileHeight;
    1332          48 :     nRasterXSize = nMatrixWidth * m_nMetaTileWidth;
    1333          48 :     nRasterYSize = nMatrixHeight * m_nMetaTileHeight;
    1334             : 
    1335          48 :     if (poProtoDS)
    1336             :     {
    1337         132 :         for (int i = 0; i < poProtoDS->GetRasterCount(); i++)
    1338             :         {
    1339          99 :             auto poProtoBand = poProtoDS->GetRasterBand(i + 1);
    1340          99 :             auto poBand = new STACTARawRasterBand(this, i + 1, poProtoBand);
    1341          99 :             SetBand(i + 1, poBand);
    1342             :         }
    1343             :     }
    1344             :     else
    1345             :     {
    1346         105 :         for (int i = 0; i < static_cast<int>(aeDT.size()); i++)
    1347             :         {
    1348          90 :             auto poBand = new STACTARawRasterBand(this, i + 1, aeDT[i],
    1349          90 :                                                   abSetNoData[i], adfNoData[i]);
    1350          90 :             SetBand(i + 1, poBand);
    1351             :         }
    1352             :     }
    1353             : 
    1354          96 :     CPLString osCRS = poTMS->crs().c_str();
    1355          48 :     if (osCRS == "http://www.opengis.net/def/crs/OGC/1.3/CRS84")
    1356          48 :         osCRS = "EPSG:4326";
    1357          48 :     if (m_oSRS.SetFromUserInput(osCRS) != OGRERR_NONE)
    1358             :     {
    1359           0 :         return false;
    1360             :     }
    1361          48 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1362          96 :     m_adfGeoTransform[0] =
    1363          48 :         oTM.mTopLeftX + m_nMinMetaTileCol * m_nMetaTileWidth * oTM.mResX;
    1364          48 :     m_adfGeoTransform[1] = oTM.mResX;
    1365          96 :     m_adfGeoTransform[3] =
    1366          48 :         oTM.mTopLeftY - m_nMinMetaTileRow * m_nMetaTileHeight * oTM.mResY;
    1367          48 :     m_adfGeoTransform[5] = -oTM.mResY;
    1368          48 :     SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    1369             : 
    1370          48 :     return true;
    1371             : }
    1372             : 
    1373             : /************************************************************************/
    1374             : /*                            GetSpatialRef ()                          */
    1375             : /************************************************************************/
    1376             : 
    1377           3 : const OGRSpatialReference *STACTADataset::GetSpatialRef() const
    1378             : {
    1379           3 :     return nBands == 0 ? nullptr : &m_oSRS;
    1380             : }
    1381             : 
    1382             : /************************************************************************/
    1383             : /*                           GetGeoTransform()                          */
    1384             : /************************************************************************/
    1385             : 
    1386           3 : CPLErr STACTADataset::GetGeoTransform(double *padfGeoTransform)
    1387             : {
    1388           3 :     memcpy(padfGeoTransform, &m_adfGeoTransform[0], 6 * sizeof(double));
    1389           3 :     return nBands == 0 ? CE_Failure : CE_None;
    1390             : }
    1391             : 
    1392             : /************************************************************************/
    1393             : /*                            OpenStatic()                              */
    1394             : /************************************************************************/
    1395             : 
    1396          23 : GDALDataset *STACTADataset::OpenStatic(GDALOpenInfo *poOpenInfo)
    1397             : {
    1398          23 :     if (!Identify(poOpenInfo))
    1399           0 :         return nullptr;
    1400          46 :     auto poDS = std::make_unique<STACTADataset>();
    1401          23 :     if (!poDS->Open(poOpenInfo))
    1402           5 :         return nullptr;
    1403          18 :     return poDS.release();
    1404             : }
    1405             : 
    1406             : /************************************************************************/
    1407             : /*                       GDALRegister_STACTA()                          */
    1408             : /************************************************************************/
    1409             : 
    1410        1682 : void GDALRegister_STACTA()
    1411             : 
    1412             : {
    1413        1682 :     if (GDALGetDriverByName("STACTA") != nullptr)
    1414         301 :         return;
    1415             : 
    1416        1381 :     GDALDriver *poDriver = new GDALDriver();
    1417             : 
    1418        1381 :     poDriver->SetDescription("STACTA");
    1419        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1420        1381 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
    1421        1381 :                               "Spatio-Temporal Asset Catalog Tiled Assets");
    1422        1381 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/stacta.html");
    1423        1381 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "json");
    1424        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1425        1381 :     poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
    1426        1381 :     poDriver->SetMetadataItem(
    1427             :         GDAL_DMD_OPENOPTIONLIST,
    1428             :         "<OpenOptionList>"
    1429             :         "   <Option name='WHOLE_METATILE' type='boolean' "
    1430             :         "description='Whether to download whole metatiles'/>"
    1431             :         "   <Option name='SKIP_MISSING_METATILE' type='boolean' "
    1432             :         "description='Whether to gracefully skip missing metatiles'/>"
    1433        1381 :         "</OpenOptionList>");
    1434             : 
    1435        1381 :     poDriver->pfnOpen = STACTADataset::OpenStatic;
    1436        1381 :     poDriver->pfnIdentify = STACTADataset::Identify;
    1437             : 
    1438        1381 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1439             : }

Generated by: LCOV version 1.14