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

Generated by: LCOV version 1.14