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

Generated by: LCOV version 1.14