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

Generated by: LCOV version 1.14