LCOV - code coverage report
Current view: top level - frmts/stacta - stactadataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 634 811 78.2 %
Date: 2025-09-10 17:48:50 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 :                                         VSIFCloseL(fp);
     530           0 :                                         m_poMasterDS
     531           0 :                                             ->m_bVSICLOUDSubstitutionOK = true;
     532           0 :                                         osURL = std::move(osNewURL);
     533           0 :                                         break;
     534             :                                     }
     535             :                                 }
     536             :                             }
     537             :                         }
     538           0 :                         if (fp == nullptr)
     539             :                         {
     540           0 :                             if (m_poMasterDS->m_bSkipMissingMetaTile)
     541             :                             {
     542           0 :                                 m_poMasterDS->m_oCacheTileDS.insert(osURL,
     543           0 :                                                                     nullptr);
     544           0 :                                 bMissingTile = true;
     545           0 :                                 break;
     546             :                             }
     547           0 :                             CPLError(CE_Failure, CPLE_OpenFailed,
     548             :                                      "Cannot open %s", osURL.c_str());
     549           0 :                             return CE_Failure;
     550             :                         }
     551           0 :                         GByte *pabyBuf = nullptr;
     552           0 :                         vsi_l_offset nSize = 0;
     553           0 :                         if (!VSIIngestFile(fp, nullptr, &pabyBuf, &nSize, -1))
     554             :                         {
     555           0 :                             VSIFCloseL(fp);
     556           0 :                             return CE_Failure;
     557             :                         }
     558           0 :                         VSIFCloseL(fp);
     559             :                         const CPLString osMEMFilename(
     560             :                             VSIMemGenerateHiddenFilename(
     561           0 :                                 std::string("stacta_")
     562           0 :                                     .append(CPLString(osURL)
     563           0 :                                                 .replaceAll("/", "_")
     564           0 :                                                 .replaceAll("\\", "_"))
     565           0 :                                     .c_str()));
     566           0 :                         VSIFCloseL(VSIFileFromMemBuffer(osMEMFilename, pabyBuf,
     567             :                                                         nSize, TRUE));
     568           0 :                         poTileDS = std::unique_ptr<GDALDataset>(
     569             :                             GDALDataset::Open(osMEMFilename,
     570             :                                               GDAL_OF_INTERNAL | GDAL_OF_RASTER,
     571           0 :                                               aosAllowedDrivers.List()));
     572           0 :                         if (poTileDS)
     573           0 :                             poTileDS->MarkSuppressOnClose();
     574             :                         else
     575           0 :                             VSIUnlink(osMEMFilename);
     576             :                     }
     577          33 :                     else if (bDownloadWholeMetaTile ||
     578          15 :                              (!STARTS_WITH(osURL, "http://") &&
     579          13 :                               !STARTS_WITH(osURL, "https://")))
     580             :                     {
     581          16 :                         aosAllowedDrivers.AddString("HTTP");
     582          16 :                         if (m_poMasterDS->m_bSkipMissingMetaTile)
     583           4 :                             CPLPushErrorHandler(CPLQuietErrorHandler);
     584             :                         poTileDS =
     585          32 :                             std::unique_ptr<GDALDataset>(GDALDataset::Open(
     586             :                                 osURL, GDAL_OF_INTERNAL | GDAL_OF_RASTER,
     587          32 :                                 aosAllowedDrivers.List()));
     588          16 :                         if (m_poMasterDS->m_bSkipMissingMetaTile)
     589           4 :                             CPLPopErrorHandler();
     590             :                     }
     591             :                     else
     592             :                     {
     593           2 :                         if (m_poMasterDS->m_bSkipMissingMetaTile)
     594           0 :                             CPLPushErrorHandler(CPLQuietErrorHandler);
     595           4 :                         poTileDS = std::unique_ptr<GDALDataset>(
     596           4 :                             GDALDataset::Open(("/vsicurl/" + osURL).c_str(),
     597             :                                               GDAL_OF_INTERNAL | GDAL_OF_RASTER,
     598           4 :                                               aosAllowedDrivers.List()));
     599           2 :                         if (m_poMasterDS->m_bSkipMissingMetaTile)
     600           0 :                             CPLPopErrorHandler();
     601           2 :                         if (poTileDS == nullptr)
     602             :                         {
     603           0 :                             if (!m_poMasterDS->m_bTriedVSICLOUDSubstitution &&
     604           0 :                                 cpl::starts_with(osURL, "https://"))
     605             :                             {
     606           0 :                                 m_poMasterDS->m_bTriedVSICLOUDSubstitution =
     607             :                                     true;
     608             :                                 std::string osNewURL =
     609           0 :                                     DoVSICLOUDSubstitution(osURL);
     610           0 :                                 if (!osNewURL.empty())
     611             :                                 {
     612           0 :                                     CPLDebug("STACTA", "Retrying with %s",
     613             :                                              osNewURL.c_str());
     614           0 :                                     if (m_poMasterDS->m_bSkipMissingMetaTile)
     615           0 :                                         CPLPushErrorHandler(
     616             :                                             CPLQuietErrorHandler);
     617           0 :                                     poTileDS = std::unique_ptr<GDALDataset>(
     618             :                                         GDALDataset::Open(
     619             :                                             osNewURL.c_str(),
     620             :                                             GDAL_OF_INTERNAL | GDAL_OF_RASTER,
     621           0 :                                             aosAllowedDrivers.List()));
     622           0 :                                     if (m_poMasterDS->m_bSkipMissingMetaTile)
     623           0 :                                         CPLPopErrorHandler();
     624           0 :                                     if (poTileDS)
     625             :                                     {
     626           0 :                                         m_poMasterDS
     627           0 :                                             ->m_bVSICLOUDSubstitutionOK = true;
     628           0 :                                         osURL = std::move(osNewURL);
     629             :                                         m_osURLTemplate =
     630           0 :                                             DoVSICLOUDSubstitution(
     631           0 :                                                 m_osURLTemplate);
     632           0 :                                         break;
     633             :                                     }
     634             :                                 }
     635             :                             }
     636             :                         }
     637             :                     }
     638          18 :                     if (poTileDS == nullptr)
     639             :                     {
     640           5 :                         if (m_poMasterDS->m_bSkipMissingMetaTile)
     641             :                         {
     642           2 :                             m_poMasterDS->m_oCacheTileDS.insert(
     643           2 :                                 osURL, std::move(poTileDS));
     644           2 :                             bMissingTile = true;
     645           2 :                             break;
     646             :                         }
     647           3 :                         CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s",
     648             :                                  osURL.c_str());
     649           3 :                         return CE_Failure;
     650             :                     }
     651          13 :                     ppoTileDS = &m_poMasterDS->m_oCacheTileDS.insert(
     652          13 :                         osURL, std::move(poTileDS));
     653             :                 }
     654          23 :                 std::unique_ptr<GDALDataset> &poTileDS = *ppoTileDS;
     655          23 :                 if (poTileDS == nullptr)
     656             :                 {
     657           0 :                     bMissingTile = true;
     658           0 :                     break;
     659             :                 }
     660             : 
     661             :                 GDALRasterIOExtraArg sExtraArgs;
     662          23 :                 INIT_RASTERIO_EXTRA_ARG(sExtraArgs);
     663          23 :                 if (bRequestFitsInSingleMetaTile)
     664             :                 {
     665           6 :                     sExtraArgs.eResampleAlg = psExtraArg->eResampleAlg;
     666           6 :                     if (psExtraArg->bFloatingPointWindowValidity)
     667             :                     {
     668           3 :                         sExtraArgs.bFloatingPointWindowValidity = true;
     669           3 :                         sExtraArgs.dfXOff =
     670           3 :                             psExtraArg->dfXOff - iX * m_nMetaTileWidth;
     671           3 :                         sExtraArgs.dfYOff =
     672           3 :                             psExtraArg->dfYOff - iY * m_nMetaTileHeight;
     673           3 :                         sExtraArgs.dfXSize = psExtraArg->dfXSize;
     674           3 :                         sExtraArgs.dfYSize = psExtraArg->dfYSize;
     675             :                     }
     676             :                 }
     677          23 :                 CPLDebugOnly("STACTA", "Reading %d,%d,%d,%d in %s", nTileXOff,
     678             :                              nTileYOff, nTileXSize, nTileYSize, osURL.c_str());
     679          23 :                 if (poTileDS->RasterIO(
     680             :                         GF_Read, nTileXOff, nTileYOff, nTileXSize, nTileYSize,
     681          23 :                         static_cast<GByte *>(pData) + nBufXOff * nPixelSpace +
     682          23 :                             nBufYOff * nLineSpace,
     683             :                         nBufXSizeEffective, nBufYSizeEffective, eBufType,
     684             :                         nBandCount, panBandMap, nPixelSpace, nLineSpace,
     685          23 :                         nBandSpace, &sExtraArgs) != CE_None)
     686             :                 {
     687           0 :                     return CE_Failure;
     688             :                 }
     689             :             } while (false);
     690             : 
     691          25 :             if (bMissingTile)
     692             :             {
     693           2 :                 CPLDebugOnly("STACTA", "Missing metatile %s", osURL.c_str());
     694           8 :                 for (int iBand = 0; iBand < nBandCount; iBand++)
     695             :                 {
     696           6 :                     int bHasNoData = FALSE;
     697           6 :                     double dfNodata = GetRasterBand(panBandMap[iBand])
     698           6 :                                           ->GetNoDataValue(&bHasNoData);
     699           6 :                     if (!bHasNoData)
     700           0 :                         dfNodata = 0;
     701        6150 :                     for (int nYBufOff = 0; nYBufOff < nBufYSizeEffective;
     702             :                          nYBufOff++)
     703             :                     {
     704        6144 :                         GByte *pabyDest = static_cast<GByte *>(pData) +
     705        6144 :                                           iBand * nBandSpace +
     706        6144 :                                           nBufXOff * nPixelSpace +
     707        6144 :                                           (nBufYOff + nYBufOff) * nLineSpace;
     708        6144 :                         GDALCopyWords(&dfNodata, GDT_Float64, 0, pabyDest,
     709             :                                       eBufType, static_cast<int>(nPixelSpace),
     710             :                                       nBufXSizeEffective);
     711             :                     }
     712             :                 }
     713             :             }
     714             : 
     715          25 :             if (iX == nMinBlockX)
     716             :             {
     717          32 :                 nBufXOff = m_nMetaTileWidth -
     718          16 :                            std::max(0, nXOff - nMinBlockX * m_nMetaTileWidth);
     719             :             }
     720             :             else
     721             :             {
     722           9 :                 nBufXOff += m_nMetaTileWidth;
     723             :             }
     724             :         }
     725             : 
     726          15 :         if (iY == nMinBlockY)
     727             :         {
     728          30 :             nBufYOff = m_nMetaTileHeight -
     729          15 :                        std::max(0, nYOff - nMinBlockY * m_nMetaTileHeight);
     730             :         }
     731             :         else
     732             :         {
     733           0 :             nBufYOff += m_nMetaTileHeight;
     734             :         }
     735             :     }
     736             : 
     737          15 :     return CE_None;
     738             : }
     739             : 
     740             : /************************************************************************/
     741             : /*                           GetGeoTransform()                          */
     742             : /************************************************************************/
     743             : 
     744           4 : CPLErr STACTARawDataset::GetGeoTransform(GDALGeoTransform &gt) const
     745             : {
     746           4 :     gt = m_gt;
     747           4 :     return CE_None;
     748             : }
     749             : 
     750             : /************************************************************************/
     751             : /*                             Identify()                               */
     752             : /************************************************************************/
     753             : 
     754       57226 : int STACTADataset::Identify(GDALOpenInfo *poOpenInfo)
     755             : {
     756       57226 :     if (STARTS_WITH(poOpenInfo->pszFilename, "STACTA:"))
     757             :     {
     758           6 :         return true;
     759             :     }
     760             : 
     761       57220 :     const bool bIsSingleDriver = poOpenInfo->IsSingleAllowedDriver("STACTA");
     762       57218 :     if (bIsSingleDriver && (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
     763           4 :                             STARTS_WITH(poOpenInfo->pszFilename, "https://")))
     764             :     {
     765           1 :         return true;
     766             :     }
     767             : 
     768       57217 :     if (
     769             : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
     770       57762 :         (!bIsSingleDriver && !poOpenInfo->IsExtensionEqualToCI("json")) ||
     771             : #endif
     772         545 :         poOpenInfo->nHeaderBytes == 0)
     773             :     {
     774       57036 :         return false;
     775             :     }
     776             : 
     777         440 :     for (int i = 0; i < 2; i++)
     778             :     {
     779             :         // TryToIngest() may reallocate pabyHeader, so do not move this
     780             :         // before the loop.
     781         310 :         const char *pszHeader =
     782             :             reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
     783         310 :         while (*pszHeader != 0 &&
     784         310 :                std::isspace(static_cast<unsigned char>(*pszHeader)))
     785           0 :             ++pszHeader;
     786         310 :         if (bIsSingleDriver)
     787             :         {
     788           4 :             return pszHeader[0] == '{';
     789             :         }
     790             : 
     791         306 :         if (strstr(pszHeader, "\"stac_extensions\"") != nullptr &&
     792          74 :             (strstr(pszHeader, "\"tiled-assets\"") != nullptr ||
     793          56 :              strstr(
     794             :                  pszHeader,
     795             :                  "https:\\/\\/stac-extensions.github.io\\/tiled-assets\\/") !=
     796          56 :                  nullptr ||
     797          56 :              strstr(pszHeader,
     798             :                     "https://stac-extensions.github.io/tiled-assets/") !=
     799             :                  nullptr))
     800             :         {
     801          46 :             return true;
     802             :         }
     803             : 
     804         260 :         if (i == 0)
     805             :         {
     806             :             // Should be enough for a STACTA .json file
     807         130 :             poOpenInfo->TryToIngest(32768);
     808             :         }
     809             :     }
     810             : 
     811         130 :     return false;
     812             : }
     813             : 
     814             : /************************************************************************/
     815             : /*                               Open()                                 */
     816             : /************************************************************************/
     817             : 
     818          25 : bool STACTADataset::Open(GDALOpenInfo *poOpenInfo)
     819             : {
     820          50 :     CPLString osFilename(poOpenInfo->pszFilename);
     821          50 :     CPLString osAssetName;
     822          50 :     CPLString osTMS;
     823          25 :     if (STARTS_WITH(poOpenInfo->pszFilename, "STACTA:"))
     824             :     {
     825             :         const CPLStringList aosTokens(CSLTokenizeString2(
     826           3 :             poOpenInfo->pszFilename, ":", CSLT_HONOURSTRINGS));
     827           4 :         if (aosTokens.size() != 2 && aosTokens.size() != 3 &&
     828           1 :             aosTokens.size() != 4)
     829           0 :             return false;
     830           3 :         osFilename = aosTokens[1];
     831           3 :         if (aosTokens.size() >= 3)
     832           3 :             osAssetName = aosTokens[2];
     833           3 :         if (aosTokens.size() == 4)
     834           1 :             osTMS = aosTokens[3];
     835             :     }
     836             : 
     837          50 :     CPLJSONDocument oDoc;
     838          50 :     if (STARTS_WITH(osFilename, "http://") ||
     839          25 :         STARTS_WITH(osFilename, "https://"))
     840             :     {
     841           0 :         if (!oDoc.LoadUrl(osFilename, nullptr))
     842           0 :             return false;
     843             :     }
     844             :     else
     845             :     {
     846          25 :         if (!oDoc.Load(osFilename))
     847           0 :             return false;
     848             :     }
     849          50 :     const auto oRoot = oDoc.GetRoot();
     850          75 :     const auto oProperties = oRoot["properties"];
     851          50 :     if (!oProperties.IsValid() ||
     852          25 :         oProperties.GetType() != CPLJSONObject::Type::Object)
     853             :     {
     854           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing properties");
     855           0 :         return false;
     856             :     }
     857             : 
     858          75 :     const auto oAssetTemplates = oRoot["asset_templates"];
     859          50 :     if (!oAssetTemplates.IsValid() ||
     860          25 :         oAssetTemplates.GetType() != CPLJSONObject::Type::Object)
     861             :     {
     862           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing asset_templates");
     863           0 :         return false;
     864             :     }
     865             : 
     866          50 :     const auto aoAssetTemplates = oAssetTemplates.GetChildren();
     867          25 :     if (aoAssetTemplates.size() == 0)
     868             :     {
     869           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Empty asset_templates");
     870           0 :         return false;
     871             :     }
     872             : 
     873          75 :     const auto oTMSs = oProperties.GetObj("tiles:tile_matrix_sets");
     874          25 :     if (!oTMSs.IsValid() || oTMSs.GetType() != CPLJSONObject::Type::Object)
     875             :     {
     876           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     877             :                  "Missing properties[\"tiles:tile_matrix_sets\"]");
     878           0 :         return false;
     879             :     }
     880          50 :     const auto aoTMSs = oTMSs.GetChildren();
     881          25 :     if (aoTMSs.empty())
     882             :     {
     883           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     884             :                  "Empty properties[\"tiles:tile_matrix_sets\"]");
     885           0 :         return false;
     886             :     }
     887             : 
     888          52 :     if ((aoAssetTemplates.size() >= 2 || aoTMSs.size() >= 2) &&
     889          52 :         osAssetName.empty() && osTMS.empty())
     890             :     {
     891           2 :         int nSDSCount = 0;
     892           5 :         for (const auto &oAssetTemplate : aoAssetTemplates)
     893             :         {
     894           6 :             const CPLString osAssetNameSubDS = oAssetTemplate.GetName();
     895           3 :             const char *pszAssetNameSubDS = osAssetNameSubDS.c_str();
     896           3 :             if (aoTMSs.size() >= 2)
     897             :             {
     898           3 :                 for (const auto &oTMS : aoTMSs)
     899             :                 {
     900           2 :                     const CPLString osTMSSubDS = oTMS.GetName();
     901           2 :                     const char *pszTMSSubDS = osTMSSubDS.c_str();
     902           2 :                     GDALDataset::SetMetadataItem(
     903             :                         CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
     904             :                         CPLSPrintf("STACTA:\"%s\":%s:%s", osFilename.c_str(),
     905             :                                    pszAssetNameSubDS, pszTMSSubDS),
     906             :                         "SUBDATASETS");
     907           2 :                     GDALDataset::SetMetadataItem(
     908             :                         CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
     909             :                         CPLSPrintf("Asset %s, tile matrix set %s",
     910             :                                    pszAssetNameSubDS, pszTMSSubDS),
     911             :                         "SUBDATASETS");
     912           2 :                     nSDSCount++;
     913             :                 }
     914             :             }
     915             :             else
     916             :             {
     917           2 :                 GDALDataset::SetMetadataItem(
     918             :                     CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
     919             :                     CPLSPrintf("STACTA:\"%s\":%s", osFilename.c_str(),
     920             :                                pszAssetNameSubDS),
     921             :                     "SUBDATASETS");
     922           2 :                 GDALDataset::SetMetadataItem(
     923             :                     CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
     924             :                     CPLSPrintf("Asset %s", pszAssetNameSubDS), "SUBDATASETS");
     925           2 :                 nSDSCount++;
     926             :             }
     927             :         }
     928           2 :         return true;
     929             :     }
     930             : 
     931          23 :     if (osAssetName.empty())
     932             :     {
     933          20 :         osAssetName = aoAssetTemplates[0].GetName();
     934             :     }
     935          46 :     const auto oAssetTemplate = oAssetTemplates.GetObj(osAssetName);
     936          46 :     if (!oAssetTemplate.IsValid() ||
     937          23 :         oAssetTemplate.GetType() != CPLJSONObject::Type::Object)
     938             :     {
     939           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     940             :                  "Cannot find asset_templates[\"%s\"]", osAssetName.c_str());
     941           0 :         return false;
     942             :     }
     943             : 
     944          23 :     if (osTMS.empty())
     945             :     {
     946          22 :         osTMS = aoTMSs[0].GetName();
     947             :     }
     948          46 :     const auto oTMS = oTMSs.GetObj(osTMS);
     949          23 :     if (!oTMS.IsValid() || oTMS.GetType() != CPLJSONObject::Type::Object)
     950             :     {
     951           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     952             :                  "Cannot find properties[\"tiles:tile_matrix_sets\"][\"%s\"]",
     953             :                  osTMS.c_str());
     954           0 :         return false;
     955             :     }
     956             : 
     957             :     auto poTMS = gdal::TileMatrixSet::parse(
     958          46 :         oTMS.Format(CPLJSONObject::PrettyFormat::Plain).c_str());
     959          23 :     if (poTMS == nullptr)
     960           0 :         return false;
     961             : 
     962          69 :     CPLString osURLTemplate = oAssetTemplate.GetString("href");
     963          23 :     if (osURLTemplate.empty())
     964             :     {
     965           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     966             :                  "Cannot find asset_templates[\"%s\"][\"href\"]",
     967             :                  osAssetName.c_str());
     968             :     }
     969          23 :     osURLTemplate.replaceAll("{TileMatrixSet}", osTMS);
     970             : 
     971             :     // UPDATE oMapVSIToURIPrefix in apps/gdalalg_raster_tile if updating below
     972             :     const std::map<std::string, std::string> oMapURIPrefixToVSI = {
     973             :         {"s3", "/vsis3/"},
     974             :         {"gs", "/vsigs/"},
     975             :         {"az", "/vsiaz/"},     // Not universally recognized
     976             :         {"azure", "/vsiaz/"},  // Not universally recognized
     977         161 :     };
     978             : 
     979          23 :     if (cpl::starts_with(osURLTemplate, "file://"))
     980             :     {
     981           0 :         osURLTemplate = osURLTemplate.substr(strlen("file://"));
     982             :     }
     983             :     else
     984             :     {
     985          23 :         const auto nPosColonSlashSlash = osURLTemplate.find("://");
     986          23 :         if (nPosColonSlashSlash != std::string::npos)
     987             :         {
     988             :             const auto oIter = oMapURIPrefixToVSI.find(
     989           3 :                 osURLTemplate.substr(0, nPosColonSlashSlash));
     990           3 :             if (oIter != oMapURIPrefixToVSI.end())
     991             :             {
     992           0 :                 osURLTemplate = std::string(oIter->second)
     993           0 :                                     .append(osURLTemplate.substr(
     994           0 :                                         nPosColonSlashSlash + strlen("://")));
     995             :             }
     996             :         }
     997             :     }
     998             : 
     999          43 :     if (!cpl::starts_with(osURLTemplate, "http://") &&
    1000          20 :         !cpl::starts_with(osURLTemplate, "https://"))
    1001             :     {
    1002          20 :         if (STARTS_WITH(osURLTemplate, "./"))
    1003          20 :             osURLTemplate = osURLTemplate.substr(2);
    1004          40 :         osURLTemplate = CPLProjectRelativeFilenameSafe(
    1005          40 :             CPLGetDirnameSafe(osFilename).c_str(), osURLTemplate);
    1006             :     }
    1007             : 
    1008             :     // Parse optional tile matrix set limits
    1009          46 :     std::map<CPLString, Limits> oMapLimits;
    1010          69 :     const auto oTMLinks = oProperties.GetObj("tiles:tile_matrix_links");
    1011          23 :     if (oTMLinks.IsValid())
    1012             :     {
    1013          20 :         if (oTMLinks.GetType() != CPLJSONObject::Type::Object)
    1014             :         {
    1015           0 :             CPLError(
    1016             :                 CE_Failure, CPLE_AppDefined,
    1017             :                 "Invalid type for properties[\"tiles:tile_matrix_links\"]");
    1018           0 :             return false;
    1019             :         }
    1020             : 
    1021          60 :         auto oLimits = oTMLinks[osTMS]["limits"];
    1022          40 :         if (oLimits.IsValid() &&
    1023          20 :             oLimits.GetType() == CPLJSONObject::Type::Object)
    1024             :         {
    1025          76 :             for (const auto &oLimit : oLimits.GetChildren())
    1026             :             {
    1027          56 :                 Limits limits;
    1028          56 :                 limits.min_tile_col = oLimit.GetInteger("min_tile_col");
    1029          56 :                 limits.max_tile_col = oLimit.GetInteger("max_tile_col");
    1030          56 :                 limits.min_tile_row = oLimit.GetInteger("min_tile_row");
    1031          56 :                 limits.max_tile_row = oLimit.GetInteger("max_tile_row");
    1032          56 :                 oMapLimits[oLimit.GetName()] = limits;
    1033             :             }
    1034             :         }
    1035             :     }
    1036          23 :     const auto &tmsList = poTMS->tileMatrixList();
    1037          23 :     if (tmsList.empty())
    1038           0 :         return false;
    1039             : 
    1040          46 :     m_bSkipMissingMetaTile = CPLTestBool(CSLFetchNameValueDef(
    1041          23 :         poOpenInfo->papszOpenOptions, "SKIP_MISSING_METATILE",
    1042             :         CPLGetConfigOption("GDAL_STACTA_SKIP_MISSING_METATILE", "NO")));
    1043             : 
    1044             :     // STAC 1.1 uses bands instead of eo:bands and raster:bands
    1045          69 :     const auto oBands = oAssetTemplate.GetArray("bands");
    1046             : 
    1047             :     // Check if there are both eo:bands and raster:bands extension
    1048             :     // If so, we don't need to fetch a prototype metatile to derive the
    1049             :     // information we need (number of bands, data type and nodata value)
    1050             :     const auto oEoBands =
    1051          66 :         oBands.IsValid() ? oBands : oAssetTemplate.GetArray("eo:bands");
    1052             :     const auto oRasterBands =
    1053          66 :         oBands.IsValid() ? oBands : oAssetTemplate.GetArray("raster:bands");
    1054             : 
    1055          46 :     std::vector<GDALDataType> aeDT;
    1056          46 :     std::vector<double> adfNoData;
    1057          46 :     std::vector<bool> abSetNoData;
    1058          23 :     int nExpectedBandCount = 0;
    1059          23 :     if (oRasterBands.IsValid())
    1060             :     {
    1061          11 :         if (oEoBands.IsValid() && oEoBands.Size() != oRasterBands.Size())
    1062             :         {
    1063           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1064             :                      "Number of bands in eo:bands and raster:bands is not "
    1065             :                      "identical. Ignoring the later");
    1066             :         }
    1067             :         else
    1068             :         {
    1069          10 :             nExpectedBandCount = oRasterBands.Size();
    1070             : 
    1071             :             const struct
    1072             :             {
    1073             :                 const char *pszStacDataType;
    1074             :                 GDALDataType eGDALDataType;
    1075          10 :             } aDataTypeMapping[] = {
    1076             :                 {"int8", GDT_Int8},
    1077             :                 {"int16", GDT_Int16},
    1078             :                 {"int32", GDT_Int32},
    1079             :                 {"int64", GDT_Int64},
    1080             :                 {"uint8", GDT_Byte},
    1081             :                 {"uint16", GDT_UInt16},
    1082             :                 {"uint32", GDT_UInt32},
    1083             :                 {"uint64", GDT_UInt64},
    1084             :                 // float16: 16-bit float; unhandled
    1085             :                 {"float32", GDT_Float32},
    1086             :                 {"float64", GDT_Float64},
    1087             :                 {"cint16", GDT_CInt16},
    1088             :                 {"cint32", GDT_CInt32},
    1089             :                 {"cfloat32", GDT_CFloat32},
    1090             :                 {"cfloat64", GDT_CFloat64},
    1091             :             };
    1092             : 
    1093          42 :             for (int i = 0; i < nExpectedBandCount; ++i)
    1094             :             {
    1095          35 :                 if (oRasterBands[i].GetType() != CPLJSONObject::Type::Object)
    1096             :                 {
    1097           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1098             :                              "Wrong raster:bands[%d]", i);
    1099           3 :                     return false;
    1100             :                 }
    1101             :                 const std::string osDataType =
    1102          68 :                     oRasterBands[i].GetString("data_type");
    1103          34 :                 GDALDataType eDT = GDT_Unknown;
    1104         290 :                 for (const auto &oTuple : aDataTypeMapping)
    1105             :                 {
    1106         288 :                     if (osDataType == oTuple.pszStacDataType)
    1107             :                     {
    1108          32 :                         eDT = oTuple.eGDALDataType;
    1109          32 :                         break;
    1110             :                     }
    1111             :                 }
    1112          34 :                 if (eDT == GDT_Unknown)
    1113             :                 {
    1114           2 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1115             :                              "Wrong raster:bands[%d].data_type = %s", i,
    1116             :                              osDataType.c_str());
    1117           2 :                     return false;
    1118             :                 }
    1119          32 :                 aeDT.push_back(eDT);
    1120             : 
    1121          96 :                 const auto oNoData = oRasterBands[i].GetObj("nodata");
    1122          32 :                 if (oNoData.GetType() == CPLJSONObject::Type::String)
    1123             :                 {
    1124          48 :                     const std::string osNoData = oNoData.ToString();
    1125          16 :                     if (osNoData == "inf")
    1126             :                     {
    1127           5 :                         abSetNoData.push_back(true);
    1128           5 :                         adfNoData.push_back(
    1129           5 :                             std::numeric_limits<double>::infinity());
    1130             :                     }
    1131          11 :                     else if (osNoData == "-inf")
    1132             :                     {
    1133           5 :                         abSetNoData.push_back(true);
    1134           5 :                         adfNoData.push_back(
    1135           5 :                             -std::numeric_limits<double>::infinity());
    1136             :                     }
    1137           6 :                     else if (osNoData == "nan")
    1138             :                     {
    1139           5 :                         abSetNoData.push_back(true);
    1140           5 :                         adfNoData.push_back(
    1141           5 :                             std::numeric_limits<double>::quiet_NaN());
    1142             :                     }
    1143             :                     else
    1144             :                     {
    1145           1 :                         CPLError(CE_Warning, CPLE_AppDefined,
    1146             :                                  "Invalid raster:bands[%d].nodata = %s", i,
    1147             :                                  osNoData.c_str());
    1148           1 :                         abSetNoData.push_back(false);
    1149           1 :                         adfNoData.push_back(
    1150           1 :                             std::numeric_limits<double>::quiet_NaN());
    1151             :                     }
    1152             :                 }
    1153          16 :                 else if (oNoData.GetType() == CPLJSONObject::Type::Integer ||
    1154          29 :                          oNoData.GetType() == CPLJSONObject::Type::Long ||
    1155          13 :                          oNoData.GetType() == CPLJSONObject::Type::Double)
    1156             :                 {
    1157           8 :                     abSetNoData.push_back(true);
    1158           8 :                     adfNoData.push_back(oNoData.ToDouble());
    1159             :                 }
    1160           8 :                 else if (!oNoData.IsValid())
    1161             :                 {
    1162           7 :                     abSetNoData.push_back(false);
    1163           7 :                     adfNoData.push_back(
    1164           7 :                         std::numeric_limits<double>::quiet_NaN());
    1165             :                 }
    1166             :                 else
    1167             :                 {
    1168           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1169             :                              "Invalid raster:bands[%d].nodata", i);
    1170           1 :                     abSetNoData.push_back(false);
    1171           1 :                     adfNoData.push_back(
    1172           1 :                         std::numeric_limits<double>::quiet_NaN());
    1173             :                 }
    1174             :             }
    1175             : 
    1176           7 :             CPLAssert(aeDT.size() == abSetNoData.size());
    1177           7 :             CPLAssert(adfNoData.size() == abSetNoData.size());
    1178             :         }
    1179             :     }
    1180             : 
    1181          20 :     std::unique_ptr<GDALDataset> poProtoDS;
    1182          20 :     if (aeDT.empty())
    1183             :     {
    1184          13 :         for (int i = 0; i < static_cast<int>(tmsList.size()); i++)
    1185             :         {
    1186             :             // Open a metatile to get mostly its band data type
    1187          13 :             int nProtoTileCol = 0;
    1188          13 :             int nProtoTileRow = 0;
    1189          13 :             auto oIterLimit = oMapLimits.find(tmsList[i].mId);
    1190          13 :             if (oIterLimit != oMapLimits.end())
    1191             :             {
    1192          10 :                 nProtoTileCol = oIterLimit->second.min_tile_col;
    1193          10 :                 nProtoTileRow = oIterLimit->second.min_tile_row;
    1194             :             }
    1195             :             const CPLString osURL =
    1196          13 :                 CPLString(osURLTemplate)
    1197          26 :                     .replaceAll("{TileMatrix}", tmsList[i].mId)
    1198          26 :                     .replaceAll("{TileRow}", CPLSPrintf("%d", nProtoTileRow))
    1199          26 :                     .replaceAll("{TileCol}", CPLSPrintf("%d", nProtoTileCol));
    1200          38 :             CPLString osProtoDSName = (STARTS_WITH(osURL, "http://") ||
    1201          12 :                                        STARTS_WITH(osURL, "https://"))
    1202          26 :                                           ? CPLString("/vsicurl/" + osURL)
    1203          26 :                                           : osURL;
    1204             :             CPLConfigOptionSetter oSetter("GDAL_DISABLE_READDIR_ON_OPEN",
    1205             :                                           "EMPTY_DIR",
    1206          13 :                                           /* bSetOnlyIfUndefined = */ true);
    1207          13 :             if (m_bSkipMissingMetaTile)
    1208           2 :                 CPLPushErrorHandler(CPLQuietErrorHandler);
    1209          13 :             poProtoDS.reset(GDALDataset::Open(osProtoDSName.c_str(),
    1210             :                                               GDAL_OF_RASTER,
    1211          26 :                                               GetAllowedDrivers().List()));
    1212          13 :             if (m_bSkipMissingMetaTile)
    1213           2 :                 CPLPopErrorHandler();
    1214          13 :             if (poProtoDS != nullptr)
    1215             :             {
    1216          11 :                 break;
    1217             :             }
    1218             : 
    1219           4 :             if (!m_bTriedVSICLOUDSubstitution &&
    1220           2 :                 cpl::starts_with(osURL, "https://"))
    1221             :             {
    1222           0 :                 m_bTriedVSICLOUDSubstitution = true;
    1223           0 :                 std::string osNewURL = DoVSICLOUDSubstitution(osURL);
    1224           0 :                 if (!osNewURL.empty())
    1225             :                 {
    1226           0 :                     CPLDebug("STACTA", "Retrying with %s", osNewURL.c_str());
    1227           0 :                     if (m_bSkipMissingMetaTile)
    1228           0 :                         CPLPushErrorHandler(CPLQuietErrorHandler);
    1229           0 :                     poProtoDS.reset(
    1230             :                         GDALDataset::Open(osNewURL.c_str(), GDAL_OF_RASTER,
    1231           0 :                                           GetAllowedDrivers().List()));
    1232           0 :                     if (m_bSkipMissingMetaTile)
    1233           0 :                         CPLPopErrorHandler();
    1234           0 :                     if (poProtoDS != nullptr)
    1235             :                     {
    1236           0 :                         osURLTemplate = DoVSICLOUDSubstitution(osURLTemplate);
    1237           0 :                         break;
    1238             :                     }
    1239             :                 }
    1240             :             }
    1241             : 
    1242           2 :             if (!m_bSkipMissingMetaTile)
    1243             :             {
    1244           2 :                 CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s",
    1245             :                          osURL.c_str());
    1246           2 :                 return false;
    1247             :             }
    1248             :         }
    1249          11 :         if (poProtoDS == nullptr)
    1250             :         {
    1251           0 :             if (m_bSkipMissingMetaTile)
    1252             :             {
    1253           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1254             :                          "Cannot find prototype dataset");
    1255           0 :                 return false;
    1256             :             }
    1257             :         }
    1258             :         else
    1259             :         {
    1260          11 :             nExpectedBandCount = poProtoDS->GetRasterCount();
    1261             :         }
    1262             :     }
    1263             : 
    1264             :     // Iterate over tile matrices to create corresponding STACTARawDataset
    1265             :     // objects
    1266          68 :     for (int i = static_cast<int>(tmsList.size() - 1); i >= 0; i--)
    1267             :     {
    1268          50 :         const auto &oTM = tmsList[i];
    1269          50 :         int nMatrixWidth = oTM.mMatrixWidth;
    1270          50 :         int nMatrixHeight = oTM.mMatrixHeight;
    1271          50 :         auto oIterLimit = oMapLimits.find(tmsList[i].mId);
    1272          50 :         if (oIterLimit != oMapLimits.end())
    1273             :         {
    1274          41 :             nMatrixWidth = oIterLimit->second.max_tile_col -
    1275          41 :                            oIterLimit->second.min_tile_col + 1;
    1276          41 :             nMatrixHeight = oIterLimit->second.max_tile_row -
    1277          41 :                             oIterLimit->second.min_tile_row + 1;
    1278             :         }
    1279          50 :         if (nMatrixWidth <= 0 || oTM.mTileWidth > INT_MAX / nMatrixWidth ||
    1280          50 :             nMatrixHeight <= 0 || oTM.mTileHeight > INT_MAX / nMatrixHeight)
    1281             :         {
    1282           0 :             continue;
    1283             :         }
    1284          50 :         auto poRawDS = std::make_unique<STACTARawDataset>();
    1285         100 :         if (!poRawDS->InitRaster(poProtoDS.get(), aeDT, abSetNoData, adfNoData,
    1286          50 :                                  poTMS.get(), tmsList[i].mId, oTM, oMapLimits))
    1287             :         {
    1288           0 :             return false;
    1289             :         }
    1290          50 :         poRawDS->m_osURLTemplate = osURLTemplate;
    1291          50 :         poRawDS->m_osURLTemplate.replaceAll("{TileMatrix}", tmsList[i].mId);
    1292          50 :         poRawDS->m_poMasterDS = this;
    1293             : 
    1294          50 :         if (m_poDS == nullptr)
    1295             :         {
    1296          18 :             nRasterXSize = poRawDS->GetRasterXSize();
    1297          18 :             nRasterYSize = poRawDS->GetRasterYSize();
    1298          18 :             m_oSRS = poRawDS->m_oSRS;
    1299          18 :             m_gt = poRawDS->m_gt;
    1300          18 :             m_poDS = std::move(poRawDS);
    1301             :         }
    1302             :         else
    1303             :         {
    1304          32 :             const double dfMinX = m_gt[0];
    1305          32 :             const double dfMaxX = m_gt[0] + GetRasterXSize() * m_gt[1];
    1306          32 :             const double dfMaxY = m_gt[3];
    1307          32 :             const double dfMinY = m_gt[3] + GetRasterYSize() * m_gt[5];
    1308             : 
    1309          32 :             const double dfOvrMinX = poRawDS->m_gt[0];
    1310             :             const double dfOvrMaxX =
    1311          32 :                 poRawDS->m_gt[0] + poRawDS->GetRasterXSize() * poRawDS->m_gt[1];
    1312          32 :             const double dfOvrMaxY = poRawDS->m_gt[3];
    1313             :             const double dfOvrMinY =
    1314          32 :                 poRawDS->m_gt[3] + poRawDS->GetRasterYSize() * poRawDS->m_gt[5];
    1315             : 
    1316          32 :             if (fabs(dfMinX - dfOvrMinX) < 1e-10 * fabs(dfMinX) &&
    1317          30 :                 fabs(dfMinY - dfOvrMinY) < 1e-10 * fabs(dfMinY) &&
    1318          30 :                 fabs(dfMaxX - dfOvrMaxX) < 1e-10 * fabs(dfMaxX) &&
    1319          30 :                 fabs(dfMaxY - dfOvrMaxY) < 1e-10 * fabs(dfMaxY))
    1320             :             {
    1321          30 :                 m_apoOverviewDS.emplace_back(std::move(poRawDS));
    1322             :             }
    1323             :             else
    1324             :             {
    1325             :                 // If this zoom level doesn't share the same origin and extent
    1326             :                 // as the most resoluted one, then subset it
    1327           2 :                 CPLStringList aosOptions;
    1328           2 :                 aosOptions.AddString("-of");
    1329           2 :                 aosOptions.AddString("VRT");
    1330           2 :                 aosOptions.AddString("-projwin");
    1331           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", dfMinX));
    1332           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
    1333           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
    1334           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
    1335             :                 auto psOptions =
    1336           2 :                     GDALTranslateOptionsNew(aosOptions.List(), nullptr);
    1337             :                 auto hDS =
    1338           2 :                     GDALTranslate("", GDALDataset::ToHandle(poRawDS.get()),
    1339             :                                   psOptions, nullptr);
    1340           2 :                 GDALTranslateOptionsFree(psOptions);
    1341           2 :                 if (hDS == nullptr)
    1342           0 :                     continue;
    1343           2 :                 m_apoIntermediaryDS.emplace_back(std::move(poRawDS));
    1344           2 :                 m_apoOverviewDS.emplace_back(GDALDataset::FromHandle(hDS));
    1345             :             }
    1346             :         }
    1347             :     }
    1348          18 :     if (m_poDS == nullptr)
    1349             :     {
    1350           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find valid tile matrix");
    1351           0 :         return false;
    1352             :     }
    1353             : 
    1354             :     // Create main bands
    1355          83 :     for (int i = 0; i < m_poDS->GetRasterCount(); i++)
    1356             :     {
    1357          65 :         auto poSrcBand = m_poDS->GetRasterBand(i + 1);
    1358          65 :         auto poBand = new STACTARasterBand(this, i + 1, poSrcBand);
    1359          65 :         if (oEoBands.IsValid() && oEoBands.Size() == nExpectedBandCount)
    1360             :         {
    1361             :             // Set band metadata
    1362          50 :             if (oEoBands[i].GetType() == CPLJSONObject::Type::Object)
    1363             :             {
    1364         142 :                 for (const auto &oItem : oEoBands[i].GetChildren())
    1365             :                 {
    1366          92 :                     if (oBands.IsValid())
    1367             :                     {
    1368             :                         // STAC 1.1
    1369          26 :                         if (STARTS_WITH(oItem.GetName().c_str(), "eo:"))
    1370             :                         {
    1371           2 :                             poBand->GDALRasterBand::SetMetadataItem(
    1372           2 :                                 oItem.GetName().c_str() + strlen("eo:"),
    1373           2 :                                 oItem.ToString().c_str());
    1374             :                         }
    1375          42 :                         else if (oItem.GetName() != "data_type" &&
    1376          54 :                                  oItem.GetName() != "nodata" &&
    1377          48 :                                  oItem.GetName() != "unit" &&
    1378          46 :                                  oItem.GetName() != "raster:scale" &&
    1379          77 :                                  oItem.GetName() != "raster:offset" &&
    1380          34 :                                  oItem.GetName() != "raster:bits_per_sample")
    1381             :                         {
    1382          16 :                             poBand->GDALRasterBand::SetMetadataItem(
    1383          16 :                                 oItem.GetName().c_str(),
    1384          16 :                                 oItem.ToString().c_str());
    1385             :                         }
    1386             :                     }
    1387             :                     else
    1388             :                     {
    1389             :                         // STAC 1.0
    1390         132 :                         poBand->GDALRasterBand::SetMetadataItem(
    1391         198 :                             oItem.GetName().c_str(), oItem.ToString().c_str());
    1392             :                     }
    1393             :                 }
    1394             :             }
    1395             :         }
    1396         227 :         if (oRasterBands.IsValid() &&
    1397          97 :             oRasterBands.Size() == nExpectedBandCount &&
    1398          97 :             oRasterBands[i].GetType() == CPLJSONObject::Type::Object)
    1399             :         {
    1400          32 :             poBand->m_osUnit = oRasterBands[i].GetString("unit");
    1401          64 :             const double dfScale = oRasterBands[i].GetDouble(
    1402          32 :                 oBands.IsValid() ? "raster:scale" : "scale");
    1403          32 :             if (dfScale != 0)
    1404           5 :                 poBand->m_dfScale = dfScale;
    1405          64 :             poBand->m_dfOffset = oRasterBands[i].GetDouble(
    1406          32 :                 oBands.IsValid() ? "raster:offset" : "offset");
    1407          64 :             const int nBitsPerSample = oRasterBands[i].GetInteger(
    1408          32 :                 oBands.IsValid() ? "raster:bits_per_sample"
    1409             :                                  : "bits_per_sample");
    1410           5 :             if (((nBitsPerSample >= 1 && nBitsPerSample <= 7) &&
    1411          37 :                  poBand->GetRasterDataType() == GDT_Byte) ||
    1412           0 :                 ((nBitsPerSample >= 9 && nBitsPerSample <= 15) &&
    1413           0 :                  poBand->GetRasterDataType() == GDT_UInt16))
    1414             :             {
    1415           5 :                 poBand->GDALRasterBand::SetMetadataItem(
    1416             :                     "NBITS", CPLSPrintf("%d", nBitsPerSample),
    1417             :                     "IMAGE_STRUCTURE");
    1418             :             }
    1419             :         }
    1420          65 :         SetBand(i + 1, poBand);
    1421             :     }
    1422             : 
    1423             :     // Set dataset metadata
    1424         127 :     for (const auto &oItem : oProperties.GetChildren())
    1425             :     {
    1426         218 :         const auto osName = oItem.GetName();
    1427         203 :         if (osName != "tiles:tile_matrix_links" &&
    1428         203 :             osName != "tiles:tile_matrix_sets" &&
    1429          76 :             !cpl::starts_with(osName, "proj:"))
    1430             :         {
    1431          70 :             GDALDataset::SetMetadataItem(osName.c_str(),
    1432         140 :                                          oItem.ToString().c_str());
    1433             :         }
    1434             :     }
    1435             : 
    1436          18 :     if (poProtoDS)
    1437             :     {
    1438             :         const char *pszInterleave =
    1439          11 :             poProtoDS->GetMetadataItem("INTERLEAVE", "IMAGE_STRUCTURE");
    1440          11 :         GDALDataset::SetMetadataItem("INTERLEAVE",
    1441             :                                      pszInterleave ? pszInterleave : "PIXEL",
    1442             :                                      "IMAGE_STRUCTURE");
    1443             :     }
    1444             :     else
    1445             :     {
    1446             :         // A bit bold to assume that, but that should be a reasonable
    1447             :         // setting
    1448           7 :         GDALDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    1449             :     }
    1450             : 
    1451          18 :     m_bDownloadWholeMetaTile = CPLTestBool(CSLFetchNameValueDef(
    1452          18 :         poOpenInfo->papszOpenOptions, "WHOLE_METATILE", "NO"));
    1453             : 
    1454          18 :     return true;
    1455             : }
    1456             : 
    1457             : /************************************************************************/
    1458             : /*                          ~STACTADataset()                            */
    1459             : /************************************************************************/
    1460             : 
    1461          50 : STACTADataset::~STACTADataset()
    1462             : {
    1463          25 :     m_poDS.reset();
    1464          25 :     m_apoOverviewDS.clear();
    1465          25 :     m_apoIntermediaryDS.clear();
    1466          50 : }
    1467             : 
    1468             : /************************************************************************/
    1469             : /*                          FlushCache()                                */
    1470             : /************************************************************************/
    1471             : 
    1472           0 : CPLErr STACTADataset::FlushCache(bool bAtClosing)
    1473             : {
    1474           0 :     m_oCacheTileDS.clear();
    1475           0 :     return GDALDataset::FlushCache(bAtClosing);
    1476             : }
    1477             : 
    1478             : /************************************************************************/
    1479             : /*                            InitRaster()                              */
    1480             : /************************************************************************/
    1481             : 
    1482          50 : bool STACTARawDataset::InitRaster(GDALDataset *poProtoDS,
    1483             :                                   const std::vector<GDALDataType> &aeDT,
    1484             :                                   const std::vector<bool> &abSetNoData,
    1485             :                                   const std::vector<double> &adfNoData,
    1486             :                                   const gdal::TileMatrixSet *poTMS,
    1487             :                                   const std::string &osTMId,
    1488             :                                   const gdal::TileMatrixSet::TileMatrix &oTM,
    1489             :                                   const std::map<CPLString, Limits> &oMapLimits)
    1490             : {
    1491          50 :     int nMatrixWidth = oTM.mMatrixWidth;
    1492          50 :     int nMatrixHeight = oTM.mMatrixHeight;
    1493          50 :     auto oIterLimit = oMapLimits.find(osTMId);
    1494          50 :     if (oIterLimit != oMapLimits.end())
    1495             :     {
    1496          41 :         m_nMinMetaTileCol = oIterLimit->second.min_tile_col;
    1497          41 :         m_nMinMetaTileRow = oIterLimit->second.min_tile_row;
    1498          41 :         nMatrixWidth = oIterLimit->second.max_tile_col - m_nMinMetaTileCol + 1;
    1499          41 :         nMatrixHeight = oIterLimit->second.max_tile_row - m_nMinMetaTileRow + 1;
    1500             :     }
    1501          50 :     m_nMetaTileWidth = oTM.mTileWidth;
    1502          50 :     m_nMetaTileHeight = oTM.mTileHeight;
    1503          50 :     nRasterXSize = nMatrixWidth * m_nMetaTileWidth;
    1504          50 :     nRasterYSize = nMatrixHeight * m_nMetaTileHeight;
    1505             : 
    1506          50 :     if (poProtoDS)
    1507             :     {
    1508         132 :         for (int i = 0; i < poProtoDS->GetRasterCount(); i++)
    1509             :         {
    1510          99 :             auto poProtoBand = poProtoDS->GetRasterBand(i + 1);
    1511          99 :             auto poBand = new STACTARawRasterBand(this, i + 1, poProtoBand);
    1512          99 :             SetBand(i + 1, poBand);
    1513             :         }
    1514             :     }
    1515             :     else
    1516             :     {
    1517         109 :         for (int i = 0; i < static_cast<int>(aeDT.size()); i++)
    1518             :         {
    1519          92 :             auto poBand = new STACTARawRasterBand(this, i + 1, aeDT[i],
    1520          92 :                                                   abSetNoData[i], adfNoData[i]);
    1521          92 :             SetBand(i + 1, poBand);
    1522             :         }
    1523             :     }
    1524             : 
    1525         100 :     CPLString osCRS = poTMS->crs().c_str();
    1526          50 :     if (osCRS == "http://www.opengis.net/def/crs/OGC/1.3/CRS84")
    1527          48 :         osCRS = "EPSG:4326";
    1528          50 :     if (m_oSRS.SetFromUserInput(osCRS) != OGRERR_NONE)
    1529             :     {
    1530           0 :         return false;
    1531             :     }
    1532          50 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1533          50 :     m_gt[0] = oTM.mTopLeftX + m_nMinMetaTileCol * m_nMetaTileWidth * oTM.mResX;
    1534          50 :     m_gt[1] = oTM.mResX;
    1535          50 :     m_gt[3] = oTM.mTopLeftY - m_nMinMetaTileRow * m_nMetaTileHeight * oTM.mResY;
    1536          50 :     m_gt[5] = -oTM.mResY;
    1537          50 :     SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    1538             : 
    1539          50 :     return true;
    1540             : }
    1541             : 
    1542             : /************************************************************************/
    1543             : /*                            GetSpatialRef ()                          */
    1544             : /************************************************************************/
    1545             : 
    1546           5 : const OGRSpatialReference *STACTADataset::GetSpatialRef() const
    1547             : {
    1548           5 :     return nBands == 0 ? nullptr : &m_oSRS;
    1549             : }
    1550             : 
    1551             : /************************************************************************/
    1552             : /*                           GetGeoTransform()                          */
    1553             : /************************************************************************/
    1554             : 
    1555           5 : CPLErr STACTADataset::GetGeoTransform(GDALGeoTransform &gt) const
    1556             : {
    1557           5 :     gt = m_gt;
    1558           5 :     return nBands == 0 ? CE_Failure : CE_None;
    1559             : }
    1560             : 
    1561             : /************************************************************************/
    1562             : /*                            OpenStatic()                              */
    1563             : /************************************************************************/
    1564             : 
    1565          25 : GDALDataset *STACTADataset::OpenStatic(GDALOpenInfo *poOpenInfo)
    1566             : {
    1567          25 :     if (!Identify(poOpenInfo))
    1568           0 :         return nullptr;
    1569          50 :     auto poDS = std::make_unique<STACTADataset>();
    1570          25 :     if (!poDS->Open(poOpenInfo))
    1571           5 :         return nullptr;
    1572          20 :     return poDS.release();
    1573             : }
    1574             : 
    1575             : /************************************************************************/
    1576             : /*                       GDALRegister_STACTA()                          */
    1577             : /************************************************************************/
    1578             : 
    1579        2024 : void GDALRegister_STACTA()
    1580             : 
    1581             : {
    1582        2024 :     if (GDALGetDriverByName("STACTA") != nullptr)
    1583         283 :         return;
    1584             : 
    1585        1741 :     GDALDriver *poDriver = new GDALDriver();
    1586             : 
    1587        1741 :     poDriver->SetDescription("STACTA");
    1588        1741 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1589        1741 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
    1590        1741 :                               "Spatio-Temporal Asset Catalog Tiled Assets");
    1591        1741 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/stacta.html");
    1592        1741 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "json");
    1593        1741 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1594        1741 :     poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
    1595        1741 :     poDriver->SetMetadataItem(
    1596             :         GDAL_DMD_OPENOPTIONLIST,
    1597             :         "<OpenOptionList>"
    1598             :         "   <Option name='WHOLE_METATILE' type='boolean' "
    1599             :         "description='Whether to download whole metatiles'/>"
    1600             :         "   <Option name='SKIP_MISSING_METATILE' type='boolean' "
    1601             :         "description='Whether to gracefully skip missing metatiles'/>"
    1602        1741 :         "</OpenOptionList>");
    1603             : 
    1604        1741 :     poDriver->pfnOpen = STACTADataset::OpenStatic;
    1605        1741 :     poDriver->pfnIdentify = STACTADataset::Identify;
    1606             : 
    1607        1741 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1608             : }

Generated by: LCOV version 1.14