LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v2_array.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 871 945 92.2 %
Date: 2024-05-14 13:00:50 Functions: 26 28 92.9 %

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

Generated by: LCOV version 1.14