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

Generated by: LCOV version 1.14