LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v3_array.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 649 762 85.2 %
Date: 2025-09-10 17:48:50 Functions: 24 26 92.3 %

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

Generated by: LCOV version 1.14