LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v3_array.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 663 776 85.4 %
Date: 2025-10-22 21:46:17 Functions: 24 26 92.3 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Zarr driver
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_float.h"
      14             : #include "cpl_vsi_virtual.h"
      15             : #include "gdal_thread_pool.h"
      16             : #include "zarr.h"
      17             : 
      18             : #include <algorithm>
      19             : #include <cassert>
      20             : #include <cmath>
      21             : #include <cstdlib>
      22             : #include <limits>
      23             : #include <map>
      24             : #include <set>
      25             : 
      26             : /************************************************************************/
      27             : /*                       ZarrV3Array::ZarrV3Array()                     */
      28             : /************************************************************************/
      29             : 
      30         296 : ZarrV3Array::ZarrV3Array(
      31             :     const std::shared_ptr<ZarrSharedResource> &poSharedResource,
      32             :     const std::string &osParentName, const std::string &osName,
      33             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
      34             :     const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
      35         296 :     const std::vector<GUInt64> &anBlockSize)
      36             :     : GDALAbstractMDArray(osParentName, osName),
      37             :       ZarrArray(poSharedResource, osParentName, osName, aoDims, oType,
      38         296 :                 aoDtypeElts, anBlockSize)
      39             : {
      40         296 : }
      41             : 
      42             : /************************************************************************/
      43             : /*                         ZarrV3Array::Create()                        */
      44             : /************************************************************************/
      45             : 
      46             : std::shared_ptr<ZarrV3Array>
      47         296 : ZarrV3Array::Create(const std::shared_ptr<ZarrSharedResource> &poSharedResource,
      48             :                     const std::string &osParentName, const std::string &osName,
      49             :                     const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
      50             :                     const GDALExtendedDataType &oType,
      51             :                     const std::vector<DtypeElt> &aoDtypeElts,
      52             :                     const std::vector<GUInt64> &anBlockSize)
      53             : {
      54             :     auto arr = std::shared_ptr<ZarrV3Array>(
      55             :         new ZarrV3Array(poSharedResource, osParentName, osName, aoDims, oType,
      56         592 :                         aoDtypeElts, anBlockSize));
      57         296 :     if (arr->m_nTotalTileCount == 0)
      58           1 :         return nullptr;
      59         295 :     arr->SetSelf(arr);
      60             : 
      61         295 :     return arr;
      62             : }
      63             : 
      64             : /************************************************************************/
      65             : /*                             ~ZarrV3Array()                           */
      66             : /************************************************************************/
      67             : 
      68         592 : ZarrV3Array::~ZarrV3Array()
      69             : {
      70         296 :     ZarrV3Array::Flush();
      71         592 : }
      72             : 
      73             : /************************************************************************/
      74             : /*                                Flush()                               */
      75             : /************************************************************************/
      76             : 
      77        1265 : bool ZarrV3Array::Flush()
      78             : {
      79        1265 :     if (!m_bValid)
      80           4 :         return true;
      81             : 
      82        1261 :     bool ret = ZarrV3Array::FlushDirtyTile();
      83             : 
      84        1261 :     if (!m_aoDims.empty())
      85             :     {
      86        2926 :         for (const auto &poDim : m_aoDims)
      87             :         {
      88             :             const auto poZarrDim =
      89        1960 :                 dynamic_cast<const ZarrDimension *>(poDim.get());
      90        1960 :             if (poZarrDim && poZarrDim->IsXArrayDimension())
      91             :             {
      92        1801 :                 if (poZarrDim->IsModified())
      93           8 :                     m_bDefinitionModified = true;
      94             :             }
      95             :             else
      96             :             {
      97         159 :                 break;
      98             :             }
      99             :         }
     100             :     }
     101             : 
     102        1261 :     CPLJSONObject oAttrs;
     103        2482 :     if (m_oAttrGroup.IsModified() || m_bUnitModified || m_bOffsetModified ||
     104        2482 :         m_bScaleModified || m_bSRSModified)
     105             :     {
     106          40 :         m_bNew = false;
     107             : 
     108          40 :         oAttrs = SerializeSpecialAttributes();
     109             : 
     110          40 :         m_bDefinitionModified = true;
     111             :     }
     112             : 
     113        1261 :     if (m_bDefinitionModified)
     114             :     {
     115         159 :         if (!Serialize(oAttrs))
     116           1 :             ret = false;
     117         159 :         m_bDefinitionModified = false;
     118             :     }
     119             : 
     120        1261 :     return ret;
     121             : }
     122             : 
     123             : /************************************************************************/
     124             : /*                    ZarrV3Array::Serialize()                          */
     125             : /************************************************************************/
     126             : 
     127         159 : bool ZarrV3Array::Serialize(const CPLJSONObject &oAttrs)
     128             : {
     129         318 :     CPLJSONDocument oDoc;
     130         318 :     CPLJSONObject oRoot = oDoc.GetRoot();
     131             : 
     132         159 :     oRoot.Add("zarr_format", 3);
     133         159 :     oRoot.Add("node_type", "array");
     134             : 
     135         318 :     CPLJSONArray oShape;
     136         407 :     for (const auto &poDim : m_aoDims)
     137             :     {
     138         248 :         oShape.Add(static_cast<GInt64>(poDim->GetSize()));
     139             :     }
     140         159 :     oRoot.Add("shape", oShape);
     141             : 
     142         159 :     oRoot.Add("data_type", m_dtype.ToString());
     143             : 
     144             :     {
     145         318 :         CPLJSONObject oChunkGrid;
     146         159 :         oRoot.Add("chunk_grid", oChunkGrid);
     147         159 :         oChunkGrid.Add("name", "regular");
     148         318 :         CPLJSONObject oConfiguration;
     149         159 :         oChunkGrid.Add("configuration", oConfiguration);
     150         159 :         CPLJSONArray oChunks;
     151         407 :         for (const auto nBlockSize : m_anBlockSize)
     152             :         {
     153         248 :             oChunks.Add(static_cast<GInt64>(nBlockSize));
     154             :         }
     155         159 :         oConfiguration.Add("chunk_shape", oChunks);
     156             :     }
     157             : 
     158             :     {
     159         318 :         CPLJSONObject oChunkKeyEncoding;
     160         159 :         oRoot.Add("chunk_key_encoding", oChunkKeyEncoding);
     161         159 :         oChunkKeyEncoding.Add("name", m_bV2ChunkKeyEncoding ? "v2" : "default");
     162         159 :         CPLJSONObject oConfiguration;
     163         159 :         oChunkKeyEncoding.Add("configuration", oConfiguration);
     164         159 :         oConfiguration.Add("separator", m_osDimSeparator);
     165             :     }
     166             : 
     167         159 :     if (m_pabyNoData == nullptr)
     168             :     {
     169         269 :         if (m_oType.GetNumericDataType() == GDT_Float16 ||
     170         269 :             m_oType.GetNumericDataType() == GDT_Float32 ||
     171         127 :             m_oType.GetNumericDataType() == GDT_Float64)
     172             :         {
     173          17 :             oRoot.Add("fill_value", "NaN");
     174             :         }
     175             :         else
     176             :         {
     177         118 :             oRoot.AddNull("fill_value");
     178             :         }
     179             :     }
     180             :     else
     181             :     {
     182          48 :         if (m_oType.GetNumericDataType() == GDT_CFloat16 ||
     183          48 :             m_oType.GetNumericDataType() == GDT_CFloat32 ||
     184          16 :             m_oType.GetNumericDataType() == GDT_CFloat64)
     185             :         {
     186             :             double adfNoDataValue[2];
     187          16 :             GDALCopyWords(m_pabyNoData, m_oType.GetNumericDataType(), 0,
     188             :                           adfNoDataValue, GDT_CFloat64, 0, 1);
     189          16 :             CPLJSONArray oArray;
     190          48 :             for (int i = 0; i < 2; ++i)
     191             :             {
     192          32 :                 if (std::isnan(adfNoDataValue[i]))
     193           6 :                     oArray.Add("NaN");
     194          26 :                 else if (adfNoDataValue[i] ==
     195          26 :                          std::numeric_limits<double>::infinity())
     196           4 :                     oArray.Add("Infinity");
     197          44 :                 else if (adfNoDataValue[i] ==
     198          22 :                          -std::numeric_limits<double>::infinity())
     199           4 :                     oArray.Add("-Infinity");
     200             :                 else
     201          18 :                     oArray.Add(adfNoDataValue[i]);
     202             :             }
     203          16 :             oRoot.Add("fill_value", oArray);
     204             :         }
     205             :         else
     206             :         {
     207           8 :             SerializeNumericNoData(oRoot);
     208             :         }
     209             :     }
     210             : 
     211         159 :     if (m_poCodecs)
     212             :     {
     213         143 :         oRoot.Add("codecs", m_poCodecs->GetJSon());
     214             :     }
     215             : 
     216         159 :     oRoot.Add("attributes", oAttrs);
     217             : 
     218             :     // Set dimension_names
     219         159 :     if (!m_aoDims.empty())
     220             :     {
     221         280 :         CPLJSONArray oDimensions;
     222         372 :         for (const auto &poDim : m_aoDims)
     223             :         {
     224             :             const auto poZarrDim =
     225         248 :                 dynamic_cast<const ZarrDimension *>(poDim.get());
     226         248 :             if (poZarrDim && poZarrDim->IsXArrayDimension())
     227             :             {
     228         232 :                 oDimensions.Add(poDim->GetName());
     229             :             }
     230             :             else
     231             :             {
     232          16 :                 oDimensions = CPLJSONArray();
     233          16 :                 break;
     234             :             }
     235             :         }
     236         140 :         if (oDimensions.Size() > 0)
     237             :         {
     238         124 :             oRoot.Add("dimension_names", oDimensions);
     239             :         }
     240             :     }
     241             : 
     242             :     // TODO: codecs
     243             : 
     244         318 :     return oDoc.Save(m_osFilename);
     245             : }
     246             : 
     247             : /************************************************************************/
     248             : /*                  ZarrV3Array::NeedDecodedBuffer()                    */
     249             : /************************************************************************/
     250             : 
     251       10998 : bool ZarrV3Array::NeedDecodedBuffer() const
     252             : {
     253       21992 :     for (const auto &elt : m_aoDtypeElts)
     254             :     {
     255       10992 :         if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative)
     256             :         {
     257           0 :             return true;
     258             :         }
     259             :     }
     260       10993 :     return false;
     261             : }
     262             : 
     263             : /************************************************************************/
     264             : /*               ZarrV3Array::AllocateWorkingBuffers()                  */
     265             : /************************************************************************/
     266             : 
     267         168 : bool ZarrV3Array::AllocateWorkingBuffers() const
     268             : {
     269         168 :     if (m_bAllocateWorkingBuffersDone)
     270           5 :         return m_bWorkingBuffersOK;
     271             : 
     272         163 :     m_bAllocateWorkingBuffersDone = true;
     273             : 
     274         163 :     size_t nSizeNeeded = m_nTileSize;
     275         163 :     if (NeedDecodedBuffer())
     276             :     {
     277           0 :         size_t nDecodedBufferSize = m_oType.GetSize();
     278           0 :         for (const auto &nBlockSize : m_anBlockSize)
     279             :         {
     280           0 :             if (nDecodedBufferSize > std::numeric_limits<size_t>::max() /
     281           0 :                                          static_cast<size_t>(nBlockSize))
     282             :             {
     283           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
     284           0 :                 return false;
     285             :             }
     286           0 :             nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
     287             :         }
     288           0 :         if (nSizeNeeded >
     289           0 :             std::numeric_limits<size_t>::max() - nDecodedBufferSize)
     290             :         {
     291           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
     292           0 :             return false;
     293             :         }
     294           0 :         nSizeNeeded += nDecodedBufferSize;
     295             :     }
     296             : 
     297             :     // Reserve a buffer for tile content
     298         163 :     if (nSizeNeeded > 1024 * 1024 * 1024 &&
     299           0 :         !CPLTestBool(CPLGetConfigOption("ZARR_ALLOW_BIG_TILE_SIZE", "NO")))
     300             :     {
     301           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     302             :                  "Zarr tile allocation would require " CPL_FRMT_GUIB " bytes. "
     303             :                  "By default the driver limits to 1 GB. To allow that memory "
     304             :                  "allocation, set the ZARR_ALLOW_BIG_TILE_SIZE configuration "
     305             :                  "option to YES.",
     306             :                  static_cast<GUIntBig>(nSizeNeeded));
     307           0 :         return false;
     308             :     }
     309             : 
     310         163 :     m_bWorkingBuffersOK =
     311         163 :         AllocateWorkingBuffers(m_abyRawTileData, m_abyDecodedTileData);
     312         163 :     return m_bWorkingBuffersOK;
     313             : }
     314             : 
     315       10834 : bool ZarrV3Array::AllocateWorkingBuffers(
     316             :     ZarrByteVectorQuickResize &abyRawTileData,
     317             :     ZarrByteVectorQuickResize &abyDecodedTileData) const
     318             : {
     319             :     // This method should NOT modify any ZarrArray member, as it is going to
     320             :     // be called concurrently from several threads.
     321             : 
     322             :     // Set those #define to avoid accidental use of some global variables
     323             : #define m_abyRawTileData cannot_use_here
     324             : #define m_abyDecodedTileData cannot_use_here
     325             : 
     326             :     try
     327             :     {
     328       10834 :         abyRawTileData.resize(m_nTileSize);
     329             :     }
     330           0 :     catch (const std::bad_alloc &e)
     331             :     {
     332           0 :         CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
     333           0 :         return false;
     334             :     }
     335             : 
     336       10834 :     if (NeedDecodedBuffer())
     337             :     {
     338           0 :         size_t nDecodedBufferSize = m_oType.GetSize();
     339           0 :         for (const auto &nBlockSize : m_anBlockSize)
     340             :         {
     341           0 :             nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
     342             :         }
     343             :         try
     344             :         {
     345           0 :             abyDecodedTileData.resize(nDecodedBufferSize);
     346             :         }
     347           0 :         catch (const std::bad_alloc &e)
     348             :         {
     349           0 :             CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
     350           0 :             return false;
     351             :         }
     352             :     }
     353             : 
     354       10831 :     return true;
     355             : #undef m_abyRawTileData
     356             : #undef m_abyDecodedTileData
     357             : }
     358             : 
     359             : /************************************************************************/
     360             : /*                      ZarrV3Array::LoadTileData()                     */
     361             : /************************************************************************/
     362             : 
     363         426 : bool ZarrV3Array::LoadTileData(const uint64_t *tileIndices,
     364             :                                bool &bMissingTileOut) const
     365             : {
     366         426 :     return LoadTileData(tileIndices,
     367             :                         false,  // use mutex
     368         426 :                         m_poCodecs.get(), m_abyRawTileData,
     369         852 :                         m_abyDecodedTileData, bMissingTileOut);
     370             : }
     371             : 
     372       11097 : bool ZarrV3Array::LoadTileData(const uint64_t *tileIndices, bool bUseMutex,
     373             :                                ZarrV3CodecSequence *poCodecs,
     374             :                                ZarrByteVectorQuickResize &abyRawTileData,
     375             :                                ZarrByteVectorQuickResize &abyDecodedTileData,
     376             :                                bool &bMissingTileOut) const
     377             : {
     378             :     // This method should NOT modify any ZarrArray member, as it is going to
     379             :     // be called concurrently from several threads.
     380             : 
     381             :     // Set those #define to avoid accidental use of some global variables
     382             : #define m_abyRawTileData cannot_use_here
     383             : #define m_abyDecodedTileData cannot_use_here
     384             : #define m_poCodecs cannot_use_here
     385             : 
     386       11097 :     bMissingTileOut = false;
     387             : 
     388       22128 :     std::string osFilename = BuildTileFilename(tileIndices);
     389             : 
     390             :     // For network file systems, get the streaming version of the filename,
     391             :     // as we don't need arbitrary seeking in the file
     392       11092 :     osFilename = VSIFileManager::GetHandler(osFilename.c_str())
     393       11098 :                      ->GetStreamingFilename(osFilename);
     394             : 
     395             :     // First if we have a tile presence cache, check tile presence from it
     396             :     bool bEarlyRet;
     397       11091 :     if (bUseMutex)
     398             :     {
     399       10665 :         std::lock_guard<std::mutex> oLock(m_oMutex);
     400       10672 :         bEarlyRet = IsTileMissingFromCacheInfo(osFilename, tileIndices);
     401             :     }
     402             :     else
     403             :     {
     404         426 :         bEarlyRet = IsTileMissingFromCacheInfo(osFilename, tileIndices);
     405             :     }
     406       11098 :     if (bEarlyRet)
     407             :     {
     408          13 :         bMissingTileOut = true;
     409          13 :         return true;
     410             :     }
     411       11085 :     VSILFILE *fp = nullptr;
     412             :     // This is the number of files returned in a S3 directory listing operation
     413       11085 :     constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
     414       11085 :     const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
     415             :                                            nullptr};
     416       11085 :     const auto nErrorBefore = CPLGetErrorCounter();
     417       22164 :     if ((m_osDimSeparator == "/" && !m_anBlockSize.empty() &&
     418       33241 :          m_anBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
     419       11076 :         (m_osDimSeparator != "/" &&
     420           5 :          m_nTotalTileCount > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING))
     421             :     {
     422             :         // Avoid issuing ReadDir() when a lot of files are expected
     423             :         CPLConfigOptionSetter optionSetter("GDAL_DISABLE_READDIR_ON_OPEN",
     424           0 :                                            "YES", true);
     425           0 :         fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
     426             :     }
     427             :     else
     428             :     {
     429       11078 :         fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
     430             :     }
     431       11043 :     if (fp == nullptr)
     432             :     {
     433         299 :         if (nErrorBefore != CPLGetErrorCounter())
     434             :         {
     435           0 :             return false;
     436             :         }
     437             :         else
     438             :         {
     439             :             // Missing files are OK and indicate nodata_value
     440         299 :             CPLDebugOnly(ZARR_DEBUG_KEY, "Tile %s missing (=nodata)",
     441             :                          osFilename.c_str());
     442         299 :             bMissingTileOut = true;
     443         299 :             return true;
     444             :         }
     445             :     }
     446             : 
     447       10744 :     bMissingTileOut = false;
     448             : 
     449       10744 :     CPLAssert(abyRawTileData.capacity() >= m_nTileSize);
     450             :     // should not fail
     451       10670 :     abyRawTileData.resize(m_nTileSize);
     452             : 
     453       10663 :     bool bRet = true;
     454       10663 :     size_t nRawDataSize = abyRawTileData.size();
     455       10627 :     if (poCodecs == nullptr)
     456             :     {
     457           6 :         nRawDataSize = VSIFReadL(&abyRawTileData[0], 1, nRawDataSize, fp);
     458             :     }
     459             :     else
     460             :     {
     461       10621 :         VSIFSeekL(fp, 0, SEEK_END);
     462       10726 :         const auto nSize = VSIFTellL(fp);
     463       10652 :         VSIFSeekL(fp, 0, SEEK_SET);
     464       10700 :         if (nSize > static_cast<vsi_l_offset>(std::numeric_limits<int>::max()))
     465             :         {
     466           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Too large tile %s",
     467             :                      osFilename.c_str());
     468           0 :             bRet = false;
     469             :         }
     470             :         else
     471             :         {
     472             :             try
     473             :             {
     474       10603 :                 abyRawTileData.resize(static_cast<size_t>(nSize));
     475             :             }
     476           0 :             catch (const std::exception &)
     477             :             {
     478           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory,
     479             :                          "Cannot allocate memory for tile %s",
     480             :                          osFilename.c_str());
     481           0 :                 bRet = false;
     482             :             }
     483             : 
     484       21193 :             if (bRet && (abyRawTileData.empty() ||
     485       10591 :                          VSIFReadL(&abyRawTileData[0], 1, abyRawTileData.size(),
     486       10592 :                                    fp) != abyRawTileData.size()))
     487             :             {
     488           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     489             :                          "Could not read tile %s correctly",
     490             :                          osFilename.c_str());
     491           0 :                 bRet = false;
     492             :             }
     493             :             else
     494             :             {
     495       10588 :                 if (!poCodecs->Decode(abyRawTileData))
     496             :                 {
     497           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     498             :                              "Decompression of tile %s failed",
     499             :                              osFilename.c_str());
     500           0 :                     bRet = false;
     501             :                 }
     502             :             }
     503             :         }
     504             :     }
     505       10682 :     VSIFCloseL(fp);
     506       10711 :     if (!bRet)
     507           0 :         return false;
     508             : 
     509       10711 :     if (nRawDataSize != abyRawTileData.size())
     510             :     {
     511           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     512             :                  "Decompressed tile %s has not expected size. "
     513             :                  "Got %u instead of %u",
     514             :                  osFilename.c_str(),
     515           0 :                  static_cast<unsigned>(abyRawTileData.size()),
     516             :                  static_cast<unsigned>(nRawDataSize));
     517           0 :         return false;
     518             :     }
     519             : 
     520       10659 :     if (!abyDecodedTileData.empty())
     521             :     {
     522             :         const size_t nSourceSize =
     523           0 :             m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
     524           0 :         const auto nDTSize = m_oType.GetSize();
     525           0 :         const size_t nValues = abyDecodedTileData.size() / nDTSize;
     526           0 :         CPLAssert(nValues == m_nTileSize / nSourceSize);
     527           0 :         const GByte *pSrc = abyRawTileData.data();
     528           0 :         GByte *pDst = &abyDecodedTileData[0];
     529         121 :         for (size_t i = 0; i < nValues;
     530           0 :              i++, pSrc += nSourceSize, pDst += nDTSize)
     531             :         {
     532           0 :             DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
     533             :         }
     534             :     }
     535             : 
     536       10719 :     return true;
     537             : 
     538             : #undef m_abyRawTileData
     539             : #undef m_abyDecodedTileData
     540             : #undef m_poCodecs
     541             : }
     542             : 
     543             : /************************************************************************/
     544             : /*                      ZarrV3Array::IAdviseRead()                      */
     545             : /************************************************************************/
     546             : 
     547           6 : bool ZarrV3Array::IAdviseRead(const GUInt64 *arrayStartIdx, const size_t *count,
     548             :                               CSLConstList papszOptions) const
     549             : {
     550          12 :     std::vector<uint64_t> anIndicesCur;
     551           6 :     int nThreadsMax = 0;
     552          12 :     std::vector<uint64_t> anReqTilesIndices;
     553           6 :     size_t nReqTiles = 0;
     554           6 :     if (!IAdviseReadCommon(arrayStartIdx, count, papszOptions, anIndicesCur,
     555             :                            nThreadsMax, anReqTilesIndices, nReqTiles))
     556             :     {
     557           2 :         return false;
     558             :     }
     559           4 :     if (nThreadsMax <= 1)
     560             :     {
     561           0 :         return true;
     562             :     }
     563             : 
     564             :     const int nThreads =
     565           4 :         static_cast<int>(std::min(static_cast<size_t>(nThreadsMax), nReqTiles));
     566             : 
     567           4 :     CPLWorkerThreadPool *wtp = GDALGetGlobalThreadPool(nThreadsMax);
     568           4 :     if (wtp == nullptr)
     569           0 :         return false;
     570             : 
     571             :     struct JobStruct
     572             :     {
     573             :         JobStruct() = default;
     574             : 
     575             :         JobStruct(const JobStruct &) = delete;
     576             :         JobStruct &operator=(const JobStruct &) = delete;
     577             : 
     578             :         JobStruct(JobStruct &&) = default;
     579             :         JobStruct &operator=(JobStruct &&) = default;
     580             : 
     581             :         const ZarrV3Array *poArray = nullptr;
     582             :         bool *pbGlobalStatus = nullptr;
     583             :         int *pnRemainingThreads = nullptr;
     584             :         const std::vector<uint64_t> *panReqTilesIndices = nullptr;
     585             :         size_t nFirstIdx = 0;
     586             :         size_t nLastIdxNotIncluded = 0;
     587             :     };
     588             : 
     589           4 :     std::vector<JobStruct> asJobStructs;
     590             : 
     591           4 :     bool bGlobalStatus = true;
     592           4 :     int nRemainingThreads = nThreads;
     593             :     // Check for very highly overflow in below loop
     594           4 :     assert(static_cast<size_t>(nThreads) <
     595             :            std::numeric_limits<size_t>::max() / nReqTiles);
     596             : 
     597             :     // Setup jobs
     598          20 :     for (int i = 0; i < nThreads; i++)
     599             :     {
     600          16 :         JobStruct jobStruct;
     601          16 :         jobStruct.poArray = this;
     602          16 :         jobStruct.pbGlobalStatus = &bGlobalStatus;
     603          16 :         jobStruct.pnRemainingThreads = &nRemainingThreads;
     604          16 :         jobStruct.panReqTilesIndices = &anReqTilesIndices;
     605          16 :         jobStruct.nFirstIdx = static_cast<size_t>(i * nReqTiles / nThreads);
     606          16 :         jobStruct.nLastIdxNotIncluded = std::min(
     607          16 :             static_cast<size_t>((i + 1) * nReqTiles / nThreads), nReqTiles);
     608          16 :         asJobStructs.emplace_back(std::move(jobStruct));
     609             :     }
     610             : 
     611          16 :     const auto JobFunc = [](void *pThreadData)
     612             :     {
     613          16 :         const JobStruct *jobStruct =
     614             :             static_cast<const JobStruct *>(pThreadData);
     615             : 
     616          16 :         const auto poArray = jobStruct->poArray;
     617          16 :         const auto &aoDims = poArray->GetDimensions();
     618          16 :         const size_t l_nDims = poArray->GetDimensionCount();
     619          16 :         ZarrByteVectorQuickResize abyRawTileData;
     620          16 :         ZarrByteVectorQuickResize abyDecodedTileData;
     621           0 :         std::unique_ptr<ZarrV3CodecSequence> poCodecs;
     622          16 :         if (poArray->m_poCodecs)
     623             :         {
     624          16 :             std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     625          16 :             poCodecs = poArray->m_poCodecs->Clone();
     626             :         }
     627             : 
     628       10688 :         for (size_t iReq = jobStruct->nFirstIdx;
     629       10688 :              iReq < jobStruct->nLastIdxNotIncluded; ++iReq)
     630             :         {
     631             :             // Check if we must early exit
     632             :             {
     633       10671 :                 std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     634       10672 :                 if (!(*jobStruct->pbGlobalStatus))
     635           0 :                     return;
     636             :             }
     637             : 
     638             :             const uint64_t *tileIndices =
     639       10672 :                 jobStruct->panReqTilesIndices->data() + iReq * l_nDims;
     640             : 
     641       10672 :             uint64_t nTileIdx = 0;
     642       32015 :             for (size_t j = 0; j < l_nDims; ++j)
     643             :             {
     644       21343 :                 if (j > 0)
     645       10672 :                     nTileIdx *= aoDims[j - 1]->GetSize();
     646       21343 :                 nTileIdx += tileIndices[j];
     647             :             }
     648             : 
     649       10672 :             if (!poArray->AllocateWorkingBuffers(abyRawTileData,
     650             :                                                  abyDecodedTileData))
     651             :             {
     652           0 :                 std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     653           0 :                 *jobStruct->pbGlobalStatus = false;
     654           0 :                 break;
     655             :             }
     656             : 
     657       10670 :             bool bIsEmpty = false;
     658       10670 :             bool success = poArray->LoadTileData(tileIndices,
     659             :                                                  true,  // use mutex
     660             :                                                  poCodecs.get(), abyRawTileData,
     661             :                                                  abyDecodedTileData, bIsEmpty);
     662             : 
     663       10620 :             std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     664       10672 :             if (!success)
     665             :             {
     666           0 :                 *jobStruct->pbGlobalStatus = false;
     667           0 :                 break;
     668             :             }
     669             : 
     670       21344 :             CachedTile cachedTile;
     671       10672 :             if (!bIsEmpty)
     672             :             {
     673       10670 :                 if (!abyDecodedTileData.empty())
     674           0 :                     std::swap(cachedTile.abyDecoded, abyDecodedTileData);
     675             :                 else
     676       10670 :                     std::swap(cachedTile.abyDecoded, abyRawTileData);
     677             :             }
     678       10672 :             poArray->m_oMapTileIndexToCachedTile[nTileIdx] =
     679       21344 :                 std::move(cachedTile);
     680             :         }
     681             : 
     682          17 :         std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
     683          16 :         (*jobStruct->pnRemainingThreads)--;
     684             :     };
     685             : 
     686             :     // Start jobs
     687          20 :     for (int i = 0; i < nThreads; i++)
     688             :     {
     689          16 :         if (!wtp->SubmitJob(JobFunc, &asJobStructs[i]))
     690             :         {
     691           0 :             std::lock_guard<std::mutex> oLock(m_oMutex);
     692           0 :             bGlobalStatus = false;
     693           0 :             nRemainingThreads = i;
     694           0 :             break;
     695             :         }
     696             :     }
     697             : 
     698             :     // Wait for all jobs to be finished
     699             :     while (true)
     700             :     {
     701             :         {
     702          16 :             std::lock_guard<std::mutex> oLock(m_oMutex);
     703          16 :             if (nRemainingThreads == 0)
     704           4 :                 break;
     705             :         }
     706          12 :         wtp->WaitEvent();
     707          12 :     }
     708             : 
     709           4 :     return bGlobalStatus;
     710             : }
     711             : 
     712             : /************************************************************************/
     713             : /*                    ZarrV3Array::FlushDirtyTile()                     */
     714             : /************************************************************************/
     715             : 
     716       12139 : bool ZarrV3Array::FlushDirtyTile() const
     717             : {
     718       12139 :     if (!m_bDirtyTile)
     719        1390 :         return true;
     720       10749 :     m_bDirtyTile = false;
     721             : 
     722       21498 :     std::string osFilename = BuildTileFilename(m_anCachedTiledIndices.data());
     723             : 
     724             :     const size_t nSourceSize =
     725       10749 :         m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
     726             :     const auto &abyTile =
     727       10749 :         m_abyDecodedTileData.empty() ? m_abyRawTileData : m_abyDecodedTileData;
     728             : 
     729       10749 :     if (IsEmptyTile(abyTile))
     730             :     {
     731           2 :         m_bCachedTiledEmpty = true;
     732             : 
     733             :         VSIStatBufL sStat;
     734           2 :         if (VSIStatL(osFilename.c_str(), &sStat) == 0)
     735             :         {
     736           0 :             CPLDebugOnly(ZARR_DEBUG_KEY,
     737             :                          "Deleting tile %s that has now empty content",
     738             :                          osFilename.c_str());
     739           0 :             return VSIUnlink(osFilename.c_str()) == 0;
     740             :         }
     741           2 :         return true;
     742             :     }
     743             : 
     744       10747 :     if (!m_abyDecodedTileData.empty())
     745             :     {
     746           0 :         const size_t nDTSize = m_oType.GetSize();
     747           0 :         const size_t nValues = m_abyDecodedTileData.size() / nDTSize;
     748           0 :         GByte *pDst = &m_abyRawTileData[0];
     749           0 :         const GByte *pSrc = m_abyDecodedTileData.data();
     750           0 :         for (size_t i = 0; i < nValues;
     751           0 :              i++, pDst += nSourceSize, pSrc += nDTSize)
     752             :         {
     753           0 :             EncodeElt(m_aoDtypeElts, pSrc, pDst);
     754             :         }
     755             :     }
     756             : 
     757       10747 :     const size_t nSizeBefore = m_abyRawTileData.size();
     758       10747 :     if (m_poCodecs)
     759             :     {
     760       10747 :         if (!m_poCodecs->Encode(m_abyRawTileData))
     761             :         {
     762           0 :             m_abyRawTileData.resize(nSizeBefore);
     763           0 :             return false;
     764             :         }
     765             :     }
     766             : 
     767       10747 :     if (m_osDimSeparator == "/")
     768             :     {
     769       10747 :         std::string osDir = CPLGetDirnameSafe(osFilename.c_str());
     770             :         VSIStatBufL sStat;
     771       10747 :         if (VSIStatL(osDir.c_str(), &sStat) != 0)
     772             :         {
     773         209 :             if (VSIMkdirRecursive(osDir.c_str(), 0755) != 0)
     774             :             {
     775           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     776             :                          "Cannot create directory %s", osDir.c_str());
     777           0 :                 m_abyRawTileData.resize(nSizeBefore);
     778           0 :                 return false;
     779             :             }
     780             :         }
     781             :     }
     782             : 
     783       10747 :     VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
     784       10747 :     if (fp == nullptr)
     785             :     {
     786           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot create tile %s",
     787             :                  osFilename.c_str());
     788           0 :         m_abyRawTileData.resize(nSizeBefore);
     789           0 :         return false;
     790             :     }
     791             : 
     792       10747 :     bool bRet = true;
     793       10747 :     const size_t nRawDataSize = m_abyRawTileData.size();
     794       10747 :     if (VSIFWriteL(m_abyRawTileData.data(), 1, nRawDataSize, fp) !=
     795             :         nRawDataSize)
     796             :     {
     797           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     798             :                  "Could not write tile %s correctly", osFilename.c_str());
     799           0 :         bRet = false;
     800             :     }
     801       10747 :     VSIFCloseL(fp);
     802             : 
     803       10747 :     m_abyRawTileData.resize(nSizeBefore);
     804             : 
     805       10747 :     return bRet;
     806             : }
     807             : 
     808             : /************************************************************************/
     809             : /*                          BuildTileFilename()                         */
     810             : /************************************************************************/
     811             : 
     812       21851 : std::string ZarrV3Array::BuildTileFilename(const uint64_t *tileIndices) const
     813             : {
     814       21851 :     if (m_aoDims.empty())
     815             :     {
     816             :         return CPLFormFilenameSafe(
     817           0 :             CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
     818           0 :             m_bV2ChunkKeyEncoding ? "0" : "c", nullptr);
     819             :     }
     820             :     else
     821             :     {
     822       43690 :         std::string osFilename(CPLGetDirnameSafe(m_osFilename.c_str()));
     823       21849 :         osFilename += '/';
     824       21849 :         if (!m_bV2ChunkKeyEncoding)
     825             :         {
     826       21841 :             osFilename += 'c';
     827             :         }
     828       65524 :         for (size_t i = 0; i < m_aoDims.size(); ++i)
     829             :         {
     830       43690 :             if (i > 0 || !m_bV2ChunkKeyEncoding)
     831       43684 :                 osFilename += m_osDimSeparator;
     832       43692 :             osFilename += std::to_string(tileIndices[i]);
     833             :         }
     834       21837 :         return osFilename;
     835             :     }
     836             : }
     837             : 
     838             : /************************************************************************/
     839             : /*                          GetDataDirectory()                          */
     840             : /************************************************************************/
     841             : 
     842           2 : std::string ZarrV3Array::GetDataDirectory() const
     843             : {
     844           2 :     return std::string(CPLGetDirnameSafe(m_osFilename.c_str()));
     845             : }
     846             : 
     847             : /************************************************************************/
     848             : /*                        GetTileIndicesFromFilename()                  */
     849             : /************************************************************************/
     850             : 
     851             : CPLStringList
     852           4 : ZarrV3Array::GetTileIndicesFromFilename(const char *pszFilename) const
     853             : {
     854           4 :     if (!m_bV2ChunkKeyEncoding)
     855             :     {
     856           4 :         if (pszFilename[0] != 'c')
     857           2 :             return CPLStringList();
     858           2 :         if (m_osDimSeparator == "/")
     859             :         {
     860           2 :             if (pszFilename[1] != '/' && pszFilename[1] != '\\')
     861           0 :                 return CPLStringList();
     862             :         }
     863           0 :         else if (pszFilename[1] != m_osDimSeparator[0])
     864             :         {
     865           0 :             return CPLStringList();
     866             :         }
     867             :     }
     868             :     return CPLStringList(
     869           2 :         CSLTokenizeString2(pszFilename + (m_bV2ChunkKeyEncoding ? 0 : 2),
     870           4 :                            m_osDimSeparator.c_str(), 0));
     871             : }
     872             : 
     873             : /************************************************************************/
     874             : /*                           ParseDtypeV3()                             */
     875             : /************************************************************************/
     876             : 
     877         189 : static GDALExtendedDataType ParseDtypeV3(const CPLJSONObject &obj,
     878             :                                          std::vector<DtypeElt> &elts)
     879             : {
     880             :     do
     881             :     {
     882         189 :         if (obj.GetType() == CPLJSONObject::Type::String)
     883             :         {
     884         378 :             const auto str = obj.ToString();
     885         189 :             DtypeElt elt;
     886         189 :             GDALDataType eDT = GDT_Unknown;
     887             : 
     888         189 :             if (str == "bool")  // boolean
     889             :             {
     890           0 :                 elt.nativeType = DtypeElt::NativeType::BOOLEAN;
     891           0 :                 eDT = GDT_Byte;
     892             :             }
     893         189 :             else if (str == "int8")
     894             :             {
     895           6 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     896           6 :                 eDT = GDT_Int8;
     897             :             }
     898         183 :             else if (str == "uint8")
     899             :             {
     900          78 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     901          78 :                 eDT = GDT_Byte;
     902             :             }
     903         105 :             else if (str == "int16")
     904             :             {
     905          10 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     906          10 :                 eDT = GDT_Int16;
     907             :             }
     908          95 :             else if (str == "uint16")
     909             :             {
     910           8 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     911           8 :                 eDT = GDT_UInt16;
     912             :             }
     913          87 :             else if (str == "int32")
     914             :             {
     915           7 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     916           7 :                 eDT = GDT_Int32;
     917             :             }
     918          80 :             else if (str == "uint32")
     919             :             {
     920           7 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     921           7 :                 eDT = GDT_UInt32;
     922             :             }
     923          73 :             else if (str == "int64")
     924             :             {
     925           7 :                 elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     926           7 :                 eDT = GDT_Int64;
     927             :             }
     928          66 :             else if (str == "uint64")
     929             :             {
     930           6 :                 elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     931           6 :                 eDT = GDT_UInt64;
     932             :             }
     933          60 :             else if (str == "float16")
     934             :             {
     935             :                 // elt.nativeType = DtypeElt::NativeType::IEEEFP;
     936             :                 // elt.nativeSize = 2;
     937             :                 // elt.gdalTypeIsApproxOfNative = true;
     938             :                 // eDT = GDT_Float32;
     939           3 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
     940           3 :                 elt.nativeSize = 2;
     941           3 :                 eDT = GDT_Float16;
     942             :             }
     943          57 :             else if (str == "float32")
     944             :             {
     945           9 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
     946           9 :                 eDT = GDT_Float32;
     947             :             }
     948          48 :             else if (str == "float64")
     949             :             {
     950          17 :                 elt.nativeType = DtypeElt::NativeType::IEEEFP;
     951          17 :                 eDT = GDT_Float64;
     952             :             }
     953          31 :             else if (str == "complex64")
     954             :             {
     955          15 :                 elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     956          15 :                 eDT = GDT_CFloat32;
     957             :             }
     958          16 :             else if (str == "complex128")
     959             :             {
     960          15 :                 elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     961          15 :                 eDT = GDT_CFloat64;
     962             :             }
     963             :             else
     964           1 :                 break;
     965             : 
     966         188 :             elt.gdalType = GDALExtendedDataType::Create(eDT);
     967         188 :             elt.gdalSize = elt.gdalType.GetSize();
     968         188 :             if (!elt.gdalTypeIsApproxOfNative)
     969         188 :                 elt.nativeSize = elt.gdalSize;
     970             : 
     971         188 :             if (elt.nativeSize > 1)
     972             :             {
     973         104 :                 elt.needByteSwapping = (CPL_IS_LSB == 0);
     974             :             }
     975             : 
     976         188 :             elts.emplace_back(elt);
     977         188 :             return GDALExtendedDataType::Create(eDT);
     978             :         }
     979             :     } while (false);
     980           1 :     CPLError(CE_Failure, CPLE_AppDefined,
     981             :              "Invalid or unsupported format for data_type: %s",
     982           2 :              obj.ToString().c_str());
     983           1 :     return GDALExtendedDataType::Create(GDT_Unknown);
     984             : }
     985             : 
     986             : /************************************************************************/
     987             : /*                    ParseNoDataStringAsDouble()                       */
     988             : /************************************************************************/
     989             : 
     990          40 : static double ParseNoDataStringAsDouble(const std::string &osVal, bool &bOK)
     991             : {
     992          40 :     double dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
     993          40 :     if (osVal == "NaN")
     994             :     {
     995             :         // initialized above
     996             :     }
     997          15 :     else if (osVal == "Infinity" || osVal == "+Infinity")
     998             :     {
     999           5 :         dfNoDataValue = std::numeric_limits<double>::infinity();
    1000             :     }
    1001          10 :     else if (osVal == "-Infinity")
    1002             :     {
    1003           5 :         dfNoDataValue = -std::numeric_limits<double>::infinity();
    1004             :     }
    1005             :     else
    1006             :     {
    1007           5 :         bOK = false;
    1008             :     }
    1009          40 :     return dfNoDataValue;
    1010             : }
    1011             : 
    1012             : /************************************************************************/
    1013             : /*                     ParseNoDataComponent()                           */
    1014             : /************************************************************************/
    1015             : 
    1016             : template <typename T, typename Tint>
    1017          40 : static T ParseNoDataComponent(const CPLJSONObject &oObj, bool &bOK)
    1018             : {
    1019          40 :     if (oObj.GetType() == CPLJSONObject::Type::Integer ||
    1020          62 :         oObj.GetType() == CPLJSONObject::Type::Long ||
    1021          22 :         oObj.GetType() == CPLJSONObject::Type::Double)
    1022             :     {
    1023          22 :         return static_cast<T>(oObj.ToDouble());
    1024             :     }
    1025          18 :     else if (oObj.GetType() == CPLJSONObject::Type::String)
    1026             :     {
    1027          54 :         const auto osVal = oObj.ToString();
    1028          18 :         if (STARTS_WITH(osVal.c_str(), "0x"))
    1029             :         {
    1030           2 :             if (osVal.size() > 2 + 2 * sizeof(T))
    1031             :             {
    1032           0 :                 bOK = false;
    1033           0 :                 return 0;
    1034             :             }
    1035           2 :             Tint nVal = static_cast<Tint>(
    1036           2 :                 std::strtoull(osVal.c_str() + 2, nullptr, 16));
    1037             :             T fVal;
    1038             :             static_assert(sizeof(nVal) == sizeof(fVal),
    1039             :                           "sizeof(nVal) == sizeof(dfVal)");
    1040           2 :             memcpy(&fVal, &nVal, sizeof(nVal));
    1041           2 :             return fVal;
    1042             :         }
    1043             :         else
    1044             :         {
    1045          16 :             return static_cast<T>(ParseNoDataStringAsDouble(osVal, bOK));
    1046             :         }
    1047             :     }
    1048             :     else
    1049             :     {
    1050           0 :         bOK = false;
    1051           0 :         return 0;
    1052             :     }
    1053             : }
    1054             : 
    1055             : /************************************************************************/
    1056             : /*                     ZarrV3Group::LoadArray()                         */
    1057             : /************************************************************************/
    1058             : 
    1059             : std::shared_ptr<ZarrArray>
    1060         202 : ZarrV3Group::LoadArray(const std::string &osArrayName,
    1061             :                        const std::string &osZarrayFilename,
    1062             :                        const CPLJSONObject &oRoot) const
    1063             : {
    1064             :     // Add osZarrayFilename to m_poSharedResource during the scope
    1065             :     // of this function call.
    1066         202 :     ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
    1067         404 :                                                        osZarrayFilename);
    1068         202 :     if (!filenameAdder.ok())
    1069           0 :         return nullptr;
    1070             : 
    1071             :     // Warn about unknown members (the spec suggests to error out, but let be
    1072             :     // a bit more lenient)
    1073        2016 :     for (const auto &oNode : oRoot.GetChildren())
    1074             :     {
    1075        3628 :         const auto osName = oNode.GetName();
    1076        4836 :         if (osName != "zarr_format" && osName != "node_type" &&
    1077        3627 :             osName != "shape" && osName != "chunk_grid" &&
    1078        2421 :             osName != "data_type" && osName != "chunk_key_encoding" &&
    1079        1010 :             osName != "fill_value" &&
    1080             :             // Below are optional
    1081         823 :             osName != "dimension_names" && osName != "codecs" &&
    1082        3565 :             osName != "storage_transformers" && osName != "attributes")
    1083             :         {
    1084           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    1085             :                      "%s array definition contains a unknown member (%s). "
    1086             :                      "Interpretation of the array might be wrong.",
    1087             :                      osZarrayFilename.c_str(), osName.c_str());
    1088             :         }
    1089             :     }
    1090             : 
    1091         606 :     const auto oStorageTransformers = oRoot["storage_transformers"].ToArray();
    1092         202 :     if (oStorageTransformers.Size() > 0)
    1093             :     {
    1094           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1095             :                  "storage_transformers are not supported.");
    1096           1 :         return nullptr;
    1097             :     }
    1098             : 
    1099         603 :     const auto oShape = oRoot["shape"].ToArray();
    1100         201 :     if (!oShape.IsValid())
    1101             :     {
    1102           2 :         CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
    1103           2 :         return nullptr;
    1104             :     }
    1105             : 
    1106             :     // Parse chunk_grid
    1107         597 :     const auto oChunkGrid = oRoot["chunk_grid"];
    1108         199 :     if (oChunkGrid.GetType() != CPLJSONObject::Type::Object)
    1109             :     {
    1110           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1111             :                  "chunk_grid missing or not an object");
    1112           1 :         return nullptr;
    1113             :     }
    1114             : 
    1115         594 :     const auto oChunkGridName = oChunkGrid["name"];
    1116         198 :     if (oChunkGridName.ToString() != "regular")
    1117             :     {
    1118           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1119             :                  "Only chunk_grid.name = regular supported");
    1120           1 :         return nullptr;
    1121             :     }
    1122             : 
    1123         591 :     const auto oChunks = oChunkGrid["configuration"]["chunk_shape"].ToArray();
    1124         197 :     if (!oChunks.IsValid())
    1125             :     {
    1126           1 :         CPLError(
    1127             :             CE_Failure, CPLE_AppDefined,
    1128             :             "chunk_grid.configuration.chunk_shape missing or not an array");
    1129           1 :         return nullptr;
    1130             :     }
    1131             : 
    1132         196 :     if (oShape.Size() != oChunks.Size())
    1133             :     {
    1134           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1135             :                  "shape and chunks arrays are of different size");
    1136           1 :         return nullptr;
    1137             :     }
    1138             : 
    1139             :     // Parse chunk_key_encoding
    1140         585 :     const auto oChunkKeyEncoding = oRoot["chunk_key_encoding"];
    1141         195 :     if (oChunkKeyEncoding.GetType() != CPLJSONObject::Type::Object)
    1142             :     {
    1143           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1144             :                  "chunk_key_encoding missing or not an object");
    1145           1 :         return nullptr;
    1146             :     }
    1147             : 
    1148         388 :     std::string osDimSeparator;
    1149         194 :     bool bV2ChunkKeyEncoding = false;
    1150         582 :     const auto oChunkKeyEncodingName = oChunkKeyEncoding["name"];
    1151         194 :     if (oChunkKeyEncodingName.ToString() == "default")
    1152             :     {
    1153         186 :         osDimSeparator = "/";
    1154             :     }
    1155           8 :     else if (oChunkKeyEncodingName.ToString() == "v2")
    1156             :     {
    1157           7 :         osDimSeparator = ".";
    1158           7 :         bV2ChunkKeyEncoding = true;
    1159             :     }
    1160             :     else
    1161             :     {
    1162           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1163             :                  "Unsupported chunk_key_encoding.name");
    1164           1 :         return nullptr;
    1165             :     }
    1166             : 
    1167             :     {
    1168         386 :         auto oConfiguration = oChunkKeyEncoding["configuration"];
    1169         193 :         if (oConfiguration.GetType() == CPLJSONObject::Type::Object)
    1170             :         {
    1171         286 :             auto oSeparator = oConfiguration["separator"];
    1172         143 :             if (oSeparator.IsValid())
    1173             :             {
    1174         143 :                 osDimSeparator = oSeparator.ToString();
    1175         143 :                 if (osDimSeparator != "/" && osDimSeparator != ".")
    1176             :                 {
    1177           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1178             :                              "Separator can only be '/' or '.'");
    1179           1 :                     return nullptr;
    1180             :                 }
    1181             :             }
    1182             :         }
    1183             :     }
    1184             : 
    1185         576 :     CPLJSONObject oAttributes = oRoot["attributes"];
    1186             : 
    1187             :     // Deep-clone of oAttributes
    1188         192 :     if (oAttributes.IsValid())
    1189             :     {
    1190         134 :         oAttributes = oAttributes.Clone();
    1191             :     }
    1192             : 
    1193         384 :     std::vector<std::shared_ptr<GDALDimension>> aoDims;
    1194         492 :     for (int i = 0; i < oShape.Size(); ++i)
    1195             :     {
    1196         300 :         const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
    1197         300 :         if (nSize == 0)
    1198             :         {
    1199           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
    1200           0 :             return nullptr;
    1201             :         }
    1202         300 :         aoDims.emplace_back(std::make_shared<ZarrDimension>(
    1203         300 :             m_poSharedResource,
    1204         600 :             std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
    1205         600 :             std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
    1206         300 :             nSize));
    1207             :     }
    1208             : 
    1209             :     // Deal with dimension_names
    1210         576 :     const auto dimensionNames = oRoot["dimension_names"];
    1211             : 
    1212         232 :     const auto FindDimension = [this, &aoDims, &osArrayName, &oAttributes](
    1213             :                                    const std::string &osDimName,
    1214        3008 :                                    std::shared_ptr<GDALDimension> &poDim, int i)
    1215             :     {
    1216         232 :         auto oIter = m_oMapDimensions.find(osDimName);
    1217         232 :         if (oIter != m_oMapDimensions.end())
    1218             :         {
    1219           2 :             if (m_bDimSizeInUpdate ||
    1220           1 :                 oIter->second->GetSize() == poDim->GetSize())
    1221             :             {
    1222           1 :                 poDim = oIter->second;
    1223           1 :                 return true;
    1224             :             }
    1225             :             else
    1226             :             {
    1227           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1228             :                          "Size of _ARRAY_DIMENSIONS[%d] different "
    1229             :                          "from the one of shape",
    1230             :                          i);
    1231           0 :                 return false;
    1232             :             }
    1233             :         }
    1234             : 
    1235             :         // Try to load the indexing variable.
    1236             :         // Not in m_oMapMDArrays,
    1237             :         // then stat() the indexing variable.
    1238         458 :         else if (osArrayName != osDimName &&
    1239         458 :                  m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
    1240             :         {
    1241         454 :             std::string osDirName = m_osDirectoryName;
    1242             :             while (true)
    1243             :             {
    1244             :                 const std::string osArrayFilenameDim = CPLFormFilenameSafe(
    1245         938 :                     CPLFormFilenameSafe(osDirName.c_str(), osDimName.c_str(),
    1246             :                                         nullptr)
    1247             :                         .c_str(),
    1248         938 :                     "zarr.json", nullptr);
    1249             :                 VSIStatBufL sStat;
    1250         938 :                 if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
    1251             :                 {
    1252           6 :                     CPLJSONDocument oDoc;
    1253           3 :                     if (oDoc.Load(osArrayFilenameDim))
    1254             :                     {
    1255           3 :                         LoadArray(osDimName, osArrayFilenameDim,
    1256           6 :                                   oDoc.GetRoot());
    1257             :                     }
    1258             :                 }
    1259             :                 else
    1260             :                 {
    1261             :                     // Recurse to upper level for datasets such as
    1262             :                     // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
    1263             :                     std::string osDirNameNew =
    1264         935 :                         CPLGetPathSafe(osDirName.c_str());
    1265         935 :                     if (!osDirNameNew.empty() && osDirNameNew != osDirName)
    1266             :                     {
    1267         711 :                         osDirName = std::move(osDirNameNew);
    1268         711 :                         continue;
    1269             :                     }
    1270             :                 }
    1271         227 :                 break;
    1272         711 :             }
    1273             :         }
    1274             : 
    1275         231 :         oIter = m_oMapDimensions.find(osDimName);
    1276             :         // cppcheck-suppress knownConditionTrueFalse
    1277         234 :         if (oIter != m_oMapDimensions.end() &&
    1278           3 :             oIter->second->GetSize() == poDim->GetSize())
    1279             :         {
    1280           3 :             poDim = oIter->second;
    1281           3 :             return true;
    1282             :         }
    1283             : 
    1284         456 :         std::string osType;
    1285         456 :         std::string osDirection;
    1286         228 :         if (aoDims.size() == 1 && osArrayName == osDimName)
    1287             :         {
    1288           4 :             ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
    1289             :                                                  osDirection);
    1290             :         }
    1291             : 
    1292             :         auto poDimLocal = std::make_shared<ZarrDimension>(
    1293         228 :             m_poSharedResource,
    1294         456 :             std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
    1295         456 :             GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
    1296         228 :         poDimLocal->SetXArrayDimension();
    1297         228 :         m_oMapDimensions[osDimName] = poDimLocal;
    1298         228 :         poDim = poDimLocal;
    1299         228 :         return true;
    1300         192 :     };
    1301             : 
    1302         192 :     if (dimensionNames.GetType() == CPLJSONObject::Type::Array)
    1303             :     {
    1304         123 :         const auto arrayDims = dimensionNames.ToArray();
    1305         123 :         if (arrayDims.Size() == oShape.Size())
    1306             :         {
    1307         354 :             for (int i = 0; i < oShape.Size(); ++i)
    1308             :             {
    1309         232 :                 if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
    1310             :                 {
    1311         696 :                     const auto osDimName = arrayDims[i].ToString();
    1312         232 :                     FindDimension(osDimName, aoDims[i], i);
    1313             :                 }
    1314             :             }
    1315             :         }
    1316             :         else
    1317             :         {
    1318           1 :             CPLError(
    1319             :                 CE_Failure, CPLE_AppDefined,
    1320             :                 "Size of dimension_names[] different from the one of shape");
    1321           1 :             return nullptr;
    1322             :         }
    1323             :     }
    1324          69 :     else if (dimensionNames.IsValid())
    1325             :     {
    1326           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1327             :                  "dimension_names should be an array");
    1328           1 :         return nullptr;
    1329             :     }
    1330             : 
    1331         570 :     auto oDtype = oRoot["data_type"];
    1332         190 :     if (!oDtype.IsValid())
    1333             :     {
    1334           1 :         CPLError(CE_Failure, CPLE_NotSupported, "data_type missing");
    1335           1 :         return nullptr;
    1336             :     }
    1337         189 :     if (oDtype["fallback"].IsValid())
    1338           1 :         oDtype = oDtype["fallback"];
    1339         378 :     std::vector<DtypeElt> aoDtypeElts;
    1340         378 :     const auto oType = ParseDtypeV3(oDtype, aoDtypeElts);
    1341         378 :     if (oType.GetClass() == GEDTC_NUMERIC &&
    1342         189 :         oType.GetNumericDataType() == GDT_Unknown)
    1343           1 :         return nullptr;
    1344             : 
    1345         376 :     std::vector<GUInt64> anBlockSize;
    1346         188 :     if (!ZarrArray::ParseChunkSize(oChunks, oType, anBlockSize))
    1347           1 :         return nullptr;
    1348             : 
    1349         374 :     std::vector<GByte> abyNoData;
    1350             : 
    1351         561 :     auto oFillValue = oRoot["fill_value"];
    1352         187 :     auto eFillValueType = oFillValue.GetType();
    1353             : 
    1354         187 :     if (!oFillValue.IsValid())
    1355             :     {
    1356           0 :         CPLError(CE_Warning, CPLE_AppDefined, "Missing fill_value is invalid");
    1357             :     }
    1358         187 :     else if (eFillValueType == CPLJSONObject::Type::Null)
    1359             :     {
    1360         100 :         CPLError(CE_Warning, CPLE_AppDefined, "fill_value = null is invalid");
    1361             :     }
    1362          87 :     else if (GDALDataTypeIsComplex(oType.GetNumericDataType()) &&
    1363             :              eFillValueType != CPLJSONObject::Type::Array)
    1364             :     {
    1365           4 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1366           4 :         return nullptr;
    1367             :     }
    1368          83 :     else if (eFillValueType == CPLJSONObject::Type::String)
    1369             :     {
    1370          60 :         const auto osFillValue = oFillValue.ToString();
    1371          30 :         if (STARTS_WITH(osFillValue.c_str(), "0x"))
    1372             :         {
    1373           3 :             if (osFillValue.size() > 2 + 2 * oType.GetSize())
    1374             :             {
    1375           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1376           1 :                 return nullptr;
    1377             :             }
    1378             :             uint64_t nVal = static_cast<uint64_t>(
    1379           3 :                 std::strtoull(osFillValue.c_str() + 2, nullptr, 16));
    1380           3 :             if (oType.GetSize() == 4)
    1381             :             {
    1382           1 :                 abyNoData.resize(oType.GetSize());
    1383           1 :                 uint32_t nTmp = static_cast<uint32_t>(nVal);
    1384           1 :                 memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
    1385             :             }
    1386           2 :             else if (oType.GetSize() == 8)
    1387             :             {
    1388           1 :                 abyNoData.resize(oType.GetSize());
    1389           1 :                 memcpy(&abyNoData[0], &nVal, sizeof(nVal));
    1390             :             }
    1391             :             else
    1392             :             {
    1393           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1394             :                          "Hexadecimal representation of fill_value no "
    1395             :                          "supported for this data type");
    1396           1 :                 return nullptr;
    1397             :             }
    1398             :         }
    1399          27 :         else if (STARTS_WITH(osFillValue.c_str(), "0b"))
    1400             :         {
    1401           3 :             if (osFillValue.size() > 2 + 8 * oType.GetSize())
    1402             :             {
    1403           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1404           1 :                 return nullptr;
    1405             :             }
    1406             :             uint64_t nVal = static_cast<uint64_t>(
    1407           3 :                 std::strtoull(osFillValue.c_str() + 2, nullptr, 2));
    1408           3 :             if (oType.GetSize() == 4)
    1409             :             {
    1410           1 :                 abyNoData.resize(oType.GetSize());
    1411           1 :                 uint32_t nTmp = static_cast<uint32_t>(nVal);
    1412           1 :                 memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
    1413             :             }
    1414           2 :             else if (oType.GetSize() == 8)
    1415             :             {
    1416           1 :                 abyNoData.resize(oType.GetSize());
    1417           1 :                 memcpy(&abyNoData[0], &nVal, sizeof(nVal));
    1418             :             }
    1419             :             else
    1420             :             {
    1421           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1422             :                          "Binary representation of fill_value no supported for "
    1423             :                          "this data type");
    1424           1 :                 return nullptr;
    1425             :             }
    1426             :         }
    1427             :         else
    1428             :         {
    1429          24 :             bool bOK = true;
    1430          24 :             double dfNoDataValue = ParseNoDataStringAsDouble(osFillValue, bOK);
    1431          24 :             if (!bOK)
    1432             :             {
    1433           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1434           2 :                 return nullptr;
    1435             :             }
    1436          23 :             else if (oType.GetNumericDataType() == GDT_Float16)
    1437             :             {
    1438             :                 const GFloat16 hfNoDataValue =
    1439           1 :                     static_cast<GFloat16>(dfNoDataValue);
    1440           1 :                 abyNoData.resize(sizeof(hfNoDataValue));
    1441           1 :                 memcpy(&abyNoData[0], &hfNoDataValue, sizeof(hfNoDataValue));
    1442             :             }
    1443          22 :             else if (oType.GetNumericDataType() == GDT_Float32)
    1444             :             {
    1445           7 :                 const float fNoDataValue = static_cast<float>(dfNoDataValue);
    1446           7 :                 abyNoData.resize(sizeof(fNoDataValue));
    1447           7 :                 memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
    1448             :             }
    1449          15 :             else if (oType.GetNumericDataType() == GDT_Float64)
    1450             :             {
    1451          14 :                 abyNoData.resize(sizeof(dfNoDataValue));
    1452          14 :                 memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
    1453             :             }
    1454             :             else
    1455             :             {
    1456           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1457             :                          "Invalid fill_value for this data type");
    1458           1 :                 return nullptr;
    1459             :             }
    1460             :         }
    1461             :     }
    1462          53 :     else if (eFillValueType == CPLJSONObject::Type::Boolean ||
    1463          27 :              eFillValueType == CPLJSONObject::Type::Integer ||
    1464          27 :              eFillValueType == CPLJSONObject::Type::Long ||
    1465             :              eFillValueType == CPLJSONObject::Type::Double)
    1466             :     {
    1467          29 :         const double dfNoDataValue = oFillValue.ToDouble();
    1468          29 :         if (oType.GetNumericDataType() == GDT_Int64)
    1469             :         {
    1470             :             const int64_t nNoDataValue =
    1471           1 :                 static_cast<int64_t>(oFillValue.ToLong());
    1472           1 :             abyNoData.resize(oType.GetSize());
    1473           1 :             GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
    1474             :                           oType.GetNumericDataType(), 0, 1);
    1475             :         }
    1476          28 :         else if (oType.GetNumericDataType() == GDT_UInt64 &&
    1477             :                  /* we can't really deal with nodata value between */
    1478             :                  /* int64::max and uint64::max due to json-c limitations */
    1479           0 :                  dfNoDataValue >= 0)
    1480             :         {
    1481             :             const int64_t nNoDataValue =
    1482           0 :                 static_cast<int64_t>(oFillValue.ToLong());
    1483           0 :             abyNoData.resize(oType.GetSize());
    1484           0 :             GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
    1485             :                           oType.GetNumericDataType(), 0, 1);
    1486             :         }
    1487             :         else
    1488             :         {
    1489          28 :             abyNoData.resize(oType.GetSize());
    1490          28 :             GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
    1491             :                           oType.GetNumericDataType(), 0, 1);
    1492          29 :         }
    1493             :     }
    1494          24 :     else if (eFillValueType == CPLJSONObject::Type::Array)
    1495             :     {
    1496          24 :         const auto oFillValueArray = oFillValue.ToArray();
    1497          44 :         if (oFillValueArray.Size() == 2 &&
    1498          20 :             GDALDataTypeIsComplex(oType.GetNumericDataType()))
    1499             :         {
    1500          20 :             if (oType.GetNumericDataType() == GDT_CFloat64)
    1501             :             {
    1502          10 :                 bool bOK = true;
    1503             :                 const double adfNoDataValue[2] = {
    1504          10 :                     ParseNoDataComponent<double, uint64_t>(oFillValueArray[0],
    1505             :                                                            bOK),
    1506          10 :                     ParseNoDataComponent<double, uint64_t>(oFillValueArray[1],
    1507             :                                                            bOK),
    1508          20 :                 };
    1509          10 :                 if (!bOK)
    1510             :                 {
    1511           2 :                     CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1512           2 :                     return nullptr;
    1513             :                 }
    1514           8 :                 abyNoData.resize(oType.GetSize());
    1515           8 :                 CPLAssert(sizeof(adfNoDataValue) == oType.GetSize());
    1516           8 :                 memcpy(abyNoData.data(), adfNoDataValue,
    1517             :                        sizeof(adfNoDataValue));
    1518             :             }
    1519             :             else
    1520             :             {
    1521          10 :                 CPLAssert(oType.GetNumericDataType() == GDT_CFloat32);
    1522          10 :                 bool bOK = true;
    1523             :                 const float afNoDataValue[2] = {
    1524          10 :                     ParseNoDataComponent<float, uint32_t>(oFillValueArray[0],
    1525             :                                                           bOK),
    1526          10 :                     ParseNoDataComponent<float, uint32_t>(oFillValueArray[1],
    1527             :                                                           bOK),
    1528          20 :                 };
    1529          10 :                 if (!bOK)
    1530             :                 {
    1531           2 :                     CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1532           2 :                     return nullptr;
    1533             :                 }
    1534           8 :                 abyNoData.resize(oType.GetSize());
    1535           8 :                 CPLAssert(sizeof(afNoDataValue) == oType.GetSize());
    1536           8 :                 memcpy(abyNoData.data(), afNoDataValue, sizeof(afNoDataValue));
    1537             :             }
    1538             :         }
    1539             :         else
    1540             :         {
    1541           4 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1542           4 :             return nullptr;
    1543             :         }
    1544             :     }
    1545             :     else
    1546             :     {
    1547           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
    1548           0 :         return nullptr;
    1549             :     }
    1550             : 
    1551         513 :     const auto oCodecs = oRoot["codecs"].ToArray();
    1552         171 :     std::unique_ptr<ZarrV3CodecSequence> poCodecs;
    1553         171 :     if (oCodecs.Size() > 0)
    1554             :     {
    1555             :         // Byte swapping will be done by the codec chain
    1556         141 :         aoDtypeElts.back().needByteSwapping = false;
    1557             : 
    1558         141 :         ZarrArrayMetadata oInputArrayMetadata;
    1559         385 :         for (auto &nSize : anBlockSize)
    1560         244 :             oInputArrayMetadata.anBlockSizes.push_back(
    1561         244 :                 static_cast<size_t>(nSize));
    1562         141 :         oInputArrayMetadata.oElt = aoDtypeElts.back();
    1563         141 :         poCodecs = std::make_unique<ZarrV3CodecSequence>(oInputArrayMetadata);
    1564         141 :         if (!poCodecs->InitFromJson(oCodecs))
    1565           0 :             return nullptr;
    1566             :     }
    1567             : 
    1568             :     auto poArray =
    1569         171 :         ZarrV3Array::Create(m_poSharedResource, GetFullName(), osArrayName,
    1570         342 :                             aoDims, oType, aoDtypeElts, anBlockSize);
    1571         171 :     if (!poArray)
    1572           1 :         return nullptr;
    1573         170 :     poArray->SetUpdatable(m_bUpdatable);  // must be set before SetAttributes()
    1574         170 :     poArray->SetFilename(osZarrayFilename);
    1575         170 :     poArray->SetIsV2ChunkKeyEncoding(bV2ChunkKeyEncoding);
    1576         170 :     poArray->SetDimSeparator(osDimSeparator);
    1577         170 :     if (!abyNoData.empty())
    1578             :     {
    1579          70 :         poArray->RegisterNoDataValue(abyNoData.data());
    1580             :     }
    1581         170 :     poArray->ParseSpecialAttributes(m_pSelf.lock(), oAttributes);
    1582         170 :     poArray->SetAttributes(oAttributes);
    1583         170 :     poArray->SetDtype(oDtype);
    1584         311 :     if (oCodecs.Size() > 0 &&
    1585         311 :         oCodecs[oCodecs.Size() - 1].GetString("name") != "bytes")
    1586             :     {
    1587          58 :         poArray->SetStructuralInfo(
    1588          58 :             "COMPRESSOR", oCodecs[oCodecs.Size() - 1].ToString().c_str());
    1589             :     }
    1590         170 :     if (poCodecs)
    1591         141 :         poArray->SetCodecs(oCodecs, std::move(poCodecs));
    1592         170 :     RegisterArray(poArray);
    1593             : 
    1594             :     // If this is an indexing variable, attach it to the dimension.
    1595         170 :     if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
    1596             :     {
    1597           4 :         auto oIter = m_oMapDimensions.find(poArray->GetName());
    1598           4 :         if (oIter != m_oMapDimensions.end())
    1599             :         {
    1600           4 :             oIter->second->SetIndexingVariable(poArray);
    1601             :         }
    1602             :     }
    1603             : 
    1604         170 :     if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
    1605             :             "CACHE_TILE_PRESENCE", "NO")))
    1606             :     {
    1607           2 :         poArray->CacheTilePresence();
    1608             :     }
    1609             : 
    1610         170 :     return poArray;
    1611             : }
    1612             : 
    1613             : /************************************************************************/
    1614             : /*                   ZarrV3Array::GetRawBlockInfoInfo()                 */
    1615             : /************************************************************************/
    1616             : 
    1617           4 : CPLStringList ZarrV3Array::GetRawBlockInfoInfo() const
    1618             : {
    1619           4 :     CPLStringList aosInfo(m_aosStructuralInfo);
    1620           4 :     if (m_oType.GetSize() > 1)
    1621             :     {
    1622             :         // By default, assume that the ENDIANNESS is the native one.
    1623             :         // Otherwise there will be a ZarrV3CodecBytes instance.
    1624             : #if CPL_IS_LSB
    1625           3 :         aosInfo.SetNameValue("ENDIANNESS", "LITTLE");
    1626             : #else
    1627             :         aosInfo.SetNameValue("ENDIANNESS", "BIG");
    1628             : #endif
    1629             :     }
    1630             : 
    1631           4 :     if (m_poCodecs)
    1632             :     {
    1633           4 :         bool bHasOtherCodec = false;
    1634           7 :         for (const auto &poCodec : m_poCodecs->GetCodecs())
    1635             :         {
    1636           4 :             if (poCodec->GetName() == ZarrV3CodecBytes::NAME &&
    1637           1 :                 m_oType.GetSize() > 1)
    1638             :             {
    1639             :                 auto poBytesCodec =
    1640           1 :                     dynamic_cast<const ZarrV3CodecBytes *>(poCodec.get());
    1641           1 :                 if (poBytesCodec)
    1642             :                 {
    1643           1 :                     if (poBytesCodec->IsLittle())
    1644           0 :                         aosInfo.SetNameValue("ENDIANNESS", "LITTLE");
    1645             :                     else
    1646           1 :                         aosInfo.SetNameValue("ENDIANNESS", "BIG");
    1647             :                 }
    1648             :             }
    1649           3 :             else if (poCodec->GetName() == ZarrV3CodecTranspose::NAME &&
    1650           1 :                      m_aoDims.size() > 1)
    1651             :             {
    1652             :                 auto poTransposeCodec =
    1653           1 :                     dynamic_cast<const ZarrV3CodecTranspose *>(poCodec.get());
    1654           1 :                 if (poTransposeCodec && !poTransposeCodec->IsNoOp())
    1655             :                 {
    1656           1 :                     const auto &anOrder = poTransposeCodec->GetOrder();
    1657           1 :                     const int nDims = static_cast<int>(anOrder.size());
    1658           2 :                     std::string osOrder("[");
    1659           3 :                     for (int i = 0; i < nDims; ++i)
    1660             :                     {
    1661           2 :                         if (i > 0)
    1662           1 :                             osOrder += ',';
    1663           2 :                         osOrder += std::to_string(anOrder[i]);
    1664             :                     }
    1665           1 :                     osOrder += ']';
    1666           1 :                     aosInfo.SetNameValue("TRANSPOSE_ORDER", osOrder.c_str());
    1667             :                 }
    1668             :             }
    1669           1 :             else if (poCodec->GetName() != ZarrV3CodecGZip::NAME &&
    1670           1 :                      poCodec->GetName() != ZarrV3CodecBlosc::NAME &&
    1671           0 :                      poCodec->GetName() != ZarrV3CodecZstd::NAME)
    1672             :             {
    1673           0 :                 bHasOtherCodec = true;
    1674             :             }
    1675             :         }
    1676           4 :         if (bHasOtherCodec)
    1677             :         {
    1678           0 :             aosInfo.SetNameValue("CODECS", m_oJSONCodecs.ToString().c_str());
    1679             :         }
    1680             :     }
    1681           4 :     return aosInfo;
    1682             : }

Generated by: LCOV version 1.14