LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_array.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1281 1480 86.6 %
Date: 2025-10-16 20:08:56 Functions: 42 45 93.3 %

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

Generated by: LCOV version 1.14