LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v2_array.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 924 1001 92.3 %
Date: 2025-10-22 21:46:17 Functions: 29 31 93.5 %

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

Generated by: LCOV version 1.14