LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v2_array.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 910 984 92.5 %
Date: 2025-06-19 12:30:01 Functions: 28 30 93.3 %

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

Generated by: LCOV version 1.14