LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_array.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1337 1538 86.9 %
Date: 2025-12-05 02:43:06 Functions: 42 45 93.3 %

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

Generated by: LCOV version 1.14