LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_array.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1325 1526 86.8 %
Date: 2026-01-20 17:06:23 Functions: 43 46 93.5 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Zarr driver
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "zarr.h"
      14             : 
      15             : #include "cpl_float.h"
      16             : #include "cpl_multiproc.h"
      17             : #include "cpl_vsi_virtual.h"
      18             : #include "ucs4_utf8.hpp"
      19             : 
      20             : #include "netcdf_cf_constants.h"  // for CF_UNITS, etc
      21             : 
      22             : #include <algorithm>
      23             : #include <cassert>
      24             : #include <cmath>
      25             : #include <cstdlib>
      26             : #include <limits>
      27             : #include <map>
      28             : #include <set>
      29             : 
      30             : #if defined(__clang__) || defined(_MSC_VER)
      31             : #define COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT
      32             : #endif
      33             : 
      34             : namespace
      35             : {
      36             : 
      37           4 : inline std::vector<GByte> UTF8ToUCS4(const char *pszStr, bool needByteSwap)
      38             : {
      39           4 :     const size_t nLen = strlen(pszStr);
      40             :     // Worst case if that we need 4 more bytes than the UTF-8 one
      41             :     // (when the content is pure ASCII)
      42           4 :     if (nLen > std::numeric_limits<size_t>::max() / sizeof(uint32_t))
      43           0 :         throw std::bad_alloc();
      44           4 :     std::vector<GByte> ret(nLen * sizeof(uint32_t));
      45           4 :     size_t outPos = 0;
      46          27 :     for (size_t i = 0; i < nLen; outPos += sizeof(uint32_t))
      47             :     {
      48          23 :         uint32_t ucs4 = 0;
      49          23 :         int consumed = FcUtf8ToUcs4(
      50          23 :             reinterpret_cast<const uint8_t *>(pszStr + i), &ucs4, nLen - i);
      51          23 :         if (consumed <= 0)
      52             :         {
      53           0 :             ret.resize(outPos);
      54             :         }
      55          23 :         if (needByteSwap)
      56             :         {
      57           1 :             CPL_SWAP32PTR(&ucs4);
      58             :         }
      59          23 :         memcpy(&ret[outPos], &ucs4, sizeof(uint32_t));
      60          23 :         i += consumed;
      61             :     }
      62           4 :     ret.resize(outPos);
      63           4 :     return ret;
      64             : }
      65             : 
      66           7 : inline char *UCS4ToUTF8(const uint8_t *ucs4Ptr, size_t nSize, bool needByteSwap)
      67             : {
      68             :     // A UCS4 char can require up to 6 bytes in UTF8.
      69           7 :     if (nSize > (std::numeric_limits<size_t>::max() - 1) / 6 * 4)
      70           0 :         return nullptr;
      71           7 :     const size_t nOutSize = nSize / 4 * 6 + 1;
      72           7 :     char *ret = static_cast<char *>(VSI_MALLOC_VERBOSE(nOutSize));
      73           7 :     if (ret == nullptr)
      74           0 :         return nullptr;
      75           7 :     size_t outPos = 0;
      76          32 :     for (size_t i = 0; i + sizeof(uint32_t) - 1 < nSize; i += sizeof(uint32_t))
      77             :     {
      78             :         uint32_t ucs4;
      79          25 :         memcpy(&ucs4, ucs4Ptr + i, sizeof(uint32_t));
      80          25 :         if (needByteSwap)
      81             :         {
      82           2 :             CPL_SWAP32PTR(&ucs4);
      83             :         }
      84             :         int written =
      85          25 :             FcUcs4ToUtf8(ucs4, reinterpret_cast<uint8_t *>(ret + outPos));
      86          25 :         outPos += written;
      87             :     }
      88           7 :     ret[outPos] = 0;
      89           7 :     return ret;
      90             : }
      91             : 
      92             : }  // namespace
      93             : 
      94             : /************************************************************************/
      95             : /*                      ZarrArray::ParseChunkSize()                     */
      96             : /************************************************************************/
      97             : 
      98        1546 : /* static */ bool ZarrArray::ParseChunkSize(const CPLJSONArray &oChunks,
      99             :                                             const GDALExtendedDataType &oType,
     100             :                                             std::vector<GUInt64> &anBlockSize)
     101             : {
     102        1546 :     size_t nBlockSize = oType.GetSize();
     103        4215 :     for (const auto &item : oChunks)
     104             :     {
     105        2672 :         const auto nSize = static_cast<GUInt64>(item.ToLong());
     106        2672 :         if (nSize == 0)
     107             :         {
     108           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for chunks");
     109           3 :             return false;
     110             :         }
     111        2671 :         if (nBlockSize > std::numeric_limits<size_t>::max() / nSize)
     112             :         {
     113           2 :             CPLError(CE_Failure, CPLE_AppDefined, "Too large chunks");
     114           2 :             return false;
     115             :         }
     116        2669 :         nBlockSize *= static_cast<size_t>(nSize);
     117        2669 :         anBlockSize.emplace_back(nSize);
     118             :     }
     119             : 
     120        1543 :     return true;
     121             : }
     122             : 
     123             : /************************************************************************/
     124             : /*                     ZarrArray::ComputeBlockCount()                   */
     125             : /************************************************************************/
     126             : 
     127        1934 : /* static */ uint64_t ZarrArray::ComputeBlockCount(
     128             :     const std::string &osName,
     129             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
     130             :     const std::vector<GUInt64> &anBlockSize)
     131             : {
     132        1934 :     uint64_t nTotalBlockCount = 1;
     133        5170 :     for (size_t i = 0; i < aoDims.size(); ++i)
     134             :     {
     135             :         const uint64_t nBlockThisDim =
     136        3238 :             cpl::div_round_up(aoDims[i]->GetSize(), anBlockSize[i]);
     137        6476 :         if (nBlockThisDim != 0 &&
     138             :             nTotalBlockCount >
     139        3238 :                 std::numeric_limits<uint64_t>::max() / nBlockThisDim)
     140             :         {
     141           2 :             CPLError(
     142             :                 CE_Failure, CPLE_NotSupported,
     143             :                 "Array %s has more than 2^64 blocks. This is not supported.",
     144             :                 osName.c_str());
     145           2 :             return 0;
     146             :         }
     147        3236 :         nTotalBlockCount *= nBlockThisDim;
     148             :     }
     149        1932 :     return nTotalBlockCount;
     150             : }
     151             : 
     152             : /************************************************************************/
     153             : /*                     ComputeCountInnerBlockInOuter()                  */
     154             : /************************************************************************/
     155             : 
     156             : static std::vector<GUInt64>
     157        1934 : ComputeCountInnerBlockInOuter(const std::vector<GUInt64> &anInnerBlockSize,
     158             :                               const std::vector<GUInt64> &anOuterBlockSize)
     159             : {
     160        1934 :     std::vector<GUInt64> ret;
     161        1934 :     CPLAssert(anInnerBlockSize.size() == anOuterBlockSize.size());
     162        5172 :     for (size_t i = 0; i < anInnerBlockSize.size(); ++i)
     163             :     {
     164             :         // All those assertions must be checked by the caller before
     165             :         // constructing the ZarrArray instance.
     166        3238 :         CPLAssert(anInnerBlockSize[i] > 0);
     167        3238 :         CPLAssert(anInnerBlockSize[i] <= anOuterBlockSize[i]);
     168        3238 :         CPLAssert((anOuterBlockSize[i] % anInnerBlockSize[i]) == 0);
     169        3238 :         ret.push_back(anOuterBlockSize[i] / anInnerBlockSize[i]);
     170             :     }
     171        1934 :     return ret;
     172             : }
     173             : 
     174             : /************************************************************************/
     175             : /*                     ComputeInnerBlockSizeBytes()                     */
     176             : /************************************************************************/
     177             : 
     178             : static size_t
     179        1932 : ComputeInnerBlockSizeBytes(const std::vector<DtypeElt> &aoDtypeElts,
     180             :                            const std::vector<GUInt64> &anInnerBlockSize)
     181             : {
     182             :     const size_t nSourceSize =
     183        1932 :         aoDtypeElts.back().nativeOffset + aoDtypeElts.back().nativeSize;
     184        1932 :     size_t nInnerBlockSizeBytes = nSourceSize;
     185        5164 :     for (const auto &nBlockSize : anInnerBlockSize)
     186             :     {
     187             :         // Given that ParseChunkSize() has checked that the outer block size
     188             :         // fits on size_t, and that m_anInnerBlockSize[i] <= m_anOuterBlockSize[i],
     189             :         // this cast is safe, and the multiplication cannot overflow.
     190        3232 :         nInnerBlockSizeBytes *= static_cast<size_t>(nBlockSize);
     191             :     }
     192        1932 :     return nInnerBlockSizeBytes;
     193             : }
     194             : 
     195             : /************************************************************************/
     196             : /*                         ZarrArray::ZarrArray()                       */
     197             : /************************************************************************/
     198             : 
     199        1934 : ZarrArray::ZarrArray(
     200             :     const std::shared_ptr<ZarrSharedResource> &poSharedResource,
     201             :     const std::string &osParentName, const std::string &osName,
     202             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
     203             :     const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
     204             :     const std::vector<GUInt64> &anOuterBlockSize,
     205           0 :     const std::vector<GUInt64> &anInnerBlockSize)
     206             :     :
     207             : #if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT)
     208             :       GDALAbstractMDArray(osParentName, osName),
     209             : #endif
     210             :       GDALPamMDArray(osParentName, osName, poSharedResource->GetPAM()),
     211             :       m_poSharedResource(poSharedResource), m_aoDims(aoDims), m_oType(oType),
     212             :       m_aoDtypeElts(aoDtypeElts), m_anOuterBlockSize(anOuterBlockSize),
     213             :       m_anInnerBlockSize(anInnerBlockSize),
     214             :       m_anCountInnerBlockInOuter(ComputeCountInnerBlockInOuter(
     215        1934 :           m_anInnerBlockSize, m_anOuterBlockSize)),
     216             :       m_nTotalInnerChunkCount(
     217        1934 :           ComputeBlockCount(osName, aoDims, m_anInnerBlockSize)),
     218             :       m_nInnerBlockSizeBytes(
     219        1934 :           m_nTotalInnerChunkCount > 0
     220        1934 :               ? ComputeInnerBlockSizeBytes(m_aoDtypeElts, m_anInnerBlockSize)
     221             :               : 0),
     222        1934 :       m_oAttrGroup(m_osFullName, /*bContainerIsGroup=*/false),
     223        1934 :       m_bUseOptimizedCodePaths(CPLTestBool(
     224        7736 :           CPLGetConfigOption("GDAL_ZARR_USE_OPTIMIZED_CODE_PATHS", "YES")))
     225             : {
     226        1934 : }
     227             : 
     228             : /************************************************************************/
     229             : /*                              ~ZarrArray()                            */
     230             : /************************************************************************/
     231             : 
     232        1934 : ZarrArray::~ZarrArray()
     233             : {
     234        1934 :     if (m_pabyNoData)
     235             :     {
     236         850 :         m_oType.FreeDynamicMemory(&m_pabyNoData[0]);
     237         850 :         CPLFree(m_pabyNoData);
     238             :     }
     239             : 
     240        1934 :     DeallocateDecodedBlockData();
     241        1934 : }
     242             : 
     243             : /************************************************************************/
     244             : /*              ZarrArray::SerializeSpecialAttributes()                 */
     245             : /************************************************************************/
     246             : 
     247         390 : CPLJSONObject ZarrArray::SerializeSpecialAttributes()
     248             : {
     249         390 :     m_bSRSModified = false;
     250         390 :     m_oAttrGroup.UnsetModified();
     251             : 
     252         390 :     auto oAttrs = m_oAttrGroup.Serialize();
     253             : 
     254         390 :     if (m_poSRS)
     255             :     {
     256          40 :         CPLJSONObject oCRS;
     257          40 :         const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
     258          40 :         char *pszWKT = nullptr;
     259          40 :         if (m_poSRS->exportToWkt(&pszWKT, apszOptions) == OGRERR_NONE)
     260             :         {
     261          40 :             oCRS.Add("wkt", pszWKT);
     262             :         }
     263          40 :         CPLFree(pszWKT);
     264             : 
     265             :         {
     266          80 :             CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
     267          40 :             char *projjson = nullptr;
     268          80 :             if (m_poSRS->exportToPROJJSON(&projjson, nullptr) == OGRERR_NONE &&
     269          40 :                 projjson != nullptr)
     270             :             {
     271          80 :                 CPLJSONDocument oDocProjJSON;
     272          40 :                 if (oDocProjJSON.LoadMemory(std::string(projjson)))
     273             :                 {
     274          40 :                     oCRS.Add("projjson", oDocProjJSON.GetRoot());
     275             :                 }
     276             :             }
     277          40 :             CPLFree(projjson);
     278             :         }
     279             : 
     280          40 :         const char *pszAuthorityCode = m_poSRS->GetAuthorityCode(nullptr);
     281          40 :         const char *pszAuthorityName = m_poSRS->GetAuthorityName(nullptr);
     282          40 :         if (pszAuthorityCode && pszAuthorityName &&
     283           7 :             EQUAL(pszAuthorityName, "EPSG"))
     284             :         {
     285           7 :             oCRS.Add("url",
     286          14 :                      std::string("http://www.opengis.net/def/crs/EPSG/0/") +
     287             :                          pszAuthorityCode);
     288             :         }
     289             : 
     290          40 :         oAttrs.Add(CRS_ATTRIBUTE_NAME, oCRS);
     291             :     }
     292             : 
     293         390 :     if (m_osUnit.empty())
     294             :     {
     295         381 :         if (m_bUnitModified)
     296           0 :             oAttrs.Delete(CF_UNITS);
     297             :     }
     298             :     else
     299             :     {
     300           9 :         oAttrs.Set(CF_UNITS, m_osUnit);
     301             :     }
     302         390 :     m_bUnitModified = false;
     303             : 
     304         390 :     if (!m_bHasOffset)
     305             :     {
     306         387 :         oAttrs.Delete(CF_ADD_OFFSET);
     307             :     }
     308             :     else
     309             :     {
     310           3 :         oAttrs.Set(CF_ADD_OFFSET, m_dfOffset);
     311             :     }
     312         390 :     m_bOffsetModified = false;
     313             : 
     314         390 :     if (!m_bHasScale)
     315             :     {
     316         387 :         oAttrs.Delete(CF_SCALE_FACTOR);
     317             :     }
     318             :     else
     319             :     {
     320           3 :         oAttrs.Set(CF_SCALE_FACTOR, m_dfScale);
     321             :     }
     322         390 :     m_bScaleModified = false;
     323             : 
     324         390 :     return oAttrs;
     325             : }
     326             : 
     327             : /************************************************************************/
     328             : /*                          FillBlockSize()                             */
     329             : /************************************************************************/
     330             : 
     331             : /* static */
     332         446 : bool ZarrArray::FillBlockSize(
     333             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
     334             :     const GDALExtendedDataType &oDataType, std::vector<GUInt64> &anBlockSize,
     335             :     CSLConstList papszOptions)
     336             : {
     337         446 :     const auto nDims = aoDimensions.size();
     338         446 :     anBlockSize.resize(nDims);
     339        1108 :     for (size_t i = 0; i < nDims; ++i)
     340         662 :         anBlockSize[i] = 1;
     341         446 :     if (nDims >= 2)
     342             :     {
     343         436 :         anBlockSize[nDims - 2] =
     344         436 :             std::min(std::max<GUInt64>(1, aoDimensions[nDims - 2]->GetSize()),
     345         436 :                      static_cast<GUInt64>(256));
     346         436 :         anBlockSize[nDims - 1] =
     347         436 :             std::min(std::max<GUInt64>(1, aoDimensions[nDims - 1]->GetSize()),
     348         654 :                      static_cast<GUInt64>(256));
     349             :     }
     350         228 :     else if (nDims == 1)
     351             :     {
     352         181 :         anBlockSize[0] = std::max<GUInt64>(1, aoDimensions[0]->GetSize());
     353             :     }
     354             : 
     355         446 :     const char *pszBlockSize = CSLFetchNameValue(papszOptions, "BLOCKSIZE");
     356         446 :     if (pszBlockSize)
     357             :     {
     358             :         const auto aszTokens(
     359          18 :             CPLStringList(CSLTokenizeString2(pszBlockSize, ",", 0)));
     360          18 :         if (static_cast<size_t>(aszTokens.size()) != nDims)
     361             :         {
     362           5 :             CPLError(CE_Failure, CPLE_AppDefined,
     363             :                      "Invalid number of values in BLOCKSIZE");
     364           5 :             return false;
     365             :         }
     366          13 :         size_t nBlockSize = oDataType.GetSize();
     367          40 :         for (size_t i = 0; i < nDims; ++i)
     368             :         {
     369          28 :             const auto v = static_cast<GUInt64>(CPLAtoGIntBig(aszTokens[i]));
     370          28 :             if (v > 0)
     371             :             {
     372          28 :                 anBlockSize[i] = v;
     373             :             }
     374          28 :             if (anBlockSize[i] >
     375          28 :                 std::numeric_limits<size_t>::max() / nBlockSize)
     376             :             {
     377           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     378             :                          "Too large values in BLOCKSIZE");
     379           1 :                 return false;
     380             :             }
     381          27 :             nBlockSize *= static_cast<size_t>(anBlockSize[i]);
     382             :         }
     383             :     }
     384         440 :     return true;
     385             : }
     386             : 
     387             : /************************************************************************/
     388             : /*                      DeallocateDecodedBlockData()                     */
     389             : /************************************************************************/
     390             : 
     391        3437 : void ZarrArray::DeallocateDecodedBlockData()
     392             : {
     393        3437 :     if (!m_abyDecodedBlockData.empty())
     394             :     {
     395         223 :         const size_t nDTSize = m_oType.GetSize();
     396         223 :         GByte *pDst = &m_abyDecodedBlockData[0];
     397         223 :         const size_t nValues = m_abyDecodedBlockData.size() / nDTSize;
     398         454 :         for (const auto &elt : m_aoDtypeElts)
     399             :         {
     400         231 :             if (elt.nativeType == DtypeElt::NativeType::STRING_ASCII ||
     401         230 :                 elt.nativeType == DtypeElt::NativeType::STRING_UNICODE)
     402             :             {
     403           2 :                 for (size_t i = 0; i < nValues; i++, pDst += nDTSize)
     404             :                 {
     405             :                     char *ptr;
     406           1 :                     char **pptr =
     407           1 :                         reinterpret_cast<char **>(pDst + elt.gdalOffset);
     408           1 :                     memcpy(&ptr, pptr, sizeof(ptr));
     409           1 :                     VSIFree(ptr);
     410             :                 }
     411             :             }
     412             :         }
     413             :     }
     414        3437 : }
     415             : 
     416             : /************************************************************************/
     417             : /*                             EncodeElt()                              */
     418             : /************************************************************************/
     419             : 
     420             : /* Encode from GDAL raw type to Zarr native type */
     421             : /*static*/
     422         121 : void ZarrArray::EncodeElt(const std::vector<DtypeElt> &elts, const GByte *pSrc,
     423             :                           GByte *pDst)
     424             : {
     425         243 :     for (const auto &elt : elts)
     426             :     {
     427         122 :         if (elt.nativeType == DtypeElt::NativeType::STRING_UNICODE)
     428             :         {
     429           0 :             const char *pStr =
     430           0 :                 *reinterpret_cast<const char *const *>(pSrc + elt.gdalOffset);
     431           0 :             if (pStr)
     432             :             {
     433             :                 try
     434             :                 {
     435           0 :                     const auto ucs4 = UTF8ToUCS4(pStr, elt.needByteSwapping);
     436           0 :                     const auto ucs4Len = ucs4.size();
     437           0 :                     memcpy(pDst + elt.nativeOffset, ucs4.data(),
     438           0 :                            std::min(ucs4Len, elt.nativeSize));
     439           0 :                     if (ucs4Len > elt.nativeSize)
     440             :                     {
     441           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     442             :                                  "Too long string truncated");
     443             :                     }
     444           0 :                     else if (ucs4Len < elt.nativeSize)
     445             :                     {
     446           0 :                         memset(pDst + elt.nativeOffset + ucs4Len, 0,
     447           0 :                                elt.nativeSize - ucs4Len);
     448             :                     }
     449             :                 }
     450           0 :                 catch (const std::exception &)
     451             :                 {
     452           0 :                     memset(pDst + elt.nativeOffset, 0, elt.nativeSize);
     453             :                 }
     454             :             }
     455             :             else
     456             :             {
     457           0 :                 memset(pDst + elt.nativeOffset, 0, elt.nativeSize);
     458             :             }
     459             :         }
     460         122 :         else if (elt.needByteSwapping)
     461             :         {
     462         120 :             if (elt.nativeSize == 2)
     463             :             {
     464          24 :                 if (elt.gdalTypeIsApproxOfNative)
     465             :                 {
     466           0 :                     CPLAssert(elt.nativeType == DtypeElt::NativeType::IEEEFP);
     467           0 :                     CPLAssert(elt.gdalType.GetNumericDataType() == GDT_Float32);
     468           0 :                     const uint32_t uint32Val =
     469           0 :                         *reinterpret_cast<const uint32_t *>(pSrc +
     470           0 :                                                             elt.gdalOffset);
     471           0 :                     bool bHasWarned = false;
     472             :                     uint16_t uint16Val =
     473           0 :                         CPL_SWAP16(CPLFloatToHalf(uint32Val, bHasWarned));
     474           0 :                     memcpy(pDst + elt.nativeOffset, &uint16Val,
     475             :                            sizeof(uint16Val));
     476             :                 }
     477             :                 else
     478             :                 {
     479          24 :                     const uint16_t val =
     480          24 :                         CPL_SWAP16(*reinterpret_cast<const uint16_t *>(
     481             :                             pSrc + elt.gdalOffset));
     482          24 :                     memcpy(pDst + elt.nativeOffset, &val, sizeof(val));
     483             :                 }
     484             :             }
     485          96 :             else if (elt.nativeSize == 4)
     486             :             {
     487          36 :                 const uint32_t val = CPL_SWAP32(
     488             :                     *reinterpret_cast<const uint32_t *>(pSrc + elt.gdalOffset));
     489          36 :                 memcpy(pDst + elt.nativeOffset, &val, sizeof(val));
     490             :             }
     491          60 :             else if (elt.nativeSize == 8)
     492             :             {
     493          48 :                 if (elt.nativeType == DtypeElt::NativeType::COMPLEX_IEEEFP)
     494             :                 {
     495          12 :                     uint32_t val =
     496          12 :                         CPL_SWAP32(*reinterpret_cast<const uint32_t *>(
     497             :                             pSrc + elt.gdalOffset));
     498          12 :                     memcpy(pDst + elt.nativeOffset, &val, sizeof(val));
     499          12 :                     val = CPL_SWAP32(*reinterpret_cast<const uint32_t *>(
     500             :                         pSrc + elt.gdalOffset + 4));
     501          12 :                     memcpy(pDst + elt.nativeOffset + 4, &val, sizeof(val));
     502             :                 }
     503             :                 else
     504             :                 {
     505          36 :                     const uint64_t val =
     506          36 :                         CPL_SWAP64(*reinterpret_cast<const uint64_t *>(
     507             :                             pSrc + elt.gdalOffset));
     508          36 :                     memcpy(pDst + elt.nativeOffset, &val, sizeof(val));
     509             :                 }
     510             :             }
     511          12 :             else if (elt.nativeSize == 16)
     512             :             {
     513          12 :                 uint64_t val = CPL_SWAP64(
     514             :                     *reinterpret_cast<const uint64_t *>(pSrc + elt.gdalOffset));
     515          12 :                 memcpy(pDst + elt.nativeOffset, &val, sizeof(val));
     516          12 :                 val = CPL_SWAP64(*reinterpret_cast<const uint64_t *>(
     517             :                     pSrc + elt.gdalOffset + 8));
     518          12 :                 memcpy(pDst + elt.nativeOffset + 8, &val, sizeof(val));
     519             :             }
     520             :             else
     521             :             {
     522           0 :                 CPLAssert(false);
     523             :             }
     524             :         }
     525           2 :         else if (elt.gdalTypeIsApproxOfNative)
     526             :         {
     527           0 :             if (elt.nativeType == DtypeElt::NativeType::IEEEFP &&
     528           0 :                 elt.nativeSize == 2)
     529             :             {
     530           0 :                 CPLAssert(elt.gdalType.GetNumericDataType() == GDT_Float32);
     531           0 :                 const uint32_t uint32Val =
     532           0 :                     *reinterpret_cast<const uint32_t *>(pSrc + elt.gdalOffset);
     533           0 :                 bool bHasWarned = false;
     534             :                 const uint16_t uint16Val =
     535           0 :                     CPLFloatToHalf(uint32Val, bHasWarned);
     536           0 :                 memcpy(pDst + elt.nativeOffset, &uint16Val, sizeof(uint16Val));
     537             :             }
     538             :             else
     539             :             {
     540           0 :                 CPLAssert(false);
     541             :             }
     542             :         }
     543           2 :         else if (elt.nativeType == DtypeElt::NativeType::STRING_ASCII)
     544             :         {
     545           0 :             const char *pStr =
     546           0 :                 *reinterpret_cast<const char *const *>(pSrc + elt.gdalOffset);
     547           0 :             if (pStr)
     548             :             {
     549           0 :                 const size_t nLen = strlen(pStr);
     550           0 :                 memcpy(pDst + elt.nativeOffset, pStr,
     551           0 :                        std::min(nLen, elt.nativeSize));
     552           0 :                 if (nLen < elt.nativeSize)
     553           0 :                     memset(pDst + elt.nativeOffset + nLen, 0,
     554           0 :                            elt.nativeSize - nLen);
     555             :             }
     556             :             else
     557             :             {
     558           0 :                 memset(pDst + elt.nativeOffset, 0, elt.nativeSize);
     559             :             }
     560             :         }
     561             :         else
     562             :         {
     563           2 :             CPLAssert(elt.nativeSize == elt.gdalSize);
     564           2 :             memcpy(pDst + elt.nativeOffset, pSrc + elt.gdalOffset,
     565           2 :                    elt.nativeSize);
     566             :         }
     567             :     }
     568         121 : }
     569             : 
     570             : /************************************************************************/
     571             : /*                ZarrArray::SerializeNumericNoData()                   */
     572             : /************************************************************************/
     573             : 
     574          16 : void ZarrArray::SerializeNumericNoData(CPLJSONObject &oRoot) const
     575             : {
     576          16 :     if (m_oType.GetNumericDataType() == GDT_Int64)
     577             :     {
     578           0 :         const auto nVal = GetNoDataValueAsInt64();
     579           0 :         oRoot.Add("fill_value", static_cast<GInt64>(nVal));
     580             :     }
     581          16 :     else if (m_oType.GetNumericDataType() == GDT_UInt64)
     582             :     {
     583           0 :         const auto nVal = GetNoDataValueAsUInt64();
     584           0 :         oRoot.Add("fill_value", static_cast<uint64_t>(nVal));
     585             :     }
     586             :     else
     587             :     {
     588          16 :         const double dfVal = GetNoDataValueAsDouble();
     589          16 :         if (std::isnan(dfVal))
     590           2 :             oRoot.Add("fill_value", "NaN");
     591          14 :         else if (dfVal == std::numeric_limits<double>::infinity())
     592           2 :             oRoot.Add("fill_value", "Infinity");
     593          12 :         else if (dfVal == -std::numeric_limits<double>::infinity())
     594           2 :             oRoot.Add("fill_value", "-Infinity");
     595          10 :         else if (GDALDataTypeIsInteger(m_oType.GetNumericDataType()))
     596           8 :             oRoot.Add("fill_value", static_cast<GInt64>(dfVal));
     597             :         else
     598           2 :             oRoot.Add("fill_value", dfVal);
     599             :     }
     600          16 : }
     601             : 
     602             : /************************************************************************/
     603             : /*                    ZarrArray::GetSpatialRef()                        */
     604             : /************************************************************************/
     605             : 
     606          22 : std::shared_ptr<OGRSpatialReference> ZarrArray::GetSpatialRef() const
     607             : {
     608          22 :     if (!CheckValidAndErrorOutIfNot())
     609           0 :         return nullptr;
     610             : 
     611          22 :     if (m_poSRS)
     612          12 :         return m_poSRS;
     613          10 :     return GDALPamMDArray::GetSpatialRef();
     614             : }
     615             : 
     616             : /************************************************************************/
     617             : /*                        SetRawNoDataValue()                           */
     618             : /************************************************************************/
     619             : 
     620          18 : bool ZarrArray::SetRawNoDataValue(const void *pRawNoData)
     621             : {
     622          18 :     if (!CheckValidAndErrorOutIfNot())
     623           0 :         return false;
     624             : 
     625          18 :     if (!m_bUpdatable)
     626             :     {
     627           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Array opened in read-only mode");
     628           0 :         return false;
     629             :     }
     630          18 :     m_bDefinitionModified = true;
     631          18 :     RegisterNoDataValue(pRawNoData);
     632          18 :     return true;
     633             : }
     634             : 
     635             : /************************************************************************/
     636             : /*                        RegisterNoDataValue()                         */
     637             : /************************************************************************/
     638             : 
     639         850 : void ZarrArray::RegisterNoDataValue(const void *pNoData)
     640             : {
     641         850 :     if (m_pabyNoData)
     642             :     {
     643           0 :         m_oType.FreeDynamicMemory(&m_pabyNoData[0]);
     644             :     }
     645             : 
     646         850 :     if (pNoData == nullptr)
     647             :     {
     648           0 :         CPLFree(m_pabyNoData);
     649           0 :         m_pabyNoData = nullptr;
     650             :     }
     651             :     else
     652             :     {
     653         850 :         const auto nSize = m_oType.GetSize();
     654         850 :         if (m_pabyNoData == nullptr)
     655             :         {
     656         850 :             m_pabyNoData = static_cast<GByte *>(CPLMalloc(nSize));
     657             :         }
     658         850 :         memset(m_pabyNoData, 0, nSize);
     659         850 :         GDALExtendedDataType::CopyValue(pNoData, m_oType, m_pabyNoData,
     660         850 :                                         m_oType);
     661             :     }
     662         850 : }
     663             : 
     664             : /************************************************************************/
     665             : /*                        DecodeSourceElt()                             */
     666             : /************************************************************************/
     667             : 
     668             : /* static */
     669        1936 : void ZarrArray::DecodeSourceElt(const std::vector<DtypeElt> &elts,
     670             :                                 const GByte *pSrc, GByte *pDst)
     671             : {
     672        3904 :     for (const auto &elt : elts)
     673             :     {
     674        1968 :         if (elt.nativeType == DtypeElt::NativeType::STRING_UNICODE)
     675             :         {
     676             :             char *ptr;
     677           0 :             char **pDstPtr = reinterpret_cast<char **>(pDst + elt.gdalOffset);
     678           0 :             memcpy(&ptr, pDstPtr, sizeof(ptr));
     679           0 :             VSIFree(ptr);
     680             : 
     681           0 :             char *pDstStr = UCS4ToUTF8(pSrc + elt.nativeOffset, elt.nativeSize,
     682           0 :                                        elt.needByteSwapping);
     683           0 :             memcpy(pDstPtr, &pDstStr, sizeof(pDstStr));
     684             :         }
     685        1968 :         else if (elt.needByteSwapping)
     686             :         {
     687        1922 :             if (elt.nativeSize == 2)
     688             :             {
     689             :                 uint16_t val;
     690         386 :                 memcpy(&val, pSrc + elt.nativeOffset, sizeof(val));
     691         386 :                 if (elt.gdalTypeIsApproxOfNative)
     692             :                 {
     693           0 :                     CPLAssert(elt.nativeType == DtypeElt::NativeType::IEEEFP);
     694           0 :                     CPLAssert(elt.gdalType.GetNumericDataType() == GDT_Float32);
     695           0 :                     uint32_t uint32Val = CPLHalfToFloat(CPL_SWAP16(val));
     696           0 :                     memcpy(pDst + elt.gdalOffset, &uint32Val,
     697             :                            sizeof(uint32Val));
     698             :                 }
     699             :                 else
     700             :                 {
     701         386 :                     *reinterpret_cast<uint16_t *>(pDst + elt.gdalOffset) =
     702         386 :                         CPL_SWAP16(val);
     703             :                 }
     704             :             }
     705        1536 :             else if (elt.nativeSize == 4)
     706             :             {
     707             :                 uint32_t val;
     708         576 :                 memcpy(&val, pSrc + elt.nativeOffset, sizeof(val));
     709         576 :                 *reinterpret_cast<uint32_t *>(pDst + elt.gdalOffset) =
     710         576 :                     CPL_SWAP32(val);
     711             :             }
     712         960 :             else if (elt.nativeSize == 8)
     713             :             {
     714         768 :                 if (elt.nativeType == DtypeElt::NativeType::COMPLEX_IEEEFP)
     715             :                 {
     716             :                     uint32_t val;
     717         192 :                     memcpy(&val, pSrc + elt.nativeOffset, sizeof(val));
     718         192 :                     *reinterpret_cast<uint32_t *>(pDst + elt.gdalOffset) =
     719         192 :                         CPL_SWAP32(val);
     720         192 :                     memcpy(&val, pSrc + elt.nativeOffset + 4, sizeof(val));
     721         192 :                     *reinterpret_cast<uint32_t *>(pDst + elt.gdalOffset + 4) =
     722         192 :                         CPL_SWAP32(val);
     723             :                 }
     724             :                 else
     725             :                 {
     726             :                     uint64_t val;
     727         576 :                     memcpy(&val, pSrc + elt.nativeOffset, sizeof(val));
     728         576 :                     *reinterpret_cast<uint64_t *>(pDst + elt.gdalOffset) =
     729         576 :                         CPL_SWAP64(val);
     730             :                 }
     731             :             }
     732         192 :             else if (elt.nativeSize == 16)
     733             :             {
     734             :                 uint64_t val;
     735         192 :                 memcpy(&val, pSrc + elt.nativeOffset, sizeof(val));
     736         192 :                 *reinterpret_cast<uint64_t *>(pDst + elt.gdalOffset) =
     737         192 :                     CPL_SWAP64(val);
     738         192 :                 memcpy(&val, pSrc + elt.nativeOffset + 8, sizeof(val));
     739         192 :                 *reinterpret_cast<uint64_t *>(pDst + elt.gdalOffset + 8) =
     740         192 :                     CPL_SWAP64(val);
     741             :             }
     742             :             else
     743             :             {
     744           0 :                 CPLAssert(false);
     745             :             }
     746             :         }
     747          46 :         else if (elt.gdalTypeIsApproxOfNative)
     748             :         {
     749           0 :             if (elt.nativeType == DtypeElt::NativeType::IEEEFP &&
     750           0 :                 elt.nativeSize == 2)
     751             :             {
     752           0 :                 CPLAssert(elt.gdalType.GetNumericDataType() == GDT_Float32);
     753             :                 uint16_t uint16Val;
     754           0 :                 memcpy(&uint16Val, pSrc + elt.nativeOffset, sizeof(uint16Val));
     755           0 :                 uint32_t uint32Val = CPLHalfToFloat(uint16Val);
     756           0 :                 memcpy(pDst + elt.gdalOffset, &uint32Val, sizeof(uint32Val));
     757             :             }
     758             :             else
     759             :             {
     760           0 :                 CPLAssert(false);
     761             :             }
     762             :         }
     763          46 :         else if (elt.nativeType == DtypeElt::NativeType::STRING_ASCII)
     764             :         {
     765             :             char *ptr;
     766           3 :             char **pDstPtr = reinterpret_cast<char **>(pDst + elt.gdalOffset);
     767           3 :             memcpy(&ptr, pDstPtr, sizeof(ptr));
     768           3 :             VSIFree(ptr);
     769             : 
     770           3 :             char *pDstStr = static_cast<char *>(CPLMalloc(elt.nativeSize + 1));
     771           3 :             memcpy(pDstStr, pSrc + elt.nativeOffset, elt.nativeSize);
     772           3 :             pDstStr[elt.nativeSize] = 0;
     773           3 :             memcpy(pDstPtr, &pDstStr, sizeof(pDstStr));
     774             :         }
     775             :         else
     776             :         {
     777          43 :             CPLAssert(elt.nativeSize == elt.gdalSize);
     778          43 :             memcpy(pDst + elt.gdalOffset, pSrc + elt.nativeOffset,
     779          43 :                    elt.nativeSize);
     780             :         }
     781             :     }
     782        1936 : }
     783             : 
     784             : /************************************************************************/
     785             : /*                  ZarrArray::IAdviseReadCommon()                      */
     786             : /************************************************************************/
     787             : 
     788          13 : bool ZarrArray::IAdviseReadCommon(const GUInt64 *arrayStartIdx,
     789             :                                   const size_t *count,
     790             :                                   CSLConstList papszOptions,
     791             :                                   std::vector<uint64_t> &anIndicesCur,
     792             :                                   int &nThreadsMax,
     793             :                                   std::vector<uint64_t> &anReqBlocksIndices,
     794             :                                   size_t &nReqBlocks) const
     795             : {
     796          13 :     if (!CheckValidAndErrorOutIfNot())
     797           0 :         return false;
     798             : 
     799          13 :     const size_t nDims = m_aoDims.size();
     800          13 :     anIndicesCur.resize(nDims);
     801          26 :     std::vector<uint64_t> anIndicesMin(nDims);
     802          26 :     std::vector<uint64_t> anIndicesMax(nDims);
     803             : 
     804             :     // Compute min and max tile indices in each dimension, and the total
     805             :     // number of tiles this represents.
     806          13 :     nReqBlocks = 1;
     807          39 :     for (size_t i = 0; i < nDims; ++i)
     808             :     {
     809          26 :         anIndicesMin[i] = arrayStartIdx[i] / m_anInnerBlockSize[i];
     810          52 :         anIndicesMax[i] =
     811          26 :             (arrayStartIdx[i] + count[i] - 1) / m_anInnerBlockSize[i];
     812             :         // Overflow on number of tiles already checked in Create()
     813          26 :         nReqBlocks *=
     814          26 :             static_cast<size_t>(anIndicesMax[i] - anIndicesMin[i] + 1);
     815             :     }
     816             : 
     817             :     // Find available cache size
     818          13 :     const size_t nCacheSize = [papszOptions]()
     819             :     {
     820             :         size_t nCacheSizeTmp;
     821             :         const char *pszCacheSize =
     822          13 :             CSLFetchNameValue(papszOptions, "CACHE_SIZE");
     823          13 :         if (pszCacheSize)
     824             :         {
     825           4 :             const auto nCacheSizeBig = CPLAtoGIntBig(pszCacheSize);
     826           8 :             if (nCacheSizeBig < 0 || static_cast<uint64_t>(nCacheSizeBig) >
     827           4 :                                          std::numeric_limits<size_t>::max() / 2)
     828             :             {
     829           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory, "Too big CACHE_SIZE");
     830           0 :                 return std::numeric_limits<size_t>::max();
     831             :             }
     832           4 :             nCacheSizeTmp = static_cast<size_t>(nCacheSizeBig);
     833             :         }
     834             :         else
     835             :         {
     836             :             // Arbitrarily take half of remaining cache size
     837           9 :             nCacheSizeTmp = static_cast<size_t>(std::min(
     838          18 :                 static_cast<uint64_t>(
     839           9 :                     (GDALGetCacheMax64() - GDALGetCacheUsed64()) / 2),
     840          18 :                 static_cast<uint64_t>(std::numeric_limits<size_t>::max() / 2)));
     841           9 :             CPLDebug(ZARR_DEBUG_KEY, "Using implicit CACHE_SIZE=" CPL_FRMT_GUIB,
     842             :                      static_cast<GUIntBig>(nCacheSizeTmp));
     843             :         }
     844          13 :         return nCacheSizeTmp;
     845          13 :     }();
     846          13 :     if (nCacheSize == std::numeric_limits<size_t>::max())
     847           0 :         return false;
     848             : 
     849             :     // Check that cache size is sufficient to hold all needed tiles.
     850             :     // Also check that anReqBlocksIndices size computation won't overflow.
     851          13 :     if (nReqBlocks > nCacheSize / std::max(m_nInnerBlockSizeBytes, nDims))
     852             :     {
     853           4 :         CPLError(CE_Failure, CPLE_OutOfMemory,
     854             :                  "CACHE_SIZE=" CPL_FRMT_GUIB " is not big enough to cache "
     855             :                  "all needed tiles. "
     856             :                  "At least " CPL_FRMT_GUIB " bytes would be needed",
     857             :                  static_cast<GUIntBig>(nCacheSize),
     858             :                  static_cast<GUIntBig>(
     859           4 :                      nReqBlocks * std::max(m_nInnerBlockSizeBytes, nDims)));
     860           4 :         return false;
     861             :     }
     862             : 
     863           9 :     const char *pszNumThreads = CSLFetchNameValueDef(
     864             :         papszOptions, "NUM_THREADS",
     865             :         CPLGetConfigOption("GDAL_NUM_THREADS", "ALL_CPUS"));
     866           9 :     if (EQUAL(pszNumThreads, "ALL_CPUS"))
     867           9 :         nThreadsMax = CPLGetNumCPUs();
     868             :     else
     869           0 :         nThreadsMax = std::max(1, atoi(pszNumThreads));
     870           9 :     if (nThreadsMax > 1024)
     871           0 :         nThreadsMax = 1024;
     872           9 :     if (nThreadsMax <= 1)
     873           0 :         return true;
     874           9 :     CPLDebug(ZARR_DEBUG_KEY, "IAdviseRead(): Using up to %d threads",
     875             :              nThreadsMax);
     876             : 
     877           9 :     m_oChunkCache.clear();
     878             : 
     879             :     // Overflow checked above
     880             :     try
     881             :     {
     882           9 :         anReqBlocksIndices.resize(nDims * nReqBlocks);
     883             :     }
     884           0 :     catch (const std::bad_alloc &e)
     885             :     {
     886           0 :         CPLError(CE_Failure, CPLE_OutOfMemory,
     887           0 :                  "Cannot allocate anReqBlocksIndices: %s", e.what());
     888           0 :         return false;
     889             :     }
     890             : 
     891           9 :     size_t dimIdx = 0;
     892           9 :     size_t nBlockIter = 0;
     893       21639 : lbl_next_depth:
     894       21639 :     if (dimIdx == nDims)
     895             :     {
     896       21369 :         if (nDims == 2)
     897             :         {
     898             :             // optimize in common case
     899       21369 :             memcpy(&anReqBlocksIndices[nBlockIter * nDims], anIndicesCur.data(),
     900             :                    sizeof(uint64_t) * 2);
     901             :         }
     902           0 :         else if (nDims == 3)
     903             :         {
     904             :             // optimize in common case
     905           0 :             memcpy(&anReqBlocksIndices[nBlockIter * nDims], anIndicesCur.data(),
     906             :                    sizeof(uint64_t) * 3);
     907             :         }
     908             :         else
     909             :         {
     910           0 :             memcpy(&anReqBlocksIndices[nBlockIter * nDims], anIndicesCur.data(),
     911           0 :                    sizeof(uint64_t) * nDims);
     912             :         }
     913       21369 :         nBlockIter++;
     914             :     }
     915             :     else
     916             :     {
     917             :         // This level of loop loops over blocks
     918         270 :         anIndicesCur[dimIdx] = anIndicesMin[dimIdx];
     919             :         while (true)
     920             :         {
     921       21630 :             dimIdx++;
     922       21630 :             goto lbl_next_depth;
     923       21630 :         lbl_return_to_caller:
     924       21630 :             dimIdx--;
     925       21630 :             if (anIndicesCur[dimIdx] == anIndicesMax[dimIdx])
     926         270 :                 break;
     927       21360 :             ++anIndicesCur[dimIdx];
     928             :         }
     929             :     }
     930       21639 :     if (dimIdx > 0)
     931       21630 :         goto lbl_return_to_caller;
     932           9 :     assert(nBlockIter == nReqBlocks);
     933             : 
     934           9 :     return true;
     935             : }
     936             : 
     937             : /************************************************************************/
     938             : /*                           ZarrArray::IRead()                         */
     939             : /************************************************************************/
     940             : 
     941        2278 : bool ZarrArray::IRead(const GUInt64 *arrayStartIdx, const size_t *count,
     942             :                       const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
     943             :                       const GDALExtendedDataType &bufferDataType,
     944             :                       void *pDstBuffer) const
     945             : {
     946        2278 :     if (!CheckValidAndErrorOutIfNot())
     947           0 :         return false;
     948             : 
     949        2278 :     if (!AllocateWorkingBuffers())
     950           3 :         return false;
     951             : 
     952             :     // Need to be kept in top-level scope
     953        4550 :     std::vector<GUInt64> arrayStartIdxMod;
     954        4550 :     std::vector<GInt64> arrayStepMod;
     955        4550 :     std::vector<GPtrDiff_t> bufferStrideMod;
     956             : 
     957        2275 :     const size_t nDims = m_aoDims.size();
     958        2275 :     bool negativeStep = false;
     959        6432 :     for (size_t i = 0; i < nDims; ++i)
     960             :     {
     961        4359 :         if (arrayStep[i] < 0)
     962             :         {
     963         202 :             negativeStep = true;
     964         202 :             break;
     965             :         }
     966             :     }
     967             : 
     968             :     // const auto eBufferDT = bufferDataType.GetNumericDataType();
     969        2275 :     const auto nBufferDTSize = static_cast<int>(bufferDataType.GetSize());
     970             : 
     971             :     // Make sure that arrayStep[i] are positive for sake of simplicity
     972        2275 :     if (negativeStep)
     973             :     {
     974             : #if defined(__GNUC__)
     975             : #pragma GCC diagnostic push
     976             : #pragma GCC diagnostic ignored "-Wnull-dereference"
     977             : #endif
     978         202 :         arrayStartIdxMod.resize(nDims);
     979         202 :         arrayStepMod.resize(nDims);
     980         202 :         bufferStrideMod.resize(nDims);
     981             : #if defined(__GNUC__)
     982             : #pragma GCC diagnostic pop
     983             : #endif
     984         606 :         for (size_t i = 0; i < nDims; ++i)
     985             :         {
     986         404 :             if (arrayStep[i] < 0)
     987             :             {
     988         808 :                 arrayStartIdxMod[i] =
     989         404 :                     arrayStartIdx[i] - (count[i] - 1) * (-arrayStep[i]);
     990         404 :                 arrayStepMod[i] = -arrayStep[i];
     991         404 :                 bufferStrideMod[i] = -bufferStride[i];
     992         404 :                 pDstBuffer =
     993             :                     static_cast<GByte *>(pDstBuffer) +
     994         404 :                     bufferStride[i] *
     995         404 :                         static_cast<GPtrDiff_t>(nBufferDTSize * (count[i] - 1));
     996             :             }
     997             :             else
     998             :             {
     999           0 :                 arrayStartIdxMod[i] = arrayStartIdx[i];
    1000           0 :                 arrayStepMod[i] = arrayStep[i];
    1001           0 :                 bufferStrideMod[i] = bufferStride[i];
    1002             :             }
    1003             :         }
    1004         202 :         arrayStartIdx = arrayStartIdxMod.data();
    1005         202 :         arrayStep = arrayStepMod.data();
    1006         202 :         bufferStride = bufferStrideMod.data();
    1007             :     }
    1008             : 
    1009        4550 :     std::vector<uint64_t> indicesOuterLoop(nDims + 1);
    1010        4550 :     std::vector<GByte *> dstPtrStackOuterLoop(nDims + 1);
    1011             : 
    1012        4550 :     std::vector<uint64_t> indicesInnerLoop(nDims + 1);
    1013        4550 :     std::vector<GByte *> dstPtrStackInnerLoop(nDims + 1);
    1014             : 
    1015        4550 :     std::vector<GPtrDiff_t> dstBufferStrideBytes;
    1016        6836 :     for (size_t i = 0; i < nDims; ++i)
    1017             :     {
    1018        4561 :         dstBufferStrideBytes.push_back(bufferStride[i] *
    1019        4561 :                                        static_cast<GPtrDiff_t>(nBufferDTSize));
    1020             :     }
    1021        2275 :     dstBufferStrideBytes.push_back(0);
    1022             : 
    1023        2275 :     const auto nDTSize = m_oType.GetSize();
    1024             : 
    1025        4550 :     std::vector<uint64_t> blockIndices(nDims);
    1026             :     const size_t nSourceSize =
    1027        2275 :         m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
    1028             : 
    1029        4550 :     std::vector<size_t> countInnerLoopInit(nDims + 1, 1);
    1030        4550 :     std::vector<size_t> countInnerLoop(nDims);
    1031             : 
    1032        4532 :     const bool bBothAreNumericDT = m_oType.GetClass() == GEDTC_NUMERIC &&
    1033        2257 :                                    bufferDataType.GetClass() == GEDTC_NUMERIC;
    1034             :     const bool bSameNumericDT =
    1035        4532 :         bBothAreNumericDT &&
    1036        2257 :         m_oType.GetNumericDataType() == bufferDataType.GetNumericDataType();
    1037        2275 :     const auto nSameDTSize = bSameNumericDT ? m_oType.GetSize() : 0;
    1038             :     const bool bSameCompoundAndNoDynamicMem =
    1039        2278 :         m_oType.GetClass() == GEDTC_COMPOUND && m_oType == bufferDataType &&
    1040           3 :         !m_oType.NeedsFreeDynamicMemory();
    1041        4550 :     std::vector<GByte> abyTargetNoData;
    1042        2275 :     bool bNoDataIsZero = false;
    1043             : 
    1044        2275 :     size_t dimIdx = 0;
    1045        2275 :     dstPtrStackOuterLoop[0] = static_cast<GByte *>(pDstBuffer);
    1046       34958 : lbl_next_depth:
    1047       34958 :     if (dimIdx == nDims)
    1048             :     {
    1049       28612 :         size_t dimIdxSubLoop = 0;
    1050       28612 :         dstPtrStackInnerLoop[0] = dstPtrStackOuterLoop[nDims];
    1051       28612 :         bool bEmptyChunk = false;
    1052             : 
    1053       28612 :         const GByte *pabySrcBlock = m_abyDecodedBlockData.empty()
    1054       27621 :                                         ? m_abyRawBlockData.data()
    1055       28612 :                                         : m_abyDecodedBlockData.data();
    1056       28612 :         bool bMatchFoundInMapChunkIndexToCachedBlock = false;
    1057             : 
    1058             :         // Use cache built by IAdviseRead() if possible
    1059       28612 :         if (!m_oChunkCache.empty())
    1060             :         {
    1061       21377 :             const auto oIter = m_oChunkCache.find(blockIndices);
    1062       21377 :             if (oIter != m_oChunkCache.end())
    1063             :             {
    1064       21369 :                 bMatchFoundInMapChunkIndexToCachedBlock = true;
    1065       21369 :                 if (oIter->second.abyDecoded.empty())
    1066             :                 {
    1067           4 :                     bEmptyChunk = true;
    1068             :                 }
    1069             :                 else
    1070             :                 {
    1071       21365 :                     pabySrcBlock = oIter->second.abyDecoded.data();
    1072             :                 }
    1073             :             }
    1074             :             else
    1075             :             {
    1076             : #ifdef DEBUG
    1077          16 :                 std::string key;
    1078          24 :                 for (size_t j = 0; j < nDims; ++j)
    1079             :                 {
    1080          16 :                     if (j)
    1081           8 :                         key += ',';
    1082          16 :                     key += std::to_string(blockIndices[j]);
    1083             :                 }
    1084           8 :                 CPLDebugOnly(ZARR_DEBUG_KEY, "Cache miss for tile %s",
    1085             :                              key.c_str());
    1086             : #endif
    1087             :             }
    1088             :         }
    1089             : 
    1090       28612 :         if (!bMatchFoundInMapChunkIndexToCachedBlock)
    1091             :         {
    1092        7243 :             if (!blockIndices.empty() && blockIndices == m_anCachedBlockIndices)
    1093             :             {
    1094         221 :                 if (!m_bCachedBlockValid)
    1095         575 :                     return false;
    1096         221 :                 bEmptyChunk = m_bCachedBlockEmpty;
    1097             :             }
    1098             :             else
    1099             :             {
    1100        7022 :                 if (!FlushDirtyBlock())
    1101           0 :                     return false;
    1102             : 
    1103        7022 :                 m_anCachedBlockIndices = blockIndices;
    1104        7022 :                 m_bCachedBlockValid =
    1105        7022 :                     LoadBlockData(blockIndices.data(), bEmptyChunk);
    1106        7022 :                 if (!m_bCachedBlockValid)
    1107             :                 {
    1108         575 :                     return false;
    1109             :                 }
    1110        6447 :                 m_bCachedBlockEmpty = bEmptyChunk;
    1111             :             }
    1112             : 
    1113       13336 :             pabySrcBlock = m_abyDecodedBlockData.empty()
    1114        5677 :                                ? m_abyRawBlockData.data()
    1115         991 :                                : m_abyDecodedBlockData.data();
    1116             :         }
    1117             :         const size_t nSrcDTSize =
    1118       28037 :             m_abyDecodedBlockData.empty() ? nSourceSize : nDTSize;
    1119             : 
    1120       84229 :         for (size_t i = 0; i < nDims; ++i)
    1121             :         {
    1122       56192 :             countInnerLoopInit[i] = 1;
    1123       56192 :             if (arrayStep[i] != 0)
    1124             :             {
    1125             :                 const auto nextBlockIdx =
    1126       55490 :                     std::min((1 + indicesOuterLoop[i] / m_anInnerBlockSize[i]) *
    1127       55490 :                                  m_anInnerBlockSize[i],
    1128      110980 :                              arrayStartIdx[i] + count[i] * arrayStep[i]);
    1129       55490 :                 countInnerLoopInit[i] = static_cast<size_t>(cpl::div_round_up(
    1130       55490 :                     nextBlockIdx - indicesOuterLoop[i], arrayStep[i]));
    1131             :             }
    1132             :         }
    1133             : 
    1134       28037 :         if (bEmptyChunk && bBothAreNumericDT && abyTargetNoData.empty())
    1135             :         {
    1136         604 :             abyTargetNoData.resize(nBufferDTSize);
    1137         604 :             if (m_pabyNoData)
    1138             :             {
    1139         157 :                 GDALExtendedDataType::CopyValue(
    1140         157 :                     m_pabyNoData, m_oType, &abyTargetNoData[0], bufferDataType);
    1141         157 :                 bNoDataIsZero = true;
    1142        1311 :                 for (size_t i = 0; i < abyTargetNoData.size(); ++i)
    1143             :                 {
    1144        1154 :                     if (abyTargetNoData[i] != 0)
    1145         335 :                         bNoDataIsZero = false;
    1146             :                 }
    1147             :             }
    1148             :             else
    1149             :             {
    1150         447 :                 bNoDataIsZero = true;
    1151         447 :                 GByte zero = 0;
    1152         447 :                 GDALCopyWords(&zero, GDT_UInt8, 0, &abyTargetNoData[0],
    1153             :                               bufferDataType.GetNumericDataType(), 0, 1);
    1154             :             }
    1155             :         }
    1156             : 
    1157       27433 :     lbl_next_depth_inner_loop:
    1158      483517 :         if (nDims == 0 || dimIdxSubLoop == nDims - 1)
    1159             :         {
    1160      455358 :             indicesInnerLoop[dimIdxSubLoop] = indicesOuterLoop[dimIdxSubLoop];
    1161      455358 :             void *dst_ptr = dstPtrStackInnerLoop[dimIdxSubLoop];
    1162             : 
    1163      452871 :             if (m_bUseOptimizedCodePaths && bEmptyChunk && bBothAreNumericDT &&
    1164      908229 :                 bNoDataIsZero &&
    1165        1135 :                 nBufferDTSize == dstBufferStrideBytes[dimIdxSubLoop])
    1166             :             {
    1167        1069 :                 memset(dst_ptr, 0,
    1168        1069 :                        nBufferDTSize * countInnerLoopInit[dimIdxSubLoop]);
    1169        1069 :                 goto end_inner_loop;
    1170             :             }
    1171      451802 :             else if (m_bUseOptimizedCodePaths && bEmptyChunk &&
    1172      906472 :                      !abyTargetNoData.empty() && bBothAreNumericDT &&
    1173         381 :                      dstBufferStrideBytes[dimIdxSubLoop] <
    1174         381 :                          std::numeric_limits<int>::max())
    1175             :             {
    1176         762 :                 GDALCopyWords64(
    1177         381 :                     abyTargetNoData.data(), bufferDataType.GetNumericDataType(),
    1178             :                     0, dst_ptr, bufferDataType.GetNumericDataType(),
    1179         381 :                     static_cast<int>(dstBufferStrideBytes[dimIdxSubLoop]),
    1180         381 :                     static_cast<GPtrDiff_t>(countInnerLoopInit[dimIdxSubLoop]));
    1181         381 :                 goto end_inner_loop;
    1182             :             }
    1183      453908 :             else if (bEmptyChunk)
    1184             :             {
    1185        3875 :                 for (size_t i = 0; i < countInnerLoopInit[dimIdxSubLoop];
    1186        2528 :                      ++i, dst_ptr = static_cast<uint8_t *>(dst_ptr) +
    1187        2528 :                                     dstBufferStrideBytes[dimIdxSubLoop])
    1188             :                 {
    1189        2528 :                     if (bNoDataIsZero)
    1190             :                     {
    1191        1930 :                         if (nBufferDTSize == 1)
    1192             :                         {
    1193          36 :                             *static_cast<uint8_t *>(dst_ptr) = 0;
    1194             :                         }
    1195        1894 :                         else if (nBufferDTSize == 2)
    1196             :                         {
    1197          48 :                             *static_cast<uint16_t *>(dst_ptr) = 0;
    1198             :                         }
    1199        1846 :                         else if (nBufferDTSize == 4)
    1200             :                         {
    1201          72 :                             *static_cast<uint32_t *>(dst_ptr) = 0;
    1202             :                         }
    1203        1774 :                         else if (nBufferDTSize == 8)
    1204             :                         {
    1205        1534 :                             *static_cast<uint64_t *>(dst_ptr) = 0;
    1206             :                         }
    1207         240 :                         else if (nBufferDTSize == 16)
    1208             :                         {
    1209         240 :                             static_cast<uint64_t *>(dst_ptr)[0] = 0;
    1210         240 :                             static_cast<uint64_t *>(dst_ptr)[1] = 0;
    1211             :                         }
    1212             :                         else
    1213             :                         {
    1214           0 :                             CPLAssert(false);
    1215             :                         }
    1216             :                     }
    1217         598 :                     else if (m_pabyNoData)
    1218             :                     {
    1219         597 :                         if (bBothAreNumericDT)
    1220             :                         {
    1221         588 :                             const void *src_ptr_v = abyTargetNoData.data();
    1222         588 :                             if (nBufferDTSize == 1)
    1223          24 :                                 *static_cast<uint8_t *>(dst_ptr) =
    1224          24 :                                     *static_cast<const uint8_t *>(src_ptr_v);
    1225         564 :                             else if (nBufferDTSize == 2)
    1226           0 :                                 *static_cast<uint16_t *>(dst_ptr) =
    1227           0 :                                     *static_cast<const uint16_t *>(src_ptr_v);
    1228         564 :                             else if (nBufferDTSize == 4)
    1229          60 :                                 *static_cast<uint32_t *>(dst_ptr) =
    1230          60 :                                     *static_cast<const uint32_t *>(src_ptr_v);
    1231         504 :                             else if (nBufferDTSize == 8)
    1232         504 :                                 *static_cast<uint64_t *>(dst_ptr) =
    1233         504 :                                     *static_cast<const uint64_t *>(src_ptr_v);
    1234           0 :                             else if (nBufferDTSize == 16)
    1235             :                             {
    1236           0 :                                 static_cast<uint64_t *>(dst_ptr)[0] =
    1237           0 :                                     static_cast<const uint64_t *>(src_ptr_v)[0];
    1238           0 :                                 static_cast<uint64_t *>(dst_ptr)[1] =
    1239           0 :                                     static_cast<const uint64_t *>(src_ptr_v)[1];
    1240             :                             }
    1241             :                             else
    1242             :                             {
    1243           0 :                                 CPLAssert(false);
    1244             :                             }
    1245             :                         }
    1246             :                         else
    1247             :                         {
    1248           9 :                             GDALExtendedDataType::CopyValue(
    1249           9 :                                 m_pabyNoData, m_oType, dst_ptr, bufferDataType);
    1250             :                         }
    1251             :                     }
    1252             :                     else
    1253             :                     {
    1254           1 :                         memset(dst_ptr, 0, nBufferDTSize);
    1255             :                     }
    1256             :                 }
    1257             : 
    1258        1347 :                 goto end_inner_loop;
    1259             :             }
    1260             : 
    1261      452561 :             size_t nOffset = 0;
    1262     1370110 :             for (size_t i = 0; i < nDims; i++)
    1263             :             {
    1264      917553 :                 nOffset = static_cast<size_t>(
    1265      917553 :                     nOffset * m_anInnerBlockSize[i] +
    1266      917553 :                     (indicesInnerLoop[i] -
    1267      917553 :                      blockIndices[i] * m_anInnerBlockSize[i]));
    1268             :             }
    1269      452561 :             const GByte *src_ptr = pabySrcBlock + nOffset * nSrcDTSize;
    1270      452561 :             const auto step = nDims == 0 ? 0 : arrayStep[dimIdxSubLoop];
    1271             : 
    1272      451411 :             if (m_bUseOptimizedCodePaths && bBothAreNumericDT &&
    1273      451386 :                 step <= static_cast<GIntBig>(std::numeric_limits<int>::max() /
    1274      903972 :                                              nDTSize) &&
    1275      451386 :                 dstBufferStrideBytes[dimIdxSubLoop] <=
    1276      451386 :                     std::numeric_limits<int>::max())
    1277             :             {
    1278      451386 :                 GDALCopyWords64(
    1279             :                     src_ptr, m_oType.GetNumericDataType(),
    1280             :                     static_cast<int>(step * nDTSize), dst_ptr,
    1281             :                     bufferDataType.GetNumericDataType(),
    1282      451386 :                     static_cast<int>(dstBufferStrideBytes[dimIdxSubLoop]),
    1283      451386 :                     static_cast<GPtrDiff_t>(countInnerLoopInit[dimIdxSubLoop]));
    1284             : 
    1285      451386 :                 goto end_inner_loop;
    1286             :             }
    1287             : 
    1288        3393 :             for (size_t i = 0; i < countInnerLoopInit[dimIdxSubLoop];
    1289        2218 :                  ++i, src_ptr += step * nSrcDTSize,
    1290        2218 :                         dst_ptr = static_cast<uint8_t *>(dst_ptr) +
    1291        2218 :                                   dstBufferStrideBytes[dimIdxSubLoop])
    1292             :             {
    1293        2218 :                 if (bSameNumericDT)
    1294             :                 {
    1295         814 :                     const void *src_ptr_v = src_ptr;
    1296         814 :                     if (nSameDTSize == 1)
    1297          70 :                         *static_cast<uint8_t *>(dst_ptr) =
    1298          70 :                             *static_cast<const uint8_t *>(src_ptr_v);
    1299         744 :                     else if (nSameDTSize == 2)
    1300             :                     {
    1301          56 :                         *static_cast<uint16_t *>(dst_ptr) =
    1302          56 :                             *static_cast<const uint16_t *>(src_ptr_v);
    1303             :                     }
    1304         688 :                     else if (nSameDTSize == 4)
    1305             :                     {
    1306         154 :                         *static_cast<uint32_t *>(dst_ptr) =
    1307         154 :                             *static_cast<const uint32_t *>(src_ptr_v);
    1308             :                     }
    1309         534 :                     else if (nSameDTSize == 8)
    1310             :                     {
    1311         482 :                         *static_cast<uint64_t *>(dst_ptr) =
    1312         482 :                             *static_cast<const uint64_t *>(src_ptr_v);
    1313             :                     }
    1314          52 :                     else if (nSameDTSize == 16)
    1315             :                     {
    1316          52 :                         static_cast<uint64_t *>(dst_ptr)[0] =
    1317          52 :                             static_cast<const uint64_t *>(src_ptr_v)[0];
    1318          52 :                         static_cast<uint64_t *>(dst_ptr)[1] =
    1319          52 :                             static_cast<const uint64_t *>(src_ptr_v)[1];
    1320             :                     }
    1321             :                     else
    1322             :                     {
    1323           0 :                         CPLAssert(false);
    1324             :                     }
    1325             :                 }
    1326        1404 :                 else if (bSameCompoundAndNoDynamicMem)
    1327             :                 {
    1328           4 :                     memcpy(dst_ptr, src_ptr, nDTSize);
    1329             :                 }
    1330        1400 :                 else if (m_oType.GetClass() == GEDTC_STRING)
    1331             :                 {
    1332          27 :                     if (m_aoDtypeElts.back().nativeType ==
    1333             :                         DtypeElt::NativeType::STRING_UNICODE)
    1334             :                     {
    1335             :                         char *pDstStr =
    1336          14 :                             UCS4ToUTF8(src_ptr, nSourceSize,
    1337           7 :                                        m_aoDtypeElts.back().needByteSwapping);
    1338           7 :                         char **pDstPtr = static_cast<char **>(dst_ptr);
    1339           7 :                         memcpy(pDstPtr, &pDstStr, sizeof(pDstStr));
    1340             :                     }
    1341             :                     else
    1342             :                     {
    1343             :                         char *pDstStr =
    1344          20 :                             static_cast<char *>(CPLMalloc(nSourceSize + 1));
    1345          20 :                         memcpy(pDstStr, src_ptr, nSourceSize);
    1346          20 :                         pDstStr[nSourceSize] = 0;
    1347          20 :                         char **pDstPtr = static_cast<char **>(dst_ptr);
    1348          20 :                         memcpy(pDstPtr, &pDstStr, sizeof(char *));
    1349             :                     }
    1350             :                 }
    1351             :                 else
    1352             :                 {
    1353        1373 :                     GDALExtendedDataType::CopyValue(src_ptr, m_oType, dst_ptr,
    1354             :                                                     bufferDataType);
    1355             :                 }
    1356        1175 :             }
    1357             :         }
    1358             :         else
    1359             :         {
    1360             :             // This level of loop loops over individual samples, within a
    1361             :             // block
    1362       28159 :             indicesInnerLoop[dimIdxSubLoop] = indicesOuterLoop[dimIdxSubLoop];
    1363       28159 :             countInnerLoop[dimIdxSubLoop] = countInnerLoopInit[dimIdxSubLoop];
    1364             :             while (true)
    1365             :             {
    1366      455480 :                 dimIdxSubLoop++;
    1367      455480 :                 dstPtrStackInnerLoop[dimIdxSubLoop] =
    1368      455480 :                     dstPtrStackInnerLoop[dimIdxSubLoop - 1];
    1369      455480 :                 goto lbl_next_depth_inner_loop;
    1370      455480 :             lbl_return_to_caller_inner_loop:
    1371      455480 :                 dimIdxSubLoop--;
    1372      455480 :                 --countInnerLoop[dimIdxSubLoop];
    1373      455480 :                 if (countInnerLoop[dimIdxSubLoop] == 0)
    1374             :                 {
    1375       28159 :                     break;
    1376             :                 }
    1377      427321 :                 indicesInnerLoop[dimIdxSubLoop] += arrayStep[dimIdxSubLoop];
    1378      427321 :                 dstPtrStackInnerLoop[dimIdxSubLoop] +=
    1379      427321 :                     dstBufferStrideBytes[dimIdxSubLoop];
    1380             :             }
    1381             :         }
    1382      483517 :     end_inner_loop:
    1383      483517 :         if (dimIdxSubLoop > 0)
    1384      455480 :             goto lbl_return_to_caller_inner_loop;
    1385             :     }
    1386             :     else
    1387             :     {
    1388             :         // This level of loop loops over blocks
    1389        6346 :         indicesOuterLoop[dimIdx] = arrayStartIdx[dimIdx];
    1390        6346 :         blockIndices[dimIdx] =
    1391        6346 :             indicesOuterLoop[dimIdx] / m_anInnerBlockSize[dimIdx];
    1392             :         while (true)
    1393             :         {
    1394       32683 :             dimIdx++;
    1395       32683 :             dstPtrStackOuterLoop[dimIdx] = dstPtrStackOuterLoop[dimIdx - 1];
    1396       32683 :             goto lbl_next_depth;
    1397       31537 :         lbl_return_to_caller:
    1398       31537 :             dimIdx--;
    1399       31537 :             if (count[dimIdx] == 1 || arrayStep[dimIdx] == 0)
    1400             :                 break;
    1401             : 
    1402             :             size_t nIncr;
    1403       30443 :             if (static_cast<GUInt64>(arrayStep[dimIdx]) <
    1404       30443 :                 m_anInnerBlockSize[dimIdx])
    1405             :             {
    1406             :                 // Compute index at next block boundary
    1407             :                 auto newIdx =
    1408       30030 :                     indicesOuterLoop[dimIdx] +
    1409       30030 :                     (m_anInnerBlockSize[dimIdx] -
    1410       30030 :                      (indicesOuterLoop[dimIdx] % m_anInnerBlockSize[dimIdx]));
    1411             :                 // And round up compared to arrayStartIdx, arrayStep
    1412       30030 :                 nIncr = static_cast<size_t>(cpl::div_round_up(
    1413       30030 :                     newIdx - indicesOuterLoop[dimIdx], arrayStep[dimIdx]));
    1414             :             }
    1415             :             else
    1416             :             {
    1417         413 :                 nIncr = 1;
    1418             :             }
    1419       30443 :             indicesOuterLoop[dimIdx] += nIncr * arrayStep[dimIdx];
    1420       30443 :             if (indicesOuterLoop[dimIdx] >
    1421       30443 :                 arrayStartIdx[dimIdx] + (count[dimIdx] - 1) * arrayStep[dimIdx])
    1422        4106 :                 break;
    1423       26337 :             dstPtrStackOuterLoop[dimIdx] +=
    1424       26337 :                 bufferStride[dimIdx] *
    1425       26337 :                 static_cast<GPtrDiff_t>(nIncr * nBufferDTSize);
    1426       52674 :             blockIndices[dimIdx] =
    1427       26337 :                 indicesOuterLoop[dimIdx] / m_anInnerBlockSize[dimIdx];
    1428       26337 :         }
    1429             :     }
    1430       33237 :     if (dimIdx > 0)
    1431       31537 :         goto lbl_return_to_caller;
    1432             : 
    1433        1700 :     return true;
    1434             : }
    1435             : 
    1436             : /************************************************************************/
    1437             : /*                           ZarrArray::IWrite()                        */
    1438             : /************************************************************************/
    1439             : 
    1440         494 : bool ZarrArray::IWrite(const GUInt64 *arrayStartIdx, const size_t *count,
    1441             :                        const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
    1442             :                        const GDALExtendedDataType &bufferDataType,
    1443             :                        const void *pSrcBuffer)
    1444             : {
    1445         494 :     if (!CheckValidAndErrorOutIfNot())
    1446           0 :         return false;
    1447             : 
    1448         494 :     if (!AllocateWorkingBuffers())
    1449           0 :         return false;
    1450             : 
    1451         494 :     m_oChunkCache.clear();
    1452             : 
    1453             :     // Need to be kept in top-level scope
    1454         988 :     std::vector<GUInt64> arrayStartIdxMod;
    1455         988 :     std::vector<GInt64> arrayStepMod;
    1456         988 :     std::vector<GPtrDiff_t> bufferStrideMod;
    1457             : 
    1458         494 :     const size_t nDims = m_aoDims.size();
    1459         494 :     bool negativeStep = false;
    1460         494 :     bool bWriteWholeBlockInit = true;
    1461        1384 :     for (size_t i = 0; i < nDims; ++i)
    1462             :     {
    1463         890 :         if (arrayStep[i] < 0)
    1464             :         {
    1465         132 :             negativeStep = true;
    1466         132 :             if (arrayStep[i] != -1 && count[i] > 1)
    1467           0 :                 bWriteWholeBlockInit = false;
    1468             :         }
    1469         758 :         else if (arrayStep[i] != 1 && count[i] > 1)
    1470           0 :             bWriteWholeBlockInit = false;
    1471             :     }
    1472             : 
    1473         494 :     const auto nBufferDTSize = static_cast<int>(bufferDataType.GetSize());
    1474             : 
    1475             :     // Make sure that arrayStep[i] are positive for sake of simplicity
    1476         494 :     if (negativeStep)
    1477             :     {
    1478             : #if defined(__GNUC__)
    1479             : #pragma GCC diagnostic push
    1480             : #pragma GCC diagnostic ignored "-Wnull-dereference"
    1481             : #endif
    1482          66 :         arrayStartIdxMod.resize(nDims);
    1483          66 :         arrayStepMod.resize(nDims);
    1484          66 :         bufferStrideMod.resize(nDims);
    1485             : #if defined(__GNUC__)
    1486             : #pragma GCC diagnostic pop
    1487             : #endif
    1488         198 :         for (size_t i = 0; i < nDims; ++i)
    1489             :         {
    1490         132 :             if (arrayStep[i] < 0)
    1491             :             {
    1492         264 :                 arrayStartIdxMod[i] =
    1493         132 :                     arrayStartIdx[i] - (count[i] - 1) * (-arrayStep[i]);
    1494         132 :                 arrayStepMod[i] = -arrayStep[i];
    1495         132 :                 bufferStrideMod[i] = -bufferStride[i];
    1496         132 :                 pSrcBuffer =
    1497             :                     static_cast<const GByte *>(pSrcBuffer) +
    1498         132 :                     bufferStride[i] *
    1499         132 :                         static_cast<GPtrDiff_t>(nBufferDTSize * (count[i] - 1));
    1500             :             }
    1501             :             else
    1502             :             {
    1503           0 :                 arrayStartIdxMod[i] = arrayStartIdx[i];
    1504           0 :                 arrayStepMod[i] = arrayStep[i];
    1505           0 :                 bufferStrideMod[i] = bufferStride[i];
    1506             :             }
    1507             :         }
    1508          66 :         arrayStartIdx = arrayStartIdxMod.data();
    1509          66 :         arrayStep = arrayStepMod.data();
    1510          66 :         bufferStride = bufferStrideMod.data();
    1511             :     }
    1512             : 
    1513         988 :     std::vector<uint64_t> indicesOuterLoop(nDims + 1);
    1514         988 :     std::vector<const GByte *> srcPtrStackOuterLoop(nDims + 1);
    1515             : 
    1516         988 :     std::vector<size_t> offsetDstBuffer(nDims + 1);
    1517         988 :     std::vector<const GByte *> srcPtrStackInnerLoop(nDims + 1);
    1518             : 
    1519         988 :     std::vector<GPtrDiff_t> srcBufferStrideBytes;
    1520        1384 :     for (size_t i = 0; i < nDims; ++i)
    1521             :     {
    1522         890 :         srcBufferStrideBytes.push_back(bufferStride[i] *
    1523         890 :                                        static_cast<GPtrDiff_t>(nBufferDTSize));
    1524             :     }
    1525         494 :     srcBufferStrideBytes.push_back(0);
    1526             : 
    1527         494 :     const auto nDTSize = m_oType.GetSize();
    1528             : 
    1529         988 :     std::vector<uint64_t> blockIndices(nDims);
    1530             :     const size_t nNativeSize =
    1531         494 :         m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
    1532             : 
    1533         988 :     std::vector<size_t> countInnerLoopInit(nDims + 1, 1);
    1534         988 :     std::vector<size_t> countInnerLoop(nDims);
    1535             : 
    1536         984 :     const bool bBothAreNumericDT = m_oType.GetClass() == GEDTC_NUMERIC &&
    1537         490 :                                    bufferDataType.GetClass() == GEDTC_NUMERIC;
    1538             :     const bool bSameNumericDT =
    1539         984 :         bBothAreNumericDT &&
    1540         490 :         m_oType.GetNumericDataType() == bufferDataType.GetNumericDataType();
    1541         494 :     const auto nSameDTSize = bSameNumericDT ? m_oType.GetSize() : 0;
    1542             :     const bool bSameCompoundAndNoDynamicMem =
    1543         494 :         m_oType.GetClass() == GEDTC_COMPOUND && m_oType == bufferDataType &&
    1544           0 :         !m_oType.NeedsFreeDynamicMemory();
    1545             : 
    1546         494 :     size_t dimIdx = 0;
    1547         494 :     size_t dimIdxForCopy = nDims == 0 ? 0 : nDims - 1;
    1548         494 :     if (nDims)
    1549             :     {
    1550         562 :         while (dimIdxForCopy > 0 && count[dimIdxForCopy] == 1)
    1551          68 :             --dimIdxForCopy;
    1552             :     }
    1553             : 
    1554         494 :     srcPtrStackOuterLoop[0] = static_cast<const GByte *>(pSrcBuffer);
    1555       24644 : lbl_next_depth:
    1556       24644 :     if (dimIdx == nDims)
    1557             :     {
    1558       22820 :         bool bWriteWholeBlock = bWriteWholeBlockInit;
    1559       22820 :         bool bPartialBlock = false;
    1560       68623 :         for (size_t i = 0; i < nDims; ++i)
    1561             :         {
    1562       45803 :             countInnerLoopInit[i] = 1;
    1563       45803 :             if (arrayStep[i] != 0)
    1564             :             {
    1565             :                 const auto nextBlockIdx =
    1566       45520 :                     std::min((1 + indicesOuterLoop[i] / m_anOuterBlockSize[i]) *
    1567       45520 :                                  m_anOuterBlockSize[i],
    1568       91040 :                              arrayStartIdx[i] + count[i] * arrayStep[i]);
    1569       45520 :                 countInnerLoopInit[i] = static_cast<size_t>(cpl::div_round_up(
    1570       45520 :                     nextBlockIdx - indicesOuterLoop[i], arrayStep[i]));
    1571             :             }
    1572       45803 :             if (bWriteWholeBlock)
    1573             :             {
    1574             :                 const bool bWholePartialBlockThisDim =
    1575       46689 :                     indicesOuterLoop[i] == 0 &&
    1576        1834 :                     countInnerLoopInit[i] == m_aoDims[i]->GetSize();
    1577       44855 :                 bWriteWholeBlock =
    1578       44855 :                     (countInnerLoopInit[i] == m_anOuterBlockSize[i] ||
    1579             :                      bWholePartialBlockThisDim);
    1580       44855 :                 if (bWholePartialBlockThisDim)
    1581             :                 {
    1582         587 :                     bPartialBlock = true;
    1583             :                 }
    1584             :             }
    1585             :         }
    1586             : 
    1587       22820 :         size_t dimIdxSubLoop = 0;
    1588       22820 :         srcPtrStackInnerLoop[0] = srcPtrStackOuterLoop[nDims];
    1589             :         const size_t nCacheDTSize =
    1590       22820 :             m_abyDecodedBlockData.empty() ? nNativeSize : nDTSize;
    1591       22820 :         auto &abyBlock = m_abyDecodedBlockData.empty() ? m_abyRawBlockData
    1592       22820 :                                                        : m_abyDecodedBlockData;
    1593             : 
    1594       22820 :         if (!blockIndices.empty() && blockIndices == m_anCachedBlockIndices)
    1595             :         {
    1596           4 :             if (!m_bCachedBlockValid)
    1597           0 :                 return false;
    1598             :         }
    1599             :         else
    1600             :         {
    1601       22816 :             if (!FlushDirtyBlock())
    1602           0 :                 return false;
    1603             : 
    1604       22816 :             m_anCachedBlockIndices = blockIndices;
    1605       22816 :             m_bCachedBlockValid = true;
    1606             : 
    1607       22816 :             if (bWriteWholeBlock)
    1608             :             {
    1609       21292 :                 if (bPartialBlock)
    1610             :                 {
    1611         288 :                     DeallocateDecodedBlockData();
    1612         288 :                     memset(&abyBlock[0], 0, abyBlock.size());
    1613             :                 }
    1614             :             }
    1615             :             else
    1616             :             {
    1617             :                 // If we don't write the whole tile, we need to fetch a
    1618             :                 // potentially existing one.
    1619        1524 :                 bool bEmptyBlock = false;
    1620        1524 :                 m_bCachedBlockValid =
    1621        1524 :                     LoadBlockData(blockIndices.data(), bEmptyBlock);
    1622        1524 :                 if (!m_bCachedBlockValid)
    1623             :                 {
    1624           0 :                     return false;
    1625             :                 }
    1626             : 
    1627        1524 :                 if (bEmptyBlock)
    1628             :                 {
    1629        1215 :                     DeallocateDecodedBlockData();
    1630             : 
    1631        1215 :                     if (m_pabyNoData == nullptr)
    1632             :                     {
    1633         495 :                         memset(&abyBlock[0], 0, abyBlock.size());
    1634             :                     }
    1635             :                     else
    1636             :                     {
    1637         720 :                         const size_t nElts = abyBlock.size() / nCacheDTSize;
    1638         720 :                         GByte *dstPtr = &abyBlock[0];
    1639         720 :                         if (m_oType.GetClass() == GEDTC_NUMERIC)
    1640             :                         {
    1641         720 :                             GDALCopyWords64(
    1642         720 :                                 m_pabyNoData, m_oType.GetNumericDataType(), 0,
    1643             :                                 dstPtr, m_oType.GetNumericDataType(),
    1644         720 :                                 static_cast<int>(m_oType.GetSize()),
    1645             :                                 static_cast<GPtrDiff_t>(nElts));
    1646             :                         }
    1647             :                         else
    1648             :                         {
    1649           0 :                             for (size_t i = 0; i < nElts; ++i)
    1650             :                             {
    1651           0 :                                 GDALExtendedDataType::CopyValue(
    1652           0 :                                     m_pabyNoData, m_oType, dstPtr, m_oType);
    1653           0 :                                 dstPtr += nCacheDTSize;
    1654             :                             }
    1655             :                         }
    1656             :                     }
    1657             :                 }
    1658             :             }
    1659             :         }
    1660       22820 :         m_bDirtyBlock = true;
    1661       22820 :         m_bCachedBlockEmpty = false;
    1662       22820 :         if (nDims)
    1663       22820 :             offsetDstBuffer[0] = static_cast<size_t>(
    1664       22820 :                 indicesOuterLoop[0] - blockIndices[0] * m_anOuterBlockSize[0]);
    1665             : 
    1666       22820 :         GByte *pabyBlock = &abyBlock[0];
    1667             : 
    1668      453424 :     lbl_next_depth_inner_loop:
    1669      453424 :         if (dimIdxSubLoop == dimIdxForCopy)
    1670             :         {
    1671      430666 :             size_t nOffset = offsetDstBuffer[dimIdxSubLoop];
    1672      430666 :             GInt64 step = nDims == 0 ? 0 : arrayStep[dimIdxSubLoop];
    1673      430891 :             for (size_t i = dimIdxSubLoop + 1; i < nDims; ++i)
    1674             :             {
    1675         225 :                 nOffset = static_cast<size_t>(
    1676         225 :                     nOffset * m_anOuterBlockSize[i] +
    1677         225 :                     (indicesOuterLoop[i] -
    1678         225 :                      blockIndices[i] * m_anOuterBlockSize[i]));
    1679         225 :                 step *= m_anOuterBlockSize[i];
    1680             :             }
    1681      430666 :             const void *src_ptr = srcPtrStackInnerLoop[dimIdxSubLoop];
    1682      430666 :             GByte *dst_ptr = pabyBlock + nOffset * nCacheDTSize;
    1683             : 
    1684      430666 :             if (m_bUseOptimizedCodePaths && bBothAreNumericDT)
    1685             :             {
    1686      429936 :                 if (countInnerLoopInit[dimIdxSubLoop] == 1 && bSameNumericDT)
    1687             :                 {
    1688          86 :                     void *dst_ptr_v = dst_ptr;
    1689          86 :                     if (nSameDTSize == 1)
    1690           8 :                         *static_cast<uint8_t *>(dst_ptr_v) =
    1691           8 :                             *static_cast<const uint8_t *>(src_ptr);
    1692          78 :                     else if (nSameDTSize == 2)
    1693             :                     {
    1694           2 :                         *static_cast<uint16_t *>(dst_ptr_v) =
    1695           2 :                             *static_cast<const uint16_t *>(src_ptr);
    1696             :                     }
    1697          76 :                     else if (nSameDTSize == 4)
    1698             :                     {
    1699           2 :                         *static_cast<uint32_t *>(dst_ptr_v) =
    1700           2 :                             *static_cast<const uint32_t *>(src_ptr);
    1701             :                     }
    1702          74 :                     else if (nSameDTSize == 8)
    1703             :                     {
    1704          52 :                         *static_cast<uint64_t *>(dst_ptr_v) =
    1705          52 :                             *static_cast<const uint64_t *>(src_ptr);
    1706             :                     }
    1707          22 :                     else if (nSameDTSize == 16)
    1708             :                     {
    1709          22 :                         static_cast<uint64_t *>(dst_ptr_v)[0] =
    1710          22 :                             static_cast<const uint64_t *>(src_ptr)[0];
    1711          22 :                         static_cast<uint64_t *>(dst_ptr_v)[1] =
    1712          22 :                             static_cast<const uint64_t *>(src_ptr)[1];
    1713             :                     }
    1714             :                     else
    1715             :                     {
    1716           0 :                         CPLAssert(false);
    1717             :                     }
    1718             :                 }
    1719      429850 :                 else if (step <=
    1720      429850 :                              static_cast<GIntBig>(
    1721      859700 :                                  std::numeric_limits<int>::max() / nDTSize) &&
    1722      429850 :                          srcBufferStrideBytes[dimIdxSubLoop] <=
    1723      429850 :                              std::numeric_limits<int>::max())
    1724             :                 {
    1725      859700 :                     GDALCopyWords64(
    1726             :                         src_ptr, bufferDataType.GetNumericDataType(),
    1727      429850 :                         static_cast<int>(srcBufferStrideBytes[dimIdxSubLoop]),
    1728             :                         dst_ptr, m_oType.GetNumericDataType(),
    1729             :                         static_cast<int>(step * nDTSize),
    1730             :                         static_cast<GPtrDiff_t>(
    1731      429850 :                             countInnerLoopInit[dimIdxSubLoop]));
    1732             :                 }
    1733      429936 :                 goto end_inner_loop;
    1734             :             }
    1735             : 
    1736        2188 :             for (size_t i = 0; i < countInnerLoopInit[dimIdxSubLoop];
    1737        1458 :                  ++i, dst_ptr += step * nCacheDTSize,
    1738        1458 :                         src_ptr = static_cast<const uint8_t *>(src_ptr) +
    1739        1458 :                                   srcBufferStrideBytes[dimIdxSubLoop])
    1740             :             {
    1741        1458 :                 if (bSameNumericDT)
    1742             :                 {
    1743         300 :                     void *dst_ptr_v = dst_ptr;
    1744         300 :                     if (nSameDTSize == 1)
    1745           0 :                         *static_cast<uint8_t *>(dst_ptr_v) =
    1746           0 :                             *static_cast<const uint8_t *>(src_ptr);
    1747         300 :                     else if (nSameDTSize == 2)
    1748             :                     {
    1749           0 :                         *static_cast<uint16_t *>(dst_ptr_v) =
    1750           0 :                             *static_cast<const uint16_t *>(src_ptr);
    1751             :                     }
    1752         300 :                     else if (nSameDTSize == 4)
    1753             :                     {
    1754           0 :                         *static_cast<uint32_t *>(dst_ptr_v) =
    1755           0 :                             *static_cast<const uint32_t *>(src_ptr);
    1756             :                     }
    1757         300 :                     else if (nSameDTSize == 8)
    1758             :                     {
    1759         220 :                         *static_cast<uint64_t *>(dst_ptr_v) =
    1760         220 :                             *static_cast<const uint64_t *>(src_ptr);
    1761             :                     }
    1762          80 :                     else if (nSameDTSize == 16)
    1763             :                     {
    1764          80 :                         static_cast<uint64_t *>(dst_ptr_v)[0] =
    1765          80 :                             static_cast<const uint64_t *>(src_ptr)[0];
    1766          80 :                         static_cast<uint64_t *>(dst_ptr_v)[1] =
    1767          80 :                             static_cast<const uint64_t *>(src_ptr)[1];
    1768             :                     }
    1769             :                     else
    1770             :                     {
    1771           0 :                         CPLAssert(false);
    1772             :                     }
    1773             :                 }
    1774        1158 :                 else if (bSameCompoundAndNoDynamicMem)
    1775             :                 {
    1776           0 :                     memcpy(dst_ptr, src_ptr, nDTSize);
    1777             :                 }
    1778        1158 :                 else if (m_oType.GetClass() == GEDTC_STRING)
    1779             :                 {
    1780           6 :                     const char *pSrcStr =
    1781             :                         *static_cast<const char *const *>(src_ptr);
    1782           6 :                     if (pSrcStr)
    1783             :                     {
    1784           6 :                         const size_t nLen = strlen(pSrcStr);
    1785           6 :                         if (m_aoDtypeElts.back().nativeType ==
    1786             :                             DtypeElt::NativeType::STRING_UNICODE)
    1787             :                         {
    1788             :                             try
    1789             :                             {
    1790             :                                 const auto ucs4 = UTF8ToUCS4(
    1791             :                                     pSrcStr,
    1792           8 :                                     m_aoDtypeElts.back().needByteSwapping);
    1793           4 :                                 const auto ucs4Len = ucs4.size();
    1794           4 :                                 memcpy(dst_ptr, ucs4.data(),
    1795           4 :                                        std::min(ucs4Len, nNativeSize));
    1796           4 :                                 if (ucs4Len > nNativeSize)
    1797             :                                 {
    1798           1 :                                     CPLError(CE_Warning, CPLE_AppDefined,
    1799             :                                              "Too long string truncated");
    1800             :                                 }
    1801           3 :                                 else if (ucs4Len < nNativeSize)
    1802             :                                 {
    1803           1 :                                     memset(dst_ptr + ucs4Len, 0,
    1804           1 :                                            nNativeSize - ucs4Len);
    1805             :                                 }
    1806             :                             }
    1807           0 :                             catch (const std::exception &)
    1808             :                             {
    1809           0 :                                 memset(dst_ptr, 0, nNativeSize);
    1810             :                             }
    1811             :                         }
    1812             :                         else
    1813             :                         {
    1814           2 :                             memcpy(dst_ptr, pSrcStr,
    1815           2 :                                    std::min(nLen, nNativeSize));
    1816           2 :                             if (nLen < nNativeSize)
    1817           1 :                                 memset(dst_ptr + nLen, 0, nNativeSize - nLen);
    1818             :                         }
    1819             :                     }
    1820             :                     else
    1821             :                     {
    1822           0 :                         memset(dst_ptr, 0, nNativeSize);
    1823             :                     }
    1824             :                 }
    1825             :                 else
    1826             :                 {
    1827        1152 :                     if (m_oType.NeedsFreeDynamicMemory())
    1828           0 :                         m_oType.FreeDynamicMemory(dst_ptr);
    1829        1152 :                     GDALExtendedDataType::CopyValue(src_ptr, bufferDataType,
    1830        1152 :                                                     dst_ptr, m_oType);
    1831             :                 }
    1832             :             }
    1833             :         }
    1834             :         else
    1835             :         {
    1836             :             // This level of loop loops over individual samples, within a
    1837             :             // block
    1838       22758 :             countInnerLoop[dimIdxSubLoop] = countInnerLoopInit[dimIdxSubLoop];
    1839             :             while (true)
    1840             :             {
    1841      430604 :                 dimIdxSubLoop++;
    1842      430604 :                 srcPtrStackInnerLoop[dimIdxSubLoop] =
    1843      430604 :                     srcPtrStackInnerLoop[dimIdxSubLoop - 1];
    1844      861208 :                 offsetDstBuffer[dimIdxSubLoop] = static_cast<size_t>(
    1845     1291810 :                     offsetDstBuffer[dimIdxSubLoop - 1] *
    1846      430604 :                         m_anOuterBlockSize[dimIdxSubLoop] +
    1847      430604 :                     (indicesOuterLoop[dimIdxSubLoop] -
    1848      861208 :                      blockIndices[dimIdxSubLoop] *
    1849      430604 :                          m_anOuterBlockSize[dimIdxSubLoop]));
    1850      430604 :                 goto lbl_next_depth_inner_loop;
    1851      430604 :             lbl_return_to_caller_inner_loop:
    1852      430604 :                 dimIdxSubLoop--;
    1853      430604 :                 --countInnerLoop[dimIdxSubLoop];
    1854      430604 :                 if (countInnerLoop[dimIdxSubLoop] == 0)
    1855             :                 {
    1856       22758 :                     break;
    1857             :                 }
    1858      407846 :                 srcPtrStackInnerLoop[dimIdxSubLoop] +=
    1859      407846 :                     srcBufferStrideBytes[dimIdxSubLoop];
    1860      407846 :                 offsetDstBuffer[dimIdxSubLoop] +=
    1861      407846 :                     static_cast<size_t>(arrayStep[dimIdxSubLoop]);
    1862             :             }
    1863             :         }
    1864      430666 :     end_inner_loop:
    1865      453424 :         if (dimIdxSubLoop > 0)
    1866      430604 :             goto lbl_return_to_caller_inner_loop;
    1867             :     }
    1868             :     else
    1869             :     {
    1870             :         // This level of loop loops over blocks
    1871        1824 :         indicesOuterLoop[dimIdx] = arrayStartIdx[dimIdx];
    1872        1824 :         blockIndices[dimIdx] =
    1873        1824 :             indicesOuterLoop[dimIdx] / m_anOuterBlockSize[dimIdx];
    1874             :         while (true)
    1875             :         {
    1876       24150 :             dimIdx++;
    1877       24150 :             srcPtrStackOuterLoop[dimIdx] = srcPtrStackOuterLoop[dimIdx - 1];
    1878       24150 :             goto lbl_next_depth;
    1879       24150 :         lbl_return_to_caller:
    1880       24150 :             dimIdx--;
    1881       24150 :             if (count[dimIdx] == 1 || arrayStep[dimIdx] == 0)
    1882             :                 break;
    1883             : 
    1884             :             size_t nIncr;
    1885       23897 :             if (static_cast<GUInt64>(arrayStep[dimIdx]) <
    1886       23897 :                 m_anOuterBlockSize[dimIdx])
    1887             :             {
    1888             :                 // Compute index at next block boundary
    1889             :                 auto newIdx =
    1890       23705 :                     indicesOuterLoop[dimIdx] +
    1891       23705 :                     (m_anOuterBlockSize[dimIdx] -
    1892       23705 :                      (indicesOuterLoop[dimIdx] % m_anOuterBlockSize[dimIdx]));
    1893             :                 // And round up compared to arrayStartIdx, arrayStep
    1894       23705 :                 nIncr = static_cast<size_t>(cpl::div_round_up(
    1895       23705 :                     newIdx - indicesOuterLoop[dimIdx], arrayStep[dimIdx]));
    1896             :             }
    1897             :             else
    1898             :             {
    1899         192 :                 nIncr = 1;
    1900             :             }
    1901       23897 :             indicesOuterLoop[dimIdx] += nIncr * arrayStep[dimIdx];
    1902       23897 :             if (indicesOuterLoop[dimIdx] >
    1903       23897 :                 arrayStartIdx[dimIdx] + (count[dimIdx] - 1) * arrayStep[dimIdx])
    1904        1571 :                 break;
    1905       22326 :             srcPtrStackOuterLoop[dimIdx] +=
    1906       22326 :                 bufferStride[dimIdx] *
    1907       22326 :                 static_cast<GPtrDiff_t>(nIncr * nBufferDTSize);
    1908       44652 :             blockIndices[dimIdx] =
    1909       22326 :                 indicesOuterLoop[dimIdx] / m_anOuterBlockSize[dimIdx];
    1910       22326 :         }
    1911             :     }
    1912       24644 :     if (dimIdx > 0)
    1913       24150 :         goto lbl_return_to_caller;
    1914             : 
    1915         494 :     return true;
    1916             : }
    1917             : 
    1918             : /************************************************************************/
    1919             : /*                   ZarrArray::IsEmptyBlock()                           */
    1920             : /************************************************************************/
    1921             : 
    1922       22820 : bool ZarrArray::IsEmptyBlock(const ZarrByteVectorQuickResize &abyBlock) const
    1923             : {
    1924       44459 :     if (m_pabyNoData == nullptr || (m_oType.GetClass() == GEDTC_NUMERIC &&
    1925       21639 :                                     GetNoDataValueAsDouble() == 0.0))
    1926             :     {
    1927       22512 :         const size_t nBytes = abyBlock.size();
    1928       22512 :         size_t i = 0;
    1929       25762 :         for (; i + (sizeof(size_t) - 1) < nBytes; i += sizeof(size_t))
    1930             :         {
    1931       25115 :             if (*reinterpret_cast<const size_t *>(abyBlock.data() + i) != 0)
    1932             :             {
    1933       21865 :                 return false;
    1934             :             }
    1935             :         }
    1936        1576 :         for (; i < nBytes; ++i)
    1937             :         {
    1938         994 :             if (abyBlock[i] != 0)
    1939             :             {
    1940          65 :                 return false;
    1941             :             }
    1942             :         }
    1943         582 :         return true;
    1944             :     }
    1945         616 :     else if (m_oType.GetClass() == GEDTC_NUMERIC &&
    1946         308 :              !GDALDataTypeIsComplex(m_oType.GetNumericDataType()))
    1947             :     {
    1948         308 :         const int nDTSize = static_cast<int>(m_oType.GetSize());
    1949         308 :         const size_t nElts = abyBlock.size() / nDTSize;
    1950         308 :         const auto eDT = m_oType.GetNumericDataType();
    1951         616 :         return GDALBufferHasOnlyNoData(
    1952         308 :             abyBlock.data(), GetNoDataValueAsDouble(),
    1953             :             nElts,        // nWidth
    1954             :             1,            // nHeight
    1955             :             nElts,        // nLineStride
    1956             :             1,            // nComponents
    1957             :             nDTSize * 8,  // nBitsPerSample
    1958         308 :             GDALDataTypeIsInteger(eDT)
    1959         112 :                 ? (GDALDataTypeIsSigned(eDT) ? GSF_SIGNED_INT
    1960             :                                              : GSF_UNSIGNED_INT)
    1961         308 :                 : GSF_FLOATING_POINT);
    1962             :     }
    1963           0 :     return false;
    1964             : }
    1965             : 
    1966             : /************************************************************************/
    1967             : /*                  ZarrArray::OpenBlockPresenceCache()                 */
    1968             : /************************************************************************/
    1969             : 
    1970             : std::shared_ptr<GDALMDArray>
    1971       29933 : ZarrArray::OpenBlockPresenceCache(bool bCanCreate) const
    1972             : {
    1973       29933 :     if (m_bHasTriedBlockCachePresenceArray)
    1974       28840 :         return m_poBlockCachePresenceArray;
    1975        1093 :     m_bHasTriedBlockCachePresenceArray = true;
    1976             : 
    1977        1093 :     if (m_nTotalInnerChunkCount == 1)
    1978         388 :         return nullptr;
    1979             : 
    1980        1410 :     std::string osCacheFilename;
    1981        1410 :     auto poRGCache = GetCacheRootGroup(bCanCreate, osCacheFilename);
    1982         705 :     if (!poRGCache)
    1983         694 :         return nullptr;
    1984             : 
    1985          11 :     const std::string osBlockPresenceArrayName(MassageName(GetFullName()) +
    1986          22 :                                                "_tile_presence");
    1987             :     auto poBlockPresenceArray =
    1988          22 :         poRGCache->OpenMDArray(osBlockPresenceArrayName);
    1989          22 :     const auto eByteDT = GDALExtendedDataType::Create(GDT_UInt8);
    1990          11 :     if (poBlockPresenceArray)
    1991             :     {
    1992           8 :         bool ok = true;
    1993           8 :         const auto &apoDimsCache = poBlockPresenceArray->GetDimensions();
    1994          16 :         if (poBlockPresenceArray->GetDataType() != eByteDT ||
    1995           8 :             apoDimsCache.size() != m_aoDims.size())
    1996             :         {
    1997           0 :             ok = false;
    1998             :         }
    1999             :         else
    2000             :         {
    2001          24 :             for (size_t i = 0; i < m_aoDims.size(); i++)
    2002             :             {
    2003          16 :                 const auto nExpectedDimSize = cpl::div_round_up(
    2004          16 :                     m_aoDims[i]->GetSize(), m_anInnerBlockSize[i]);
    2005          16 :                 if (apoDimsCache[i]->GetSize() != nExpectedDimSize)
    2006             :                 {
    2007           0 :                     ok = false;
    2008           0 :                     break;
    2009             :                 }
    2010             :             }
    2011             :         }
    2012           8 :         if (!ok)
    2013             :         {
    2014           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    2015             :                      "Array %s in %s has not expected characteristics",
    2016             :                      osBlockPresenceArrayName.c_str(), osCacheFilename.c_str());
    2017           0 :             return nullptr;
    2018             :         }
    2019             : 
    2020           8 :         if (!poBlockPresenceArray->GetAttribute("filling_status") &&
    2021           0 :             !bCanCreate)
    2022             :         {
    2023           0 :             CPLDebug(ZARR_DEBUG_KEY,
    2024             :                      "Cache tile presence array for %s found, but filling not "
    2025             :                      "finished",
    2026           0 :                      GetFullName().c_str());
    2027           0 :             return nullptr;
    2028             :         }
    2029             : 
    2030           8 :         CPLDebug(ZARR_DEBUG_KEY, "Using cache tile presence for %s",
    2031           8 :                  GetFullName().c_str());
    2032             :     }
    2033           3 :     else if (bCanCreate)
    2034             :     {
    2035           3 :         int idxDim = 0;
    2036           3 :         std::string osBlockSize;
    2037           3 :         std::vector<std::shared_ptr<GDALDimension>> apoNewDims;
    2038           9 :         for (const auto &poDim : m_aoDims)
    2039             :         {
    2040           6 :             auto poNewDim = poRGCache->CreateDimension(
    2041          12 :                 osBlockPresenceArrayName + '_' + std::to_string(idxDim),
    2042          12 :                 std::string(), std::string(),
    2043             :                 cpl::div_round_up(poDim->GetSize(),
    2044          12 :                                   m_anInnerBlockSize[idxDim]));
    2045           6 :             if (!poNewDim)
    2046           0 :                 return nullptr;
    2047           6 :             apoNewDims.emplace_back(poNewDim);
    2048             : 
    2049           6 :             if (!osBlockSize.empty())
    2050           3 :                 osBlockSize += ',';
    2051           6 :             constexpr GUInt64 BLOCKSIZE = 256;
    2052             :             osBlockSize +=
    2053           6 :                 std::to_string(std::min(poNewDim->GetSize(), BLOCKSIZE));
    2054             : 
    2055           6 :             idxDim++;
    2056             :         }
    2057             : 
    2058           3 :         CPLStringList aosOptionsBlockPresence;
    2059           3 :         aosOptionsBlockPresence.SetNameValue("BLOCKSIZE", osBlockSize.c_str());
    2060             :         poBlockPresenceArray =
    2061           9 :             poRGCache->CreateMDArray(osBlockPresenceArrayName, apoNewDims,
    2062           6 :                                      eByteDT, aosOptionsBlockPresence.List());
    2063           3 :         if (!poBlockPresenceArray)
    2064             :         {
    2065           0 :             CPLError(CE_Failure, CPLE_NotSupported, "Cannot create %s in %s",
    2066             :                      osBlockPresenceArrayName.c_str(), osCacheFilename.c_str());
    2067           0 :             return nullptr;
    2068             :         }
    2069           3 :         poBlockPresenceArray->SetNoDataValue(0);
    2070             :     }
    2071             :     else
    2072             :     {
    2073           0 :         return nullptr;
    2074             :     }
    2075             : 
    2076          11 :     m_poBlockCachePresenceArray = poBlockPresenceArray;
    2077             : 
    2078          11 :     return poBlockPresenceArray;
    2079             : }
    2080             : 
    2081             : /************************************************************************/
    2082             : /*                    ZarrArray::BlockCachePresence()                   */
    2083             : /************************************************************************/
    2084             : 
    2085           5 : bool ZarrArray::BlockCachePresence()
    2086             : {
    2087           5 :     if (m_nTotalInnerChunkCount == 1)
    2088           0 :         return true;
    2089             : 
    2090          10 :     const std::string osDirectoryName = GetDataDirectory();
    2091             : 
    2092             :     auto psDir = std::unique_ptr<VSIDIR, decltype(&VSICloseDir)>(
    2093          10 :         VSIOpenDir(osDirectoryName.c_str(), -1, nullptr), VSICloseDir);
    2094           5 :     if (!psDir)
    2095           0 :         return false;
    2096             : 
    2097          10 :     auto poBlockPresenceArray = OpenBlockPresenceCache(true);
    2098           5 :     if (!poBlockPresenceArray)
    2099             :     {
    2100           0 :         return false;
    2101             :     }
    2102             : 
    2103           5 :     if (poBlockPresenceArray->GetAttribute("filling_status"))
    2104             :     {
    2105           2 :         CPLDebug(ZARR_DEBUG_KEY,
    2106             :                  "BlockCachePresence(): %s already filled. Nothing to do",
    2107           2 :                  poBlockPresenceArray->GetName().c_str());
    2108           2 :         return true;
    2109             :     }
    2110             : 
    2111           3 :     const auto nDims = m_aoDims.size();
    2112           6 :     std::vector<GUInt64> anInnerBlockIdx(nDims);
    2113           6 :     std::vector<GUInt64> anInnerBlockCounter(nDims);
    2114           6 :     const std::vector<size_t> anCount(nDims, 1);
    2115           6 :     const std::vector<GInt64> anArrayStep(nDims, 0);
    2116           6 :     const std::vector<GPtrDiff_t> anBufferStride(nDims, 0);
    2117           3 :     const auto &apoDimsCache = poBlockPresenceArray->GetDimensions();
    2118           6 :     const auto eByteDT = GDALExtendedDataType::Create(GDT_UInt8);
    2119             : 
    2120           3 :     CPLDebug(ZARR_DEBUG_KEY,
    2121             :              "BlockCachePresence(): Iterating over %s to find which tiles are "
    2122             :              "present...",
    2123             :              osDirectoryName.c_str());
    2124           3 :     uint64_t nCounter = 0;
    2125             :     const char chSrcFilenameDirSeparator =
    2126           3 :         VSIGetDirectorySeparator(osDirectoryName.c_str())[0];
    2127          30 :     while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir.get()))
    2128             :     {
    2129          27 :         if (!VSI_ISDIR(psEntry->nMode))
    2130             :         {
    2131             :             const CPLStringList aosTokens = GetChunkIndicesFromFilename(
    2132           0 :                 CPLString(psEntry->pszName)
    2133          20 :                     .replaceAll(chSrcFilenameDirSeparator, '/')
    2134          40 :                     .c_str());
    2135          20 :             if (aosTokens.size() == static_cast<int>(nDims))
    2136             :             {
    2137             :                 // Get tile indices from filename
    2138          14 :                 bool unexpectedIndex = false;
    2139          14 :                 uint64_t nInnerChunksInOuter = 1;
    2140          42 :                 for (int i = 0; i < aosTokens.size(); ++i)
    2141             :                 {
    2142          28 :                     if (CPLGetValueType(aosTokens[i]) != CPL_VALUE_INTEGER)
    2143             :                     {
    2144           2 :                         unexpectedIndex = true;
    2145             :                     }
    2146          56 :                     anInnerBlockIdx[i] =
    2147          28 :                         static_cast<GUInt64>(CPLAtoGIntBig(aosTokens[i]));
    2148             :                     const auto nInnerChunkCounterThisDim =
    2149          28 :                         m_anCountInnerBlockInOuter[i];
    2150          28 :                     nInnerChunksInOuter *= nInnerChunkCounterThisDim;
    2151          28 :                     if (anInnerBlockIdx[i] >=
    2152          28 :                         apoDimsCache[i]->GetSize() / nInnerChunkCounterThisDim)
    2153             :                     {
    2154           6 :                         unexpectedIndex = true;
    2155             :                     }
    2156          28 :                     anInnerBlockIdx[i] *= nInnerChunkCounterThisDim;
    2157             :                 }
    2158          14 :                 if (unexpectedIndex)
    2159             :                 {
    2160           6 :                     continue;
    2161             :                 }
    2162             : 
    2163           8 :                 std::fill(anInnerBlockCounter.begin(),
    2164           8 :                           anInnerBlockCounter.end(), 0);
    2165             : 
    2166          28 :                 for (uint64_t iInnerChunk = 0;
    2167          28 :                      iInnerChunk < nInnerChunksInOuter; ++iInnerChunk)
    2168             :                 {
    2169          20 :                     if (iInnerChunk > 0)
    2170             :                     {
    2171             :                         // Update chunk coordinates
    2172          12 :                         size_t iDim = m_anInnerBlockSize.size() - 1;
    2173             :                         const auto nInnerChunkCounterThisDim =
    2174          12 :                             m_anCountInnerBlockInOuter[iDim];
    2175             : 
    2176          12 :                         ++anInnerBlockIdx[iDim];
    2177          12 :                         ++anInnerBlockCounter[iDim];
    2178             : 
    2179          16 :                         while (anInnerBlockCounter[iDim] ==
    2180             :                                nInnerChunkCounterThisDim)
    2181             :                         {
    2182           4 :                             anInnerBlockIdx[iDim] -= nInnerChunkCounterThisDim;
    2183           4 :                             anInnerBlockCounter[iDim] = 0;
    2184           4 :                             --iDim;
    2185             : 
    2186           4 :                             ++anInnerBlockIdx[iDim];
    2187           4 :                             ++anInnerBlockCounter[iDim];
    2188             :                         }
    2189             :                     }
    2190             : 
    2191          20 :                     nCounter++;
    2192          20 :                     if ((nCounter % 1000) == 0)
    2193             :                     {
    2194           0 :                         CPLDebug(
    2195             :                             ZARR_DEBUG_KEY,
    2196             :                             "BlockCachePresence(): Listing in progress "
    2197             :                             "(last examined %s, at least %.02f %% completed)",
    2198           0 :                             psEntry->pszName,
    2199           0 :                             100.0 * double(nCounter) /
    2200           0 :                                 double(m_nTotalInnerChunkCount));
    2201             :                     }
    2202          20 :                     constexpr GByte byOne = 1;
    2203             :                     // CPLDebugOnly(ZARR_DEBUG_KEY, "Marking %s has present",
    2204             :                     // psEntry->pszName);
    2205          40 :                     if (!poBlockPresenceArray->Write(
    2206          20 :                             anInnerBlockIdx.data(), anCount.data(),
    2207             :                             anArrayStep.data(), anBufferStride.data(), eByteDT,
    2208             :                             &byOne))
    2209             :                     {
    2210           0 :                         return false;
    2211             :                     }
    2212             :                 }
    2213             :             }
    2214             :         }
    2215          27 :     }
    2216           3 :     CPLDebug(ZARR_DEBUG_KEY, "BlockCachePresence(): finished");
    2217             : 
    2218             :     // Write filling_status attribute
    2219           3 :     auto poAttr = poBlockPresenceArray->CreateAttribute(
    2220           6 :         "filling_status", {}, GDALExtendedDataType::CreateString(), nullptr);
    2221           3 :     if (poAttr)
    2222             :     {
    2223           3 :         if (nCounter == 0)
    2224           0 :             poAttr->Write("no_tile_present");
    2225           3 :         else if (nCounter == m_nTotalInnerChunkCount)
    2226           0 :             poAttr->Write("all_tiles_present");
    2227             :         else
    2228           3 :             poAttr->Write("some_tiles_missing");
    2229             :     }
    2230             : 
    2231             :     // Force closing
    2232           3 :     m_poBlockCachePresenceArray = nullptr;
    2233           3 :     m_bHasTriedBlockCachePresenceArray = false;
    2234             : 
    2235           3 :     return true;
    2236             : }
    2237             : 
    2238             : /************************************************************************/
    2239             : /*                      ZarrArray::CreateAttribute()                    */
    2240             : /************************************************************************/
    2241             : 
    2242         115 : std::shared_ptr<GDALAttribute> ZarrArray::CreateAttribute(
    2243             :     const std::string &osName, const std::vector<GUInt64> &anDimensions,
    2244             :     const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
    2245             : {
    2246         115 :     if (!CheckValidAndErrorOutIfNot())
    2247           0 :         return nullptr;
    2248             : 
    2249         115 :     if (!m_bUpdatable)
    2250             :     {
    2251           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    2252             :                  "Dataset not open in update mode");
    2253           2 :         return nullptr;
    2254             :     }
    2255         113 :     if (anDimensions.size() >= 2)
    2256             :     {
    2257           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    2258             :                  "Cannot create attributes of dimension >= 2");
    2259           2 :         return nullptr;
    2260             :     }
    2261             :     return m_oAttrGroup.CreateAttribute(osName, anDimensions, oDataType,
    2262         111 :                                         papszOptions);
    2263             : }
    2264             : 
    2265             : /************************************************************************/
    2266             : /*                  ZarrGroupBase::DeleteAttribute()                    */
    2267             : /************************************************************************/
    2268             : 
    2269          18 : bool ZarrArray::DeleteAttribute(const std::string &osName, CSLConstList)
    2270             : {
    2271          18 :     if (!CheckValidAndErrorOutIfNot())
    2272           0 :         return false;
    2273             : 
    2274          18 :     if (!m_bUpdatable)
    2275             :     {
    2276           6 :         CPLError(CE_Failure, CPLE_NotSupported,
    2277             :                  "Dataset not open in update mode");
    2278           6 :         return false;
    2279             :     }
    2280             : 
    2281          12 :     return m_oAttrGroup.DeleteAttribute(osName);
    2282             : }
    2283             : 
    2284             : /************************************************************************/
    2285             : /*                      ZarrArray::SetSpatialRef()                      */
    2286             : /************************************************************************/
    2287             : 
    2288          42 : bool ZarrArray::SetSpatialRef(const OGRSpatialReference *poSRS)
    2289             : {
    2290          42 :     if (!CheckValidAndErrorOutIfNot())
    2291           0 :         return false;
    2292             : 
    2293          42 :     if (!m_bUpdatable)
    2294             :     {
    2295           2 :         return GDALPamMDArray::SetSpatialRef(poSRS);
    2296             :     }
    2297          40 :     m_poSRS.reset();
    2298          40 :     if (poSRS)
    2299          40 :         m_poSRS.reset(poSRS->Clone());
    2300          40 :     m_bSRSModified = true;
    2301          40 :     return true;
    2302             : }
    2303             : 
    2304             : /************************************************************************/
    2305             : /*                         ZarrArray::SetUnit()                         */
    2306             : /************************************************************************/
    2307             : 
    2308           9 : bool ZarrArray::SetUnit(const std::string &osUnit)
    2309             : {
    2310           9 :     if (!CheckValidAndErrorOutIfNot())
    2311           0 :         return false;
    2312             : 
    2313           9 :     if (!m_bUpdatable)
    2314             :     {
    2315           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2316             :                  "Dataset not open in update mode");
    2317           0 :         return false;
    2318             :     }
    2319           9 :     m_osUnit = osUnit;
    2320           9 :     m_bUnitModified = true;
    2321           9 :     return true;
    2322             : }
    2323             : 
    2324             : /************************************************************************/
    2325             : /*                       ZarrArray::GetOffset()                         */
    2326             : /************************************************************************/
    2327             : 
    2328          79 : double ZarrArray::GetOffset(bool *pbHasOffset,
    2329             :                             GDALDataType *peStorageType) const
    2330             : {
    2331          79 :     if (pbHasOffset)
    2332          79 :         *pbHasOffset = m_bHasOffset;
    2333          79 :     if (peStorageType)
    2334           0 :         *peStorageType = GDT_Unknown;
    2335          79 :     return m_dfOffset;
    2336             : }
    2337             : 
    2338             : /************************************************************************/
    2339             : /*                       ZarrArray::GetScale()                          */
    2340             : /************************************************************************/
    2341             : 
    2342          77 : double ZarrArray::GetScale(bool *pbHasScale, GDALDataType *peStorageType) const
    2343             : {
    2344          77 :     if (pbHasScale)
    2345          77 :         *pbHasScale = m_bHasScale;
    2346          77 :     if (peStorageType)
    2347           0 :         *peStorageType = GDT_Unknown;
    2348          77 :     return m_dfScale;
    2349             : }
    2350             : 
    2351             : /************************************************************************/
    2352             : /*                       ZarrArray::SetOffset()                         */
    2353             : /************************************************************************/
    2354             : 
    2355           3 : bool ZarrArray::SetOffset(double dfOffset, GDALDataType /* eStorageType */)
    2356             : {
    2357           3 :     if (!CheckValidAndErrorOutIfNot())
    2358           0 :         return false;
    2359             : 
    2360           3 :     m_dfOffset = dfOffset;
    2361           3 :     m_bHasOffset = true;
    2362           3 :     m_bOffsetModified = true;
    2363           3 :     return true;
    2364             : }
    2365             : 
    2366             : /************************************************************************/
    2367             : /*                       ZarrArray::SetScale()                          */
    2368             : /************************************************************************/
    2369             : 
    2370           3 : bool ZarrArray::SetScale(double dfScale, GDALDataType /* eStorageType */)
    2371             : {
    2372           3 :     if (!CheckValidAndErrorOutIfNot())
    2373           0 :         return false;
    2374             : 
    2375           3 :     m_dfScale = dfScale;
    2376           3 :     m_bHasScale = true;
    2377           3 :     m_bScaleModified = true;
    2378           3 :     return true;
    2379             : }
    2380             : 
    2381             : /************************************************************************/
    2382             : /*                      GetDimensionTypeDirection()                     */
    2383             : /************************************************************************/
    2384             : 
    2385             : /* static */
    2386         154 : void ZarrArray::GetDimensionTypeDirection(CPLJSONObject &oAttributes,
    2387             :                                           std::string &osType,
    2388             :                                           std::string &osDirection)
    2389             : {
    2390         308 :     std::string osUnit;
    2391         462 :     const auto unit = oAttributes[CF_UNITS];
    2392         154 :     if (unit.GetType() == CPLJSONObject::Type::String)
    2393             :     {
    2394          52 :         osUnit = unit.ToString();
    2395             :     }
    2396             : 
    2397         462 :     const auto oStdName = oAttributes[CF_STD_NAME];
    2398         154 :     if (oStdName.GetType() == CPLJSONObject::Type::String)
    2399             :     {
    2400         156 :         const auto osStdName = oStdName.ToString();
    2401          52 :         if (osStdName == CF_PROJ_X_COORD || osStdName == CF_LONGITUDE_STD_NAME)
    2402             :         {
    2403          26 :             osType = GDAL_DIM_TYPE_HORIZONTAL_X;
    2404          26 :             oAttributes.Delete(CF_STD_NAME);
    2405          26 :             if (osUnit == CF_DEGREES_EAST)
    2406             :             {
    2407          24 :                 osDirection = "EAST";
    2408             :             }
    2409             :         }
    2410          50 :         else if (osStdName == CF_PROJ_Y_COORD ||
    2411          24 :                  osStdName == CF_LATITUDE_STD_NAME)
    2412             :         {
    2413          26 :             osType = GDAL_DIM_TYPE_HORIZONTAL_Y;
    2414          26 :             oAttributes.Delete(CF_STD_NAME);
    2415          26 :             if (osUnit == CF_DEGREES_NORTH)
    2416             :             {
    2417          24 :                 osDirection = "NORTH";
    2418             :             }
    2419             :         }
    2420           0 :         else if (osStdName == "time")
    2421             :         {
    2422           0 :             osType = GDAL_DIM_TYPE_TEMPORAL;
    2423           0 :             oAttributes.Delete(CF_STD_NAME);
    2424             :         }
    2425             :     }
    2426             : 
    2427         462 :     const auto osAxis = oAttributes[CF_AXIS].ToString();
    2428         154 :     if (osAxis == "Z")
    2429             :     {
    2430           0 :         osType = GDAL_DIM_TYPE_VERTICAL;
    2431           0 :         const auto osPositive = oAttributes["positive"].ToString();
    2432           0 :         if (osPositive == "up")
    2433             :         {
    2434           0 :             osDirection = "UP";
    2435           0 :             oAttributes.Delete("positive");
    2436             :         }
    2437           0 :         else if (osPositive == "down")
    2438             :         {
    2439           0 :             osDirection = "DOWN";
    2440           0 :             oAttributes.Delete("positive");
    2441             :         }
    2442           0 :         oAttributes.Delete(CF_AXIS);
    2443             :     }
    2444         154 : }
    2445             : 
    2446             : /************************************************************************/
    2447             : /*                      GetCoordinateVariables()                        */
    2448             : /************************************************************************/
    2449             : 
    2450             : std::vector<std::shared_ptr<GDALMDArray>>
    2451           2 : ZarrArray::GetCoordinateVariables() const
    2452             : {
    2453           2 :     if (!CheckValidAndErrorOutIfNot())
    2454           0 :         return {};
    2455             : 
    2456           4 :     std::vector<std::shared_ptr<GDALMDArray>> ret;
    2457           6 :     const auto poCoordinates = GetAttribute("coordinates");
    2458           1 :     if (poCoordinates &&
    2459           3 :         poCoordinates->GetDataType().GetClass() == GEDTC_STRING &&
    2460           1 :         poCoordinates->GetDimensionCount() == 0)
    2461             :     {
    2462           1 :         const char *pszCoordinates = poCoordinates->ReadAsString();
    2463           1 :         if (pszCoordinates)
    2464             :         {
    2465           2 :             auto poGroup = m_poGroupWeak.lock();
    2466           1 :             if (!poGroup)
    2467             :             {
    2468           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2469             :                          "Cannot access coordinate variables of %s has "
    2470             :                          "belonging group has gone out of scope",
    2471           0 :                          GetName().c_str());
    2472             :             }
    2473             :             else
    2474             :             {
    2475             :                 const CPLStringList aosNames(
    2476           2 :                     CSLTokenizeString2(pszCoordinates, " ", 0));
    2477           3 :                 for (int i = 0; i < aosNames.size(); i++)
    2478             :                 {
    2479           6 :                     auto poCoordinateVar = poGroup->OpenMDArray(aosNames[i]);
    2480           2 :                     if (poCoordinateVar)
    2481             :                     {
    2482           2 :                         ret.emplace_back(poCoordinateVar);
    2483             :                     }
    2484             :                     else
    2485             :                     {
    2486           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    2487             :                                  "Cannot find variable corresponding to "
    2488             :                                  "coordinate %s",
    2489             :                                  aosNames[i]);
    2490             :                     }
    2491             :                 }
    2492             :             }
    2493             :         }
    2494             :     }
    2495             : 
    2496           2 :     return ret;
    2497             : }
    2498             : 
    2499             : /************************************************************************/
    2500             : /*                            Resize()                                  */
    2501             : /************************************************************************/
    2502             : 
    2503          16 : bool ZarrArray::Resize(const std::vector<GUInt64> &anNewDimSizes,
    2504             :                        CSLConstList /* papszOptions */)
    2505             : {
    2506          16 :     if (!CheckValidAndErrorOutIfNot())
    2507           0 :         return false;
    2508             : 
    2509          16 :     if (!IsWritable())
    2510             :     {
    2511           3 :         CPLError(CE_Failure, CPLE_AppDefined,
    2512             :                  "Resize() not supported on read-only file");
    2513           3 :         return false;
    2514             :     }
    2515             : 
    2516          13 :     const auto nDimCount = GetDimensionCount();
    2517          13 :     if (anNewDimSizes.size() != nDimCount)
    2518             :     {
    2519           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
    2520             :                  "Not expected number of values in anNewDimSizes.");
    2521           0 :         return false;
    2522             :     }
    2523             : 
    2524          13 :     auto &dims = GetDimensions();
    2525          26 :     std::vector<size_t> anGrownDimIdx;
    2526          26 :     std::map<GDALDimension *, GUInt64> oMapDimToSize;
    2527          27 :     for (size_t i = 0; i < nDimCount; ++i)
    2528             :     {
    2529          21 :         auto oIter = oMapDimToSize.find(dims[i].get());
    2530          21 :         if (oIter != oMapDimToSize.end() && oIter->second != anNewDimSizes[i])
    2531             :         {
    2532           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    2533             :                      "Cannot resize a dimension referenced several times "
    2534             :                      "to different sizes");
    2535           7 :             return false;
    2536             :         }
    2537          19 :         if (anNewDimSizes[i] != dims[i]->GetSize())
    2538             :         {
    2539          14 :             if (anNewDimSizes[i] < dims[i]->GetSize())
    2540             :             {
    2541           5 :                 CPLError(CE_Failure, CPLE_NotSupported,
    2542             :                          "Resize() does not support shrinking the array.");
    2543           5 :                 return false;
    2544             :             }
    2545             : 
    2546           9 :             oMapDimToSize[dims[i].get()] = anNewDimSizes[i];
    2547           9 :             anGrownDimIdx.push_back(i);
    2548             :         }
    2549             :         else
    2550             :         {
    2551           5 :             oMapDimToSize[dims[i].get()] = dims[i]->GetSize();
    2552             :         }
    2553             :     }
    2554           6 :     if (!anGrownDimIdx.empty())
    2555             :     {
    2556           6 :         m_bDefinitionModified = true;
    2557          13 :         for (size_t dimIdx : anGrownDimIdx)
    2558             :         {
    2559          14 :             auto dim = std::dynamic_pointer_cast<ZarrDimension>(dims[dimIdx]);
    2560           7 :             if (dim)
    2561             :             {
    2562           7 :                 dim->SetSize(anNewDimSizes[dimIdx]);
    2563           7 :                 if (dim->GetName() != dim->GetFullName())
    2564             :                 {
    2565             :                     // This is not a local dimension
    2566           7 :                     m_poSharedResource->UpdateDimensionSize(dim);
    2567             :                 }
    2568             :             }
    2569             :             else
    2570             :             {
    2571           0 :                 CPLAssert(false);
    2572             :             }
    2573             :         }
    2574             :     }
    2575           6 :     return true;
    2576             : }
    2577             : 
    2578             : /************************************************************************/
    2579             : /*                       NotifyChildrenOfRenaming()                     */
    2580             : /************************************************************************/
    2581             : 
    2582          15 : void ZarrArray::NotifyChildrenOfRenaming()
    2583             : {
    2584          15 :     m_oAttrGroup.ParentRenamed(m_osFullName);
    2585          15 : }
    2586             : 
    2587             : /************************************************************************/
    2588             : /*                          ParentRenamed()                             */
    2589             : /************************************************************************/
    2590             : 
    2591           9 : void ZarrArray::ParentRenamed(const std::string &osNewParentFullName)
    2592             : {
    2593           9 :     GDALMDArray::ParentRenamed(osNewParentFullName);
    2594             : 
    2595           9 :     auto poParent = m_poGroupWeak.lock();
    2596             :     // The parent necessarily exist, since it notified us
    2597           9 :     CPLAssert(poParent);
    2598             : 
    2599          27 :     m_osFilename = CPLFormFilenameSafe(
    2600          18 :         CPLFormFilenameSafe(poParent->GetDirectoryName().c_str(),
    2601           9 :                             m_osName.c_str(), nullptr)
    2602             :             .c_str(),
    2603           9 :         CPLGetFilename(m_osFilename.c_str()), nullptr);
    2604           9 : }
    2605             : 
    2606             : /************************************************************************/
    2607             : /*                              Rename()                                */
    2608             : /************************************************************************/
    2609             : 
    2610          21 : bool ZarrArray::Rename(const std::string &osNewName)
    2611             : {
    2612          21 :     if (!CheckValidAndErrorOutIfNot())
    2613           0 :         return false;
    2614             : 
    2615          21 :     if (!m_bUpdatable)
    2616             :     {
    2617           6 :         CPLError(CE_Failure, CPLE_NotSupported,
    2618             :                  "Dataset not open in update mode");
    2619           6 :         return false;
    2620             :     }
    2621          15 :     if (!ZarrGroupBase::IsValidObjectName(osNewName))
    2622             :     {
    2623           3 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid array name");
    2624           3 :         return false;
    2625             :     }
    2626             : 
    2627          24 :     auto poParent = m_poGroupWeak.lock();
    2628          12 :     if (poParent)
    2629             :     {
    2630          12 :         if (!poParent->CheckArrayOrGroupWithSameNameDoesNotExist(osNewName))
    2631           6 :             return false;
    2632             :     }
    2633             : 
    2634             :     const std::string osRootDirectoryName(
    2635          12 :         CPLGetDirnameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str()));
    2636             :     const std::string osOldDirectoryName = CPLFormFilenameSafe(
    2637          12 :         osRootDirectoryName.c_str(), m_osName.c_str(), nullptr);
    2638             :     const std::string osNewDirectoryName = CPLFormFilenameSafe(
    2639          12 :         osRootDirectoryName.c_str(), osNewName.c_str(), nullptr);
    2640             : 
    2641           6 :     if (VSIRename(osOldDirectoryName.c_str(), osNewDirectoryName.c_str()) != 0)
    2642             :     {
    2643           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Renaming of %s to %s failed",
    2644             :                  osOldDirectoryName.c_str(), osNewDirectoryName.c_str());
    2645           0 :         return false;
    2646             :     }
    2647             : 
    2648           6 :     m_poSharedResource->RenameZMetadataRecursive(osOldDirectoryName,
    2649             :                                                  osNewDirectoryName);
    2650             : 
    2651             :     m_osFilename =
    2652          12 :         CPLFormFilenameSafe(osNewDirectoryName.c_str(),
    2653           6 :                             CPLGetFilename(m_osFilename.c_str()), nullptr);
    2654             : 
    2655           6 :     if (poParent)
    2656             :     {
    2657           6 :         poParent->NotifyArrayRenamed(m_osName, osNewName);
    2658             :     }
    2659             : 
    2660           6 :     BaseRename(osNewName);
    2661             : 
    2662           6 :     return true;
    2663             : }
    2664             : 
    2665             : /************************************************************************/
    2666             : /*                       NotifyChildrenOfDeletion()                     */
    2667             : /************************************************************************/
    2668             : 
    2669           8 : void ZarrArray::NotifyChildrenOfDeletion()
    2670             : {
    2671           8 :     m_oAttrGroup.ParentDeleted();
    2672           8 : }
    2673             : 
    2674             : /************************************************************************/
    2675             : /*                     ParseSpecialAttributes()                         */
    2676             : /************************************************************************/
    2677             : 
    2678        1495 : void ZarrArray::ParseSpecialAttributes(
    2679             :     const std::shared_ptr<GDALGroup> &poGroup, CPLJSONObject &oAttributes)
    2680             : {
    2681        4485 :     const auto crs = oAttributes[CRS_ATTRIBUTE_NAME];
    2682        1495 :     std::shared_ptr<OGRSpatialReference> poSRS;
    2683        1495 :     if (crs.GetType() == CPLJSONObject::Type::Object)
    2684             :     {
    2685          48 :         for (const char *key : {"url", "wkt", "projjson"})
    2686             :         {
    2687          96 :             const auto item = crs[key];
    2688          48 :             if (item.IsValid())
    2689             :             {
    2690          29 :                 poSRS = std::make_shared<OGRSpatialReference>();
    2691          29 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2692          58 :                 if (poSRS->SetFromUserInput(
    2693          58 :                         item.ToString().c_str(),
    2694             :                         OGRSpatialReference::
    2695          29 :                             SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
    2696             :                     OGRERR_NONE)
    2697             :                 {
    2698          29 :                     oAttributes.Delete(CRS_ATTRIBUTE_NAME);
    2699          29 :                     break;
    2700             :                 }
    2701           0 :                 poSRS.reset();
    2702             :             }
    2703             :         }
    2704             :     }
    2705             :     else
    2706             :     {
    2707             :         // Check if SRS is using CF-1 conventions
    2708        4398 :         const auto gridMapping = oAttributes["grid_mapping"];
    2709        1466 :         if (gridMapping.GetType() == CPLJSONObject::Type::String)
    2710             :         {
    2711             :             const auto gridMappingArray =
    2712           6 :                 poGroup->OpenMDArray(gridMapping.ToString());
    2713           2 :             if (gridMappingArray)
    2714             :             {
    2715           2 :                 poSRS = std::make_shared<OGRSpatialReference>();
    2716           2 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2717           4 :                 CPLStringList aosKeyValues;
    2718          22 :                 for (const auto &poAttr : gridMappingArray->GetAttributes())
    2719             :                 {
    2720          20 :                     if (poAttr->GetDataType().GetClass() == GEDTC_STRING)
    2721             :                     {
    2722           4 :                         aosKeyValues.SetNameValue(poAttr->GetName().c_str(),
    2723           8 :                                                   poAttr->ReadAsString());
    2724             :                     }
    2725          16 :                     else if (poAttr->GetDataType().GetClass() == GEDTC_NUMERIC)
    2726             :                     {
    2727          32 :                         std::string osVal;
    2728          32 :                         for (double val : poAttr->ReadAsDoubleArray())
    2729             :                         {
    2730          16 :                             if (!osVal.empty())
    2731           0 :                                 osVal += ',';
    2732          16 :                             osVal += CPLSPrintf("%.17g", val);
    2733             :                         }
    2734          16 :                         aosKeyValues.SetNameValue(poAttr->GetName().c_str(),
    2735          32 :                                                   osVal.c_str());
    2736             :                     }
    2737             :                 }
    2738           2 :                 if (poSRS->importFromCF1(aosKeyValues.List(), nullptr) !=
    2739             :                     OGRERR_NONE)
    2740             :                 {
    2741           0 :                     poSRS.reset();
    2742             :                 }
    2743             :             }
    2744             :         }
    2745             :     }
    2746             : 
    2747             :     // For EOPF Sentinel Zarr Samples Service datasets, read attributes from
    2748             :     // the STAC Proj extension attributes to get the CRS.
    2749        1495 :     if (!poSRS)
    2750             :     {
    2751        4392 :         const auto oProjEPSG = oAttributes["proj:epsg"];
    2752        1464 :         if (oProjEPSG.GetType() == CPLJSONObject::Type::Integer)
    2753             :         {
    2754           1 :             poSRS = std::make_shared<OGRSpatialReference>();
    2755           1 :             poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2756           1 :             if (poSRS->importFromEPSG(oProjEPSG.ToInteger()) != OGRERR_NONE)
    2757             :             {
    2758           0 :                 poSRS.reset();
    2759             :             }
    2760             :         }
    2761             :         else
    2762             :         {
    2763        4389 :             const auto oProjWKT2 = oAttributes["proj:wkt2"];
    2764        1463 :             if (oProjWKT2.GetType() == CPLJSONObject::Type::String)
    2765             :             {
    2766           1 :                 poSRS = std::make_shared<OGRSpatialReference>();
    2767           1 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2768           1 :                 if (poSRS->importFromWkt(oProjWKT2.ToString().c_str()) !=
    2769             :                     OGRERR_NONE)
    2770             :                 {
    2771           0 :                     poSRS.reset();
    2772             :                 }
    2773             :             }
    2774             :         }
    2775             : 
    2776             :         // There is also a "proj:transform" attribute, but we don't need to
    2777             :         // use it since the x and y dimensions are already associated with a
    2778             :         // 1-dimensional array with the values.
    2779             :     }
    2780             : 
    2781        1495 :     if (poSRS)
    2782             :     {
    2783          33 :         int iDimX = 0;
    2784          33 :         int iDimY = 0;
    2785          33 :         int iCount = 1;
    2786          99 :         for (const auto &poDim : GetDimensions())
    2787             :         {
    2788          66 :             if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X)
    2789           2 :                 iDimX = iCount;
    2790          64 :             else if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y)
    2791           2 :                 iDimY = iCount;
    2792          66 :             iCount++;
    2793             :         }
    2794          33 :         if ((iDimX == 0 || iDimY == 0) && GetDimensionCount() >= 2)
    2795             :         {
    2796          31 :             iDimX = static_cast<int>(GetDimensionCount());
    2797          31 :             iDimY = iDimX - 1;
    2798             :         }
    2799          33 :         if (iDimX > 0 && iDimY > 0)
    2800             :         {
    2801          33 :             const auto &oMapping = poSRS->GetDataAxisToSRSAxisMapping();
    2802          92 :             if (oMapping == std::vector<int>{2, 1} ||
    2803          59 :                 oMapping == std::vector<int>{2, 1, 3})
    2804           7 :                 poSRS->SetDataAxisToSRSAxisMapping({iDimY, iDimX});
    2805          53 :             else if (oMapping == std::vector<int>{1, 2} ||
    2806          27 :                      oMapping == std::vector<int>{1, 2, 3})
    2807          26 :                 poSRS->SetDataAxisToSRSAxisMapping({iDimX, iDimY});
    2808             :         }
    2809             : 
    2810          33 :         SetSRS(poSRS);
    2811             :     }
    2812             : 
    2813        4485 :     const auto unit = oAttributes[CF_UNITS];
    2814        1495 :     if (unit.GetType() == CPLJSONObject::Type::String)
    2815             :     {
    2816         165 :         std::string osUnit = unit.ToString();
    2817          55 :         oAttributes.Delete(CF_UNITS);
    2818          55 :         RegisterUnit(osUnit);
    2819             :     }
    2820             : 
    2821        4485 :     const auto offset = oAttributes[CF_ADD_OFFSET];
    2822        1495 :     const auto offsetType = offset.GetType();
    2823        1495 :     if (offsetType == CPLJSONObject::Type::Integer ||
    2824        1495 :         offsetType == CPLJSONObject::Type::Long ||
    2825             :         offsetType == CPLJSONObject::Type::Double)
    2826             :     {
    2827           3 :         double dfOffset = offset.ToDouble();
    2828           3 :         oAttributes.Delete(CF_ADD_OFFSET);
    2829           3 :         RegisterOffset(dfOffset);
    2830             :     }
    2831             : 
    2832        4485 :     const auto scale = oAttributes[CF_SCALE_FACTOR];
    2833        1495 :     const auto scaleType = scale.GetType();
    2834        1495 :     if (scaleType == CPLJSONObject::Type::Integer ||
    2835        1495 :         scaleType == CPLJSONObject::Type::Long ||
    2836             :         scaleType == CPLJSONObject::Type::Double)
    2837             :     {
    2838           3 :         double dfScale = scale.ToDouble();
    2839           3 :         oAttributes.Delete(CF_SCALE_FACTOR);
    2840           3 :         RegisterScale(dfScale);
    2841             :     }
    2842        1495 : }
    2843             : 
    2844             : /************************************************************************/
    2845             : /*                           SetStatistics()                            */
    2846             : /************************************************************************/
    2847             : 
    2848           1 : bool ZarrArray::SetStatistics(bool bApproxStats, double dfMin, double dfMax,
    2849             :                               double dfMean, double dfStdDev,
    2850             :                               GUInt64 nValidCount, CSLConstList papszOptions)
    2851             : {
    2852           2 :     if (!bApproxStats && m_bUpdatable &&
    2853           1 :         CPLTestBool(
    2854             :             CSLFetchNameValueDef(papszOptions, "UPDATE_METADATA", "NO")))
    2855             :     {
    2856           3 :         auto poAttr = GetAttribute("actual_range");
    2857           1 :         if (!poAttr)
    2858             :         {
    2859             :             poAttr =
    2860           1 :                 CreateAttribute("actual_range", {2}, GetDataType(), nullptr);
    2861             :         }
    2862           1 :         if (poAttr)
    2863             :         {
    2864           2 :             std::vector<GUInt64> startIdx = {0};
    2865           2 :             std::vector<size_t> count = {2};
    2866           1 :             std::vector<double> values = {dfMin, dfMax};
    2867           2 :             poAttr->Write(startIdx.data(), count.data(), nullptr, nullptr,
    2868           2 :                           GDALExtendedDataType::Create(GDT_Float64),
    2869           1 :                           values.data(), nullptr, 0);
    2870             :         }
    2871             :     }
    2872           1 :     return GDALPamMDArray::SetStatistics(bApproxStats, dfMin, dfMax, dfMean,
    2873           1 :                                          dfStdDev, nValidCount, papszOptions);
    2874             : }
    2875             : 
    2876             : /************************************************************************/
    2877             : /*                ZarrArray::IsBlockMissingFromCacheInfo()              */
    2878             : /************************************************************************/
    2879             : 
    2880       29928 : bool ZarrArray::IsBlockMissingFromCacheInfo(const std::string &osFilename,
    2881             :                                             const uint64_t *blockIndices) const
    2882             : {
    2883       29928 :     CPL_IGNORE_RET_VAL(osFilename);
    2884       59856 :     auto poBlockPresenceArray = OpenBlockPresenceCache(false);
    2885       29928 :     if (poBlockPresenceArray)
    2886             :     {
    2887          36 :         std::vector<GUInt64> anBlockIdx(m_aoDims.size());
    2888          36 :         const std::vector<size_t> anCount(m_aoDims.size(), 1);
    2889          36 :         const std::vector<GInt64> anArrayStep(m_aoDims.size(), 0);
    2890          36 :         const std::vector<GPtrDiff_t> anBufferStride(m_aoDims.size(), 0);
    2891          36 :         const auto eByteDT = GDALExtendedDataType::Create(GDT_UInt8);
    2892         108 :         for (size_t i = 0; i < m_aoDims.size(); ++i)
    2893             :         {
    2894          72 :             anBlockIdx[i] = static_cast<GUInt64>(blockIndices[i]);
    2895             :         }
    2896          36 :         GByte byValue = 0;
    2897          72 :         if (poBlockPresenceArray->Read(
    2898          36 :                 anBlockIdx.data(), anCount.data(), anArrayStep.data(),
    2899          72 :                 anBufferStride.data(), eByteDT, &byValue) &&
    2900          36 :             byValue == 0)
    2901             :         {
    2902          26 :             CPLDebugOnly(ZARR_DEBUG_KEY, "Block %s missing (=nodata)",
    2903             :                          osFilename.c_str());
    2904          26 :             return true;
    2905             :         }
    2906             :     }
    2907       29902 :     return false;
    2908             : }
    2909             : 
    2910             : /************************************************************************/
    2911             : /*                     ZarrArray::GetRawBlockInfo()                     */
    2912             : /************************************************************************/
    2913             : 
    2914          16 : bool ZarrArray::GetRawBlockInfo(const uint64_t *panBlockCoordinates,
    2915             :                                 GDALMDArrayRawBlockInfo &info) const
    2916             : {
    2917          16 :     info.clear();
    2918          33 :     for (size_t i = 0; i < m_anInnerBlockSize.size(); ++i)
    2919             :     {
    2920          20 :         const auto nBlockSize = m_anInnerBlockSize[i];
    2921             :         const auto nBlockCount =
    2922          20 :             cpl::div_round_up(m_aoDims[i]->GetSize(), nBlockSize);
    2923          20 :         if (panBlockCoordinates[i] >= nBlockCount)
    2924             :         {
    2925           3 :             CPLError(CE_Failure, CPLE_AppDefined,
    2926             :                      "GetRawBlockInfo() failed: array %s: "
    2927             :                      "invalid block coordinate (%u) for dimension %u",
    2928           3 :                      GetName().c_str(),
    2929           3 :                      static_cast<unsigned>(panBlockCoordinates[i]),
    2930             :                      static_cast<unsigned>(i));
    2931           3 :             return false;
    2932             :         }
    2933             :     }
    2934             : 
    2935          26 :     std::vector<uint64_t> anOuterBlockIndices;
    2936          30 :     for (size_t i = 0; i < m_anCountInnerBlockInOuter.size(); ++i)
    2937             :     {
    2938          17 :         anOuterBlockIndices.push_back(panBlockCoordinates[i] /
    2939          17 :                                       m_anCountInnerBlockInOuter[i]);
    2940             :     }
    2941             : 
    2942          26 :     std::string osFilename = BuildChunkFilename(anOuterBlockIndices.data());
    2943             : 
    2944             :     // For network file systems, get the streaming version of the filename,
    2945             :     // as we don't need arbitrary seeking in the file
    2946          13 :     osFilename = VSIFileManager::GetHandler(osFilename.c_str())
    2947          13 :                      ->GetStreamingFilename(osFilename);
    2948             :     {
    2949          13 :         std::lock_guard<std::mutex> oLock(m_oMutex);
    2950          13 :         if (IsBlockMissingFromCacheInfo(osFilename, panBlockCoordinates))
    2951           0 :             return true;
    2952             :     }
    2953             : 
    2954          13 :     VSILFILE *fp = nullptr;
    2955             :     // This is the number of files returned in a S3 directory listing operation
    2956          13 :     const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
    2957             :                                            nullptr};
    2958          13 :     const auto nErrorBefore = CPLGetErrorCounter();
    2959             :     {
    2960             :         // Avoid issuing ReadDir() when a lot of files are expected
    2961             :         CPLConfigOptionSetter optionSetter("GDAL_DISABLE_READDIR_ON_OPEN",
    2962          13 :                                            "YES", true);
    2963          13 :         fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
    2964             :     }
    2965          13 :     if (fp == nullptr)
    2966             :     {
    2967           1 :         if (nErrorBefore != CPLGetErrorCounter())
    2968             :         {
    2969           0 :             return false;
    2970             :         }
    2971             :         else
    2972             :         {
    2973             :             // Missing files are OK and indicate nodata_value
    2974           1 :             return true;
    2975             :         }
    2976             :     }
    2977          12 :     VSIFSeekL(fp, 0, SEEK_END);
    2978          12 :     const auto nFileSize = VSIFTellL(fp);
    2979          12 :     VSIFCloseL(fp);
    2980             : 
    2981             :     // For Kerchunk files, get information on the actual location
    2982             :     const CPLStringList aosMetadata(
    2983          12 :         VSIGetFileMetadata(osFilename.c_str(), "CHUNK_INFO", nullptr));
    2984          12 :     if (!aosMetadata.empty())
    2985             :     {
    2986           2 :         const char *pszFilename = aosMetadata.FetchNameValue("FILENAME");
    2987           2 :         if (pszFilename)
    2988           1 :             info.pszFilename = CPLStrdup(pszFilename);
    2989           2 :         info.nOffset = std::strtoull(
    2990             :             aosMetadata.FetchNameValueDef("OFFSET", "0"), nullptr, 10);
    2991           2 :         info.nSize = std::strtoull(aosMetadata.FetchNameValueDef("SIZE", "0"),
    2992             :                                    nullptr, 10);
    2993           2 :         const char *pszBase64 = aosMetadata.FetchNameValue("BASE64");
    2994           2 :         if (pszBase64)
    2995             :         {
    2996           1 :             const size_t nSizeBase64 = strlen(pszBase64) + 1;
    2997           1 :             info.pabyInlineData = static_cast<GByte *>(CPLMalloc(nSizeBase64));
    2998           1 :             memcpy(info.pabyInlineData, pszBase64, nSizeBase64);
    2999             :             const int nDecodedSize =
    3000           1 :                 CPLBase64DecodeInPlace(info.pabyInlineData);
    3001           1 :             CPLAssert(static_cast<size_t>(nDecodedSize) ==
    3002             :                       static_cast<size_t>(info.nSize));
    3003           1 :             CPL_IGNORE_RET_VAL(nDecodedSize);
    3004             :         }
    3005             :     }
    3006             :     else
    3007             :     {
    3008          10 :         info.pszFilename = CPLStrdup(osFilename.c_str());
    3009          10 :         info.nOffset = 0;
    3010          10 :         info.nSize = nFileSize;
    3011             :     }
    3012             : 
    3013          12 :     info.papszInfo = CSLDuplicate(GetRawBlockInfoInfo().List());
    3014             : 
    3015          12 :     return true;
    3016             : }

Generated by: LCOV version 1.14