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

Generated by: LCOV version 1.14