LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v3_array.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 627 744 84.3 %
Date: 2025-01-18 12:42:00 Functions: 23 25 92.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Zarr driver
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_vsi_virtual.h"
      14             : #include "gdal_thread_pool.h"
      15             : #include "zarr.h"
      16             : 
      17             : #include <algorithm>
      18             : #include <cassert>
      19             : #include <cmath>
      20             : #include <cstdlib>
      21             : #include <limits>
      22             : #include <map>
      23             : #include <set>
      24             : 
      25             : /************************************************************************/
      26             : /*                       ZarrV3Array::ZarrV3Array()                     */
      27             : /************************************************************************/
      28             : 
      29         270 : ZarrV3Array::ZarrV3Array(
      30             :     const std::shared_ptr<ZarrSharedResource> &poSharedResource,
      31             :     const std::string &osParentName, const std::string &osName,
      32             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
      33             :     const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
      34         270 :     const std::vector<GUInt64> &anBlockSize)
      35             :     : GDALAbstractMDArray(osParentName, osName),
      36             :       ZarrArray(poSharedResource, osParentName, osName, aoDims, oType,
      37         270 :                 aoDtypeElts, anBlockSize)
      38             : {
      39         270 : }
      40             : 
      41             : /************************************************************************/
      42             : /*                         ZarrV3Array::Create()                        */
      43             : /************************************************************************/
      44             : 
      45             : std::shared_ptr<ZarrV3Array>
      46         270 : ZarrV3Array::Create(const std::shared_ptr<ZarrSharedResource> &poSharedResource,
      47             :                     const std::string &osParentName, const std::string &osName,
      48             :                     const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
      49             :                     const GDALExtendedDataType &oType,
      50             :                     const std::vector<DtypeElt> &aoDtypeElts,
      51             :                     const std::vector<GUInt64> &anBlockSize)
      52             : {
      53             :     auto arr = std::shared_ptr<ZarrV3Array>(
      54             :         new ZarrV3Array(poSharedResource, osParentName, osName, aoDims, oType,
      55         540 :                         aoDtypeElts, anBlockSize));
      56         270 :     if (arr->m_nTotalTileCount == 0)
      57           1 :         return nullptr;
      58         269 :     arr->SetSelf(arr);
      59             : 
      60         269 :     return arr;
      61             : }
      62             : 
      63             : /************************************************************************/
      64             : /*                             ~ZarrV3Array()                           */
      65             : /************************************************************************/
      66             : 
      67         540 : ZarrV3Array::~ZarrV3Array()
      68             : {
      69         270 :     ZarrV3Array::Flush();
      70         540 : }
      71             : 
      72             : /************************************************************************/
      73             : /*                                Flush()                               */
      74             : /************************************************************************/
      75             : 
      76         656 : void ZarrV3Array::Flush()
      77             : {
      78         656 :     if (!m_bValid)
      79           3 :         return;
      80             : 
      81         653 :     ZarrV3Array::FlushDirtyTile();
      82             : 
      83         653 :     if (!m_aoDims.empty())
      84             :     {
      85        1357 :         for (const auto &poDim : m_aoDims)
      86             :         {
      87             :             const auto poZarrDim =
      88         831 :                 dynamic_cast<const ZarrDimension *>(poDim.get());
      89         831 :             if (poZarrDim && poZarrDim->IsXArrayDimension())
      90             :             {
      91         774 :                 if (poZarrDim->IsModified())
      92           4 :                     m_bDefinitionModified = true;
      93             :             }
      94             :             else
      95             :             {
      96          57 :                 break;
      97             :             }
      98             :         }
      99             :     }
     100             : 
     101        1306 :     CPLJSONObject oAttrs;
     102        1268 :     if (m_oAttrGroup.IsModified() || m_bUnitModified || m_bOffsetModified ||
     103        1268 :         m_bScaleModified || m_bSRSModified)
     104             :     {
     105          38 :         m_bNew = false;
     106             : 
     107          38 :         oAttrs = SerializeSpecialAttributes();
     108             : 
     109          38 :         m_bDefinitionModified = true;
     110             :     }
     111             : 
     112         653 :     if (m_bDefinitionModified)
     113             :     {
     114         168 :         Serialize(oAttrs);
     115         168 :         m_bDefinitionModified = false;
     116             :     }
     117             : }
     118             : 
     119             : /************************************************************************/
     120             : /*                    ZarrV3Array::Serialize()                          */
     121             : /************************************************************************/
     122             : 
     123         168 : void ZarrV3Array::Serialize(const CPLJSONObject &oAttrs)
     124             : {
     125         336 :     CPLJSONDocument oDoc;
     126         336 :     CPLJSONObject oRoot = oDoc.GetRoot();
     127             : 
     128         168 :     oRoot.Add("zarr_format", 3);
     129         168 :     oRoot.Add("node_type", "array");
     130             : 
     131         336 :     CPLJSONArray oShape;
     132         373 :     for (const auto &poDim : m_aoDims)
     133             :     {
     134         205 :         oShape.Add(static_cast<GInt64>(poDim->GetSize()));
     135             :     }
     136         168 :     oRoot.Add("shape", oShape);
     137             : 
     138         168 :     oRoot.Add("data_type", m_dtype.ToString());
     139             : 
     140             :     {
     141         336 :         CPLJSONObject oChunkGrid;
     142         168 :         oRoot.Add("chunk_grid", oChunkGrid);
     143         168 :         oChunkGrid.Add("name", "regular");
     144         336 :         CPLJSONObject oConfiguration;
     145         168 :         oChunkGrid.Add("configuration", oConfiguration);
     146         168 :         CPLJSONArray oChunks;
     147         373 :         for (const auto nBlockSize : m_anBlockSize)
     148             :         {
     149         205 :             oChunks.Add(static_cast<GInt64>(nBlockSize));
     150             :         }
     151         168 :         oConfiguration.Add("chunk_shape", oChunks);
     152             :     }
     153             : 
     154             :     {
     155         336 :         CPLJSONObject oChunkKeyEncoding;
     156         168 :         oRoot.Add("chunk_key_encoding", oChunkKeyEncoding);
     157         168 :         oChunkKeyEncoding.Add("name", m_bV2ChunkKeyEncoding ? "v2" : "default");
     158         168 :         CPLJSONObject oConfiguration;
     159         168 :         oChunkKeyEncoding.Add("configuration", oConfiguration);
     160         168 :         oConfiguration.Add("separator", m_osDimSeparator);
     161             :     }
     162             : 
     163         168 :     if (m_pabyNoData == nullptr)
     164             :     {
     165         281 :         if (m_oType.GetNumericDataType() == GDT_Float32 ||
     166         137 :             m_oType.GetNumericDataType() == GDT_Float64)
     167             :         {
     168          18 :             oRoot.Add("fill_value", "NaN");
     169             :         }
     170             :         else
     171             :         {
     172         126 :             oRoot.AddNull("fill_value");
     173             :         }
     174             :     }
     175             :     else
     176             :     {
     177          40 :         if (m_oType.GetNumericDataType() == GDT_CFloat32 ||
     178          16 :             m_oType.GetNumericDataType() == GDT_CFloat64)
     179             :         {
     180             :             double adfNoDataValue[2];
     181          16 :             GDALCopyWords(m_pabyNoData, m_oType.GetNumericDataType(), 0,
     182             :                           adfNoDataValue, GDT_CFloat64, 0, 1);
     183          16 :             CPLJSONArray oArray;
     184          48 :             for (int i = 0; i < 2; ++i)
     185             :             {
     186          32 :                 if (std::isnan(adfNoDataValue[i]))
     187           6 :                     oArray.Add("NaN");
     188          26 :                 else if (adfNoDataValue[i] ==
     189          26 :                          std::numeric_limits<double>::infinity())
     190           4 :                     oArray.Add("Infinity");
     191          44 :                 else if (adfNoDataValue[i] ==
     192          22 :                          -std::numeric_limits<double>::infinity())
     193           4 :                     oArray.Add("-Infinity");
     194             :                 else
     195          18 :                     oArray.Add(adfNoDataValue[i]);
     196             :             }
     197          16 :             oRoot.Add("fill_value", oArray);
     198             :         }
     199             :         else
     200             :         {
     201           8 :             SerializeNumericNoData(oRoot);
     202             :         }
     203             :     }
     204             : 
     205         168 :     if (m_poCodecs)
     206             :     {
     207          66 :         oRoot.Add("codecs", m_poCodecs->GetJSon());
     208             :     }
     209             : 
     210         168 :     oRoot.Add("attributes", oAttrs);
     211             : 
     212             :     // Set dimension_names
     213         168 :     if (!m_aoDims.empty())
     214             :     {
     215         288 :         CPLJSONArray oDimensions;
     216         333 :         for (const auto &poDim : m_aoDims)
     217             :         {
     218             :             const auto poZarrDim =
     219         205 :                 dynamic_cast<const ZarrDimension *>(poDim.get());
     220         205 :             if (poZarrDim && poZarrDim->IsXArrayDimension())
     221             :             {
     222         189 :                 oDimensions.Add(poDim->GetName());
     223             :             }
     224             :             else
     225             :             {
     226          16 :                 oDimensions = CPLJSONArray();
     227          16 :                 break;
     228             :             }
     229             :         }
     230         144 :         if (oDimensions.Size() > 0)
     231             :         {
     232         128 :             oRoot.Add("dimension_names", oDimensions);
     233             :         }
     234             :     }
     235             : 
     236             :     // TODO: codecs
     237             : 
     238         168 :     oDoc.Save(m_osFilename);
     239         168 : }
     240             : 
     241             : /************************************************************************/
     242             : /*                  ZarrV3Array::NeedDecodedBuffer()                    */
     243             : /************************************************************************/
     244             : 
     245       10956 : bool ZarrV3Array::NeedDecodedBuffer() const
     246             : {
     247       21904 :     for (const auto &elt : m_aoDtypeElts)
     248             :     {
     249       10935 :         if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative)
     250             :         {
     251           0 :             return true;
     252             :         }
     253             :     }
     254       10950 :     return false;
     255             : }
     256             : 
     257             : /************************************************************************/
     258             : /*               ZarrV3Array::AllocateWorkingBuffers()                  */
     259             : /************************************************************************/
     260             : 
     261         153 : bool ZarrV3Array::AllocateWorkingBuffers() const
     262             : {
     263         153 :     if (m_bAllocateWorkingBuffersDone)
     264           5 :         return m_bWorkingBuffersOK;
     265             : 
     266         148 :     m_bAllocateWorkingBuffersDone = true;
     267             : 
     268         148 :     size_t nSizeNeeded = m_nTileSize;
     269         148 :     if (NeedDecodedBuffer())
     270             :     {
     271           0 :         size_t nDecodedBufferSize = m_oType.GetSize();
     272           0 :         for (const auto &nBlockSize : m_anBlockSize)
     273             :         {
     274           0 :             if (nDecodedBufferSize > std::numeric_limits<size_t>::max() /
     275           0 :                                          static_cast<size_t>(nBlockSize))
     276             :             {
     277           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
     278           0 :                 return false;
     279             :             }
     280           0 :             nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
     281             :         }
     282           0 :         if (nSizeNeeded >
     283           0 :             std::numeric_limits<size_t>::max() - nDecodedBufferSize)
     284             :         {
     285           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
     286           0 :             return false;
     287             :         }
     288           0 :         nSizeNeeded += nDecodedBufferSize;
     289             :     }
     290             : 
     291             :     // Reserve a buffer for tile content
     292         148 :     if (nSizeNeeded > 1024 * 1024 * 1024 &&
     293           0 :         !CPLTestBool(CPLGetConfigOption("ZARR_ALLOW_BIG_TILE_SIZE", "NO")))
     294             :     {
     295           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     296             :                  "Zarr tile allocation would require " CPL_FRMT_GUIB " bytes. "
     297             :                  "By default the driver limits to 1 GB. To allow that memory "
     298             :                  "allocation, set the ZARR_ALLOW_BIG_TILE_SIZE configuration "
     299             :                  "option to YES.",
     300             :                  static_cast<GUIntBig>(nSizeNeeded));
     301           0 :         return false;
     302             :     }
     303             : 
     304         148 :     m_bWorkingBuffersOK =
     305         148 :         AllocateWorkingBuffers(m_abyRawTileData, m_abyDecodedTileData);
     306         148 :     return m_bWorkingBuffersOK;
     307             : }
     308             : 
     309       10819 : bool ZarrV3Array::AllocateWorkingBuffers(
     310             :     ZarrByteVectorQuickResize &abyRawTileData,
     311             :     ZarrByteVectorQuickResize &abyDecodedTileData) const
     312             : {
     313             :     // This method should NOT modify any ZarrArray member, as it is going to
     314             :     // be called concurrently from several threads.
     315             : 
     316             :     // Set those #define to avoid accidental use of some global variables
     317             : #define m_abyRawTileData cannot_use_here
     318             : #define m_abyDecodedTileData cannot_use_here
     319             : 
     320             :     try
     321             :     {
     322       10819 :         abyRawTileData.resize(m_nTileSize);
     323             :     }
     324           0 :     catch (const std::bad_alloc &e)
     325             :     {
     326           0 :         CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
     327           0 :         return false;
     328             :     }
     329             : 
     330       10811 :     if (NeedDecodedBuffer())
     331             :     {
     332           0 :         size_t nDecodedBufferSize = m_oType.GetSize();
     333           0 :         for (const auto &nBlockSize : m_anBlockSize)
     334             :         {
     335           0 :             nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
     336             :         }
     337             :         try
     338             :         {
     339           0 :             abyDecodedTileData.resize(nDecodedBufferSize);
     340             :         }
     341           0 :         catch (const std::bad_alloc &e)
     342             :         {
     343           0 :             CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
     344           0 :             return false;
     345             :         }
     346             :     }
     347             : 
     348       10809 :     return true;
     349             : #undef m_abyRawTileData
     350             : #undef m_abyDecodedTileData
     351             : }
     352             : 
     353             : /************************************************************************/
     354             : /*                      ZarrV3Array::LoadTileData()                     */
     355             : /************************************************************************/
     356             : 
     357         416 : bool ZarrV3Array::LoadTileData(const uint64_t *tileIndices,
     358             :                                bool &bMissingTileOut) const
     359             : {
     360         416 :     return LoadTileData(tileIndices,
     361             :                         false,  // use mutex
     362         416 :                         m_poCodecs.get(), m_abyRawTileData,
     363         832 :                         m_abyDecodedTileData, bMissingTileOut);
     364             : }
     365             : 
     366       11076 : bool ZarrV3Array::LoadTileData(const uint64_t *tileIndices, bool bUseMutex,
     367             :                                ZarrV3CodecSequence *poCodecs,
     368             :                                ZarrByteVectorQuickResize &abyRawTileData,
     369             :                                ZarrByteVectorQuickResize &abyDecodedTileData,
     370             :                                bool &bMissingTileOut) const
     371             : {
     372             :     // This method should NOT modify any ZarrArray member, as it is going to
     373             :     // be called concurrently from several threads.
     374             : 
     375             :     // Set those #define to avoid accidental use of some global variables
     376             : #define m_abyRawTileData cannot_use_here
     377             : #define m_abyDecodedTileData cannot_use_here
     378             : #define m_poCodecs cannot_use_here
     379             : 
     380       11076 :     bMissingTileOut = false;
     381             : 
     382       21733 :     std::string osFilename = BuildTileFilename(tileIndices);
     383             : 
     384             :     // For network file systems, get the streaming version of the filename,
     385             :     // as we don't need arbitrary seeking in the file
     386       11046 :     osFilename = VSIFileManager::GetHandler(osFilename.c_str())
     387       11073 :                      ->GetStreamingFilename(osFilename);
     388             : 
     389             :     // First if we have a tile presence cache, check tile presence from it
     390       11045 :     if (bUseMutex)
     391       10578 :         m_oMutex.lock();
     392       21894 :     auto poTilePresenceArray = OpenTilePresenceCache(false);
     393       11088 :     if (poTilePresenceArray)
     394             :     {
     395          18 :         std::vector<GUInt64> anTileIdx(m_aoDims.size());
     396          18 :         const std::vector<size_t> anCount(m_aoDims.size(), 1);
     397          18 :         const std::vector<GInt64> anArrayStep(m_aoDims.size(), 0);
     398          18 :         const std::vector<GPtrDiff_t> anBufferStride(m_aoDims.size(), 0);
     399          18 :         const auto eByteDT = GDALExtendedDataType::Create(GDT_Byte);
     400          54 :         for (size_t i = 0; i < m_aoDims.size(); ++i)
     401             :         {
     402          36 :             anTileIdx[i] = static_cast<GUInt64>(tileIndices[i]);
     403             :         }
     404          18 :         GByte byValue = 0;
     405          18 :         if (poTilePresenceArray->Read(anTileIdx.data(), anCount.data(),
     406             :                                       anArrayStep.data(), anBufferStride.data(),
     407          36 :                                       eByteDT, &byValue) &&
     408          18 :             byValue == 0)
     409             :         {
     410          13 :             if (bUseMutex)
     411           0 :                 m_oMutex.unlock();
     412          13 :             CPLDebugOnly(ZARR_DEBUG_KEY, "Tile %s missing (=nodata)",
     413             :                          osFilename.c_str());
     414          13 :             bMissingTileOut = true;
     415          13 :             return true;
     416             :         }
     417             :     }
     418       11075 :     if (bUseMutex)
     419       10672 :         m_oMutex.unlock();
     420             : 
     421       11075 :     VSILFILE *fp = nullptr;
     422             :     // This is the number of files returned in a S3 directory listing operation
     423       11075 :     constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
     424       11075 :     const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
     425             :                                            nullptr};
     426       22147 :     if ((m_osDimSeparator == "/" && !m_anBlockSize.empty() &&
     427       33169 :          m_anBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
     428       11068 :         (m_osDimSeparator != "/" &&
     429           2 :          m_nTotalTileCount > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING))
     430             :     {
     431             :         // Avoid issuing ReadDir() when a lot of files are expected
     432             :         CPLConfigOptionSetter optionSetter("GDAL_DISABLE_READDIR_ON_OPEN",
     433           0 :                                            "YES", true);
     434           0 :         fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
     435             :     }
     436             :     else
     437             :     {
     438       11017 :         fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
     439             :     }
     440       11041 :     if (fp == nullptr)
     441             :     {
     442             :         // Missing files are OK and indicate nodata_value
     443         299 :         CPLDebugOnly(ZARR_DEBUG_KEY, "Tile %s missing (=nodata)",
     444             :                      osFilename.c_str());
     445         299 :         bMissingTileOut = true;
     446         299 :         return true;
     447             :     }
     448             : 
     449       10742 :     bMissingTileOut = false;
     450             : 
     451       10742 :     CPLAssert(abyRawTileData.capacity() >= m_nTileSize);
     452             :     // should not fail
     453       10669 :     abyRawTileData.resize(m_nTileSize);
     454             : 
     455       10613 :     bool bRet = true;
     456       10613 :     size_t nRawDataSize = abyRawTileData.size();
     457       10641 :     if (poCodecs == nullptr)
     458             :     {
     459        5297 :         nRawDataSize = VSIFReadL(&abyRawTileData[0], 1, nRawDataSize, fp);
     460             :     }
     461             :     else
     462             :     {
     463        5344 :         VSIFSeekL(fp, 0, SEEK_END);
     464        5327 :         const auto nSize = VSIFTellL(fp);
     465        5071 :         VSIFSeekL(fp, 0, SEEK_SET);
     466        5099 :         if (nSize > static_cast<vsi_l_offset>(std::numeric_limits<int>::max()))
     467             :         {
     468           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Too large tile %s",
     469             :                      osFilename.c_str());
     470           0 :             bRet = false;
     471             :         }
     472             :         else
     473             :         {
     474             :             try
     475             :             {
     476        5088 :                 abyRawTileData.resize(static_cast<size_t>(nSize));
     477             :             }
     478           0 :             catch (const std::exception &)
     479             :             {
     480           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory,
     481             :                          "Cannot allocate memory for tile %s",
     482             :                          osFilename.c_str());
     483           0 :                 bRet = false;
     484             :             }
     485             : 
     486       10188 :             if (bRet && (abyRawTileData.empty() ||
     487        5137 :                          VSIFReadL(&abyRawTileData[0], 1, abyRawTileData.size(),
     488        5040 :                                    fp) != abyRawTileData.size()))
     489             :             {
     490           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     491             :                          "Could not read tile %s correctly",
     492             :                          osFilename.c_str());
     493           0 :                 bRet = false;
     494             :             }
     495             :             else
     496             :             {
     497        5035 :                 if (!poCodecs->Decode(abyRawTileData))
     498             :                 {
     499           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     500             :                              "Decompression of tile %s failed",
     501             :                              osFilename.c_str());
     502           0 :                     bRet = false;
     503             :                 }
     504             :             }
     505             :         }
     506             :     }
     507       10558 :     VSIFCloseL(fp);
     508       10663 :     if (!bRet)
     509           0 :         return false;
     510             : 
     511       10663 :     if (nRawDataSize != abyRawTileData.size())
     512             :     {
     513           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     514             :                  "Decompressed tile %s has not expected size. "
     515             :                  "Got %u instead of %u",
     516             :                  osFilename.c_str(),
     517           0 :                  static_cast<unsigned>(abyRawTileData.size()),
     518             :                  static_cast<unsigned>(nRawDataSize));
     519           0 :         return false;
     520             :     }
     521             : 
     522       10255 :     if (!abyDecodedTileData.empty())
     523             :     {
     524             :         const size_t nSourceSize =
     525           0 :             m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
     526           0 :         const auto nDTSize = m_oType.GetSize();
     527           0 :         const size_t nValues = abyDecodedTileData.size() / nDTSize;
     528           0 :         CPLAssert(nValues == m_nTileSize / nSourceSize);
     529           0 :         const GByte *pSrc = abyRawTileData.data();
     530           0 :         GByte *pDst = &abyDecodedTileData[0];
     531          57 :         for (size_t i = 0; i < nValues;
     532           0 :              i++, pSrc += nSourceSize, pDst += nDTSize)
     533             :         {
     534           0 :             DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
     535             :         }
     536             :     }
     537             : 
     538       10443 :     return true;
     539             : 
     540             : #undef m_abyRawTileData
     541             : #undef m_abyDecodedTileData
     542             : #undef m_poCodecs
     543             : }
     544             : 
     545             : /************************************************************************/
     546             : /*                      ZarrV3Array::IAdviseRead()                      */
     547             : /************************************************************************/
     548             : 
     549           6 : bool ZarrV3Array::IAdviseRead(const GUInt64 *arrayStartIdx, const size_t *count,
     550             :                               CSLConstList papszOptions) const
     551             : {
     552          12 :     std::vector<uint64_t> anIndicesCur;
     553           6 :     int nThreadsMax = 0;
     554          12 :     std::vector<uint64_t> anReqTilesIndices;
     555           6 :     size_t nReqTiles = 0;
     556           6 :     if (!IAdviseReadCommon(arrayStartIdx, count, papszOptions, anIndicesCur,
     557             :                            nThreadsMax, anReqTilesIndices, nReqTiles))
     558             :     {
     559           2 :         return false;
     560             :     }
     561           4 :     if (nThreadsMax <= 1)
     562             :     {
     563           0 :         return true;
     564             :     }
     565             : 
     566             :     const int nThreads =
     567           4 :         static_cast<int>(std::min(static_cast<size_t>(nThreadsMax), nReqTiles));
     568             : 
     569           4 :     CPLWorkerThreadPool *wtp = GDALGetGlobalThreadPool(nThreadsMax);
     570           4 :     if (wtp == nullptr)
     571           0 :         return false;
     572             : 
     573             :     struct JobStruct
     574             :     {
     575             :         JobStruct() = default;
     576             : 
     577             :         JobStruct(const JobStruct &) = delete;
     578             :         JobStruct &operator=(const JobStruct &) = delete;
     579             : 
     580             :         JobStruct(JobStruct &&) = default;
     581             :         JobStruct &operator=(JobStruct &&) = default;
     582             : 
     583             :         const ZarrV3Array *poArray = nullptr;
     584             :         bool *pbGlobalStatus = nullptr;
     585             :         int *pnRemainingThreads = nullptr;
     586             :         const std::vector<uint64_t> *panReqTilesIndices = nullptr;
     587             :         size_t nFirstIdx = 0;
     588             :         size_t nLastIdxNotIncluded = 0;
     589             :     };
     590             : 
     591           4 :     std::vector<JobStruct> asJobStructs;
     592             : 
     593           4 :     bool bGlobalStatus = true;
     594           4 :     int nRemainingThreads = nThreads;
     595             :     // Check for very highly overflow in below loop
     596           4 :     assert(static_cast<size_t>(nThreads) <
     597             :            std::numeric_limits<size_t>::max() / nReqTiles);
     598             : 
     599             :     // Setup jobs
     600          20 :     for (int i = 0; i < nThreads; i++)
     601             :     {
     602          16 :         JobStruct jobStruct;
     603          16 :         jobStruct.poArray = this;
     604          16 :         jobStruct.pbGlobalStatus = &bGlobalStatus;
     605          16 :         jobStruct.pnRemainingThreads = &nRemainingThreads;
     606          16 :         jobStruct.panReqTilesIndices = &anReqTilesIndices;
     607          16 :         jobStruct.nFirstIdx = static_cast<size_t>(i * nReqTiles / nThreads);
     608          16 :         jobStruct.nLastIdxNotIncluded = std::min(
     609          16 :             static_cast<size_t>((i + 1) * nReqTiles / nThreads), nReqTiles);
     610          16 :         asJobStructs.emplace_back(std::move(jobStruct));
     611             :     }
     612             : 
     613          16 :     const auto JobFunc = [](void *pThreadData)
     614             :     {
     615          16 :         const JobStruct *jobStruct =
     616             :             static_cast<const JobStruct *>(pThreadData);
     617             : 
     618          16 :         const auto poArray = jobStruct->poArray;
     619          16 :         const auto &aoDims = poArray->GetDimensions();
     620          16 :         const size_t l_nDims = poArray->GetDimensionCount();
     621          16 :         ZarrByteVectorQuickResize abyRawTileData;
     622          16 :         ZarrByteVectorQuickResize abyDecodedTileData;
     623           0 :         std::unique_ptr<ZarrV3CodecSequence> poCodecs;
     624          16 :         if (poArray->m_poCodecs)
     625             :         {
     626           8 :             std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     627           8 :             poCodecs = poArray->m_poCodecs->Clone();
     628             :         }
     629             : 
     630       10688 :         for (size_t iReq = jobStruct->nFirstIdx;
     631       10688 :              iReq < jobStruct->nLastIdxNotIncluded; ++iReq)
     632             :         {
     633             :             // Check if we must early exit
     634             :             {
     635       10672 :                 std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     636       10672 :                 if (!(*jobStruct->pbGlobalStatus))
     637           0 :                     return;
     638             :             }
     639             : 
     640             :             const uint64_t *tileIndices =
     641       10672 :                 jobStruct->panReqTilesIndices->data() + iReq * l_nDims;
     642             : 
     643       10661 :             uint64_t nTileIdx = 0;
     644       31996 :             for (size_t j = 0; j < l_nDims; ++j)
     645             :             {
     646       21330 :                 if (j > 0)
     647       10668 :                     nTileIdx *= aoDims[j - 1]->GetSize();
     648       21335 :                 nTileIdx += tileIndices[j];
     649             :             }
     650             : 
     651       10666 :             if (!poArray->AllocateWorkingBuffers(abyRawTileData,
     652             :                                                  abyDecodedTileData))
     653             :             {
     654           0 :                 std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     655           0 :                 *jobStruct->pbGlobalStatus = false;
     656           0 :                 break;
     657             :             }
     658             : 
     659       10653 :             bool bIsEmpty = false;
     660       10653 :             bool success = poArray->LoadTileData(tileIndices,
     661             :                                                  true,  // use mutex
     662             :                                                  poCodecs.get(), abyRawTileData,
     663             :                                                  abyDecodedTileData, bIsEmpty);
     664             : 
     665       10493 :             std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     666       10672 :             if (!success)
     667             :             {
     668           0 :                 *jobStruct->pbGlobalStatus = false;
     669           0 :                 break;
     670             :             }
     671             : 
     672       21344 :             CachedTile cachedTile;
     673       10672 :             if (!bIsEmpty)
     674             :             {
     675       10670 :                 if (!abyDecodedTileData.empty())
     676           0 :                     std::swap(cachedTile.abyDecoded, abyDecodedTileData);
     677             :                 else
     678       10670 :                     std::swap(cachedTile.abyDecoded, abyRawTileData);
     679             :             }
     680       10672 :             poArray->m_oMapTileIndexToCachedTile[nTileIdx] =
     681       21344 :                 std::move(cachedTile);
     682             :         }
     683             : 
     684          16 :         std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     685          16 :         (*jobStruct->pnRemainingThreads)--;
     686             :     };
     687             : 
     688             :     // Start jobs
     689          20 :     for (int i = 0; i < nThreads; i++)
     690             :     {
     691          16 :         if (!wtp->SubmitJob(JobFunc, &asJobStructs[i]))
     692             :         {
     693           0 :             std::lock_guard<std::mutex> oLock(m_oMutex);
     694           0 :             bGlobalStatus = false;
     695           0 :             nRemainingThreads = i;
     696           0 :             break;
     697             :         }
     698             :     }
     699             : 
     700             :     // Wait for all jobs to be finished
     701             :     while (true)
     702             :     {
     703             :         {
     704          16 :             std::lock_guard<std::mutex> oLock(m_oMutex);
     705          16 :             if (nRemainingThreads == 0)
     706           4 :                 break;
     707             :         }
     708          12 :         wtp->WaitEvent();
     709          12 :     }
     710             : 
     711           4 :     return bGlobalStatus;
     712             : }
     713             : 
     714             : /************************************************************************/
     715             : /*                    ZarrV3Array::FlushDirtyTile()                     */
     716             : /************************************************************************/
     717             : 
     718       11516 : bool ZarrV3Array::FlushDirtyTile() const
     719             : {
     720       11516 :     if (!m_bDirtyTile)
     721         772 :         return true;
     722       10744 :     m_bDirtyTile = false;
     723             : 
     724       21488 :     std::string osFilename = BuildTileFilename(m_anCachedTiledIndices.data());
     725             : 
     726             :     const size_t nSourceSize =
     727       10744 :         m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
     728             :     const auto &abyTile =
     729       10744 :         m_abyDecodedTileData.empty() ? m_abyRawTileData : m_abyDecodedTileData;
     730             : 
     731       10744 :     if (IsEmptyTile(abyTile))
     732             :     {
     733           2 :         m_bCachedTiledEmpty = true;
     734             : 
     735             :         VSIStatBufL sStat;
     736           2 :         if (VSIStatL(osFilename.c_str(), &sStat) == 0)
     737             :         {
     738           0 :             CPLDebugOnly(ZARR_DEBUG_KEY,
     739             :                          "Deleting tile %s that has now empty content",
     740             :                          osFilename.c_str());
     741           0 :             return VSIUnlink(osFilename.c_str()) == 0;
     742             :         }
     743           2 :         return true;
     744             :     }
     745             : 
     746       10742 :     if (!m_abyDecodedTileData.empty())
     747             :     {
     748           0 :         const size_t nDTSize = m_oType.GetSize();
     749           0 :         const size_t nValues = m_abyDecodedTileData.size() / nDTSize;
     750           0 :         GByte *pDst = &m_abyRawTileData[0];
     751           0 :         const GByte *pSrc = m_abyDecodedTileData.data();
     752           0 :         for (size_t i = 0; i < nValues;
     753           0 :              i++, pDst += nSourceSize, pSrc += nDTSize)
     754             :         {
     755           0 :             EncodeElt(m_aoDtypeElts, pSrc, pDst);
     756             :         }
     757             :     }
     758             : 
     759       10742 :     const size_t nSizeBefore = m_abyRawTileData.size();
     760       10742 :     if (m_poCodecs)
     761             :     {
     762        5405 :         if (!m_poCodecs->Encode(m_abyRawTileData))
     763             :         {
     764           0 :             m_abyRawTileData.resize(nSizeBefore);
     765           0 :             return false;
     766             :         }
     767             :     }
     768             : 
     769       10742 :     if (m_osDimSeparator == "/")
     770             :     {
     771       10742 :         std::string osDir = CPLGetDirnameSafe(osFilename.c_str());
     772             :         VSIStatBufL sStat;
     773       10742 :         if (VSIStatL(osDir.c_str(), &sStat) != 0)
     774             :         {
     775         204 :             if (VSIMkdirRecursive(osDir.c_str(), 0755) != 0)
     776             :             {
     777           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     778             :                          "Cannot create directory %s", osDir.c_str());
     779           0 :                 m_abyRawTileData.resize(nSizeBefore);
     780           0 :                 return false;
     781             :             }
     782             :         }
     783             :     }
     784             : 
     785       10742 :     VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
     786       10742 :     if (fp == nullptr)
     787             :     {
     788           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot create tile %s",
     789             :                  osFilename.c_str());
     790           0 :         m_abyRawTileData.resize(nSizeBefore);
     791           0 :         return false;
     792             :     }
     793             : 
     794       10742 :     bool bRet = true;
     795       10742 :     const size_t nRawDataSize = m_abyRawTileData.size();
     796       10742 :     if (VSIFWriteL(m_abyRawTileData.data(), 1, nRawDataSize, fp) !=
     797             :         nRawDataSize)
     798             :     {
     799           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     800             :                  "Could not write tile %s correctly", osFilename.c_str());
     801           0 :         bRet = false;
     802             :     }
     803       10742 :     VSIFCloseL(fp);
     804             : 
     805       10742 :     m_abyRawTileData.resize(nSizeBefore);
     806             : 
     807       10742 :     return bRet;
     808             : }
     809             : 
     810             : /************************************************************************/
     811             : /*                          BuildTileFilename()                         */
     812             : /************************************************************************/
     813             : 
     814       21810 : std::string ZarrV3Array::BuildTileFilename(const uint64_t *tileIndices) const
     815             : {
     816       21810 :     if (m_aoDims.empty())
     817             :     {
     818             :         return CPLFormFilenameSafe(
     819           0 :             CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
     820           0 :             m_bV2ChunkKeyEncoding ? "0" : "c", nullptr);
     821             :     }
     822             :     else
     823             :     {
     824       43602 :         std::string osFilename(CPLGetDirnameSafe(m_osFilename.c_str()));
     825       21815 :         osFilename += '/';
     826       21792 :         if (!m_bV2ChunkKeyEncoding)
     827             :         {
     828       21790 :             osFilename += 'c';
     829             :         }
     830       65301 :         for (size_t i = 0; i < m_aoDims.size(); ++i)
     831             :         {
     832       43477 :             if (i > 0 || !m_bV2ChunkKeyEncoding)
     833       43470 :                 osFilename += m_osDimSeparator;
     834       43450 :             osFilename += std::to_string(tileIndices[i]);
     835             :         }
     836       21817 :         return osFilename;
     837             :     }
     838             : }
     839             : 
     840             : /************************************************************************/
     841             : /*                          GetDataDirectory()                          */
     842             : /************************************************************************/
     843             : 
     844           2 : std::string ZarrV3Array::GetDataDirectory() const
     845             : {
     846           2 :     return std::string(CPLGetDirnameSafe(m_osFilename.c_str()));
     847             : }
     848             : 
     849             : /************************************************************************/
     850             : /*                        GetTileIndicesFromFilename()                  */
     851             : /************************************************************************/
     852             : 
     853             : CPLStringList
     854           4 : ZarrV3Array::GetTileIndicesFromFilename(const char *pszFilename) const
     855             : {
     856           4 :     if (!m_bV2ChunkKeyEncoding)
     857             :     {
     858           4 :         if (pszFilename[0] != 'c')
     859           2 :             return CPLStringList();
     860           2 :         if (m_osDimSeparator == "/")
     861             :         {
     862           2 :             if (pszFilename[1] != '/' && pszFilename[1] != '\\')
     863           0 :                 return CPLStringList();
     864             :         }
     865           0 :         else if (pszFilename[1] != m_osDimSeparator[0])
     866             :         {
     867           0 :             return CPLStringList();
     868             :         }
     869             :     }
     870             :     return CPLStringList(
     871           2 :         CSLTokenizeString2(pszFilename + (m_bV2ChunkKeyEncoding ? 0 : 2),
     872           4 :                            m_osDimSeparator.c_str(), 0));
     873             : }
     874             : 
     875             : /************************************************************************/
     876             : /*                           ParseDtypeV3()                             */
     877             : /************************************************************************/
     878             : 
     879         170 : static GDALExtendedDataType ParseDtypeV3(const CPLJSONObject &obj,
     880             :                                          std::vector<DtypeElt> &elts)
     881             : {
     882             :     do
     883             :     {
     884         170 :         if (obj.GetType() == CPLJSONObject::Type::String)
     885             :         {
     886         340 :             const auto str = obj.ToString();
     887         170 :             DtypeElt elt;
     888         170 :             GDALDataType eDT = GDT_Unknown;
     889             : 
     890         170 :             if (str == "bool")  // boolean
     891             :             {
     892           0 :                 elt.nativeType = DtypeElt::NativeType::BOOLEAN;
     893           0 :                 eDT = GDT_Byte;
     894             :             }
     895         170 :             else if (str == "int8")
     896             :             {
     897           6 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     898           6 :                 eDT = GDT_Int8;
     899             :             }
     900         164 :             else if (str == "uint8")
     901             :             {
     902          65 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     903          65 :                 eDT = GDT_Byte;
     904             :             }
     905          99 :             else if (str == "int16")
     906             :             {
     907          10 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     908          10 :                 eDT = GDT_Int16;
     909             :             }
     910          89 :             else if (str == "uint16")
     911             :             {
     912           7 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     913           7 :                 eDT = GDT_UInt16;
     914             :             }
     915          82 :             else if (str == "int32")
     916             :             {
     917           7 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     918           7 :                 eDT = GDT_Int32;
     919             :             }
     920          75 :             else if (str == "uint32")
     921             :             {
     922           7 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     923           7 :                 eDT = GDT_UInt32;
     924             :             }
     925          68 :             else if (str == "int64")
     926             :             {
     927           7 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     928           7 :                 eDT = GDT_Int64;
     929             :             }
     930          61 :             else if (str == "uint64")
     931             :             {
     932           6 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     933           6 :                 eDT = GDT_UInt64;
     934             :             }
     935          55 :             else if (str == "float16")
     936             :             {
     937           0 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
     938           0 :                 elt.nativeSize = 2;
     939           0 :                 elt.gdalTypeIsApproxOfNative = true;
     940           0 :                 eDT = GDT_Float32;
     941             :             }
     942          55 :             else if (str == "float32")
     943             :             {
     944           9 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
     945           9 :                 eDT = GDT_Float32;
     946             :             }
     947          46 :             else if (str == "float64")
     948             :             {
     949          15 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
     950          15 :                 eDT = GDT_Float64;
     951             :             }
     952          31 :             else if (str == "complex64")
     953             :             {
     954          15 :                 elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     955          15 :                 eDT = GDT_CFloat32;
     956             :             }
     957          16 :             else if (str == "complex128")
     958             :             {
     959          15 :                 elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     960          15 :                 eDT = GDT_CFloat64;
     961             :             }
     962             :             else
     963           1 :                 break;
     964             : 
     965         169 :             elt.gdalType = GDALExtendedDataType::Create(eDT);
     966         169 :             elt.gdalSize = elt.gdalType.GetSize();
     967         169 :             if (!elt.gdalTypeIsApproxOfNative)
     968         169 :                 elt.nativeSize = elt.gdalSize;
     969             : 
     970         169 :             if (elt.nativeSize > 1)
     971             :             {
     972          98 :                 elt.needByteSwapping = (CPL_IS_LSB == 0);
     973             :             }
     974             : 
     975         169 :             elts.emplace_back(elt);
     976         169 :             return GDALExtendedDataType::Create(eDT);
     977             :         }
     978             :     } while (false);
     979           1 :     CPLError(CE_Failure, CPLE_AppDefined,
     980             :              "Invalid or unsupported format for data_type: %s",
     981           2 :              obj.ToString().c_str());
     982           1 :     return GDALExtendedDataType::Create(GDT_Unknown);
     983             : }
     984             : 
     985             : /************************************************************************/
     986             : /*                    ParseNoDataStringAsDouble()                       */
     987             : /************************************************************************/
     988             : 
     989          37 : static double ParseNoDataStringAsDouble(const std::string &osVal, bool &bOK)
     990             : {
     991          37 :     double dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
     992          37 :     if (osVal == "NaN")
     993             :     {
     994             :         // initialized above
     995             :     }
     996          15 :     else if (osVal == "Infinity" || osVal == "+Infinity")
     997             :     {
     998           5 :         dfNoDataValue = std::numeric_limits<double>::infinity();
     999             :     }
    1000          10 :     else if (osVal == "-Infinity")
    1001             :     {
    1002           5 :         dfNoDataValue = -std::numeric_limits<double>::infinity();
    1003             :     }
    1004             :     else
    1005             :     {
    1006           5 :         bOK = false;
    1007             :     }
    1008          37 :     return dfNoDataValue;
    1009             : }
    1010             : 
    1011             : /************************************************************************/
    1012             : /*                     ParseNoDataComponent()                           */
    1013             : /************************************************************************/
    1014             : 
    1015             : template <typename T, typename Tint>
    1016          40 : static T ParseNoDataComponent(const CPLJSONObject &oObj, bool &bOK)
    1017             : {
    1018          40 :     if (oObj.GetType() == CPLJSONObject::Type::Integer ||
    1019          62 :         oObj.GetType() == CPLJSONObject::Type::Long ||
    1020          22 :         oObj.GetType() == CPLJSONObject::Type::Double)
    1021             :     {
    1022          22 :         return static_cast<T>(oObj.ToDouble());
    1023             :     }
    1024          18 :     else if (oObj.GetType() == CPLJSONObject::Type::String)
    1025             :     {
    1026          54 :         const auto osVal = oObj.ToString();
    1027          18 :         if (STARTS_WITH(osVal.c_str(), "0x"))
    1028             :         {
    1029           2 :             if (osVal.size() > 2 + 2 * sizeof(T))
    1030             :             {
    1031           0 :                 bOK = false;
    1032           0 :                 return 0;
    1033             :             }
    1034           2 :             Tint nVal = static_cast<Tint>(
    1035           2 :                 std::strtoull(osVal.c_str() + 2, nullptr, 16));
    1036             :             T fVal;
    1037             :             static_assert(sizeof(nVal) == sizeof(fVal),
    1038             :                           "sizeof(nVal) == sizeof(dfVal)");
    1039           2 :             memcpy(&fVal, &nVal, sizeof(nVal));
    1040           2 :             return fVal;
    1041             :         }
    1042             :         else
    1043             :         {
    1044          16 :             return static_cast<T>(ParseNoDataStringAsDouble(osVal, bOK));
    1045             :         }
    1046             :     }
    1047             :     else
    1048             :     {
    1049           0 :         bOK = false;
    1050           0 :         return 0;
    1051             :     }
    1052             : }
    1053             : 
    1054             : /************************************************************************/
    1055             : /*                     ZarrV3Group::LoadArray()                         */
    1056             : /************************************************************************/
    1057             : 
    1058             : std::shared_ptr<ZarrArray>
    1059         183 : ZarrV3Group::LoadArray(const std::string &osArrayName,
    1060             :                        const std::string &osZarrayFilename,
    1061             :                        const CPLJSONObject &oRoot) const
    1062             : {
    1063             :     // Add osZarrayFilename to m_poSharedResource during the scope
    1064             :     // of this function call.
    1065         183 :     ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
    1066         366 :                                                        osZarrayFilename);
    1067         183 :     if (!filenameAdder.ok())
    1068           0 :         return nullptr;
    1069             : 
    1070             :     // Warn about unknown members (the spec suggests to error out, but let be
    1071             :     // a bit more lenient)
    1072        1766 :     for (const auto &oNode : oRoot.GetChildren())
    1073             :     {
    1074        3166 :         const auto osName = oNode.GetName();
    1075        4200 :         if (osName != "zarr_format" && osName != "node_type" &&
    1076        3105 :             osName != "shape" && osName != "chunk_grid" &&
    1077        2013 :             osName != "data_type" && osName != "chunk_key_encoding" &&
    1078         795 :             osName != "fill_value" &&
    1079             :             // Below are optional
    1080         627 :             osName != "dimension_names" && osName != "codecs" &&
    1081        3111 :             osName != "storage_transformers" && osName != "attributes")
    1082             :         {
    1083           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    1084             :                      "%s array definition contains a unknown member (%s). "
    1085             :                      "Interpretation of the array might be wrong.",
    1086             :                      osZarrayFilename.c_str(), osName.c_str());
    1087             :         }
    1088             :     }
    1089             : 
    1090         549 :     const auto oStorageTransformers = oRoot["storage_transformers"].ToArray();
    1091         183 :     if (oStorageTransformers.Size() > 0)
    1092             :     {
    1093           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1094             :                  "storage_transformers are not supported.");
    1095           1 :         return nullptr;
    1096             :     }
    1097             : 
    1098         546 :     const auto oShape = oRoot["shape"].ToArray();
    1099         182 :     if (!oShape.IsValid())
    1100             :     {
    1101           2 :         CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
    1102           2 :         return nullptr;
    1103             :     }
    1104             : 
    1105             :     // Parse chunk_grid
    1106         540 :     const auto oChunkGrid = oRoot["chunk_grid"];
    1107         180 :     if (oChunkGrid.GetType() != CPLJSONObject::Type::Object)
    1108             :     {
    1109           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1110             :                  "chunk_grid missing or not an object");
    1111           1 :         return nullptr;
    1112             :     }
    1113             : 
    1114         537 :     const auto oChunkGridName = oChunkGrid["name"];
    1115         179 :     if (oChunkGridName.ToString() != "regular")
    1116             :     {
    1117           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1118             :                  "Only chunk_grid.name = regular supported");
    1119           1 :         return nullptr;
    1120             :     }
    1121             : 
    1122         534 :     const auto oChunks = oChunkGrid["configuration"]["chunk_shape"].ToArray();
    1123         178 :     if (!oChunks.IsValid())
    1124             :     {
    1125           1 :         CPLError(
    1126             :             CE_Failure, CPLE_AppDefined,
    1127             :             "chunk_grid.configuration.chunk_shape missing or not an array");
    1128           1 :         return nullptr;
    1129             :     }
    1130             : 
    1131         177 :     if (oShape.Size() != oChunks.Size())
    1132             :     {
    1133           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1134             :                  "shape and chunks arrays are of different size");
    1135           1 :         return nullptr;
    1136             :     }
    1137             : 
    1138             :     // Parse chunk_key_encoding
    1139         528 :     const auto oChunkKeyEncoding = oRoot["chunk_key_encoding"];
    1140         176 :     if (oChunkKeyEncoding.GetType() != CPLJSONObject::Type::Object)
    1141             :     {
    1142           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1143             :                  "chunk_key_encoding missing or not an object");
    1144           1 :         return nullptr;
    1145             :     }
    1146             : 
    1147         350 :     std::string osDimSeparator;
    1148         175 :     bool bV2ChunkKeyEncoding = false;
    1149         525 :     const auto oChunkKeyEncodingName = oChunkKeyEncoding["name"];
    1150         175 :     if (oChunkKeyEncodingName.ToString() == "default")
    1151             :     {
    1152         171 :         osDimSeparator = "/";
    1153             :     }
    1154           4 :     else if (oChunkKeyEncodingName.ToString() == "v2")
    1155             :     {
    1156           3 :         osDimSeparator = ".";
    1157           3 :         bV2ChunkKeyEncoding = true;
    1158             :     }
    1159             :     else
    1160             :     {
    1161           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1162             :                  "Unsupported chunk_key_encoding.name");
    1163           1 :         return nullptr;
    1164             :     }
    1165             : 
    1166             :     {
    1167         348 :         auto oConfiguration = oChunkKeyEncoding["configuration"];
    1168         174 :         if (oConfiguration.GetType() == CPLJSONObject::Type::Object)
    1169             :         {
    1170         256 :             auto oSeparator = oConfiguration["separator"];
    1171         128 :             if (oSeparator.IsValid())
    1172             :             {
    1173         128 :                 osDimSeparator = oSeparator.ToString();
    1174         128 :                 if (osDimSeparator != "/" && osDimSeparator != ".")
    1175             :                 {
    1176           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1177             :                              "Separator can only be '/' or '.'");
    1178           1 :                     return nullptr;
    1179             :                 }
    1180             :             }
    1181             :         }
    1182             :     }
    1183             : 
    1184         519 :     CPLJSONObject oAttributes = oRoot["attributes"];
    1185             : 
    1186             :     // Deep-clone of oAttributes
    1187         173 :     if (oAttributes.IsValid())
    1188             :     {
    1189         123 :         oAttributes = oAttributes.Clone();
    1190             :     }
    1191             : 
    1192         346 :     std::vector<std::shared_ptr<GDALDimension>> aoDims;
    1193         395 :     for (int i = 0; i < oShape.Size(); ++i)
    1194             :     {
    1195         222 :         const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
    1196         222 :         if (nSize == 0)
    1197             :         {
    1198           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
    1199           0 :             return nullptr;
    1200             :         }
    1201         222 :         aoDims.emplace_back(std::make_shared<ZarrDimension>(
    1202         222 :             m_poSharedResource,
    1203         444 :             std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
    1204         444 :             std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
    1205         222 :             nSize));
    1206             :     }
    1207             : 
    1208             :     // Deal with dimension_names
    1209         519 :     const auto dimensionNames = oRoot["dimension_names"];
    1210             : 
    1211         166 :     const auto FindDimension = [this, &aoDims, &osArrayName, &oAttributes](
    1212             :                                    const std::string &osDimName,
    1213        2212 :                                    std::shared_ptr<GDALDimension> &poDim, int i)
    1214             :     {
    1215         166 :         auto oIter = m_oMapDimensions.find(osDimName);
    1216         166 :         if (oIter != m_oMapDimensions.end())
    1217             :         {
    1218           0 :             if (m_bDimSizeInUpdate ||
    1219           0 :                 oIter->second->GetSize() == poDim->GetSize())
    1220             :             {
    1221           0 :                 poDim = oIter->second;
    1222           0 :                 return true;
    1223             :             }
    1224             :             else
    1225             :             {
    1226           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1227             :                          "Size of _ARRAY_DIMENSIONS[%d] different "
    1228             :                          "from the one of shape",
    1229             :                          i);
    1230           0 :                 return false;
    1231             :             }
    1232             :         }
    1233             : 
    1234             :         // Try to load the indexing variable.
    1235             :         // Not in m_oMapMDArrays,
    1236             :         // then stat() the indexing variable.
    1237         330 :         else if (osArrayName != osDimName &&
    1238         330 :                  m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
    1239             :         {
    1240         328 :             std::string osDirName = m_osDirectoryName;
    1241             :             while (true)
    1242             :             {
    1243             :                 const std::string osArrayFilenameDim = CPLFormFilenameSafe(
    1244         483 :                     CPLFormFilenameSafe(osDirName.c_str(), osDimName.c_str(),
    1245             :                                         nullptr)
    1246             :                         .c_str(),
    1247         483 :                     "zarr.json", nullptr);
    1248             :                 VSIStatBufL sStat;
    1249         483 :                 if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
    1250             :                 {
    1251           4 :                     CPLJSONDocument oDoc;
    1252           2 :                     if (oDoc.Load(osArrayFilenameDim))
    1253             :                     {
    1254           2 :                         LoadArray(osDimName, osArrayFilenameDim,
    1255           4 :                                   oDoc.GetRoot());
    1256             :                     }
    1257             :                 }
    1258             :                 else
    1259             :                 {
    1260             :                     // Recurse to upper level for datasets such as
    1261             :                     // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
    1262             :                     const std::string osDirNameNew =
    1263         481 :                         CPLGetPathSafe(osDirName.c_str());
    1264         481 :                     if (!osDirNameNew.empty() && osDirNameNew != osDirName)
    1265             :                     {
    1266         319 :                         osDirName = osDirNameNew;
    1267         319 :                         continue;
    1268             :                     }
    1269             :                 }
    1270         164 :                 break;
    1271         319 :             }
    1272             :         }
    1273             : 
    1274         166 :         oIter = m_oMapDimensions.find(osDimName);
    1275             :         // cppcheck-suppress knownConditionTrueFalse
    1276         168 :         if (oIter != m_oMapDimensions.end() &&
    1277           2 :             oIter->second->GetSize() == poDim->GetSize())
    1278             :         {
    1279           2 :             poDim = oIter->second;
    1280           2 :             return true;
    1281             :         }
    1282             : 
    1283         328 :         std::string osType;
    1284         328 :         std::string osDirection;
    1285         164 :         if (aoDims.size() == 1 && osArrayName == osDimName)
    1286             :         {
    1287           2 :             ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
    1288             :                                                  osDirection);
    1289             :         }
    1290             : 
    1291             :         auto poDimLocal = std::make_shared<ZarrDimension>(
    1292         164 :             m_poSharedResource,
    1293         328 :             std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
    1294         328 :             GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
    1295         164 :         poDimLocal->SetXArrayDimension();
    1296         164 :         m_oMapDimensions[osDimName] = poDimLocal;
    1297         164 :         poDim = poDimLocal;
    1298         164 :         return true;
    1299         173 :     };
    1300             : 
    1301         173 :     if (dimensionNames.GetType() == CPLJSONObject::Type::Array)
    1302             :     {
    1303         112 :         const auto arrayDims = dimensionNames.ToArray();
    1304         112 :         if (arrayDims.Size() == oShape.Size())
    1305             :         {
    1306         277 :             for (int i = 0; i < oShape.Size(); ++i)
    1307             :             {
    1308         166 :                 if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
    1309             :                 {
    1310         498 :                     const auto osDimName = arrayDims[i].ToString();
    1311         166 :                     FindDimension(osDimName, aoDims[i], i);
    1312             :                 }
    1313             :             }
    1314             :         }
    1315             :         else
    1316             :         {
    1317           1 :             CPLError(
    1318             :                 CE_Failure, CPLE_AppDefined,
    1319             :                 "Size of dimension_names[] different from the one of shape");
    1320           1 :             return nullptr;
    1321             :         }
    1322             :     }
    1323          61 :     else if (dimensionNames.IsValid())
    1324             :     {
    1325           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1326             :                  "dimension_names should be an array");
    1327           1 :         return nullptr;
    1328             :     }
    1329             : 
    1330         513 :     auto oDtype = oRoot["data_type"];
    1331         171 :     if (!oDtype.IsValid())
    1332             :     {
    1333           1 :         CPLError(CE_Failure, CPLE_NotSupported, "data_type missing");
    1334           1 :         return nullptr;
    1335             :     }
    1336         170 :     if (oDtype["fallback"].IsValid())
    1337           1 :         oDtype = oDtype["fallback"];
    1338         340 :     std::vector<DtypeElt> aoDtypeElts;
    1339         340 :     const auto oType = ParseDtypeV3(oDtype, aoDtypeElts);
    1340         340 :     if (oType.GetClass() == GEDTC_NUMERIC &&
    1341         170 :         oType.GetNumericDataType() == GDT_Unknown)
    1342           1 :         return nullptr;
    1343             : 
    1344         338 :     std::vector<GUInt64> anBlockSize;
    1345         169 :     if (!ZarrArray::ParseChunkSize(oChunks, oType, anBlockSize))
    1346           1 :         return nullptr;
    1347             : 
    1348         336 :     std::vector<GByte> abyNoData;
    1349             : 
    1350         504 :     auto oFillValue = oRoot["fill_value"];
    1351         168 :     auto eFillValueType = oFillValue.GetType();
    1352             : 
    1353         168 :     if (!oFillValue.IsValid())
    1354             :     {
    1355           0 :         CPLError(CE_Warning, CPLE_AppDefined, "Missing fill_value is invalid");
    1356             :     }
    1357         168 :     else if (eFillValueType == CPLJSONObject::Type::Null)
    1358             :     {
    1359          97 :         CPLError(CE_Warning, CPLE_AppDefined, "fill_value = null is invalid");
    1360             :     }
    1361          71 :     else if (GDALDataTypeIsComplex(oType.GetNumericDataType()) &&
    1362             :              eFillValueType != CPLJSONObject::Type::Array)
    1363             :     {
    1364           4 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1365           4 :         return nullptr;
    1366             :     }
    1367          67 :     else if (eFillValueType == CPLJSONObject::Type::String)
    1368             :     {
    1369          54 :         const auto osFillValue = oFillValue.ToString();
    1370          27 :         if (STARTS_WITH(osFillValue.c_str(), "0x"))
    1371             :         {
    1372           3 :             if (osFillValue.size() > 2 + 2 * oType.GetSize())
    1373             :             {
    1374           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1375           1 :                 return nullptr;
    1376             :             }
    1377             :             uint64_t nVal = static_cast<uint64_t>(
    1378           3 :                 std::strtoull(osFillValue.c_str() + 2, nullptr, 16));
    1379           3 :             if (oType.GetSize() == 4)
    1380             :             {
    1381           1 :                 abyNoData.resize(oType.GetSize());
    1382           1 :                 uint32_t nTmp = static_cast<uint32_t>(nVal);
    1383           1 :                 memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
    1384             :             }
    1385           2 :             else if (oType.GetSize() == 8)
    1386             :             {
    1387           1 :                 abyNoData.resize(oType.GetSize());
    1388           1 :                 memcpy(&abyNoData[0], &nVal, sizeof(nVal));
    1389             :             }
    1390             :             else
    1391             :             {
    1392           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1393             :                          "Hexadecimal representation of fill_value no "
    1394             :                          "supported for this data type");
    1395           1 :                 return nullptr;
    1396             :             }
    1397             :         }
    1398          24 :         else if (STARTS_WITH(osFillValue.c_str(), "0b"))
    1399             :         {
    1400           3 :             if (osFillValue.size() > 2 + 8 * oType.GetSize())
    1401             :             {
    1402           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1403           1 :                 return nullptr;
    1404             :             }
    1405             :             uint64_t nVal = static_cast<uint64_t>(
    1406           3 :                 std::strtoull(osFillValue.c_str() + 2, nullptr, 2));
    1407           3 :             if (oType.GetSize() == 4)
    1408             :             {
    1409           1 :                 abyNoData.resize(oType.GetSize());
    1410           1 :                 uint32_t nTmp = static_cast<uint32_t>(nVal);
    1411           1 :                 memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
    1412             :             }
    1413           2 :             else if (oType.GetSize() == 8)
    1414             :             {
    1415           1 :                 abyNoData.resize(oType.GetSize());
    1416           1 :                 memcpy(&abyNoData[0], &nVal, sizeof(nVal));
    1417             :             }
    1418             :             else
    1419             :             {
    1420           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1421             :                          "Binary representation of fill_value no supported for "
    1422             :                          "this data type");
    1423           1 :                 return nullptr;
    1424             :             }
    1425             :         }
    1426             :         else
    1427             :         {
    1428          21 :             bool bOK = true;
    1429          21 :             double dfNoDataValue = ParseNoDataStringAsDouble(osFillValue, bOK);
    1430          21 :             if (!bOK)
    1431             :             {
    1432           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1433           2 :                 return nullptr;
    1434             :             }
    1435          20 :             else if (oType.GetNumericDataType() == GDT_Float32)
    1436             :             {
    1437           7 :                 const float fNoDataValue = static_cast<float>(dfNoDataValue);
    1438           7 :                 abyNoData.resize(sizeof(fNoDataValue));
    1439           7 :                 memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
    1440             :             }
    1441          13 :             else if (oType.GetNumericDataType() == GDT_Float64)
    1442             :             {
    1443          12 :                 abyNoData.resize(sizeof(dfNoDataValue));
    1444          12 :                 memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
    1445             :             }
    1446             :             else
    1447             :             {
    1448           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1449             :                          "Invalid fill_value for this data type");
    1450           1 :                 return nullptr;
    1451             :             }
    1452             :         }
    1453             :     }
    1454          40 :     else if (eFillValueType == CPLJSONObject::Type::Boolean ||
    1455          25 :              eFillValueType == CPLJSONObject::Type::Integer ||
    1456          25 :              eFillValueType == CPLJSONObject::Type::Long ||
    1457             :              eFillValueType == CPLJSONObject::Type::Double)
    1458             :     {
    1459          16 :         const double dfNoDataValue = oFillValue.ToDouble();
    1460          16 :         if (oType.GetNumericDataType() == GDT_Int64)
    1461             :         {
    1462             :             const int64_t nNoDataValue =
    1463           1 :                 static_cast<int64_t>(oFillValue.ToLong());
    1464           1 :             abyNoData.resize(oType.GetSize());
    1465           1 :             GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
    1466             :                           oType.GetNumericDataType(), 0, 1);
    1467             :         }
    1468          15 :         else if (oType.GetNumericDataType() == GDT_UInt64 &&
    1469             :                  /* we can't really deal with nodata value between */
    1470             :                  /* int64::max and uint64::max due to json-c limitations */
    1471           0 :                  dfNoDataValue >= 0)
    1472             :         {
    1473             :             const int64_t nNoDataValue =
    1474           0 :                 static_cast<int64_t>(oFillValue.ToLong());
    1475           0 :             abyNoData.resize(oType.GetSize());
    1476           0 :             GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
    1477             :                           oType.GetNumericDataType(), 0, 1);
    1478             :         }
    1479             :         else
    1480             :         {
    1481          15 :             abyNoData.resize(oType.GetSize());
    1482          15 :             GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
    1483             :                           oType.GetNumericDataType(), 0, 1);
    1484          16 :         }
    1485             :     }
    1486          24 :     else if (eFillValueType == CPLJSONObject::Type::Array)
    1487             :     {
    1488          24 :         const auto oFillValueArray = oFillValue.ToArray();
    1489          44 :         if (oFillValueArray.Size() == 2 &&
    1490          20 :             GDALDataTypeIsComplex(oType.GetNumericDataType()))
    1491             :         {
    1492          20 :             if (oType.GetNumericDataType() == GDT_CFloat64)
    1493             :             {
    1494          10 :                 bool bOK = true;
    1495             :                 const double adfNoDataValue[2] = {
    1496          10 :                     ParseNoDataComponent<double, uint64_t>(oFillValueArray[0],
    1497             :                                                            bOK),
    1498          10 :                     ParseNoDataComponent<double, uint64_t>(oFillValueArray[1],
    1499             :                                                            bOK),
    1500          20 :                 };
    1501          10 :                 if (!bOK)
    1502             :                 {
    1503           2 :                     CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1504           2 :                     return nullptr;
    1505             :                 }
    1506           8 :                 abyNoData.resize(oType.GetSize());
    1507           8 :                 CPLAssert(sizeof(adfNoDataValue) == oType.GetSize());
    1508           8 :                 memcpy(abyNoData.data(), adfNoDataValue,
    1509             :                        sizeof(adfNoDataValue));
    1510             :             }
    1511             :             else
    1512             :             {
    1513          10 :                 CPLAssert(oType.GetNumericDataType() == GDT_CFloat32);
    1514          10 :                 bool bOK = true;
    1515             :                 const float afNoDataValue[2] = {
    1516          10 :                     ParseNoDataComponent<float, uint32_t>(oFillValueArray[0],
    1517             :                                                           bOK),
    1518          10 :                     ParseNoDataComponent<float, uint32_t>(oFillValueArray[1],
    1519             :                                                           bOK),
    1520          20 :                 };
    1521          10 :                 if (!bOK)
    1522             :                 {
    1523           2 :                     CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1524           2 :                     return nullptr;
    1525             :                 }
    1526           8 :                 abyNoData.resize(oType.GetSize());
    1527           8 :                 CPLAssert(sizeof(afNoDataValue) == oType.GetSize());
    1528           8 :                 memcpy(abyNoData.data(), afNoDataValue, sizeof(afNoDataValue));
    1529             :             }
    1530             :         }
    1531             :         else
    1532             :         {
    1533           4 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1534           4 :             return nullptr;
    1535             :         }
    1536             :     }
    1537             :     else
    1538             :     {
    1539           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1540           0 :         return nullptr;
    1541             :     }
    1542             : 
    1543         456 :     const auto oCodecs = oRoot["codecs"].ToArray();
    1544         152 :     std::unique_ptr<ZarrV3CodecSequence> poCodecs;
    1545         152 :     if (oCodecs.Size() > 0)
    1546             :     {
    1547             :         // Byte swapping will be done by the codec chain
    1548          65 :         aoDtypeElts.back().needByteSwapping = false;
    1549             : 
    1550          65 :         ZarrArrayMetadata oInputArrayMetadata;
    1551         151 :         for (auto &nSize : anBlockSize)
    1552          86 :             oInputArrayMetadata.anBlockSizes.push_back(
    1553          86 :                 static_cast<size_t>(nSize));
    1554          65 :         oInputArrayMetadata.oElt = aoDtypeElts.back();
    1555          65 :         poCodecs = std::make_unique<ZarrV3CodecSequence>(oInputArrayMetadata);
    1556          65 :         if (!poCodecs->InitFromJson(oCodecs))
    1557           0 :             return nullptr;
    1558             :     }
    1559             : 
    1560             :     auto poArray =
    1561         152 :         ZarrV3Array::Create(m_poSharedResource, GetFullName(), osArrayName,
    1562         304 :                             aoDims, oType, aoDtypeElts, anBlockSize);
    1563         152 :     if (!poArray)
    1564           1 :         return nullptr;
    1565         151 :     poArray->SetUpdatable(m_bUpdatable);  // must be set before SetAttributes()
    1566         151 :     poArray->SetFilename(osZarrayFilename);
    1567         151 :     poArray->SetIsV2ChunkKeyEncoding(bV2ChunkKeyEncoding);
    1568         151 :     poArray->SetDimSeparator(osDimSeparator);
    1569         151 :     if (!abyNoData.empty())
    1570             :     {
    1571          54 :         poArray->RegisterNoDataValue(abyNoData.data());
    1572             :     }
    1573         151 :     poArray->ParseSpecialAttributes(m_pSelf.lock(), oAttributes);
    1574         151 :     poArray->SetAttributes(oAttributes);
    1575         151 :     poArray->SetDtype(oDtype);
    1576         151 :     if (poCodecs)
    1577          65 :         poArray->SetCodecs(std::move(poCodecs));
    1578         151 :     RegisterArray(poArray);
    1579             : 
    1580             :     // If this is an indexing variable, attach it to the dimension.
    1581         151 :     if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
    1582             :     {
    1583           2 :         auto oIter = m_oMapDimensions.find(poArray->GetName());
    1584           2 :         if (oIter != m_oMapDimensions.end())
    1585             :         {
    1586           2 :             oIter->second->SetIndexingVariable(poArray);
    1587             :         }
    1588             :     }
    1589             : 
    1590         151 :     if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
    1591             :             "CACHE_TILE_PRESENCE", "NO")))
    1592             :     {
    1593           2 :         poArray->CacheTilePresence();
    1594             :     }
    1595             : 
    1596         151 :     return poArray;
    1597             : }

Generated by: LCOV version 1.14