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

Generated by: LCOV version 1.14