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

Generated by: LCOV version 1.14