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 742 84.5 %
Date: 2024-11-21 22:18:42 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       10961 : bool ZarrV3Array::NeedDecodedBuffer() const
     246             : {
     247       21910 :     for (const auto &elt : m_aoDtypeElts)
     248             :     {
     249       10957 :         if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative)
     250             :         {
     251           8 :             return true;
     252             :         }
     253             :     }
     254       10952 :     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       10810 :     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       10802 :     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       11065 : 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       11065 :     bMissingTileOut = false;
     381             : 
     382       21730 :     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       11045 :     osFilename = VSIFileManager::GetHandler(osFilename.c_str())
     387       11080 :                      ->GetStreamingFilename(osFilename);
     388             : 
     389             :     // First if we have a tile presence cache, check tile presence from it
     390       11043 :     if (bUseMutex)
     391       10582 :         m_oMutex.lock();
     392       21938 :     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       11073 :     VSILFILE *fp = nullptr;
     422             :     // This is the number of files returned in a S3 directory listing operation
     423       11073 :     constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
     424       11073 :     const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
     425             :                                            nullptr};
     426       22144 :     if ((m_osDimSeparator == "/" && !m_anBlockSize.empty() &&
     427       33180 :          m_anBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
     428       11043 :         (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       11052 :         fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
     439             :     }
     440       11053 :     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       10754 :     bMissingTileOut = false;
     450             : 
     451       10754 :     CPLAssert(abyRawTileData.capacity() >= m_nTileSize);
     452             :     // should not fail
     453       10718 :     abyRawTileData.resize(m_nTileSize);
     454             : 
     455       10687 :     bool bRet = true;
     456       10687 :     size_t nRawDataSize = abyRawTileData.size();
     457       10658 :     if (poCodecs == nullptr)
     458             :     {
     459        5296 :         nRawDataSize = VSIFReadL(&abyRawTileData[0], 1, nRawDataSize, fp);
     460             :     }
     461             :     else
     462             :     {
     463        5362 :         VSIFSeekL(fp, 0, SEEK_END);
     464        5354 :         const auto nSize = VSIFTellL(fp);
     465        5269 :         VSIFSeekL(fp, 0, SEEK_SET);
     466        5202 :         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        5199 :                 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       10509 :             if (bRet && (abyRawTileData.empty() ||
     487        5302 :                          VSIFReadL(&abyRawTileData[0], 1, abyRawTileData.size(),
     488        5238 :                                    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        5213 :                 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       10662 :     VSIFCloseL(fp);
     508       10574 :     if (!bRet)
     509           0 :         return false;
     510             : 
     511       10574 :     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       10419 :     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         243 :         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       10493 :     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       10670 :                 jobStruct->panReqTilesIndices->data() + iReq * l_nDims;
     642             : 
     643       10672 :             uint64_t nTileIdx = 0;
     644       32016 :             for (size_t j = 0; j < l_nDims; ++j)
     645             :             {
     646       21344 :                 if (j > 0)
     647       10672 :                     nTileIdx *= aoDims[j - 1]->GetSize();
     648       21344 :                 nTileIdx += tileIndices[j];
     649             :             }
     650             : 
     651       10672 :             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       10659 :             bool bIsEmpty = false;
     660       10659 :             bool success = poArray->LoadTileData(tileIndices,
     661             :                                                  true,  // use mutex
     662             :                                                  poCodecs.get(), abyRawTileData,
     663             :                                                  abyDecodedTileData, bIsEmpty);
     664             : 
     665       10562 :             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          15 :             std::lock_guard<std::mutex> oLock(m_oMutex);
     705          15 :             if (nRemainingThreads == 0)
     706           4 :                 break;
     707             :         }
     708          11 :         wtp->WaitEvent();
     709          11 :     }
     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 = CPLGetDirname(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       21817 : std::string ZarrV3Array::BuildTileFilename(const uint64_t *tileIndices) const
     815             : {
     816       21817 :     if (m_aoDims.empty())
     817             :     {
     818             :         return CPLFormFilename(CPLGetDirname(m_osFilename.c_str()),
     819           0 :                                m_bV2ChunkKeyEncoding ? "0" : "c", nullptr);
     820             :     }
     821             :     else
     822             :     {
     823       43595 :         std::string osFilename(CPLGetDirname(m_osFilename.c_str()));
     824       21799 :         osFilename += '/';
     825       21823 :         if (!m_bV2ChunkKeyEncoding)
     826             :         {
     827       21809 :             osFilename += 'c';
     828             :         }
     829       65333 :         for (size_t i = 0; i < m_aoDims.size(); ++i)
     830             :         {
     831       43493 :             if (i > 0 || !m_bV2ChunkKeyEncoding)
     832       43504 :                 osFilename += m_osDimSeparator;
     833       43475 :             osFilename += std::to_string(tileIndices[i]);
     834             :         }
     835       21848 :         return osFilename;
     836             :     }
     837             : }
     838             : 
     839             : /************************************************************************/
     840             : /*                          GetDataDirectory()                          */
     841             : /************************************************************************/
     842             : 
     843           2 : std::string ZarrV3Array::GetDataDirectory() const
     844             : {
     845           2 :     return std::string(CPLGetDirname(m_osFilename.c_str()));
     846             : }
     847             : 
     848             : /************************************************************************/
     849             : /*                        GetTileIndicesFromFilename()                  */
     850             : /************************************************************************/
     851             : 
     852             : CPLStringList
     853           4 : ZarrV3Array::GetTileIndicesFromFilename(const char *pszFilename) const
     854             : {
     855           4 :     if (!m_bV2ChunkKeyEncoding)
     856             :     {
     857           4 :         if (pszFilename[0] != 'c')
     858           2 :             return CPLStringList();
     859           2 :         if (m_osDimSeparator == "/")
     860             :         {
     861           2 :             if (pszFilename[1] != '/' && pszFilename[1] != '\\')
     862           0 :                 return CPLStringList();
     863             :         }
     864           0 :         else if (pszFilename[1] != m_osDimSeparator[0])
     865             :         {
     866           0 :             return CPLStringList();
     867             :         }
     868             :     }
     869             :     return CPLStringList(
     870           2 :         CSLTokenizeString2(pszFilename + (m_bV2ChunkKeyEncoding ? 0 : 2),
     871           4 :                            m_osDimSeparator.c_str(), 0));
     872             : }
     873             : 
     874             : /************************************************************************/
     875             : /*                           ParseDtypeV3()                             */
     876             : /************************************************************************/
     877             : 
     878         170 : static GDALExtendedDataType ParseDtypeV3(const CPLJSONObject &obj,
     879             :                                          std::vector<DtypeElt> &elts)
     880             : {
     881             :     do
     882             :     {
     883         170 :         if (obj.GetType() == CPLJSONObject::Type::String)
     884             :         {
     885         340 :             const auto str = obj.ToString();
     886         170 :             DtypeElt elt;
     887         170 :             GDALDataType eDT = GDT_Unknown;
     888             : 
     889         170 :             if (str == "bool")  // boolean
     890             :             {
     891           0 :                 elt.nativeType = DtypeElt::NativeType::BOOLEAN;
     892           0 :                 eDT = GDT_Byte;
     893             :             }
     894         170 :             else if (str == "int8")
     895             :             {
     896           6 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     897           6 :                 eDT = GDT_Int8;
     898             :             }
     899         164 :             else if (str == "uint8")
     900             :             {
     901          65 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     902          65 :                 eDT = GDT_Byte;
     903             :             }
     904          99 :             else if (str == "int16")
     905             :             {
     906          10 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     907          10 :                 eDT = GDT_Int16;
     908             :             }
     909          89 :             else if (str == "uint16")
     910             :             {
     911           7 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     912           7 :                 eDT = GDT_UInt16;
     913             :             }
     914          82 :             else if (str == "int32")
     915             :             {
     916           7 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     917           7 :                 eDT = GDT_Int32;
     918             :             }
     919          75 :             else if (str == "uint32")
     920             :             {
     921           7 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     922           7 :                 eDT = GDT_UInt32;
     923             :             }
     924          68 :             else if (str == "int64")
     925             :             {
     926           7 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     927           7 :                 eDT = GDT_Int64;
     928             :             }
     929          61 :             else if (str == "uint64")
     930             :             {
     931           6 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     932           6 :                 eDT = GDT_UInt64;
     933             :             }
     934          55 :             else if (str == "float16")
     935             :             {
     936           0 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
     937           0 :                 elt.nativeSize = 2;
     938           0 :                 elt.gdalTypeIsApproxOfNative = true;
     939           0 :                 eDT = GDT_Float32;
     940             :             }
     941          55 :             else if (str == "float32")
     942             :             {
     943           9 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
     944           9 :                 eDT = GDT_Float32;
     945             :             }
     946          46 :             else if (str == "float64")
     947             :             {
     948          15 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
     949          15 :                 eDT = GDT_Float64;
     950             :             }
     951          31 :             else if (str == "complex64")
     952             :             {
     953          15 :                 elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     954          15 :                 eDT = GDT_CFloat32;
     955             :             }
     956          16 :             else if (str == "complex128")
     957             :             {
     958          15 :                 elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     959          15 :                 eDT = GDT_CFloat64;
     960             :             }
     961             :             else
     962           1 :                 break;
     963             : 
     964         169 :             elt.gdalType = GDALExtendedDataType::Create(eDT);
     965         169 :             elt.gdalSize = elt.gdalType.GetSize();
     966         169 :             if (!elt.gdalTypeIsApproxOfNative)
     967         169 :                 elt.nativeSize = elt.gdalSize;
     968             : 
     969         169 :             if (elt.nativeSize > 1)
     970             :             {
     971          98 :                 elt.needByteSwapping = (CPL_IS_LSB == 0);
     972             :             }
     973             : 
     974         169 :             elts.emplace_back(elt);
     975         169 :             return GDALExtendedDataType::Create(eDT);
     976             :         }
     977             :     } while (false);
     978           1 :     CPLError(CE_Failure, CPLE_AppDefined,
     979             :              "Invalid or unsupported format for data_type: %s",
     980           2 :              obj.ToString().c_str());
     981           1 :     return GDALExtendedDataType::Create(GDT_Unknown);
     982             : }
     983             : 
     984             : /************************************************************************/
     985             : /*                    ParseNoDataStringAsDouble()                       */
     986             : /************************************************************************/
     987             : 
     988          37 : static double ParseNoDataStringAsDouble(const std::string &osVal, bool &bOK)
     989             : {
     990          37 :     double dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
     991          37 :     if (osVal == "NaN")
     992             :     {
     993             :         // initialized above
     994             :     }
     995          15 :     else if (osVal == "Infinity" || osVal == "+Infinity")
     996             :     {
     997           5 :         dfNoDataValue = std::numeric_limits<double>::infinity();
     998             :     }
     999          10 :     else if (osVal == "-Infinity")
    1000             :     {
    1001           5 :         dfNoDataValue = -std::numeric_limits<double>::infinity();
    1002             :     }
    1003             :     else
    1004             :     {
    1005           5 :         bOK = false;
    1006             :     }
    1007          37 :     return dfNoDataValue;
    1008             : }
    1009             : 
    1010             : /************************************************************************/
    1011             : /*                     ParseNoDataComponent()                           */
    1012             : /************************************************************************/
    1013             : 
    1014             : template <typename T, typename Tint>
    1015          40 : static T ParseNoDataComponent(const CPLJSONObject &oObj, bool &bOK)
    1016             : {
    1017          40 :     if (oObj.GetType() == CPLJSONObject::Type::Integer ||
    1018          62 :         oObj.GetType() == CPLJSONObject::Type::Long ||
    1019          22 :         oObj.GetType() == CPLJSONObject::Type::Double)
    1020             :     {
    1021          22 :         return static_cast<T>(oObj.ToDouble());
    1022             :     }
    1023          18 :     else if (oObj.GetType() == CPLJSONObject::Type::String)
    1024             :     {
    1025          54 :         const auto osVal = oObj.ToString();
    1026          18 :         if (STARTS_WITH(osVal.c_str(), "0x"))
    1027             :         {
    1028           2 :             if (osVal.size() > 2 + 2 * sizeof(T))
    1029             :             {
    1030           0 :                 bOK = false;
    1031           0 :                 return 0;
    1032             :             }
    1033           2 :             Tint nVal = static_cast<Tint>(
    1034           2 :                 std::strtoull(osVal.c_str() + 2, nullptr, 16));
    1035             :             T fVal;
    1036             :             static_assert(sizeof(nVal) == sizeof(fVal),
    1037             :                           "sizeof(nVal) == sizeof(dfVal)");
    1038           2 :             memcpy(&fVal, &nVal, sizeof(nVal));
    1039           2 :             return fVal;
    1040             :         }
    1041             :         else
    1042             :         {
    1043          16 :             return static_cast<T>(ParseNoDataStringAsDouble(osVal, bOK));
    1044             :         }
    1045             :     }
    1046             :     else
    1047             :     {
    1048           0 :         bOK = false;
    1049           0 :         return 0;
    1050             :     }
    1051             : }
    1052             : 
    1053             : /************************************************************************/
    1054             : /*                     ZarrV3Group::LoadArray()                         */
    1055             : /************************************************************************/
    1056             : 
    1057             : std::shared_ptr<ZarrArray>
    1058         183 : ZarrV3Group::LoadArray(const std::string &osArrayName,
    1059             :                        const std::string &osZarrayFilename,
    1060             :                        const CPLJSONObject &oRoot) const
    1061             : {
    1062             :     // Add osZarrayFilename to m_poSharedResource during the scope
    1063             :     // of this function call.
    1064         183 :     ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
    1065         366 :                                                        osZarrayFilename);
    1066         183 :     if (!filenameAdder.ok())
    1067           0 :         return nullptr;
    1068             : 
    1069             :     // Warn about unknown members (the spec suggests to error out, but let be
    1070             :     // a bit more lenient)
    1071        1766 :     for (const auto &oNode : oRoot.GetChildren())
    1072             :     {
    1073        3166 :         const auto osName = oNode.GetName();
    1074        4200 :         if (osName != "zarr_format" && osName != "node_type" &&
    1075        3105 :             osName != "shape" && osName != "chunk_grid" &&
    1076        2013 :             osName != "data_type" && osName != "chunk_key_encoding" &&
    1077         795 :             osName != "fill_value" &&
    1078             :             // Below are optional
    1079         627 :             osName != "dimension_names" && osName != "codecs" &&
    1080        3111 :             osName != "storage_transformers" && osName != "attributes")
    1081             :         {
    1082           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    1083             :                      "%s array definition contains a unknown member (%s). "
    1084             :                      "Interpretation of the array might be wrong.",
    1085             :                      osZarrayFilename.c_str(), osName.c_str());
    1086             :         }
    1087             :     }
    1088             : 
    1089         549 :     const auto oStorageTransformers = oRoot["storage_transformers"].ToArray();
    1090         183 :     if (oStorageTransformers.Size() > 0)
    1091             :     {
    1092           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1093             :                  "storage_transformers are not supported.");
    1094           1 :         return nullptr;
    1095             :     }
    1096             : 
    1097         546 :     const auto oShape = oRoot["shape"].ToArray();
    1098         182 :     if (!oShape.IsValid())
    1099             :     {
    1100           2 :         CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
    1101           2 :         return nullptr;
    1102             :     }
    1103             : 
    1104             :     // Parse chunk_grid
    1105         540 :     const auto oChunkGrid = oRoot["chunk_grid"];
    1106         180 :     if (oChunkGrid.GetType() != CPLJSONObject::Type::Object)
    1107             :     {
    1108           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1109             :                  "chunk_grid missing or not an object");
    1110           1 :         return nullptr;
    1111             :     }
    1112             : 
    1113         537 :     const auto oChunkGridName = oChunkGrid["name"];
    1114         179 :     if (oChunkGridName.ToString() != "regular")
    1115             :     {
    1116           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1117             :                  "Only chunk_grid.name = regular supported");
    1118           1 :         return nullptr;
    1119             :     }
    1120             : 
    1121         534 :     const auto oChunks = oChunkGrid["configuration"]["chunk_shape"].ToArray();
    1122         178 :     if (!oChunks.IsValid())
    1123             :     {
    1124           1 :         CPLError(
    1125             :             CE_Failure, CPLE_AppDefined,
    1126             :             "chunk_grid.configuration.chunk_shape missing or not an array");
    1127           1 :         return nullptr;
    1128             :     }
    1129             : 
    1130         177 :     if (oShape.Size() != oChunks.Size())
    1131             :     {
    1132           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1133             :                  "shape and chunks arrays are of different size");
    1134           1 :         return nullptr;
    1135             :     }
    1136             : 
    1137             :     // Parse chunk_key_encoding
    1138         528 :     const auto oChunkKeyEncoding = oRoot["chunk_key_encoding"];
    1139         176 :     if (oChunkKeyEncoding.GetType() != CPLJSONObject::Type::Object)
    1140             :     {
    1141           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1142             :                  "chunk_key_encoding missing or not an object");
    1143           1 :         return nullptr;
    1144             :     }
    1145             : 
    1146         350 :     std::string osDimSeparator;
    1147         175 :     bool bV2ChunkKeyEncoding = false;
    1148         525 :     const auto oChunkKeyEncodingName = oChunkKeyEncoding["name"];
    1149         175 :     if (oChunkKeyEncodingName.ToString() == "default")
    1150             :     {
    1151         171 :         osDimSeparator = "/";
    1152             :     }
    1153           4 :     else if (oChunkKeyEncodingName.ToString() == "v2")
    1154             :     {
    1155           3 :         osDimSeparator = ".";
    1156           3 :         bV2ChunkKeyEncoding = true;
    1157             :     }
    1158             :     else
    1159             :     {
    1160           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1161             :                  "Unsupported chunk_key_encoding.name");
    1162           1 :         return nullptr;
    1163             :     }
    1164             : 
    1165             :     {
    1166         348 :         auto oConfiguration = oChunkKeyEncoding["configuration"];
    1167         174 :         if (oConfiguration.GetType() == CPLJSONObject::Type::Object)
    1168             :         {
    1169         256 :             auto oSeparator = oConfiguration["separator"];
    1170         128 :             if (oSeparator.IsValid())
    1171             :             {
    1172         128 :                 osDimSeparator = oSeparator.ToString();
    1173         128 :                 if (osDimSeparator != "/" && osDimSeparator != ".")
    1174             :                 {
    1175           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1176             :                              "Separator can only be '/' or '.'");
    1177           1 :                     return nullptr;
    1178             :                 }
    1179             :             }
    1180             :         }
    1181             :     }
    1182             : 
    1183         519 :     CPLJSONObject oAttributes = oRoot["attributes"];
    1184             : 
    1185             :     // Deep-clone of oAttributes
    1186         173 :     if (oAttributes.IsValid())
    1187             :     {
    1188         123 :         oAttributes = oAttributes.Clone();
    1189             :     }
    1190             : 
    1191         346 :     std::vector<std::shared_ptr<GDALDimension>> aoDims;
    1192         395 :     for (int i = 0; i < oShape.Size(); ++i)
    1193             :     {
    1194         222 :         const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
    1195         222 :         if (nSize == 0)
    1196             :         {
    1197           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
    1198           0 :             return nullptr;
    1199             :         }
    1200         222 :         aoDims.emplace_back(std::make_shared<ZarrDimension>(
    1201         222 :             m_poSharedResource,
    1202         444 :             std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
    1203         444 :             std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
    1204         222 :             nSize));
    1205             :     }
    1206             : 
    1207             :     // Deal with dimension_names
    1208         519 :     const auto dimensionNames = oRoot["dimension_names"];
    1209             : 
    1210         166 :     const auto FindDimension = [this, &aoDims, &osArrayName, &oAttributes](
    1211             :                                    const std::string &osDimName,
    1212        2212 :                                    std::shared_ptr<GDALDimension> &poDim, int i)
    1213             :     {
    1214         166 :         auto oIter = m_oMapDimensions.find(osDimName);
    1215         166 :         if (oIter != m_oMapDimensions.end())
    1216             :         {
    1217           0 :             if (m_bDimSizeInUpdate ||
    1218           0 :                 oIter->second->GetSize() == poDim->GetSize())
    1219             :             {
    1220           0 :                 poDim = oIter->second;
    1221           0 :                 return true;
    1222             :             }
    1223             :             else
    1224             :             {
    1225           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1226             :                          "Size of _ARRAY_DIMENSIONS[%d] different "
    1227             :                          "from the one of shape",
    1228             :                          i);
    1229           0 :                 return false;
    1230             :             }
    1231             :         }
    1232             : 
    1233             :         // Try to load the indexing variable.
    1234             :         // Not in m_oMapMDArrays,
    1235             :         // then stat() the indexing variable.
    1236         330 :         else if (osArrayName != osDimName &&
    1237         330 :                  m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
    1238             :         {
    1239         328 :             std::string osDirName = m_osDirectoryName;
    1240             :             while (true)
    1241             :             {
    1242             :                 const std::string osArrayFilenameDim =
    1243             :                     CPLFormFilename(CPLFormFilename(osDirName.c_str(),
    1244             :                                                     osDimName.c_str(), nullptr),
    1245         483 :                                     "zarr.json", nullptr);
    1246             :                 VSIStatBufL sStat;
    1247         483 :                 if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
    1248             :                 {
    1249           4 :                     CPLJSONDocument oDoc;
    1250           2 :                     if (oDoc.Load(osArrayFilenameDim))
    1251             :                     {
    1252           2 :                         LoadArray(osDimName, osArrayFilenameDim,
    1253           4 :                                   oDoc.GetRoot());
    1254             :                     }
    1255             :                 }
    1256             :                 else
    1257             :                 {
    1258             :                     // Recurse to upper level for datasets such as
    1259             :                     // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
    1260             :                     const std::string osDirNameNew =
    1261         481 :                         CPLGetPath(osDirName.c_str());
    1262         481 :                     if (!osDirNameNew.empty() && osDirNameNew != osDirName)
    1263             :                     {
    1264         319 :                         osDirName = osDirNameNew;
    1265         319 :                         continue;
    1266             :                     }
    1267             :                 }
    1268         164 :                 break;
    1269         319 :             }
    1270             :         }
    1271             : 
    1272         166 :         oIter = m_oMapDimensions.find(osDimName);
    1273             :         // cppcheck-suppress knownConditionTrueFalse
    1274         168 :         if (oIter != m_oMapDimensions.end() &&
    1275           2 :             oIter->second->GetSize() == poDim->GetSize())
    1276             :         {
    1277           2 :             poDim = oIter->second;
    1278           2 :             return true;
    1279             :         }
    1280             : 
    1281         328 :         std::string osType;
    1282         328 :         std::string osDirection;
    1283         164 :         if (aoDims.size() == 1 && osArrayName == osDimName)
    1284             :         {
    1285           2 :             ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
    1286             :                                                  osDirection);
    1287             :         }
    1288             : 
    1289             :         auto poDimLocal = std::make_shared<ZarrDimension>(
    1290         164 :             m_poSharedResource,
    1291         328 :             std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
    1292         328 :             GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
    1293         164 :         poDimLocal->SetXArrayDimension();
    1294         164 :         m_oMapDimensions[osDimName] = poDimLocal;
    1295         164 :         poDim = poDimLocal;
    1296         164 :         return true;
    1297         173 :     };
    1298             : 
    1299         173 :     if (dimensionNames.GetType() == CPLJSONObject::Type::Array)
    1300             :     {
    1301         112 :         const auto arrayDims = dimensionNames.ToArray();
    1302         112 :         if (arrayDims.Size() == oShape.Size())
    1303             :         {
    1304         277 :             for (int i = 0; i < oShape.Size(); ++i)
    1305             :             {
    1306         166 :                 if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
    1307             :                 {
    1308         498 :                     const auto osDimName = arrayDims[i].ToString();
    1309         166 :                     FindDimension(osDimName, aoDims[i], i);
    1310             :                 }
    1311             :             }
    1312             :         }
    1313             :         else
    1314             :         {
    1315           1 :             CPLError(
    1316             :                 CE_Failure, CPLE_AppDefined,
    1317             :                 "Size of dimension_names[] different from the one of shape");
    1318           1 :             return nullptr;
    1319             :         }
    1320             :     }
    1321          61 :     else if (dimensionNames.IsValid())
    1322             :     {
    1323           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1324             :                  "dimension_names should be an array");
    1325           1 :         return nullptr;
    1326             :     }
    1327             : 
    1328         513 :     auto oDtype = oRoot["data_type"];
    1329         171 :     if (!oDtype.IsValid())
    1330             :     {
    1331           1 :         CPLError(CE_Failure, CPLE_NotSupported, "data_type missing");
    1332           1 :         return nullptr;
    1333             :     }
    1334         170 :     if (oDtype["fallback"].IsValid())
    1335           1 :         oDtype = oDtype["fallback"];
    1336         340 :     std::vector<DtypeElt> aoDtypeElts;
    1337         340 :     const auto oType = ParseDtypeV3(oDtype, aoDtypeElts);
    1338         340 :     if (oType.GetClass() == GEDTC_NUMERIC &&
    1339         170 :         oType.GetNumericDataType() == GDT_Unknown)
    1340           1 :         return nullptr;
    1341             : 
    1342         338 :     std::vector<GUInt64> anBlockSize;
    1343         169 :     if (!ZarrArray::ParseChunkSize(oChunks, oType, anBlockSize))
    1344           1 :         return nullptr;
    1345             : 
    1346         336 :     std::vector<GByte> abyNoData;
    1347             : 
    1348         504 :     auto oFillValue = oRoot["fill_value"];
    1349         168 :     auto eFillValueType = oFillValue.GetType();
    1350             : 
    1351         168 :     if (!oFillValue.IsValid())
    1352             :     {
    1353           0 :         CPLError(CE_Warning, CPLE_AppDefined, "Missing fill_value is invalid");
    1354             :     }
    1355         168 :     else if (eFillValueType == CPLJSONObject::Type::Null)
    1356             :     {
    1357          97 :         CPLError(CE_Warning, CPLE_AppDefined, "fill_value = null is invalid");
    1358             :     }
    1359          71 :     else if (GDALDataTypeIsComplex(oType.GetNumericDataType()) &&
    1360             :              eFillValueType != CPLJSONObject::Type::Array)
    1361             :     {
    1362           4 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1363           4 :         return nullptr;
    1364             :     }
    1365          67 :     else if (eFillValueType == CPLJSONObject::Type::String)
    1366             :     {
    1367          54 :         const auto osFillValue = oFillValue.ToString();
    1368          27 :         if (STARTS_WITH(osFillValue.c_str(), "0x"))
    1369             :         {
    1370           3 :             if (osFillValue.size() > 2 + 2 * oType.GetSize())
    1371             :             {
    1372           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1373           1 :                 return nullptr;
    1374             :             }
    1375             :             uint64_t nVal = static_cast<uint64_t>(
    1376           3 :                 std::strtoull(osFillValue.c_str() + 2, nullptr, 16));
    1377           3 :             if (oType.GetSize() == 4)
    1378             :             {
    1379           1 :                 abyNoData.resize(oType.GetSize());
    1380           1 :                 uint32_t nTmp = static_cast<uint32_t>(nVal);
    1381           1 :                 memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
    1382             :             }
    1383           2 :             else if (oType.GetSize() == 8)
    1384             :             {
    1385           1 :                 abyNoData.resize(oType.GetSize());
    1386           1 :                 memcpy(&abyNoData[0], &nVal, sizeof(nVal));
    1387             :             }
    1388             :             else
    1389             :             {
    1390           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1391             :                          "Hexadecimal representation of fill_value no "
    1392             :                          "supported for this data type");
    1393           1 :                 return nullptr;
    1394             :             }
    1395             :         }
    1396          24 :         else if (STARTS_WITH(osFillValue.c_str(), "0b"))
    1397             :         {
    1398           3 :             if (osFillValue.size() > 2 + 8 * oType.GetSize())
    1399             :             {
    1400           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1401           1 :                 return nullptr;
    1402             :             }
    1403             :             uint64_t nVal = static_cast<uint64_t>(
    1404           3 :                 std::strtoull(osFillValue.c_str() + 2, nullptr, 2));
    1405           3 :             if (oType.GetSize() == 4)
    1406             :             {
    1407           1 :                 abyNoData.resize(oType.GetSize());
    1408           1 :                 uint32_t nTmp = static_cast<uint32_t>(nVal);
    1409           1 :                 memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
    1410             :             }
    1411           2 :             else if (oType.GetSize() == 8)
    1412             :             {
    1413           1 :                 abyNoData.resize(oType.GetSize());
    1414           1 :                 memcpy(&abyNoData[0], &nVal, sizeof(nVal));
    1415             :             }
    1416             :             else
    1417             :             {
    1418           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1419             :                          "Binary representation of fill_value no supported for "
    1420             :                          "this data type");
    1421           1 :                 return nullptr;
    1422             :             }
    1423             :         }
    1424             :         else
    1425             :         {
    1426          21 :             bool bOK = true;
    1427          21 :             double dfNoDataValue = ParseNoDataStringAsDouble(osFillValue, bOK);
    1428          21 :             if (!bOK)
    1429             :             {
    1430           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1431           2 :                 return nullptr;
    1432             :             }
    1433          20 :             else if (oType.GetNumericDataType() == GDT_Float32)
    1434             :             {
    1435           7 :                 const float fNoDataValue = static_cast<float>(dfNoDataValue);
    1436           7 :                 abyNoData.resize(sizeof(fNoDataValue));
    1437           7 :                 memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
    1438             :             }
    1439          13 :             else if (oType.GetNumericDataType() == GDT_Float64)
    1440             :             {
    1441          12 :                 abyNoData.resize(sizeof(dfNoDataValue));
    1442          12 :                 memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
    1443             :             }
    1444             :             else
    1445             :             {
    1446           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1447             :                          "Invalid fill_value for this data type");
    1448           1 :                 return nullptr;
    1449             :             }
    1450             :         }
    1451             :     }
    1452          40 :     else if (eFillValueType == CPLJSONObject::Type::Boolean ||
    1453          25 :              eFillValueType == CPLJSONObject::Type::Integer ||
    1454          25 :              eFillValueType == CPLJSONObject::Type::Long ||
    1455             :              eFillValueType == CPLJSONObject::Type::Double)
    1456             :     {
    1457          16 :         const double dfNoDataValue = oFillValue.ToDouble();
    1458          16 :         if (oType.GetNumericDataType() == GDT_Int64)
    1459             :         {
    1460             :             const int64_t nNoDataValue =
    1461           1 :                 static_cast<int64_t>(oFillValue.ToLong());
    1462           1 :             abyNoData.resize(oType.GetSize());
    1463           1 :             GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
    1464             :                           oType.GetNumericDataType(), 0, 1);
    1465             :         }
    1466          15 :         else if (oType.GetNumericDataType() == GDT_UInt64 &&
    1467             :                  /* we can't really deal with nodata value between */
    1468             :                  /* int64::max and uint64::max due to json-c limitations */
    1469           0 :                  dfNoDataValue >= 0)
    1470             :         {
    1471             :             const int64_t nNoDataValue =
    1472           0 :                 static_cast<int64_t>(oFillValue.ToLong());
    1473           0 :             abyNoData.resize(oType.GetSize());
    1474           0 :             GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
    1475             :                           oType.GetNumericDataType(), 0, 1);
    1476             :         }
    1477             :         else
    1478             :         {
    1479          15 :             abyNoData.resize(oType.GetSize());
    1480          15 :             GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
    1481             :                           oType.GetNumericDataType(), 0, 1);
    1482          16 :         }
    1483             :     }
    1484          24 :     else if (eFillValueType == CPLJSONObject::Type::Array)
    1485             :     {
    1486          24 :         const auto oFillValueArray = oFillValue.ToArray();
    1487          44 :         if (oFillValueArray.Size() == 2 &&
    1488          20 :             GDALDataTypeIsComplex(oType.GetNumericDataType()))
    1489             :         {
    1490          20 :             if (oType.GetNumericDataType() == GDT_CFloat64)
    1491             :             {
    1492          10 :                 bool bOK = true;
    1493             :                 const double adfNoDataValue[2] = {
    1494          10 :                     ParseNoDataComponent<double, uint64_t>(oFillValueArray[0],
    1495             :                                                            bOK),
    1496          10 :                     ParseNoDataComponent<double, uint64_t>(oFillValueArray[1],
    1497             :                                                            bOK),
    1498          20 :                 };
    1499          10 :                 if (!bOK)
    1500             :                 {
    1501           2 :                     CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1502           2 :                     return nullptr;
    1503             :                 }
    1504           8 :                 abyNoData.resize(oType.GetSize());
    1505           8 :                 CPLAssert(sizeof(adfNoDataValue) == oType.GetSize());
    1506           8 :                 memcpy(abyNoData.data(), adfNoDataValue,
    1507             :                        sizeof(adfNoDataValue));
    1508             :             }
    1509             :             else
    1510             :             {
    1511          10 :                 CPLAssert(oType.GetNumericDataType() == GDT_CFloat32);
    1512          10 :                 bool bOK = true;
    1513             :                 const float afNoDataValue[2] = {
    1514          10 :                     ParseNoDataComponent<float, uint32_t>(oFillValueArray[0],
    1515             :                                                           bOK),
    1516          10 :                     ParseNoDataComponent<float, uint32_t>(oFillValueArray[1],
    1517             :                                                           bOK),
    1518          20 :                 };
    1519          10 :                 if (!bOK)
    1520             :                 {
    1521           2 :                     CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1522           2 :                     return nullptr;
    1523             :                 }
    1524           8 :                 abyNoData.resize(oType.GetSize());
    1525           8 :                 CPLAssert(sizeof(afNoDataValue) == oType.GetSize());
    1526           8 :                 memcpy(abyNoData.data(), afNoDataValue, sizeof(afNoDataValue));
    1527             :             }
    1528             :         }
    1529             :         else
    1530             :         {
    1531           4 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1532           4 :             return nullptr;
    1533             :         }
    1534             :     }
    1535             :     else
    1536             :     {
    1537           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1538           0 :         return nullptr;
    1539             :     }
    1540             : 
    1541         456 :     const auto oCodecs = oRoot["codecs"].ToArray();
    1542         152 :     std::unique_ptr<ZarrV3CodecSequence> poCodecs;
    1543         152 :     if (oCodecs.Size() > 0)
    1544             :     {
    1545             :         // Byte swapping will be done by the codec chain
    1546          65 :         aoDtypeElts.back().needByteSwapping = false;
    1547             : 
    1548          65 :         ZarrArrayMetadata oInputArrayMetadata;
    1549         151 :         for (auto &nSize : anBlockSize)
    1550          86 :             oInputArrayMetadata.anBlockSizes.push_back(
    1551          86 :                 static_cast<size_t>(nSize));
    1552          65 :         oInputArrayMetadata.oElt = aoDtypeElts.back();
    1553          65 :         poCodecs = std::make_unique<ZarrV3CodecSequence>(oInputArrayMetadata);
    1554          65 :         if (!poCodecs->InitFromJson(oCodecs))
    1555           0 :             return nullptr;
    1556             :     }
    1557             : 
    1558             :     auto poArray =
    1559         152 :         ZarrV3Array::Create(m_poSharedResource, GetFullName(), osArrayName,
    1560         304 :                             aoDims, oType, aoDtypeElts, anBlockSize);
    1561         152 :     if (!poArray)
    1562           1 :         return nullptr;
    1563         151 :     poArray->SetUpdatable(m_bUpdatable);  // must be set before SetAttributes()
    1564         151 :     poArray->SetFilename(osZarrayFilename);
    1565         151 :     poArray->SetIsV2ChunkKeyEncoding(bV2ChunkKeyEncoding);
    1566         151 :     poArray->SetDimSeparator(osDimSeparator);
    1567         151 :     if (!abyNoData.empty())
    1568             :     {
    1569          54 :         poArray->RegisterNoDataValue(abyNoData.data());
    1570             :     }
    1571         151 :     poArray->ParseSpecialAttributes(m_pSelf.lock(), oAttributes);
    1572         151 :     poArray->SetAttributes(oAttributes);
    1573         151 :     poArray->SetDtype(oDtype);
    1574         151 :     if (poCodecs)
    1575          65 :         poArray->SetCodecs(std::move(poCodecs));
    1576         151 :     RegisterArray(poArray);
    1577             : 
    1578             :     // If this is an indexing variable, attach it to the dimension.
    1579         151 :     if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
    1580             :     {
    1581           2 :         auto oIter = m_oMapDimensions.find(poArray->GetName());
    1582           2 :         if (oIter != m_oMapDimensions.end())
    1583             :         {
    1584           2 :             oIter->second->SetIndexingVariable(poArray);
    1585             :         }
    1586             :     }
    1587             : 
    1588         151 :     if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
    1589             :             "CACHE_TILE_PRESENCE", "NO")))
    1590             :     {
    1591           2 :         poArray->CacheTilePresence();
    1592             :     }
    1593             : 
    1594         151 :     return poArray;
    1595             : }

Generated by: LCOV version 1.14