LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v3_array.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 647 757 85.5 %
Date: 2025-08-01 10:10:57 Functions: 23 25 92.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Zarr driver
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_float.h"
      14             : #include "cpl_vsi_virtual.h"
      15             : #include "gdal_thread_pool.h"
      16             : #include "zarr.h"
      17             : 
      18             : #include <algorithm>
      19             : #include <cassert>
      20             : #include <cmath>
      21             : #include <cstdlib>
      22             : #include <limits>
      23             : #include <map>
      24             : #include <set>
      25             : 
      26             : /************************************************************************/
      27             : /*                       ZarrV3Array::ZarrV3Array()                     */
      28             : /************************************************************************/
      29             : 
      30         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       21977 :     for (const auto &elt : m_aoDtypeElts)
     254             :     {
     255       10982 :         if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative)
     256             :         {
     257           0 :             return true;
     258             :         }
     259             :     }
     260       10989 :     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       10828 :     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       10832 :     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       11085 : 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       11085 :     bMissingTileOut = false;
     387             : 
     388       21511 :     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       11089 :     osFilename = VSIFileManager::GetHandler(osFilename.c_str())
     393       11091 :                      ->GetStreamingFilename(osFilename);
     394             : 
     395             :     // First if we have a tile presence cache, check tile presence from it
     396       11075 :     if (bUseMutex)
     397       10648 :         m_oMutex.lock();
     398       21829 :     auto poTilePresenceArray = OpenTilePresenceCache(false);
     399       11097 :     if (poTilePresenceArray)
     400             :     {
     401          18 :         std::vector<GUInt64> anTileIdx(m_aoDims.size());
     402          18 :         const std::vector<size_t> anCount(m_aoDims.size(), 1);
     403          18 :         const std::vector<GInt64> anArrayStep(m_aoDims.size(), 0);
     404          18 :         const std::vector<GPtrDiff_t> anBufferStride(m_aoDims.size(), 0);
     405          18 :         const auto eByteDT = GDALExtendedDataType::Create(GDT_Byte);
     406          54 :         for (size_t i = 0; i < m_aoDims.size(); ++i)
     407             :         {
     408          36 :             anTileIdx[i] = static_cast<GUInt64>(tileIndices[i]);
     409             :         }
     410          18 :         GByte byValue = 0;
     411          18 :         if (poTilePresenceArray->Read(anTileIdx.data(), anCount.data(),
     412             :                                       anArrayStep.data(), anBufferStride.data(),
     413          36 :                                       eByteDT, &byValue) &&
     414          18 :             byValue == 0)
     415             :         {
     416          13 :             if (bUseMutex)
     417           0 :                 m_oMutex.unlock();
     418          13 :             CPLDebugOnly(ZARR_DEBUG_KEY, "Tile %s missing (=nodata)",
     419             :                          osFilename.c_str());
     420          13 :             bMissingTileOut = true;
     421          13 :             return true;
     422             :         }
     423             :     }
     424       11084 :     if (bUseMutex)
     425       10672 :         m_oMutex.unlock();
     426             : 
     427       11082 :     VSILFILE *fp = nullptr;
     428             :     // This is the number of files returned in a S3 directory listing operation
     429       11082 :     constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
     430       11082 :     const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
     431             :                                            nullptr};
     432       11082 :     const auto nErrorBefore = CPLGetErrorCounter();
     433       22124 :     if ((m_osDimSeparator == "/" && !m_anBlockSize.empty() &&
     434       33106 :          m_anBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
     435       11055 :         (m_osDimSeparator != "/" &&
     436           4 :          m_nTotalTileCount > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING))
     437             :     {
     438             :         // Avoid issuing ReadDir() when a lot of files are expected
     439             :         CPLConfigOptionSetter optionSetter("GDAL_DISABLE_READDIR_ON_OPEN",
     440           0 :                                            "YES", true);
     441           0 :         fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
     442             :     }
     443             :     else
     444             :     {
     445       11003 :         fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
     446             :     }
     447       11042 :     if (fp == nullptr)
     448             :     {
     449         299 :         if (nErrorBefore != CPLGetErrorCounter())
     450             :         {
     451           0 :             return false;
     452             :         }
     453             :         else
     454             :         {
     455             :             // Missing files are OK and indicate nodata_value
     456         299 :             CPLDebugOnly(ZARR_DEBUG_KEY, "Tile %s missing (=nodata)",
     457             :                          osFilename.c_str());
     458         299 :             bMissingTileOut = true;
     459         299 :             return true;
     460             :         }
     461             :     }
     462             : 
     463       10743 :     bMissingTileOut = false;
     464             : 
     465       10743 :     CPLAssert(abyRawTileData.capacity() >= m_nTileSize);
     466             :     // should not fail
     467       10715 :     abyRawTileData.resize(m_nTileSize);
     468             : 
     469       10618 :     bool bRet = true;
     470       10618 :     size_t nRawDataSize = abyRawTileData.size();
     471       10542 :     if (poCodecs == nullptr)
     472             :     {
     473           6 :         nRawDataSize = VSIFReadL(&abyRawTileData[0], 1, nRawDataSize, fp);
     474             :     }
     475             :     else
     476             :     {
     477       10536 :         VSIFSeekL(fp, 0, SEEK_END);
     478       10511 :         const auto nSize = VSIFTellL(fp);
     479       10353 :         VSIFSeekL(fp, 0, SEEK_SET);
     480       10227 :         if (nSize > static_cast<vsi_l_offset>(std::numeric_limits<int>::max()))
     481             :         {
     482           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Too large tile %s",
     483             :                      osFilename.c_str());
     484           0 :             bRet = false;
     485             :         }
     486             :         else
     487             :         {
     488             :             try
     489             :             {
     490       10315 :                 abyRawTileData.resize(static_cast<size_t>(nSize));
     491             :             }
     492           0 :             catch (const std::exception &)
     493             :             {
     494           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory,
     495             :                          "Cannot allocate memory for tile %s",
     496             :                          osFilename.c_str());
     497           0 :                 bRet = false;
     498             :             }
     499             : 
     500       20476 :             if (bRet && (abyRawTileData.empty() ||
     501       10346 :                          VSIFReadL(&abyRawTileData[0], 1, abyRawTileData.size(),
     502       10254 :                                    fp) != abyRawTileData.size()))
     503             :             {
     504           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     505             :                          "Could not read tile %s correctly",
     506             :                          osFilename.c_str());
     507           0 :                 bRet = false;
     508             :             }
     509             :             else
     510             :             {
     511       10140 :                 if (!poCodecs->Decode(abyRawTileData))
     512             :                 {
     513           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     514             :                              "Decompression of tile %s failed",
     515             :                              osFilename.c_str());
     516           0 :                     bRet = false;
     517             :                 }
     518             :             }
     519             :         }
     520             :     }
     521       10458 :     VSIFCloseL(fp);
     522       10556 :     if (!bRet)
     523           0 :         return false;
     524             : 
     525       10556 :     if (nRawDataSize != abyRawTileData.size())
     526             :     {
     527           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     528             :                  "Decompressed tile %s has not expected size. "
     529             :                  "Got %u instead of %u",
     530             :                  osFilename.c_str(),
     531           0 :                  static_cast<unsigned>(abyRawTileData.size()),
     532             :                  static_cast<unsigned>(nRawDataSize));
     533           0 :         return false;
     534             :     }
     535             : 
     536       10335 :     if (!abyDecodedTileData.empty())
     537             :     {
     538             :         const size_t nSourceSize =
     539           0 :             m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
     540           0 :         const auto nDTSize = m_oType.GetSize();
     541           0 :         const size_t nValues = abyDecodedTileData.size() / nDTSize;
     542           0 :         CPLAssert(nValues == m_nTileSize / nSourceSize);
     543           0 :         const GByte *pSrc = abyRawTileData.data();
     544           0 :         GByte *pDst = &abyDecodedTileData[0];
     545         147 :         for (size_t i = 0; i < nValues;
     546           0 :              i++, pSrc += nSourceSize, pDst += nDTSize)
     547             :         {
     548           0 :             DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
     549             :         }
     550             :     }
     551             : 
     552       10418 :     return true;
     553             : 
     554             : #undef m_abyRawTileData
     555             : #undef m_abyDecodedTileData
     556             : #undef m_poCodecs
     557             : }
     558             : 
     559             : /************************************************************************/
     560             : /*                      ZarrV3Array::IAdviseRead()                      */
     561             : /************************************************************************/
     562             : 
     563           6 : bool ZarrV3Array::IAdviseRead(const GUInt64 *arrayStartIdx, const size_t *count,
     564             :                               CSLConstList papszOptions) const
     565             : {
     566          12 :     std::vector<uint64_t> anIndicesCur;
     567           6 :     int nThreadsMax = 0;
     568          12 :     std::vector<uint64_t> anReqTilesIndices;
     569           6 :     size_t nReqTiles = 0;
     570           6 :     if (!IAdviseReadCommon(arrayStartIdx, count, papszOptions, anIndicesCur,
     571             :                            nThreadsMax, anReqTilesIndices, nReqTiles))
     572             :     {
     573           2 :         return false;
     574             :     }
     575           4 :     if (nThreadsMax <= 1)
     576             :     {
     577           0 :         return true;
     578             :     }
     579             : 
     580             :     const int nThreads =
     581           4 :         static_cast<int>(std::min(static_cast<size_t>(nThreadsMax), nReqTiles));
     582             : 
     583           4 :     CPLWorkerThreadPool *wtp = GDALGetGlobalThreadPool(nThreadsMax);
     584           4 :     if (wtp == nullptr)
     585           0 :         return false;
     586             : 
     587             :     struct JobStruct
     588             :     {
     589             :         JobStruct() = default;
     590             : 
     591             :         JobStruct(const JobStruct &) = delete;
     592             :         JobStruct &operator=(const JobStruct &) = delete;
     593             : 
     594             :         JobStruct(JobStruct &&) = default;
     595             :         JobStruct &operator=(JobStruct &&) = default;
     596             : 
     597             :         const ZarrV3Array *poArray = nullptr;
     598             :         bool *pbGlobalStatus = nullptr;
     599             :         int *pnRemainingThreads = nullptr;
     600             :         const std::vector<uint64_t> *panReqTilesIndices = nullptr;
     601             :         size_t nFirstIdx = 0;
     602             :         size_t nLastIdxNotIncluded = 0;
     603             :     };
     604             : 
     605           4 :     std::vector<JobStruct> asJobStructs;
     606             : 
     607           4 :     bool bGlobalStatus = true;
     608           4 :     int nRemainingThreads = nThreads;
     609             :     // Check for very highly overflow in below loop
     610           4 :     assert(static_cast<size_t>(nThreads) <
     611             :            std::numeric_limits<size_t>::max() / nReqTiles);
     612             : 
     613             :     // Setup jobs
     614          20 :     for (int i = 0; i < nThreads; i++)
     615             :     {
     616          16 :         JobStruct jobStruct;
     617          16 :         jobStruct.poArray = this;
     618          16 :         jobStruct.pbGlobalStatus = &bGlobalStatus;
     619          16 :         jobStruct.pnRemainingThreads = &nRemainingThreads;
     620          16 :         jobStruct.panReqTilesIndices = &anReqTilesIndices;
     621          16 :         jobStruct.nFirstIdx = static_cast<size_t>(i * nReqTiles / nThreads);
     622          16 :         jobStruct.nLastIdxNotIncluded = std::min(
     623          16 :             static_cast<size_t>((i + 1) * nReqTiles / nThreads), nReqTiles);
     624          16 :         asJobStructs.emplace_back(std::move(jobStruct));
     625             :     }
     626             : 
     627          16 :     const auto JobFunc = [](void *pThreadData)
     628             :     {
     629          16 :         const JobStruct *jobStruct =
     630             :             static_cast<const JobStruct *>(pThreadData);
     631             : 
     632          16 :         const auto poArray = jobStruct->poArray;
     633          16 :         const auto &aoDims = poArray->GetDimensions();
     634          16 :         const size_t l_nDims = poArray->GetDimensionCount();
     635          16 :         ZarrByteVectorQuickResize abyRawTileData;
     636          16 :         ZarrByteVectorQuickResize abyDecodedTileData;
     637           0 :         std::unique_ptr<ZarrV3CodecSequence> poCodecs;
     638          16 :         if (poArray->m_poCodecs)
     639             :         {
     640          16 :             std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     641          16 :             poCodecs = poArray->m_poCodecs->Clone();
     642             :         }
     643             : 
     644       10688 :         for (size_t iReq = jobStruct->nFirstIdx;
     645       10688 :              iReq < jobStruct->nLastIdxNotIncluded; ++iReq)
     646             :         {
     647             :             // Check if we must early exit
     648             :             {
     649       10671 :                 std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     650       10672 :                 if (!(*jobStruct->pbGlobalStatus))
     651           0 :                     return;
     652             :             }
     653             : 
     654             :             const uint64_t *tileIndices =
     655       10671 :                 jobStruct->panReqTilesIndices->data() + iReq * l_nDims;
     656             : 
     657       10672 :             uint64_t nTileIdx = 0;
     658       32011 :             for (size_t j = 0; j < l_nDims; ++j)
     659             :             {
     660       21343 :                 if (j > 0)
     661       10671 :                     nTileIdx *= aoDims[j - 1]->GetSize();
     662       21339 :                 nTileIdx += tileIndices[j];
     663             :             }
     664             : 
     665       10668 :             if (!poArray->AllocateWorkingBuffers(abyRawTileData,
     666             :                                                  abyDecodedTileData))
     667             :             {
     668           0 :                 std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     669           0 :                 *jobStruct->pbGlobalStatus = false;
     670           0 :                 break;
     671             :             }
     672             : 
     673       10666 :             bool bIsEmpty = false;
     674       10666 :             bool success = poArray->LoadTileData(tileIndices,
     675             :                                                  true,  // use mutex
     676             :                                                  poCodecs.get(), abyRawTileData,
     677             :                                                  abyDecodedTileData, bIsEmpty);
     678             : 
     679       10478 :             std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     680       10672 :             if (!success)
     681             :             {
     682           0 :                 *jobStruct->pbGlobalStatus = false;
     683           0 :                 break;
     684             :             }
     685             : 
     686       21344 :             CachedTile cachedTile;
     687       10672 :             if (!bIsEmpty)
     688             :             {
     689       10670 :                 if (!abyDecodedTileData.empty())
     690           0 :                     std::swap(cachedTile.abyDecoded, abyDecodedTileData);
     691             :                 else
     692       10670 :                     std::swap(cachedTile.abyDecoded, abyRawTileData);
     693             :             }
     694       10672 :             poArray->m_oMapTileIndexToCachedTile[nTileIdx] =
     695       21344 :                 std::move(cachedTile);
     696             :         }
     697             : 
     698          17 :         std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     699          16 :         (*jobStruct->pnRemainingThreads)--;
     700             :     };
     701             : 
     702             :     // Start jobs
     703          20 :     for (int i = 0; i < nThreads; i++)
     704             :     {
     705          16 :         if (!wtp->SubmitJob(JobFunc, &asJobStructs[i]))
     706             :         {
     707           0 :             std::lock_guard<std::mutex> oLock(m_oMutex);
     708           0 :             bGlobalStatus = false;
     709           0 :             nRemainingThreads = i;
     710           0 :             break;
     711             :         }
     712             :     }
     713             : 
     714             :     // Wait for all jobs to be finished
     715             :     while (true)
     716             :     {
     717             :         {
     718          18 :             std::lock_guard<std::mutex> oLock(m_oMutex);
     719          18 :             if (nRemainingThreads == 0)
     720           4 :                 break;
     721             :         }
     722          14 :         wtp->WaitEvent();
     723          14 :     }
     724             : 
     725           4 :     return bGlobalStatus;
     726             : }
     727             : 
     728             : /************************************************************************/
     729             : /*                    ZarrV3Array::FlushDirtyTile()                     */
     730             : /************************************************************************/
     731             : 
     732       12112 : bool ZarrV3Array::FlushDirtyTile() const
     733             : {
     734       12112 :     if (!m_bDirtyTile)
     735        1363 :         return true;
     736       10749 :     m_bDirtyTile = false;
     737             : 
     738       21498 :     std::string osFilename = BuildTileFilename(m_anCachedTiledIndices.data());
     739             : 
     740             :     const size_t nSourceSize =
     741       10749 :         m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
     742             :     const auto &abyTile =
     743       10749 :         m_abyDecodedTileData.empty() ? m_abyRawTileData : m_abyDecodedTileData;
     744             : 
     745       10749 :     if (IsEmptyTile(abyTile))
     746             :     {
     747           2 :         m_bCachedTiledEmpty = true;
     748             : 
     749             :         VSIStatBufL sStat;
     750           2 :         if (VSIStatL(osFilename.c_str(), &sStat) == 0)
     751             :         {
     752           0 :             CPLDebugOnly(ZARR_DEBUG_KEY,
     753             :                          "Deleting tile %s that has now empty content",
     754             :                          osFilename.c_str());
     755           0 :             return VSIUnlink(osFilename.c_str()) == 0;
     756             :         }
     757           2 :         return true;
     758             :     }
     759             : 
     760       10747 :     if (!m_abyDecodedTileData.empty())
     761             :     {
     762           0 :         const size_t nDTSize = m_oType.GetSize();
     763           0 :         const size_t nValues = m_abyDecodedTileData.size() / nDTSize;
     764           0 :         GByte *pDst = &m_abyRawTileData[0];
     765           0 :         const GByte *pSrc = m_abyDecodedTileData.data();
     766           0 :         for (size_t i = 0; i < nValues;
     767           0 :              i++, pDst += nSourceSize, pSrc += nDTSize)
     768             :         {
     769           0 :             EncodeElt(m_aoDtypeElts, pSrc, pDst);
     770             :         }
     771             :     }
     772             : 
     773       10747 :     const size_t nSizeBefore = m_abyRawTileData.size();
     774       10747 :     if (m_poCodecs)
     775             :     {
     776       10747 :         if (!m_poCodecs->Encode(m_abyRawTileData))
     777             :         {
     778           0 :             m_abyRawTileData.resize(nSizeBefore);
     779           0 :             return false;
     780             :         }
     781             :     }
     782             : 
     783       10747 :     if (m_osDimSeparator == "/")
     784             :     {
     785       10747 :         std::string osDir = CPLGetDirnameSafe(osFilename.c_str());
     786             :         VSIStatBufL sStat;
     787       10747 :         if (VSIStatL(osDir.c_str(), &sStat) != 0)
     788             :         {
     789         209 :             if (VSIMkdirRecursive(osDir.c_str(), 0755) != 0)
     790             :             {
     791           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     792             :                          "Cannot create directory %s", osDir.c_str());
     793           0 :                 m_abyRawTileData.resize(nSizeBefore);
     794           0 :                 return false;
     795             :             }
     796             :         }
     797             :     }
     798             : 
     799       10747 :     VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
     800       10747 :     if (fp == nullptr)
     801             :     {
     802           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot create tile %s",
     803             :                  osFilename.c_str());
     804           0 :         m_abyRawTileData.resize(nSizeBefore);
     805           0 :         return false;
     806             :     }
     807             : 
     808       10747 :     bool bRet = true;
     809       10747 :     const size_t nRawDataSize = m_abyRawTileData.size();
     810       10747 :     if (VSIFWriteL(m_abyRawTileData.data(), 1, nRawDataSize, fp) !=
     811             :         nRawDataSize)
     812             :     {
     813           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     814             :                  "Could not write tile %s correctly", osFilename.c_str());
     815           0 :         bRet = false;
     816             :     }
     817       10747 :     VSIFCloseL(fp);
     818             : 
     819       10747 :     m_abyRawTileData.resize(nSizeBefore);
     820             : 
     821       10747 :     return bRet;
     822             : }
     823             : 
     824             : /************************************************************************/
     825             : /*                          BuildTileFilename()                         */
     826             : /************************************************************************/
     827             : 
     828       21841 : std::string ZarrV3Array::BuildTileFilename(const uint64_t *tileIndices) const
     829             : {
     830       21841 :     if (m_aoDims.empty())
     831             :     {
     832             :         return CPLFormFilenameSafe(
     833           0 :             CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
     834           0 :             m_bV2ChunkKeyEncoding ? "0" : "c", nullptr);
     835             :     }
     836             :     else
     837             :     {
     838       43667 :         std::string osFilename(CPLGetDirnameSafe(m_osFilename.c_str()));
     839       21822 :         osFilename += '/';
     840       21830 :         if (!m_bV2ChunkKeyEncoding)
     841             :         {
     842       21814 :             osFilename += 'c';
     843             :         }
     844       65398 :         for (size_t i = 0; i < m_aoDims.size(); ++i)
     845             :         {
     846       43550 :             if (i > 0 || !m_bV2ChunkKeyEncoding)
     847       43552 :                 osFilename += m_osDimSeparator;
     848       43542 :             osFilename += std::to_string(tileIndices[i]);
     849             :         }
     850       21849 :         return osFilename;
     851             :     }
     852             : }
     853             : 
     854             : /************************************************************************/
     855             : /*                          GetDataDirectory()                          */
     856             : /************************************************************************/
     857             : 
     858           2 : std::string ZarrV3Array::GetDataDirectory() const
     859             : {
     860           2 :     return std::string(CPLGetDirnameSafe(m_osFilename.c_str()));
     861             : }
     862             : 
     863             : /************************************************************************/
     864             : /*                        GetTileIndicesFromFilename()                  */
     865             : /************************************************************************/
     866             : 
     867             : CPLStringList
     868           4 : ZarrV3Array::GetTileIndicesFromFilename(const char *pszFilename) const
     869             : {
     870           4 :     if (!m_bV2ChunkKeyEncoding)
     871             :     {
     872           4 :         if (pszFilename[0] != 'c')
     873           2 :             return CPLStringList();
     874           2 :         if (m_osDimSeparator == "/")
     875             :         {
     876           2 :             if (pszFilename[1] != '/' && pszFilename[1] != '\\')
     877           0 :                 return CPLStringList();
     878             :         }
     879           0 :         else if (pszFilename[1] != m_osDimSeparator[0])
     880             :         {
     881           0 :             return CPLStringList();
     882             :         }
     883             :     }
     884             :     return CPLStringList(
     885           2 :         CSLTokenizeString2(pszFilename + (m_bV2ChunkKeyEncoding ? 0 : 2),
     886           4 :                            m_osDimSeparator.c_str(), 0));
     887             : }
     888             : 
     889             : /************************************************************************/
     890             : /*                           ParseDtypeV3()                             */
     891             : /************************************************************************/
     892             : 
     893         183 : static GDALExtendedDataType ParseDtypeV3(const CPLJSONObject &obj,
     894             :                                          std::vector<DtypeElt> &elts)
     895             : {
     896             :     do
     897             :     {
     898         183 :         if (obj.GetType() == CPLJSONObject::Type::String)
     899             :         {
     900         366 :             const auto str = obj.ToString();
     901         183 :             DtypeElt elt;
     902         183 :             GDALDataType eDT = GDT_Unknown;
     903             : 
     904         183 :             if (str == "bool")  // boolean
     905             :             {
     906           0 :                 elt.nativeType = DtypeElt::NativeType::BOOLEAN;
     907           0 :                 eDT = GDT_Byte;
     908             :             }
     909         183 :             else if (str == "int8")
     910             :             {
     911           6 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     912           6 :                 eDT = GDT_Int8;
     913             :             }
     914         177 :             else if (str == "uint8")
     915             :             {
     916          75 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     917          75 :                 eDT = GDT_Byte;
     918             :             }
     919         102 :             else if (str == "int16")
     920             :             {
     921          10 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     922          10 :                 eDT = GDT_Int16;
     923             :             }
     924          92 :             else if (str == "uint16")
     925             :             {
     926           7 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     927           7 :                 eDT = GDT_UInt16;
     928             :             }
     929          85 :             else if (str == "int32")
     930             :             {
     931           7 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     932           7 :                 eDT = GDT_Int32;
     933             :             }
     934          78 :             else if (str == "uint32")
     935             :             {
     936           7 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     937           7 :                 eDT = GDT_UInt32;
     938             :             }
     939          71 :             else if (str == "int64")
     940             :             {
     941           7 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     942           7 :                 eDT = GDT_Int64;
     943             :             }
     944          64 :             else if (str == "uint64")
     945             :             {
     946           6 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     947           6 :                 eDT = GDT_UInt64;
     948             :             }
     949          58 :             else if (str == "float16")
     950             :             {
     951             :                 // elt.nativeType = DtypeElt::NativeType::IEEEFP;
     952             :                 // elt.nativeSize = 2;
     953             :                 // elt.gdalTypeIsApproxOfNative = true;
     954             :                 // eDT = GDT_Float32;
     955           1 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
     956           1 :                 elt.nativeSize = 2;
     957           1 :                 eDT = GDT_Float16;
     958             :             }
     959          57 :             else if (str == "float32")
     960             :             {
     961           9 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
     962           9 :                 eDT = GDT_Float32;
     963             :             }
     964          48 :             else if (str == "float64")
     965             :             {
     966          17 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
     967          17 :                 eDT = GDT_Float64;
     968             :             }
     969          31 :             else if (str == "complex64")
     970             :             {
     971          15 :                 elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     972          15 :                 eDT = GDT_CFloat32;
     973             :             }
     974          16 :             else if (str == "complex128")
     975             :             {
     976          15 :                 elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     977          15 :                 eDT = GDT_CFloat64;
     978             :             }
     979             :             else
     980           1 :                 break;
     981             : 
     982         182 :             elt.gdalType = GDALExtendedDataType::Create(eDT);
     983         182 :             elt.gdalSize = elt.gdalType.GetSize();
     984         182 :             if (!elt.gdalTypeIsApproxOfNative)
     985         182 :                 elt.nativeSize = elt.gdalSize;
     986             : 
     987         182 :             if (elt.nativeSize > 1)
     988             :             {
     989         101 :                 elt.needByteSwapping = (CPL_IS_LSB == 0);
     990             :             }
     991             : 
     992         182 :             elts.emplace_back(elt);
     993         182 :             return GDALExtendedDataType::Create(eDT);
     994             :         }
     995             :     } while (false);
     996           1 :     CPLError(CE_Failure, CPLE_AppDefined,
     997             :              "Invalid or unsupported format for data_type: %s",
     998           2 :              obj.ToString().c_str());
     999           1 :     return GDALExtendedDataType::Create(GDT_Unknown);
    1000             : }
    1001             : 
    1002             : /************************************************************************/
    1003             : /*                    ParseNoDataStringAsDouble()                       */
    1004             : /************************************************************************/
    1005             : 
    1006          40 : static double ParseNoDataStringAsDouble(const std::string &osVal, bool &bOK)
    1007             : {
    1008          40 :     double dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
    1009          40 :     if (osVal == "NaN")
    1010             :     {
    1011             :         // initialized above
    1012             :     }
    1013          15 :     else if (osVal == "Infinity" || osVal == "+Infinity")
    1014             :     {
    1015           5 :         dfNoDataValue = std::numeric_limits<double>::infinity();
    1016             :     }
    1017          10 :     else if (osVal == "-Infinity")
    1018             :     {
    1019           5 :         dfNoDataValue = -std::numeric_limits<double>::infinity();
    1020             :     }
    1021             :     else
    1022             :     {
    1023           5 :         bOK = false;
    1024             :     }
    1025          40 :     return dfNoDataValue;
    1026             : }
    1027             : 
    1028             : /************************************************************************/
    1029             : /*                     ParseNoDataComponent()                           */
    1030             : /************************************************************************/
    1031             : 
    1032             : template <typename T, typename Tint>
    1033          40 : static T ParseNoDataComponent(const CPLJSONObject &oObj, bool &bOK)
    1034             : {
    1035          40 :     if (oObj.GetType() == CPLJSONObject::Type::Integer ||
    1036          62 :         oObj.GetType() == CPLJSONObject::Type::Long ||
    1037          22 :         oObj.GetType() == CPLJSONObject::Type::Double)
    1038             :     {
    1039          22 :         return static_cast<T>(oObj.ToDouble());
    1040             :     }
    1041          18 :     else if (oObj.GetType() == CPLJSONObject::Type::String)
    1042             :     {
    1043          54 :         const auto osVal = oObj.ToString();
    1044          18 :         if (STARTS_WITH(osVal.c_str(), "0x"))
    1045             :         {
    1046           2 :             if (osVal.size() > 2 + 2 * sizeof(T))
    1047             :             {
    1048           0 :                 bOK = false;
    1049           0 :                 return 0;
    1050             :             }
    1051           2 :             Tint nVal = static_cast<Tint>(
    1052           2 :                 std::strtoull(osVal.c_str() + 2, nullptr, 16));
    1053             :             T fVal;
    1054             :             static_assert(sizeof(nVal) == sizeof(fVal),
    1055             :                           "sizeof(nVal) == sizeof(dfVal)");
    1056           2 :             memcpy(&fVal, &nVal, sizeof(nVal));
    1057           2 :             return fVal;
    1058             :         }
    1059             :         else
    1060             :         {
    1061          16 :             return static_cast<T>(ParseNoDataStringAsDouble(osVal, bOK));
    1062             :         }
    1063             :     }
    1064             :     else
    1065             :     {
    1066           0 :         bOK = false;
    1067           0 :         return 0;
    1068             :     }
    1069             : }
    1070             : 
    1071             : /************************************************************************/
    1072             : /*                     ZarrV3Group::LoadArray()                         */
    1073             : /************************************************************************/
    1074             : 
    1075             : std::shared_ptr<ZarrArray>
    1076         196 : ZarrV3Group::LoadArray(const std::string &osArrayName,
    1077             :                        const std::string &osZarrayFilename,
    1078             :                        const CPLJSONObject &oRoot) const
    1079             : {
    1080             :     // Add osZarrayFilename to m_poSharedResource during the scope
    1081             :     // of this function call.
    1082         196 :     ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
    1083         392 :                                                        osZarrayFilename);
    1084         196 :     if (!filenameAdder.ok())
    1085           0 :         return nullptr;
    1086             : 
    1087             :     // Warn about unknown members (the spec suggests to error out, but let be
    1088             :     // a bit more lenient)
    1089        1954 :     for (const auto &oNode : oRoot.GetChildren())
    1090             :     {
    1091        3516 :         const auto osName = oNode.GetName();
    1092        4686 :         if (osName != "zarr_format" && osName != "node_type" &&
    1093        3513 :             osName != "shape" && osName != "chunk_grid" &&
    1094        2343 :             osName != "data_type" && osName != "chunk_key_encoding" &&
    1095         976 :             osName != "fill_value" &&
    1096             :             // Below are optional
    1097         795 :             osName != "dimension_names" && osName != "codecs" &&
    1098        3455 :             osName != "storage_transformers" && osName != "attributes")
    1099             :         {
    1100           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    1101             :                      "%s array definition contains a unknown member (%s). "
    1102             :                      "Interpretation of the array might be wrong.",
    1103             :                      osZarrayFilename.c_str(), osName.c_str());
    1104             :         }
    1105             :     }
    1106             : 
    1107         588 :     const auto oStorageTransformers = oRoot["storage_transformers"].ToArray();
    1108         196 :     if (oStorageTransformers.Size() > 0)
    1109             :     {
    1110           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1111             :                  "storage_transformers are not supported.");
    1112           1 :         return nullptr;
    1113             :     }
    1114             : 
    1115         585 :     const auto oShape = oRoot["shape"].ToArray();
    1116         195 :     if (!oShape.IsValid())
    1117             :     {
    1118           2 :         CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
    1119           2 :         return nullptr;
    1120             :     }
    1121             : 
    1122             :     // Parse chunk_grid
    1123         579 :     const auto oChunkGrid = oRoot["chunk_grid"];
    1124         193 :     if (oChunkGrid.GetType() != CPLJSONObject::Type::Object)
    1125             :     {
    1126           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1127             :                  "chunk_grid missing or not an object");
    1128           1 :         return nullptr;
    1129             :     }
    1130             : 
    1131         576 :     const auto oChunkGridName = oChunkGrid["name"];
    1132         192 :     if (oChunkGridName.ToString() != "regular")
    1133             :     {
    1134           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1135             :                  "Only chunk_grid.name = regular supported");
    1136           1 :         return nullptr;
    1137             :     }
    1138             : 
    1139         573 :     const auto oChunks = oChunkGrid["configuration"]["chunk_shape"].ToArray();
    1140         191 :     if (!oChunks.IsValid())
    1141             :     {
    1142           1 :         CPLError(
    1143             :             CE_Failure, CPLE_AppDefined,
    1144             :             "chunk_grid.configuration.chunk_shape missing or not an array");
    1145           1 :         return nullptr;
    1146             :     }
    1147             : 
    1148         190 :     if (oShape.Size() != oChunks.Size())
    1149             :     {
    1150           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1151             :                  "shape and chunks arrays are of different size");
    1152           1 :         return nullptr;
    1153             :     }
    1154             : 
    1155             :     // Parse chunk_key_encoding
    1156         567 :     const auto oChunkKeyEncoding = oRoot["chunk_key_encoding"];
    1157         189 :     if (oChunkKeyEncoding.GetType() != CPLJSONObject::Type::Object)
    1158             :     {
    1159           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1160             :                  "chunk_key_encoding missing or not an object");
    1161           1 :         return nullptr;
    1162             :     }
    1163             : 
    1164         376 :     std::string osDimSeparator;
    1165         188 :     bool bV2ChunkKeyEncoding = false;
    1166         564 :     const auto oChunkKeyEncodingName = oChunkKeyEncoding["name"];
    1167         188 :     if (oChunkKeyEncodingName.ToString() == "default")
    1168             :     {
    1169         181 :         osDimSeparator = "/";
    1170             :     }
    1171           7 :     else if (oChunkKeyEncodingName.ToString() == "v2")
    1172             :     {
    1173           6 :         osDimSeparator = ".";
    1174           6 :         bV2ChunkKeyEncoding = true;
    1175             :     }
    1176             :     else
    1177             :     {
    1178           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1179             :                  "Unsupported chunk_key_encoding.name");
    1180           1 :         return nullptr;
    1181             :     }
    1182             : 
    1183             :     {
    1184         374 :         auto oConfiguration = oChunkKeyEncoding["configuration"];
    1185         187 :         if (oConfiguration.GetType() == CPLJSONObject::Type::Object)
    1186             :         {
    1187         276 :             auto oSeparator = oConfiguration["separator"];
    1188         138 :             if (oSeparator.IsValid())
    1189             :             {
    1190         138 :                 osDimSeparator = oSeparator.ToString();
    1191         138 :                 if (osDimSeparator != "/" && osDimSeparator != ".")
    1192             :                 {
    1193           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1194             :                              "Separator can only be '/' or '.'");
    1195           1 :                     return nullptr;
    1196             :                 }
    1197             :             }
    1198             :         }
    1199             :     }
    1200             : 
    1201         558 :     CPLJSONObject oAttributes = oRoot["attributes"];
    1202             : 
    1203             :     // Deep-clone of oAttributes
    1204         186 :     if (oAttributes.IsValid())
    1205             :     {
    1206         130 :         oAttributes = oAttributes.Clone();
    1207             :     }
    1208             : 
    1209         372 :     std::vector<std::shared_ptr<GDALDimension>> aoDims;
    1210         427 :     for (int i = 0; i < oShape.Size(); ++i)
    1211             :     {
    1212         241 :         const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
    1213         241 :         if (nSize == 0)
    1214             :         {
    1215           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
    1216           0 :             return nullptr;
    1217             :         }
    1218         241 :         aoDims.emplace_back(std::make_shared<ZarrDimension>(
    1219         241 :             m_poSharedResource,
    1220         482 :             std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
    1221         482 :             std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
    1222         241 :             nSize));
    1223             :     }
    1224             : 
    1225             :     // Deal with dimension_names
    1226         558 :     const auto dimensionNames = oRoot["dimension_names"];
    1227             : 
    1228         176 :     const auto FindDimension = [this, &aoDims, &osArrayName, &oAttributes](
    1229             :                                    const std::string &osDimName,
    1230        2328 :                                    std::shared_ptr<GDALDimension> &poDim, int i)
    1231             :     {
    1232         176 :         auto oIter = m_oMapDimensions.find(osDimName);
    1233         176 :         if (oIter != m_oMapDimensions.end())
    1234             :         {
    1235           2 :             if (m_bDimSizeInUpdate ||
    1236           1 :                 oIter->second->GetSize() == poDim->GetSize())
    1237             :             {
    1238           1 :                 poDim = oIter->second;
    1239           1 :                 return true;
    1240             :             }
    1241             :             else
    1242             :             {
    1243           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1244             :                          "Size of _ARRAY_DIMENSIONS[%d] different "
    1245             :                          "from the one of shape",
    1246             :                          i);
    1247           0 :                 return false;
    1248             :             }
    1249             :         }
    1250             : 
    1251             :         // Try to load the indexing variable.
    1252             :         // Not in m_oMapMDArrays,
    1253             :         // then stat() the indexing variable.
    1254         346 :         else if (osArrayName != osDimName &&
    1255         346 :                  m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
    1256             :         {
    1257         342 :             std::string osDirName = m_osDirectoryName;
    1258             :             while (true)
    1259             :             {
    1260             :                 const std::string osArrayFilenameDim = CPLFormFilenameSafe(
    1261         714 :                     CPLFormFilenameSafe(osDirName.c_str(), osDimName.c_str(),
    1262             :                                         nullptr)
    1263             :                         .c_str(),
    1264         714 :                     "zarr.json", nullptr);
    1265             :                 VSIStatBufL sStat;
    1266         714 :                 if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
    1267             :                 {
    1268           6 :                     CPLJSONDocument oDoc;
    1269           3 :                     if (oDoc.Load(osArrayFilenameDim))
    1270             :                     {
    1271           3 :                         LoadArray(osDimName, osArrayFilenameDim,
    1272           6 :                                   oDoc.GetRoot());
    1273             :                     }
    1274             :                 }
    1275             :                 else
    1276             :                 {
    1277             :                     // Recurse to upper level for datasets such as
    1278             :                     // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
    1279             :                     std::string osDirNameNew =
    1280         711 :                         CPLGetPathSafe(osDirName.c_str());
    1281         711 :                     if (!osDirNameNew.empty() && osDirNameNew != osDirName)
    1282             :                     {
    1283         543 :                         osDirName = std::move(osDirNameNew);
    1284         543 :                         continue;
    1285             :                     }
    1286             :                 }
    1287         171 :                 break;
    1288         543 :             }
    1289             :         }
    1290             : 
    1291         175 :         oIter = m_oMapDimensions.find(osDimName);
    1292             :         // cppcheck-suppress knownConditionTrueFalse
    1293         178 :         if (oIter != m_oMapDimensions.end() &&
    1294           3 :             oIter->second->GetSize() == poDim->GetSize())
    1295             :         {
    1296           3 :             poDim = oIter->second;
    1297           3 :             return true;
    1298             :         }
    1299             : 
    1300         344 :         std::string osType;
    1301         344 :         std::string osDirection;
    1302         172 :         if (aoDims.size() == 1 && osArrayName == osDimName)
    1303             :         {
    1304           4 :             ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
    1305             :                                                  osDirection);
    1306             :         }
    1307             : 
    1308             :         auto poDimLocal = std::make_shared<ZarrDimension>(
    1309         172 :             m_poSharedResource,
    1310         344 :             std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
    1311         344 :             GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
    1312         172 :         poDimLocal->SetXArrayDimension();
    1313         172 :         m_oMapDimensions[osDimName] = poDimLocal;
    1314         172 :         poDim = poDimLocal;
    1315         172 :         return true;
    1316         186 :     };
    1317             : 
    1318         186 :     if (dimensionNames.GetType() == CPLJSONObject::Type::Array)
    1319             :     {
    1320         119 :         const auto arrayDims = dimensionNames.ToArray();
    1321         119 :         if (arrayDims.Size() == oShape.Size())
    1322             :         {
    1323         294 :             for (int i = 0; i < oShape.Size(); ++i)
    1324             :             {
    1325         176 :                 if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
    1326             :                 {
    1327         528 :                     const auto osDimName = arrayDims[i].ToString();
    1328         176 :                     FindDimension(osDimName, aoDims[i], i);
    1329             :                 }
    1330             :             }
    1331             :         }
    1332             :         else
    1333             :         {
    1334           1 :             CPLError(
    1335             :                 CE_Failure, CPLE_AppDefined,
    1336             :                 "Size of dimension_names[] different from the one of shape");
    1337           1 :             return nullptr;
    1338             :         }
    1339             :     }
    1340          67 :     else if (dimensionNames.IsValid())
    1341             :     {
    1342           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1343             :                  "dimension_names should be an array");
    1344           1 :         return nullptr;
    1345             :     }
    1346             : 
    1347         552 :     auto oDtype = oRoot["data_type"];
    1348         184 :     if (!oDtype.IsValid())
    1349             :     {
    1350           1 :         CPLError(CE_Failure, CPLE_NotSupported, "data_type missing");
    1351           1 :         return nullptr;
    1352             :     }
    1353         183 :     if (oDtype["fallback"].IsValid())
    1354           1 :         oDtype = oDtype["fallback"];
    1355         366 :     std::vector<DtypeElt> aoDtypeElts;
    1356         366 :     const auto oType = ParseDtypeV3(oDtype, aoDtypeElts);
    1357         366 :     if (oType.GetClass() == GEDTC_NUMERIC &&
    1358         183 :         oType.GetNumericDataType() == GDT_Unknown)
    1359           1 :         return nullptr;
    1360             : 
    1361         364 :     std::vector<GUInt64> anBlockSize;
    1362         182 :     if (!ZarrArray::ParseChunkSize(oChunks, oType, anBlockSize))
    1363           1 :         return nullptr;
    1364             : 
    1365         362 :     std::vector<GByte> abyNoData;
    1366             : 
    1367         543 :     auto oFillValue = oRoot["fill_value"];
    1368         181 :     auto eFillValueType = oFillValue.GetType();
    1369             : 
    1370         181 :     if (!oFillValue.IsValid())
    1371             :     {
    1372           0 :         CPLError(CE_Warning, CPLE_AppDefined, "Missing fill_value is invalid");
    1373             :     }
    1374         181 :     else if (eFillValueType == CPLJSONObject::Type::Null)
    1375             :     {
    1376         100 :         CPLError(CE_Warning, CPLE_AppDefined, "fill_value = null is invalid");
    1377             :     }
    1378          81 :     else if (GDALDataTypeIsComplex(oType.GetNumericDataType()) &&
    1379             :              eFillValueType != CPLJSONObject::Type::Array)
    1380             :     {
    1381           4 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1382           4 :         return nullptr;
    1383             :     }
    1384          77 :     else if (eFillValueType == CPLJSONObject::Type::String)
    1385             :     {
    1386          60 :         const auto osFillValue = oFillValue.ToString();
    1387          30 :         if (STARTS_WITH(osFillValue.c_str(), "0x"))
    1388             :         {
    1389           3 :             if (osFillValue.size() > 2 + 2 * oType.GetSize())
    1390             :             {
    1391           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1392           1 :                 return nullptr;
    1393             :             }
    1394             :             uint64_t nVal = static_cast<uint64_t>(
    1395           3 :                 std::strtoull(osFillValue.c_str() + 2, nullptr, 16));
    1396           3 :             if (oType.GetSize() == 4)
    1397             :             {
    1398           1 :                 abyNoData.resize(oType.GetSize());
    1399           1 :                 uint32_t nTmp = static_cast<uint32_t>(nVal);
    1400           1 :                 memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
    1401             :             }
    1402           2 :             else if (oType.GetSize() == 8)
    1403             :             {
    1404           1 :                 abyNoData.resize(oType.GetSize());
    1405           1 :                 memcpy(&abyNoData[0], &nVal, sizeof(nVal));
    1406             :             }
    1407             :             else
    1408             :             {
    1409           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1410             :                          "Hexadecimal representation of fill_value no "
    1411             :                          "supported for this data type");
    1412           1 :                 return nullptr;
    1413             :             }
    1414             :         }
    1415          27 :         else if (STARTS_WITH(osFillValue.c_str(), "0b"))
    1416             :         {
    1417           3 :             if (osFillValue.size() > 2 + 8 * oType.GetSize())
    1418             :             {
    1419           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1420           1 :                 return nullptr;
    1421             :             }
    1422             :             uint64_t nVal = static_cast<uint64_t>(
    1423           3 :                 std::strtoull(osFillValue.c_str() + 2, nullptr, 2));
    1424           3 :             if (oType.GetSize() == 4)
    1425             :             {
    1426           1 :                 abyNoData.resize(oType.GetSize());
    1427           1 :                 uint32_t nTmp = static_cast<uint32_t>(nVal);
    1428           1 :                 memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
    1429             :             }
    1430           2 :             else if (oType.GetSize() == 8)
    1431             :             {
    1432           1 :                 abyNoData.resize(oType.GetSize());
    1433           1 :                 memcpy(&abyNoData[0], &nVal, sizeof(nVal));
    1434             :             }
    1435             :             else
    1436             :             {
    1437           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1438             :                          "Binary representation of fill_value no supported for "
    1439             :                          "this data type");
    1440           1 :                 return nullptr;
    1441             :             }
    1442             :         }
    1443             :         else
    1444             :         {
    1445          24 :             bool bOK = true;
    1446          24 :             double dfNoDataValue = ParseNoDataStringAsDouble(osFillValue, bOK);
    1447          24 :             if (!bOK)
    1448             :             {
    1449           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1450           2 :                 return nullptr;
    1451             :             }
    1452          23 :             else if (oType.GetNumericDataType() == GDT_Float16)
    1453             :             {
    1454             :                 const GFloat16 hfNoDataValue =
    1455           1 :                     static_cast<GFloat16>(dfNoDataValue);
    1456           1 :                 abyNoData.resize(sizeof(hfNoDataValue));
    1457           1 :                 memcpy(&abyNoData[0], &hfNoDataValue, sizeof(hfNoDataValue));
    1458             :             }
    1459          22 :             else if (oType.GetNumericDataType() == GDT_Float32)
    1460             :             {
    1461           7 :                 const float fNoDataValue = static_cast<float>(dfNoDataValue);
    1462           7 :                 abyNoData.resize(sizeof(fNoDataValue));
    1463           7 :                 memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
    1464             :             }
    1465          15 :             else if (oType.GetNumericDataType() == GDT_Float64)
    1466             :             {
    1467          14 :                 abyNoData.resize(sizeof(dfNoDataValue));
    1468          14 :                 memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
    1469             :             }
    1470             :             else
    1471             :             {
    1472           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1473             :                          "Invalid fill_value for this data type");
    1474           1 :                 return nullptr;
    1475             :             }
    1476             :         }
    1477             :     }
    1478          47 :     else if (eFillValueType == CPLJSONObject::Type::Boolean ||
    1479          25 :              eFillValueType == CPLJSONObject::Type::Integer ||
    1480          25 :              eFillValueType == CPLJSONObject::Type::Long ||
    1481             :              eFillValueType == CPLJSONObject::Type::Double)
    1482             :     {
    1483          23 :         const double dfNoDataValue = oFillValue.ToDouble();
    1484          23 :         if (oType.GetNumericDataType() == GDT_Int64)
    1485             :         {
    1486             :             const int64_t nNoDataValue =
    1487           1 :                 static_cast<int64_t>(oFillValue.ToLong());
    1488           1 :             abyNoData.resize(oType.GetSize());
    1489           1 :             GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
    1490             :                           oType.GetNumericDataType(), 0, 1);
    1491             :         }
    1492          22 :         else if (oType.GetNumericDataType() == GDT_UInt64 &&
    1493             :                  /* we can't really deal with nodata value between */
    1494             :                  /* int64::max and uint64::max due to json-c limitations */
    1495           0 :                  dfNoDataValue >= 0)
    1496             :         {
    1497             :             const int64_t nNoDataValue =
    1498           0 :                 static_cast<int64_t>(oFillValue.ToLong());
    1499           0 :             abyNoData.resize(oType.GetSize());
    1500           0 :             GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
    1501             :                           oType.GetNumericDataType(), 0, 1);
    1502             :         }
    1503             :         else
    1504             :         {
    1505          22 :             abyNoData.resize(oType.GetSize());
    1506          22 :             GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
    1507             :                           oType.GetNumericDataType(), 0, 1);
    1508          23 :         }
    1509             :     }
    1510          24 :     else if (eFillValueType == CPLJSONObject::Type::Array)
    1511             :     {
    1512          24 :         const auto oFillValueArray = oFillValue.ToArray();
    1513          44 :         if (oFillValueArray.Size() == 2 &&
    1514          20 :             GDALDataTypeIsComplex(oType.GetNumericDataType()))
    1515             :         {
    1516          20 :             if (oType.GetNumericDataType() == GDT_CFloat64)
    1517             :             {
    1518          10 :                 bool bOK = true;
    1519             :                 const double adfNoDataValue[2] = {
    1520          10 :                     ParseNoDataComponent<double, uint64_t>(oFillValueArray[0],
    1521             :                                                            bOK),
    1522          10 :                     ParseNoDataComponent<double, uint64_t>(oFillValueArray[1],
    1523             :                                                            bOK),
    1524          20 :                 };
    1525          10 :                 if (!bOK)
    1526             :                 {
    1527           2 :                     CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1528           2 :                     return nullptr;
    1529             :                 }
    1530           8 :                 abyNoData.resize(oType.GetSize());
    1531           8 :                 CPLAssert(sizeof(adfNoDataValue) == oType.GetSize());
    1532           8 :                 memcpy(abyNoData.data(), adfNoDataValue,
    1533             :                        sizeof(adfNoDataValue));
    1534             :             }
    1535             :             else
    1536             :             {
    1537          10 :                 CPLAssert(oType.GetNumericDataType() == GDT_CFloat32);
    1538          10 :                 bool bOK = true;
    1539             :                 const float afNoDataValue[2] = {
    1540          10 :                     ParseNoDataComponent<float, uint32_t>(oFillValueArray[0],
    1541             :                                                           bOK),
    1542          10 :                     ParseNoDataComponent<float, uint32_t>(oFillValueArray[1],
    1543             :                                                           bOK),
    1544          20 :                 };
    1545          10 :                 if (!bOK)
    1546             :                 {
    1547           2 :                     CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1548           2 :                     return nullptr;
    1549             :                 }
    1550           8 :                 abyNoData.resize(oType.GetSize());
    1551           8 :                 CPLAssert(sizeof(afNoDataValue) == oType.GetSize());
    1552           8 :                 memcpy(abyNoData.data(), afNoDataValue, sizeof(afNoDataValue));
    1553             :             }
    1554             :         }
    1555             :         else
    1556             :         {
    1557           4 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1558           4 :             return nullptr;
    1559             :         }
    1560             :     }
    1561             :     else
    1562             :     {
    1563           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1564           0 :         return nullptr;
    1565             :     }
    1566             : 
    1567         495 :     const auto oCodecs = oRoot["codecs"].ToArray();
    1568         165 :     std::unique_ptr<ZarrV3CodecSequence> poCodecs;
    1569         165 :     if (oCodecs.Size() > 0)
    1570             :     {
    1571             :         // Byte swapping will be done by the codec chain
    1572         135 :         aoDtypeElts.back().needByteSwapping = false;
    1573             : 
    1574         135 :         ZarrArrayMetadata oInputArrayMetadata;
    1575         320 :         for (auto &nSize : anBlockSize)
    1576         185 :             oInputArrayMetadata.anBlockSizes.push_back(
    1577         185 :                 static_cast<size_t>(nSize));
    1578         135 :         oInputArrayMetadata.oElt = aoDtypeElts.back();
    1579         135 :         poCodecs = std::make_unique<ZarrV3CodecSequence>(oInputArrayMetadata);
    1580         135 :         if (!poCodecs->InitFromJson(oCodecs))
    1581           0 :             return nullptr;
    1582             :     }
    1583             : 
    1584             :     auto poArray =
    1585         165 :         ZarrV3Array::Create(m_poSharedResource, GetFullName(), osArrayName,
    1586         330 :                             aoDims, oType, aoDtypeElts, anBlockSize);
    1587         165 :     if (!poArray)
    1588           1 :         return nullptr;
    1589         164 :     poArray->SetUpdatable(m_bUpdatable);  // must be set before SetAttributes()
    1590         164 :     poArray->SetFilename(osZarrayFilename);
    1591         164 :     poArray->SetIsV2ChunkKeyEncoding(bV2ChunkKeyEncoding);
    1592         164 :     poArray->SetDimSeparator(osDimSeparator);
    1593         164 :     if (!abyNoData.empty())
    1594             :     {
    1595          64 :         poArray->RegisterNoDataValue(abyNoData.data());
    1596             :     }
    1597         164 :     poArray->ParseSpecialAttributes(m_pSelf.lock(), oAttributes);
    1598         164 :     poArray->SetAttributes(oAttributes);
    1599         164 :     poArray->SetDtype(oDtype);
    1600         299 :     if (oCodecs.Size() > 0 &&
    1601         299 :         oCodecs[oCodecs.Size() - 1].GetString("name") != "bytes")
    1602             :     {
    1603          56 :         poArray->SetStructuralInfo(
    1604          56 :             "COMPRESSOR", oCodecs[oCodecs.Size() - 1].ToString().c_str());
    1605             :     }
    1606         164 :     if (poCodecs)
    1607         135 :         poArray->SetCodecs(std::move(poCodecs));
    1608         164 :     RegisterArray(poArray);
    1609             : 
    1610             :     // If this is an indexing variable, attach it to the dimension.
    1611         164 :     if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
    1612             :     {
    1613           4 :         auto oIter = m_oMapDimensions.find(poArray->GetName());
    1614           4 :         if (oIter != m_oMapDimensions.end())
    1615             :         {
    1616           4 :             oIter->second->SetIndexingVariable(poArray);
    1617             :         }
    1618             :     }
    1619             : 
    1620         164 :     if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
    1621             :             "CACHE_TILE_PRESENCE", "NO")))
    1622             :     {
    1623           2 :         poArray->CacheTilePresence();
    1624             :     }
    1625             : 
    1626         164 :     return poArray;
    1627             : }

Generated by: LCOV version 1.14