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

Generated by: LCOV version 1.14