LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v2_array.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 871 945 92.2 %
Date: 2024-11-21 22:18:42 Functions: 26 28 92.9 %

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

Generated by: LCOV version 1.14