LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v2_array.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 875 950 92.1 %
Date: 2025-01-18 12:42:00 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         898 : 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         898 :     const std::vector<GUInt64> &anBlockSize, bool bFortranOrder)
      36             :     : GDALAbstractMDArray(osParentName, osName),
      37             :       ZarrArray(poSharedResource, osParentName, osName, aoDims, oType,
      38             :                 aoDtypeElts, anBlockSize),
      39         898 :       m_bFortranOrder(bFortranOrder)
      40             : {
      41         898 :     m_oCompressorJSon.Deinit();
      42         898 : }
      43             : 
      44             : /************************************************************************/
      45             : /*                         ZarrV2Array::Create()                        */
      46             : /************************************************************************/
      47             : 
      48             : std::shared_ptr<ZarrV2Array>
      49         898 : 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        1796 :                         aoDtypeElts, anBlockSize, bFortranOrder));
      59         898 :     if (arr->m_nTotalTileCount == 0)
      60           1 :         return nullptr;
      61         897 :     arr->SetSelf(arr);
      62             : 
      63         897 :     return arr;
      64             : }
      65             : 
      66             : /************************************************************************/
      67             : /*                             ~ZarrV2Array()                           */
      68             : /************************************************************************/
      69             : 
      70        1796 : ZarrV2Array::~ZarrV2Array()
      71             : {
      72         898 :     ZarrV2Array::Flush();
      73        1796 : }
      74             : 
      75             : /************************************************************************/
      76             : /*                                Flush()                               */
      77             : /************************************************************************/
      78             : 
      79        2092 : void ZarrV2Array::Flush()
      80             : {
      81        2092 :     if (!m_bValid)
      82           9 :         return;
      83             : 
      84        2083 :     ZarrV2Array::FlushDirtyTile();
      85             : 
      86        2083 :     if (m_bDefinitionModified)
      87             :     {
      88         321 :         Serialize();
      89         321 :         m_bDefinitionModified = false;
      90             :     }
      91             : 
      92        4166 :     CPLJSONArray j_ARRAY_DIMENSIONS;
      93        2083 :     bool bDimensionsModified = false;
      94        2083 :     if (!m_aoDims.empty())
      95             :     {
      96        4184 :         for (const auto &poDim : m_aoDims)
      97             :         {
      98             :             const auto poZarrDim =
      99        2715 :                 dynamic_cast<const ZarrDimension *>(poDim.get());
     100        2715 :             if (poZarrDim && poZarrDim->IsXArrayDimension())
     101             :             {
     102        2258 :                 if (poZarrDim->IsModified())
     103           8 :                     bDimensionsModified = true;
     104        2258 :                 j_ARRAY_DIMENSIONS.Add(poDim->GetName());
     105             :             }
     106             :             else
     107             :             {
     108         457 :                 j_ARRAY_DIMENSIONS = CPLJSONArray();
     109         457 :                 break;
     110             :             }
     111             :         }
     112             :     }
     113             : 
     114        4114 :     if (m_oAttrGroup.IsModified() || bDimensionsModified ||
     115        2023 :         (m_bNew && j_ARRAY_DIMENSIONS.Size() != 0) || m_bUnitModified ||
     116        4114 :         m_bOffsetModified || m_bScaleModified || m_bSRSModified)
     117             :     {
     118         367 :         m_bNew = false;
     119             : 
     120         734 :         auto oAttrs = SerializeSpecialAttributes();
     121             : 
     122         367 :         if (j_ARRAY_DIMENSIONS.Size() != 0)
     123             :         {
     124         351 :             oAttrs.Delete("_ARRAY_DIMENSIONS");
     125         351 :             oAttrs.Add("_ARRAY_DIMENSIONS", j_ARRAY_DIMENSIONS);
     126             :         }
     127             : 
     128         734 :         CPLJSONDocument oDoc;
     129         367 :         oDoc.SetRoot(oAttrs);
     130             :         const std::string osAttrFilename =
     131         367 :             CPLFormFilenameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
     132         734 :                                 ".zattrs", nullptr);
     133         367 :         oDoc.Save(osAttrFilename);
     134         367 :         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         321 : void ZarrV2Array::Serialize()
     157             : {
     158         642 :     CPLJSONDocument oDoc;
     159         642 :     CPLJSONObject oRoot = oDoc.GetRoot();
     160             : 
     161         642 :     CPLJSONArray oChunks;
     162         784 :     for (const auto nBlockSize : m_anBlockSize)
     163             :     {
     164         463 :         oChunks.Add(static_cast<GInt64>(nBlockSize));
     165             :     }
     166         321 :     oRoot.Add("chunks", oChunks);
     167             : 
     168         321 :     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         293 :         oRoot.AddNull("compressor");
     177             :     }
     178             : 
     179         321 :     if (m_dtype.GetType() == CPLJSONObject::Type::Object)
     180         308 :         oRoot.Add("dtype", m_dtype["dummy"]);
     181             :     else
     182          13 :         oRoot.Add("dtype", m_dtype);
     183             : 
     184         321 :     if (m_pabyNoData == nullptr)
     185             :     {
     186         311 :         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         321 :     if (m_oFiltersArray.Size() == 0)
     236         320 :         oRoot.AddNull("filters");
     237             :     else
     238           1 :         oRoot.Add("filters", m_oFiltersArray);
     239             : 
     240         321 :     oRoot.Add("order", m_bFortranOrder ? "F" : "C");
     241             : 
     242         642 :     CPLJSONArray oShape;
     243         784 :     for (const auto &poDim : m_aoDims)
     244             :     {
     245         463 :         oShape.Add(static_cast<GInt64>(poDim->GetSize()));
     246             :     }
     247         321 :     oRoot.Add("shape", oShape);
     248             : 
     249         321 :     oRoot.Add("zarr_format", 2);
     250             : 
     251         321 :     if (m_osDimSeparator != ".")
     252             :     {
     253          10 :         oRoot.Add("dimension_separator", m_osDimSeparator);
     254             :     }
     255             : 
     256         321 :     oDoc.Save(m_osFilename);
     257             : 
     258         321 :     m_poSharedResource->SetZMetadataItem(m_osFilename, oRoot);
     259         321 : }
     260             : 
     261             : /************************************************************************/
     262             : /*                  ZarrV2Array::NeedDecodedBuffer()                    */
     263             : /************************************************************************/
     264             : 
     265       11588 : bool ZarrV2Array::NeedDecodedBuffer() const
     266             : {
     267             :     const size_t nSourceSize =
     268       11588 :         m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
     269       11579 :     if (m_oType.GetClass() == GEDTC_COMPOUND &&
     270           6 :         nSourceSize != m_oType.GetSize())
     271             :     {
     272           4 :         return true;
     273             :     }
     274       11554 :     else if (m_oType.GetClass() != GEDTC_STRING)
     275             :     {
     276       22988 :         for (const auto &elt : m_aoDtypeElts)
     277             :         {
     278       11546 :             if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative ||
     279       11461 :                 elt.nativeType == DtypeElt::NativeType::STRING_ASCII ||
     280       11458 :                 elt.nativeType == DtypeElt::NativeType::STRING_UNICODE)
     281             :             {
     282          96 :                 return true;
     283             :             }
     284             :         }
     285             :     }
     286       11477 :     return false;
     287             : }
     288             : 
     289             : /************************************************************************/
     290             : /*               ZarrV2Array::AllocateWorkingBuffers()                  */
     291             : /************************************************************************/
     292             : 
     293        1897 : bool ZarrV2Array::AllocateWorkingBuffers() const
     294             : {
     295        1897 :     if (m_bAllocateWorkingBuffersDone)
     296        1434 :         return m_bWorkingBuffersOK;
     297             : 
     298         463 :     m_bAllocateWorkingBuffersDone = true;
     299             : 
     300         463 :     size_t nSizeNeeded = m_nTileSize;
     301         463 :     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         462 :     if (NeedDecodedBuffer())
     311             :     {
     312          44 :         size_t nDecodedBufferSize = m_oType.GetSize();
     313         128 :         for (const auto &nBlockSize : m_anBlockSize)
     314             :         {
     315          84 :             if (nDecodedBufferSize > std::numeric_limits<size_t>::max() /
     316          84 :                                          static_cast<size_t>(nBlockSize))
     317             :             {
     318           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
     319           0 :                 return false;
     320             :             }
     321          84 :             nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
     322             :         }
     323          44 :         if (nSizeNeeded >
     324          44 :             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          44 :         nSizeNeeded += nDecodedBufferSize;
     330             :     }
     331             : 
     332             :     // Reserve a buffer for tile content
     333         464 :     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         920 :     m_bWorkingBuffersOK = AllocateWorkingBuffers(
     346         460 :         m_abyRawTileData, m_abyTmpRawTileData, m_abyDecodedTileData);
     347         460 :     return m_bWorkingBuffersOK;
     348             : }
     349             : 
     350       11130 : 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       11130 :         abyRawTileData.resize(m_nTileSize);
     366       11115 :         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       11095 :     if (NeedDecodedBuffer())
     376             :     {
     377          44 :         size_t nDecodedBufferSize = m_oType.GetSize();
     378         128 :         for (const auto &nBlockSize : m_anBlockSize)
     379             :         {
     380          84 :             nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
     381             :         }
     382             :         try
     383             :         {
     384          44 :             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       11123 :     return true;
     394             : #undef m_abyTmpRawTileData
     395             : #undef m_abyRawTileData
     396             : #undef m_abyDecodedTileData
     397             : }
     398             : 
     399             : /************************************************************************/
     400             : /*                      ZarrV2Array::LoadTileData()                     */
     401             : /************************************************************************/
     402             : 
     403        4834 : bool ZarrV2Array::LoadTileData(const uint64_t *tileIndices,
     404             :                                bool &bMissingTileOut) const
     405             : {
     406        9668 :     return LoadTileData(tileIndices,
     407             :                         false,  // use mutex
     408        4834 :                         m_psDecompressor, m_abyRawTileData, m_abyTmpRawTileData,
     409        4834 :                         m_abyDecodedTileData, bMissingTileOut);
     410             : }
     411             : 
     412       15497 : 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       15497 :     bMissingTileOut = false;
     429             : 
     430       30351 :     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       15474 :     osFilename = VSIFileManager::GetHandler(osFilename.c_str())
     435       15504 :                      ->GetStreamingFilename(osFilename);
     436             : 
     437             :     // First if we have a tile presence cache, check tile presence from it
     438       15478 :     if (bUseMutex)
     439       10629 :         m_oMutex.lock();
     440       30538 :     auto poTilePresenceArray = OpenTilePresenceCache(false);
     441       15506 :     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       15493 :     if (bUseMutex)
     467       10672 :         m_oMutex.unlock();
     468             : 
     469       15492 :     VSILFILE *fp = nullptr;
     470             :     // This is the number of files returned in a S3 directory listing operation
     471       15492 :     constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
     472       15492 :     const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
     473             :                                            nullptr};
     474       15512 :     if ((m_osDimSeparator == "/" && !m_anBlockSize.empty() &&
     475       30997 :          m_anBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
     476       15487 :         (m_osDimSeparator != "/" &&
     477       15467 :          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       10978 :                                            "YES", true);
     482       10829 :         fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
     483             :     }
     484             :     else
     485             :     {
     486        4512 :         fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
     487             :     }
     488       15360 :     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       12650 :     bMissingTileOut = false;
     498       12650 :     bool bRet = true;
     499       12650 :     size_t nRawDataSize = abyRawTileData.size();
     500       12391 :     if (psDecompressor == nullptr)
     501             :     {
     502        7056 :         nRawDataSize = VSIFReadL(&abyRawTileData[0], 1, nRawDataSize, fp);
     503             :     }
     504             :     else
     505             :     {
     506        5335 :         VSIFSeekL(fp, 0, SEEK_END);
     507        5408 :         const auto nSize = VSIFTellL(fp);
     508        5250 :         VSIFSeekL(fp, 0, SEEK_SET);
     509        5194 :         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       10502 :             ZarrByteVectorQuickResize abyCompressedData;
     518             :             try
     519             :             {
     520        5206 :                 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       15386 :             if (bRet &&
     531       10328 :                 (abyCompressedData.empty() ||
     532        5018 :                  VSIFReadL(&abyCompressedData[0], 1, abyCompressedData.size(),
     533        5101 :                            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        5067 :                 void *out_buffer = &abyRawTileData[0];
     543        5206 :                 if (!psDecompressor->pfnFunc(
     544        5150 :                         abyCompressedData.data(), abyCompressedData.size(),
     545             :                         &out_buffer, &nRawDataSize, nullptr,
     546        5105 :                         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       12270 :     VSIFCloseL(fp);
     557       12554 :     if (!bRet)
     558           0 :         return false;
     559             : 
     560       12602 :     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       11968 :     if (nRawDataSize != abyRawTileData.size())
     591             :     {
     592           0 :         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       12203 :     if (m_bFortranOrder && !m_aoDims.empty())
     599             :     {
     600          46 :         BlockTranspose(abyRawTileData, abyTmpRawTileData, true);
     601          46 :         std::swap(abyRawTileData, abyTmpRawTileData);
     602             :     }
     603             : 
     604       12203 :     if (!abyDecodedTileData.empty())
     605             :     {
     606             :         const size_t nSourceSize =
     607         327 :             m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
     608         327 :         const auto nDTSize = m_oType.GetSize();
     609         327 :         const size_t nValues = abyDecodedTileData.size() / nDTSize;
     610         327 :         const GByte *pSrc = abyRawTileData.data();
     611         327 :         GByte *pDst = &abyDecodedTileData[0];
     612        2375 :         for (size_t i = 0; i < nValues;
     613        1933 :              i++, pSrc += nSourceSize, pDst += nDTSize)
     614             :         {
     615        1933 :             DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
     616             :         }
     617             :     }
     618             : 
     619       12338 :     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       10687 :         for (size_t iReq = jobStruct->nFirstIdx;
     710       10687 :              iReq < jobStruct->nLastIdxNotIncluded; ++iReq)
     711             :         {
     712             :             // Check if we must early exit
     713             :             {
     714       10671 :                 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       10669 :             uint64_t nTileIdx = 0;
     723       31997 :             for (size_t j = 0; j < l_nDims; ++j)
     724             :             {
     725       21338 :                 if (j > 0)
     726       10671 :                     nTileIdx *= aoDims[j - 1]->GetSize();
     727       21328 :                 nTileIdx += tileIndices[j];
     728             :             }
     729             : 
     730       10659 :             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       10651 :             bool bIsEmpty = false;
     739       10651 :             bool success = poArray->LoadTileData(tileIndices,
     740             :                                                  true,  // use mutex
     741             :                                                  psDecompressor, abyRawTileData,
     742             :                                                  abyTmpRawTileData,
     743             :                                                  abyDecodedTileData, bIsEmpty);
     744             : 
     745       10372 :             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          17 :         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          16 :             std::lock_guard<std::mutex> oLock(m_oMutex);
     785          16 :             if (nRemainingThreads == 0)
     786           4 :                 break;
     787             :         }
     788          12 :         wtp->WaitEvent();
     789          12 :     }
     790             : 
     791           4 :     return bGlobalStatus;
     792             : }
     793             : 
     794             : /************************************************************************/
     795             : /*                    ZarrV2Array::FlushDirtyTile()                     */
     796             : /************************************************************************/
     797             : 
     798       17748 : bool ZarrV2Array::FlushDirtyTile() const
     799             : {
     800       17748 :     if (!m_bDirtyTile)
     801        5688 :         return true;
     802       12060 :     m_bDirtyTile = false;
     803             : 
     804       24120 :     std::string osFilename = BuildTileFilename(m_anCachedTiledIndices.data());
     805             : 
     806             :     const size_t nSourceSize =
     807       12060 :         m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
     808             :     const auto &abyTile =
     809       12060 :         m_abyDecodedTileData.empty() ? m_abyRawTileData : m_abyDecodedTileData;
     810             : 
     811       12060 :     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       11370 :     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       11370 :     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       11370 :     size_t nRawDataSize = m_abyRawTileData.size();
     846       11372 :     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       11370 :     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       11370 :     VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
     890       11370 :     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       11370 :     bool bRet = true;
     898       11370 :     if (m_psCompressor == nullptr)
     899             :     {
     900        5920 :         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       11370 :     VSIFCloseL(fp);
     966             : 
     967       11370 :     return bRet;
     968             : }
     969             : 
     970             : /************************************************************************/
     971             : /*                          BuildTileFilename()                         */
     972             : /************************************************************************/
     973             : 
     974       27557 : std::string ZarrV2Array::BuildTileFilename(const uint64_t *tileIndices) const
     975             : {
     976       27557 :     std::string osFilename;
     977       27552 :     if (m_aoDims.empty())
     978             :     {
     979           1 :         osFilename = "0";
     980             :     }
     981             :     else
     982             :     {
     983       83189 :         for (size_t i = 0; i < m_aoDims.size(); ++i)
     984             :         {
     985       55628 :             if (!osFilename.empty())
     986       28103 :                 osFilename += m_osDimSeparator;
     987       55632 :             osFilename += std::to_string(tileIndices[i]);
     988             :         }
     989             :     }
     990             : 
     991       55059 :     return CPLFormFilenameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
     992       82596 :                                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         639 : 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         639 :         if (obj.GetType() == CPLJSONObject::Type::String)
    1066             :         {
    1067        1256 :             const auto str = obj.ToString();
    1068         628 :             char chEndianness = 0;
    1069             :             char chType;
    1070             :             int nBytes;
    1071         628 :             DtypeElt elt;
    1072         628 :             if (str.size() < 3)
    1073           3 :                 break;
    1074         625 :             chEndianness = str[0];
    1075         625 :             chType = str[1];
    1076         625 :             nBytes = atoi(str.c_str() + 2);
    1077         625 :             if (nBytes <= 0 || nBytes >= 1000)
    1078             :                 break;
    1079             : 
    1080         623 :             elt.needByteSwapping = false;
    1081         623 :             if ((nBytes > 1 && chType != 'S') || chType == 'U')
    1082             :             {
    1083         344 :                 if (chEndianness == '<')
    1084         301 :                     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         623 :             if (!elts.empty())
    1091             :             {
    1092          11 :                 elt.nativeOffset =
    1093          11 :                     elts.back().nativeOffset + elts.back().nativeSize;
    1094             :             }
    1095         623 :             elt.nativeSize = nBytes;
    1096         623 :             if (chType == 'b' && nBytes == 1)  // boolean
    1097             :             {
    1098          66 :                 elt.nativeType = DtypeElt::NativeType::BOOLEAN;
    1099          66 :                 eDT = GDT_Byte;
    1100             :             }
    1101         557 :             else if (chType == 'u' && nBytes == 1)
    1102             :             {
    1103         192 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
    1104         192 :                 eDT = GDT_Byte;
    1105             :             }
    1106         365 :             else if (chType == 'i' && nBytes == 1)
    1107             :             {
    1108          12 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
    1109          12 :                 eDT = GDT_Int8;
    1110             :             }
    1111         353 :             else if (chType == 'i' && nBytes == 2)
    1112             :             {
    1113          20 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
    1114          20 :                 eDT = GDT_Int16;
    1115             :             }
    1116         333 :             else if (chType == 'i' && nBytes == 4)
    1117             :             {
    1118          26 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
    1119          26 :                 eDT = GDT_Int32;
    1120             :             }
    1121         307 :             else if (chType == 'i' && nBytes == 8)
    1122             :             {
    1123          15 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
    1124          15 :                 eDT = GDT_Int64;
    1125             :             }
    1126         292 :             else if (chType == 'u' && nBytes == 2)
    1127             :             {
    1128          21 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
    1129          21 :                 eDT = GDT_UInt16;
    1130             :             }
    1131         271 :             else if (chType == 'u' && nBytes == 4)
    1132             :             {
    1133          18 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
    1134          18 :                 eDT = GDT_UInt32;
    1135             :             }
    1136         253 :             else if (chType == 'u' && nBytes == 8)
    1137             :             {
    1138          14 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
    1139          14 :                 eDT = GDT_UInt64;
    1140             :             }
    1141         239 :             else if (chType == 'f' && nBytes == 2)
    1142             :             {
    1143           2 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
    1144           2 :                 elt.gdalTypeIsApproxOfNative = true;
    1145           2 :                 eDT = GDT_Float32;
    1146             :             }
    1147         237 :             else if (chType == 'f' && nBytes == 4)
    1148             :             {
    1149          43 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
    1150          43 :                 eDT = GDT_Float32;
    1151             :             }
    1152         194 :             else if (chType == 'f' && nBytes == 8)
    1153             :             {
    1154         156 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
    1155         156 :                 eDT = GDT_Float64;
    1156             :             }
    1157          38 :             else if (chType == 'c' && nBytes == 8)
    1158             :             {
    1159          11 :                 elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
    1160          11 :                 eDT = GDT_CFloat32;
    1161             :             }
    1162          27 :             else if (chType == 'c' && nBytes == 16)
    1163             :             {
    1164          12 :                 elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
    1165          12 :                 eDT = GDT_CFloat64;
    1166             :             }
    1167          15 :             else if (chType == 'S')
    1168             :             {
    1169           9 :                 elt.nativeType = DtypeElt::NativeType::STRING_ASCII;
    1170           9 :                 elt.gdalType = GDALExtendedDataType::CreateString(nBytes);
    1171           9 :                 elt.gdalSize = elt.gdalType.GetSize();
    1172           9 :                 elts.emplace_back(elt);
    1173           9 :                 return GDALExtendedDataType::CreateString(nBytes);
    1174             :             }
    1175           6 :             else if (chType == 'U')
    1176             :             {
    1177           5 :                 elt.nativeType = DtypeElt::NativeType::STRING_UNICODE;
    1178             :                 // the dtype declaration is number of UCS4 characters. Store it
    1179             :                 // as bytes
    1180           5 :                 elt.nativeSize *= 4;
    1181             :                 // We can really map UCS4 size to UTF-8
    1182           5 :                 elt.gdalType = GDALExtendedDataType::CreateString();
    1183           5 :                 elt.gdalSize = elt.gdalType.GetSize();
    1184           5 :                 elts.emplace_back(elt);
    1185           5 :                 return GDALExtendedDataType::CreateString();
    1186             :             }
    1187             :             else
    1188           1 :                 break;
    1189         608 :             elt.gdalType = GDALExtendedDataType::Create(eDT);
    1190         608 :             elt.gdalSize = elt.gdalType.GetSize();
    1191         608 :             elts.emplace_back(elt);
    1192         608 :             return GDALExtendedDataType::Create(eDT);
    1193             :         }
    1194          11 :         else if (obj.GetType() == CPLJSONObject::Type::Array)
    1195             :         {
    1196           9 :             bool error = false;
    1197           9 :             const auto oArray = obj.ToArray();
    1198           9 :             std::vector<std::unique_ptr<GDALEDTComponent>> comps;
    1199           9 :             size_t offset = 0;
    1200           9 :             size_t alignmentMax = 1;
    1201          29 :             for (const auto &oElt : oArray)
    1202             :             {
    1203          20 :                 const auto oEltArray = oElt.ToArray();
    1204          40 :                 if (!oEltArray.IsValid() || oEltArray.Size() != 2 ||
    1205          40 :                     oEltArray[0].GetType() != CPLJSONObject::Type::String)
    1206             :                 {
    1207           0 :                     error = true;
    1208           0 :                     break;
    1209             :                 }
    1210          20 :                 GDALExtendedDataType subDT = ParseDtype(oEltArray[1], elts);
    1211          35 :                 if (subDT.GetClass() == GEDTC_NUMERIC &&
    1212          15 :                     subDT.GetNumericDataType() == GDT_Unknown)
    1213             :                 {
    1214           0 :                     error = true;
    1215           0 :                     break;
    1216             :                 }
    1217             : 
    1218          40 :                 const std::string osName = oEltArray[0].ToString();
    1219             :                 // Add padding for alignment
    1220          20 :                 const size_t alignmentSub = GetAlignment(oEltArray[1]);
    1221          20 :                 assert(alignmentSub);
    1222          20 :                 alignmentMax = std::max(alignmentMax, alignmentSub);
    1223          20 :                 offset = AlignOffsetOn(offset, alignmentSub);
    1224          40 :                 comps.emplace_back(std::unique_ptr<GDALEDTComponent>(
    1225          40 :                     new GDALEDTComponent(osName, offset, subDT)));
    1226          20 :                 offset += subDT.GetSize();
    1227             :             }
    1228           9 :             if (error)
    1229           0 :                 break;
    1230           9 :             size_t nTotalSize = offset;
    1231           9 :             nTotalSize = AlignOffsetOn(nTotalSize, alignmentMax);
    1232          18 :             return GDALExtendedDataType::Create(obj.ToString(), nTotalSize,
    1233          18 :                                                 std::move(comps));
    1234             :         }
    1235             :     } while (false);
    1236           8 :     CPLError(CE_Failure, CPLE_AppDefined,
    1237             :              "Invalid or unsupported format for dtype: %s",
    1238          16 :              obj.ToString().c_str());
    1239           8 :     return GDALExtendedDataType::Create(GDT_Unknown);
    1240             : }
    1241             : 
    1242         631 : static void SetGDALOffset(const GDALExtendedDataType &dt,
    1243             :                           const size_t nBaseOffset, std::vector<DtypeElt> &elts,
    1244             :                           size_t &iCurElt)
    1245             : {
    1246         631 :     if (dt.GetClass() == GEDTC_COMPOUND)
    1247             :     {
    1248           9 :         const auto &comps = dt.GetComponents();
    1249          29 :         for (const auto &comp : comps)
    1250             :         {
    1251          20 :             const size_t nBaseOffsetSub = nBaseOffset + comp->GetOffset();
    1252          20 :             SetGDALOffset(comp->GetType(), nBaseOffsetSub, elts, iCurElt);
    1253             :         }
    1254             :     }
    1255             :     else
    1256             :     {
    1257         622 :         elts[iCurElt].gdalOffset = nBaseOffset;
    1258         622 :         iCurElt++;
    1259             :     }
    1260         631 : }
    1261             : 
    1262             : /************************************************************************/
    1263             : /*                     ZarrV2Group::LoadArray()                         */
    1264             : /************************************************************************/
    1265             : 
    1266             : std::shared_ptr<ZarrArray>
    1267         637 : ZarrV2Group::LoadArray(const std::string &osArrayName,
    1268             :                        const std::string &osZarrayFilename,
    1269             :                        const CPLJSONObject &oRoot, bool bLoadedFromZMetadata,
    1270             :                        const CPLJSONObject &oAttributesIn) const
    1271             : {
    1272             :     // Add osZarrayFilename to m_poSharedResource during the scope
    1273             :     // of this function call.
    1274         637 :     ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
    1275        1274 :                                                        osZarrayFilename);
    1276         637 :     if (!filenameAdder.ok())
    1277           2 :         return nullptr;
    1278             : 
    1279        1905 :     const auto osFormat = oRoot["zarr_format"].ToString();
    1280         635 :     if (osFormat != "2")
    1281             :     {
    1282           3 :         CPLError(CE_Failure, CPLE_NotSupported,
    1283             :                  "Invalid value for zarr_format");
    1284           3 :         return nullptr;
    1285             :     }
    1286             : 
    1287         632 :     bool bFortranOrder = false;
    1288         632 :     const char *orderKey = "order";
    1289        1896 :     const auto osOrder = oRoot[orderKey].ToString();
    1290         632 :     if (osOrder == "C")
    1291             :     {
    1292             :         // ok
    1293             :     }
    1294          33 :     else if (osOrder == "F")
    1295             :     {
    1296          30 :         bFortranOrder = true;
    1297             :     }
    1298             :     else
    1299             :     {
    1300           3 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid value for %s",
    1301             :                  orderKey);
    1302           3 :         return nullptr;
    1303             :     }
    1304             : 
    1305        1887 :     const auto oShape = oRoot["shape"].ToArray();
    1306         629 :     if (!oShape.IsValid())
    1307             :     {
    1308           3 :         CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
    1309           3 :         return nullptr;
    1310             :     }
    1311             : 
    1312         626 :     const char *chunksKey = "chunks";
    1313        1878 :     const auto oChunks = oRoot[chunksKey].ToArray();
    1314         626 :     if (!oChunks.IsValid())
    1315             :     {
    1316           3 :         CPLError(CE_Failure, CPLE_AppDefined, "%s missing or not an array",
    1317             :                  chunksKey);
    1318           3 :         return nullptr;
    1319             :     }
    1320             : 
    1321         623 :     if (oShape.Size() != oChunks.Size())
    1322             :     {
    1323           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    1324             :                  "shape and chunks arrays are of different size");
    1325           2 :         return nullptr;
    1326             :     }
    1327             : 
    1328        1242 :     CPLJSONObject oAttributes(oAttributesIn);
    1329         621 :     if (!bLoadedFromZMetadata)
    1330             :     {
    1331         696 :         CPLJSONDocument oDoc;
    1332             :         const std::string osZattrsFilename(CPLFormFilenameSafe(
    1333         348 :             CPLGetDirnameSafe(osZarrayFilename.c_str()).c_str(), ".zattrs",
    1334         696 :             nullptr));
    1335         696 :         CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    1336         348 :         if (oDoc.Load(osZattrsFilename))
    1337             :         {
    1338         114 :             oAttributes = oDoc.GetRoot();
    1339             :         }
    1340             :     }
    1341             : 
    1342             :     // Deep-clone of oAttributes
    1343             :     {
    1344         621 :         CPLJSONDocument oTmpDoc;
    1345         621 :         oTmpDoc.SetRoot(oAttributes);
    1346         621 :         CPL_IGNORE_RET_VAL(oTmpDoc.LoadMemory(oTmpDoc.SaveAsString()));
    1347         621 :         oAttributes = oTmpDoc.GetRoot();
    1348             :     }
    1349             : 
    1350        1242 :     std::vector<std::shared_ptr<GDALDimension>> aoDims;
    1351        1606 :     for (int i = 0; i < oShape.Size(); ++i)
    1352             :     {
    1353         986 :         const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
    1354         986 :         if (nSize == 0)
    1355             :         {
    1356           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
    1357           1 :             return nullptr;
    1358             :         }
    1359         985 :         aoDims.emplace_back(std::make_shared<ZarrDimension>(
    1360         985 :             m_poSharedResource,
    1361        1970 :             std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
    1362        1970 :             std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
    1363         985 :             nSize));
    1364             :     }
    1365             : 
    1366             :     // XArray extension
    1367        1860 :     const auto arrayDimensionsObj = oAttributes["_ARRAY_DIMENSIONS"];
    1368             : 
    1369             :     const auto FindDimension =
    1370         489 :         [this, &aoDims, bLoadedFromZMetadata, &osArrayName,
    1371             :          &oAttributes](const std::string &osDimName,
    1372        5584 :                        std::shared_ptr<GDALDimension> &poDim, int i)
    1373             :     {
    1374         489 :         auto oIter = m_oMapDimensions.find(osDimName);
    1375         489 :         if (oIter != m_oMapDimensions.end())
    1376             :         {
    1377         238 :             if (m_bDimSizeInUpdate ||
    1378         118 :                 oIter->second->GetSize() == poDim->GetSize())
    1379             :             {
    1380         120 :                 poDim = oIter->second;
    1381         120 :                 return true;
    1382             :             }
    1383             :             else
    1384             :             {
    1385           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1386             :                          "Size of _ARRAY_DIMENSIONS[%d] different "
    1387             :                          "from the one of shape",
    1388             :                          i);
    1389           0 :                 return false;
    1390             :             }
    1391             :         }
    1392             : 
    1393             :         // Try to load the indexing variable.
    1394             : 
    1395             :         // If loading from zmetadata, we should have normally
    1396             :         // already loaded the dimension variables, unless they
    1397             :         // are in a upper level.
    1398         547 :         if (bLoadedFromZMetadata && osArrayName != osDimName &&
    1399         547 :             m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
    1400             :         {
    1401         178 :             auto poParent = m_poParent.lock();
    1402         192 :             while (poParent != nullptr)
    1403             :             {
    1404          21 :                 oIter = poParent->m_oMapDimensions.find(osDimName);
    1405          28 :                 if (oIter != poParent->m_oMapDimensions.end() &&
    1406           7 :                     oIter->second->GetSize() == poDim->GetSize())
    1407             :                 {
    1408           7 :                     poDim = oIter->second;
    1409           7 :                     return true;
    1410             :                 }
    1411          14 :                 poParent = poParent->m_poParent.lock();
    1412             :             }
    1413             :         }
    1414             : 
    1415             :         // Not loading from zmetadata, and not in m_oMapMDArrays,
    1416             :         // then stat() the indexing variable.
    1417         260 :         else if (!bLoadedFromZMetadata && osArrayName != osDimName &&
    1418         260 :                  m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
    1419             :         {
    1420         138 :             std::string osDirName = m_osDirectoryName;
    1421             :             while (true)
    1422             :             {
    1423             :                 const std::string osArrayFilenameDim = CPLFormFilenameSafe(
    1424         134 :                     CPLFormFilenameSafe(osDirName.c_str(), osDimName.c_str(),
    1425             :                                         nullptr)
    1426             :                         .c_str(),
    1427         134 :                     ".zarray", nullptr);
    1428             :                 VSIStatBufL sStat;
    1429         134 :                 if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
    1430             :                 {
    1431          96 :                     CPLJSONDocument oDoc;
    1432          48 :                     if (oDoc.Load(osArrayFilenameDim))
    1433             :                     {
    1434          48 :                         LoadArray(osDimName, osArrayFilenameDim, oDoc.GetRoot(),
    1435          96 :                                   false, CPLJSONObject());
    1436             :                     }
    1437             :                 }
    1438             :                 else
    1439             :                 {
    1440             :                     // Recurse to upper level for datasets such as
    1441             :                     // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
    1442             :                     const std::string osDirNameNew =
    1443          86 :                         CPLGetPathSafe(osDirName.c_str());
    1444          86 :                     if (!osDirNameNew.empty() && osDirNameNew != osDirName)
    1445             :                     {
    1446          65 :                         osDirName = osDirNameNew;
    1447          65 :                         continue;
    1448             :                     }
    1449             :                 }
    1450          69 :                 break;
    1451          65 :             }
    1452             :         }
    1453             : 
    1454         362 :         oIter = m_oMapDimensions.find(osDimName);
    1455         376 :         if (oIter != m_oMapDimensions.end() &&
    1456          14 :             oIter->second->GetSize() == poDim->GetSize())
    1457             :         {
    1458          14 :             poDim = oIter->second;
    1459          14 :             return true;
    1460             :         }
    1461             : 
    1462         696 :         std::string osType;
    1463         696 :         std::string osDirection;
    1464         348 :         if (aoDims.size() == 1 && osArrayName == osDimName)
    1465             :         {
    1466         122 :             ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
    1467             :                                                  osDirection);
    1468             :         }
    1469             : 
    1470             :         auto poDimLocal = std::make_shared<ZarrDimension>(
    1471         348 :             m_poSharedResource,
    1472         696 :             std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
    1473         696 :             GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
    1474         348 :         poDimLocal->SetXArrayDimension();
    1475         348 :         m_oMapDimensions[osDimName] = poDimLocal;
    1476         348 :         poDim = poDimLocal;
    1477         348 :         return true;
    1478         620 :     };
    1479             : 
    1480         620 :     if (arrayDimensionsObj.GetType() == CPLJSONObject::Type::Array)
    1481             :     {
    1482         652 :         const auto arrayDims = arrayDimensionsObj.ToArray();
    1483         326 :         if (arrayDims.Size() == oShape.Size())
    1484             :         {
    1485         326 :             bool ok = true;
    1486         815 :             for (int i = 0; i < oShape.Size(); ++i)
    1487             :             {
    1488         489 :                 if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
    1489             :                 {
    1490         978 :                     const auto osDimName = arrayDims[i].ToString();
    1491         489 :                     ok &= FindDimension(osDimName, aoDims[i], i);
    1492             :                 }
    1493             :             }
    1494         326 :             if (ok)
    1495             :             {
    1496         326 :                 oAttributes.Delete("_ARRAY_DIMENSIONS");
    1497             :             }
    1498             :         }
    1499             :         else
    1500             :         {
    1501           0 :             CPLError(
    1502             :                 CE_Warning, CPLE_AppDefined,
    1503             :                 "Size of _ARRAY_DIMENSIONS different from the one of shape");
    1504             :         }
    1505             :     }
    1506             : 
    1507             :     // _NCZARR_ARRAY extension
    1508        1860 :     const auto nczarrArrayDimrefs = oRoot["_NCZARR_ARRAY"]["dimrefs"].ToArray();
    1509         620 :     if (nczarrArrayDimrefs.IsValid())
    1510             :     {
    1511          42 :         const auto arrayDims = nczarrArrayDimrefs.ToArray();
    1512          21 :         if (arrayDims.Size() == oShape.Size())
    1513             :         {
    1514             :             auto poRG =
    1515          42 :                 std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
    1516          21 :             CPLAssert(poRG != nullptr);
    1517             :             while (true)
    1518             :             {
    1519          48 :                 auto poNewRG = poRG->m_poParent.lock();
    1520          48 :                 if (poNewRG == nullptr)
    1521          21 :                     break;
    1522          27 :                 poRG = std::move(poNewRG);
    1523          27 :             }
    1524             : 
    1525          49 :             for (int i = 0; i < oShape.Size(); ++i)
    1526             :             {
    1527          28 :                 if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
    1528             :                 {
    1529          84 :                     const auto osDimFullpath = arrayDims[i].ToString();
    1530             :                     const std::string osArrayFullname =
    1531          56 :                         (GetFullName() != "/" ? GetFullName() : std::string()) +
    1532          56 :                         '/' + osArrayName;
    1533          48 :                     if (aoDims.size() == 1 &&
    1534          20 :                         (osDimFullpath == osArrayFullname ||
    1535          34 :                          osDimFullpath == "/" + osArrayFullname))
    1536             :                     {
    1537             :                         // If this is an indexing variable, then fetch the
    1538             :                         // dimension type and direction, and patch the dimension
    1539          28 :                         std::string osType;
    1540          28 :                         std::string osDirection;
    1541          14 :                         ZarrArray::GetDimensionTypeDirection(
    1542             :                             oAttributes, osType, osDirection);
    1543             : 
    1544             :                         auto poDimLocal = std::make_shared<ZarrDimension>(
    1545          14 :                             m_poSharedResource,
    1546          28 :                             std::dynamic_pointer_cast<ZarrGroupBase>(
    1547          14 :                                 m_pSelf.lock()),
    1548          14 :                             GetFullName(), osArrayName, osType, osDirection,
    1549          28 :                             aoDims[i]->GetSize());
    1550          14 :                         aoDims[i] = poDimLocal;
    1551             : 
    1552          14 :                         m_oMapDimensions[osArrayName] = std::move(poDimLocal);
    1553             :                     }
    1554          14 :                     else if (auto poDim =
    1555          28 :                                  poRG->OpenDimensionFromFullname(osDimFullpath))
    1556             :                     {
    1557          13 :                         if (poDim->GetSize() != aoDims[i]->GetSize())
    1558             :                         {
    1559           1 :                             CPLError(CE_Failure, CPLE_AppDefined,
    1560             :                                      "Inconsistency in size between NCZarr "
    1561             :                                      "dimension %s and regular dimension",
    1562             :                                      osDimFullpath.c_str());
    1563             :                         }
    1564             :                         else
    1565             :                         {
    1566          12 :                             aoDims[i] = std::move(poDim);
    1567             :                         }
    1568             :                     }
    1569             :                     else
    1570             :                     {
    1571           1 :                         CPLError(CE_Failure, CPLE_AppDefined,
    1572             :                                  "Cannot find NCZarr dimension %s",
    1573             :                                  osDimFullpath.c_str());
    1574             :                     }
    1575             :                 }
    1576             :             }
    1577             :         }
    1578             :         else
    1579             :         {
    1580           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1581             :                      "Size of _NCZARR_ARRAY.dimrefs different from the one of "
    1582             :                      "shape");
    1583             :         }
    1584             :     }
    1585             : 
    1586         620 :     constexpr const char *dtypeKey = "dtype";
    1587        1860 :     auto oDtype = oRoot[dtypeKey];
    1588         620 :     if (!oDtype.IsValid())
    1589             :     {
    1590           1 :         CPLError(CE_Failure, CPLE_NotSupported, "%s missing", dtypeKey);
    1591           1 :         return nullptr;
    1592             :     }
    1593        1238 :     std::vector<DtypeElt> aoDtypeElts;
    1594        1238 :     const auto oType = ParseDtype(oDtype, aoDtypeElts);
    1595        1220 :     if (oType.GetClass() == GEDTC_NUMERIC &&
    1596         601 :         oType.GetNumericDataType() == GDT_Unknown)
    1597           8 :         return nullptr;
    1598         611 :     size_t iCurElt = 0;
    1599         611 :     SetGDALOffset(oType, 0, aoDtypeElts, iCurElt);
    1600             : 
    1601        1222 :     std::vector<GUInt64> anBlockSize;
    1602         611 :     if (!ZarrArray::ParseChunkSize(oChunks, oType, anBlockSize))
    1603           2 :         return nullptr;
    1604             : 
    1605        1827 :     std::string osDimSeparator = oRoot["dimension_separator"].ToString();
    1606         609 :     if (osDimSeparator.empty())
    1607         599 :         osDimSeparator = ".";
    1608             : 
    1609        1218 :     std::vector<GByte> abyNoData;
    1610             : 
    1611             :     struct NoDataFreer
    1612             :     {
    1613             :         std::vector<GByte> &m_abyNodata;
    1614             :         const GDALExtendedDataType &m_oType;
    1615             : 
    1616         609 :         NoDataFreer(std::vector<GByte> &abyNoDataIn,
    1617             :                     const GDALExtendedDataType &oTypeIn)
    1618         609 :             : m_abyNodata(abyNoDataIn), m_oType(oTypeIn)
    1619             :         {
    1620         609 :         }
    1621             : 
    1622         609 :         ~NoDataFreer()
    1623         609 :         {
    1624         609 :             if (!m_abyNodata.empty())
    1625         103 :                 m_oType.FreeDynamicMemory(&m_abyNodata[0]);
    1626         609 :         }
    1627             :     };
    1628             : 
    1629        1218 :     NoDataFreer NoDataFreer(abyNoData, oType);
    1630             : 
    1631        1827 :     auto oFillValue = oRoot["fill_value"];
    1632         609 :     auto eFillValueType = oFillValue.GetType();
    1633             : 
    1634             :     // Normally arrays are not supported, but that's what NCZarr 4.8.0 outputs
    1635         610 :     if (eFillValueType == CPLJSONObject::Type::Array &&
    1636         610 :         oFillValue.ToArray().Size() == 1)
    1637             :     {
    1638           0 :         oFillValue = oFillValue.ToArray()[0];
    1639           0 :         eFillValueType = oFillValue.GetType();
    1640             :     }
    1641             : 
    1642         609 :     if (!oFillValue.IsValid())
    1643             :     {
    1644             :         // fill_value is normally required but some implementations
    1645             :         // are lacking it: https://github.com/Unidata/netcdf-c/issues/2059
    1646           1 :         CPLError(CE_Warning, CPLE_AppDefined, "fill_value missing");
    1647             :     }
    1648         608 :     else if (eFillValueType == CPLJSONObject::Type::Null)
    1649             :     {
    1650             :         // Nothing to do
    1651             :     }
    1652         111 :     else if (eFillValueType == CPLJSONObject::Type::String)
    1653             :     {
    1654          98 :         const auto osFillValue = oFillValue.ToString();
    1655          86 :         if (oType.GetClass() == GEDTC_NUMERIC &&
    1656          37 :             CPLGetValueType(osFillValue.c_str()) != CPL_VALUE_STRING)
    1657             :         {
    1658           8 :             abyNoData.resize(oType.GetSize());
    1659             :             // Be tolerant with numeric values serialized as strings.
    1660           8 :             if (oType.GetNumericDataType() == GDT_Int64)
    1661             :             {
    1662             :                 const int64_t nVal = static_cast<int64_t>(
    1663           2 :                     std::strtoll(osFillValue.c_str(), nullptr, 10));
    1664           2 :                 GDALCopyWords(&nVal, GDT_Int64, 0, &abyNoData[0],
    1665             :                               oType.GetNumericDataType(), 0, 1);
    1666             :             }
    1667           6 :             else if (oType.GetNumericDataType() == GDT_UInt64)
    1668             :             {
    1669             :                 const uint64_t nVal = static_cast<uint64_t>(
    1670           2 :                     std::strtoull(osFillValue.c_str(), nullptr, 10));
    1671           2 :                 GDALCopyWords(&nVal, GDT_UInt64, 0, &abyNoData[0],
    1672             :                               oType.GetNumericDataType(), 0, 1);
    1673             :             }
    1674             :             else
    1675             :             {
    1676           4 :                 const double dfNoDataValue = CPLAtof(osFillValue.c_str());
    1677           4 :                 GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
    1678             :                               oType.GetNumericDataType(), 0, 1);
    1679             :             }
    1680             :         }
    1681          41 :         else if (oType.GetClass() == GEDTC_NUMERIC)
    1682             :         {
    1683             :             double dfNoDataValue;
    1684          29 :             if (osFillValue == "NaN")
    1685             :             {
    1686          10 :                 dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
    1687             :             }
    1688          19 :             else if (osFillValue == "Infinity")
    1689             :             {
    1690           9 :                 dfNoDataValue = std::numeric_limits<double>::infinity();
    1691             :             }
    1692          10 :             else if (osFillValue == "-Infinity")
    1693             :             {
    1694           9 :                 dfNoDataValue = -std::numeric_limits<double>::infinity();
    1695             :             }
    1696             :             else
    1697             :             {
    1698           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1699           2 :                 return nullptr;
    1700             :             }
    1701          28 :             if (oType.GetNumericDataType() == GDT_Float32)
    1702             :             {
    1703          12 :                 const float fNoDataValue = static_cast<float>(dfNoDataValue);
    1704          12 :                 abyNoData.resize(sizeof(fNoDataValue));
    1705          12 :                 memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
    1706             :             }
    1707          16 :             else if (oType.GetNumericDataType() == GDT_Float64)
    1708             :             {
    1709          15 :                 abyNoData.resize(sizeof(dfNoDataValue));
    1710          15 :                 memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
    1711             :             }
    1712             :             else
    1713             :             {
    1714           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1715           1 :                 return nullptr;
    1716             :             }
    1717             :         }
    1718          12 :         else if (oType.GetClass() == GEDTC_STRING)
    1719             :         {
    1720             :             // zarr.open('unicode_be.zarr', mode = 'w', shape=(1,), dtype =
    1721             :             // '>U1', compressor = None) oddly generates "fill_value": "0"
    1722           7 :             if (osFillValue != "0")
    1723             :             {
    1724           3 :                 std::vector<GByte> abyNativeFillValue(osFillValue.size() + 1);
    1725           3 :                 memcpy(&abyNativeFillValue[0], osFillValue.data(),
    1726             :                        osFillValue.size());
    1727           3 :                 int nBytes = CPLBase64DecodeInPlace(&abyNativeFillValue[0]);
    1728           3 :                 abyNativeFillValue.resize(nBytes + 1);
    1729           3 :                 abyNativeFillValue[nBytes] = 0;
    1730           3 :                 abyNoData.resize(oType.GetSize());
    1731           3 :                 char *pDstStr = CPLStrdup(
    1732           3 :                     reinterpret_cast<const char *>(&abyNativeFillValue[0]));
    1733           3 :                 char **pDstPtr = reinterpret_cast<char **>(&abyNoData[0]);
    1734           3 :                 memcpy(pDstPtr, &pDstStr, sizeof(pDstStr));
    1735             :             }
    1736             :         }
    1737             :         else
    1738             :         {
    1739           5 :             std::vector<GByte> abyNativeFillValue(osFillValue.size() + 1);
    1740           5 :             memcpy(&abyNativeFillValue[0], osFillValue.data(),
    1741             :                    osFillValue.size());
    1742           5 :             int nBytes = CPLBase64DecodeInPlace(&abyNativeFillValue[0]);
    1743           5 :             abyNativeFillValue.resize(nBytes);
    1744           5 :             if (abyNativeFillValue.size() !=
    1745           5 :                 aoDtypeElts.back().nativeOffset + aoDtypeElts.back().nativeSize)
    1746             :             {
    1747           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1748           0 :                 return nullptr;
    1749             :             }
    1750           5 :             abyNoData.resize(oType.GetSize());
    1751           5 :             ZarrArray::DecodeSourceElt(aoDtypeElts, abyNativeFillValue.data(),
    1752           5 :                                        &abyNoData[0]);
    1753             :         }
    1754             :     }
    1755          62 :     else if (eFillValueType == CPLJSONObject::Type::Boolean ||
    1756          14 :              eFillValueType == CPLJSONObject::Type::Integer ||
    1757           6 :              eFillValueType == CPLJSONObject::Type::Long ||
    1758             :              eFillValueType == CPLJSONObject::Type::Double)
    1759             :     {
    1760          61 :         if (oType.GetClass() == GEDTC_NUMERIC)
    1761             :         {
    1762          60 :             const double dfNoDataValue = oFillValue.ToDouble();
    1763          60 :             if (oType.GetNumericDataType() == GDT_Int64)
    1764             :             {
    1765             :                 const int64_t nNoDataValue =
    1766           2 :                     static_cast<int64_t>(oFillValue.ToLong());
    1767           2 :                 abyNoData.resize(oType.GetSize());
    1768           2 :                 GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
    1769             :                               oType.GetNumericDataType(), 0, 1);
    1770             :             }
    1771          61 :             else if (oType.GetNumericDataType() == GDT_UInt64 &&
    1772             :                      /* we can't really deal with nodata value between */
    1773             :                      /* int64::max and uint64::max due to json-c limitations */
    1774           3 :                      dfNoDataValue >= 0)
    1775             :             {
    1776             :                 const int64_t nNoDataValue =
    1777           3 :                     static_cast<int64_t>(oFillValue.ToLong());
    1778           3 :                 abyNoData.resize(oType.GetSize());
    1779           3 :                 GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
    1780             :                               oType.GetNumericDataType(), 0, 1);
    1781             :             }
    1782             :             else
    1783             :             {
    1784          55 :                 abyNoData.resize(oType.GetSize());
    1785          55 :                 GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
    1786             :                               oType.GetNumericDataType(), 0, 1);
    1787             :             }
    1788             :         }
    1789             :         else
    1790             :         {
    1791           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1792           1 :             return nullptr;
    1793          60 :         }
    1794             :     }
    1795             :     else
    1796             :     {
    1797           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1798           1 :         return nullptr;
    1799             :     }
    1800             : 
    1801         605 :     const CPLCompressor *psCompressor = nullptr;
    1802         605 :     const CPLCompressor *psDecompressor = nullptr;
    1803        1815 :     const auto oCompressor = oRoot["compressor"];
    1804        1210 :     std::string osDecompressorId("NONE");
    1805             : 
    1806         605 :     if (!oCompressor.IsValid())
    1807             :     {
    1808           1 :         CPLError(CE_Failure, CPLE_AppDefined, "compressor missing");
    1809           1 :         return nullptr;
    1810             :     }
    1811         604 :     if (oCompressor.GetType() == CPLJSONObject::Type::Null)
    1812             :     {
    1813             :         // nothing to do
    1814             :     }
    1815          32 :     else if (oCompressor.GetType() == CPLJSONObject::Type::Object)
    1816             :     {
    1817          31 :         osDecompressorId = oCompressor["id"].ToString();
    1818          31 :         if (osDecompressorId.empty())
    1819             :         {
    1820           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Missing compressor id");
    1821           1 :             return nullptr;
    1822             :         }
    1823          30 :         psCompressor = CPLGetCompressor(osDecompressorId.c_str());
    1824          30 :         psDecompressor = CPLGetDecompressor(osDecompressorId.c_str());
    1825          30 :         if (psCompressor == nullptr || psDecompressor == nullptr)
    1826             :         {
    1827           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Decompressor %s not handled",
    1828             :                      osDecompressorId.c_str());
    1829           1 :             return nullptr;
    1830             :         }
    1831             :     }
    1832             :     else
    1833             :     {
    1834           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid compressor");
    1835           1 :         return nullptr;
    1836             :     }
    1837             : 
    1838        1202 :     CPLJSONArray oFiltersArray;
    1839        1803 :     const auto oFilters = oRoot["filters"];
    1840         601 :     if (!oFilters.IsValid())
    1841             :     {
    1842           1 :         CPLError(CE_Failure, CPLE_AppDefined, "filters missing");
    1843           1 :         return nullptr;
    1844             :     }
    1845         600 :     if (oFilters.GetType() == CPLJSONObject::Type::Null)
    1846             :     {
    1847             :     }
    1848           9 :     else if (oFilters.GetType() == CPLJSONObject::Type::Array)
    1849             :     {
    1850           7 :         oFiltersArray = oFilters.ToArray();
    1851          11 :         for (const auto &oFilter : oFiltersArray)
    1852             :         {
    1853          10 :             const auto osFilterId = oFilter["id"].ToString();
    1854           5 :             if (osFilterId.empty())
    1855             :             {
    1856           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Missing filter id");
    1857           1 :                 return nullptr;
    1858             :             }
    1859             :             const auto psFilterCompressor =
    1860           4 :                 CPLGetCompressor(osFilterId.c_str());
    1861             :             const auto psFilterDecompressor =
    1862           4 :                 CPLGetDecompressor(osFilterId.c_str());
    1863           4 :             if (psFilterCompressor == nullptr ||
    1864             :                 psFilterDecompressor == nullptr)
    1865             :             {
    1866           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Filter %s not handled",
    1867             :                          osFilterId.c_str());
    1868           0 :                 return nullptr;
    1869             :             }
    1870             :         }
    1871             :     }
    1872             :     else
    1873             :     {
    1874           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid filters");
    1875           2 :         return nullptr;
    1876             :     }
    1877             : 
    1878         597 :     auto poArray = ZarrV2Array::Create(m_poSharedResource, GetFullName(),
    1879             :                                        osArrayName, aoDims, oType, aoDtypeElts,
    1880        1194 :                                        anBlockSize, bFortranOrder);
    1881         597 :     if (!poArray)
    1882           1 :         return nullptr;
    1883         596 :     poArray->SetCompressorJson(oCompressor);
    1884         596 :     poArray->SetUpdatable(m_bUpdatable);  // must be set before SetAttributes()
    1885         596 :     poArray->SetFilename(osZarrayFilename);
    1886         596 :     poArray->SetDimSeparator(osDimSeparator);
    1887         596 :     poArray->SetCompressorDecompressor(osDecompressorId, psCompressor,
    1888             :                                        psDecompressor);
    1889         596 :     poArray->SetFilters(oFiltersArray);
    1890         596 :     if (!abyNoData.empty())
    1891             :     {
    1892         103 :         poArray->RegisterNoDataValue(abyNoData.data());
    1893             :     }
    1894             : 
    1895        1788 :     const auto gridMapping = oAttributes["grid_mapping"];
    1896         596 :     if (gridMapping.GetType() == CPLJSONObject::Type::String)
    1897             :     {
    1898           3 :         const std::string gridMappingName = gridMapping.ToString();
    1899           1 :         if (m_oMapMDArrays.find(gridMappingName) == m_oMapMDArrays.end())
    1900             :         {
    1901             :             const std::string osArrayFilenameDim = CPLFormFilenameSafe(
    1902           1 :                 CPLFormFilenameSafe(m_osDirectoryName.c_str(),
    1903             :                                     gridMappingName.c_str(), nullptr)
    1904             :                     .c_str(),
    1905           2 :                 ".zarray", nullptr);
    1906             :             VSIStatBufL sStat;
    1907           1 :             if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
    1908             :             {
    1909           2 :                 CPLJSONDocument oDoc;
    1910           1 :                 if (oDoc.Load(osArrayFilenameDim))
    1911             :                 {
    1912           1 :                     LoadArray(gridMappingName, osArrayFilenameDim,
    1913           2 :                               oDoc.GetRoot(), false, CPLJSONObject());
    1914             :                 }
    1915             :             }
    1916             :         }
    1917             :     }
    1918             : 
    1919         596 :     poArray->ParseSpecialAttributes(m_pSelf.lock(), oAttributes);
    1920         596 :     poArray->SetAttributes(oAttributes);
    1921         596 :     poArray->SetDtype(oDtype);
    1922         596 :     RegisterArray(poArray);
    1923             : 
    1924             :     // If this is an indexing variable, attach it to the dimension.
    1925         596 :     if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
    1926             :     {
    1927         136 :         auto oIter = m_oMapDimensions.find(poArray->GetName());
    1928         136 :         if (oIter != m_oMapDimensions.end())
    1929             :         {
    1930         136 :             oIter->second->SetIndexingVariable(poArray);
    1931             :         }
    1932             :     }
    1933             : 
    1934         596 :     if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
    1935             :             "CACHE_TILE_PRESENCE", "NO")))
    1936             :     {
    1937           2 :         poArray->CacheTilePresence();
    1938             :     }
    1939             : 
    1940         596 :     return poArray;
    1941             : }

Generated by: LCOV version 1.14