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

Generated by: LCOV version 1.14