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

Generated by: LCOV version 1.14