LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v2_array.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 895 973 92.0 %
Date: 2025-03-25 20:12:57 Functions: 28 30 93.3 %

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

Generated by: LCOV version 1.14