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

Generated by: LCOV version 1.14