LCOV - code coverage report
Current view: top level - frmts/stacta - stactadataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 634 810 78.3 %
Date: 2025-08-19 18:03:11 Functions: 22 26 84.6 %

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

Generated by: LCOV version 1.14