LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v2_array.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 990 1067 92.8 %
Date: 2026-04-25 18:18:19 Functions: 30 32 93.8 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Zarr driver
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_float.h"
      14             : #include "cpl_vsi_virtual.h"
      15             : #include "cpl_worker_thread_pool.h"
      16             : #include "gdal_thread_pool.h"
      17             : #include "zarr.h"
      18             : #include "vsikerchunk.h"
      19             : 
      20             : #include "netcdf_cf_constants.h"  // for CF_UNITS, etc
      21             : 
      22             : #include <algorithm>
      23             : #include <cassert>
      24             : #include <cstdlib>
      25             : #include <limits>
      26             : #include <map>
      27             : #include <set>
      28             : 
      29             : /************************************************************************/
      30             : /*                      ZarrV2Array::ZarrV2Array()                      */
      31             : /************************************************************************/
      32             : 
      33        1098 : ZarrV2Array::ZarrV2Array(
      34             :     const std::shared_ptr<ZarrSharedResource> &poSharedResource,
      35             :     const std::shared_ptr<ZarrGroupBase> &poParent, const std::string &osName,
      36             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
      37             :     const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
      38        1098 :     const std::vector<GUInt64> &anBlockSize, bool bFortranOrder)
      39        1098 :     : GDALAbstractMDArray(poParent->GetFullName(), osName),
      40             :       ZarrArray(poSharedResource, poParent, osName, aoDims, oType, aoDtypeElts,
      41             :                 anBlockSize, anBlockSize),
      42        1098 :       m_bFortranOrder(bFortranOrder)
      43             : {
      44        1098 :     m_oCompressorJSon.Deinit();
      45        1098 : }
      46             : 
      47             : /************************************************************************/
      48             : /*                        ZarrV2Array::Create()                         */
      49             : /************************************************************************/
      50             : 
      51        1098 : std::shared_ptr<ZarrV2Array> ZarrV2Array::Create(
      52             :     const std::shared_ptr<ZarrSharedResource> &poSharedResource,
      53             :     const std::shared_ptr<ZarrGroupBase> &poParent, const std::string &osName,
      54             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
      55             :     const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
      56             :     const std::vector<GUInt64> &anBlockSize, bool bFortranOrder)
      57             : {
      58             :     auto arr = std::shared_ptr<ZarrV2Array>(
      59             :         new ZarrV2Array(poSharedResource, poParent, osName, aoDims, oType,
      60        2196 :                         aoDtypeElts, anBlockSize, bFortranOrder));
      61        1098 :     if (arr->m_nTotalInnerChunkCount == 0)
      62           1 :         return nullptr;
      63        1097 :     arr->SetSelf(arr);
      64             : 
      65        1097 :     return arr;
      66             : }
      67             : 
      68             : /************************************************************************/
      69             : /*                            ~ZarrV2Array()                            */
      70             : /************************************************************************/
      71             : 
      72        2196 : ZarrV2Array::~ZarrV2Array()
      73             : {
      74        1098 :     ZarrV2Array::Flush();
      75        2196 : }
      76             : 
      77             : /************************************************************************/
      78             : /*                               Flush()                                */
      79             : /************************************************************************/
      80             : 
      81        4891 : bool ZarrV2Array::Flush()
      82             : {
      83        4891 :     if (!m_bValid)
      84          12 :         return true;
      85             : 
      86        4879 :     bool ret = ZarrV2Array::FlushDirtyBlock();
      87             : 
      88        4879 :     m_anCachedBlockIndices.clear();
      89             : 
      90        4879 :     if (m_bDefinitionModified)
      91             :     {
      92         333 :         if (!Serialize())
      93           3 :             ret = false;
      94         333 :         m_bDefinitionModified = false;
      95             :     }
      96             : 
      97        4879 :     CPLJSONArray j_ARRAY_DIMENSIONS;
      98        4879 :     bool bDimensionsModified = false;
      99        4879 :     if (!m_aoDims.empty())
     100             :     {
     101        9245 :         for (const auto &poDim : m_aoDims)
     102             :         {
     103             :             const auto poZarrDim =
     104        6136 :                 dynamic_cast<const ZarrDimension *>(poDim.get());
     105        6136 :             if (poZarrDim && poZarrDim->IsXArrayDimension())
     106             :             {
     107        4707 :                 if (poZarrDim->IsModified())
     108          16 :                     bDimensionsModified = true;
     109        4707 :                 j_ARRAY_DIMENSIONS.Add(poDim->GetName());
     110             :             }
     111             :             else
     112             :             {
     113        1429 :                 j_ARRAY_DIMENSIONS = CPLJSONArray();
     114        1429 :                 break;
     115             :             }
     116             :         }
     117             :     }
     118             : 
     119        9700 :     if (m_oAttrGroup.IsModified() || bDimensionsModified ||
     120        4805 :         (m_bNew && j_ARRAY_DIMENSIONS.Size() != 0) || m_bUnitModified ||
     121        9700 :         m_bOffsetModified || m_bScaleModified || m_bSRSModified)
     122             :     {
     123         358 :         m_bNew = false;
     124             : 
     125         716 :         auto oAttrs = SerializeSpecialAttributes();
     126             : 
     127         358 :         if (j_ARRAY_DIMENSIONS.Size() != 0)
     128             :         {
     129         342 :             oAttrs.Delete("_ARRAY_DIMENSIONS");
     130         342 :             oAttrs.Add("_ARRAY_DIMENSIONS", j_ARRAY_DIMENSIONS);
     131             :         }
     132             : 
     133         716 :         CPLJSONDocument oDoc;
     134         358 :         oDoc.SetRoot(oAttrs);
     135             :         const std::string osAttrFilename =
     136         358 :             CPLFormFilenameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
     137         716 :                                 ".zattrs", nullptr);
     138         358 :         if (!oDoc.Save(osAttrFilename))
     139           7 :             ret = false;
     140         358 :         m_poSharedResource->SetZMetadataItem(osAttrFilename, oAttrs);
     141             :     }
     142             : 
     143        4879 :     return ret;
     144             : }
     145             : 
     146             : /************************************************************************/
     147             : /*            StripUselessItemsFromCompressorConfiguration()            */
     148             : /************************************************************************/
     149             : 
     150          28 : static void StripUselessItemsFromCompressorConfiguration(CPLJSONObject &o)
     151             : {
     152          28 :     if (o.GetType() == CPLJSONObject::Type::Object)
     153             :     {
     154          18 :         o.Delete("num_threads");  // Blosc
     155          18 :         o.Delete("typesize");     // Blosc
     156          18 :         o.Delete("header");       // LZ4
     157             :     }
     158          28 : }
     159             : 
     160             : /************************************************************************/
     161             : /*                       ZarrV2Array::Serialize()                       */
     162             : /************************************************************************/
     163             : 
     164         333 : bool ZarrV2Array::Serialize()
     165             : {
     166         666 :     CPLJSONDocument oDoc;
     167         666 :     CPLJSONObject oRoot = oDoc.GetRoot();
     168             : 
     169         666 :     CPLJSONArray oChunks;
     170         809 :     for (const auto nBlockSize : m_anOuterBlockSize)
     171             :     {
     172         476 :         oChunks.Add(static_cast<GInt64>(nBlockSize));
     173             :     }
     174         333 :     oRoot.Add("chunks", oChunks);
     175             : 
     176         333 :     if (m_oCompressorJSon.IsValid())
     177             :     {
     178          28 :         oRoot.Add("compressor", m_oCompressorJSon);
     179          84 :         CPLJSONObject compressor = oRoot["compressor"];
     180          28 :         StripUselessItemsFromCompressorConfiguration(compressor);
     181             :     }
     182             :     else
     183             :     {
     184         305 :         oRoot.AddNull("compressor");
     185             :     }
     186             : 
     187         333 :     if (m_dtype.GetType() == CPLJSONObject::Type::Object)
     188         321 :         oRoot.Add("dtype", m_dtype["dummy"]);
     189             :     else
     190          12 :         oRoot.Add("dtype", m_dtype);
     191             : 
     192         333 :     if (m_pabyNoData == nullptr)
     193             :     {
     194         323 :         oRoot.AddNull("fill_value");
     195             :     }
     196             :     else
     197             :     {
     198          10 :         switch (m_oType.GetClass())
     199             :         {
     200           8 :             case GEDTC_NUMERIC:
     201             :             {
     202           8 :                 SerializeNumericNoData(oRoot);
     203           8 :                 break;
     204             :             }
     205             : 
     206           1 :             case GEDTC_STRING:
     207             :             {
     208             :                 char *pszStr;
     209           1 :                 char **ppszStr = reinterpret_cast<char **>(m_pabyNoData);
     210           1 :                 memcpy(&pszStr, ppszStr, sizeof(pszStr));
     211           1 :                 if (pszStr)
     212             :                 {
     213             :                     const size_t nNativeSize =
     214           1 :                         m_aoDtypeElts.back().nativeOffset +
     215           1 :                         m_aoDtypeElts.back().nativeSize;
     216           3 :                     char *base64 = CPLBase64Encode(
     217           1 :                         static_cast<int>(std::min(nNativeSize, strlen(pszStr))),
     218             :                         reinterpret_cast<const GByte *>(pszStr));
     219           1 :                     oRoot.Add("fill_value", base64);
     220           1 :                     CPLFree(base64);
     221             :                 }
     222             :                 else
     223             :                 {
     224           0 :                     oRoot.AddNull("fill_value");
     225             :                 }
     226           1 :                 break;
     227             :             }
     228             : 
     229           1 :             case GEDTC_COMPOUND:
     230             :             {
     231           1 :                 const size_t nNativeSize = m_aoDtypeElts.back().nativeOffset +
     232           1 :                                            m_aoDtypeElts.back().nativeSize;
     233           2 :                 std::vector<GByte> nativeNoData(nNativeSize);
     234           1 :                 EncodeElt(m_aoDtypeElts, m_pabyNoData, &nativeNoData[0]);
     235           1 :                 char *base64 = CPLBase64Encode(static_cast<int>(nNativeSize),
     236           1 :                                                nativeNoData.data());
     237           1 :                 oRoot.Add("fill_value", base64);
     238           1 :                 CPLFree(base64);
     239             :             }
     240             :         }
     241             :     }
     242             : 
     243         333 :     if (m_oFiltersArray.Size() == 0)
     244         332 :         oRoot.AddNull("filters");
     245             :     else
     246           1 :         oRoot.Add("filters", m_oFiltersArray);
     247             : 
     248         333 :     oRoot.Add("order", m_bFortranOrder ? "F" : "C");
     249             : 
     250         333 :     CPLJSONArray oShape;
     251         809 :     for (const auto &poDim : m_aoDims)
     252             :     {
     253         476 :         oShape.Add(static_cast<GInt64>(poDim->GetSize()));
     254             :     }
     255         333 :     oRoot.Add("shape", oShape);
     256             : 
     257         333 :     oRoot.Add("zarr_format", 2);
     258             : 
     259         333 :     if (m_osDimSeparator != ".")
     260             :     {
     261          10 :         oRoot.Add("dimension_separator", m_osDimSeparator);
     262             :     }
     263             : 
     264         333 :     bool ret = oDoc.Save(m_osFilename);
     265             : 
     266         333 :     m_poSharedResource->SetZMetadataItem(m_osFilename, oRoot);
     267             : 
     268         666 :     return ret;
     269             : }
     270             : 
     271             : /************************************************************************/
     272             : /*                   ZarrV2Array::NeedDecodedBuffer()                   */
     273             : /************************************************************************/
     274             : 
     275       13302 : bool ZarrV2Array::NeedDecodedBuffer() const
     276             : {
     277             :     const size_t nSourceSize =
     278       13302 :         m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
     279       13308 :     if (m_oType.GetClass() == GEDTC_COMPOUND &&
     280           6 :         nSourceSize != m_oType.GetSize())
     281             :     {
     282           4 :         return true;
     283             :     }
     284       13298 :     else if (m_oType.GetClass() != GEDTC_STRING)
     285             :     {
     286       26028 :         for (const auto &elt : m_aoDtypeElts)
     287             :         {
     288       13276 :             if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative ||
     289       12754 :                 elt.nativeType == DtypeElt::NativeType::STRING_ASCII ||
     290       12754 :                 elt.nativeType == DtypeElt::NativeType::STRING_UNICODE)
     291             :             {
     292         522 :                 return true;
     293             :             }
     294             :         }
     295             :     }
     296       12776 :     return false;
     297             : }
     298             : 
     299             : /************************************************************************/
     300             : /*                ZarrV2Array::AllocateWorkingBuffers()                 */
     301             : /************************************************************************/
     302             : 
     303        2517 : bool ZarrV2Array::AllocateWorkingBuffers() const
     304             : {
     305        2517 :     if (m_bAllocateWorkingBuffersDone)
     306        1899 :         return m_bWorkingBuffersOK;
     307             : 
     308         618 :     m_bAllocateWorkingBuffersDone = true;
     309             : 
     310         618 :     size_t nSizeNeeded = m_nInnerBlockSizeBytes;
     311         618 :     if (m_bFortranOrder || m_oFiltersArray.Size() != 0)
     312             :     {
     313          49 :         if (nSizeNeeded > std::numeric_limits<size_t>::max() / 2)
     314             :         {
     315           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
     316           1 :             return false;
     317             :         }
     318          48 :         nSizeNeeded *= 2;
     319             :     }
     320         617 :     if (NeedDecodedBuffer())
     321             :     {
     322          63 :         size_t nDecodedBufferSize = m_oType.GetSize();
     323         186 :         for (const auto &nBlockSize : m_anOuterBlockSize)
     324             :         {
     325         123 :             if (nDecodedBufferSize > std::numeric_limits<size_t>::max() /
     326         123 :                                          static_cast<size_t>(nBlockSize))
     327             :             {
     328           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
     329           0 :                 return false;
     330             :             }
     331         123 :             nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
     332             :         }
     333          63 :         if (nSizeNeeded >
     334          63 :             std::numeric_limits<size_t>::max() - nDecodedBufferSize)
     335             :         {
     336           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
     337           0 :             return false;
     338             :         }
     339          63 :         nSizeNeeded += nDecodedBufferSize;
     340             :     }
     341             : 
     342             :     // Reserve a buffer for tile content
     343         619 :     if (nSizeNeeded > 1024 * 1024 * 1024 &&
     344           2 :         !CPLTestBool(CPLGetConfigOption("ZARR_ALLOW_BIG_TILE_SIZE", "NO")))
     345             :     {
     346           2 :         CPLError(CE_Failure, CPLE_AppDefined,
     347             :                  "Zarr tile allocation would require " CPL_FRMT_GUIB " bytes. "
     348             :                  "By default the driver limits to 1 GB. To allow that memory "
     349             :                  "allocation, set the ZARR_ALLOW_BIG_TILE_SIZE configuration "
     350             :                  "option to YES.",
     351             :                  static_cast<GUIntBig>(nSizeNeeded));
     352           2 :         return false;
     353             :     }
     354             : 
     355        1230 :     m_bWorkingBuffersOK = AllocateWorkingBuffers(
     356         615 :         m_abyRawBlockData, m_abyTmpRawBlockData, m_abyDecodedBlockData);
     357         615 :     return m_bWorkingBuffersOK;
     358             : }
     359             : 
     360       12685 : bool ZarrV2Array::AllocateWorkingBuffers(
     361             :     ZarrByteVectorQuickResize &abyRawBlockData,
     362             :     ZarrByteVectorQuickResize &abyTmpRawBlockData,
     363             :     ZarrByteVectorQuickResize &abyDecodedBlockData) const
     364             : {
     365             :     // This method should NOT modify any ZarrArray member, as it is going to
     366             :     // be called concurrently from several threads.
     367             : 
     368             :     // Set those #define to avoid accidental use of some global variables
     369             : #define m_abyTmpRawBlockData cannot_use_here
     370             : #define m_abyRawBlockData cannot_use_here
     371             : #define m_abyDecodedBlockData cannot_use_here
     372             : 
     373             :     try
     374             :     {
     375       12685 :         abyRawBlockData.resize(m_nInnerBlockSizeBytes);
     376       12685 :         if (m_bFortranOrder || m_oFiltersArray.Size() != 0)
     377          48 :             abyTmpRawBlockData.resize(m_nInnerBlockSizeBytes);
     378             :     }
     379           0 :     catch (const std::bad_alloc &e)
     380             :     {
     381           0 :         CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
     382           0 :         return false;
     383             :     }
     384             : 
     385       12685 :     if (NeedDecodedBuffer())
     386             :     {
     387         463 :         size_t nDecodedBufferSize = m_oType.GetSize();
     388        1386 :         for (const auto &nBlockSize : m_anOuterBlockSize)
     389             :         {
     390         923 :             nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
     391             :         }
     392             :         try
     393             :         {
     394         463 :             abyDecodedBlockData.resize(nDecodedBufferSize);
     395             :         }
     396           0 :         catch (const std::bad_alloc &e)
     397             :         {
     398           0 :             CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
     399           0 :             return false;
     400             :         }
     401             :     }
     402             : 
     403       12685 :     return true;
     404             : #undef m_abyTmpRawBlockData
     405             : #undef m_abyRawBlockData
     406             : #undef m_abyDecodedBlockData
     407             : }
     408             : 
     409             : /************************************************************************/
     410             : /*                    ZarrV2Array::BlockTranspose()                     */
     411             : /************************************************************************/
     412             : 
     413          66 : void ZarrV2Array::BlockTranspose(const ZarrByteVectorQuickResize &abySrc,
     414             :                                  ZarrByteVectorQuickResize &abyDst,
     415             :                                  bool bDecode) const
     416             : {
     417             :     // Perform transposition
     418          66 :     const size_t nDims = m_anOuterBlockSize.size();
     419             :     const size_t nSourceSize =
     420          66 :         m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
     421             : 
     422             :     struct Stack
     423             :     {
     424             :         size_t nIters = 0;
     425             :         const GByte *src_ptr = nullptr;
     426             :         GByte *dst_ptr = nullptr;
     427             :         size_t src_inc_offset = 0;
     428             :         size_t dst_inc_offset = 0;
     429             :     };
     430             : 
     431         132 :     std::vector<Stack> stack(nDims);
     432             : #if defined(__GNUC__)
     433             : #pragma GCC diagnostic push
     434             : #pragma GCC diagnostic ignored "-Wnull-dereference"
     435             : #endif
     436             :     stack.emplace_back(
     437          66 :         Stack());  // to make gcc 9.3 -O2 -Wnull-dereference happy
     438             : #if defined(__GNUC__)
     439             : #pragma GCC diagnostic pop
     440             : #endif
     441             : 
     442          66 :     if (bDecode)
     443             :     {
     444          46 :         stack[0].src_inc_offset = nSourceSize;
     445         118 :         for (size_t i = 1; i < nDims; ++i)
     446             :         {
     447          72 :             stack[i].src_inc_offset =
     448          72 :                 stack[i - 1].src_inc_offset *
     449          72 :                 static_cast<size_t>(m_anOuterBlockSize[i - 1]);
     450             :         }
     451             : 
     452          46 :         stack[nDims - 1].dst_inc_offset = nSourceSize;
     453         118 :         for (size_t i = nDims - 1; i > 0;)
     454             :         {
     455          72 :             --i;
     456          72 :             stack[i].dst_inc_offset =
     457          72 :                 stack[i + 1].dst_inc_offset *
     458          72 :                 static_cast<size_t>(m_anOuterBlockSize[i + 1]);
     459             :         }
     460             :     }
     461             :     else
     462             :     {
     463          20 :         stack[0].dst_inc_offset = nSourceSize;
     464          60 :         for (size_t i = 1; i < nDims; ++i)
     465             :         {
     466          40 :             stack[i].dst_inc_offset =
     467          40 :                 stack[i - 1].dst_inc_offset *
     468          40 :                 static_cast<size_t>(m_anOuterBlockSize[i - 1]);
     469             :         }
     470             : 
     471          20 :         stack[nDims - 1].src_inc_offset = nSourceSize;
     472          60 :         for (size_t i = nDims - 1; i > 0;)
     473             :         {
     474          40 :             --i;
     475          40 :             stack[i].src_inc_offset =
     476          40 :                 stack[i + 1].src_inc_offset *
     477          40 :                 static_cast<size_t>(m_anOuterBlockSize[i + 1]);
     478             :         }
     479             :     }
     480             : 
     481          66 :     stack[0].src_ptr = abySrc.data();
     482          66 :     stack[0].dst_ptr = &abyDst[0];
     483             : 
     484          66 :     size_t dimIdx = 0;
     485        1058 : lbl_next_depth:
     486        1058 :     if (dimIdx == nDims)
     487             :     {
     488         744 :         void *dst_ptr = stack[nDims].dst_ptr;
     489         744 :         const void *src_ptr = stack[nDims].src_ptr;
     490         744 :         if (nSourceSize == 1)
     491         264 :             *stack[nDims].dst_ptr = *stack[nDims].src_ptr;
     492         480 :         else if (nSourceSize == 2)
     493         120 :             *static_cast<uint16_t *>(dst_ptr) =
     494         120 :                 *static_cast<const uint16_t *>(src_ptr);
     495         360 :         else if (nSourceSize == 4)
     496         168 :             *static_cast<uint32_t *>(dst_ptr) =
     497         168 :                 *static_cast<const uint32_t *>(src_ptr);
     498         192 :         else if (nSourceSize == 8)
     499         168 :             *static_cast<uint64_t *>(dst_ptr) =
     500         168 :                 *static_cast<const uint64_t *>(src_ptr);
     501             :         else
     502          24 :             memcpy(dst_ptr, src_ptr, nSourceSize);
     503             :     }
     504             :     else
     505             :     {
     506         314 :         stack[dimIdx].nIters = static_cast<size_t>(m_anOuterBlockSize[dimIdx]);
     507             :         while (true)
     508             :         {
     509         992 :             dimIdx++;
     510         992 :             stack[dimIdx].src_ptr = stack[dimIdx - 1].src_ptr;
     511         992 :             stack[dimIdx].dst_ptr = stack[dimIdx - 1].dst_ptr;
     512         992 :             goto lbl_next_depth;
     513         992 :         lbl_return_to_caller:
     514         992 :             dimIdx--;
     515         992 :             if ((--stack[dimIdx].nIters) == 0)
     516         314 :                 break;
     517         678 :             stack[dimIdx].src_ptr += stack[dimIdx].src_inc_offset;
     518         678 :             stack[dimIdx].dst_ptr += stack[dimIdx].dst_inc_offset;
     519             :         }
     520             :     }
     521        1058 :     if (dimIdx > 0)
     522         992 :         goto lbl_return_to_caller;
     523          66 : }
     524             : 
     525             : /************************************************************************/
     526             : /*                     ZarrV2Array::LoadBlockData()                     */
     527             : /************************************************************************/
     528             : 
     529        5397 : bool ZarrV2Array::LoadBlockData(const uint64_t *blockIndices,
     530             :                                 bool &bMissingBlockOut) const
     531             : {
     532       10794 :     return LoadBlockData(blockIndices,
     533             :                          false,  // use mutex
     534        5397 :                          m_psDecompressor, m_abyRawBlockData,
     535        5397 :                          m_abyTmpRawBlockData, m_abyDecodedBlockData,
     536        5397 :                          bMissingBlockOut);
     537             : }
     538             : 
     539       17467 : bool ZarrV2Array::LoadBlockData(const uint64_t *blockIndices, bool bUseMutex,
     540             :                                 const CPLCompressor *psDecompressor,
     541             :                                 ZarrByteVectorQuickResize &abyRawBlockData,
     542             :                                 ZarrByteVectorQuickResize &abyTmpRawBlockData,
     543             :                                 ZarrByteVectorQuickResize &abyDecodedBlockData,
     544             :                                 bool &bMissingBlockOut) const
     545             : {
     546             :     // This method should NOT modify any ZarrArray member, as it is going to
     547             :     // be called concurrently from several threads.
     548             : 
     549             :     // Set those #define to avoid accidental use of some global variables
     550             : #define m_abyTmpRawBlockData cannot_use_here
     551             : #define m_abyRawBlockData cannot_use_here
     552             : #define m_abyDecodedBlockData cannot_use_here
     553             : #define m_psDecompressor cannot_use_here
     554             : 
     555       17467 :     bMissingBlockOut = false;
     556             : 
     557       34934 :     std::string osFilename = BuildChunkFilename(blockIndices);
     558             : 
     559             :     // For network file systems, get the streaming version of the filename,
     560             :     // as we don't need arbitrary seeking in the file
     561       17467 :     osFilename = VSIFileManager::GetHandler(osFilename.c_str())
     562       17467 :                      ->GetStreamingFilename(osFilename);
     563             : 
     564             :     // First if we have a tile presence cache, check tile presence from it
     565             :     bool bEarlyRet;
     566       17467 :     if (bUseMutex)
     567             :     {
     568       12070 :         std::lock_guard<std::mutex> oLock(m_oMutex);
     569       12070 :         bEarlyRet = IsBlockMissingFromCacheInfo(osFilename, blockIndices);
     570             :     }
     571             :     else
     572             :     {
     573        5397 :         bEarlyRet = IsBlockMissingFromCacheInfo(osFilename, blockIndices);
     574             :     }
     575       17467 :     if (bEarlyRet)
     576             :     {
     577          26 :         bMissingBlockOut = true;
     578          26 :         return true;
     579             :     }
     580             : 
     581       17441 :     VSILFILE *fp = nullptr;
     582             :     // This is the number of files returned in a S3 directory listing operation
     583       17441 :     constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
     584       17441 :     const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
     585             :                                            nullptr};
     586       17441 :     const auto nErrorBefore = CPLGetErrorCounter();
     587       17461 :     if ((m_osDimSeparator == "/" && !m_anOuterBlockSize.empty() &&
     588       34902 :          m_anOuterBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
     589       17441 :         (m_osDimSeparator != "/" &&
     590       17421 :          m_nTotalInnerChunkCount > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING))
     591             :     {
     592             :         // Avoid issuing ReadDir() when a lot of files are expected
     593             :         CPLConfigOptionSetter optionSetter("GDAL_DISABLE_READDIR_ON_OPEN",
     594       10982 :                                            "YES", true);
     595       10982 :         fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
     596             :     }
     597             :     else
     598             :     {
     599        6459 :         fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
     600             :     }
     601       17441 :     if (fp == nullptr)
     602             :     {
     603        4057 :         if (nErrorBefore != CPLGetErrorCounter())
     604             :         {
     605           4 :             return false;
     606             :         }
     607             :         else
     608             :         {
     609             :             // Missing files are OK and indicate nodata_value
     610        4053 :             CPLDebugOnly(ZARR_DEBUG_KEY, "Block %s missing (=nodata)",
     611             :                          osFilename.c_str());
     612        4053 :             bMissingBlockOut = true;
     613        4053 :             return true;
     614             :         }
     615             :     }
     616             : 
     617       13384 :     bMissingBlockOut = false;
     618       13384 :     bool bRet = true;
     619       13384 :     size_t nRawDataSize = abyRawBlockData.size();
     620       13384 :     if (psDecompressor == nullptr)
     621             :     {
     622        7970 :         nRawDataSize = VSIFReadL(&abyRawBlockData[0], 1, nRawDataSize, fp);
     623             :     }
     624             :     else
     625             :     {
     626        5414 :         VSIFSeekL(fp, 0, SEEK_END);
     627        5414 :         const auto nSize = VSIFTellL(fp);
     628        5414 :         VSIFSeekL(fp, 0, SEEK_SET);
     629        5414 :         if (nSize > static_cast<vsi_l_offset>(std::numeric_limits<int>::max()))
     630             :         {
     631           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Too large tile %s",
     632             :                      osFilename.c_str());
     633           0 :             bRet = false;
     634             :         }
     635             :         else
     636             :         {
     637       10828 :             ZarrByteVectorQuickResize abyCompressedData;
     638             :             try
     639             :             {
     640        5414 :                 abyCompressedData.resize(static_cast<size_t>(nSize));
     641             :             }
     642           0 :             catch (const std::exception &)
     643             :             {
     644           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory,
     645             :                          "Cannot allocate memory for tile %s",
     646             :                          osFilename.c_str());
     647           0 :                 bRet = false;
     648             :             }
     649             : 
     650       16242 :             if (bRet &&
     651       10828 :                 (abyCompressedData.empty() ||
     652        5414 :                  VSIFReadL(&abyCompressedData[0], 1, abyCompressedData.size(),
     653        5414 :                            fp) != abyCompressedData.size()))
     654             :             {
     655           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     656             :                          "Could not read tile %s correctly",
     657             :                          osFilename.c_str());
     658           0 :                 bRet = false;
     659             :             }
     660             :             else
     661             :             {
     662        5414 :                 void *out_buffer = &abyRawBlockData[0];
     663        5414 :                 if (!psDecompressor->pfnFunc(
     664        5414 :                         abyCompressedData.data(), abyCompressedData.size(),
     665             :                         &out_buffer, &nRawDataSize, nullptr,
     666        5414 :                         psDecompressor->user_data))
     667             :                 {
     668           3 :                     CPLError(CE_Failure, CPLE_AppDefined,
     669             :                              "Decompression of tile %s failed",
     670             :                              osFilename.c_str());
     671           3 :                     bRet = false;
     672             :                 }
     673             :             }
     674             :         }
     675             :     }
     676       13384 :     VSIFCloseL(fp);
     677       13384 :     if (!bRet)
     678           3 :         return false;
     679             : 
     680       13396 :     for (int i = m_oFiltersArray.Size(); i > 0;)
     681             :     {
     682          15 :         --i;
     683             :         const CPLCompressor *psFilterDecompressor;
     684          15 :         CPLStringList aosOptions;
     685          15 :         std::string osFilterId;
     686             :         {
     687             :             // Below CPLJSONObject/CPLJSONArray uses are not thread safe
     688          15 :             std::lock_guard<std::mutex> oLock(m_oMutex);
     689          15 :             const auto &oFilter = m_oFiltersArray[i];
     690          15 :             osFilterId = oFilter["id"].ToString();
     691          15 :             if (osFilterId == "bitround")
     692           1 :                 continue;  // no-op on decoding
     693          14 :             psFilterDecompressor =
     694          14 :                 EQUAL(osFilterId.c_str(), "shuffle")
     695          26 :                     ? ZarrGetShuffleDecompressor()
     696          12 :                 : EQUAL(osFilterId.c_str(), "quantize")
     697          22 :                     ? ZarrGetQuantizeDecompressor()
     698          10 :                 : EQUAL(osFilterId.c_str(), "fixedscaleoffset")
     699          10 :                     ? ZarrGetFixedScaleOffsetDecompressor()
     700           5 :                     : CPLGetDecompressor(osFilterId.c_str());
     701          14 :             CPLAssert(psFilterDecompressor);
     702             : 
     703          61 :             for (const auto &obj : oFilter.GetChildren())
     704             :             {
     705          94 :                 aosOptions.SetNameValue(obj.GetName().c_str(),
     706         141 :                                         obj.ToString().c_str());
     707             :             }
     708             :         }
     709             : 
     710          14 :         void *out_buffer = &abyTmpRawBlockData[0];
     711          14 :         size_t nOutSize = abyTmpRawBlockData.size();
     712          14 :         if (!psFilterDecompressor->pfnFunc(
     713          14 :                 abyRawBlockData.data(), nRawDataSize, &out_buffer, &nOutSize,
     714          14 :                 aosOptions.List(), psFilterDecompressor->user_data))
     715             :         {
     716           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     717             :                      "Filter %s for tile %s failed", osFilterId.c_str(),
     718             :                      osFilename.c_str());
     719           0 :             return false;
     720             :         }
     721             : 
     722          14 :         nRawDataSize = nOutSize;
     723          14 :         std::swap(abyRawBlockData, abyTmpRawBlockData);
     724             :     }
     725       13381 :     if (nRawDataSize != abyRawBlockData.size())
     726             :     {
     727           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     728             :                  "Decompressed tile %s has not expected size after filters",
     729             :                  osFilename.c_str());
     730           0 :         return false;
     731             :     }
     732             : 
     733       13381 :     if (m_bFortranOrder && !m_aoDims.empty())
     734             :     {
     735          46 :         BlockTranspose(abyRawBlockData, abyTmpRawBlockData, true);
     736          46 :         std::swap(abyRawBlockData, abyTmpRawBlockData);
     737             :     }
     738             : 
     739       13381 :     if (!abyDecodedBlockData.empty())
     740             :     {
     741             :         const size_t nSourceSize =
     742         386 :             m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
     743         386 :         const auto nDTSize = m_oType.GetSize();
     744         386 :         const size_t nValues = abyDecodedBlockData.size() / nDTSize;
     745         386 :         const GByte *pSrc = abyRawBlockData.data();
     746         386 :         GByte *pDst = &abyDecodedBlockData[0];
     747        2677 :         for (size_t i = 0; i < nValues;
     748        2291 :              i++, pSrc += nSourceSize, pDst += nDTSize)
     749             :         {
     750        2291 :             DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
     751             :         }
     752             :     }
     753             : 
     754       13381 :     return true;
     755             : 
     756             : #undef m_abyTmpRawBlockData
     757             : #undef m_abyRawBlockData
     758             : #undef m_abyDecodedBlockData
     759             : #undef m_psDecompressor
     760             : }
     761             : 
     762             : /************************************************************************/
     763             : /*                      ZarrV2Array::IAdviseRead()                      */
     764             : /************************************************************************/
     765             : 
     766         609 : bool ZarrV2Array::IAdviseRead(const GUInt64 *arrayStartIdx, const size_t *count,
     767             :                               CSLConstList papszOptions) const
     768             : {
     769        1218 :     std::vector<uint64_t> anIndicesCur;
     770         609 :     int nThreadsMax = 0;
     771        1218 :     std::vector<uint64_t> anReqBlocksIndices;
     772         609 :     size_t nReqBlocks = 0;
     773         609 :     if (!IAdviseReadCommon(arrayStartIdx, count, papszOptions, anIndicesCur,
     774             :                            nThreadsMax, anReqBlocksIndices, nReqBlocks))
     775             :     {
     776           2 :         return false;
     777             :     }
     778         607 :     if (nThreadsMax <= 1)
     779             :     {
     780         336 :         return true;
     781             :     }
     782             : 
     783             :     const int nThreads = static_cast<int>(
     784         271 :         std::min(static_cast<size_t>(nThreadsMax), nReqBlocks));
     785             : 
     786         271 :     CPLWorkerThreadPool *wtp = GDALGetGlobalThreadPool(nThreadsMax);
     787         271 :     if (wtp == nullptr)
     788           0 :         return false;
     789             : 
     790             :     struct JobStruct
     791             :     {
     792             :         JobStruct() = default;
     793             : 
     794             :         JobStruct(const JobStruct &) = delete;
     795             :         JobStruct &operator=(const JobStruct &) = delete;
     796             : 
     797             :         JobStruct(JobStruct &&) = default;
     798             :         JobStruct &operator=(JobStruct &&) = default;
     799             : 
     800             :         const ZarrV2Array *poArray = nullptr;
     801             :         bool *pbGlobalStatus = nullptr;
     802             :         int *pnRemainingThreads = nullptr;
     803             :         const std::vector<uint64_t> *panReqBlocksIndices = nullptr;
     804             :         size_t nFirstIdx = 0;
     805             :         size_t nLastIdxNotIncluded = 0;
     806             :     };
     807             : 
     808         271 :     std::vector<JobStruct> asJobStructs;
     809             : 
     810         271 :     bool bGlobalStatus = true;
     811         271 :     int nRemainingThreads = nThreads;
     812             :     // Check for very highly overflow in below loop
     813         271 :     assert(static_cast<size_t>(nThreads) <
     814             :            std::numeric_limits<size_t>::max() / nReqBlocks);
     815             : 
     816             :     // Setup jobs
     817        1223 :     for (int i = 0; i < nThreads; i++)
     818             :     {
     819         952 :         JobStruct jobStruct;
     820         952 :         jobStruct.poArray = this;
     821         952 :         jobStruct.pbGlobalStatus = &bGlobalStatus;
     822         952 :         jobStruct.pnRemainingThreads = &nRemainingThreads;
     823         952 :         jobStruct.panReqBlocksIndices = &anReqBlocksIndices;
     824         952 :         jobStruct.nFirstIdx = static_cast<size_t>(i * nReqBlocks / nThreads);
     825         952 :         jobStruct.nLastIdxNotIncluded = std::min(
     826         952 :             static_cast<size_t>((i + 1) * nReqBlocks / nThreads), nReqBlocks);
     827         952 :         asJobStructs.emplace_back(std::move(jobStruct));
     828             :     }
     829             : 
     830         952 :     const auto JobFunc = [](void *pThreadData)
     831             :     {
     832         952 :         const JobStruct *jobStruct =
     833             :             static_cast<const JobStruct *>(pThreadData);
     834             : 
     835         952 :         const auto poArray = jobStruct->poArray;
     836         952 :         const size_t l_nDims = poArray->GetDimensionCount();
     837         952 :         ZarrByteVectorQuickResize abyRawBlockData;
     838         952 :         ZarrByteVectorQuickResize abyDecodedBlockData;
     839         952 :         ZarrByteVectorQuickResize abyTmpRawBlockData;
     840             :         const CPLCompressor *psDecompressor =
     841         952 :             CPLGetDecompressor(poArray->m_osDecompressorId.c_str());
     842             : 
     843       13022 :         for (size_t iReq = jobStruct->nFirstIdx;
     844       13022 :              iReq < jobStruct->nLastIdxNotIncluded; ++iReq)
     845             :         {
     846             :             // Check if we must early exit
     847             :             {
     848       12070 :                 std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     849       12070 :                 if (!(*jobStruct->pbGlobalStatus))
     850           0 :                     return;
     851             :             }
     852             : 
     853             :             const uint64_t *blockIndices =
     854       12070 :                 jobStruct->panReqBlocksIndices->data() + iReq * l_nDims;
     855             : 
     856       12070 :             if (!poArray->AllocateWorkingBuffers(
     857             :                     abyRawBlockData, abyTmpRawBlockData, abyDecodedBlockData))
     858             :             {
     859           0 :                 std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     860           0 :                 *jobStruct->pbGlobalStatus = false;
     861           0 :                 break;
     862             :             }
     863             : 
     864       12070 :             bool bIsEmpty = false;
     865       12070 :             bool success = poArray->LoadBlockData(
     866             :                 blockIndices,
     867             :                 true,  // use mutex
     868             :                 psDecompressor, abyRawBlockData, abyTmpRawBlockData,
     869             :                 abyDecodedBlockData, bIsEmpty);
     870             : 
     871       12070 :             std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     872       12070 :             if (!success)
     873             :             {
     874           0 :                 *jobStruct->pbGlobalStatus = false;
     875           0 :                 break;
     876             :             }
     877             : 
     878       24140 :             CachedBlock cachedBlock;
     879       12070 :             if (!bIsEmpty)
     880             :             {
     881       11172 :                 if (!abyDecodedBlockData.empty())
     882          40 :                     std::swap(cachedBlock.abyDecoded, abyDecodedBlockData);
     883             :                 else
     884       11132 :                     std::swap(cachedBlock.abyDecoded, abyRawBlockData);
     885             :             }
     886             :             const std::vector<uint64_t> cacheKey{blockIndices,
     887       24140 :                                                  blockIndices + l_nDims};
     888       12070 :             poArray->m_oChunkCache[cacheKey] = std::move(cachedBlock);
     889             :         }
     890             : 
     891         952 :         std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     892         952 :         (*jobStruct->pnRemainingThreads)--;
     893             :     };
     894             : 
     895             :     // Start jobs
     896        1223 :     for (int i = 0; i < nThreads; i++)
     897             :     {
     898         952 :         if (!wtp->SubmitJob(JobFunc, &asJobStructs[i]))
     899             :         {
     900           0 :             std::lock_guard<std::mutex> oLock(m_oMutex);
     901           0 :             bGlobalStatus = false;
     902           0 :             nRemainingThreads = i;
     903           0 :             break;
     904             :         }
     905             :     }
     906             : 
     907             :     // Wait for all jobs to be finished
     908             :     while (true)
     909             :     {
     910             :         {
     911         926 :             std::lock_guard<std::mutex> oLock(m_oMutex);
     912         926 :             if (nRemainingThreads == 0)
     913         271 :                 break;
     914             :         }
     915         655 :         wtp->WaitEvent();
     916         655 :     }
     917             : 
     918         271 :     return bGlobalStatus;
     919             : }
     920             : 
     921             : /************************************************************************/
     922             : /*                    ZarrV2Array::FlushDirtyBlock()                    */
     923             : /************************************************************************/
     924             : 
     925       22155 : bool ZarrV2Array::FlushDirtyBlock() const
     926             : {
     927       22155 :     if (!m_bDirtyBlock)
     928        9238 :         return true;
     929       12917 :     m_bDirtyBlock = false;
     930             : 
     931       25834 :     std::string osFilename = BuildChunkFilename(m_anCachedBlockIndices.data());
     932             : 
     933             :     const size_t nSourceSize =
     934       12917 :         m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
     935       12917 :     const auto &abyBlock = m_abyDecodedBlockData.empty()
     936             :                                ? m_abyRawBlockData
     937       12917 :                                : m_abyDecodedBlockData;
     938             : 
     939       12917 :     if (IsEmptyBlock(abyBlock))
     940             :     {
     941        1368 :         m_bCachedBlockEmpty = true;
     942             : 
     943             :         VSIStatBufL sStat;
     944        1368 :         if (VSIStatL(osFilename.c_str(), &sStat) == 0)
     945             :         {
     946         216 :             CPLDebugOnly(ZARR_DEBUG_KEY,
     947             :                          "Deleting tile %s that has now empty content",
     948             :                          osFilename.c_str());
     949         216 :             return VSIUnlink(osFilename.c_str()) == 0;
     950             :         }
     951        1152 :         return true;
     952             :     }
     953             : 
     954       11549 :     if (!m_abyDecodedBlockData.empty())
     955             :     {
     956          40 :         const size_t nDTSize = m_oType.GetSize();
     957          40 :         const size_t nValues = m_abyDecodedBlockData.size() / nDTSize;
     958          40 :         GByte *pDst = &m_abyRawBlockData[0];
     959          40 :         const GByte *pSrc = m_abyDecodedBlockData.data();
     960         280 :         for (size_t i = 0; i < nValues;
     961         240 :              i++, pDst += nSourceSize, pSrc += nDTSize)
     962             :         {
     963         240 :             EncodeElt(m_aoDtypeElts, pSrc, pDst);
     964             :         }
     965             :     }
     966             : 
     967       11549 :     if (m_bFortranOrder && !m_aoDims.empty())
     968             :     {
     969          20 :         BlockTranspose(m_abyRawBlockData, m_abyTmpRawBlockData, false);
     970          20 :         std::swap(m_abyRawBlockData, m_abyTmpRawBlockData);
     971             :     }
     972             : 
     973       11549 :     size_t nRawDataSize = m_abyRawBlockData.size();
     974       11552 :     for (const auto &oFilter : m_oFiltersArray)
     975             :     {
     976          10 :         const auto osFilterId = oFilter["id"].ToString();
     977           9 :         if (osFilterId == "quantize" || osFilterId == "fixedscaleoffset" ||
     978           4 :             osFilterId == "bitround")
     979             :         {
     980           2 :             CPLError(CE_Failure, CPLE_NotSupported,
     981             :                      "%s filter not supported for writing", osFilterId.c_str());
     982           2 :             return false;
     983             :         }
     984             :         const auto psFilterCompressor =
     985           3 :             EQUAL(osFilterId.c_str(), "shuffle")
     986           3 :                 ? ZarrGetShuffleCompressor()
     987           2 :                 : CPLGetCompressor(osFilterId.c_str());
     988           3 :         CPLAssert(psFilterCompressor);
     989             : 
     990           3 :         CPLStringList aosOptions;
     991           9 :         for (const auto &obj : oFilter.GetChildren())
     992             :         {
     993          12 :             aosOptions.SetNameValue(obj.GetName().c_str(),
     994          18 :                                     obj.ToString().c_str());
     995             :         }
     996           3 :         void *out_buffer = &m_abyTmpRawBlockData[0];
     997           3 :         size_t nOutSize = m_abyTmpRawBlockData.size();
     998           3 :         if (!psFilterCompressor->pfnFunc(
     999           3 :                 m_abyRawBlockData.data(), nRawDataSize, &out_buffer, &nOutSize,
    1000           3 :                 aosOptions.List(), psFilterCompressor->user_data))
    1001             :         {
    1002           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1003             :                      "Filter %s for tile %s failed", osFilterId.c_str(),
    1004             :                      osFilename.c_str());
    1005           0 :             return false;
    1006             :         }
    1007             : 
    1008           3 :         nRawDataSize = nOutSize;
    1009           3 :         std::swap(m_abyRawBlockData, m_abyTmpRawBlockData);
    1010             :     }
    1011             : 
    1012       11547 :     if (m_osDimSeparator == "/")
    1013             :     {
    1014          20 :         std::string osDir = CPLGetDirnameSafe(osFilename.c_str());
    1015             :         VSIStatBufL sStat;
    1016          20 :         if (VSIStatL(osDir.c_str(), &sStat) != 0)
    1017             :         {
    1018          20 :             if (VSIMkdirRecursive(osDir.c_str(), 0755) != 0)
    1019             :             {
    1020           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1021             :                          "Cannot create directory %s", osDir.c_str());
    1022           0 :                 return false;
    1023             :             }
    1024             :         }
    1025             :     }
    1026             : 
    1027       11547 :     if (m_psCompressor == nullptr && m_psDecompressor != nullptr)
    1028             :     {
    1029             :         // Case of imagecodecs_tiff
    1030             : 
    1031           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    1032             :                  "Only decompression supported for '%s' compression method",
    1033             :                  m_osDecompressorId.c_str());
    1034           1 :         return false;
    1035             :     }
    1036             : 
    1037       11546 :     VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
    1038       11546 :     if (fp == nullptr)
    1039             :     {
    1040           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot create tile %s",
    1041             :                  osFilename.c_str());
    1042           0 :         return false;
    1043             :     }
    1044             : 
    1045       11546 :     bool bRet = true;
    1046       11546 :     if (m_psCompressor == nullptr)
    1047             :     {
    1048        6160 :         if (VSIFWriteL(m_abyRawBlockData.data(), 1, nRawDataSize, fp) !=
    1049             :             nRawDataSize)
    1050             :         {
    1051           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    1052             :                      "Could not write tile %s correctly", osFilename.c_str());
    1053           2 :             bRet = false;
    1054             :         }
    1055             :     }
    1056             :     else
    1057             :     {
    1058       10772 :         std::vector<GByte> abyCompressedData;
    1059             :         try
    1060             :         {
    1061        5386 :             constexpr size_t MIN_BUF_SIZE = 64;  // somewhat arbitrary
    1062        5386 :             abyCompressedData.resize(static_cast<size_t>(
    1063        5386 :                 MIN_BUF_SIZE + nRawDataSize + nRawDataSize / 3));
    1064             :         }
    1065           0 :         catch (const std::exception &)
    1066             :         {
    1067           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
    1068             :                      "Cannot allocate memory for tile %s", osFilename.c_str());
    1069           0 :             bRet = false;
    1070             :         }
    1071             : 
    1072        5386 :         if (bRet)
    1073             :         {
    1074        5386 :             void *out_buffer = &abyCompressedData[0];
    1075        5386 :             size_t out_size = abyCompressedData.size();
    1076       10772 :             CPLStringList aosOptions;
    1077        5386 :             const auto &compressorConfig = m_oCompressorJSon;
    1078       16158 :             for (const auto &obj : compressorConfig.GetChildren())
    1079             :             {
    1080       21544 :                 aosOptions.SetNameValue(obj.GetName().c_str(),
    1081       32316 :                                         obj.ToString().c_str());
    1082             :             }
    1083        5386 :             if (EQUAL(m_psCompressor->pszId, "blosc") &&
    1084           0 :                 m_oType.GetClass() == GEDTC_NUMERIC)
    1085             :             {
    1086             :                 aosOptions.SetNameValue(
    1087             :                     "TYPESIZE",
    1088             :                     CPLSPrintf("%d", GDALGetDataTypeSizeBytes(
    1089             :                                          GDALGetNonComplexDataType(
    1090           0 :                                              m_oType.GetNumericDataType()))));
    1091             :             }
    1092             : 
    1093        5386 :             if (!m_psCompressor->pfnFunc(
    1094        5386 :                     m_abyRawBlockData.data(), nRawDataSize, &out_buffer,
    1095        5386 :                     &out_size, aosOptions.List(), m_psCompressor->user_data))
    1096             :             {
    1097           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1098             :                          "Compression of tile %s failed", osFilename.c_str());
    1099           0 :                 bRet = false;
    1100             :             }
    1101        5386 :             abyCompressedData.resize(out_size);
    1102             :         }
    1103             : 
    1104       10772 :         if (bRet &&
    1105        5386 :             VSIFWriteL(abyCompressedData.data(), 1, abyCompressedData.size(),
    1106        5386 :                        fp) != abyCompressedData.size())
    1107             :         {
    1108           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1109             :                      "Could not write tile %s correctly", osFilename.c_str());
    1110           0 :             bRet = false;
    1111             :         }
    1112             :     }
    1113       11546 :     VSIFCloseL(fp);
    1114             : 
    1115       11546 :     return bRet;
    1116             : }
    1117             : 
    1118             : /************************************************************************/
    1119             : /*                         BuildChunkFilename()                         */
    1120             : /************************************************************************/
    1121             : 
    1122       30391 : std::string ZarrV2Array::BuildChunkFilename(const uint64_t *blockIndices) const
    1123             : {
    1124       30391 :     std::string osFilename;
    1125       30391 :     if (m_aoDims.empty())
    1126             :     {
    1127           5 :         osFilename = "0";
    1128             :     }
    1129             :     else
    1130             :     {
    1131       91295 :         for (size_t i = 0; i < m_aoDims.size(); ++i)
    1132             :         {
    1133       60909 :             if (!osFilename.empty())
    1134       30523 :                 osFilename += m_osDimSeparator;
    1135       60909 :             osFilename += std::to_string(blockIndices[i]);
    1136             :         }
    1137             :     }
    1138             : 
    1139       60782 :     return CPLFormFilenameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
    1140       91173 :                                osFilename.c_str(), nullptr);
    1141             : }
    1142             : 
    1143             : /************************************************************************/
    1144             : /*                          GetDataDirectory()                          */
    1145             : /************************************************************************/
    1146             : 
    1147           4 : std::string ZarrV2Array::GetDataDirectory() const
    1148             : {
    1149           4 :     return CPLGetDirnameSafe(m_osFilename.c_str());
    1150             : }
    1151             : 
    1152             : /************************************************************************/
    1153             : /*                    GetChunkIndicesFromFilename()                     */
    1154             : /************************************************************************/
    1155             : 
    1156             : CPLStringList
    1157          10 : ZarrV2Array::GetChunkIndicesFromFilename(const char *pszFilename) const
    1158             : {
    1159             :     return CPLStringList(
    1160          10 :         CSLTokenizeString2(pszFilename, m_osDimSeparator.c_str(), 0));
    1161             : }
    1162             : 
    1163             : /************************************************************************/
    1164             : /*                             ParseDtype()                             */
    1165             : /************************************************************************/
    1166             : 
    1167          26 : static size_t GetAlignment(const CPLJSONObject &obj)
    1168             : {
    1169          26 :     if (obj.GetType() == CPLJSONObject::Type::String)
    1170             :     {
    1171          69 :         const auto str = obj.ToString();
    1172          23 :         if (str.size() < 3)
    1173           0 :             return 1;
    1174          23 :         const char chType = str[1];
    1175          23 :         const int nBytes = atoi(str.c_str() + 2);
    1176          23 :         if (chType == 'S')
    1177           2 :             return sizeof(char *);
    1178          21 :         if (chType == 'c' && nBytes == 8)
    1179           0 :             return sizeof(float);
    1180          21 :         if (chType == 'c' && nBytes == 16)
    1181           0 :             return sizeof(double);
    1182          21 :         return nBytes;
    1183             :     }
    1184           3 :     else if (obj.GetType() == CPLJSONObject::Type::Array)
    1185             :     {
    1186           6 :         const auto oArray = obj.ToArray();
    1187           3 :         size_t nAlignment = 1;
    1188           9 :         for (const auto &oElt : oArray)
    1189             :         {
    1190           6 :             const auto oEltArray = oElt.ToArray();
    1191          12 :             if (!oEltArray.IsValid() || oEltArray.Size() != 2 ||
    1192          12 :                 oEltArray[0].GetType() != CPLJSONObject::Type::String)
    1193             :             {
    1194           0 :                 return 1;
    1195             :             }
    1196           6 :             nAlignment = std::max(nAlignment, GetAlignment(oEltArray[1]));
    1197           6 :             if (nAlignment == sizeof(void *))
    1198           0 :                 break;
    1199             :         }
    1200           3 :         return nAlignment;
    1201             :     }
    1202           0 :     return 1;
    1203             : }
    1204             : 
    1205         819 : static GDALExtendedDataType ParseDtype(const CPLJSONObject &obj,
    1206             :                                        std::vector<DtypeElt> &elts)
    1207             : {
    1208          29 :     const auto AlignOffsetOn = [](size_t offset, size_t alignment)
    1209          29 :     { return offset + (alignment - (offset % alignment)) % alignment; };
    1210             : 
    1211             :     do
    1212             :     {
    1213         819 :         if (obj.GetType() == CPLJSONObject::Type::String)
    1214             :         {
    1215        1616 :             const auto str = obj.ToString();
    1216         808 :             char chEndianness = 0;
    1217             :             char chType;
    1218             :             int nBytes;
    1219         808 :             DtypeElt elt;
    1220         808 :             if (str.size() < 3)
    1221           3 :                 break;
    1222         805 :             chEndianness = str[0];
    1223         805 :             chType = str[1];
    1224         805 :             nBytes = atoi(str.c_str() + 2);
    1225         805 :             if (nBytes <= 0 || nBytes >= 1000)
    1226             :                 break;
    1227             : 
    1228         803 :             elt.needByteSwapping = false;
    1229         803 :             if ((nBytes > 1 && chType != 'S') || chType == 'U')
    1230             :             {
    1231         457 :                 if (chEndianness == '<')
    1232         393 :                     elt.needByteSwapping = (CPL_IS_LSB == 0);
    1233          64 :                 else if (chEndianness == '>')
    1234          64 :                     elt.needByteSwapping = (CPL_IS_LSB != 0);
    1235             :             }
    1236             : 
    1237             :             GDALDataType eDT;
    1238         803 :             if (!elts.empty())
    1239             :             {
    1240          11 :                 elt.nativeOffset =
    1241          11 :                     elts.back().nativeOffset + elts.back().nativeSize;
    1242             :             }
    1243         803 :             elt.nativeSize = nBytes;
    1244         803 :             if (chType == 'b' && nBytes == 1)  // boolean
    1245             :             {
    1246          69 :                 elt.nativeType = DtypeElt::NativeType::BOOLEAN;
    1247          69 :                 eDT = GDT_UInt8;
    1248             :             }
    1249         734 :             else if (chType == 'u' && nBytes == 1)
    1250             :             {
    1251         252 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
    1252         252 :                 eDT = GDT_UInt8;
    1253             :             }
    1254         482 :             else if (chType == 'i' && nBytes == 1)
    1255             :             {
    1256          16 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
    1257          16 :                 eDT = GDT_Int8;
    1258             :             }
    1259         466 :             else if (chType == 'i' && nBytes == 2)
    1260             :             {
    1261          24 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
    1262          24 :                 eDT = GDT_Int16;
    1263             :             }
    1264         442 :             else if (chType == 'i' && nBytes == 4)
    1265             :             {
    1266          32 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
    1267          32 :                 eDT = GDT_Int32;
    1268             :             }
    1269         410 :             else if (chType == 'i' && nBytes == 8)
    1270             :             {
    1271          19 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
    1272          19 :                 eDT = GDT_Int64;
    1273             :             }
    1274         391 :             else if (chType == 'u' && nBytes == 2)
    1275             :             {
    1276          47 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
    1277          47 :                 eDT = GDT_UInt16;
    1278             :             }
    1279         344 :             else if (chType == 'u' && nBytes == 4)
    1280             :             {
    1281          24 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
    1282          24 :                 eDT = GDT_UInt32;
    1283             :             }
    1284         320 :             else if (chType == 'u' && nBytes == 8)
    1285             :             {
    1286          18 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
    1287          18 :                 eDT = GDT_UInt64;
    1288             :             }
    1289         302 :             else if (chType == 'f' && nBytes == 2)
    1290             :             {
    1291           5 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
    1292           5 :                 eDT = GDT_Float16;
    1293             :             }
    1294         297 :             else if (chType == 'f' && nBytes == 4)
    1295             :             {
    1296          56 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
    1297          56 :                 eDT = GDT_Float32;
    1298             :             }
    1299         241 :             else if (chType == 'f' && nBytes == 8)
    1300             :             {
    1301         194 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
    1302         194 :                 eDT = GDT_Float64;
    1303             :             }
    1304          47 :             else if (chType == 'c' && nBytes == 8)
    1305             :             {
    1306          15 :                 elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
    1307          15 :                 eDT = GDT_CFloat32;
    1308             :             }
    1309          32 :             else if (chType == 'c' && nBytes == 16)
    1310             :             {
    1311          16 :                 elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
    1312          16 :                 eDT = GDT_CFloat64;
    1313             :             }
    1314          16 :             else if (chType == 'S')
    1315             :             {
    1316           9 :                 elt.nativeType = DtypeElt::NativeType::STRING_ASCII;
    1317           9 :                 elt.gdalType = GDALExtendedDataType::CreateString(nBytes);
    1318           9 :                 elt.gdalSize = elt.gdalType.GetSize();
    1319           9 :                 elts.emplace_back(elt);
    1320           9 :                 return GDALExtendedDataType::CreateString(nBytes);
    1321             :             }
    1322           7 :             else if (chType == 'U')
    1323             :             {
    1324           6 :                 elt.nativeType = DtypeElt::NativeType::STRING_UNICODE;
    1325             :                 // the dtype declaration is number of UCS4 characters. Store it
    1326             :                 // as bytes
    1327           6 :                 elt.nativeSize *= 4;
    1328             :                 // We can really map UCS4 size to UTF-8
    1329           6 :                 elt.gdalType = GDALExtendedDataType::CreateString();
    1330           6 :                 elt.gdalSize = elt.gdalType.GetSize();
    1331           6 :                 elts.emplace_back(elt);
    1332           6 :                 return GDALExtendedDataType::CreateString();
    1333             :             }
    1334             :             else
    1335           1 :                 break;
    1336         787 :             elt.gdalType = GDALExtendedDataType::Create(eDT);
    1337         787 :             elt.gdalSize = elt.gdalType.GetSize();
    1338         787 :             elts.emplace_back(elt);
    1339         787 :             return GDALExtendedDataType::Create(eDT);
    1340             :         }
    1341          11 :         else if (obj.GetType() == CPLJSONObject::Type::Array)
    1342             :         {
    1343           9 :             bool error = false;
    1344           9 :             const auto oArray = obj.ToArray();
    1345           9 :             std::vector<std::unique_ptr<GDALEDTComponent>> comps;
    1346           9 :             size_t offset = 0;
    1347           9 :             size_t alignmentMax = 1;
    1348          29 :             for (const auto &oElt : oArray)
    1349             :             {
    1350          20 :                 const auto oEltArray = oElt.ToArray();
    1351          40 :                 if (!oEltArray.IsValid() || oEltArray.Size() != 2 ||
    1352          40 :                     oEltArray[0].GetType() != CPLJSONObject::Type::String)
    1353             :                 {
    1354           0 :                     error = true;
    1355           0 :                     break;
    1356             :                 }
    1357          20 :                 GDALExtendedDataType subDT = ParseDtype(oEltArray[1], elts);
    1358          35 :                 if (subDT.GetClass() == GEDTC_NUMERIC &&
    1359          15 :                     subDT.GetNumericDataType() == GDT_Unknown)
    1360             :                 {
    1361           0 :                     error = true;
    1362           0 :                     break;
    1363             :                 }
    1364             : 
    1365          40 :                 const std::string osName = oEltArray[0].ToString();
    1366             :                 // Add padding for alignment
    1367          20 :                 const size_t alignmentSub = GetAlignment(oEltArray[1]);
    1368          20 :                 assert(alignmentSub);
    1369          20 :                 alignmentMax = std::max(alignmentMax, alignmentSub);
    1370          20 :                 offset = AlignOffsetOn(offset, alignmentSub);
    1371          40 :                 comps.emplace_back(std::unique_ptr<GDALEDTComponent>(
    1372          40 :                     new GDALEDTComponent(osName, offset, subDT)));
    1373          20 :                 offset += subDT.GetSize();
    1374             :             }
    1375           9 :             if (error)
    1376           0 :                 break;
    1377           9 :             size_t nTotalSize = offset;
    1378           9 :             nTotalSize = AlignOffsetOn(nTotalSize, alignmentMax);
    1379          18 :             return GDALExtendedDataType::Create(obj.ToString(), nTotalSize,
    1380          18 :                                                 std::move(comps));
    1381             :         }
    1382             :     } while (false);
    1383           8 :     CPLError(CE_Failure, CPLE_AppDefined,
    1384             :              "Invalid or unsupported format for dtype: %s",
    1385          16 :              obj.ToString().c_str());
    1386           8 :     return GDALExtendedDataType::Create(GDT_Unknown);
    1387             : }
    1388             : 
    1389         811 : static void SetGDALOffset(const GDALExtendedDataType &dt,
    1390             :                           const size_t nBaseOffset, std::vector<DtypeElt> &elts,
    1391             :                           size_t &iCurElt)
    1392             : {
    1393         811 :     if (dt.GetClass() == GEDTC_COMPOUND)
    1394             :     {
    1395           9 :         const auto &comps = dt.GetComponents();
    1396          29 :         for (const auto &comp : comps)
    1397             :         {
    1398          20 :             const size_t nBaseOffsetSub = nBaseOffset + comp->GetOffset();
    1399          20 :             SetGDALOffset(comp->GetType(), nBaseOffsetSub, elts, iCurElt);
    1400             :         }
    1401             :     }
    1402             :     else
    1403             :     {
    1404         802 :         elts[iCurElt].gdalOffset = nBaseOffset;
    1405         802 :         iCurElt++;
    1406             :     }
    1407         811 : }
    1408             : 
    1409             : /************************************************************************/
    1410             : /*                       ZarrV2Group::LoadArray()                       */
    1411             : /************************************************************************/
    1412             : 
    1413             : std::shared_ptr<ZarrArray>
    1414         817 : ZarrV2Group::LoadArray(const std::string &osArrayName,
    1415             :                        const std::string &osZarrayFilename,
    1416             :                        const CPLJSONObject &oRoot, bool bLoadedFromZMetadata,
    1417             :                        const CPLJSONObject &oAttributesIn) const
    1418             : {
    1419             :     // Add osZarrayFilename to m_poSharedResource during the scope
    1420             :     // of this function call.
    1421         817 :     ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
    1422        1634 :                                                        osZarrayFilename);
    1423         817 :     if (!filenameAdder.ok())
    1424           2 :         return nullptr;
    1425             : 
    1426        2445 :     const auto osFormat = oRoot["zarr_format"].ToString();
    1427         815 :     if (osFormat != "2")
    1428             :     {
    1429           3 :         CPLError(CE_Failure, CPLE_NotSupported,
    1430             :                  "Invalid value for zarr_format: %s", osFormat.c_str());
    1431           3 :         return nullptr;
    1432             :     }
    1433             : 
    1434         812 :     bool bFortranOrder = false;
    1435         812 :     const char *orderKey = "order";
    1436        2436 :     const auto osOrder = oRoot[orderKey].ToString();
    1437         812 :     if (osOrder == "C")
    1438             :     {
    1439             :         // ok
    1440             :     }
    1441          34 :     else if (osOrder == "F")
    1442             :     {
    1443          31 :         bFortranOrder = true;
    1444             :     }
    1445             :     else
    1446             :     {
    1447           3 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid value for %s",
    1448             :                  orderKey);
    1449           3 :         return nullptr;
    1450             :     }
    1451             : 
    1452        2427 :     const auto oShape = oRoot["shape"].ToArray();
    1453         809 :     if (!oShape.IsValid())
    1454             :     {
    1455           3 :         CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
    1456           3 :         return nullptr;
    1457             :     }
    1458             : 
    1459         806 :     const char *chunksKey = "chunks";
    1460        2418 :     const auto oChunks = oRoot[chunksKey].ToArray();
    1461         806 :     if (!oChunks.IsValid())
    1462             :     {
    1463           3 :         CPLError(CE_Failure, CPLE_AppDefined, "%s missing or not an array",
    1464             :                  chunksKey);
    1465           3 :         return nullptr;
    1466             :     }
    1467             : 
    1468         803 :     if (oShape.Size() != oChunks.Size())
    1469             :     {
    1470           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    1471             :                  "shape and chunks arrays are of different size");
    1472           2 :         return nullptr;
    1473             :     }
    1474             : 
    1475        1602 :     CPLJSONObject oAttributes(oAttributesIn);
    1476         801 :     if (!bLoadedFromZMetadata)
    1477             :     {
    1478         960 :         CPLJSONDocument oDoc;
    1479             :         const std::string osZattrsFilename(CPLFormFilenameSafe(
    1480         480 :             CPLGetDirnameSafe(osZarrayFilename.c_str()).c_str(), ".zattrs",
    1481         960 :             nullptr));
    1482         960 :         CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    1483         480 :         if (oDoc.Load(osZattrsFilename))
    1484             :         {
    1485         165 :             oAttributes = oDoc.GetRoot();
    1486             :         }
    1487             :     }
    1488             : 
    1489             :     // Deep-clone of oAttributes
    1490             :     {
    1491         801 :         CPLJSONDocument oTmpDoc;
    1492         801 :         oTmpDoc.SetRoot(oAttributes);
    1493         801 :         CPL_IGNORE_RET_VAL(oTmpDoc.LoadMemory(oTmpDoc.SaveAsString()));
    1494         801 :         oAttributes = oTmpDoc.GetRoot();
    1495             :     }
    1496             : 
    1497        1602 :     std::vector<std::shared_ptr<GDALDimension>> aoDims;
    1498        2073 :     for (int i = 0; i < oShape.Size(); ++i)
    1499             :     {
    1500        1273 :         const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
    1501        1273 :         if (nSize == 0)
    1502             :         {
    1503           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
    1504           1 :             return nullptr;
    1505             :         }
    1506        1272 :         aoDims.emplace_back(std::make_shared<ZarrDimension>(
    1507        1272 :             m_poSharedResource,
    1508        2544 :             std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
    1509        2544 :             std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
    1510        1272 :             nSize));
    1511             :     }
    1512             : 
    1513             :     // XArray extension
    1514        2400 :     const auto arrayDimensionsObj = oAttributes["_ARRAY_DIMENSIONS"];
    1515             : 
    1516             :     const auto FindDimension =
    1517         568 :         [this, &aoDims, bLoadedFromZMetadata, &osArrayName,
    1518             :          &oAttributes](const std::string &osDimName,
    1519        6506 :                        std::shared_ptr<GDALDimension> &poDim, int i)
    1520             :     {
    1521         568 :         auto oIter = m_oMapDimensions.find(osDimName);
    1522         568 :         if (oIter != m_oMapDimensions.end())
    1523             :         {
    1524         276 :             if (m_bDimSizeInUpdate ||
    1525         137 :                 oIter->second->GetSize() == poDim->GetSize())
    1526             :             {
    1527         139 :                 poDim = oIter->second;
    1528         139 :                 return true;
    1529             :             }
    1530             :             else
    1531             :             {
    1532           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1533             :                          "Size of _ARRAY_DIMENSIONS[%d] different "
    1534             :                          "from the one of shape",
    1535             :                          i);
    1536           0 :                 return false;
    1537             :             }
    1538             :         }
    1539             : 
    1540             :         // Try to load the indexing variable.
    1541             : 
    1542             :         // If loading from zmetadata, we should have normally
    1543             :         // already loaded the dimension variables, unless they
    1544             :         // are in a upper level.
    1545         627 :         if (bLoadedFromZMetadata && osArrayName != osDimName &&
    1546         627 :             m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
    1547             :         {
    1548         198 :             auto poParent = m_poParent.lock();
    1549         214 :             while (poParent != nullptr)
    1550             :             {
    1551          22 :                 oIter = poParent->m_oMapDimensions.find(osDimName);
    1552          28 :                 if (oIter != poParent->m_oMapDimensions.end() &&
    1553           6 :                     oIter->second->GetSize() == poDim->GetSize())
    1554             :                 {
    1555           6 :                     poDim = oIter->second;
    1556           6 :                     return true;
    1557             :                 }
    1558          16 :                 poParent = poParent->m_poParent.lock();
    1559             :             }
    1560             :         }
    1561             : 
    1562             :         // Not loading from zmetadata, and not in m_oMapMDArrays,
    1563             :         // then stat() the indexing variable.
    1564         320 :         else if (!bLoadedFromZMetadata && osArrayName != osDimName &&
    1565         320 :                  m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
    1566             :         {
    1567          89 :             std::string osDirName = m_osDirectoryName;
    1568             :             while (true)
    1569             :             {
    1570         247 :                 if (CPLHasPathTraversal(osDimName.c_str()))
    1571             :                 {
    1572           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1573             :                              "Path traversal detected in %s",
    1574             :                              osDimName.c_str());
    1575           0 :                     return false;
    1576             :                 }
    1577             :                 const std::string osArrayFilenameDim = CPLFormFilenameSafe(
    1578         247 :                     CPLFormFilenameSafe(osDirName.c_str(), osDimName.c_str(),
    1579             :                                         nullptr)
    1580             :                         .c_str(),
    1581         247 :                     ".zarray", nullptr);
    1582             :                 VSIStatBufL sStat;
    1583         247 :                 if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
    1584             :                 {
    1585         102 :                     CPLJSONDocument oDoc;
    1586          51 :                     if (oDoc.Load(osArrayFilenameDim))
    1587             :                     {
    1588          51 :                         LoadArray(osDimName, osArrayFilenameDim, oDoc.GetRoot(),
    1589         102 :                                   false, CPLJSONObject());
    1590             :                     }
    1591             :                 }
    1592             :                 else
    1593             :                 {
    1594         196 :                     if ((cpl::starts_with(osDirName, JSON_REF_FS_PREFIX) ||
    1595         199 :                          cpl::starts_with(osDirName, PARQUET_REF_FS_PREFIX)) &&
    1596           3 :                         osDirName.back() == '}')
    1597             :                     {
    1598           3 :                         break;
    1599             :                     }
    1600             : 
    1601             :                     // Recurse to upper level for datasets such as
    1602             :                     // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
    1603             :                     std::string osDirNameNew =
    1604         193 :                         CPLGetPathSafe(osDirName.c_str());
    1605         193 :                     if (!osDirNameNew.empty() && osDirNameNew != osDirName)
    1606             :                     {
    1607         158 :                         osDirName = std::move(osDirNameNew);
    1608         158 :                         continue;
    1609             :                     }
    1610             :                 }
    1611          86 :                 break;
    1612         158 :             }
    1613             :         }
    1614             : 
    1615         423 :         oIter = m_oMapDimensions.find(osDimName);
    1616         440 :         if (oIter != m_oMapDimensions.end() &&
    1617          17 :             oIter->second->GetSize() == poDim->GetSize())
    1618             :         {
    1619          17 :             poDim = oIter->second;
    1620          17 :             return true;
    1621             :         }
    1622             : 
    1623         812 :         std::string osType;
    1624         812 :         std::string osDirection;
    1625         406 :         if (aoDims.size() == 1 && osArrayName == osDimName)
    1626             :         {
    1627         142 :             ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
    1628             :                                                  osDirection);
    1629             :         }
    1630             : 
    1631             :         auto poDimLocal = std::make_shared<ZarrDimension>(
    1632         406 :             m_poSharedResource,
    1633         812 :             std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
    1634         812 :             GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
    1635         406 :         poDimLocal->SetXArrayDimension();
    1636         406 :         m_oMapDimensions[osDimName] = poDimLocal;
    1637         406 :         poDim = poDimLocal;
    1638         406 :         return true;
    1639         800 :     };
    1640             : 
    1641         800 :     if (arrayDimensionsObj.GetType() == CPLJSONObject::Type::Array)
    1642             :     {
    1643         762 :         const auto arrayDims = arrayDimensionsObj.ToArray();
    1644         381 :         if (arrayDims.Size() == oShape.Size())
    1645             :         {
    1646         381 :             bool ok = true;
    1647         949 :             for (int i = 0; i < oShape.Size(); ++i)
    1648             :             {
    1649         568 :                 if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
    1650             :                 {
    1651        1136 :                     const auto osDimName = arrayDims[i].ToString();
    1652         568 :                     ok &= FindDimension(osDimName, aoDims[i], i);
    1653             :                 }
    1654             :             }
    1655         381 :             if (ok)
    1656             :             {
    1657         381 :                 oAttributes.Delete("_ARRAY_DIMENSIONS");
    1658             :             }
    1659             :         }
    1660             :         else
    1661             :         {
    1662           0 :             CPLError(
    1663             :                 CE_Warning, CPLE_AppDefined,
    1664             :                 "Size of _ARRAY_DIMENSIONS different from the one of shape");
    1665             :         }
    1666             :     }
    1667             : 
    1668             :     // _NCZARR_ARRAY extension
    1669        2400 :     const auto nczarrArrayDimrefs = oRoot["_NCZARR_ARRAY"]["dimrefs"].ToArray();
    1670         800 :     if (nczarrArrayDimrefs.IsValid())
    1671             :     {
    1672          42 :         const auto arrayDims = nczarrArrayDimrefs.ToArray();
    1673          21 :         if (arrayDims.Size() == oShape.Size())
    1674             :         {
    1675             :             auto poRG =
    1676          42 :                 std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
    1677          21 :             CPLAssert(poRG != nullptr);
    1678             :             while (true)
    1679             :             {
    1680          48 :                 auto poNewRG = poRG->m_poParent.lock();
    1681          48 :                 if (poNewRG == nullptr)
    1682          21 :                     break;
    1683          27 :                 poRG = std::move(poNewRG);
    1684          27 :             }
    1685             : 
    1686          49 :             for (int i = 0; i < oShape.Size(); ++i)
    1687             :             {
    1688          28 :                 if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
    1689             :                 {
    1690          84 :                     const auto osDimFullpath = arrayDims[i].ToString();
    1691             :                     const std::string osArrayFullname =
    1692          56 :                         (GetFullName() != "/" ? GetFullName() : std::string()) +
    1693          56 :                         '/' + osArrayName;
    1694          48 :                     if (aoDims.size() == 1 &&
    1695          20 :                         (osDimFullpath == osArrayFullname ||
    1696          34 :                          osDimFullpath == "/" + osArrayFullname))
    1697             :                     {
    1698             :                         // If this is an indexing variable, then fetch the
    1699             :                         // dimension type and direction, and patch the dimension
    1700          28 :                         std::string osType;
    1701          28 :                         std::string osDirection;
    1702          14 :                         ZarrArray::GetDimensionTypeDirection(
    1703             :                             oAttributes, osType, osDirection);
    1704             : 
    1705             :                         auto poDimLocal = std::make_shared<ZarrDimension>(
    1706          14 :                             m_poSharedResource,
    1707          28 :                             std::dynamic_pointer_cast<ZarrGroupBase>(
    1708          14 :                                 m_pSelf.lock()),
    1709          14 :                             GetFullName(), osArrayName, osType, osDirection,
    1710          28 :                             aoDims[i]->GetSize());
    1711          14 :                         aoDims[i] = poDimLocal;
    1712             : 
    1713          14 :                         m_oMapDimensions[osArrayName] = std::move(poDimLocal);
    1714             :                     }
    1715          14 :                     else if (auto poDim =
    1716          28 :                                  poRG->OpenDimensionFromFullname(osDimFullpath))
    1717             :                     {
    1718          13 :                         if (poDim->GetSize() != aoDims[i]->GetSize())
    1719             :                         {
    1720           1 :                             CPLError(CE_Failure, CPLE_AppDefined,
    1721             :                                      "Inconsistency in size between NCZarr "
    1722             :                                      "dimension %s and regular dimension",
    1723             :                                      osDimFullpath.c_str());
    1724             :                         }
    1725             :                         else
    1726             :                         {
    1727          12 :                             aoDims[i] = std::move(poDim);
    1728             :                         }
    1729             :                     }
    1730             :                     else
    1731             :                     {
    1732           1 :                         CPLError(CE_Failure, CPLE_AppDefined,
    1733             :                                  "Cannot find NCZarr dimension %s",
    1734             :                                  osDimFullpath.c_str());
    1735             :                     }
    1736             :                 }
    1737             :             }
    1738             :         }
    1739             :         else
    1740             :         {
    1741           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1742             :                      "Size of _NCZARR_ARRAY.dimrefs different from the one of "
    1743             :                      "shape");
    1744             :         }
    1745             :     }
    1746             : 
    1747         800 :     constexpr const char *dtypeKey = "dtype";
    1748        2400 :     auto oDtype = oRoot[dtypeKey];
    1749         800 :     if (!oDtype.IsValid())
    1750             :     {
    1751           1 :         CPLError(CE_Failure, CPLE_NotSupported, "%s missing", dtypeKey);
    1752           1 :         return nullptr;
    1753             :     }
    1754        1598 :     std::vector<DtypeElt> aoDtypeElts;
    1755        1598 :     const auto oType = ParseDtype(oDtype, aoDtypeElts);
    1756        1579 :     if (oType.GetClass() == GEDTC_NUMERIC &&
    1757         780 :         oType.GetNumericDataType() == GDT_Unknown)
    1758           8 :         return nullptr;
    1759         791 :     size_t iCurElt = 0;
    1760         791 :     SetGDALOffset(oType, 0, aoDtypeElts, iCurElt);
    1761             : 
    1762        1582 :     std::vector<GUInt64> anBlockSize;
    1763         791 :     if (!ZarrArray::ParseChunkSize(oChunks, oType, anBlockSize))
    1764           2 :         return nullptr;
    1765             : 
    1766        2367 :     std::string osDimSeparator = oRoot["dimension_separator"].ToString();
    1767         789 :     if (osDimSeparator.empty())
    1768         778 :         osDimSeparator = ".";
    1769             : 
    1770        1578 :     std::vector<GByte> abyNoData;
    1771             : 
    1772             :     struct NoDataFreer
    1773             :     {
    1774             :         std::vector<GByte> &m_abyNodata;
    1775             :         const GDALExtendedDataType &m_oType;
    1776             : 
    1777         789 :         NoDataFreer(std::vector<GByte> &abyNoDataIn,
    1778             :                     const GDALExtendedDataType &oTypeIn)
    1779         789 :             : m_abyNodata(abyNoDataIn), m_oType(oTypeIn)
    1780             :         {
    1781         789 :         }
    1782             : 
    1783         789 :         ~NoDataFreer()
    1784         789 :         {
    1785         789 :             if (!m_abyNodata.empty())
    1786         154 :                 m_oType.FreeDynamicMemory(&m_abyNodata[0]);
    1787         789 :         }
    1788             :     };
    1789             : 
    1790        1578 :     NoDataFreer NoDataFreer(abyNoData, oType);
    1791             : 
    1792        2367 :     auto oFillValue = oRoot["fill_value"];
    1793         789 :     auto eFillValueType = oFillValue.GetType();
    1794             : 
    1795             :     // Normally arrays are not supported, but that's what NCZarr 4.8.0 outputs
    1796         790 :     if (eFillValueType == CPLJSONObject::Type::Array &&
    1797         790 :         oFillValue.ToArray().Size() == 1)
    1798             :     {
    1799           0 :         oFillValue = oFillValue.ToArray()[0];
    1800           0 :         eFillValueType = oFillValue.GetType();
    1801             :     }
    1802             : 
    1803         789 :     if (!oFillValue.IsValid())
    1804             :     {
    1805             :         // fill_value is normally required but some implementations
    1806             :         // are lacking it: https://github.com/Unidata/netcdf-c/issues/2059
    1807           1 :         CPLError(CE_Warning, CPLE_AppDefined, "fill_value missing");
    1808             :     }
    1809         788 :     else if (eFillValueType == CPLJSONObject::Type::Null)
    1810             :     {
    1811             :         // Nothing to do
    1812             :     }
    1813         163 :     else if (eFillValueType == CPLJSONObject::Type::String)
    1814             :     {
    1815         128 :         const auto osFillValue = oFillValue.ToString();
    1816         115 :         if (oType.GetClass() == GEDTC_NUMERIC &&
    1817          51 :             CPLGetValueType(osFillValue.c_str()) != CPL_VALUE_STRING)
    1818             :         {
    1819          10 :             abyNoData.resize(oType.GetSize());
    1820             :             // Be tolerant with numeric values serialized as strings.
    1821          10 :             if (oType.GetNumericDataType() == GDT_Int64)
    1822             :             {
    1823             :                 const int64_t nVal = static_cast<int64_t>(
    1824           2 :                     std::strtoll(osFillValue.c_str(), nullptr, 10));
    1825           2 :                 GDALCopyWords(&nVal, GDT_Int64, 0, &abyNoData[0],
    1826             :                               oType.GetNumericDataType(), 0, 1);
    1827             :             }
    1828           8 :             else if (oType.GetNumericDataType() == GDT_UInt64)
    1829             :             {
    1830             :                 const uint64_t nVal = static_cast<uint64_t>(
    1831           2 :                     std::strtoull(osFillValue.c_str(), nullptr, 10));
    1832           2 :                 GDALCopyWords(&nVal, GDT_UInt64, 0, &abyNoData[0],
    1833             :                               oType.GetNumericDataType(), 0, 1);
    1834             :             }
    1835             :             else
    1836             :             {
    1837           6 :                 const double dfNoDataValue = CPLAtof(osFillValue.c_str());
    1838           6 :                 GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
    1839             :                               oType.GetNumericDataType(), 0, 1);
    1840             :             }
    1841             :         }
    1842          54 :         else if (oType.GetClass() == GEDTC_NUMERIC)
    1843             :         {
    1844             :             double dfNoDataValue;
    1845          41 :             if (osFillValue == "NaN")
    1846             :             {
    1847          14 :                 dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
    1848             :             }
    1849          27 :             else if (osFillValue == "Infinity")
    1850             :             {
    1851          13 :                 dfNoDataValue = std::numeric_limits<double>::infinity();
    1852             :             }
    1853          14 :             else if (osFillValue == "-Infinity")
    1854             :             {
    1855          13 :                 dfNoDataValue = -std::numeric_limits<double>::infinity();
    1856             :             }
    1857             :             else
    1858             :             {
    1859           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1860           2 :                 return nullptr;
    1861             :             }
    1862          40 :             if (oType.GetNumericDataType() == GDT_Float16)
    1863             :             {
    1864             :                 const GFloat16 hfNoDataValue =
    1865           0 :                     static_cast<GFloat16>(dfNoDataValue);
    1866           0 :                 abyNoData.resize(sizeof(hfNoDataValue));
    1867           0 :                 memcpy(&abyNoData[0], &hfNoDataValue, sizeof(hfNoDataValue));
    1868             :             }
    1869          40 :             if (oType.GetNumericDataType() == GDT_Float32)
    1870             :             {
    1871          18 :                 const float fNoDataValue = static_cast<float>(dfNoDataValue);
    1872          18 :                 abyNoData.resize(sizeof(fNoDataValue));
    1873          18 :                 memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
    1874             :             }
    1875          22 :             else if (oType.GetNumericDataType() == GDT_Float64)
    1876             :             {
    1877          21 :                 abyNoData.resize(sizeof(dfNoDataValue));
    1878          21 :                 memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
    1879             :             }
    1880             :             else
    1881             :             {
    1882           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1883           1 :                 return nullptr;
    1884             :             }
    1885             :         }
    1886          13 :         else if (oType.GetClass() == GEDTC_STRING)
    1887             :         {
    1888             :             // zarr.open('unicode_be.zarr', mode = 'w', shape=(1,), dtype =
    1889             :             // '>U1', compressor = None) oddly generates "fill_value": "0"
    1890           8 :             if (osFillValue != "0")
    1891             :             {
    1892           3 :                 std::vector<GByte> abyNativeFillValue(osFillValue.size() + 1);
    1893           3 :                 memcpy(&abyNativeFillValue[0], osFillValue.data(),
    1894             :                        osFillValue.size());
    1895           3 :                 int nBytes = CPLBase64DecodeInPlace(&abyNativeFillValue[0]);
    1896           3 :                 abyNativeFillValue.resize(nBytes + 1);
    1897           3 :                 abyNativeFillValue[nBytes] = 0;
    1898           3 :                 abyNoData.resize(oType.GetSize());
    1899           3 :                 char *pDstStr = CPLStrdup(
    1900           3 :                     reinterpret_cast<const char *>(&abyNativeFillValue[0]));
    1901           3 :                 char **pDstPtr = reinterpret_cast<char **>(&abyNoData[0]);
    1902           3 :                 memcpy(pDstPtr, &pDstStr, sizeof(pDstStr));
    1903             :             }
    1904             :         }
    1905             :         else
    1906             :         {
    1907           5 :             std::vector<GByte> abyNativeFillValue(osFillValue.size() + 1);
    1908           5 :             memcpy(&abyNativeFillValue[0], osFillValue.data(),
    1909             :                    osFillValue.size());
    1910           5 :             int nBytes = CPLBase64DecodeInPlace(&abyNativeFillValue[0]);
    1911           5 :             abyNativeFillValue.resize(nBytes);
    1912           5 :             if (abyNativeFillValue.size() !=
    1913           5 :                 aoDtypeElts.back().nativeOffset + aoDtypeElts.back().nativeSize)
    1914             :             {
    1915           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1916           0 :                 return nullptr;
    1917             :             }
    1918           5 :             abyNoData.resize(oType.GetSize());
    1919           5 :             ZarrArray::DecodeSourceElt(aoDtypeElts, abyNativeFillValue.data(),
    1920           5 :                                        &abyNoData[0]);
    1921             :         }
    1922             :     }
    1923          99 :     else if (eFillValueType == CPLJSONObject::Type::Boolean ||
    1924          27 :              eFillValueType == CPLJSONObject::Type::Integer ||
    1925          15 :              eFillValueType == CPLJSONObject::Type::Long ||
    1926             :              eFillValueType == CPLJSONObject::Type::Double)
    1927             :     {
    1928          98 :         if (oType.GetClass() == GEDTC_NUMERIC)
    1929             :         {
    1930          97 :             const double dfNoDataValue = oFillValue.ToDouble();
    1931          97 :             if (oType.GetNumericDataType() == GDT_Int64)
    1932             :             {
    1933             :                 const int64_t nNoDataValue =
    1934           2 :                     static_cast<int64_t>(oFillValue.ToLong());
    1935           2 :                 abyNoData.resize(oType.GetSize());
    1936           2 :                 GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
    1937             :                               oType.GetNumericDataType(), 0, 1);
    1938             :             }
    1939         100 :             else if (oType.GetNumericDataType() == GDT_UInt64 &&
    1940             :                      /* we can't really deal with nodata value between */
    1941             :                      /* int64::max and uint64::max due to json-c limitations */
    1942           5 :                      dfNoDataValue >= 0)
    1943             :             {
    1944             :                 const int64_t nNoDataValue =
    1945           5 :                     static_cast<int64_t>(oFillValue.ToLong());
    1946           5 :                 abyNoData.resize(oType.GetSize());
    1947           5 :                 GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
    1948             :                               oType.GetNumericDataType(), 0, 1);
    1949             :             }
    1950             :             else
    1951             :             {
    1952          90 :                 abyNoData.resize(oType.GetSize());
    1953          90 :                 GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
    1954             :                               oType.GetNumericDataType(), 0, 1);
    1955             :             }
    1956             :         }
    1957             :         else
    1958             :         {
    1959           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1960           1 :             return nullptr;
    1961          97 :         }
    1962             :     }
    1963             :     else
    1964             :     {
    1965           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1966           1 :         return nullptr;
    1967             :     }
    1968             : 
    1969         785 :     const CPLCompressor *psCompressor = nullptr;
    1970         785 :     const CPLCompressor *psDecompressor = nullptr;
    1971        2355 :     const auto oCompressor = oRoot["compressor"];
    1972        1570 :     std::string osDecompressorId("NONE");
    1973             : 
    1974         785 :     if (!oCompressor.IsValid())
    1975             :     {
    1976           1 :         CPLError(CE_Failure, CPLE_AppDefined, "compressor missing");
    1977           1 :         return nullptr;
    1978             :     }
    1979         784 :     if (oCompressor.GetType() == CPLJSONObject::Type::Null)
    1980             :     {
    1981             :         // nothing to do
    1982             :     }
    1983          38 :     else if (oCompressor.GetType() == CPLJSONObject::Type::Object)
    1984             :     {
    1985          37 :         osDecompressorId = oCompressor["id"].ToString();
    1986          37 :         if (osDecompressorId.empty())
    1987             :         {
    1988           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Missing compressor id");
    1989           1 :             return nullptr;
    1990             :         }
    1991          36 :         if (osDecompressorId == "imagecodecs_tiff")
    1992             :         {
    1993           5 :             psDecompressor = ZarrGetTIFFDecompressor();
    1994             :         }
    1995             :         else
    1996             :         {
    1997          31 :             psCompressor = CPLGetCompressor(osDecompressorId.c_str());
    1998          31 :             psDecompressor = CPLGetDecompressor(osDecompressorId.c_str());
    1999          31 :             if (psCompressor == nullptr || psDecompressor == nullptr)
    2000             :             {
    2001           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2002             :                          "Decompressor %s not handled",
    2003             :                          osDecompressorId.c_str());
    2004           1 :                 return nullptr;
    2005             :             }
    2006             :         }
    2007             :     }
    2008             :     else
    2009             :     {
    2010           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid compressor");
    2011           1 :         return nullptr;
    2012             :     }
    2013             : 
    2014        1562 :     CPLJSONArray oFiltersArray;
    2015        2343 :     const auto oFilters = oRoot["filters"];
    2016         781 :     if (!oFilters.IsValid())
    2017             :     {
    2018           1 :         CPLError(CE_Failure, CPLE_AppDefined, "filters missing");
    2019           1 :         return nullptr;
    2020             :     }
    2021         780 :     if (oFilters.GetType() == CPLJSONObject::Type::Null)
    2022             :     {
    2023             :     }
    2024          49 :     else if (oFilters.GetType() == CPLJSONObject::Type::Array)
    2025             :     {
    2026          47 :         oFiltersArray = oFilters.ToArray();
    2027          65 :         for (const auto &oFilter : oFiltersArray)
    2028             :         {
    2029          38 :             const auto osFilterId = oFilter["id"].ToString();
    2030          19 :             if (osFilterId.empty())
    2031             :             {
    2032           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Missing filter id");
    2033           1 :                 return nullptr;
    2034             :             }
    2035          18 :             if (!EQUAL(osFilterId.c_str(), "shuffle") &&
    2036          15 :                 !EQUAL(osFilterId.c_str(), "quantize") &&
    2037          41 :                 !EQUAL(osFilterId.c_str(), "fixedscaleoffset") &&
    2038           8 :                 !EQUAL(osFilterId.c_str(), "bitround"))
    2039             :             {
    2040             :                 const auto psFilterCompressor =
    2041           6 :                     CPLGetCompressor(osFilterId.c_str());
    2042             :                 const auto psFilterDecompressor =
    2043           6 :                     CPLGetDecompressor(osFilterId.c_str());
    2044           6 :                 if (psFilterCompressor == nullptr ||
    2045             :                     psFilterDecompressor == nullptr)
    2046             :                 {
    2047           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    2048             :                              "Filter %s not handled", osFilterId.c_str());
    2049           0 :                     return nullptr;
    2050             :                 }
    2051             :             }
    2052             :         }
    2053             :     }
    2054             :     else
    2055             :     {
    2056           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid filters");
    2057           2 :         return nullptr;
    2058             :     }
    2059             : 
    2060             :     auto poArray =
    2061           0 :         ZarrV2Array::Create(m_poSharedResource, Self(), osArrayName, aoDims,
    2062        1554 :                             oType, aoDtypeElts, anBlockSize, bFortranOrder);
    2063         777 :     if (!poArray)
    2064           1 :         return nullptr;
    2065         776 :     poArray->SetCompressorJson(oCompressor);
    2066         776 :     poArray->SetUpdatable(m_bUpdatable);  // must be set before SetAttributes()
    2067         776 :     poArray->SetFilename(osZarrayFilename);
    2068         776 :     poArray->SetDimSeparator(osDimSeparator);
    2069         776 :     poArray->SetCompressorDecompressor(osDecompressorId, psCompressor,
    2070             :                                        psDecompressor);
    2071         776 :     poArray->SetFilters(oFiltersArray);
    2072         776 :     if (!abyNoData.empty())
    2073             :     {
    2074         154 :         poArray->RegisterNoDataValue(abyNoData.data());
    2075             :     }
    2076             : 
    2077        2328 :     const auto gridMapping = oAttributes["grid_mapping"];
    2078         776 :     if (gridMapping.GetType() == CPLJSONObject::Type::String)
    2079             :     {
    2080           2 :         const std::string gridMappingName = gridMapping.ToString();
    2081           1 :         if (m_oMapMDArrays.find(gridMappingName) == m_oMapMDArrays.end())
    2082             :         {
    2083           1 :             if (CPLHasPathTraversal(gridMappingName.c_str()))
    2084             :             {
    2085           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2086             :                          "Path traversal detected in %s",
    2087             :                          gridMappingName.c_str());
    2088           0 :                 return nullptr;
    2089             :             }
    2090             :             const std::string osArrayFilenameDim = CPLFormFilenameSafe(
    2091           1 :                 CPLFormFilenameSafe(m_osDirectoryName.c_str(),
    2092             :                                     gridMappingName.c_str(), nullptr)
    2093             :                     .c_str(),
    2094           2 :                 ".zarray", nullptr);
    2095             :             VSIStatBufL sStat;
    2096           1 :             if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
    2097             :             {
    2098           2 :                 CPLJSONDocument oDoc;
    2099           1 :                 if (oDoc.Load(osArrayFilenameDim))
    2100             :                 {
    2101           1 :                     LoadArray(gridMappingName, osArrayFilenameDim,
    2102           2 :                               oDoc.GetRoot(), false, CPLJSONObject());
    2103             :                 }
    2104             :             }
    2105             :         }
    2106             :     }
    2107             : 
    2108         776 :     poArray->SetAttributes(Self(), oAttributes);
    2109         776 :     poArray->SetDtype(oDtype);
    2110         776 :     RegisterArray(poArray);
    2111             : 
    2112             :     // If this is an indexing variable, attach it to the dimension.
    2113         776 :     if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
    2114             :     {
    2115         156 :         auto oIter = m_oMapDimensions.find(poArray->GetName());
    2116         156 :         if (oIter != m_oMapDimensions.end())
    2117             :         {
    2118         156 :             oIter->second->SetIndexingVariable(poArray);
    2119             :         }
    2120             :     }
    2121             : 
    2122         776 :     if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
    2123             :             "CACHE_TILE_PRESENCE", "NO")))
    2124             :     {
    2125           4 :         poArray->BlockCachePresence();
    2126             :     }
    2127             : 
    2128         776 :     return poArray;
    2129             : }
    2130             : 
    2131             : /************************************************************************/
    2132             : /*                   ZarrV2Array::SetCompressorJson()                   */
    2133             : /************************************************************************/
    2134             : 
    2135         793 : void ZarrV2Array::SetCompressorJson(const CPLJSONObject &oCompressor)
    2136             : {
    2137         793 :     m_oCompressorJSon = oCompressor;
    2138         793 :     if (oCompressor.GetType() != CPLJSONObject::Type::Null)
    2139             :         m_aosStructuralInfo.SetNameValue("COMPRESSOR",
    2140          52 :                                          oCompressor.ToString().c_str());
    2141         793 : }
    2142             : 
    2143             : /************************************************************************/
    2144             : /*                      ZarrV2Array::SetFilters()                       */
    2145             : /************************************************************************/
    2146             : 
    2147        1097 : void ZarrV2Array::SetFilters(const CPLJSONArray &oFiltersArray)
    2148             : {
    2149        1097 :     m_oFiltersArray = oFiltersArray;
    2150        1097 :     if (oFiltersArray.Size() > 0)
    2151             :         m_aosStructuralInfo.SetNameValue("FILTERS",
    2152          17 :                                          oFiltersArray.ToString().c_str());
    2153        1097 : }
    2154             : 
    2155             : /************************************************************************/
    2156             : /*                  ZarrV2Array::GetRawBlockInfoInfo()                  */
    2157             : /************************************************************************/
    2158             : 
    2159           6 : CPLStringList ZarrV2Array::GetRawBlockInfoInfo() const
    2160             : {
    2161           6 :     CPLStringList aosInfo(m_aosStructuralInfo);
    2162          12 :     if (!m_aoDtypeElts.empty() && m_aoDtypeElts[0].nativeSize > 1 &&
    2163          15 :         m_aoDtypeElts[0].nativeType != DtypeElt::NativeType::STRING_ASCII &&
    2164           3 :         m_aoDtypeElts[0].nativeType != DtypeElt::NativeType::STRING_UNICODE)
    2165             :     {
    2166           3 :         if (m_aoDtypeElts[0].needByteSwapping ^ CPL_IS_LSB)
    2167           2 :             aosInfo.SetNameValue("ENDIANNESS", "LITTLE");
    2168             :         else
    2169           1 :             aosInfo.SetNameValue("ENDIANNESS", "BIG");
    2170             :     }
    2171           6 :     if (m_bFortranOrder)
    2172             :     {
    2173           1 :         const int nDims = static_cast<int>(m_aoDims.size());
    2174           1 :         if (nDims > 1)
    2175             :         {
    2176           2 :             std::string osOrder("[");
    2177           3 :             for (int i = 0; i < nDims; ++i)
    2178             :             {
    2179           2 :                 if (i > 0)
    2180           1 :                     osOrder += ',';
    2181           2 :                 osOrder += std::to_string(nDims - 1 - i);
    2182             :             }
    2183           1 :             osOrder += ']';
    2184           1 :             aosInfo.SetNameValue("TRANSPOSE_ORDER", osOrder.c_str());
    2185             :         }
    2186             :     }
    2187           6 :     return aosInfo;
    2188             : }

Generated by: LCOV version 1.14