LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_array.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1726 1922 89.8 %
Date: 2026-03-06 01:02:02 Functions: 52 55 94.5 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Zarr driver
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "zarr.h"
      14             : 
      15             : #include "cpl_float.h"
      16             : #include "cpl_mem_cache.h"
      17             : #include "cpl_multiproc.h"
      18             : #include "cpl_vsi_virtual.h"
      19             : #include "ucs4_utf8.hpp"
      20             : #include "gdal_thread_pool.h"
      21             : 
      22             : #include "netcdf_cf_constants.h"  // for CF_UNITS, etc
      23             : 
      24             : #include <algorithm>
      25             : #include <cassert>
      26             : #include <cmath>
      27             : #include <cstdlib>
      28             : #include <limits>
      29             : #include <map>
      30             : #include <set>
      31             : 
      32             : #if defined(__clang__) || defined(_MSC_VER)
      33             : #define COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT
      34             : #endif
      35             : 
      36             : namespace
      37             : {
      38             : 
      39           4 : inline std::vector<GByte> UTF8ToUCS4(const char *pszStr, bool needByteSwap)
      40             : {
      41           4 :     const size_t nLen = strlen(pszStr);
      42             :     // Worst case if that we need 4 more bytes than the UTF-8 one
      43             :     // (when the content is pure ASCII)
      44           4 :     if (nLen > std::numeric_limits<size_t>::max() / sizeof(uint32_t))
      45           0 :         throw std::bad_alloc();
      46           4 :     std::vector<GByte> ret(nLen * sizeof(uint32_t));
      47           4 :     size_t outPos = 0;
      48          27 :     for (size_t i = 0; i < nLen; outPos += sizeof(uint32_t))
      49             :     {
      50          23 :         uint32_t ucs4 = 0;
      51          23 :         int consumed = FcUtf8ToUcs4(
      52          23 :             reinterpret_cast<const uint8_t *>(pszStr + i), &ucs4, nLen - i);
      53          23 :         if (consumed <= 0)
      54             :         {
      55           0 :             ret.resize(outPos);
      56             :         }
      57          23 :         if (needByteSwap)
      58             :         {
      59           1 :             CPL_SWAP32PTR(&ucs4);
      60             :         }
      61          23 :         memcpy(&ret[outPos], &ucs4, sizeof(uint32_t));
      62          23 :         i += consumed;
      63             :     }
      64           4 :     ret.resize(outPos);
      65           4 :     return ret;
      66             : }
      67             : 
      68           9 : inline char *UCS4ToUTF8(const uint8_t *ucs4Ptr, size_t nSize, bool needByteSwap)
      69             : {
      70             :     // A UCS4 char can require up to 6 bytes in UTF8.
      71           9 :     if (nSize > (std::numeric_limits<size_t>::max() - 1) / 6 * 4)
      72           0 :         return nullptr;
      73           9 :     const size_t nOutSize = nSize / 4 * 6 + 1;
      74           9 :     char *ret = static_cast<char *>(VSI_MALLOC_VERBOSE(nOutSize));
      75           9 :     if (ret == nullptr)
      76           0 :         return nullptr;
      77           9 :     size_t outPos = 0;
      78          38 :     for (size_t i = 0; i + sizeof(uint32_t) - 1 < nSize; i += sizeof(uint32_t))
      79             :     {
      80             :         uint32_t ucs4;
      81          29 :         memcpy(&ucs4, ucs4Ptr + i, sizeof(uint32_t));
      82          29 :         if (needByteSwap)
      83             :         {
      84           2 :             CPL_SWAP32PTR(&ucs4);
      85             :         }
      86             :         int written =
      87          29 :             FcUcs4ToUtf8(ucs4, reinterpret_cast<uint8_t *>(ret + outPos));
      88          29 :         outPos += written;
      89             :     }
      90           9 :     ret[outPos] = 0;
      91           9 :     return ret;
      92             : }
      93             : 
      94             : }  // namespace
      95             : 
      96             : /************************************************************************/
      97             : /*                     ZarrArray::ParseChunkSize()                      */
      98             : /************************************************************************/
      99             : 
     100        1941 : /* static */ bool ZarrArray::ParseChunkSize(const CPLJSONArray &oChunks,
     101             :                                             const GDALExtendedDataType &oType,
     102             :                                             std::vector<GUInt64> &anBlockSize)
     103             : {
     104        1941 :     size_t nBlockSize = oType.GetSize();
     105        5315 :     for (const auto &item : oChunks)
     106             :     {
     107        3377 :         const auto nSize = static_cast<GUInt64>(item.ToLong());
     108        3377 :         if (nSize == 0)
     109             :         {
     110           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for chunks");
     111           3 :             return false;
     112             :         }
     113        3376 :         if (nBlockSize > std::numeric_limits<size_t>::max() / nSize)
     114             :         {
     115           2 :             CPLError(CE_Failure, CPLE_AppDefined, "Too large chunks");
     116           2 :             return false;
     117             :         }
     118        3374 :         nBlockSize *= static_cast<size_t>(nSize);
     119        3374 :         anBlockSize.emplace_back(nSize);
     120             :     }
     121             : 
     122        1938 :     return true;
     123             : }
     124             : 
     125             : /************************************************************************/
     126             : /*                    ZarrArray::ComputeBlockCount()                    */
     127             : /************************************************************************/
     128             : 
     129        2402 : /* static */ uint64_t ZarrArray::ComputeBlockCount(
     130             :     const std::string &osName,
     131             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
     132             :     const std::vector<GUInt64> &anBlockSize)
     133             : {
     134        2402 :     uint64_t nTotalBlockCount = 1;
     135        6474 :     for (size_t i = 0; i < aoDims.size(); ++i)
     136             :     {
     137             :         const uint64_t nBlockThisDim =
     138        4074 :             cpl::div_round_up(aoDims[i]->GetSize(), anBlockSize[i]);
     139        8148 :         if (nBlockThisDim != 0 &&
     140             :             nTotalBlockCount >
     141        4074 :                 std::numeric_limits<uint64_t>::max() / nBlockThisDim)
     142             :         {
     143           2 :             CPLError(
     144             :                 CE_Failure, CPLE_NotSupported,
     145             :                 "Array %s has more than 2^64 blocks. This is not supported.",
     146             :                 osName.c_str());
     147           2 :             return 0;
     148             :         }
     149        4072 :         nTotalBlockCount *= nBlockThisDim;
     150             :     }
     151        2400 :     return nTotalBlockCount;
     152             : }
     153             : 
     154             : /************************************************************************/
     155             : /*                   ComputeCountInnerBlockInOuter()                    */
     156             : /************************************************************************/
     157             : 
     158             : static std::vector<GUInt64>
     159        2402 : ComputeCountInnerBlockInOuter(const std::vector<GUInt64> &anInnerBlockSize,
     160             :                               const std::vector<GUInt64> &anOuterBlockSize)
     161             : {
     162        2402 :     std::vector<GUInt64> ret;
     163        2402 :     CPLAssert(anInnerBlockSize.size() == anOuterBlockSize.size());
     164        6476 :     for (size_t i = 0; i < anInnerBlockSize.size(); ++i)
     165             :     {
     166             :         // All those assertions must be checked by the caller before
     167             :         // constructing the ZarrArray instance.
     168        4074 :         CPLAssert(anInnerBlockSize[i] > 0);
     169        4074 :         CPLAssert(anInnerBlockSize[i] <= anOuterBlockSize[i]);
     170        4074 :         CPLAssert((anOuterBlockSize[i] % anInnerBlockSize[i]) == 0);
     171        4074 :         ret.push_back(anOuterBlockSize[i] / anInnerBlockSize[i]);
     172             :     }
     173        2402 :     return ret;
     174             : }
     175             : 
     176             : /************************************************************************/
     177             : /*                     ComputeInnerBlockSizeBytes()                     */
     178             : /************************************************************************/
     179             : 
     180             : static size_t
     181        2400 : ComputeInnerBlockSizeBytes(const std::vector<DtypeElt> &aoDtypeElts,
     182             :                            const std::vector<GUInt64> &anInnerBlockSize)
     183             : {
     184             :     const size_t nSourceSize =
     185        2400 :         aoDtypeElts.back().nativeOffset + aoDtypeElts.back().nativeSize;
     186        2400 :     size_t nInnerBlockSizeBytes = nSourceSize;
     187        6468 :     for (const auto &nBlockSize : anInnerBlockSize)
     188             :     {
     189             :         // Given that ParseChunkSize() has checked that the outer block size
     190             :         // fits on size_t, and that m_anInnerBlockSize[i] <= m_anOuterBlockSize[i],
     191             :         // this cast is safe, and the multiplication cannot overflow.
     192        4068 :         nInnerBlockSizeBytes *= static_cast<size_t>(nBlockSize);
     193             :     }
     194        2400 :     return nInnerBlockSizeBytes;
     195             : }
     196             : 
     197             : /************************************************************************/
     198             : /*                        ZarrArray::ZarrArray()                        */
     199             : /************************************************************************/
     200             : 
     201        2402 : ZarrArray::ZarrArray(
     202             :     const std::shared_ptr<ZarrSharedResource> &poSharedResource,
     203             :     const std::shared_ptr<ZarrGroupBase> &poParent, const std::string &osName,
     204             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
     205             :     const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
     206             :     const std::vector<GUInt64> &anOuterBlockSize,
     207           0 :     const std::vector<GUInt64> &anInnerBlockSize)
     208             :     :
     209             : #if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT)
     210             :       GDALAbstractMDArray(poParent->GetFullName(), osName),
     211             : #endif
     212        2402 :       GDALPamMDArray(poParent->GetFullName(), osName,
     213             :                      poSharedResource->GetPAM()),
     214             :       m_poSharedResource(poSharedResource), m_poParent(poParent),
     215             :       m_aoDims(aoDims), m_oType(oType), m_aoDtypeElts(aoDtypeElts),
     216             :       m_anOuterBlockSize(anOuterBlockSize),
     217             :       m_anInnerBlockSize(anInnerBlockSize),
     218             :       m_anCountInnerBlockInOuter(ComputeCountInnerBlockInOuter(
     219        2402 :           m_anInnerBlockSize, m_anOuterBlockSize)),
     220             :       m_nTotalInnerChunkCount(
     221        2402 :           ComputeBlockCount(osName, aoDims, m_anInnerBlockSize)),
     222             :       m_nInnerBlockSizeBytes(
     223        2402 :           m_nTotalInnerChunkCount > 0
     224        2402 :               ? ComputeInnerBlockSizeBytes(m_aoDtypeElts, m_anInnerBlockSize)
     225             :               : 0),
     226        2402 :       m_oAttrGroup(m_osFullName, /*bContainerIsGroup=*/false),
     227        2402 :       m_bUseOptimizedCodePaths(CPLTestBool(
     228       12010 :           CPLGetConfigOption("GDAL_ZARR_USE_OPTIMIZED_CODE_PATHS", "YES")))
     229             : {
     230        2402 : }
     231             : 
     232             : /************************************************************************/
     233             : /*                             ~ZarrArray()                             */
     234             : /************************************************************************/
     235             : 
     236        2402 : ZarrArray::~ZarrArray()
     237             : {
     238        2402 :     if (m_pabyNoData)
     239             :     {
     240        1179 :         m_oType.FreeDynamicMemory(&m_pabyNoData[0]);
     241        1179 :         CPLFree(m_pabyNoData);
     242             :     }
     243             : 
     244        2402 :     DeallocateDecodedBlockData();
     245        2402 : }
     246             : 
     247             : /************************************************************************/
     248             : /*               ZarrArray::SerializeSpecialAttributes()                */
     249             : /************************************************************************/
     250             : 
     251         405 : CPLJSONObject ZarrArray::SerializeSpecialAttributes()
     252             : {
     253         405 :     m_bSRSModified = false;
     254         405 :     m_oAttrGroup.UnsetModified();
     255             : 
     256         405 :     auto oAttrs = m_oAttrGroup.Serialize();
     257             : 
     258             :     const bool bUseSpatialProjConventions =
     259         405 :         EQUAL(m_aosCreationOptions.FetchNameValueDef(
     260             :                   "GEOREFERENCING_CONVENTION", "GDAL"),
     261             :               "SPATIAL_PROJ");
     262             : 
     263          41 :     const auto ExportToWkt2AndPROJJSON = [this](CPLJSONObject &oContainer,
     264             :                                                 const char *pszWKT2AttrName,
     265          82 :                                                 const char *pszPROJJSONAttrName)
     266             :     {
     267          41 :         const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
     268          41 :         char *pszWKT = nullptr;
     269          41 :         if (m_poSRS->exportToWkt(&pszWKT, apszOptions) == OGRERR_NONE)
     270             :         {
     271          41 :             oContainer.Set(pszWKT2AttrName, pszWKT);
     272             :         }
     273          41 :         CPLFree(pszWKT);
     274             : 
     275             :         {
     276          82 :             CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
     277          41 :             char *projjson = nullptr;
     278          82 :             if (m_poSRS->exportToPROJJSON(&projjson, nullptr) == OGRERR_NONE &&
     279          41 :                 projjson != nullptr)
     280             :             {
     281          82 :                 CPLJSONDocument oDocProjJSON;
     282          41 :                 if (oDocProjJSON.LoadMemory(std::string(projjson)))
     283             :                 {
     284          41 :                     oContainer.Set(pszPROJJSONAttrName, oDocProjJSON.GetRoot());
     285             :                 }
     286             :             }
     287          41 :             CPLFree(projjson);
     288             :         }
     289          41 :     };
     290             : 
     291         810 :     CPLJSONArray oZarrConventionsArray;
     292         405 :     if (bUseSpatialProjConventions)
     293             :     {
     294           2 :         if (m_poSRS)
     295             :         {
     296           4 :             CPLJSONObject oConventionProj;
     297           2 :             oConventionProj.Set(
     298             :                 "schema_url",
     299             :                 "https://raw.githubusercontent.com/zarr-experimental/geo-proj/"
     300             :                 "refs/tags/v1/schema.json");
     301           2 :             oConventionProj.Set("spec_url",
     302             :                                 "https://github.com/zarr-experimental/geo-proj/"
     303             :                                 "blob/v1/README.md");
     304           2 :             oConventionProj.Set("uuid", "f17cb550-5864-4468-aeb7-f3180cfb622f");
     305           2 :             oConventionProj.Set("name", "proj:");  // ending colon intended
     306           2 :             oConventionProj.Set(
     307             :                 "description",
     308             :                 "Coordinate reference system information for geospatial data");
     309             : 
     310           2 :             oZarrConventionsArray.Add(oConventionProj);
     311             : 
     312           2 :             const char *pszAuthorityName = m_poSRS->GetAuthorityName(nullptr);
     313           2 :             const char *pszAuthorityCode = m_poSRS->GetAuthorityCode(nullptr);
     314           2 :             if (pszAuthorityName && pszAuthorityCode)
     315             :             {
     316           1 :                 oAttrs.Set("proj:code", CPLSPrintf("%s:%s", pszAuthorityName,
     317             :                                                    pszAuthorityCode));
     318             :             }
     319             :             else
     320             :             {
     321           1 :                 ExportToWkt2AndPROJJSON(oAttrs, "proj:wkt2", "proj:projjson");
     322             :             }
     323             :         }
     324             : 
     325           2 :         if (GetDimensionCount() >= 2)
     326             :         {
     327           2 :             bool bAddSpatialProjConvention = false;
     328             : 
     329           2 :             double dfXOff = std::numeric_limits<double>::quiet_NaN();
     330           2 :             double dfXRes = std::numeric_limits<double>::quiet_NaN();
     331           2 :             double dfYOff = std::numeric_limits<double>::quiet_NaN();
     332           2 :             double dfYRes = std::numeric_limits<double>::quiet_NaN();
     333           4 :             std::string osDimXName;
     334           4 :             std::string osDimYName;
     335           4 :             std::string osDimZName;
     336           2 :             double dfWidth = 0, dfHeight = 0;
     337           6 :             for (const auto &poDim : GetDimensions())
     338             :             {
     339           4 :                 if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X)
     340             :                 {
     341           2 :                     osDimXName = poDim->GetName();
     342           2 :                     dfWidth = static_cast<double>(poDim->GetSize());
     343           4 :                     auto poVar = poDim->GetIndexingVariable();
     344           2 :                     if (poVar && poVar->IsRegularlySpaced(dfXOff, dfXRes))
     345             :                     {
     346           1 :                         dfXOff -= dfXRes / 2;
     347             :                     }
     348             :                 }
     349           2 :                 else if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y)
     350             :                 {
     351           2 :                     osDimYName = poDim->GetName();
     352           2 :                     dfHeight = static_cast<double>(poDim->GetSize());
     353           4 :                     auto poVar = poDim->GetIndexingVariable();
     354           2 :                     if (poVar && poVar->IsRegularlySpaced(dfYOff, dfYRes))
     355             :                     {
     356           1 :                         dfYOff -= dfYRes / 2;
     357             :                     }
     358             :                 }
     359           0 :                 else if (poDim->GetType() == GDAL_DIM_TYPE_VERTICAL)
     360             :                 {
     361           0 :                     osDimZName = poDim->GetName();
     362             :                 }
     363             :             }
     364             : 
     365           2 :             GDALGeoTransform gt;
     366           2 :             if (!osDimXName.empty() && !osDimYName.empty())
     367             :             {
     368           6 :                 const auto oGDALGeoTransform = oAttrs["gdal:geotransform"];
     369             :                 const bool bHasGDALGeoTransform =
     370           4 :                     (oGDALGeoTransform.GetType() ==
     371           4 :                          CPLJSONObject::Type::Array &&
     372           4 :                      oGDALGeoTransform.ToArray().size() == 6);
     373           2 :                 if (bHasGDALGeoTransform)
     374             :                 {
     375             :                     const auto oGDALGeoTransformArray =
     376           2 :                         oGDALGeoTransform.ToArray();
     377          14 :                     for (int i = 0; i < 6; ++i)
     378             :                     {
     379          12 :                         gt[i] = oGDALGeoTransformArray[i].ToDouble();
     380             :                     }
     381           2 :                     bAddSpatialProjConvention = true;
     382             :                 }
     383           0 :                 else if (!std::isnan(dfXOff) && !std::isnan(dfXRes) &&
     384           0 :                          !std::isnan(dfYOff) && !std::isnan(dfYRes))
     385             :                 {
     386           0 :                     gt.xorig = dfXOff;
     387           0 :                     gt.xscale = dfXRes;
     388           0 :                     gt.xrot = 0;  // xrot
     389           0 :                     gt.yorig = dfYOff;
     390           0 :                     gt.yrot = 0;  // yrot
     391           0 :                     gt.yscale = dfYRes;
     392           0 :                     bAddSpatialProjConvention = true;
     393             :                 }
     394             :             }
     395             : 
     396           2 :             if (bAddSpatialProjConvention)
     397             :             {
     398             :                 const auto osGDALMD_AREA_OR_POINT =
     399           6 :                     oAttrs.GetString(GDALMD_AREA_OR_POINT);
     400           2 :                 if (osGDALMD_AREA_OR_POINT == GDALMD_AOP_AREA)
     401             :                 {
     402           1 :                     oAttrs.Add("spatial:registration", "pixel");
     403           1 :                     oAttrs.Delete(GDALMD_AREA_OR_POINT);
     404             :                 }
     405           1 :                 else if (osGDALMD_AREA_OR_POINT == GDALMD_AOP_POINT)
     406             :                 {
     407           1 :                     oAttrs.Add("spatial:registration", "node");
     408           1 :                     oAttrs.Delete(GDALMD_AREA_OR_POINT);
     409             : 
     410             :                     // Going from GDAL's corner convention to pixel center
     411           1 :                     gt.xorig += 0.5 * gt.xscale + 0.5 * gt.xrot;
     412           1 :                     gt.yorig += 0.5 * gt.yrot + 0.5 * gt.yscale;
     413           1 :                     dfWidth -= 1.0;
     414           1 :                     dfHeight -= 1.0;
     415             :                 }
     416             : 
     417           4 :                 CPLJSONArray oAttrSpatialTransform;
     418           2 :                 oAttrSpatialTransform.Add(gt.xscale);  // xres
     419           2 :                 oAttrSpatialTransform.Add(gt.xrot);    // xrot
     420           2 :                 oAttrSpatialTransform.Add(gt.xorig);   // xoff
     421           2 :                 oAttrSpatialTransform.Add(gt.yrot);    // yrot
     422           2 :                 oAttrSpatialTransform.Add(gt.yscale);  // yres
     423           2 :                 oAttrSpatialTransform.Add(gt.yorig);   // yoff
     424             : 
     425           2 :                 oAttrs.Add("spatial:transform_type", "affine");
     426           2 :                 oAttrs.Add("spatial:transform", oAttrSpatialTransform);
     427           2 :                 oAttrs.Delete("gdal:geotransform");
     428             : 
     429             :                 double dfX0, dfY0;
     430             :                 double dfX1, dfY1;
     431             :                 double dfX2, dfY2;
     432             :                 double dfX3, dfY3;
     433           2 :                 gt.Apply(0, 0, &dfX0, &dfY0);
     434           2 :                 gt.Apply(dfWidth, 0, &dfX1, &dfY1);
     435           2 :                 gt.Apply(0, dfHeight, &dfX2, &dfY2);
     436           2 :                 gt.Apply(dfWidth, dfHeight, &dfX3, &dfY3);
     437             :                 const double dfXMin =
     438           2 :                     std::min(std::min(dfX0, dfX1), std::min(dfX2, dfX3));
     439             :                 const double dfYMin =
     440           2 :                     std::min(std::min(dfY0, dfY1), std::min(dfY2, dfY3));
     441             :                 const double dfXMax =
     442           2 :                     std::max(std::max(dfX0, dfX1), std::max(dfX2, dfX3));
     443             :                 const double dfYMax =
     444           2 :                     std::max(std::max(dfY0, dfY1), std::max(dfY2, dfY3));
     445             : 
     446           4 :                 CPLJSONArray oAttrSpatialBBOX;
     447           2 :                 oAttrSpatialBBOX.Add(dfXMin);
     448           2 :                 oAttrSpatialBBOX.Add(dfYMin);
     449           2 :                 oAttrSpatialBBOX.Add(dfXMax);
     450           2 :                 oAttrSpatialBBOX.Add(dfYMax);
     451           2 :                 oAttrs.Add("spatial:bbox", oAttrSpatialBBOX);
     452             : 
     453           4 :                 CPLJSONArray aoSpatialDimensions;
     454           2 :                 if (!osDimZName.empty())
     455           0 :                     aoSpatialDimensions.Add(osDimZName);
     456           2 :                 aoSpatialDimensions.Add(osDimYName);
     457           2 :                 aoSpatialDimensions.Add(osDimXName);
     458           2 :                 oAttrs.Add("spatial:dimensions", aoSpatialDimensions);
     459             : 
     460           4 :                 CPLJSONObject oConventionSpatial;
     461           2 :                 oConventionSpatial.Set(
     462             :                     "schema_url",
     463             :                     "https://raw.githubusercontent.com/zarr-conventions/"
     464             :                     "spatial/refs/tags/v1/schema.json");
     465           2 :                 oConventionSpatial.Set("spec_url",
     466             :                                        "https://github.com/zarr-conventions/"
     467             :                                        "spatial/blob/v1/README.md");
     468           2 :                 oConventionSpatial.Set("uuid",
     469             :                                        "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4");
     470           2 :                 oConventionSpatial.Set("name",
     471             :                                        "spatial:");  // ending colon intended
     472           2 :                 oConventionSpatial.Set("description",
     473             :                                        "Spatial coordinate information");
     474             : 
     475           2 :                 oZarrConventionsArray.Add(oConventionSpatial);
     476             :             }
     477             :         }
     478             : 
     479           2 :         if (oZarrConventionsArray.size() > 0)
     480             :         {
     481           2 :             oAttrs.Add("zarr_conventions", oZarrConventionsArray);
     482             :         }
     483             :     }
     484         403 :     else if (m_poSRS)
     485             :     {
     486             :         // GDAL convention
     487             : 
     488          40 :         CPLJSONObject oCRS;
     489          40 :         ExportToWkt2AndPROJJSON(oCRS, "wkt", "projjson");
     490             : 
     491          40 :         const char *pszAuthorityCode = m_poSRS->GetAuthorityCode(nullptr);
     492          40 :         const char *pszAuthorityName = m_poSRS->GetAuthorityName(nullptr);
     493          40 :         if (pszAuthorityCode && pszAuthorityName &&
     494           7 :             EQUAL(pszAuthorityName, "EPSG"))
     495             :         {
     496           7 :             oCRS.Add("url",
     497          14 :                      std::string("http://www.opengis.net/def/crs/EPSG/0/") +
     498             :                          pszAuthorityCode);
     499             :         }
     500             : 
     501          40 :         oAttrs.Add(CRS_ATTRIBUTE_NAME, oCRS);
     502             :     }
     503             : 
     504         405 :     if (m_osUnit.empty())
     505             :     {
     506         396 :         if (m_bUnitModified)
     507           0 :             oAttrs.Delete(CF_UNITS);
     508             :     }
     509             :     else
     510             :     {
     511           9 :         oAttrs.Set(CF_UNITS, m_osUnit);
     512             :     }
     513         405 :     m_bUnitModified = false;
     514             : 
     515         405 :     if (!m_bHasOffset)
     516             :     {
     517         402 :         oAttrs.Delete(CF_ADD_OFFSET);
     518             :     }
     519             :     else
     520             :     {
     521           3 :         oAttrs.Set(CF_ADD_OFFSET, m_dfOffset);
     522             :     }
     523         405 :     m_bOffsetModified = false;
     524             : 
     525         405 :     if (!m_bHasScale)
     526             :     {
     527         402 :         oAttrs.Delete(CF_SCALE_FACTOR);
     528             :     }
     529             :     else
     530             :     {
     531           3 :         oAttrs.Set(CF_SCALE_FACTOR, m_dfScale);
     532             :     }
     533         405 :     m_bScaleModified = false;
     534             : 
     535         810 :     return oAttrs;
     536             : }
     537             : 
     538             : /************************************************************************/
     539             : /*                           FillBlockSize()                            */
     540             : /************************************************************************/
     541             : 
     542             : /* static */
     543         521 : bool ZarrArray::FillBlockSize(
     544             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
     545             :     const GDALExtendedDataType &oDataType, std::vector<GUInt64> &anBlockSize,
     546             :     CSLConstList papszOptions)
     547             : {
     548         521 :     const auto nDims = aoDimensions.size();
     549         521 :     anBlockSize.resize(nDims);
     550        1318 :     for (size_t i = 0; i < nDims; ++i)
     551         797 :         anBlockSize[i] = 1;
     552         521 :     if (nDims >= 2)
     553             :     {
     554         536 :         anBlockSize[nDims - 2] =
     555         536 :             std::min(std::max<GUInt64>(1, aoDimensions[nDims - 2]->GetSize()),
     556         536 :                      static_cast<GUInt64>(256));
     557         536 :         anBlockSize[nDims - 1] =
     558         536 :             std::min(std::max<GUInt64>(1, aoDimensions[nDims - 1]->GetSize()),
     559         804 :                      static_cast<GUInt64>(256));
     560             :     }
     561         253 :     else if (nDims == 1)
     562             :     {
     563         206 :         anBlockSize[0] = std::max<GUInt64>(1, aoDimensions[0]->GetSize());
     564             :     }
     565             : 
     566         521 :     const char *pszBlockSize = CSLFetchNameValue(papszOptions, "BLOCKSIZE");
     567         521 :     if (pszBlockSize)
     568             :     {
     569             :         const auto aszTokens(
     570          54 :             CPLStringList(CSLTokenizeString2(pszBlockSize, ",", 0)));
     571          54 :         if (static_cast<size_t>(aszTokens.size()) != nDims)
     572             :         {
     573           5 :             CPLError(CE_Failure, CPLE_AppDefined,
     574             :                      "Invalid number of values in BLOCKSIZE");
     575           5 :             return false;
     576             :         }
     577          49 :         size_t nBlockSize = oDataType.GetSize();
     578         157 :         for (size_t i = 0; i < nDims; ++i)
     579             :         {
     580         109 :             const auto v = static_cast<GUInt64>(CPLAtoGIntBig(aszTokens[i]));
     581         109 :             if (v > 0)
     582             :             {
     583         109 :                 anBlockSize[i] = v;
     584             :             }
     585         109 :             if (anBlockSize[i] >
     586         109 :                 std::numeric_limits<size_t>::max() / nBlockSize)
     587             :             {
     588           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     589             :                          "Too large values in BLOCKSIZE");
     590           1 :                 return false;
     591             :             }
     592         108 :             nBlockSize *= static_cast<size_t>(anBlockSize[i]);
     593             :         }
     594             :     }
     595         515 :     return true;
     596             : }
     597             : 
     598             : /************************************************************************/
     599             : /*                     DeallocateDecodedBlockData()                     */
     600             : /************************************************************************/
     601             : 
     602        4517 : void ZarrArray::DeallocateDecodedBlockData()
     603             : {
     604        4517 :     if (!m_abyDecodedBlockData.empty())
     605             :     {
     606         423 :         const size_t nDTSize = m_oType.GetSize();
     607         423 :         GByte *pDst = &m_abyDecodedBlockData[0];
     608         423 :         const size_t nValues = m_abyDecodedBlockData.size() / nDTSize;
     609         854 :         for (const auto &elt : m_aoDtypeElts)
     610             :         {
     611         431 :             if (elt.nativeType == DtypeElt::NativeType::STRING_ASCII ||
     612         430 :                 elt.nativeType == DtypeElt::NativeType::STRING_UNICODE)
     613             :             {
     614           2 :                 for (size_t i = 0; i < nValues; i++, pDst += nDTSize)
     615             :                 {
     616             :                     char *ptr;
     617           1 :                     char **pptr =
     618           1 :                         reinterpret_cast<char **>(pDst + elt.gdalOffset);
     619           1 :                     memcpy(&ptr, pptr, sizeof(ptr));
     620           1 :                     VSIFree(ptr);
     621             :                 }
     622             :             }
     623             :         }
     624             :     }
     625        4517 : }
     626             : 
     627             : /************************************************************************/
     628             : /*                             EncodeElt()                              */
     629             : /************************************************************************/
     630             : 
     631             : /* Encode from GDAL raw type to Zarr native type */
     632             : /*static*/
     633         241 : void ZarrArray::EncodeElt(const std::vector<DtypeElt> &elts, const GByte *pSrc,
     634             :                           GByte *pDst)
     635             : {
     636         483 :     for (const auto &elt : elts)
     637             :     {
     638         242 :         if (elt.nativeType == DtypeElt::NativeType::STRING_UNICODE)
     639             :         {
     640           0 :             const char *pStr =
     641           0 :                 *reinterpret_cast<const char *const *>(pSrc + elt.gdalOffset);
     642           0 :             if (pStr)
     643             :             {
     644             :                 try
     645             :                 {
     646           0 :                     const auto ucs4 = UTF8ToUCS4(pStr, elt.needByteSwapping);
     647           0 :                     const auto ucs4Len = ucs4.size();
     648           0 :                     memcpy(pDst + elt.nativeOffset, ucs4.data(),
     649           0 :                            std::min(ucs4Len, elt.nativeSize));
     650           0 :                     if (ucs4Len > elt.nativeSize)
     651             :                     {
     652           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     653             :                                  "Too long string truncated");
     654             :                     }
     655           0 :                     else if (ucs4Len < elt.nativeSize)
     656             :                     {
     657           0 :                         memset(pDst + elt.nativeOffset + ucs4Len, 0,
     658           0 :                                elt.nativeSize - ucs4Len);
     659             :                     }
     660             :                 }
     661           0 :                 catch (const std::exception &)
     662             :                 {
     663           0 :                     memset(pDst + elt.nativeOffset, 0, elt.nativeSize);
     664             :                 }
     665             :             }
     666             :             else
     667             :             {
     668           0 :                 memset(pDst + elt.nativeOffset, 0, elt.nativeSize);
     669             :             }
     670             :         }
     671         242 :         else if (elt.needByteSwapping)
     672             :         {
     673         240 :             if (elt.nativeSize == 2)
     674             :             {
     675          48 :                 const uint16_t val = CPL_SWAP16(
     676             :                     *reinterpret_cast<const uint16_t *>(pSrc + elt.gdalOffset));
     677          48 :                 memcpy(pDst + elt.nativeOffset, &val, sizeof(val));
     678             :             }
     679         192 :             else if (elt.nativeSize == 4)
     680             :             {
     681          72 :                 const uint32_t val = CPL_SWAP32(
     682             :                     *reinterpret_cast<const uint32_t *>(pSrc + elt.gdalOffset));
     683          72 :                 memcpy(pDst + elt.nativeOffset, &val, sizeof(val));
     684             :             }
     685         120 :             else if (elt.nativeSize == 8)
     686             :             {
     687          96 :                 if (elt.nativeType == DtypeElt::NativeType::COMPLEX_IEEEFP)
     688             :                 {
     689          24 :                     uint32_t val =
     690          24 :                         CPL_SWAP32(*reinterpret_cast<const uint32_t *>(
     691             :                             pSrc + elt.gdalOffset));
     692          24 :                     memcpy(pDst + elt.nativeOffset, &val, sizeof(val));
     693          24 :                     val = CPL_SWAP32(*reinterpret_cast<const uint32_t *>(
     694             :                         pSrc + elt.gdalOffset + 4));
     695          24 :                     memcpy(pDst + elt.nativeOffset + 4, &val, sizeof(val));
     696             :                 }
     697             :                 else
     698             :                 {
     699          72 :                     const uint64_t val =
     700          72 :                         CPL_SWAP64(*reinterpret_cast<const uint64_t *>(
     701             :                             pSrc + elt.gdalOffset));
     702          72 :                     memcpy(pDst + elt.nativeOffset, &val, sizeof(val));
     703             :                 }
     704             :             }
     705          24 :             else if (elt.nativeSize == 16)
     706             :             {
     707          24 :                 uint64_t val = CPL_SWAP64(
     708             :                     *reinterpret_cast<const uint64_t *>(pSrc + elt.gdalOffset));
     709          24 :                 memcpy(pDst + elt.nativeOffset, &val, sizeof(val));
     710          24 :                 val = CPL_SWAP64(*reinterpret_cast<const uint64_t *>(
     711             :                     pSrc + elt.gdalOffset + 8));
     712          24 :                 memcpy(pDst + elt.nativeOffset + 8, &val, sizeof(val));
     713             :             }
     714             :             else
     715             :             {
     716           0 :                 CPLAssert(false);
     717             :             }
     718             :         }
     719           2 :         else if (elt.gdalTypeIsApproxOfNative)
     720             :         {
     721           0 :             CPLAssert(false);
     722             :         }
     723           2 :         else if (elt.nativeType == DtypeElt::NativeType::STRING_ASCII)
     724             :         {
     725           0 :             const char *pStr =
     726           0 :                 *reinterpret_cast<const char *const *>(pSrc + elt.gdalOffset);
     727           0 :             if (pStr)
     728             :             {
     729           0 :                 const size_t nLen = strlen(pStr);
     730           0 :                 memcpy(pDst + elt.nativeOffset, pStr,
     731           0 :                        std::min(nLen, elt.nativeSize));
     732           0 :                 if (nLen < elt.nativeSize)
     733           0 :                     memset(pDst + elt.nativeOffset + nLen, 0,
     734           0 :                            elt.nativeSize - nLen);
     735             :             }
     736             :             else
     737             :             {
     738           0 :                 memset(pDst + elt.nativeOffset, 0, elt.nativeSize);
     739             :             }
     740             :         }
     741             :         else
     742             :         {
     743           2 :             CPLAssert(elt.nativeSize == elt.gdalSize);
     744           2 :             memcpy(pDst + elt.nativeOffset, pSrc + elt.gdalOffset,
     745           2 :                    elt.nativeSize);
     746             :         }
     747             :     }
     748         241 : }
     749             : 
     750             : /************************************************************************/
     751             : /*                 ZarrArray::SerializeNumericNoData()                  */
     752             : /************************************************************************/
     753             : 
     754          22 : void ZarrArray::SerializeNumericNoData(CPLJSONObject &oRoot) const
     755             : {
     756          22 :     if (m_oType.GetNumericDataType() == GDT_Int64)
     757             :     {
     758           0 :         const auto nVal = GetNoDataValueAsInt64();
     759           0 :         oRoot.Add("fill_value", static_cast<GInt64>(nVal));
     760             :     }
     761          22 :     else if (m_oType.GetNumericDataType() == GDT_UInt64)
     762             :     {
     763           0 :         const auto nVal = GetNoDataValueAsUInt64();
     764           0 :         oRoot.Add("fill_value", static_cast<uint64_t>(nVal));
     765             :     }
     766             :     else
     767             :     {
     768          22 :         const double dfVal = GetNoDataValueAsDouble();
     769          22 :         if (std::isnan(dfVal))
     770           3 :             oRoot.Add("fill_value", "NaN");
     771          19 :         else if (dfVal == std::numeric_limits<double>::infinity())
     772           2 :             oRoot.Add("fill_value", "Infinity");
     773          17 :         else if (dfVal == -std::numeric_limits<double>::infinity())
     774           2 :             oRoot.Add("fill_value", "-Infinity");
     775          15 :         else if (GDALDataTypeIsInteger(m_oType.GetNumericDataType()))
     776           9 :             oRoot.Add("fill_value", static_cast<GInt64>(dfVal));
     777             :         else
     778           6 :             oRoot.Add("fill_value", dfVal);
     779             :     }
     780          22 : }
     781             : 
     782             : /************************************************************************/
     783             : /*                      ZarrArray::GetSpatialRef()                      */
     784             : /************************************************************************/
     785             : 
     786          49 : std::shared_ptr<OGRSpatialReference> ZarrArray::GetSpatialRef() const
     787             : {
     788          49 :     if (!CheckValidAndErrorOutIfNot())
     789           0 :         return nullptr;
     790             : 
     791          49 :     if (m_poSRS)
     792          30 :         return m_poSRS;
     793          19 :     return GDALPamMDArray::GetSpatialRef();
     794             : }
     795             : 
     796             : /************************************************************************/
     797             : /*                         SetRawNoDataValue()                          */
     798             : /************************************************************************/
     799             : 
     800          25 : bool ZarrArray::SetRawNoDataValue(const void *pRawNoData)
     801             : {
     802          25 :     if (!CheckValidAndErrorOutIfNot())
     803           0 :         return false;
     804             : 
     805          25 :     if (!m_bUpdatable)
     806             :     {
     807           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Array opened in read-only mode");
     808           0 :         return false;
     809             :     }
     810          25 :     m_bDefinitionModified = true;
     811          25 :     RegisterNoDataValue(pRawNoData);
     812          25 :     return true;
     813             : }
     814             : 
     815             : /************************************************************************/
     816             : /*                        RegisterNoDataValue()                         */
     817             : /************************************************************************/
     818             : 
     819        1179 : void ZarrArray::RegisterNoDataValue(const void *pNoData)
     820             : {
     821        1179 :     if (m_pabyNoData)
     822             :     {
     823           0 :         m_oType.FreeDynamicMemory(&m_pabyNoData[0]);
     824             :     }
     825             : 
     826        1179 :     if (pNoData == nullptr)
     827             :     {
     828           0 :         CPLFree(m_pabyNoData);
     829           0 :         m_pabyNoData = nullptr;
     830             :     }
     831             :     else
     832             :     {
     833        1179 :         const auto nSize = m_oType.GetSize();
     834        1179 :         if (m_pabyNoData == nullptr)
     835             :         {
     836        1179 :             m_pabyNoData = static_cast<GByte *>(CPLMalloc(nSize));
     837             :         }
     838        1179 :         memset(m_pabyNoData, 0, nSize);
     839        1179 :         GDALExtendedDataType::CopyValue(pNoData, m_oType, m_pabyNoData,
     840        1179 :                                         m_oType);
     841             :     }
     842        1179 : }
     843             : 
     844             : /************************************************************************/
     845             : /*                          DecodeSourceElt()                           */
     846             : /************************************************************************/
     847             : 
     848             : /* static */
     849        2296 : void ZarrArray::DecodeSourceElt(const std::vector<DtypeElt> &elts,
     850             :                                 const GByte *pSrc, GByte *pDst)
     851             : {
     852        4624 :     for (const auto &elt : elts)
     853             :     {
     854        2328 :         if (elt.nativeType == DtypeElt::NativeType::STRING_UNICODE)
     855             :         {
     856             :             char *ptr;
     857           0 :             char **pDstPtr = reinterpret_cast<char **>(pDst + elt.gdalOffset);
     858           0 :             memcpy(&ptr, pDstPtr, sizeof(ptr));
     859           0 :             VSIFree(ptr);
     860             : 
     861           0 :             char *pDstStr = UCS4ToUTF8(pSrc + elt.nativeOffset, elt.nativeSize,
     862           0 :                                        elt.needByteSwapping);
     863           0 :             memcpy(pDstPtr, &pDstStr, sizeof(pDstStr));
     864             :         }
     865        2328 :         else if (elt.needByteSwapping)
     866             :         {
     867        2282 :             if (elt.nativeSize == 2)
     868             :             {
     869             :                 uint16_t val;
     870         458 :                 memcpy(&val, pSrc + elt.nativeOffset, sizeof(val));
     871         458 :                 *reinterpret_cast<uint16_t *>(pDst + elt.gdalOffset) =
     872         458 :                     CPL_SWAP16(val);
     873             :             }
     874        1824 :             else if (elt.nativeSize == 4)
     875             :             {
     876             :                 uint32_t val;
     877         684 :                 memcpy(&val, pSrc + elt.nativeOffset, sizeof(val));
     878         684 :                 *reinterpret_cast<uint32_t *>(pDst + elt.gdalOffset) =
     879         684 :                     CPL_SWAP32(val);
     880             :             }
     881        1140 :             else if (elt.nativeSize == 8)
     882             :             {
     883         912 :                 if (elt.nativeType == DtypeElt::NativeType::COMPLEX_IEEEFP)
     884             :                 {
     885             :                     uint32_t val;
     886         228 :                     memcpy(&val, pSrc + elt.nativeOffset, sizeof(val));
     887         228 :                     *reinterpret_cast<uint32_t *>(pDst + elt.gdalOffset) =
     888         228 :                         CPL_SWAP32(val);
     889         228 :                     memcpy(&val, pSrc + elt.nativeOffset + 4, sizeof(val));
     890         228 :                     *reinterpret_cast<uint32_t *>(pDst + elt.gdalOffset + 4) =
     891         228 :                         CPL_SWAP32(val);
     892             :                 }
     893             :                 else
     894             :                 {
     895             :                     uint64_t val;
     896         684 :                     memcpy(&val, pSrc + elt.nativeOffset, sizeof(val));
     897         684 :                     *reinterpret_cast<uint64_t *>(pDst + elt.gdalOffset) =
     898         684 :                         CPL_SWAP64(val);
     899             :                 }
     900             :             }
     901         228 :             else if (elt.nativeSize == 16)
     902             :             {
     903             :                 uint64_t val;
     904         228 :                 memcpy(&val, pSrc + elt.nativeOffset, sizeof(val));
     905         228 :                 *reinterpret_cast<uint64_t *>(pDst + elt.gdalOffset) =
     906         228 :                     CPL_SWAP64(val);
     907         228 :                 memcpy(&val, pSrc + elt.nativeOffset + 8, sizeof(val));
     908         228 :                 *reinterpret_cast<uint64_t *>(pDst + elt.gdalOffset + 8) =
     909         228 :                     CPL_SWAP64(val);
     910             :             }
     911             :             else
     912             :             {
     913           0 :                 CPLAssert(false);
     914             :             }
     915             :         }
     916          46 :         else if (elt.gdalTypeIsApproxOfNative)
     917             :         {
     918           0 :             CPLAssert(false);
     919             :         }
     920          46 :         else if (elt.nativeType == DtypeElt::NativeType::STRING_ASCII)
     921             :         {
     922             :             char *ptr;
     923           3 :             char **pDstPtr = reinterpret_cast<char **>(pDst + elt.gdalOffset);
     924           3 :             memcpy(&ptr, pDstPtr, sizeof(ptr));
     925           3 :             VSIFree(ptr);
     926             : 
     927           3 :             char *pDstStr = static_cast<char *>(CPLMalloc(elt.nativeSize + 1));
     928           3 :             memcpy(pDstStr, pSrc + elt.nativeOffset, elt.nativeSize);
     929           3 :             pDstStr[elt.nativeSize] = 0;
     930           3 :             memcpy(pDstPtr, &pDstStr, sizeof(pDstStr));
     931             :         }
     932             :         else
     933             :         {
     934          43 :             CPLAssert(elt.nativeSize == elt.gdalSize);
     935          43 :             memcpy(pDst + elt.gdalOffset, pSrc + elt.nativeOffset,
     936          43 :                    elt.nativeSize);
     937             :         }
     938             :     }
     939        2296 : }
     940             : 
     941             : /************************************************************************/
     942             : /*                    ZarrArray::IAdviseReadCommon()                    */
     943             : /************************************************************************/
     944             : 
     945         632 : bool ZarrArray::IAdviseReadCommon(const GUInt64 *arrayStartIdx,
     946             :                                   const size_t *count,
     947             :                                   CSLConstList papszOptions,
     948             :                                   std::vector<uint64_t> &anIndicesCur,
     949             :                                   int &nThreadsMax,
     950             :                                   std::vector<uint64_t> &anReqBlocksIndices,
     951             :                                   size_t &nReqBlocks) const
     952             : {
     953         632 :     if (!CheckValidAndErrorOutIfNot())
     954           0 :         return false;
     955             : 
     956         632 :     if (!FlushDirtyBlock())
     957           0 :         return false;
     958             : 
     959         632 :     const size_t nDims = m_aoDims.size();
     960         632 :     anIndicesCur.resize(nDims);
     961        1264 :     std::vector<uint64_t> anIndicesMin(nDims);
     962        1264 :     std::vector<uint64_t> anIndicesMax(nDims);
     963             : 
     964             :     // Compute min and max tile indices in each dimension, and the total
     965             :     // number of tiles this represents.
     966         632 :     nReqBlocks = 1;
     967        1906 :     for (size_t i = 0; i < nDims; ++i)
     968             :     {
     969        1274 :         anIndicesMin[i] = arrayStartIdx[i] / m_anInnerBlockSize[i];
     970        2548 :         anIndicesMax[i] =
     971        1274 :             (arrayStartIdx[i] + count[i] - 1) / m_anInnerBlockSize[i];
     972             :         // Overflow on number of tiles already checked in Create()
     973        1274 :         nReqBlocks *=
     974        1274 :             static_cast<size_t>(anIndicesMax[i] - anIndicesMin[i] + 1);
     975             :     }
     976             : 
     977             :     // Find available cache size
     978         632 :     const size_t nCacheSize = [papszOptions]()
     979             :     {
     980             :         size_t nCacheSizeTmp;
     981             :         const char *pszCacheSize =
     982         632 :             CSLFetchNameValue(papszOptions, "CACHE_SIZE");
     983         632 :         if (pszCacheSize)
     984             :         {
     985           4 :             const auto nCacheSizeBig = CPLAtoGIntBig(pszCacheSize);
     986           8 :             if (nCacheSizeBig < 0 || static_cast<uint64_t>(nCacheSizeBig) >
     987           4 :                                          std::numeric_limits<size_t>::max() / 2)
     988             :             {
     989           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory, "Too big CACHE_SIZE");
     990           0 :                 return std::numeric_limits<size_t>::max();
     991             :             }
     992           4 :             nCacheSizeTmp = static_cast<size_t>(nCacheSizeBig);
     993             :         }
     994             :         else
     995             :         {
     996             :             // Arbitrarily take half of remaining cache size
     997         628 :             nCacheSizeTmp = static_cast<size_t>(std::min(
     998        1256 :                 static_cast<uint64_t>(
     999         628 :                     (GDALGetCacheMax64() - GDALGetCacheUsed64()) / 2),
    1000        1256 :                 static_cast<uint64_t>(std::numeric_limits<size_t>::max() / 2)));
    1001         628 :             CPLDebug(ZARR_DEBUG_KEY, "Using implicit CACHE_SIZE=" CPL_FRMT_GUIB,
    1002             :                      static_cast<GUIntBig>(nCacheSizeTmp));
    1003             :         }
    1004         632 :         return nCacheSizeTmp;
    1005         632 :     }();
    1006         632 :     if (nCacheSize == std::numeric_limits<size_t>::max())
    1007           0 :         return false;
    1008             : 
    1009             :     // Check that cache size is sufficient to hold all needed tiles.
    1010             :     // Also check that anReqBlocksIndices size computation won't overflow.
    1011         632 :     if (nReqBlocks > nCacheSize / std::max(m_nInnerBlockSizeBytes, nDims))
    1012             :     {
    1013           4 :         CPLError(CE_Failure, CPLE_OutOfMemory,
    1014             :                  "CACHE_SIZE=" CPL_FRMT_GUIB " is not big enough to cache "
    1015             :                  "all needed tiles. "
    1016             :                  "At least " CPL_FRMT_GUIB " bytes would be needed",
    1017             :                  static_cast<GUIntBig>(nCacheSize),
    1018             :                  static_cast<GUIntBig>(
    1019           4 :                      nReqBlocks * std::max(m_nInnerBlockSizeBytes, nDims)));
    1020           4 :         return false;
    1021             :     }
    1022             : 
    1023         628 :     nThreadsMax = GDALGetNumThreads(papszOptions, "NUM_THREADS",
    1024             :                                     GDAL_DEFAULT_MAX_THREAD_COUNT,
    1025             :                                     /* bDefaultAllCPUs=*/true);
    1026         628 :     if (nThreadsMax <= 1)
    1027         336 :         return true;
    1028             : 
    1029             :     // libhdf5 doesn't like concurrent access to the same file, even under
    1030             :     // a mutex...
    1031         584 :     auto poBlockPresenceArray = OpenBlockPresenceCache(false);
    1032         292 :     if (poBlockPresenceArray)
    1033             :     {
    1034           6 :         nThreadsMax = 1;
    1035           6 :         return true;
    1036             :     }
    1037             : 
    1038         286 :     CPLDebug(ZARR_DEBUG_KEY, "IAdviseRead(): Using up to %d threads",
    1039             :              nThreadsMax);
    1040             : 
    1041         286 :     m_oChunkCache.clear();
    1042         286 :     m_anCachedAdviseReadStart.assign(arrayStartIdx, arrayStartIdx + nDims);
    1043         286 :     m_anCachedAdviseReadCount.assign(count, count + nDims);
    1044             : 
    1045             :     // Overflow checked above
    1046             :     try
    1047             :     {
    1048         286 :         anReqBlocksIndices.resize(nDims * nReqBlocks);
    1049             :     }
    1050           0 :     catch (const std::bad_alloc &e)
    1051             :     {
    1052           0 :         CPLError(CE_Failure, CPLE_OutOfMemory,
    1053           0 :                  "Cannot allocate anReqBlocksIndices: %s", e.what());
    1054           0 :         return false;
    1055             :     }
    1056             : 
    1057         286 :     size_t dimIdx = 0;
    1058         286 :     size_t nBlockIter = 0;
    1059       28278 : lbl_next_depth:
    1060       28278 :     if (dimIdx == nDims)
    1061             :     {
    1062       26922 :         if (nDims == 2)
    1063             :         {
    1064             :             // optimize in common case
    1065       22748 :             memcpy(&anReqBlocksIndices[nBlockIter * nDims], anIndicesCur.data(),
    1066             :                    sizeof(uint64_t) * 2);
    1067             :         }
    1068        4174 :         else if (nDims == 3)
    1069             :         {
    1070             :             // optimize in common case
    1071        4174 :             memcpy(&anReqBlocksIndices[nBlockIter * nDims], anIndicesCur.data(),
    1072             :                    sizeof(uint64_t) * 3);
    1073             :         }
    1074             :         else
    1075             :         {
    1076           0 :             memcpy(&anReqBlocksIndices[nBlockIter * nDims], anIndicesCur.data(),
    1077           0 :                    sizeof(uint64_t) * nDims);
    1078             :         }
    1079       26922 :         nBlockIter++;
    1080             :     }
    1081             :     else
    1082             :     {
    1083             :         // This level of loop loops over blocks
    1084        1356 :         anIndicesCur[dimIdx] = anIndicesMin[dimIdx];
    1085             :         while (true)
    1086             :         {
    1087       27992 :             dimIdx++;
    1088       27992 :             goto lbl_next_depth;
    1089       27992 :         lbl_return_to_caller:
    1090       27992 :             dimIdx--;
    1091       27992 :             if (anIndicesCur[dimIdx] == anIndicesMax[dimIdx])
    1092        1356 :                 break;
    1093       26636 :             ++anIndicesCur[dimIdx];
    1094             :         }
    1095             :     }
    1096       28278 :     if (dimIdx > 0)
    1097       27992 :         goto lbl_return_to_caller;
    1098         286 :     assert(nBlockIter == nReqBlocks);
    1099             : 
    1100         286 :     return true;
    1101             : }
    1102             : 
    1103             : /************************************************************************/
    1104             : /*                          ZarrArray::IRead()                          */
    1105             : /************************************************************************/
    1106             : 
    1107        2748 : bool ZarrArray::IRead(const GUInt64 *arrayStartIdx, const size_t *count,
    1108             :                       const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
    1109             :                       const GDALExtendedDataType &bufferDataType,
    1110             :                       void *pDstBuffer) const
    1111             : {
    1112        2748 :     if (!CheckValidAndErrorOutIfNot())
    1113           0 :         return false;
    1114             : 
    1115        2748 :     if (!AllocateWorkingBuffers())
    1116           3 :         return false;
    1117             : 
    1118             :     // Need to be kept in top-level scope
    1119        5490 :     std::vector<GUInt64> arrayStartIdxMod;
    1120        5490 :     std::vector<GInt64> arrayStepMod;
    1121        5490 :     std::vector<GPtrDiff_t> bufferStrideMod;
    1122             : 
    1123        2745 :     const size_t nDims = m_aoDims.size();
    1124        2745 :     bool negativeStep = false;
    1125        7580 :     for (size_t i = 0; i < nDims; ++i)
    1126             :     {
    1127        5169 :         if (arrayStep[i] < 0)
    1128             :         {
    1129         334 :             negativeStep = true;
    1130         334 :             break;
    1131             :         }
    1132             :     }
    1133             : 
    1134             :     // const auto eBufferDT = bufferDataType.GetNumericDataType();
    1135        2745 :     const auto nBufferDTSize = static_cast<int>(bufferDataType.GetSize());
    1136             : 
    1137             :     // Make sure that arrayStep[i] are positive for sake of simplicity
    1138        2745 :     if (negativeStep)
    1139             :     {
    1140             : #if defined(__GNUC__)
    1141             : #pragma GCC diagnostic push
    1142             : #pragma GCC diagnostic ignored "-Wnull-dereference"
    1143             : #endif
    1144         334 :         arrayStartIdxMod.resize(nDims);
    1145         334 :         arrayStepMod.resize(nDims);
    1146         334 :         bufferStrideMod.resize(nDims);
    1147             : #if defined(__GNUC__)
    1148             : #pragma GCC diagnostic pop
    1149             : #endif
    1150        1002 :         for (size_t i = 0; i < nDims; ++i)
    1151             :         {
    1152         668 :             if (arrayStep[i] < 0)
    1153             :             {
    1154        1336 :                 arrayStartIdxMod[i] =
    1155         668 :                     arrayStartIdx[i] - (count[i] - 1) * (-arrayStep[i]);
    1156         668 :                 arrayStepMod[i] = -arrayStep[i];
    1157         668 :                 bufferStrideMod[i] = -bufferStride[i];
    1158         668 :                 pDstBuffer =
    1159             :                     static_cast<GByte *>(pDstBuffer) +
    1160         668 :                     bufferStride[i] *
    1161         668 :                         static_cast<GPtrDiff_t>(nBufferDTSize * (count[i] - 1));
    1162             :             }
    1163             :             else
    1164             :             {
    1165           0 :                 arrayStartIdxMod[i] = arrayStartIdx[i];
    1166           0 :                 arrayStepMod[i] = arrayStep[i];
    1167           0 :                 bufferStrideMod[i] = bufferStride[i];
    1168             :             }
    1169             :         }
    1170         334 :         arrayStartIdx = arrayStartIdxMod.data();
    1171         334 :         arrayStep = arrayStepMod.data();
    1172         334 :         bufferStride = bufferStrideMod.data();
    1173             :     }
    1174             : 
    1175             :     // Auto-parallel: prefetch multi-chunk reads via IAdviseRead().
    1176             :     // arrayStep[i] guaranteed positive after negative-step normalization above.
    1177             :     // Skip if the current read region is already covered by a previous
    1178             :     // IAdviseRead (explicit or auto), so we don't blow away a useful cache.
    1179        2745 :     if (nDims >= 2)
    1180             :     {
    1181        2552 :         bool bAlreadyCached = false;
    1182        2552 :         if (!m_oChunkCache.empty() && m_anCachedAdviseReadStart.size() == nDims)
    1183             :         {
    1184         152 :             bAlreadyCached = true;
    1185         386 :             for (size_t i = 0; i < nDims && bAlreadyCached; ++i)
    1186             :             {
    1187         234 :                 const GUInt64 reqEnd =
    1188         234 :                     arrayStartIdx[i] +
    1189         234 :                     (count[i] - 1) * static_cast<uint64_t>(arrayStep[i]);
    1190         234 :                 const GUInt64 cachedEnd = m_anCachedAdviseReadStart[i] +
    1191         234 :                                           m_anCachedAdviseReadCount[i] - 1;
    1192         234 :                 if (arrayStartIdx[i] < m_anCachedAdviseReadStart[i] ||
    1193             :                     reqEnd > cachedEnd)
    1194             :                 {
    1195          74 :                     bAlreadyCached = false;
    1196             :                 }
    1197             :             }
    1198             :         }
    1199             : 
    1200        2552 :         if (!bAlreadyCached)
    1201             :         {
    1202             :             const char *pszNumThreads =
    1203        2474 :                 CPLGetConfigOption("GDAL_NUM_THREADS", nullptr);
    1204        2474 :             if (pszNumThreads != nullptr)
    1205             :             {
    1206         619 :                 size_t nReqChunks = 1;
    1207        1867 :                 for (size_t i = 0; i < nDims; ++i)
    1208             :                 {
    1209             :                     const uint64_t startBlock =
    1210        1248 :                         arrayStartIdx[i] / m_anInnerBlockSize[i];
    1211             :                     const uint64_t endBlock =
    1212        1248 :                         (arrayStartIdx[i] +
    1213        2496 :                          (count[i] - 1) * static_cast<uint64_t>(arrayStep[i])) /
    1214        1248 :                         m_anInnerBlockSize[i];
    1215        1248 :                     nReqChunks *=
    1216        1248 :                         static_cast<size_t>(endBlock - startBlock + 1);
    1217             :                 }
    1218         619 :                 if (nReqChunks > 1)
    1219             :                 {
    1220             :                     // IAdviseRead expects the contiguous element count per
    1221             :                     // dimension, not the strided output count.  When step > 1,
    1222             :                     // expand count to cover the full element range.
    1223        1238 :                     std::vector<size_t> anAdjustedCount(nDims);
    1224        1867 :                     for (size_t i = 0; i < nDims; ++i)
    1225             :                     {
    1226        1248 :                         anAdjustedCount[i] =
    1227        1248 :                             1 +
    1228        1248 :                             (count[i] - 1) * static_cast<size_t>(arrayStep[i]);
    1229             :                     }
    1230         619 :                     CPLStringList aosOptions;
    1231         619 :                     aosOptions.SetNameValue("NUM_THREADS", pszNumThreads);
    1232         619 :                     CPL_IGNORE_RET_VAL(IAdviseRead(arrayStartIdx,
    1233         619 :                                                    anAdjustedCount.data(),
    1234         619 :                                                    aosOptions.List()));
    1235             :                 }
    1236             :             }
    1237             :         }
    1238             :     }
    1239             : 
    1240        5490 :     std::vector<uint64_t> indicesOuterLoop(nDims + 1);
    1241        5490 :     std::vector<GByte *> dstPtrStackOuterLoop(nDims + 1);
    1242             : 
    1243        5490 :     std::vector<uint64_t> indicesInnerLoop(nDims + 1);
    1244        5490 :     std::vector<GByte *> dstPtrStackInnerLoop(nDims + 1);
    1245             : 
    1246        5490 :     std::vector<GPtrDiff_t> dstBufferStrideBytes;
    1247        8248 :     for (size_t i = 0; i < nDims; ++i)
    1248             :     {
    1249        5503 :         dstBufferStrideBytes.push_back(bufferStride[i] *
    1250        5503 :                                        static_cast<GPtrDiff_t>(nBufferDTSize));
    1251             :     }
    1252        2745 :     dstBufferStrideBytes.push_back(0);
    1253             : 
    1254        2745 :     const auto nDTSize = m_oType.GetSize();
    1255             : 
    1256        5490 :     std::vector<uint64_t> blockIndices(nDims);
    1257             :     const size_t nSourceSize =
    1258        2745 :         m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
    1259             : 
    1260        5490 :     std::vector<size_t> countInnerLoopInit(nDims + 1, 1);
    1261        5490 :     std::vector<size_t> countInnerLoop(nDims);
    1262             : 
    1263        5463 :     const bool bBothAreNumericDT = m_oType.GetClass() == GEDTC_NUMERIC &&
    1264        2718 :                                    bufferDataType.GetClass() == GEDTC_NUMERIC;
    1265             :     const bool bSameNumericDT =
    1266        5463 :         bBothAreNumericDT &&
    1267        2718 :         m_oType.GetNumericDataType() == bufferDataType.GetNumericDataType();
    1268        2745 :     const auto nSameDTSize = bSameNumericDT ? m_oType.GetSize() : 0;
    1269             :     const bool bSameCompoundAndNoDynamicMem =
    1270        2748 :         m_oType.GetClass() == GEDTC_COMPOUND && m_oType == bufferDataType &&
    1271           3 :         !m_oType.NeedsFreeDynamicMemory();
    1272        5490 :     std::vector<GByte> abyTargetNoData;
    1273        2745 :     bool bNoDataIsZero = false;
    1274             : 
    1275        2745 :     size_t dimIdx = 0;
    1276        2745 :     dstPtrStackOuterLoop[0] = static_cast<GByte *>(pDstBuffer);
    1277       45124 : lbl_next_depth:
    1278       45124 :     if (dimIdx == nDims)
    1279             :     {
    1280       36831 :         size_t dimIdxSubLoop = 0;
    1281       36831 :         dstPtrStackInnerLoop[0] = dstPtrStackOuterLoop[nDims];
    1282       36831 :         bool bEmptyChunk = false;
    1283             : 
    1284       36831 :         const GByte *pabySrcBlock = m_abyDecodedBlockData.empty()
    1285       35400 :                                         ? m_abyRawBlockData.data()
    1286       36831 :                                         : m_abyDecodedBlockData.data();
    1287       36831 :         bool bMatchFoundInMapChunkIndexToCachedBlock = false;
    1288             : 
    1289             :         // Use cache built by IAdviseRead() if possible
    1290       36831 :         if (!m_oChunkCache.empty())
    1291             :         {
    1292       32320 :             const auto oIter = m_oChunkCache.find(blockIndices);
    1293       32320 :             if (oIter != m_oChunkCache.end())
    1294             :             {
    1295       31316 :                 bMatchFoundInMapChunkIndexToCachedBlock = true;
    1296       31316 :                 if (oIter->second.abyDecoded.empty())
    1297             :                 {
    1298         944 :                     bEmptyChunk = true;
    1299             :                 }
    1300             :                 else
    1301             :                 {
    1302       30372 :                     pabySrcBlock = oIter->second.abyDecoded.data();
    1303             :                 }
    1304             :             }
    1305             :             else
    1306             :             {
    1307             : #ifdef DEBUG
    1308        2008 :                 std::string key;
    1309        3012 :                 for (size_t j = 0; j < nDims; ++j)
    1310             :                 {
    1311        2008 :                     if (j)
    1312        1004 :                         key += ',';
    1313        2008 :                     key += std::to_string(blockIndices[j]);
    1314             :                 }
    1315        1004 :                 CPLDebugOnly(ZARR_DEBUG_KEY, "Cache miss for tile %s",
    1316             :                              key.c_str());
    1317             : #endif
    1318             :             }
    1319             :         }
    1320             : 
    1321       36831 :         if (!bMatchFoundInMapChunkIndexToCachedBlock)
    1322             :         {
    1323        5515 :             if (!blockIndices.empty() && blockIndices == m_anCachedBlockIndices)
    1324             :             {
    1325         243 :                 if (!m_bCachedBlockValid)
    1326         495 :                     return false;
    1327         243 :                 bEmptyChunk = m_bCachedBlockEmpty;
    1328             :             }
    1329             :             else
    1330             :             {
    1331        5272 :                 if (!FlushDirtyBlock())
    1332           0 :                     return false;
    1333             : 
    1334        5272 :                 m_anCachedBlockIndices = blockIndices;
    1335        5272 :                 m_bCachedBlockValid =
    1336        5272 :                     LoadBlockData(blockIndices.data(), bEmptyChunk);
    1337        5272 :                 if (!m_bCachedBlockValid)
    1338             :                 {
    1339         495 :                     return false;
    1340             :                 }
    1341        4777 :                 m_bCachedBlockEmpty = bEmptyChunk;
    1342             :             }
    1343             : 
    1344       10040 :             pabySrcBlock = m_abyDecodedBlockData.empty()
    1345        4029 :                                ? m_abyRawBlockData.data()
    1346         991 :                                : m_abyDecodedBlockData.data();
    1347             :         }
    1348             :         const size_t nSrcDTSize =
    1349       36336 :             m_abyDecodedBlockData.empty() ? nSourceSize : nDTSize;
    1350             : 
    1351      113426 :         for (size_t i = 0; i < nDims; ++i)
    1352             :         {
    1353       77090 :             countInnerLoopInit[i] = 1;
    1354       77090 :             if (arrayStep[i] != 0)
    1355             :             {
    1356             :                 const auto nextBlockIdx =
    1357       76388 :                     std::min((1 + indicesOuterLoop[i] / m_anInnerBlockSize[i]) *
    1358       76388 :                                  m_anInnerBlockSize[i],
    1359      152776 :                              arrayStartIdx[i] + count[i] * arrayStep[i]);
    1360       76388 :                 countInnerLoopInit[i] = static_cast<size_t>(cpl::div_round_up(
    1361       76388 :                     nextBlockIdx - indicesOuterLoop[i], arrayStep[i]));
    1362             :             }
    1363             :         }
    1364             : 
    1365       36336 :         if (bEmptyChunk && bBothAreNumericDT && abyTargetNoData.empty())
    1366             :         {
    1367         855 :             abyTargetNoData.resize(nBufferDTSize);
    1368         855 :             if (m_pabyNoData)
    1369             :             {
    1370         181 :                 GDALExtendedDataType::CopyValue(
    1371         181 :                     m_pabyNoData, m_oType, &abyTargetNoData[0], bufferDataType);
    1372         181 :                 bNoDataIsZero = true;
    1373        1519 :                 for (size_t i = 0; i < abyTargetNoData.size(); ++i)
    1374             :                 {
    1375        1338 :                     if (abyTargetNoData[i] != 0)
    1376         389 :                         bNoDataIsZero = false;
    1377             :                 }
    1378             :             }
    1379             :             else
    1380             :             {
    1381         674 :                 bNoDataIsZero = true;
    1382         674 :                 GByte zero = 0;
    1383         674 :                 GDALCopyWords(&zero, GDT_UInt8, 0, &abyTargetNoData[0],
    1384             :                               bufferDataType.GetNumericDataType(), 0, 1);
    1385             :             }
    1386             :         }
    1387             : 
    1388       35481 :     lbl_next_depth_inner_loop:
    1389      862941 :         if (nDims == 0 || dimIdxSubLoop == nDims - 1)
    1390             :         {
    1391      814525 :             indicesInnerLoop[dimIdxSubLoop] = indicesOuterLoop[dimIdxSubLoop];
    1392      814525 :             void *dst_ptr = dstPtrStackInnerLoop[dimIdxSubLoop];
    1393             : 
    1394      810916 :             if (m_bUseOptimizedCodePaths && bEmptyChunk && bBothAreNumericDT &&
    1395     1625440 :                 bNoDataIsZero &&
    1396        1838 :                 nBufferDTSize == dstBufferStrideBytes[dimIdxSubLoop])
    1397             :             {
    1398        1728 :                 memset(dst_ptr, 0,
    1399        1728 :                        nBufferDTSize * countInnerLoopInit[dimIdxSubLoop]);
    1400        1728 :                 goto end_inner_loop;
    1401             :             }
    1402      809188 :             else if (m_bUseOptimizedCodePaths && bEmptyChunk &&
    1403     1622750 :                      !abyTargetNoData.empty() && bBothAreNumericDT &&
    1404         769 :                      dstBufferStrideBytes[dimIdxSubLoop] <
    1405         769 :                          std::numeric_limits<int>::max())
    1406             :             {
    1407        1538 :                 GDALCopyWords64(
    1408         769 :                     abyTargetNoData.data(), bufferDataType.GetNumericDataType(),
    1409             :                     0, dst_ptr, bufferDataType.GetNumericDataType(),
    1410         769 :                     static_cast<int>(dstBufferStrideBytes[dimIdxSubLoop]),
    1411         769 :                     static_cast<GPtrDiff_t>(countInnerLoopInit[dimIdxSubLoop]));
    1412         769 :                 goto end_inner_loop;
    1413             :             }
    1414      812028 :             else if (bEmptyChunk)
    1415             :             {
    1416        6085 :                 for (size_t i = 0; i < countInnerLoopInit[dimIdxSubLoop];
    1417        3996 :                      ++i, dst_ptr = static_cast<uint8_t *>(dst_ptr) +
    1418        3996 :                                     dstBufferStrideBytes[dimIdxSubLoop])
    1419             :                 {
    1420        3996 :                     if (bNoDataIsZero)
    1421             :                     {
    1422        3200 :                         if (nBufferDTSize == 1)
    1423             :                         {
    1424          36 :                             *static_cast<uint8_t *>(dst_ptr) = 0;
    1425             :                         }
    1426        3164 :                         else if (nBufferDTSize == 2)
    1427             :                         {
    1428          48 :                             *static_cast<uint16_t *>(dst_ptr) = 0;
    1429             :                         }
    1430        3116 :                         else if (nBufferDTSize == 4)
    1431             :                         {
    1432          72 :                             *static_cast<uint32_t *>(dst_ptr) = 0;
    1433             :                         }
    1434        3044 :                         else if (nBufferDTSize == 8)
    1435             :                         {
    1436        2588 :                             *static_cast<uint64_t *>(dst_ptr) = 0;
    1437             :                         }
    1438         456 :                         else if (nBufferDTSize == 16)
    1439             :                         {
    1440         456 :                             static_cast<uint64_t *>(dst_ptr)[0] = 0;
    1441         456 :                             static_cast<uint64_t *>(dst_ptr)[1] = 0;
    1442             :                         }
    1443             :                         else
    1444             :                         {
    1445           0 :                             CPLAssert(false);
    1446             :                         }
    1447             :                     }
    1448         796 :                     else if (m_pabyNoData)
    1449             :                     {
    1450         795 :                         if (bBothAreNumericDT)
    1451             :                         {
    1452         786 :                             const void *src_ptr_v = abyTargetNoData.data();
    1453         786 :                             if (nBufferDTSize == 1)
    1454          24 :                                 *static_cast<uint8_t *>(dst_ptr) =
    1455          24 :                                     *static_cast<const uint8_t *>(src_ptr_v);
    1456         762 :                             else if (nBufferDTSize == 2)
    1457           0 :                                 *static_cast<uint16_t *>(dst_ptr) =
    1458           0 :                                     *static_cast<const uint16_t *>(src_ptr_v);
    1459         762 :                             else if (nBufferDTSize == 4)
    1460          60 :                                 *static_cast<uint32_t *>(dst_ptr) =
    1461          60 :                                     *static_cast<const uint32_t *>(src_ptr_v);
    1462         702 :                             else if (nBufferDTSize == 8)
    1463         702 :                                 *static_cast<uint64_t *>(dst_ptr) =
    1464         702 :                                     *static_cast<const uint64_t *>(src_ptr_v);
    1465           0 :                             else if (nBufferDTSize == 16)
    1466             :                             {
    1467           0 :                                 static_cast<uint64_t *>(dst_ptr)[0] =
    1468           0 :                                     static_cast<const uint64_t *>(src_ptr_v)[0];
    1469           0 :                                 static_cast<uint64_t *>(dst_ptr)[1] =
    1470           0 :                                     static_cast<const uint64_t *>(src_ptr_v)[1];
    1471             :                             }
    1472             :                             else
    1473             :                             {
    1474           0 :                                 CPLAssert(false);
    1475             :                             }
    1476             :                         }
    1477             :                         else
    1478             :                         {
    1479           9 :                             GDALExtendedDataType::CopyValue(
    1480           9 :                                 m_pabyNoData, m_oType, dst_ptr, bufferDataType);
    1481             :                         }
    1482             :                     }
    1483             :                     else
    1484             :                     {
    1485           1 :                         memset(dst_ptr, 0, nBufferDTSize);
    1486             :                     }
    1487             :                 }
    1488             : 
    1489        2089 :                 goto end_inner_loop;
    1490             :             }
    1491             : 
    1492      809939 :             size_t nOffset = 0;
    1493     2781820 :             for (size_t i = 0; i < nDims; i++)
    1494             :             {
    1495     1971880 :                 nOffset = static_cast<size_t>(
    1496     1971880 :                     nOffset * m_anInnerBlockSize[i] +
    1497     1971880 :                     (indicesInnerLoop[i] -
    1498     1971880 :                      blockIndices[i] * m_anInnerBlockSize[i]));
    1499             :             }
    1500      809939 :             const GByte *src_ptr = pabySrcBlock + nOffset * nSrcDTSize;
    1501      809939 :             const auto step = nDims == 0 ? 0 : arrayStep[dimIdxSubLoop];
    1502             : 
    1503      808409 :             if (m_bUseOptimizedCodePaths && bBothAreNumericDT &&
    1504      808374 :                 step <= static_cast<GIntBig>(std::numeric_limits<int>::max() /
    1505     1618350 :                                              nDTSize) &&
    1506      808374 :                 dstBufferStrideBytes[dimIdxSubLoop] <=
    1507      808374 :                     std::numeric_limits<int>::max())
    1508             :             {
    1509      808374 :                 GDALCopyWords64(
    1510             :                     src_ptr, m_oType.GetNumericDataType(),
    1511             :                     static_cast<int>(step * nDTSize), dst_ptr,
    1512             :                     bufferDataType.GetNumericDataType(),
    1513      808374 :                     static_cast<int>(dstBufferStrideBytes[dimIdxSubLoop]),
    1514      808374 :                     static_cast<GPtrDiff_t>(countInnerLoopInit[dimIdxSubLoop]));
    1515             : 
    1516      808374 :                 goto end_inner_loop;
    1517             :             }
    1518             : 
    1519        4584 :             for (size_t i = 0; i < countInnerLoopInit[dimIdxSubLoop];
    1520        3019 :                  ++i, src_ptr += step * nSrcDTSize,
    1521        3019 :                         dst_ptr = static_cast<uint8_t *>(dst_ptr) +
    1522        3019 :                                   dstBufferStrideBytes[dimIdxSubLoop])
    1523             :             {
    1524        3019 :                 if (bSameNumericDT)
    1525             :                 {
    1526         996 :                     const void *src_ptr_v = src_ptr;
    1527         996 :                     if (nSameDTSize == 1)
    1528          70 :                         *static_cast<uint8_t *>(dst_ptr) =
    1529          70 :                             *static_cast<const uint8_t *>(src_ptr_v);
    1530         926 :                     else if (nSameDTSize == 2)
    1531             :                     {
    1532          56 :                         *static_cast<uint16_t *>(dst_ptr) =
    1533          56 :                             *static_cast<const uint16_t *>(src_ptr_v);
    1534             :                     }
    1535         870 :                     else if (nSameDTSize == 4)
    1536             :                     {
    1537         154 :                         *static_cast<uint32_t *>(dst_ptr) =
    1538         154 :                             *static_cast<const uint32_t *>(src_ptr_v);
    1539             :                     }
    1540         716 :                     else if (nSameDTSize == 8)
    1541             :                     {
    1542         652 :                         *static_cast<uint64_t *>(dst_ptr) =
    1543         652 :                             *static_cast<const uint64_t *>(src_ptr_v);
    1544             :                     }
    1545          64 :                     else if (nSameDTSize == 16)
    1546             :                     {
    1547          64 :                         static_cast<uint64_t *>(dst_ptr)[0] =
    1548          64 :                             static_cast<const uint64_t *>(src_ptr_v)[0];
    1549          64 :                         static_cast<uint64_t *>(dst_ptr)[1] =
    1550          64 :                             static_cast<const uint64_t *>(src_ptr_v)[1];
    1551             :                     }
    1552             :                     else
    1553             :                     {
    1554           0 :                         CPLAssert(false);
    1555             :                     }
    1556             :                 }
    1557        2023 :                 else if (bSameCompoundAndNoDynamicMem)
    1558             :                 {
    1559           4 :                     memcpy(dst_ptr, src_ptr, nDTSize);
    1560             :                 }
    1561        2019 :                 else if (m_oType.GetClass() == GEDTC_STRING)
    1562             :                 {
    1563          52 :                     if (m_aoDtypeElts.back().nativeType ==
    1564             :                         DtypeElt::NativeType::STRING_UNICODE)
    1565             :                     {
    1566             :                         char *pDstStr =
    1567          18 :                             UCS4ToUTF8(src_ptr, nSourceSize,
    1568           9 :                                        m_aoDtypeElts.back().needByteSwapping);
    1569           9 :                         char **pDstPtr = static_cast<char **>(dst_ptr);
    1570           9 :                         memcpy(pDstPtr, &pDstStr, sizeof(pDstStr));
    1571             :                     }
    1572             :                     else
    1573             :                     {
    1574             :                         char *pDstStr =
    1575          43 :                             static_cast<char *>(CPLMalloc(nSourceSize + 1));
    1576          43 :                         memcpy(pDstStr, src_ptr, nSourceSize);
    1577          43 :                         pDstStr[nSourceSize] = 0;
    1578          43 :                         char **pDstPtr = static_cast<char **>(dst_ptr);
    1579          43 :                         memcpy(pDstPtr, &pDstStr, sizeof(char *));
    1580             :                     }
    1581             :                 }
    1582             :                 else
    1583             :                 {
    1584        1967 :                     GDALExtendedDataType::CopyValue(src_ptr, m_oType, dst_ptr,
    1585             :                                                     bufferDataType);
    1586             :                 }
    1587        1565 :             }
    1588             :         }
    1589             :         else
    1590             :         {
    1591             :             // This level of loop loops over individual samples, within a
    1592             :             // block
    1593       48416 :             indicesInnerLoop[dimIdxSubLoop] = indicesOuterLoop[dimIdxSubLoop];
    1594       48416 :             countInnerLoop[dimIdxSubLoop] = countInnerLoopInit[dimIdxSubLoop];
    1595             :             while (true)
    1596             :             {
    1597      826605 :                 dimIdxSubLoop++;
    1598      826605 :                 dstPtrStackInnerLoop[dimIdxSubLoop] =
    1599      826605 :                     dstPtrStackInnerLoop[dimIdxSubLoop - 1];
    1600      826605 :                 goto lbl_next_depth_inner_loop;
    1601      826605 :             lbl_return_to_caller_inner_loop:
    1602      826605 :                 dimIdxSubLoop--;
    1603      826605 :                 --countInnerLoop[dimIdxSubLoop];
    1604      826605 :                 if (countInnerLoop[dimIdxSubLoop] == 0)
    1605             :                 {
    1606       48416 :                     break;
    1607             :                 }
    1608      778189 :                 indicesInnerLoop[dimIdxSubLoop] += arrayStep[dimIdxSubLoop];
    1609      778189 :                 dstPtrStackInnerLoop[dimIdxSubLoop] +=
    1610      778189 :                     dstBufferStrideBytes[dimIdxSubLoop];
    1611             :             }
    1612             :         }
    1613      862941 :     end_inner_loop:
    1614      862941 :         if (dimIdxSubLoop > 0)
    1615      826605 :             goto lbl_return_to_caller_inner_loop;
    1616             :     }
    1617             :     else
    1618             :     {
    1619             :         // This level of loop loops over blocks
    1620        8293 :         indicesOuterLoop[dimIdx] = arrayStartIdx[dimIdx];
    1621        8293 :         blockIndices[dimIdx] =
    1622        8293 :             indicesOuterLoop[dimIdx] / m_anInnerBlockSize[dimIdx];
    1623             :         while (true)
    1624             :         {
    1625       42379 :             dimIdx++;
    1626       42379 :             dstPtrStackOuterLoop[dimIdx] = dstPtrStackOuterLoop[dimIdx - 1];
    1627       42379 :             goto lbl_next_depth;
    1628       41393 :         lbl_return_to_caller:
    1629       41393 :             dimIdx--;
    1630       41393 :             if (count[dimIdx] == 1 || arrayStep[dimIdx] == 0)
    1631             :                 break;
    1632             : 
    1633             :             size_t nIncr;
    1634       40278 :             if (static_cast<GUInt64>(arrayStep[dimIdx]) <
    1635       40278 :                 m_anInnerBlockSize[dimIdx])
    1636             :             {
    1637             :                 // Compute index at next block boundary
    1638             :                 auto newIdx =
    1639       37041 :                     indicesOuterLoop[dimIdx] +
    1640       37041 :                     (m_anInnerBlockSize[dimIdx] -
    1641       37041 :                      (indicesOuterLoop[dimIdx] % m_anInnerBlockSize[dimIdx]));
    1642             :                 // And round up compared to arrayStartIdx, arrayStep
    1643       37041 :                 nIncr = static_cast<size_t>(cpl::div_round_up(
    1644       37041 :                     newIdx - indicesOuterLoop[dimIdx], arrayStep[dimIdx]));
    1645             :             }
    1646             :             else
    1647             :             {
    1648        3237 :                 nIncr = 1;
    1649             :             }
    1650       40278 :             indicesOuterLoop[dimIdx] += nIncr * arrayStep[dimIdx];
    1651       40278 :             if (indicesOuterLoop[dimIdx] >
    1652       40278 :                 arrayStartIdx[dimIdx] + (count[dimIdx] - 1) * arrayStep[dimIdx])
    1653        6192 :                 break;
    1654       34086 :             dstPtrStackOuterLoop[dimIdx] +=
    1655       34086 :                 bufferStride[dimIdx] *
    1656       34086 :                 static_cast<GPtrDiff_t>(nIncr * nBufferDTSize);
    1657       68172 :             blockIndices[dimIdx] =
    1658       34086 :                 indicesOuterLoop[dimIdx] / m_anInnerBlockSize[dimIdx];
    1659       34086 :         }
    1660             :     }
    1661       43643 :     if (dimIdx > 0)
    1662       41393 :         goto lbl_return_to_caller;
    1663             : 
    1664        2250 :     return true;
    1665             : }
    1666             : 
    1667             : /************************************************************************/
    1668             : /*                         ZarrArray::IWrite()                          */
    1669             : /************************************************************************/
    1670             : 
    1671         754 : bool ZarrArray::IWrite(const GUInt64 *arrayStartIdx, const size_t *count,
    1672             :                        const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
    1673             :                        const GDALExtendedDataType &bufferDataType,
    1674             :                        const void *pSrcBuffer)
    1675             : {
    1676         754 :     if (!CheckValidAndErrorOutIfNot())
    1677           0 :         return false;
    1678             : 
    1679         754 :     if (!AllocateWorkingBuffers())
    1680           0 :         return false;
    1681             : 
    1682         754 :     m_oChunkCache.clear();
    1683             : 
    1684             :     // Need to be kept in top-level scope
    1685        1508 :     std::vector<GUInt64> arrayStartIdxMod;
    1686        1508 :     std::vector<GInt64> arrayStepMod;
    1687        1508 :     std::vector<GPtrDiff_t> bufferStrideMod;
    1688             : 
    1689         754 :     const size_t nDims = m_aoDims.size();
    1690         754 :     bool negativeStep = false;
    1691         754 :     bool bWriteWholeBlockInit = true;
    1692        2143 :     for (size_t i = 0; i < nDims; ++i)
    1693             :     {
    1694        1389 :         if (arrayStep[i] < 0)
    1695             :         {
    1696         264 :             negativeStep = true;
    1697         264 :             if (arrayStep[i] != -1 && count[i] > 1)
    1698           0 :                 bWriteWholeBlockInit = false;
    1699             :         }
    1700        1125 :         else if (arrayStep[i] != 1 && count[i] > 1)
    1701           0 :             bWriteWholeBlockInit = false;
    1702             :     }
    1703             : 
    1704         754 :     const auto nBufferDTSize = static_cast<int>(bufferDataType.GetSize());
    1705             : 
    1706             :     // Make sure that arrayStep[i] are positive for sake of simplicity
    1707         754 :     if (negativeStep)
    1708             :     {
    1709             : #if defined(__GNUC__)
    1710             : #pragma GCC diagnostic push
    1711             : #pragma GCC diagnostic ignored "-Wnull-dereference"
    1712             : #endif
    1713         132 :         arrayStartIdxMod.resize(nDims);
    1714         132 :         arrayStepMod.resize(nDims);
    1715         132 :         bufferStrideMod.resize(nDims);
    1716             : #if defined(__GNUC__)
    1717             : #pragma GCC diagnostic pop
    1718             : #endif
    1719         396 :         for (size_t i = 0; i < nDims; ++i)
    1720             :         {
    1721         264 :             if (arrayStep[i] < 0)
    1722             :             {
    1723         528 :                 arrayStartIdxMod[i] =
    1724         264 :                     arrayStartIdx[i] - (count[i] - 1) * (-arrayStep[i]);
    1725         264 :                 arrayStepMod[i] = -arrayStep[i];
    1726         264 :                 bufferStrideMod[i] = -bufferStride[i];
    1727         264 :                 pSrcBuffer =
    1728             :                     static_cast<const GByte *>(pSrcBuffer) +
    1729         264 :                     bufferStride[i] *
    1730         264 :                         static_cast<GPtrDiff_t>(nBufferDTSize * (count[i] - 1));
    1731             :             }
    1732             :             else
    1733             :             {
    1734           0 :                 arrayStartIdxMod[i] = arrayStartIdx[i];
    1735           0 :                 arrayStepMod[i] = arrayStep[i];
    1736           0 :                 bufferStrideMod[i] = bufferStride[i];
    1737             :             }
    1738             :         }
    1739         132 :         arrayStartIdx = arrayStartIdxMod.data();
    1740         132 :         arrayStep = arrayStepMod.data();
    1741         132 :         bufferStride = bufferStrideMod.data();
    1742             :     }
    1743             : 
    1744        1508 :     std::vector<uint64_t> indicesOuterLoop(nDims + 1);
    1745        1508 :     std::vector<const GByte *> srcPtrStackOuterLoop(nDims + 1);
    1746             : 
    1747        1508 :     std::vector<size_t> offsetDstBuffer(nDims + 1);
    1748        1508 :     std::vector<const GByte *> srcPtrStackInnerLoop(nDims + 1);
    1749             : 
    1750        1508 :     std::vector<GPtrDiff_t> srcBufferStrideBytes;
    1751        2143 :     for (size_t i = 0; i < nDims; ++i)
    1752             :     {
    1753        1389 :         srcBufferStrideBytes.push_back(bufferStride[i] *
    1754        1389 :                                        static_cast<GPtrDiff_t>(nBufferDTSize));
    1755             :     }
    1756         754 :     srcBufferStrideBytes.push_back(0);
    1757             : 
    1758         754 :     const auto nDTSize = m_oType.GetSize();
    1759             : 
    1760        1508 :     std::vector<uint64_t> blockIndices(nDims);
    1761             :     const size_t nNativeSize =
    1762         754 :         m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
    1763             : 
    1764        1508 :     std::vector<size_t> countInnerLoopInit(nDims + 1, 1);
    1765        1508 :     std::vector<size_t> countInnerLoop(nDims);
    1766             : 
    1767        1500 :     const bool bBothAreNumericDT = m_oType.GetClass() == GEDTC_NUMERIC &&
    1768         746 :                                    bufferDataType.GetClass() == GEDTC_NUMERIC;
    1769             :     const bool bSameNumericDT =
    1770        1500 :         bBothAreNumericDT &&
    1771         746 :         m_oType.GetNumericDataType() == bufferDataType.GetNumericDataType();
    1772         754 :     const auto nSameDTSize = bSameNumericDT ? m_oType.GetSize() : 0;
    1773             :     const bool bSameCompoundAndNoDynamicMem =
    1774         754 :         m_oType.GetClass() == GEDTC_COMPOUND && m_oType == bufferDataType &&
    1775           0 :         !m_oType.NeedsFreeDynamicMemory();
    1776             : 
    1777         754 :     size_t dimIdx = 0;
    1778         754 :     size_t dimIdxForCopy = nDims == 0 ? 0 : nDims - 1;
    1779         754 :     if (nDims)
    1780             :     {
    1781         822 :         while (dimIdxForCopy > 0 && count[dimIdxForCopy] == 1)
    1782          68 :             --dimIdxForCopy;
    1783             :     }
    1784             : 
    1785         754 :     srcPtrStackOuterLoop[0] = static_cast<const GByte *>(pSrcBuffer);
    1786       26403 : lbl_next_depth:
    1787       26403 :     if (dimIdx == nDims)
    1788             :     {
    1789       23896 :         bool bWriteWholeBlock = bWriteWholeBlockInit;
    1790       23896 :         bool bPartialBlock = false;
    1791       71765 :         for (size_t i = 0; i < nDims; ++i)
    1792             :         {
    1793       47869 :             countInnerLoopInit[i] = 1;
    1794       47869 :             if (arrayStep[i] != 0)
    1795             :             {
    1796             :                 const auto nextBlockIdx =
    1797       47832 :                     std::min((1 + indicesOuterLoop[i] / m_anInnerBlockSize[i]) *
    1798       47832 :                                  m_anInnerBlockSize[i],
    1799       95664 :                              arrayStartIdx[i] + count[i] * arrayStep[i]);
    1800       47832 :                 countInnerLoopInit[i] = static_cast<size_t>(cpl::div_round_up(
    1801       47832 :                     nextBlockIdx - indicesOuterLoop[i], arrayStep[i]));
    1802             :             }
    1803       47869 :             if (bWriteWholeBlock)
    1804             :             {
    1805             :                 const bool bWholePartialBlockThisDim =
    1806       49221 :                     indicesOuterLoop[i] == 0 &&
    1807        2518 :                     countInnerLoopInit[i] == m_aoDims[i]->GetSize();
    1808       46703 :                 bWriteWholeBlock =
    1809       46703 :                     (countInnerLoopInit[i] == m_anInnerBlockSize[i] ||
    1810             :                      bWholePartialBlockThisDim);
    1811       46703 :                 if (bWholePartialBlockThisDim)
    1812             :                 {
    1813         645 :                     bPartialBlock = true;
    1814             :                 }
    1815             :             }
    1816             :         }
    1817             : 
    1818       23896 :         size_t dimIdxSubLoop = 0;
    1819       23896 :         srcPtrStackInnerLoop[0] = srcPtrStackOuterLoop[nDims];
    1820             :         const size_t nCacheDTSize =
    1821       23896 :             m_abyDecodedBlockData.empty() ? nNativeSize : nDTSize;
    1822       23896 :         auto &abyBlock = m_abyDecodedBlockData.empty() ? m_abyRawBlockData
    1823       23896 :                                                        : m_abyDecodedBlockData;
    1824             : 
    1825       23896 :         if (!blockIndices.empty() && blockIndices == m_anCachedBlockIndices)
    1826             :         {
    1827           5 :             if (!m_bCachedBlockValid)
    1828           0 :                 return false;
    1829             :         }
    1830             :         else
    1831             :         {
    1832       23891 :             if (!FlushDirtyBlock())
    1833           0 :                 return false;
    1834             : 
    1835       23891 :             m_anCachedBlockIndices = blockIndices;
    1836       23891 :             m_bCachedBlockValid = true;
    1837             : 
    1838       23891 :             if (bWriteWholeBlock)
    1839             :             {
    1840       21917 :                 if (bPartialBlock)
    1841             :                 {
    1842         419 :                     DeallocateDecodedBlockData();
    1843         419 :                     memset(&abyBlock[0], 0, abyBlock.size());
    1844             :                 }
    1845             :             }
    1846             :             else
    1847             :             {
    1848             :                 // If we don't write the whole tile, we need to fetch a
    1849             :                 // potentially existing one.
    1850        1974 :                 bool bEmptyBlock = false;
    1851        1974 :                 m_bCachedBlockValid =
    1852        1974 :                     LoadBlockData(blockIndices.data(), bEmptyBlock);
    1853        1974 :                 if (!m_bCachedBlockValid)
    1854             :                 {
    1855           0 :                     return false;
    1856             :                 }
    1857             : 
    1858        1974 :                 if (bEmptyBlock)
    1859             :                 {
    1860        1696 :                     DeallocateDecodedBlockData();
    1861             : 
    1862        1696 :                     if (m_pabyNoData == nullptr)
    1863             :                     {
    1864         844 :                         memset(&abyBlock[0], 0, abyBlock.size());
    1865             :                     }
    1866             :                     else
    1867             :                     {
    1868         852 :                         const size_t nElts = abyBlock.size() / nCacheDTSize;
    1869         852 :                         GByte *dstPtr = &abyBlock[0];
    1870         852 :                         if (m_oType.GetClass() == GEDTC_NUMERIC)
    1871             :                         {
    1872         852 :                             GDALCopyWords64(
    1873         852 :                                 m_pabyNoData, m_oType.GetNumericDataType(), 0,
    1874             :                                 dstPtr, m_oType.GetNumericDataType(),
    1875         852 :                                 static_cast<int>(m_oType.GetSize()),
    1876             :                                 static_cast<GPtrDiff_t>(nElts));
    1877             :                         }
    1878             :                         else
    1879             :                         {
    1880           0 :                             for (size_t i = 0; i < nElts; ++i)
    1881             :                             {
    1882           0 :                                 GDALExtendedDataType::CopyValue(
    1883           0 :                                     m_pabyNoData, m_oType, dstPtr, m_oType);
    1884           0 :                                 dstPtr += nCacheDTSize;
    1885             :                             }
    1886             :                         }
    1887             :                     }
    1888             :                 }
    1889             :             }
    1890             :         }
    1891       23896 :         m_bDirtyBlock = true;
    1892       23896 :         m_bCachedBlockEmpty = false;
    1893       23896 :         if (nDims)
    1894       23896 :             offsetDstBuffer[0] = static_cast<size_t>(
    1895       23896 :                 indicesOuterLoop[0] - blockIndices[0] * m_anInnerBlockSize[0]);
    1896             : 
    1897       23896 :         GByte *pabyBlock = &abyBlock[0];
    1898             : 
    1899      463488 :     lbl_next_depth_inner_loop:
    1900      463488 :         if (dimIdxSubLoop == dimIdxForCopy)
    1901             :         {
    1902      439529 :             size_t nOffset = offsetDstBuffer[dimIdxSubLoop];
    1903      439529 :             GInt64 step = nDims == 0 ? 0 : arrayStep[dimIdxSubLoop];
    1904      439607 :             for (size_t i = dimIdxSubLoop + 1; i < nDims; ++i)
    1905             :             {
    1906          78 :                 nOffset = static_cast<size_t>(
    1907          78 :                     nOffset * m_anInnerBlockSize[i] +
    1908          78 :                     (indicesOuterLoop[i] -
    1909          78 :                      blockIndices[i] * m_anInnerBlockSize[i]));
    1910          78 :                 step *= m_anInnerBlockSize[i];
    1911             :             }
    1912      439529 :             const void *src_ptr = srcPtrStackInnerLoop[dimIdxSubLoop];
    1913      439529 :             GByte *dst_ptr = pabyBlock + nOffset * nCacheDTSize;
    1914             : 
    1915      439529 :             if (m_bUseOptimizedCodePaths && bBothAreNumericDT)
    1916             :             {
    1917      438069 :                 if (countInnerLoopInit[dimIdxSubLoop] == 1 && bSameNumericDT)
    1918             :                 {
    1919         159 :                     void *dst_ptr_v = dst_ptr;
    1920         159 :                     if (nSameDTSize == 1)
    1921          11 :                         *static_cast<uint8_t *>(dst_ptr_v) =
    1922          11 :                             *static_cast<const uint8_t *>(src_ptr);
    1923         148 :                     else if (nSameDTSize == 2)
    1924             :                     {
    1925           2 :                         *static_cast<uint16_t *>(dst_ptr_v) =
    1926           2 :                             *static_cast<const uint16_t *>(src_ptr);
    1927             :                     }
    1928         146 :                     else if (nSameDTSize == 4)
    1929             :                     {
    1930           2 :                         *static_cast<uint32_t *>(dst_ptr_v) =
    1931           2 :                             *static_cast<const uint32_t *>(src_ptr);
    1932             :                     }
    1933         144 :                     else if (nSameDTSize == 8)
    1934             :                     {
    1935         102 :                         *static_cast<uint64_t *>(dst_ptr_v) =
    1936         102 :                             *static_cast<const uint64_t *>(src_ptr);
    1937             :                     }
    1938          42 :                     else if (nSameDTSize == 16)
    1939             :                     {
    1940          42 :                         static_cast<uint64_t *>(dst_ptr_v)[0] =
    1941          42 :                             static_cast<const uint64_t *>(src_ptr)[0];
    1942          42 :                         static_cast<uint64_t *>(dst_ptr_v)[1] =
    1943          42 :                             static_cast<const uint64_t *>(src_ptr)[1];
    1944             :                     }
    1945             :                     else
    1946             :                     {
    1947           0 :                         CPLAssert(false);
    1948             :                     }
    1949             :                 }
    1950      437910 :                 else if (step <=
    1951      437910 :                              static_cast<GIntBig>(
    1952      875820 :                                  std::numeric_limits<int>::max() / nDTSize) &&
    1953      437910 :                          srcBufferStrideBytes[dimIdxSubLoop] <=
    1954      437910 :                              std::numeric_limits<int>::max())
    1955             :                 {
    1956      875820 :                     GDALCopyWords64(
    1957             :                         src_ptr, bufferDataType.GetNumericDataType(),
    1958      437910 :                         static_cast<int>(srcBufferStrideBytes[dimIdxSubLoop]),
    1959             :                         dst_ptr, m_oType.GetNumericDataType(),
    1960             :                         static_cast<int>(step * nDTSize),
    1961             :                         static_cast<GPtrDiff_t>(
    1962      437910 :                             countInnerLoopInit[dimIdxSubLoop]));
    1963             :                 }
    1964      438069 :                 goto end_inner_loop;
    1965             :             }
    1966             : 
    1967        4381 :             for (size_t i = 0; i < countInnerLoopInit[dimIdxSubLoop];
    1968        2921 :                  ++i, dst_ptr += step * nCacheDTSize,
    1969        2921 :                         src_ptr = static_cast<const uint8_t *>(src_ptr) +
    1970        2921 :                                   srcBufferStrideBytes[dimIdxSubLoop])
    1971             :             {
    1972        2921 :                 if (bSameNumericDT)
    1973             :                 {
    1974         600 :                     void *dst_ptr_v = dst_ptr;
    1975         600 :                     if (nSameDTSize == 1)
    1976           0 :                         *static_cast<uint8_t *>(dst_ptr_v) =
    1977           0 :                             *static_cast<const uint8_t *>(src_ptr);
    1978         600 :                     else if (nSameDTSize == 2)
    1979             :                     {
    1980           0 :                         *static_cast<uint16_t *>(dst_ptr_v) =
    1981           0 :                             *static_cast<const uint16_t *>(src_ptr);
    1982             :                     }
    1983         600 :                     else if (nSameDTSize == 4)
    1984             :                     {
    1985           0 :                         *static_cast<uint32_t *>(dst_ptr_v) =
    1986           0 :                             *static_cast<const uint32_t *>(src_ptr);
    1987             :                     }
    1988         600 :                     else if (nSameDTSize == 8)
    1989             :                     {
    1990         440 :                         *static_cast<uint64_t *>(dst_ptr_v) =
    1991         440 :                             *static_cast<const uint64_t *>(src_ptr);
    1992             :                     }
    1993         160 :                     else if (nSameDTSize == 16)
    1994             :                     {
    1995         160 :                         static_cast<uint64_t *>(dst_ptr_v)[0] =
    1996         160 :                             static_cast<const uint64_t *>(src_ptr)[0];
    1997         160 :                         static_cast<uint64_t *>(dst_ptr_v)[1] =
    1998         160 :                             static_cast<const uint64_t *>(src_ptr)[1];
    1999             :                     }
    2000             :                     else
    2001             :                     {
    2002           0 :                         CPLAssert(false);
    2003             :                     }
    2004             :                 }
    2005        2321 :                 else if (bSameCompoundAndNoDynamicMem)
    2006             :                 {
    2007           0 :                     memcpy(dst_ptr, src_ptr, nDTSize);
    2008             :                 }
    2009        2321 :                 else if (m_oType.GetClass() == GEDTC_STRING)
    2010             :                 {
    2011          17 :                     const char *pSrcStr =
    2012             :                         *static_cast<const char *const *>(src_ptr);
    2013          17 :                     if (pSrcStr)
    2014             :                     {
    2015          17 :                         const size_t nLen = strlen(pSrcStr);
    2016          17 :                         if (m_aoDtypeElts.back().nativeType ==
    2017             :                             DtypeElt::NativeType::STRING_UNICODE)
    2018             :                         {
    2019             :                             try
    2020             :                             {
    2021             :                                 const auto ucs4 = UTF8ToUCS4(
    2022             :                                     pSrcStr,
    2023           8 :                                     m_aoDtypeElts.back().needByteSwapping);
    2024           4 :                                 const auto ucs4Len = ucs4.size();
    2025           4 :                                 memcpy(dst_ptr, ucs4.data(),
    2026           4 :                                        std::min(ucs4Len, nNativeSize));
    2027           4 :                                 if (ucs4Len > nNativeSize)
    2028             :                                 {
    2029           1 :                                     CPLError(CE_Warning, CPLE_AppDefined,
    2030             :                                              "Too long string truncated");
    2031             :                                 }
    2032           3 :                                 else if (ucs4Len < nNativeSize)
    2033             :                                 {
    2034           1 :                                     memset(dst_ptr + ucs4Len, 0,
    2035           1 :                                            nNativeSize - ucs4Len);
    2036             :                                 }
    2037             :                             }
    2038           0 :                             catch (const std::exception &)
    2039             :                             {
    2040           0 :                                 memset(dst_ptr, 0, nNativeSize);
    2041             :                             }
    2042             :                         }
    2043             :                         else
    2044             :                         {
    2045          13 :                             memcpy(dst_ptr, pSrcStr,
    2046          13 :                                    std::min(nLen, nNativeSize));
    2047          13 :                             if (nLen > nNativeSize)
    2048             :                             {
    2049           2 :                                 CPLError(CE_Warning, CPLE_AppDefined,
    2050             :                                          "Too long string truncated");
    2051             :                             }
    2052          11 :                             else if (nLen < nNativeSize)
    2053          11 :                                 memset(dst_ptr + nLen, 0, nNativeSize - nLen);
    2054             :                         }
    2055             :                     }
    2056             :                     else
    2057             :                     {
    2058           0 :                         memset(dst_ptr, 0, nNativeSize);
    2059             :                     }
    2060             :                 }
    2061             :                 else
    2062             :                 {
    2063        2304 :                     if (m_oType.NeedsFreeDynamicMemory())
    2064           0 :                         m_oType.FreeDynamicMemory(dst_ptr);
    2065        2304 :                     GDALExtendedDataType::CopyValue(src_ptr, bufferDataType,
    2066        2304 :                                                     dst_ptr, m_oType);
    2067             :                 }
    2068             :             }
    2069             :         }
    2070             :         else
    2071             :         {
    2072             :             // This level of loop loops over individual samples, within a
    2073             :             // block
    2074       23959 :             countInnerLoop[dimIdxSubLoop] = countInnerLoopInit[dimIdxSubLoop];
    2075             :             while (true)
    2076             :             {
    2077      439592 :                 dimIdxSubLoop++;
    2078      439592 :                 srcPtrStackInnerLoop[dimIdxSubLoop] =
    2079      439592 :                     srcPtrStackInnerLoop[dimIdxSubLoop - 1];
    2080      879184 :                 offsetDstBuffer[dimIdxSubLoop] = static_cast<size_t>(
    2081     1318780 :                     offsetDstBuffer[dimIdxSubLoop - 1] *
    2082      439592 :                         m_anInnerBlockSize[dimIdxSubLoop] +
    2083      439592 :                     (indicesOuterLoop[dimIdxSubLoop] -
    2084      879184 :                      blockIndices[dimIdxSubLoop] *
    2085      439592 :                          m_anInnerBlockSize[dimIdxSubLoop]));
    2086      439592 :                 goto lbl_next_depth_inner_loop;
    2087      439592 :             lbl_return_to_caller_inner_loop:
    2088      439592 :                 dimIdxSubLoop--;
    2089      439592 :                 --countInnerLoop[dimIdxSubLoop];
    2090      439592 :                 if (countInnerLoop[dimIdxSubLoop] == 0)
    2091             :                 {
    2092       23959 :                     break;
    2093             :                 }
    2094      415633 :                 srcPtrStackInnerLoop[dimIdxSubLoop] +=
    2095      415633 :                     srcBufferStrideBytes[dimIdxSubLoop];
    2096      415633 :                 offsetDstBuffer[dimIdxSubLoop] +=
    2097      415633 :                     static_cast<size_t>(arrayStep[dimIdxSubLoop]);
    2098             :             }
    2099             :         }
    2100      439529 :     end_inner_loop:
    2101      463488 :         if (dimIdxSubLoop > 0)
    2102      439592 :             goto lbl_return_to_caller_inner_loop;
    2103             :     }
    2104             :     else
    2105             :     {
    2106             :         // This level of loop loops over blocks
    2107        2507 :         indicesOuterLoop[dimIdx] = arrayStartIdx[dimIdx];
    2108        2507 :         blockIndices[dimIdx] =
    2109        2507 :             indicesOuterLoop[dimIdx] / m_anInnerBlockSize[dimIdx];
    2110             :         while (true)
    2111             :         {
    2112       25649 :             dimIdx++;
    2113       25649 :             srcPtrStackOuterLoop[dimIdx] = srcPtrStackOuterLoop[dimIdx - 1];
    2114       25649 :             goto lbl_next_depth;
    2115       25649 :         lbl_return_to_caller:
    2116       25649 :             dimIdx--;
    2117       25649 :             if (count[dimIdx] == 1 || arrayStep[dimIdx] == 0)
    2118             :                 break;
    2119             : 
    2120             :             size_t nIncr;
    2121       25539 :             if (static_cast<GUInt64>(arrayStep[dimIdx]) <
    2122       25539 :                 m_anInnerBlockSize[dimIdx])
    2123             :             {
    2124             :                 // Compute index at next block boundary
    2125             :                 auto newIdx =
    2126       25435 :                     indicesOuterLoop[dimIdx] +
    2127       25435 :                     (m_anInnerBlockSize[dimIdx] -
    2128       25435 :                      (indicesOuterLoop[dimIdx] % m_anInnerBlockSize[dimIdx]));
    2129             :                 // And round up compared to arrayStartIdx, arrayStep
    2130       25435 :                 nIncr = static_cast<size_t>(cpl::div_round_up(
    2131       25435 :                     newIdx - indicesOuterLoop[dimIdx], arrayStep[dimIdx]));
    2132             :             }
    2133             :             else
    2134             :             {
    2135         104 :                 nIncr = 1;
    2136             :             }
    2137       25539 :             indicesOuterLoop[dimIdx] += nIncr * arrayStep[dimIdx];
    2138       25539 :             if (indicesOuterLoop[dimIdx] >
    2139       25539 :                 arrayStartIdx[dimIdx] + (count[dimIdx] - 1) * arrayStep[dimIdx])
    2140        2397 :                 break;
    2141       23142 :             srcPtrStackOuterLoop[dimIdx] +=
    2142       23142 :                 bufferStride[dimIdx] *
    2143       23142 :                 static_cast<GPtrDiff_t>(nIncr * nBufferDTSize);
    2144       46284 :             blockIndices[dimIdx] =
    2145       23142 :                 indicesOuterLoop[dimIdx] / m_anInnerBlockSize[dimIdx];
    2146       23142 :         }
    2147             :     }
    2148       26403 :     if (dimIdx > 0)
    2149       25649 :         goto lbl_return_to_caller;
    2150             : 
    2151         754 :     return true;
    2152             : }
    2153             : 
    2154             : /************************************************************************/
    2155             : /*                      ZarrArray::IsEmptyBlock()                       */
    2156             : /************************************************************************/
    2157             : 
    2158       27959 : bool ZarrArray::IsEmptyBlock(const ZarrByteVectorQuickResize &abyBlock) const
    2159             : {
    2160       49909 :     if (m_pabyNoData == nullptr || (m_oType.GetClass() == GEDTC_NUMERIC &&
    2161       21950 :                                     GetNoDataValueAsDouble() == 0.0))
    2162             :     {
    2163       27340 :         const size_t nBytes = abyBlock.size();
    2164       27340 :         size_t i = 0;
    2165       32655 :         for (; i + (sizeof(size_t) - 1) < nBytes; i += sizeof(size_t))
    2166             :         {
    2167       31425 :             if (*reinterpret_cast<const size_t *>(abyBlock.data() + i) != 0)
    2168             :             {
    2169       26110 :                 return false;
    2170             :             }
    2171             :         }
    2172        3049 :         for (; i < nBytes; ++i)
    2173             :         {
    2174        1898 :             if (abyBlock[i] != 0)
    2175             :             {
    2176          79 :                 return false;
    2177             :             }
    2178             :         }
    2179        1151 :         return true;
    2180             :     }
    2181        1238 :     else if (m_oType.GetClass() == GEDTC_NUMERIC &&
    2182         619 :              !GDALDataTypeIsComplex(m_oType.GetNumericDataType()))
    2183             :     {
    2184         619 :         const int nDTSize = static_cast<int>(m_oType.GetSize());
    2185         619 :         const size_t nElts = abyBlock.size() / nDTSize;
    2186         619 :         const auto eDT = m_oType.GetNumericDataType();
    2187        1238 :         return GDALBufferHasOnlyNoData(
    2188         619 :             abyBlock.data(), GetNoDataValueAsDouble(),
    2189             :             nElts,        // nWidth
    2190             :             1,            // nHeight
    2191             :             nElts,        // nLineStride
    2192             :             1,            // nComponents
    2193             :             nDTSize * 8,  // nBitsPerSample
    2194         619 :             GDALDataTypeIsInteger(eDT)
    2195         224 :                 ? (GDALDataTypeIsSigned(eDT) ? GSF_SIGNED_INT
    2196             :                                              : GSF_UNSIGNED_INT)
    2197         619 :                 : GSF_FLOATING_POINT);
    2198             :     }
    2199           0 :     return false;
    2200             : }
    2201             : 
    2202             : /************************************************************************/
    2203             : /*                 ZarrArray::OpenBlockPresenceCache()                  */
    2204             : /************************************************************************/
    2205             : 
    2206             : std::shared_ptr<GDALMDArray>
    2207       34482 : ZarrArray::OpenBlockPresenceCache(bool bCanCreate) const
    2208             : {
    2209       34482 :     if (m_bHasTriedBlockCachePresenceArray)
    2210       33256 :         return m_poBlockCachePresenceArray;
    2211        1226 :     m_bHasTriedBlockCachePresenceArray = true;
    2212             : 
    2213        1226 :     if (m_nTotalInnerChunkCount == 1)
    2214         408 :         return nullptr;
    2215             : 
    2216        1636 :     std::string osCacheFilename;
    2217        1636 :     auto poRGCache = GetCacheRootGroup(bCanCreate, osCacheFilename);
    2218         818 :     if (!poRGCache)
    2219         797 :         return nullptr;
    2220             : 
    2221          21 :     const std::string osBlockPresenceArrayName(MassageName(GetFullName()) +
    2222          42 :                                                "_tile_presence");
    2223             :     auto poBlockPresenceArray =
    2224          42 :         poRGCache->OpenMDArray(osBlockPresenceArrayName);
    2225          42 :     const auto eByteDT = GDALExtendedDataType::Create(GDT_UInt8);
    2226          21 :     if (poBlockPresenceArray)
    2227             :     {
    2228          16 :         bool ok = true;
    2229          16 :         const auto &apoDimsCache = poBlockPresenceArray->GetDimensions();
    2230          32 :         if (poBlockPresenceArray->GetDataType() != eByteDT ||
    2231          16 :             apoDimsCache.size() != m_aoDims.size())
    2232             :         {
    2233           0 :             ok = false;
    2234             :         }
    2235             :         else
    2236             :         {
    2237          48 :             for (size_t i = 0; i < m_aoDims.size(); i++)
    2238             :             {
    2239          32 :                 const auto nExpectedDimSize = cpl::div_round_up(
    2240          32 :                     m_aoDims[i]->GetSize(), m_anInnerBlockSize[i]);
    2241          32 :                 if (apoDimsCache[i]->GetSize() != nExpectedDimSize)
    2242             :                 {
    2243           0 :                     ok = false;
    2244           0 :                     break;
    2245             :                 }
    2246             :             }
    2247             :         }
    2248          16 :         if (!ok)
    2249             :         {
    2250           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    2251             :                      "Array %s in %s has not expected characteristics",
    2252             :                      osBlockPresenceArrayName.c_str(), osCacheFilename.c_str());
    2253           0 :             return nullptr;
    2254             :         }
    2255             : 
    2256          16 :         if (!poBlockPresenceArray->GetAttribute("filling_status") &&
    2257           0 :             !bCanCreate)
    2258             :         {
    2259           0 :             CPLDebug(ZARR_DEBUG_KEY,
    2260             :                      "Cache tile presence array for %s found, but filling not "
    2261             :                      "finished",
    2262           0 :                      GetFullName().c_str());
    2263           0 :             return nullptr;
    2264             :         }
    2265             : 
    2266          16 :         CPLDebug(ZARR_DEBUG_KEY, "Using cache tile presence for %s",
    2267          16 :                  GetFullName().c_str());
    2268             :     }
    2269           5 :     else if (bCanCreate)
    2270             :     {
    2271           5 :         int idxDim = 0;
    2272           5 :         std::string osBlockSize;
    2273           5 :         std::vector<std::shared_ptr<GDALDimension>> apoNewDims;
    2274          15 :         for (const auto &poDim : m_aoDims)
    2275             :         {
    2276          10 :             auto poNewDim = poRGCache->CreateDimension(
    2277          20 :                 osBlockPresenceArrayName + '_' + std::to_string(idxDim),
    2278          20 :                 std::string(), std::string(),
    2279             :                 cpl::div_round_up(poDim->GetSize(),
    2280          20 :                                   m_anInnerBlockSize[idxDim]));
    2281          10 :             if (!poNewDim)
    2282           0 :                 return nullptr;
    2283          10 :             apoNewDims.emplace_back(poNewDim);
    2284             : 
    2285          10 :             if (!osBlockSize.empty())
    2286           5 :                 osBlockSize += ',';
    2287          10 :             constexpr GUInt64 BLOCKSIZE = 256;
    2288             :             osBlockSize +=
    2289          10 :                 std::to_string(std::min(poNewDim->GetSize(), BLOCKSIZE));
    2290             : 
    2291          10 :             idxDim++;
    2292             :         }
    2293             : 
    2294           5 :         CPLStringList aosOptionsBlockPresence;
    2295           5 :         aosOptionsBlockPresence.SetNameValue("BLOCKSIZE", osBlockSize.c_str());
    2296             :         poBlockPresenceArray =
    2297          15 :             poRGCache->CreateMDArray(osBlockPresenceArrayName, apoNewDims,
    2298          10 :                                      eByteDT, aosOptionsBlockPresence.List());
    2299           5 :         if (!poBlockPresenceArray)
    2300             :         {
    2301           0 :             CPLError(CE_Failure, CPLE_NotSupported, "Cannot create %s in %s",
    2302             :                      osBlockPresenceArrayName.c_str(), osCacheFilename.c_str());
    2303           0 :             return nullptr;
    2304             :         }
    2305           5 :         poBlockPresenceArray->SetNoDataValue(0);
    2306             :     }
    2307             :     else
    2308             :     {
    2309           0 :         return nullptr;
    2310             :     }
    2311             : 
    2312          21 :     m_poBlockCachePresenceArray = poBlockPresenceArray;
    2313             : 
    2314          21 :     return poBlockPresenceArray;
    2315             : }
    2316             : 
    2317             : /************************************************************************/
    2318             : /*                   ZarrArray::BlockCachePresence()                    */
    2319             : /************************************************************************/
    2320             : 
    2321           9 : bool ZarrArray::BlockCachePresence()
    2322             : {
    2323           9 :     if (m_nTotalInnerChunkCount == 1)
    2324           0 :         return true;
    2325             : 
    2326          18 :     const std::string osDirectoryName = GetDataDirectory();
    2327             : 
    2328             :     auto psDir = std::unique_ptr<VSIDIR, decltype(&VSICloseDir)>(
    2329          18 :         VSIOpenDir(osDirectoryName.c_str(), -1, nullptr), VSICloseDir);
    2330           9 :     if (!psDir)
    2331           0 :         return false;
    2332             : 
    2333          18 :     auto poBlockPresenceArray = OpenBlockPresenceCache(true);
    2334           9 :     if (!poBlockPresenceArray)
    2335             :     {
    2336           0 :         return false;
    2337             :     }
    2338             : 
    2339           9 :     if (poBlockPresenceArray->GetAttribute("filling_status"))
    2340             :     {
    2341           4 :         CPLDebug(ZARR_DEBUG_KEY,
    2342             :                  "BlockCachePresence(): %s already filled. Nothing to do",
    2343           4 :                  poBlockPresenceArray->GetName().c_str());
    2344           4 :         return true;
    2345             :     }
    2346             : 
    2347           5 :     const auto nDims = m_aoDims.size();
    2348          10 :     std::vector<GUInt64> anInnerBlockIdx(nDims);
    2349          10 :     std::vector<GUInt64> anInnerBlockCounter(nDims);
    2350          10 :     const std::vector<size_t> anCount(nDims, 1);
    2351          10 :     const std::vector<GInt64> anArrayStep(nDims, 0);
    2352          10 :     const std::vector<GPtrDiff_t> anBufferStride(nDims, 0);
    2353           5 :     const auto &apoDimsCache = poBlockPresenceArray->GetDimensions();
    2354          10 :     const auto eByteDT = GDALExtendedDataType::Create(GDT_UInt8);
    2355             : 
    2356           5 :     CPLDebug(ZARR_DEBUG_KEY,
    2357             :              "BlockCachePresence(): Iterating over %s to find which tiles are "
    2358             :              "present...",
    2359             :              osDirectoryName.c_str());
    2360           5 :     uint64_t nCounter = 0;
    2361             :     const char chSrcFilenameDirSeparator =
    2362           5 :         VSIGetDirectorySeparator(osDirectoryName.c_str())[0];
    2363          44 :     while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir.get()))
    2364             :     {
    2365          39 :         if (!VSI_ISDIR(psEntry->nMode))
    2366             :         {
    2367             :             const CPLStringList aosTokens = GetChunkIndicesFromFilename(
    2368           0 :                 CPLString(psEntry->pszName)
    2369          29 :                     .replaceAll(chSrcFilenameDirSeparator, '/')
    2370          58 :                     .c_str());
    2371          29 :             if (aosTokens.size() == static_cast<int>(nDims))
    2372             :             {
    2373             :                 // Get tile indices from filename
    2374          19 :                 bool unexpectedIndex = false;
    2375          19 :                 uint64_t nInnerChunksInOuter = 1;
    2376          57 :                 for (int i = 0; i < aosTokens.size(); ++i)
    2377             :                 {
    2378          38 :                     if (CPLGetValueType(aosTokens[i]) != CPL_VALUE_INTEGER)
    2379             :                     {
    2380           4 :                         unexpectedIndex = true;
    2381             :                     }
    2382          76 :                     anInnerBlockIdx[i] =
    2383          38 :                         static_cast<GUInt64>(CPLAtoGIntBig(aosTokens[i]));
    2384             :                     const auto nInnerChunkCounterThisDim =
    2385          38 :                         m_anCountInnerBlockInOuter[i];
    2386          38 :                     nInnerChunksInOuter *= nInnerChunkCounterThisDim;
    2387          38 :                     if (anInnerBlockIdx[i] >=
    2388          38 :                         apoDimsCache[i]->GetSize() / nInnerChunkCounterThisDim)
    2389             :                     {
    2390           6 :                         unexpectedIndex = true;
    2391             :                     }
    2392          38 :                     anInnerBlockIdx[i] *= nInnerChunkCounterThisDim;
    2393             :                 }
    2394          19 :                 if (unexpectedIndex)
    2395             :                 {
    2396           7 :                     continue;
    2397             :                 }
    2398             : 
    2399          12 :                 std::fill(anInnerBlockCounter.begin(),
    2400          12 :                           anInnerBlockCounter.end(), 0);
    2401             : 
    2402          36 :                 for (uint64_t iInnerChunk = 0;
    2403          36 :                      iInnerChunk < nInnerChunksInOuter; ++iInnerChunk)
    2404             :                 {
    2405          24 :                     if (iInnerChunk > 0)
    2406             :                     {
    2407             :                         // Update chunk coordinates
    2408          12 :                         size_t iDim = m_anInnerBlockSize.size() - 1;
    2409             :                         const auto nInnerChunkCounterThisDim =
    2410          12 :                             m_anCountInnerBlockInOuter[iDim];
    2411             : 
    2412          12 :                         ++anInnerBlockIdx[iDim];
    2413          12 :                         ++anInnerBlockCounter[iDim];
    2414             : 
    2415          16 :                         while (anInnerBlockCounter[iDim] ==
    2416             :                                nInnerChunkCounterThisDim)
    2417             :                         {
    2418           4 :                             anInnerBlockIdx[iDim] -= nInnerChunkCounterThisDim;
    2419           4 :                             anInnerBlockCounter[iDim] = 0;
    2420           4 :                             --iDim;
    2421             : 
    2422           4 :                             ++anInnerBlockIdx[iDim];
    2423           4 :                             ++anInnerBlockCounter[iDim];
    2424             :                         }
    2425             :                     }
    2426             : 
    2427          24 :                     nCounter++;
    2428          24 :                     if ((nCounter % 1000) == 0)
    2429             :                     {
    2430           0 :                         CPLDebug(
    2431             :                             ZARR_DEBUG_KEY,
    2432             :                             "BlockCachePresence(): Listing in progress "
    2433             :                             "(last examined %s, at least %.02f %% completed)",
    2434           0 :                             psEntry->pszName,
    2435           0 :                             100.0 * double(nCounter) /
    2436           0 :                                 double(m_nTotalInnerChunkCount));
    2437             :                     }
    2438          24 :                     constexpr GByte byOne = 1;
    2439             :                     // CPLDebugOnly(ZARR_DEBUG_KEY, "Marking %s has present",
    2440             :                     // psEntry->pszName);
    2441          48 :                     if (!poBlockPresenceArray->Write(
    2442          24 :                             anInnerBlockIdx.data(), anCount.data(),
    2443             :                             anArrayStep.data(), anBufferStride.data(), eByteDT,
    2444             :                             &byOne))
    2445             :                     {
    2446           0 :                         return false;
    2447             :                     }
    2448             :                 }
    2449             :             }
    2450             :         }
    2451          39 :     }
    2452           5 :     CPLDebug(ZARR_DEBUG_KEY, "BlockCachePresence(): finished");
    2453             : 
    2454             :     // Write filling_status attribute
    2455           5 :     auto poAttr = poBlockPresenceArray->CreateAttribute(
    2456          10 :         "filling_status", {}, GDALExtendedDataType::CreateString(), nullptr);
    2457           5 :     if (poAttr)
    2458             :     {
    2459           5 :         if (nCounter == 0)
    2460           0 :             poAttr->Write("no_tile_present");
    2461           5 :         else if (nCounter == m_nTotalInnerChunkCount)
    2462           0 :             poAttr->Write("all_tiles_present");
    2463             :         else
    2464           5 :             poAttr->Write("some_tiles_missing");
    2465             :     }
    2466             : 
    2467             :     // Force closing
    2468           5 :     m_poBlockCachePresenceArray = nullptr;
    2469           5 :     m_bHasTriedBlockCachePresenceArray = false;
    2470             : 
    2471           5 :     return true;
    2472             : }
    2473             : 
    2474             : /************************************************************************/
    2475             : /*                     ZarrArray::CreateAttribute()                     */
    2476             : /************************************************************************/
    2477             : 
    2478         130 : std::shared_ptr<GDALAttribute> ZarrArray::CreateAttribute(
    2479             :     const std::string &osName, const std::vector<GUInt64> &anDimensions,
    2480             :     const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
    2481             : {
    2482         130 :     if (!CheckValidAndErrorOutIfNot())
    2483           0 :         return nullptr;
    2484             : 
    2485         130 :     if (!m_bUpdatable)
    2486             :     {
    2487           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    2488             :                  "Dataset not open in update mode");
    2489           2 :         return nullptr;
    2490             :     }
    2491         128 :     if (anDimensions.size() >= 2)
    2492             :     {
    2493           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    2494             :                  "Cannot create attributes of dimension >= 2");
    2495           2 :         return nullptr;
    2496             :     }
    2497             :     return m_oAttrGroup.CreateAttribute(osName, anDimensions, oDataType,
    2498         126 :                                         papszOptions);
    2499             : }
    2500             : 
    2501             : /************************************************************************/
    2502             : /*                   ZarrGroupBase::DeleteAttribute()                   */
    2503             : /************************************************************************/
    2504             : 
    2505          18 : bool ZarrArray::DeleteAttribute(const std::string &osName, CSLConstList)
    2506             : {
    2507          18 :     if (!CheckValidAndErrorOutIfNot())
    2508           0 :         return false;
    2509             : 
    2510          18 :     if (!m_bUpdatable)
    2511             :     {
    2512           6 :         CPLError(CE_Failure, CPLE_NotSupported,
    2513             :                  "Dataset not open in update mode");
    2514           6 :         return false;
    2515             :     }
    2516             : 
    2517          12 :     return m_oAttrGroup.DeleteAttribute(osName);
    2518             : }
    2519             : 
    2520             : /************************************************************************/
    2521             : /*                      ZarrArray::SetSpatialRef()                      */
    2522             : /************************************************************************/
    2523             : 
    2524          44 : bool ZarrArray::SetSpatialRef(const OGRSpatialReference *poSRS)
    2525             : {
    2526          44 :     if (!CheckValidAndErrorOutIfNot())
    2527           0 :         return false;
    2528             : 
    2529          44 :     if (!m_bUpdatable)
    2530             :     {
    2531           2 :         return GDALPamMDArray::SetSpatialRef(poSRS);
    2532             :     }
    2533          42 :     m_poSRS.reset();
    2534          42 :     if (poSRS)
    2535          42 :         m_poSRS.reset(poSRS->Clone());
    2536          42 :     m_bSRSModified = true;
    2537          42 :     return true;
    2538             : }
    2539             : 
    2540             : /************************************************************************/
    2541             : /*                         ZarrArray::SetUnit()                         */
    2542             : /************************************************************************/
    2543             : 
    2544           9 : bool ZarrArray::SetUnit(const std::string &osUnit)
    2545             : {
    2546           9 :     if (!CheckValidAndErrorOutIfNot())
    2547           0 :         return false;
    2548             : 
    2549           9 :     if (!m_bUpdatable)
    2550             :     {
    2551           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2552             :                  "Dataset not open in update mode");
    2553           0 :         return false;
    2554             :     }
    2555           9 :     m_osUnit = osUnit;
    2556           9 :     m_bUnitModified = true;
    2557           9 :     return true;
    2558             : }
    2559             : 
    2560             : /************************************************************************/
    2561             : /*                        ZarrArray::GetOffset()                        */
    2562             : /************************************************************************/
    2563             : 
    2564          87 : double ZarrArray::GetOffset(bool *pbHasOffset,
    2565             :                             GDALDataType *peStorageType) const
    2566             : {
    2567          87 :     if (pbHasOffset)
    2568          87 :         *pbHasOffset = m_bHasOffset;
    2569          87 :     if (peStorageType)
    2570           0 :         *peStorageType = GDT_Unknown;
    2571          87 :     return m_dfOffset;
    2572             : }
    2573             : 
    2574             : /************************************************************************/
    2575             : /*                        ZarrArray::GetScale()                         */
    2576             : /************************************************************************/
    2577             : 
    2578          85 : double ZarrArray::GetScale(bool *pbHasScale, GDALDataType *peStorageType) const
    2579             : {
    2580          85 :     if (pbHasScale)
    2581          85 :         *pbHasScale = m_bHasScale;
    2582          85 :     if (peStorageType)
    2583           0 :         *peStorageType = GDT_Unknown;
    2584          85 :     return m_dfScale;
    2585             : }
    2586             : 
    2587             : /************************************************************************/
    2588             : /*                        ZarrArray::SetOffset()                        */
    2589             : /************************************************************************/
    2590             : 
    2591           3 : bool ZarrArray::SetOffset(double dfOffset, GDALDataType /* eStorageType */)
    2592             : {
    2593           3 :     if (!CheckValidAndErrorOutIfNot())
    2594           0 :         return false;
    2595             : 
    2596           3 :     m_dfOffset = dfOffset;
    2597           3 :     m_bHasOffset = true;
    2598           3 :     m_bOffsetModified = true;
    2599           3 :     return true;
    2600             : }
    2601             : 
    2602             : /************************************************************************/
    2603             : /*                        ZarrArray::SetScale()                         */
    2604             : /************************************************************************/
    2605             : 
    2606           3 : bool ZarrArray::SetScale(double dfScale, GDALDataType /* eStorageType */)
    2607             : {
    2608           3 :     if (!CheckValidAndErrorOutIfNot())
    2609           0 :         return false;
    2610             : 
    2611           3 :     m_dfScale = dfScale;
    2612           3 :     m_bHasScale = true;
    2613           3 :     m_bScaleModified = true;
    2614           3 :     return true;
    2615             : }
    2616             : 
    2617             : /************************************************************************/
    2618             : /*                     GetDimensionTypeDirection()                      */
    2619             : /************************************************************************/
    2620             : 
    2621             : /* static */
    2622         232 : void ZarrArray::GetDimensionTypeDirection(CPLJSONObject &oAttributes,
    2623             :                                           std::string &osType,
    2624             :                                           std::string &osDirection)
    2625             : {
    2626         464 :     std::string osUnit;
    2627         696 :     const auto unit = oAttributes[CF_UNITS];
    2628         232 :     if (unit.GetType() == CPLJSONObject::Type::String)
    2629             :     {
    2630          52 :         osUnit = unit.ToString();
    2631             :     }
    2632             : 
    2633         696 :     const auto oStdName = oAttributes[CF_STD_NAME];
    2634         232 :     if (oStdName.GetType() == CPLJSONObject::Type::String)
    2635             :     {
    2636         156 :         const auto osStdName = oStdName.ToString();
    2637          52 :         if (osStdName == CF_PROJ_X_COORD || osStdName == CF_LONGITUDE_STD_NAME)
    2638             :         {
    2639          26 :             osType = GDAL_DIM_TYPE_HORIZONTAL_X;
    2640          26 :             oAttributes.Delete(CF_STD_NAME);
    2641          26 :             if (osUnit == CF_DEGREES_EAST)
    2642             :             {
    2643          24 :                 osDirection = "EAST";
    2644             :             }
    2645             :         }
    2646          50 :         else if (osStdName == CF_PROJ_Y_COORD ||
    2647          24 :                  osStdName == CF_LATITUDE_STD_NAME)
    2648             :         {
    2649          26 :             osType = GDAL_DIM_TYPE_HORIZONTAL_Y;
    2650          26 :             oAttributes.Delete(CF_STD_NAME);
    2651          26 :             if (osUnit == CF_DEGREES_NORTH)
    2652             :             {
    2653          24 :                 osDirection = "NORTH";
    2654             :             }
    2655             :         }
    2656           0 :         else if (osStdName == "time")
    2657             :         {
    2658           0 :             osType = GDAL_DIM_TYPE_TEMPORAL;
    2659           0 :             oAttributes.Delete(CF_STD_NAME);
    2660             :         }
    2661             :     }
    2662             : 
    2663         696 :     const auto osAxis = oAttributes[CF_AXIS].ToString();
    2664         232 :     if (osAxis == "Z")
    2665             :     {
    2666           0 :         osType = GDAL_DIM_TYPE_VERTICAL;
    2667           0 :         const auto osPositive = oAttributes["positive"].ToString();
    2668           0 :         if (osPositive == "up")
    2669             :         {
    2670           0 :             osDirection = "UP";
    2671           0 :             oAttributes.Delete("positive");
    2672             :         }
    2673           0 :         else if (osPositive == "down")
    2674             :         {
    2675           0 :             osDirection = "DOWN";
    2676           0 :             oAttributes.Delete("positive");
    2677             :         }
    2678           0 :         oAttributes.Delete(CF_AXIS);
    2679             :     }
    2680         232 : }
    2681             : 
    2682             : /************************************************************************/
    2683             : /*                       GetCoordinateVariables()                       */
    2684             : /************************************************************************/
    2685             : 
    2686             : std::vector<std::shared_ptr<GDALMDArray>>
    2687           2 : ZarrArray::GetCoordinateVariables() const
    2688             : {
    2689           2 :     if (!CheckValidAndErrorOutIfNot())
    2690           0 :         return {};
    2691             : 
    2692           4 :     std::vector<std::shared_ptr<GDALMDArray>> ret;
    2693           6 :     const auto poCoordinates = GetAttribute("coordinates");
    2694           1 :     if (poCoordinates &&
    2695           3 :         poCoordinates->GetDataType().GetClass() == GEDTC_STRING &&
    2696           1 :         poCoordinates->GetDimensionCount() == 0)
    2697             :     {
    2698           1 :         const char *pszCoordinates = poCoordinates->ReadAsString();
    2699           1 :         if (pszCoordinates)
    2700             :         {
    2701           2 :             auto poGroup = m_poGroupWeak.lock();
    2702           1 :             if (!poGroup)
    2703             :             {
    2704           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2705             :                          "Cannot access coordinate variables of %s has "
    2706             :                          "belonging group has gone out of scope",
    2707           0 :                          GetName().c_str());
    2708             :             }
    2709             :             else
    2710             :             {
    2711             :                 const CPLStringList aosNames(
    2712           2 :                     CSLTokenizeString2(pszCoordinates, " ", 0));
    2713           3 :                 for (int i = 0; i < aosNames.size(); i++)
    2714             :                 {
    2715           6 :                     auto poCoordinateVar = poGroup->OpenMDArray(aosNames[i]);
    2716           2 :                     if (poCoordinateVar)
    2717             :                     {
    2718           2 :                         ret.emplace_back(poCoordinateVar);
    2719             :                     }
    2720             :                     else
    2721             :                     {
    2722           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    2723             :                                  "Cannot find variable corresponding to "
    2724             :                                  "coordinate %s",
    2725             :                                  aosNames[i]);
    2726             :                     }
    2727             :                 }
    2728             :             }
    2729             :         }
    2730             :     }
    2731             : 
    2732           2 :     return ret;
    2733             : }
    2734             : 
    2735             : /************************************************************************/
    2736             : /*                               Resize()                               */
    2737             : /************************************************************************/
    2738             : 
    2739          16 : bool ZarrArray::Resize(const std::vector<GUInt64> &anNewDimSizes,
    2740             :                        CSLConstList /* papszOptions */)
    2741             : {
    2742          16 :     if (!CheckValidAndErrorOutIfNot())
    2743           0 :         return false;
    2744             : 
    2745          16 :     if (!IsWritable())
    2746             :     {
    2747           3 :         CPLError(CE_Failure, CPLE_AppDefined,
    2748             :                  "Resize() not supported on read-only file");
    2749           3 :         return false;
    2750             :     }
    2751             : 
    2752          13 :     const auto nDimCount = GetDimensionCount();
    2753          13 :     if (anNewDimSizes.size() != nDimCount)
    2754             :     {
    2755           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
    2756             :                  "Not expected number of values in anNewDimSizes.");
    2757           0 :         return false;
    2758             :     }
    2759             : 
    2760          13 :     auto &dims = GetDimensions();
    2761          26 :     std::vector<size_t> anGrownDimIdx;
    2762          26 :     std::map<GDALDimension *, GUInt64> oMapDimToSize;
    2763          27 :     for (size_t i = 0; i < nDimCount; ++i)
    2764             :     {
    2765          21 :         auto oIter = oMapDimToSize.find(dims[i].get());
    2766          21 :         if (oIter != oMapDimToSize.end() && oIter->second != anNewDimSizes[i])
    2767             :         {
    2768           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    2769             :                      "Cannot resize a dimension referenced several times "
    2770             :                      "to different sizes");
    2771           7 :             return false;
    2772             :         }
    2773          19 :         if (anNewDimSizes[i] != dims[i]->GetSize())
    2774             :         {
    2775          14 :             if (anNewDimSizes[i] < dims[i]->GetSize())
    2776             :             {
    2777           5 :                 CPLError(CE_Failure, CPLE_NotSupported,
    2778             :                          "Resize() does not support shrinking the array.");
    2779           5 :                 return false;
    2780             :             }
    2781             : 
    2782           9 :             oMapDimToSize[dims[i].get()] = anNewDimSizes[i];
    2783           9 :             anGrownDimIdx.push_back(i);
    2784             :         }
    2785             :         else
    2786             :         {
    2787           5 :             oMapDimToSize[dims[i].get()] = dims[i]->GetSize();
    2788             :         }
    2789             :     }
    2790           6 :     if (!anGrownDimIdx.empty())
    2791             :     {
    2792           6 :         m_bDefinitionModified = true;
    2793          13 :         for (size_t dimIdx : anGrownDimIdx)
    2794             :         {
    2795          14 :             auto dim = std::dynamic_pointer_cast<ZarrDimension>(dims[dimIdx]);
    2796           7 :             if (dim)
    2797             :             {
    2798           7 :                 dim->SetSize(anNewDimSizes[dimIdx]);
    2799           7 :                 if (dim->GetName() != dim->GetFullName())
    2800             :                 {
    2801             :                     // This is not a local dimension
    2802           7 :                     m_poSharedResource->UpdateDimensionSize(dim);
    2803             :                 }
    2804             :             }
    2805             :             else
    2806             :             {
    2807           0 :                 CPLAssert(false);
    2808             :             }
    2809             :         }
    2810             :     }
    2811           6 :     return true;
    2812             : }
    2813             : 
    2814             : /************************************************************************/
    2815             : /*                      NotifyChildrenOfRenaming()                      */
    2816             : /************************************************************************/
    2817             : 
    2818          15 : void ZarrArray::NotifyChildrenOfRenaming()
    2819             : {
    2820          15 :     m_oAttrGroup.ParentRenamed(m_osFullName);
    2821          15 : }
    2822             : 
    2823             : /************************************************************************/
    2824             : /*                           ParentRenamed()                            */
    2825             : /************************************************************************/
    2826             : 
    2827           9 : void ZarrArray::ParentRenamed(const std::string &osNewParentFullName)
    2828             : {
    2829           9 :     GDALMDArray::ParentRenamed(osNewParentFullName);
    2830             : 
    2831           9 :     auto poParent = m_poGroupWeak.lock();
    2832             :     // The parent necessarily exist, since it notified us
    2833           9 :     CPLAssert(poParent);
    2834             : 
    2835          27 :     m_osFilename = CPLFormFilenameSafe(
    2836          18 :         CPLFormFilenameSafe(poParent->GetDirectoryName().c_str(),
    2837           9 :                             m_osName.c_str(), nullptr)
    2838             :             .c_str(),
    2839           9 :         CPLGetFilename(m_osFilename.c_str()), nullptr);
    2840           9 : }
    2841             : 
    2842             : /************************************************************************/
    2843             : /*                               Rename()                               */
    2844             : /************************************************************************/
    2845             : 
    2846          21 : bool ZarrArray::Rename(const std::string &osNewName)
    2847             : {
    2848          21 :     if (!CheckValidAndErrorOutIfNot())
    2849           0 :         return false;
    2850             : 
    2851          21 :     if (!m_bUpdatable)
    2852             :     {
    2853           6 :         CPLError(CE_Failure, CPLE_NotSupported,
    2854             :                  "Dataset not open in update mode");
    2855           6 :         return false;
    2856             :     }
    2857          15 :     if (!ZarrGroupBase::IsValidObjectName(osNewName))
    2858             :     {
    2859           3 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid array name");
    2860           3 :         return false;
    2861             :     }
    2862             : 
    2863          24 :     auto poParent = m_poGroupWeak.lock();
    2864          12 :     if (poParent)
    2865             :     {
    2866          12 :         if (!poParent->CheckArrayOrGroupWithSameNameDoesNotExist(osNewName))
    2867           6 :             return false;
    2868             :     }
    2869             : 
    2870             :     const std::string osRootDirectoryName(
    2871          12 :         CPLGetDirnameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str()));
    2872             :     const std::string osOldDirectoryName = CPLFormFilenameSafe(
    2873          12 :         osRootDirectoryName.c_str(), m_osName.c_str(), nullptr);
    2874             :     const std::string osNewDirectoryName = CPLFormFilenameSafe(
    2875          12 :         osRootDirectoryName.c_str(), osNewName.c_str(), nullptr);
    2876             : 
    2877           6 :     if (VSIRename(osOldDirectoryName.c_str(), osNewDirectoryName.c_str()) != 0)
    2878             :     {
    2879           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Renaming of %s to %s failed",
    2880             :                  osOldDirectoryName.c_str(), osNewDirectoryName.c_str());
    2881           0 :         return false;
    2882             :     }
    2883             : 
    2884           6 :     m_poSharedResource->RenameZMetadataRecursive(osOldDirectoryName,
    2885             :                                                  osNewDirectoryName);
    2886             : 
    2887             :     m_osFilename =
    2888          12 :         CPLFormFilenameSafe(osNewDirectoryName.c_str(),
    2889           6 :                             CPLGetFilename(m_osFilename.c_str()), nullptr);
    2890             : 
    2891           6 :     if (poParent)
    2892             :     {
    2893           6 :         poParent->NotifyArrayRenamed(m_osName, osNewName);
    2894             :     }
    2895             : 
    2896           6 :     BaseRename(osNewName);
    2897             : 
    2898           6 :     return true;
    2899             : }
    2900             : 
    2901             : /************************************************************************/
    2902             : /*                      NotifyChildrenOfDeletion()                      */
    2903             : /************************************************************************/
    2904             : 
    2905          10 : void ZarrArray::NotifyChildrenOfDeletion()
    2906             : {
    2907          10 :     m_oAttrGroup.ParentDeleted();
    2908          10 : }
    2909             : 
    2910             : /************************************************************************/
    2911             : /*                            ParseProjCRS()                            */
    2912             : /************************************************************************/
    2913             : 
    2914        1914 : static void ParseProjCRS(const ZarrAttributeGroup *poAttrGroup,
    2915             :                          CPLJSONObject &oAttributes, bool bFoundProjUUID,
    2916             :                          std::shared_ptr<OGRSpatialReference> &poSRS)
    2917             : {
    2918             :     const auto poAttrProjCode =
    2919        3835 :         bFoundProjUUID ? poAttrGroup->GetAttribute("proj:code") : nullptr;
    2920             :     const char *pszProjCode =
    2921        1914 :         poAttrProjCode ? poAttrProjCode->ReadAsString() : nullptr;
    2922        1914 :     if (pszProjCode)
    2923             :     {
    2924           2 :         poSRS = std::make_shared<OGRSpatialReference>();
    2925           2 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2926           2 :         if (poSRS->SetFromUserInput(
    2927             :                 pszProjCode,
    2928           2 :                 OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
    2929             :             OGRERR_NONE)
    2930             :         {
    2931           0 :             poSRS.reset();
    2932             :         }
    2933             :         else
    2934             :         {
    2935           2 :             oAttributes.Delete("proj:code");
    2936             :         }
    2937             :     }
    2938             :     else
    2939             :     {
    2940             :         // EOP Sentinel Zarr Samples Service only
    2941        5736 :         const auto poAttrProjEPSG = poAttrGroup->GetAttribute("proj:epsg");
    2942        1912 :         if (poAttrProjEPSG)
    2943             :         {
    2944           1 :             poSRS = std::make_shared<OGRSpatialReference>();
    2945           1 :             poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2946           1 :             if (poSRS->importFromEPSG(poAttrProjEPSG->ReadAsInt()) !=
    2947             :                 OGRERR_NONE)
    2948             :             {
    2949           0 :                 poSRS.reset();
    2950             :             }
    2951             :             else
    2952             :             {
    2953           1 :                 oAttributes.Delete("proj:epsg");
    2954             :             }
    2955             :         }
    2956             :         else
    2957             :         {
    2958             :             // Both EOPF Sentinel Zarr Samples Service and geo-proj convention
    2959        5733 :             const auto poAttrProjWKT2 = poAttrGroup->GetAttribute("proj:wkt2");
    2960             :             const char *pszProjWKT2 =
    2961        1911 :                 poAttrProjWKT2 ? poAttrProjWKT2->ReadAsString() : nullptr;
    2962        1911 :             if (pszProjWKT2)
    2963             :             {
    2964           3 :                 poSRS = std::make_shared<OGRSpatialReference>();
    2965           3 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2966           3 :                 if (poSRS->importFromWkt(pszProjWKT2) != OGRERR_NONE)
    2967             :                 {
    2968           0 :                     poSRS.reset();
    2969             :                 }
    2970             :                 else
    2971             :                 {
    2972           3 :                     oAttributes.Delete("proj:wkt2");
    2973             :                 }
    2974             :             }
    2975        1908 :             else if (bFoundProjUUID)
    2976             :             {
    2977             :                 // geo-proj convention
    2978             :                 const auto poAttrProjPROJJSON =
    2979           9 :                     poAttrGroup->GetAttribute("proj:projjson");
    2980             :                 const char *pszProjPROJJSON =
    2981           3 :                     poAttrProjPROJJSON ? poAttrProjPROJJSON->ReadAsString()
    2982           3 :                                        : nullptr;
    2983           3 :                 if (pszProjPROJJSON)
    2984             :                 {
    2985           1 :                     poSRS = std::make_shared<OGRSpatialReference>();
    2986           1 :                     poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2987           1 :                     if (poSRS->SetFromUserInput(
    2988             :                             pszProjPROJJSON,
    2989             :                             OGRSpatialReference::
    2990           1 :                                 SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
    2991             :                         OGRERR_NONE)
    2992             :                     {
    2993           0 :                         poSRS.reset();
    2994             :                     }
    2995             :                     else
    2996             :                     {
    2997           1 :                         oAttributes.Delete("proj:projjson");
    2998             :                     }
    2999             :                 }
    3000             :             }
    3001             :         }
    3002             :     }
    3003        1914 : }
    3004             : 
    3005             : /************************************************************************/
    3006             : /*                      ParseSpatialConventions()                       */
    3007             : /************************************************************************/
    3008             : 
    3009         116 : static void ParseSpatialConventions(
    3010             :     const std::shared_ptr<ZarrSharedResource> &poSharedResource,
    3011             :     const ZarrAttributeGroup *poAttrGroup, CPLJSONObject &oAttributes,
    3012             :     std::shared_ptr<OGRSpatialReference> &poSRS, bool &bAxisAssigned,
    3013             :     const std::vector<std::shared_ptr<GDALDimension>> &apoDims)
    3014             : {
    3015             :     // From https://github.com/zarr-conventions/spatial
    3016             :     const auto poAttrSpatialDimensions =
    3017         232 :         poAttrGroup->GetAttribute("spatial:dimensions");
    3018         116 :     if (!poAttrSpatialDimensions)
    3019         110 :         return;
    3020             : 
    3021             :     const auto aosSpatialDimensions =
    3022           6 :         poAttrSpatialDimensions->ReadAsStringArray();
    3023           6 :     if (aosSpatialDimensions.size() < 2)
    3024           0 :         return;
    3025             : 
    3026           6 :     int iDimNameY = 0;
    3027           6 :     int iDimNameX = 0;
    3028             :     const char *pszNameY =
    3029           6 :         aosSpatialDimensions[aosSpatialDimensions.size() - 2];
    3030             :     const char *pszNameX =
    3031           6 :         aosSpatialDimensions[aosSpatialDimensions.size() - 1];
    3032           6 :     int iDim = 1;
    3033          18 :     for (const auto &poDim : apoDims)
    3034             :     {
    3035          12 :         if (poDim->GetName() == pszNameX)
    3036           6 :             iDimNameX = iDim;
    3037           6 :         else if (poDim->GetName() == pszNameY)
    3038           6 :             iDimNameY = iDim;
    3039          12 :         ++iDim;
    3040             :     }
    3041           6 :     if (iDimNameX == 0)
    3042             :     {
    3043           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    3044             :                  "spatial:dimensions[%d] = %s is a unknown "
    3045             :                  "Zarr dimension",
    3046           0 :                  static_cast<int>(aosSpatialDimensions.size() - 1), pszNameX);
    3047             :     }
    3048           6 :     if (iDimNameY == 0)
    3049             :     {
    3050           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    3051             :                  "spatial_dimensions[%d] = %s is a unknown "
    3052             :                  "Zarr dimension",
    3053           0 :                  static_cast<int>(aosSpatialDimensions.size() - 2), pszNameY);
    3054             :     }
    3055             : 
    3056           6 :     if (iDimNameX > 0 && iDimNameY > 0)
    3057             :     {
    3058           6 :         oAttributes.Delete("spatial:dimensions");
    3059             : 
    3060           6 :         if (!bAxisAssigned && poSRS)
    3061             :         {
    3062           5 :             const auto &oMapping = poSRS->GetDataAxisToSRSAxisMapping();
    3063          15 :             if (oMapping == std::vector<int>{2, 1} ||
    3064          10 :                 oMapping == std::vector<int>{2, 1, 3})
    3065           0 :                 poSRS->SetDataAxisToSRSAxisMapping({iDimNameY, iDimNameX});
    3066          10 :             else if (oMapping == std::vector<int>{1, 2} ||
    3067           5 :                      oMapping == std::vector<int>{1, 2, 3})
    3068           5 :                 poSRS->SetDataAxisToSRSAxisMapping({iDimNameX, iDimNameY});
    3069             : 
    3070           5 :             bAxisAssigned = true;
    3071             :         }
    3072             :     }
    3073             : 
    3074             :     const auto poAttrSpatialRegistration =
    3075          18 :         poAttrGroup->GetAttribute("spatial:registration");
    3076           6 :     bool bIsNodeRegistration = false;
    3077           6 :     if (!poAttrSpatialRegistration)
    3078           3 :         oAttributes.Set("spatial:registration", "pixel");  // default value
    3079             :     else
    3080             :     {
    3081             :         const char *pszSpatialRegistration =
    3082           3 :             poAttrSpatialRegistration->ReadAsString();
    3083           3 :         if (pszSpatialRegistration &&
    3084           3 :             strcmp(pszSpatialRegistration, "node") == 0)
    3085           1 :             bIsNodeRegistration = true;
    3086             :     }
    3087             : 
    3088             :     const auto poAttrSpatialTransform =
    3089          18 :         poAttrGroup->GetAttribute("spatial:transform");
    3090             :     const auto poAttrSpatialTransformType =
    3091          18 :         poAttrGroup->GetAttribute("spatial:transform_type");
    3092             :     const char *pszAttrSpatialTransformType =
    3093           6 :         poAttrSpatialTransformType ? poAttrSpatialTransformType->ReadAsString()
    3094           6 :                                    : nullptr;
    3095             : 
    3096           8 :     if (poAttrSpatialTransform &&
    3097           2 :         (!pszAttrSpatialTransformType ||
    3098           8 :          strcmp(pszAttrSpatialTransformType, "affine") == 0))
    3099             :     {
    3100          12 :         auto adfSpatialTransform = poAttrSpatialTransform->ReadAsDoubleArray();
    3101           6 :         if (adfSpatialTransform.size() == 6)
    3102             :         {
    3103           6 :             oAttributes.Delete("spatial:transform");
    3104           6 :             oAttributes.Delete("spatial:transform_type");
    3105             : 
    3106             :             // If we have rotation/shear coefficients, expose a gdal:geotransform
    3107             :             // attributes
    3108           6 :             if (adfSpatialTransform[1] != 0 || adfSpatialTransform[3] != 0)
    3109             :             {
    3110           2 :                 if (bIsNodeRegistration)
    3111             :                 {
    3112             :                     // From pixel center convention to GDAL's corner convention
    3113           1 :                     adfSpatialTransform[2] -= 0.5 * adfSpatialTransform[0] +
    3114           1 :                                               0.5 * adfSpatialTransform[1];
    3115           2 :                     adfSpatialTransform[5] -= 0.5 * adfSpatialTransform[3] +
    3116           1 :                                               0.5 * adfSpatialTransform[4];
    3117             :                 }
    3118             : 
    3119           2 :                 CPLJSONArray oGeoTransform;
    3120             :                 // Reorder coefficients to GDAL convention
    3121          14 :                 for (int idx : {2, 0, 1, 5, 3, 4})
    3122          12 :                     oGeoTransform.Add(adfSpatialTransform[idx]);
    3123           2 :                 oAttributes["gdal:geotransform"] = oGeoTransform;
    3124             :             }
    3125             :             else
    3126             :             {
    3127           4 :                 auto &poDimX = apoDims[iDimNameX - 1];
    3128           4 :                 auto &poDimY = apoDims[iDimNameY - 1];
    3129           4 :                 if (!dynamic_cast<GDALMDArrayRegularlySpaced *>(
    3130          16 :                         poDimX->GetIndexingVariable().get()) &&
    3131           4 :                     !dynamic_cast<GDALMDArrayRegularlySpaced *>(
    3132           9 :                         poDimY->GetIndexingVariable().get()))
    3133             :                 {
    3134             :                     auto poIndexingVarX = GDALMDArrayRegularlySpaced::Create(
    3135           4 :                         std::string(), poDimX->GetName(), poDimX,
    3136          12 :                         adfSpatialTransform[2] + adfSpatialTransform[0] / 2,
    3137          12 :                         adfSpatialTransform[0], 0);
    3138           4 :                     poDimX->SetIndexingVariable(poIndexingVarX);
    3139             : 
    3140             :                     // Make the shared resource hold a strong
    3141             :                     // reference on the indexing variable,
    3142             :                     // so that it remains available to anyone
    3143             :                     // querying the dimension for it.
    3144           4 :                     poSharedResource->RegisterIndexingVariable(
    3145             :                         poDimX->GetFullName(), poIndexingVarX);
    3146             : 
    3147             :                     auto poIndexingVarY = GDALMDArrayRegularlySpaced::Create(
    3148           4 :                         std::string(), poDimY->GetName(), poDimY,
    3149          12 :                         adfSpatialTransform[5] + adfSpatialTransform[4] / 2,
    3150           8 :                         adfSpatialTransform[4], 0);
    3151           4 :                     poDimY->SetIndexingVariable(poIndexingVarY);
    3152           4 :                     poSharedResource->RegisterIndexingVariable(
    3153             :                         poDimY->GetFullName(), poIndexingVarY);
    3154             :                 }
    3155             :             }
    3156             :         }
    3157             :         else
    3158             :         {
    3159           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    3160             :                      "spatial:transform[] contains an "
    3161             :                      "unexpected number of values: %d",
    3162           0 :                      static_cast<int>(adfSpatialTransform.size()));
    3163             :         }
    3164             :     }
    3165             : }
    3166             : 
    3167             : /************************************************************************/
    3168             : /*               DetectSRSFromEOPFSampleServiceMetadata()               */
    3169             : /************************************************************************/
    3170             : 
    3171             : /* This function is derived from ExtractCoordinateMetadata() of
    3172             :  * https://github.com/EOPF-Sample-Service/GDAL-ZARR-EOPF/blob/main/src/eopf_metadata.cpp
    3173             :  * released under the MIT license and
    3174             :  * Copyright (c) 2024 Yuvraj Adagale and contributors
    3175             :  *
    3176             :  * Note: it does not handle defaulting to EPSG:4326 as it is not clear to me
    3177             :  * (E. Rouault) why this would be needed, at least for Sentinel2 L1C or L2
    3178             :  * products
    3179             :  */
    3180          13 : static void DetectSRSFromEOPFSampleServiceMetadata(
    3181             :     const std::string &osRootDirectoryName,
    3182             :     const std::shared_ptr<ZarrGroupBase> &poGroup,
    3183             :     std::shared_ptr<OGRSpatialReference> &poSRS)
    3184             : {
    3185          13 :     const CPLJSONObject obj = poGroup->GetAttributeGroup().Serialize();
    3186             : 
    3187             :     // -----------------------------------
    3188             :     // STEP 1: Extract spatial reference information
    3189             :     // -----------------------------------
    3190             : 
    3191             :     // Find EPSG code directly or in STAC properties
    3192          13 :     int nEPSGCode = 0;
    3193          13 :     const CPLJSONObject &stacDiscovery = obj.GetObj("stac_discovery");
    3194          13 :     if (stacDiscovery.IsValid())
    3195             :     {
    3196          14 :         const CPLJSONObject &properties = stacDiscovery.GetObj("properties");
    3197           7 :         if (properties.IsValid())
    3198             :         {
    3199             :             // Try to get proj:epsg as a number first, then as a string
    3200           6 :             nEPSGCode = properties.GetInteger("proj:epsg", 0);
    3201           6 :             if (nEPSGCode <= 0)
    3202             :             {
    3203             :                 nEPSGCode =
    3204           4 :                     std::atoi(properties.GetString("proj:epsg", "").c_str());
    3205             :             }
    3206           6 :             if (nEPSGCode > 0)
    3207             :             {
    3208           2 :                 CPLDebugOnly(ZARR_DEBUG_KEY,
    3209             :                              "Found proj:epsg in STAC properties: %d",
    3210             :                              nEPSGCode);
    3211             :             }
    3212             :         }
    3213             :     }
    3214             : 
    3215             :     // If not found in STAC, try top level
    3216          13 :     if (nEPSGCode <= 0)
    3217             :     {
    3218          11 :         nEPSGCode = obj.GetInteger("proj:epsg", obj.GetInteger("epsg", 0));
    3219          11 :         if (nEPSGCode <= 0)
    3220             :         {
    3221           9 :             nEPSGCode = std::atoi(
    3222          18 :                 obj.GetString("proj:epsg", obj.GetString("epsg", "")).c_str());
    3223             :         }
    3224          11 :         if (nEPSGCode > 0)
    3225             :         {
    3226           2 :             CPLDebugOnly(ZARR_DEBUG_KEY, "Found proj:epsg at top level: %d",
    3227             :                          nEPSGCode);
    3228             :         }
    3229             :     }
    3230             : 
    3231             :     // If still not found, simple search in common locations
    3232          13 :     if (nEPSGCode <= 0)
    3233             :     {
    3234          17 :         for (const auto &child : obj.GetChildren())
    3235             :         {
    3236           8 :             if (child.GetType() == CPLJSONObject::Type::Object)
    3237             :             {
    3238             :                 nEPSGCode =
    3239           7 :                     child.GetInteger("proj:epsg", child.GetInteger("epsg", 0));
    3240           7 :                 if (nEPSGCode <= 0)
    3241             :                 {
    3242           5 :                     nEPSGCode = std::atoi(
    3243             :                         child
    3244          10 :                             .GetString("proj:epsg", child.GetString("epsg", ""))
    3245             :                             .c_str());
    3246             :                 }
    3247           7 :                 if (nEPSGCode > 0)
    3248             :                 {
    3249           2 :                     CPLDebugOnly(ZARR_DEBUG_KEY,
    3250             :                                  "Found proj:epsg in child %s: %d",
    3251             :                                  child.GetName().c_str(), nEPSGCode);
    3252           2 :                     break;
    3253             :                 }
    3254             :             }
    3255             :         }
    3256             :     }
    3257             : 
    3258             :     // Enhanced search for STAC discovery metadata with better structure parsing
    3259          13 :     if (nEPSGCode <= 0 && stacDiscovery.IsValid())
    3260             :     {
    3261             :         // Try to get the full STAC item
    3262          10 :         const CPLJSONObject &geometry = stacDiscovery.GetObj("geometry");
    3263             : 
    3264           5 :         if (geometry.IsValid())
    3265             :         {
    3266           2 :             const CPLJSONObject &geomCrs = geometry.GetObj("crs");
    3267           1 :             if (geomCrs.IsValid())
    3268             :             {
    3269           2 :                 const CPLJSONObject &geomProps = geomCrs.GetObj("properties");
    3270           1 :                 if (geomProps.IsValid())
    3271             :                 {
    3272           1 :                     const int nGeomEpsg = geomProps.GetInteger("code", 0);
    3273           1 :                     if (nGeomEpsg != 0)
    3274             :                     {
    3275           1 :                         nEPSGCode = nGeomEpsg;
    3276           1 :                         CPLDebugOnly(ZARR_DEBUG_KEY,
    3277             :                                      "Found CRS code in STAC geometry: %d",
    3278             :                                      nEPSGCode);
    3279             :                     }
    3280             :                 }
    3281             :             }
    3282             :         }
    3283             :     }
    3284             : 
    3285             :     // Try to infer CRS from Sentinel-2 tile naming convention
    3286          13 :     if (nEPSGCode <= 0)
    3287             :     {
    3288             :         // Look for Sentinel-2 tile ID pattern in dataset name or metadata
    3289          12 :         std::string tileName;
    3290             : 
    3291             :         // First, try to extract from dataset name if it contains T##XXX pattern
    3292          12 :         std::string dsNameStr(CPLGetFilename(osRootDirectoryName.c_str()));
    3293           6 :         const size_t tilePos = dsNameStr.find("_T");
    3294           6 :         if (tilePos != std::string::npos && tilePos + 6 < dsNameStr.length())
    3295             :         {
    3296           1 :             tileName = dsNameStr.substr(tilePos + 1, 6);  // Extract T##XXX
    3297           1 :             CPLDebugOnly(ZARR_DEBUG_KEY,
    3298             :                          "Extracted tile name from dataset name: %s",
    3299             :                          tileName.c_str());
    3300             :         }
    3301             : 
    3302             :         // Also check in STAC discovery metadata
    3303           6 :         if (tileName.empty() && stacDiscovery.IsValid())
    3304             :         {
    3305             :             const CPLJSONObject &properties =
    3306           8 :                 stacDiscovery.GetObj("properties");
    3307           4 :             if (properties.IsValid())
    3308             :             {
    3309           8 :                 tileName = properties.GetString(
    3310             :                     "s2:mgrs_tile",
    3311           8 :                     properties.GetString("mgrs_tile",
    3312          12 :                                          properties.GetString("tile_id", "")));
    3313           4 :                 if (!tileName.empty())
    3314             :                 {
    3315           3 :                     CPLDebug("EOPFZARR",
    3316             :                              "Found tile name in STAC properties: %s",
    3317             :                              tileName.c_str());
    3318             :                 }
    3319             :             }
    3320             :         }
    3321             : 
    3322             :         // Parse tile name to get EPSG code (T##XXX -> UTM Zone ## North/South)
    3323           6 :         if (!tileName.empty() && tileName.length() >= 3 && tileName[0] == 'T')
    3324             :         {
    3325             :             // Extract zone number (characters 1-2)
    3326           8 :             const std::string zoneStr = tileName.substr(1, 2);
    3327           4 :             const int zone = std::atoi(zoneStr.c_str());
    3328             : 
    3329           4 :             if (zone >= 1 && zone <= 60)
    3330             :             {
    3331             :                 // Determine hemisphere from the third character
    3332             :                 const char hemisphere =
    3333           4 :                     tileName.length() > 3 ? tileName[3] : 'N';
    3334             : 
    3335             :                 // For Sentinel-2, assume Northern hemisphere unless explicitly Southern
    3336             :                 // Most Sentinel-2 data is Northern hemisphere
    3337             :                 // Cf https://en.wikipedia.org/wiki/Military_Grid_Reference_System#Grid_zone_designation
    3338           4 :                 const bool isNorth = (hemisphere >= 'N' && hemisphere <= 'X');
    3339             : 
    3340           4 :                 nEPSGCode = isNorth ? (32600 + zone) : (32700 + zone);
    3341           4 :                 CPLDebugOnly(ZARR_DEBUG_KEY,
    3342             :                              "Inferred EPSG %d from Sentinel-2 tile %s (zone "
    3343             :                              "%d, %s hemisphere)",
    3344             :                              nEPSGCode, tileName.c_str(), zone,
    3345             :                              isNorth ? "North" : "South");
    3346             :             }
    3347             :         }
    3348             :     }
    3349             : 
    3350          13 :     if (nEPSGCode > 0)
    3351             :     {
    3352          11 :         poSRS = std::make_shared<OGRSpatialReference>();
    3353          11 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3354          11 :         if (poSRS->importFromEPSG(nEPSGCode) == OGRERR_NONE)
    3355             :         {
    3356          11 :             return;
    3357             :         }
    3358           0 :         poSRS.reset();
    3359             :     }
    3360             : 
    3361             :     // Look for WKT
    3362           4 :     std::string wkt = obj.GetString("spatial_ref", "");
    3363           2 :     if (wkt.empty() && stacDiscovery.IsValid())
    3364             :     {
    3365           2 :         const CPLJSONObject &properties = stacDiscovery.GetObj("properties");
    3366           1 :         if (properties.IsValid())
    3367             :         {
    3368           1 :             wkt = properties.GetString("spatial_ref", "");
    3369             :         }
    3370             :     }
    3371           2 :     if (!wkt.empty())
    3372             :     {
    3373           2 :         poSRS = std::make_shared<OGRSpatialReference>();
    3374           2 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3375           2 :         if (poSRS->importFromWkt(wkt.c_str()) == OGRERR_NONE)
    3376             :         {
    3377           2 :             return;
    3378             :         }
    3379           0 :         poSRS.reset();
    3380             :     }
    3381             : }
    3382             : 
    3383             : /************************************************************************/
    3384             : /*                           SetAttributes()                            */
    3385             : /************************************************************************/
    3386             : 
    3387        1890 : void ZarrArray::SetAttributes(const std::shared_ptr<ZarrGroupBase> &poGroup,
    3388             :                               CPLJSONObject &oAttributes)
    3389             : {
    3390        5670 :     const auto crs = oAttributes[CRS_ATTRIBUTE_NAME];
    3391        1890 :     std::shared_ptr<OGRSpatialReference> poSRS;
    3392        1890 :     if (crs.GetType() == CPLJSONObject::Type::Object)
    3393             :     {
    3394          48 :         for (const char *key : {"url", "wkt", "projjson"})
    3395             :         {
    3396          96 :             const auto item = crs[key];
    3397          48 :             if (item.IsValid())
    3398             :             {
    3399          29 :                 poSRS = std::make_shared<OGRSpatialReference>();
    3400          29 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3401          58 :                 if (poSRS->SetFromUserInput(
    3402          58 :                         item.ToString().c_str(),
    3403             :                         OGRSpatialReference::
    3404          29 :                             SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
    3405             :                     OGRERR_NONE)
    3406             :                 {
    3407          29 :                     oAttributes.Delete(CRS_ATTRIBUTE_NAME);
    3408          29 :                     break;
    3409             :                 }
    3410           0 :                 poSRS.reset();
    3411             :             }
    3412             :         }
    3413             :     }
    3414             :     else
    3415             :     {
    3416             :         // Check if SRS is using CF-1 conventions
    3417        5583 :         const auto gridMapping = oAttributes["grid_mapping"];
    3418        1861 :         if (gridMapping.GetType() == CPLJSONObject::Type::String)
    3419             :         {
    3420             :             const auto gridMappingArray =
    3421           6 :                 poGroup->OpenMDArray(gridMapping.ToString());
    3422           2 :             if (gridMappingArray)
    3423             :             {
    3424           2 :                 poSRS = std::make_shared<OGRSpatialReference>();
    3425           2 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3426           4 :                 CPLStringList aosKeyValues;
    3427          22 :                 for (const auto &poAttr : gridMappingArray->GetAttributes())
    3428             :                 {
    3429          20 :                     if (poAttr->GetDataType().GetClass() == GEDTC_STRING)
    3430             :                     {
    3431           4 :                         aosKeyValues.SetNameValue(poAttr->GetName().c_str(),
    3432           8 :                                                   poAttr->ReadAsString());
    3433             :                     }
    3434          16 :                     else if (poAttr->GetDataType().GetClass() == GEDTC_NUMERIC)
    3435             :                     {
    3436          32 :                         std::string osVal;
    3437          32 :                         for (double val : poAttr->ReadAsDoubleArray())
    3438             :                         {
    3439          16 :                             if (!osVal.empty())
    3440           0 :                                 osVal += ',';
    3441          16 :                             osVal += CPLSPrintf("%.17g", val);
    3442             :                         }
    3443          16 :                         aosKeyValues.SetNameValue(poAttr->GetName().c_str(),
    3444          32 :                                                   osVal.c_str());
    3445             :                     }
    3446             :                 }
    3447           2 :                 if (poSRS->importFromCF1(aosKeyValues.List(), nullptr) !=
    3448             :                     OGRERR_NONE)
    3449             :                 {
    3450           0 :                     poSRS.reset();
    3451             :                 }
    3452             :             }
    3453             :         }
    3454             :     }
    3455             : 
    3456             :     // For EOPF Sentinel Zarr Samples Service datasets, read attributes from
    3457             :     // the STAC Proj extension attributes to get the CRS.
    3458             :     // There is also partly intersection with https://github.com/zarr-conventions/geo-proj
    3459             :     // For zarr-conventions/geo-proj and zarr-conventions/spatial, try first
    3460             :     // at array level, then parent and finally grandparent.
    3461             : 
    3462        3780 :     auto poRootGroup = std::dynamic_pointer_cast<ZarrGroupBase>(GetRootGroup());
    3463             : 
    3464        3780 :     std::vector<const ZarrAttributeGroup *> apoAttrGroup;
    3465             : 
    3466        1890 :     ZarrAttributeGroup oThisAttrGroup(std::string(),
    3467        3780 :                                       /* bContainerIsGroup = */ false);
    3468        1890 :     oThisAttrGroup.Init(oAttributes, /* bUpdatable=*/false);
    3469             : 
    3470        1890 :     apoAttrGroup.push_back(&oThisAttrGroup);
    3471        1890 :     if (GetDimensionCount() >= 2)
    3472             :     {
    3473        1386 :         apoAttrGroup.push_back(&(poGroup->GetAttributeGroup()));
    3474             :         // Only use root group to detect conventions
    3475        1386 :         if (poRootGroup)
    3476        1386 :             apoAttrGroup.push_back(&(poRootGroup->GetAttributeGroup()));
    3477             :     }
    3478             : 
    3479             :     // Look for declaration of geo-proj and spatial conventions
    3480        1890 :     bool bFoundSpatialUUID = false;
    3481        1890 :     bool bFoundProjUUID = false;
    3482        6373 :     for (const ZarrAttributeGroup *poAttrGroup : apoAttrGroup)
    3483             :     {
    3484             :         const auto poAttrZarrConventions =
    3485        9280 :             poAttrGroup->GetAttribute("zarr_conventions");
    3486        4640 :         if (poAttrZarrConventions)
    3487             :         {
    3488             :             const char *pszZarrConventions =
    3489         157 :                 poAttrZarrConventions->ReadAsString();
    3490         157 :             if (pszZarrConventions)
    3491             :             {
    3492         314 :                 CPLJSONDocument oDoc;
    3493         157 :                 if (oDoc.LoadMemory(pszZarrConventions))
    3494             :                 {
    3495         314 :                     const auto oZarrConventions = oDoc.GetRoot();
    3496         157 :                     if (oZarrConventions.GetType() ==
    3497             :                         CPLJSONObject::Type::Array)
    3498             :                     {
    3499             :                         const auto oZarrConventionsArray =
    3500         157 :                             oZarrConventions.ToArray();
    3501             : 
    3502             :                         const auto hasSpatialUUIDLambda =
    3503         214 :                             [](const CPLJSONObject &obj)
    3504             :                         {
    3505         214 :                             constexpr const char *SPATIAL_UUID =
    3506             :                                 "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4";
    3507         214 :                             return obj.GetString("uuid") == SPATIAL_UUID;
    3508             :                         };
    3509             :                         bFoundSpatialUUID =
    3510         314 :                             std::find_if(oZarrConventionsArray.begin(),
    3511         314 :                                          oZarrConventionsArray.end(),
    3512         157 :                                          hasSpatialUUIDLambda) !=
    3513         314 :                             oZarrConventionsArray.end();
    3514             : 
    3515             :                         const auto hasProjUUIDLambda =
    3516         209 :                             [](const CPLJSONObject &obj)
    3517             :                         {
    3518         209 :                             constexpr const char *PROJ_UUID =
    3519             :                                 "f17cb550-5864-4468-aeb7-f3180cfb622f";
    3520         209 :                             return obj.GetString("uuid") == PROJ_UUID;
    3521             :                         };
    3522             :                         bFoundProjUUID =
    3523         314 :                             std::find_if(oZarrConventionsArray.begin(),
    3524         314 :                                          oZarrConventionsArray.end(),
    3525         157 :                                          hasProjUUIDLambda) !=
    3526         314 :                             oZarrConventionsArray.end();
    3527             :                     }
    3528             :                 }
    3529             :             }
    3530         157 :             break;
    3531             :         }
    3532             :     }
    3533             : 
    3534             :     // If there is neither spatial nor geo-proj, just consider the current array
    3535             :     // for EOPF Sentinel Zarr Samples Service datasets
    3536        1890 :     if (!bFoundSpatialUUID && !bFoundProjUUID)
    3537        1832 :         apoAttrGroup.resize(1);
    3538          58 :     else if (apoAttrGroup.size() == 3)
    3539          58 :         apoAttrGroup.resize(2);
    3540             : 
    3541        1890 :     bool bAxisAssigned = false;
    3542        3838 :     for (const ZarrAttributeGroup *poAttrGroup : apoAttrGroup)
    3543             :     {
    3544        1948 :         if (!poSRS)
    3545             :         {
    3546        1914 :             ParseProjCRS(poAttrGroup, oAttributes, bFoundProjUUID, poSRS);
    3547             :         }
    3548             : 
    3549        1948 :         if (GetDimensionCount() >= 2 && bFoundSpatialUUID)
    3550             :         {
    3551         116 :             const bool bAxisAssignedBefore = bAxisAssigned;
    3552         116 :             ParseSpatialConventions(m_poSharedResource, poAttrGroup,
    3553             :                                     oAttributes, poSRS, bAxisAssigned,
    3554         116 :                                     m_aoDims);
    3555         116 :             if (bAxisAssigned && !bAxisAssignedBefore)
    3556           5 :                 SetSRS(poSRS);
    3557             : 
    3558             :             // Note: we ignore EOPF Sentinel Zarr Samples Service "proj:transform"
    3559             :             // attribute, as we don't need to
    3560             :             // use it since the x and y dimensions are already associated with a
    3561             :             // 1-dimensional array with the values.
    3562             :         }
    3563             :     }
    3564             : 
    3565        1890 :     if (!poSRS && poRootGroup && oAttributes.GetObj("_eopf_attrs").IsValid())
    3566             :     {
    3567          13 :         DetectSRSFromEOPFSampleServiceMetadata(
    3568          13 :             m_poSharedResource->GetRootDirectoryName(), poRootGroup, poSRS);
    3569             :     }
    3570             : 
    3571        1890 :     if (poSRS && !bAxisAssigned)
    3572             :     {
    3573          46 :         int iDimX = 0;
    3574          46 :         int iDimY = 0;
    3575          46 :         int iCount = 1;
    3576         138 :         for (const auto &poDim : GetDimensions())
    3577             :         {
    3578          92 :             if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X)
    3579           2 :                 iDimX = iCount;
    3580          90 :             else if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y)
    3581           2 :                 iDimY = iCount;
    3582          92 :             iCount++;
    3583             :         }
    3584          46 :         if ((iDimX == 0 || iDimY == 0) && GetDimensionCount() >= 2)
    3585             :         {
    3586          44 :             iDimX = static_cast<int>(GetDimensionCount());
    3587          44 :             iDimY = iDimX - 1;
    3588             :         }
    3589          46 :         if (iDimX > 0 && iDimY > 0)
    3590             :         {
    3591          46 :             const auto &oMapping = poSRS->GetDataAxisToSRSAxisMapping();
    3592         131 :             if (oMapping == std::vector<int>{2, 1} ||
    3593          85 :                 oMapping == std::vector<int>{2, 1, 3})
    3594           7 :                 poSRS->SetDataAxisToSRSAxisMapping({iDimY, iDimX});
    3595          79 :             else if (oMapping == std::vector<int>{1, 2} ||
    3596          40 :                      oMapping == std::vector<int>{1, 2, 3})
    3597          39 :                 poSRS->SetDataAxisToSRSAxisMapping({iDimX, iDimY});
    3598             :         }
    3599             : 
    3600          46 :         SetSRS(poSRS);
    3601             :     }
    3602             : 
    3603        5670 :     const auto unit = oAttributes[CF_UNITS];
    3604        1890 :     if (unit.GetType() == CPLJSONObject::Type::String)
    3605             :     {
    3606         165 :         std::string osUnit = unit.ToString();
    3607          55 :         oAttributes.Delete(CF_UNITS);
    3608          55 :         RegisterUnit(osUnit);
    3609             :     }
    3610             : 
    3611        5670 :     const auto offset = oAttributes[CF_ADD_OFFSET];
    3612        1890 :     const auto offsetType = offset.GetType();
    3613        1890 :     if (offsetType == CPLJSONObject::Type::Integer ||
    3614        1890 :         offsetType == CPLJSONObject::Type::Long ||
    3615             :         offsetType == CPLJSONObject::Type::Double)
    3616             :     {
    3617           3 :         double dfOffset = offset.ToDouble();
    3618           3 :         oAttributes.Delete(CF_ADD_OFFSET);
    3619           3 :         RegisterOffset(dfOffset);
    3620             :     }
    3621             : 
    3622        5670 :     const auto scale = oAttributes[CF_SCALE_FACTOR];
    3623        1890 :     const auto scaleType = scale.GetType();
    3624        1890 :     if (scaleType == CPLJSONObject::Type::Integer ||
    3625        1890 :         scaleType == CPLJSONObject::Type::Long ||
    3626             :         scaleType == CPLJSONObject::Type::Double)
    3627             :     {
    3628           3 :         double dfScale = scale.ToDouble();
    3629           3 :         oAttributes.Delete(CF_SCALE_FACTOR);
    3630           3 :         RegisterScale(dfScale);
    3631             :     }
    3632             : 
    3633        1890 :     m_oAttrGroup.Init(oAttributes, m_bUpdatable);
    3634        1890 : }
    3635             : 
    3636             : /************************************************************************/
    3637             : /*                           SetStatistics()                            */
    3638             : /************************************************************************/
    3639             : 
    3640           1 : bool ZarrArray::SetStatistics(bool bApproxStats, double dfMin, double dfMax,
    3641             :                               double dfMean, double dfStdDev,
    3642             :                               GUInt64 nValidCount, CSLConstList papszOptions)
    3643             : {
    3644           2 :     if (!bApproxStats && m_bUpdatable &&
    3645           1 :         CPLTestBool(
    3646             :             CSLFetchNameValueDef(papszOptions, "UPDATE_METADATA", "NO")))
    3647             :     {
    3648           3 :         auto poAttr = GetAttribute("actual_range");
    3649           1 :         if (!poAttr)
    3650             :         {
    3651             :             poAttr =
    3652           1 :                 CreateAttribute("actual_range", {2}, GetDataType(), nullptr);
    3653             :         }
    3654           1 :         if (poAttr)
    3655             :         {
    3656           2 :             std::vector<GUInt64> startIdx = {0};
    3657           2 :             std::vector<size_t> count = {2};
    3658           1 :             std::vector<double> values = {dfMin, dfMax};
    3659           2 :             poAttr->Write(startIdx.data(), count.data(), nullptr, nullptr,
    3660           2 :                           GDALExtendedDataType::Create(GDT_Float64),
    3661           1 :                           values.data(), nullptr, 0);
    3662             :         }
    3663             :     }
    3664           1 :     return GDALPamMDArray::SetStatistics(bApproxStats, dfMin, dfMax, dfMean,
    3665           1 :                                          dfStdDev, nValidCount, papszOptions);
    3666             : }
    3667             : 
    3668             : /************************************************************************/
    3669             : /*               ZarrArray::IsBlockMissingFromCacheInfo()               */
    3670             : /************************************************************************/
    3671             : 
    3672       34181 : bool ZarrArray::IsBlockMissingFromCacheInfo(const std::string &osFilename,
    3673             :                                             const uint64_t *blockIndices) const
    3674             : {
    3675       34181 :     CPL_IGNORE_RET_VAL(osFilename);
    3676       68362 :     auto poBlockPresenceArray = OpenBlockPresenceCache(false);
    3677       34181 :     if (poBlockPresenceArray)
    3678             :     {
    3679          72 :         std::vector<GUInt64> anBlockIdx(m_aoDims.size());
    3680          72 :         const std::vector<size_t> anCount(m_aoDims.size(), 1);
    3681          72 :         const std::vector<GInt64> anArrayStep(m_aoDims.size(), 0);
    3682          72 :         const std::vector<GPtrDiff_t> anBufferStride(m_aoDims.size(), 0);
    3683          72 :         const auto eByteDT = GDALExtendedDataType::Create(GDT_UInt8);
    3684         216 :         for (size_t i = 0; i < m_aoDims.size(); ++i)
    3685             :         {
    3686         144 :             anBlockIdx[i] = static_cast<GUInt64>(blockIndices[i]);
    3687             :         }
    3688          72 :         GByte byValue = 0;
    3689         144 :         if (poBlockPresenceArray->Read(
    3690          72 :                 anBlockIdx.data(), anCount.data(), anArrayStep.data(),
    3691         144 :                 anBufferStride.data(), eByteDT, &byValue) &&
    3692          72 :             byValue == 0)
    3693             :         {
    3694          52 :             CPLDebugOnly(ZARR_DEBUG_KEY, "Block %s missing (=nodata)",
    3695             :                          osFilename.c_str());
    3696          52 :             return true;
    3697             :         }
    3698             :     }
    3699       34129 :     return false;
    3700             : }
    3701             : 
    3702             : /************************************************************************/
    3703             : /*                     ZarrArray::GetRawBlockInfo()                     */
    3704             : /************************************************************************/
    3705             : 
    3706          16 : bool ZarrArray::GetRawBlockInfo(const uint64_t *panBlockCoordinates,
    3707             :                                 GDALMDArrayRawBlockInfo &info) const
    3708             : {
    3709          16 :     info.clear();
    3710          33 :     for (size_t i = 0; i < m_anInnerBlockSize.size(); ++i)
    3711             :     {
    3712          20 :         const auto nBlockSize = m_anInnerBlockSize[i];
    3713             :         const auto nBlockCount =
    3714          20 :             cpl::div_round_up(m_aoDims[i]->GetSize(), nBlockSize);
    3715          20 :         if (panBlockCoordinates[i] >= nBlockCount)
    3716             :         {
    3717           3 :             CPLError(CE_Failure, CPLE_AppDefined,
    3718             :                      "GetRawBlockInfo() failed: array %s: "
    3719             :                      "invalid block coordinate (%u) for dimension %u",
    3720           3 :                      GetName().c_str(),
    3721           3 :                      static_cast<unsigned>(panBlockCoordinates[i]),
    3722             :                      static_cast<unsigned>(i));
    3723           3 :             return false;
    3724             :         }
    3725             :     }
    3726             : 
    3727          26 :     std::vector<uint64_t> anOuterBlockIndices;
    3728          30 :     for (size_t i = 0; i < m_anCountInnerBlockInOuter.size(); ++i)
    3729             :     {
    3730          17 :         anOuterBlockIndices.push_back(panBlockCoordinates[i] /
    3731          17 :                                       m_anCountInnerBlockInOuter[i]);
    3732             :     }
    3733             : 
    3734          26 :     std::string osFilename = BuildChunkFilename(anOuterBlockIndices.data());
    3735             : 
    3736             :     // For network file systems, get the streaming version of the filename,
    3737             :     // as we don't need arbitrary seeking in the file
    3738          13 :     osFilename = VSIFileManager::GetHandler(osFilename.c_str())
    3739          13 :                      ->GetStreamingFilename(osFilename);
    3740             :     {
    3741          13 :         std::lock_guard<std::mutex> oLock(m_oMutex);
    3742          13 :         if (IsBlockMissingFromCacheInfo(osFilename, panBlockCoordinates))
    3743           0 :             return true;
    3744             :     }
    3745             : 
    3746          13 :     VSILFILE *fp = nullptr;
    3747             :     // This is the number of files returned in a S3 directory listing operation
    3748          13 :     const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
    3749             :                                            nullptr};
    3750          13 :     const auto nErrorBefore = CPLGetErrorCounter();
    3751             :     {
    3752             :         // Avoid issuing ReadDir() when a lot of files are expected
    3753             :         CPLConfigOptionSetter optionSetter("GDAL_DISABLE_READDIR_ON_OPEN",
    3754          13 :                                            "YES", true);
    3755          13 :         fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
    3756             :     }
    3757          13 :     if (fp == nullptr)
    3758             :     {
    3759           1 :         if (nErrorBefore != CPLGetErrorCounter())
    3760             :         {
    3761           0 :             return false;
    3762             :         }
    3763             :         else
    3764             :         {
    3765             :             // Missing files are OK and indicate nodata_value
    3766           1 :             return true;
    3767             :         }
    3768             :     }
    3769          12 :     VSIFSeekL(fp, 0, SEEK_END);
    3770          12 :     const auto nFileSize = VSIFTellL(fp);
    3771          12 :     VSIFCloseL(fp);
    3772             : 
    3773             :     // For Kerchunk files, get information on the actual location
    3774             :     const CPLStringList aosMetadata(
    3775          12 :         VSIGetFileMetadata(osFilename.c_str(), "CHUNK_INFO", nullptr));
    3776          12 :     if (!aosMetadata.empty())
    3777             :     {
    3778           2 :         const char *pszFilename = aosMetadata.FetchNameValue("FILENAME");
    3779           2 :         if (pszFilename)
    3780           1 :             info.pszFilename = CPLStrdup(pszFilename);
    3781           2 :         info.nOffset = std::strtoull(
    3782             :             aosMetadata.FetchNameValueDef("OFFSET", "0"), nullptr, 10);
    3783           2 :         info.nSize = std::strtoull(aosMetadata.FetchNameValueDef("SIZE", "0"),
    3784             :                                    nullptr, 10);
    3785           2 :         const char *pszBase64 = aosMetadata.FetchNameValue("BASE64");
    3786           2 :         if (pszBase64)
    3787             :         {
    3788           1 :             const size_t nSizeBase64 = strlen(pszBase64) + 1;
    3789           1 :             info.pabyInlineData = static_cast<GByte *>(CPLMalloc(nSizeBase64));
    3790           1 :             memcpy(info.pabyInlineData, pszBase64, nSizeBase64);
    3791             :             const int nDecodedSize =
    3792           1 :                 CPLBase64DecodeInPlace(info.pabyInlineData);
    3793           1 :             CPLAssert(static_cast<size_t>(nDecodedSize) ==
    3794             :                       static_cast<size_t>(info.nSize));
    3795           1 :             CPL_IGNORE_RET_VAL(nDecodedSize);
    3796             :         }
    3797             :     }
    3798             :     else
    3799             :     {
    3800          10 :         info.pszFilename = CPLStrdup(osFilename.c_str());
    3801          10 :         info.nOffset = 0;
    3802          10 :         info.nSize = nFileSize;
    3803             :     }
    3804             : 
    3805          12 :     info.papszInfo = CSLDuplicate(GetRawBlockInfoInfo().List());
    3806             : 
    3807          12 :     return true;
    3808             : }
    3809             : 
    3810             : /************************************************************************/
    3811             : /*                     ZarrArray::GetParentGroup()                      */
    3812             : /************************************************************************/
    3813             : 
    3814          67 : std::shared_ptr<ZarrGroupBase> ZarrArray::GetParentGroup() const
    3815             : {
    3816          67 :     std::shared_ptr<ZarrGroupBase> poGroup = m_poParent.lock();
    3817          67 :     if (!poGroup)
    3818             :     {
    3819          12 :         if (auto poRootGroup = m_poSharedResource->GetRootGroup())
    3820             :         {
    3821           6 :             const auto nPos = m_osFullName.rfind('/');
    3822           6 :             if (nPos == 0)
    3823             :             {
    3824           2 :                 poGroup = std::dynamic_pointer_cast<ZarrGroupBase>(poRootGroup);
    3825             :             }
    3826           4 :             else if (nPos != std::string::npos)
    3827             :             {
    3828           8 :                 poGroup = std::dynamic_pointer_cast<ZarrGroupBase>(
    3829          12 :                     poRootGroup->OpenGroupFromFullname(
    3830          12 :                         m_osFullName.substr(0, nPos)));
    3831             :             }
    3832             :         }
    3833             :     }
    3834          67 :     return poGroup;
    3835             : }
    3836             : 
    3837             : /************************************************************************/
    3838             : /*                    ZarrArray::IsRegularlySpaced()                    */
    3839             : /************************************************************************/
    3840             : 
    3841             : // Process-level LRU cache for coordinate array regularity results.
    3842             : // Keyed by root directory + array full name.
    3843             : // Avoids redundant HTTP reads for immutable cloud-hosted coordinate arrays.
    3844             : // Thread-safe: lru11::Cache with std::mutex handles locking internally.
    3845             : 
    3846             : struct CoordCacheEntry
    3847             : {
    3848             :     bool bIsRegular;
    3849             :     double dfStart;
    3850             :     double dfIncrement;
    3851             : };
    3852             : 
    3853             : static lru11::Cache<std::string, CoordCacheEntry, std::mutex> g_oCoordCache{
    3854             :     128};
    3855             : 
    3856           4 : void ZarrClearCoordinateCache()
    3857             : {
    3858           4 :     g_oCoordCache.clear();
    3859           4 : }
    3860             : 
    3861          98 : bool ZarrArray::IsRegularlySpaced(double &dfStart, double &dfIncrement) const
    3862             : {
    3863             :     // Only cache 1D coordinate arrays (the ones that trigger HTTP reads)
    3864          98 :     if (GetDimensionCount() != 1)
    3865           0 :         return GDALMDArray::IsRegularlySpaced(dfStart, dfIncrement);
    3866             : 
    3867          98 :     const std::string &osKey = GetFilename();
    3868             : 
    3869             :     CoordCacheEntry entry;
    3870          98 :     if (g_oCoordCache.tryGet(osKey, entry))
    3871             :     {
    3872           6 :         CPLDebugOnly("ZARR", "IsRegularlySpaced cache hit for %s",
    3873             :                      osKey.c_str());
    3874           6 :         dfStart = entry.dfStart;
    3875           6 :         dfIncrement = entry.dfIncrement;
    3876           6 :         return entry.bIsRegular;
    3877             :     }
    3878             : 
    3879             :     // Cache miss: perform the full coordinate read
    3880          92 :     const bool bResult = GDALMDArray::IsRegularlySpaced(dfStart, dfIncrement);
    3881             : 
    3882          92 :     g_oCoordCache.insert(osKey, {bResult, dfStart, dfIncrement});
    3883             : 
    3884          92 :     CPLDebugOnly("ZARR", "IsRegularlySpaced cached for %s: %s", osKey.c_str(),
    3885             :                  bResult ? "regular" : "irregular");
    3886             : 
    3887          92 :     return bResult;
    3888             : }

Generated by: LCOV version 1.14