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

Generated by: LCOV version 1.14