LCOV - code coverage report
Current view: top level - frmts/zarr - zarrdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1141 1259 90.6 %
Date: 2026-08-21 03:01:58 Functions: 56 58 96.6 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Zarr driver
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "zarr.h"
      14             : #include "zarrdrivercore.h"
      15             : #include "vsikerchunk.h"
      16             : 
      17             : #include "cpl_minixml.h"
      18             : 
      19             : #include "gdalalgorithm.h"
      20             : #include "gdal_frmts.h"
      21             : 
      22             : #include <algorithm>
      23             : #include <cassert>
      24             : #include <cinttypes>
      25             : #include <cmath>
      26             : #include <limits>
      27             : #include <future>
      28             : #include <mutex>
      29             : 
      30             : #ifdef HAVE_BLOSC
      31             : #include <blosc.h>
      32             : #endif
      33             : 
      34             : /************************************************************************/
      35             : /*                            ZarrDataset()                             */
      36             : /************************************************************************/
      37             : 
      38        2144 : ZarrDataset::ZarrDataset(const std::shared_ptr<ZarrGroupBase> &poRootGroup)
      39        2144 :     : m_poRootGroup(poRootGroup)
      40             : {
      41        2144 : }
      42             : 
      43             : /************************************************************************/
      44             : /*                            OpenMultidim()                            */
      45             : /************************************************************************/
      46             : 
      47        1690 : GDALDataset *ZarrDataset::OpenMultidim(const char *pszFilename,
      48             :                                        bool bUpdateMode,
      49             :                                        CSLConstList papszOpenOptionsIn)
      50             : {
      51        3380 :     CPLString osFilename(pszFilename);
      52        1690 :     if (osFilename.back() == '/')
      53           0 :         osFilename.pop_back();
      54             : 
      55        3380 :     auto poSharedResource = ZarrSharedResource::Create(osFilename, bUpdateMode);
      56        1690 :     poSharedResource->SetOpenOptions(papszOpenOptionsIn);
      57             : 
      58        3380 :     auto poRG = poSharedResource->GetRootGroup();
      59        1690 :     if (!poRG)
      60             :     {
      61             :         // Kerchunk Parquet auto-detection: OpenRootGroup found a
      62             :         // .zmetadata with record_size, signaling a redirect.
      63         108 :         const auto &osKerchunkPath = poSharedResource->GetKerchunkParquetPath();
      64         108 :         if (!osKerchunkPath.empty())
      65           5 :             return OpenMultidim(osKerchunkPath.c_str(), bUpdateMode,
      66           5 :                                 papszOpenOptionsIn);
      67         103 :         return nullptr;
      68             :     }
      69        1582 :     return new ZarrDataset(poRG);
      70             : }
      71             : 
      72             : /************************************************************************/
      73             : /*                            ExploreGroup()                            */
      74             : /************************************************************************/
      75             : 
      76         159 : static bool ExploreGroup(const std::shared_ptr<GDALGroup> &poGroup,
      77             :                          std::vector<std::string> &aosArrays, int nRecCount)
      78             : {
      79         159 :     if (nRecCount == 32)
      80             :     {
      81           0 :         CPLError(CE_Failure, CPLE_NotSupported,
      82             :                  "Too deep recursion level in ExploreGroup()");
      83           0 :         return false;
      84             :     }
      85         318 :     const auto aosGroupArrayNames = poGroup->GetMDArrayNames();
      86         405 :     for (const auto &osArrayName : aosGroupArrayNames)
      87             :     {
      88         246 :         std::string osArrayFullname = poGroup->GetFullName();
      89         246 :         if (osArrayName != "/")
      90             :         {
      91         246 :             if (osArrayFullname != "/")
      92          12 :                 osArrayFullname += '/';
      93         246 :             osArrayFullname += osArrayName;
      94             :         }
      95         246 :         aosArrays.emplace_back(std::move(osArrayFullname));
      96         246 :         if (aosArrays.size() == 10000)
      97             :         {
      98           0 :             CPLError(CE_Failure, CPLE_NotSupported,
      99             :                      "Too many arrays found by ExploreGroup()");
     100           0 :             return false;
     101             :         }
     102             :     }
     103             : 
     104         318 :     const auto aosSubGroups = poGroup->GetGroupNames();
     105         177 :     for (const auto &osSubGroup : aosSubGroups)
     106             :     {
     107          18 :         const auto poSubGroup = poGroup->OpenGroup(osSubGroup);
     108          18 :         if (poSubGroup)
     109             :         {
     110          18 :             if (!ExploreGroup(poSubGroup, aosArrays, nRecCount + 1))
     111           0 :                 return false;
     112             :         }
     113             :     }
     114         159 :     return true;
     115             : }
     116             : 
     117             : /************************************************************************/
     118             : /*                          GetMetadataItem()                           */
     119             : /************************************************************************/
     120             : 
     121          54 : const char *ZarrDataset::GetMetadataItem(const char *pszName,
     122             :                                          const char *pszDomain)
     123             : {
     124          54 :     if (pszDomain != nullptr && EQUAL(pszDomain, GDAL_MDD_SUBDATASETS))
     125           0 :         return m_aosSubdatasets.FetchNameValue(pszName);
     126          54 :     if (pszDomain != nullptr && EQUAL(pszDomain, GDAL_MDD_IMAGE_STRUCTURE))
     127          39 :         return GDALDataset::GetMetadataItem(pszName, pszDomain);
     128          15 :     return nullptr;
     129             : }
     130             : 
     131             : /************************************************************************/
     132             : /*                            GetMetadata()                             */
     133             : /************************************************************************/
     134             : 
     135          24 : CSLConstList ZarrDataset::GetMetadata(const char *pszDomain)
     136             : {
     137          24 :     if (pszDomain != nullptr && EQUAL(pszDomain, GDAL_MDD_SUBDATASETS))
     138          11 :         return m_aosSubdatasets.List();
     139          13 :     if (pszDomain != nullptr && EQUAL(pszDomain, GDAL_MDD_IMAGE_STRUCTURE))
     140           0 :         return GDALDataset::GetMetadata(pszDomain);
     141          13 :     return nullptr;
     142             : }
     143             : 
     144             : /************************************************************************/
     145             : /*                          IBuildOverviews()                           */
     146             : /************************************************************************/
     147             : 
     148           2 : CPLErr ZarrDataset::IBuildOverviews(const char *pszResampling, int nOverviews,
     149             :                                     const int *panOverviewList,
     150             :                                     int /* nListBands */,
     151             :                                     const int * /*panBandList*/,
     152             :                                     GDALProgressFunc pfnProgress,
     153             :                                     void *pProgressData,
     154             :                                     CSLConstList papszOptions)
     155             : {
     156           6 :     for (int i = 0; i < nBands; ++i)
     157             :     {
     158           4 :         auto poBand = cpl::down_cast<ZarrRasterBand *>(papoBands[i]);
     159           4 :         for (auto &[_, value] : poBand->m_oMapOverview)
     160             :         {
     161           0 :             poBand->m_aoOverviewOld.push_back(std::move(value));
     162             :         }
     163           4 :         poBand->m_oMapOverview.clear();
     164             :     }
     165             : 
     166           2 :     if (m_poSingleArray)
     167             :     {
     168           4 :         return m_poSingleArray->BuildOverviews(pszResampling, nOverviews,
     169             :                                                panOverviewList, pfnProgress,
     170           2 :                                                pProgressData, papszOptions);
     171             :     }
     172             :     else
     173             :     {
     174           0 :         CPLErr eErr = CE_None;
     175           0 :         for (int i = 0; i < nBands && eErr == CE_None; ++i)
     176             :         {
     177             :             std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
     178             :                 pScaledProgress(
     179           0 :                     GDALCreateScaledProgress(static_cast<double>(i) /
     180           0 :                                                  static_cast<double>(nBands),
     181           0 :                                              static_cast<double>(i + 1) /
     182           0 :                                                  static_cast<double>(nBands),
     183             :                                              pfnProgress, pProgressData),
     184           0 :                     GDALDestroyScaledProgress);
     185           0 :             eErr = cpl::down_cast<ZarrRasterBand *>(papoBands[i])
     186           0 :                        ->m_poArray->BuildOverviews(
     187             :                            pszResampling, nOverviews, panOverviewList,
     188           0 :                            pScaledProgress ? GDALScaledProgress : nullptr,
     189           0 :                            pScaledProgress.get(), papszOptions);
     190             :         }
     191           0 :         return eErr;
     192             :     }
     193             : }
     194             : 
     195             : /************************************************************************/
     196             : /*                       GetXYDimensionIndices()                        */
     197             : /************************************************************************/
     198             : 
     199         169 : static void GetXYDimensionIndices(const std::shared_ptr<GDALMDArray> &poArray,
     200             :                                   const GDALOpenInfo *poOpenInfo, size_t &iXDim,
     201             :                                   size_t &iYDim)
     202             : {
     203         169 :     const size_t nDims = poArray->GetDimensionCount();
     204         169 :     iYDim = nDims >= 2 ? nDims - 2 : 0;
     205         169 :     iXDim = nDims >= 2 ? nDims - 1 : 0;
     206             : 
     207         169 :     if (nDims >= 2)
     208             :     {
     209             :         const char *pszDimX =
     210         152 :             CSLFetchNameValue(poOpenInfo->papszOpenOptions, "DIM_X");
     211             :         const char *pszDimY =
     212         152 :             CSLFetchNameValue(poOpenInfo->papszOpenOptions, "DIM_Y");
     213         152 :         bool bFoundX = false;
     214         152 :         bool bFoundY = false;
     215         152 :         const auto &apoDims = poArray->GetDimensions();
     216         522 :         for (size_t i = 0; i < nDims; ++i)
     217             :         {
     218         370 :             if (pszDimX && apoDims[i]->GetName() == pszDimX)
     219             :             {
     220           1 :                 bFoundX = true;
     221           1 :                 iXDim = i;
     222             :             }
     223         369 :             else if (pszDimY && apoDims[i]->GetName() == pszDimY)
     224             :             {
     225           1 :                 bFoundY = true;
     226           1 :                 iYDim = i;
     227             :             }
     228        1082 :             else if (!pszDimX &&
     229         714 :                      (apoDims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X ||
     230         356 :                       apoDims[i]->GetName() == "X"))
     231          85 :                 iXDim = i;
     232         827 :             else if (!pszDimY &&
     233         544 :                      (apoDims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y ||
     234         271 :                       apoDims[i]->GetName() == "Y"))
     235          85 :                 iYDim = i;
     236             :         }
     237         152 :         if (pszDimX)
     238             :         {
     239           4 :             if (!bFoundX && CPLGetValueType(pszDimX) == CPL_VALUE_INTEGER)
     240             :             {
     241           2 :                 const int nTmp = atoi(pszDimX);
     242           2 :                 if (nTmp >= 0 && nTmp <= static_cast<int>(nDims))
     243             :                 {
     244           2 :                     iXDim = nTmp;
     245           2 :                     bFoundX = true;
     246             :                 }
     247             :             }
     248           4 :             if (!bFoundX)
     249             :             {
     250           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
     251             :                          "Cannot find dimension DIM_X=%s", pszDimX);
     252             :             }
     253             :         }
     254         152 :         if (pszDimY)
     255             :         {
     256           4 :             if (!bFoundY && CPLGetValueType(pszDimY) == CPL_VALUE_INTEGER)
     257             :             {
     258           2 :                 const int nTmp = atoi(pszDimY);
     259           2 :                 if (nTmp >= 0 && nTmp <= static_cast<int>(nDims))
     260             :                 {
     261           2 :                     iYDim = nTmp;
     262           2 :                     bFoundY = true;
     263             :                 }
     264             :             }
     265           4 :             if (!bFoundY)
     266             :             {
     267           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
     268             :                          "Cannot find dimension DIM_Y=%s", pszDimY);
     269             :             }
     270             :         }
     271             :     }
     272         169 : }
     273             : 
     274             : /************************************************************************/
     275             : /*                       GetExtraDimSampleCount()                       */
     276             : /************************************************************************/
     277             : 
     278             : static uint64_t
     279          48 : GetExtraDimSampleCount(const std::shared_ptr<GDALMDArray> &poArray,
     280             :                        size_t iXDim, size_t iYDim)
     281             : {
     282          48 :     uint64_t nExtraDimSamples = 1;
     283          48 :     const auto &apoDims = poArray->GetDimensions();
     284         194 :     for (size_t i = 0; i < apoDims.size(); ++i)
     285             :     {
     286         146 :         if (i != iXDim && i != iYDim)
     287          52 :             nExtraDimSamples *= apoDims[i]->GetSize();
     288             :     }
     289          48 :     return nExtraDimSamples;
     290             : }
     291             : 
     292             : /************************************************************************/
     293             : /*                        PrefetchCoordArrays()                         */
     294             : /************************************************************************/
     295             : 
     296             : // Warm g_oCoordCache by reading X and Y coordinate arrays in parallel.
     297             : // For remote datasets this avoids sequential HTTP round-trips in
     298             : // GuessGeoTransform() (can save ~800 ms).  Each ZarrArray has its own
     299             : // mutex and VSI opens independent handles, so sibling reads are safe.
     300         153 : static void PrefetchCoordArrays(const std::shared_ptr<GDALMDArray> &poArray,
     301             :                                 size_t iXDim, size_t iYDim)
     302             : {
     303         153 :     const auto nDimCount = poArray->GetDimensionCount();
     304         153 :     if (nDimCount < 2 || iXDim >= nDimCount || iYDim >= nDimCount)
     305         153 :         return;
     306         135 :     const auto &dims = poArray->GetDimensions();
     307         135 :     auto poVarX = dims[iXDim]->GetIndexingVariable();
     308         135 :     auto poVarY = dims[iYDim]->GetIndexingVariable();
     309         183 :     if (!poVarX || poVarX->GetDimensionCount() != 1 || !poVarY ||
     310          48 :         poVarY->GetDimensionCount() != 1)
     311          87 :         return;
     312          48 :     if (VSIIsLocal(poVarX->GetFilename().c_str()))
     313          48 :         return;
     314             : 
     315           0 :     double dfXStart = 0, dfXSpacing = 0, dfYStart = 0, dfYSpacing = 0;
     316             :     auto futureX =
     317           0 :         std::async(std::launch::async, [&poVarX, &dfXStart, &dfXSpacing]()
     318           0 :                    { return poVarX->IsRegularlySpaced(dfXStart, dfXSpacing); });
     319           0 :     CPL_IGNORE_RET_VAL(poVarY->IsRegularlySpaced(dfYStart, dfYSpacing));
     320           0 :     CPL_IGNORE_RET_VAL(futureX.get());
     321             : }
     322             : 
     323             : /************************************************************************/
     324             : /*                                Open()                                */
     325             : /************************************************************************/
     326             : 
     327        1704 : GDALDataset *ZarrDataset::Open(GDALOpenInfo *poOpenInfo)
     328             : {
     329        1704 :     if (!ZARRDriverIdentify(poOpenInfo))
     330             :     {
     331           0 :         return nullptr;
     332             :     }
     333             : 
     334             :     // Used by gdal_translate kerchunk_ref.json kerchunk_parq.parq -of ZARR -co CONVERT_TO_KERCHUNK_PARQUET_REFERENCE=YES
     335        1704 :     if (STARTS_WITH(poOpenInfo->pszFilename, "ZARR_DUMMY:"))
     336             :     {
     337             :         class ZarrDummyDataset final : public GDALDataset
     338             :         {
     339             :           public:
     340           1 :             ZarrDummyDataset()
     341           1 :             {
     342           1 :                 nRasterXSize = 0;
     343           1 :                 nRasterYSize = 0;
     344           1 :             }
     345             :         };
     346             : 
     347           2 :         auto poDS = std::make_unique<ZarrDummyDataset>();
     348           1 :         poDS->SetDescription(poOpenInfo->pszFilename + strlen("ZARR_DUMMY:"));
     349           1 :         return poDS.release();
     350             :     }
     351             : 
     352        1703 :     const bool bKerchunkCached = CPLFetchBool(poOpenInfo->papszOpenOptions,
     353             :                                               "CACHE_KERCHUNK_JSON", false);
     354             : 
     355        1703 :     if (ZARRIsLikelyKerchunkJSONRef(poOpenInfo))
     356             :     {
     357          38 :         GDALOpenInfo oOpenInfo(std::string("ZARR:\"")
     358             :                                    .append(bKerchunkCached
     359             :                                                ? JSON_REF_CACHED_FS_PREFIX
     360          19 :                                                : JSON_REF_FS_PREFIX)
     361          19 :                                    .append("{")
     362          19 :                                    .append(poOpenInfo->pszFilename)
     363          19 :                                    .append("}\"")
     364             :                                    .c_str(),
     365          38 :                                GA_ReadOnly);
     366          19 :         oOpenInfo.nOpenFlags = poOpenInfo->nOpenFlags;
     367          19 :         oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
     368          19 :         return Open(&oOpenInfo);
     369             :     }
     370        1684 :     else if (STARTS_WITH(poOpenInfo->pszFilename, JSON_REF_FS_PREFIX) ||
     371        1682 :              STARTS_WITH(poOpenInfo->pszFilename, JSON_REF_CACHED_FS_PREFIX))
     372             :     {
     373             :         GDALOpenInfo oOpenInfo(
     374           4 :             std::string("ZARR:").append(poOpenInfo->pszFilename).c_str(),
     375           4 :             GA_ReadOnly);
     376           2 :         oOpenInfo.nOpenFlags = poOpenInfo->nOpenFlags;
     377           2 :         oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
     378           2 :         return Open(&oOpenInfo);
     379             :     }
     380             : 
     381        3364 :     CPLString osFilename(poOpenInfo->pszFilename);
     382        1682 :     if (!poOpenInfo->bIsDirectory)
     383             :     {
     384         159 :         osFilename = CPLGetPathSafe(osFilename);
     385             :     }
     386        3364 :     CPLString osArrayOfInterest;
     387        3364 :     std::vector<uint64_t> anExtraDimIndices;
     388        1682 :     if (STARTS_WITH(poOpenInfo->pszFilename, "ZARR:"))
     389             :     {
     390             :         const CPLStringList aosTokens(CSLTokenizeString2(
     391         114 :             poOpenInfo->pszFilename, ":", CSLT_HONOURSTRINGS));
     392         114 :         if (aosTokens.size() < 2)
     393           0 :             return nullptr;
     394         114 :         osFilename = aosTokens[1];
     395             : 
     396         114 :         if (!cpl::starts_with(osFilename, JSON_REF_FS_PREFIX) &&
     397         317 :             !cpl::starts_with(osFilename, JSON_REF_CACHED_FS_PREFIX) &&
     398         203 :             CPLGetExtensionSafe(osFilename) == "json")
     399             :         {
     400             :             VSIStatBufL sStat;
     401           4 :             if (VSIStatL(osFilename.c_str(), &sStat) == 0 &&
     402           2 :                 !VSI_ISDIR(sStat.st_mode))
     403             :             {
     404             :                 osFilename =
     405           4 :                     std::string(bKerchunkCached ? JSON_REF_CACHED_FS_PREFIX
     406             :                                                 : JSON_REF_FS_PREFIX)
     407           2 :                         .append("{")
     408           2 :                         .append(osFilename)
     409           2 :                         .append("}");
     410             :             }
     411             :         }
     412             : 
     413         114 :         std::string osErrorMsg;
     414         114 :         if (osFilename == "http" || osFilename == "https")
     415             :         {
     416             :             osErrorMsg = "There is likely a quoting error of the whole "
     417             :                          "connection string, and the filename should "
     418           1 :                          "likely be prefixed with /vsicurl/";
     419             :         }
     420         226 :         else if (osFilename == "/vsicurl/http" ||
     421         113 :                  osFilename == "/vsicurl/https")
     422             :         {
     423             :             osErrorMsg = "There is likely a quoting error of the whole "
     424           1 :                          "connection string.";
     425             :         }
     426         224 :         else if (STARTS_WITH(osFilename.c_str(), "http://") ||
     427         112 :                  STARTS_WITH(osFilename.c_str(), "https://"))
     428             :         {
     429             :             osErrorMsg =
     430           1 :                 "The filename should likely be prefixed with /vsicurl/";
     431             :         }
     432         114 :         if (!osErrorMsg.empty())
     433             :         {
     434           3 :             CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrorMsg.c_str());
     435           3 :             return nullptr;
     436             :         }
     437         111 :         if (aosTokens.size() >= 3)
     438             :         {
     439          23 :             osArrayOfInterest = aosTokens[2];
     440          41 :             for (int i = 3; i < aosTokens.size(); ++i)
     441             :             {
     442          18 :                 anExtraDimIndices.push_back(
     443          18 :                     static_cast<uint64_t>(CPLAtoGIntBig(aosTokens[i])));
     444             :             }
     445             :         }
     446             :     }
     447             : 
     448             :     auto poDSMultiDim = std::unique_ptr<GDALDataset>(
     449        1679 :         OpenMultidim(osFilename.c_str(), poOpenInfo->eAccess == GA_Update,
     450        3358 :                      poOpenInfo->papszOpenOptions));
     451        3255 :     if (poDSMultiDim == nullptr ||
     452        1576 :         (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER) != 0)
     453             :     {
     454        1515 :         return poDSMultiDim.release();
     455             :     }
     456             : 
     457         328 :     auto poRG = poDSMultiDim->GetRootGroup();
     458             : 
     459         328 :     auto poDS = std::make_unique<ZarrDataset>(nullptr);
     460         164 :     std::shared_ptr<GDALMDArray> poMainArray;
     461         328 :     std::vector<std::string> aosArrays;
     462         328 :     std::string osMainArray;
     463         164 :     const bool bMultiband = CPLTestBool(
     464         164 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MULTIBAND", "YES"));
     465         164 :     size_t iXDim = 0;
     466         164 :     size_t iYDim = 0;
     467             : 
     468         164 :     if (!osArrayOfInterest.empty())
     469             :     {
     470          23 :         poMainArray = osArrayOfInterest == "/"
     471          46 :                           ? poRG->OpenMDArray("/")
     472          23 :                           : poRG->OpenMDArrayFromFullname(osArrayOfInterest);
     473          23 :         if (poMainArray == nullptr)
     474           1 :             return nullptr;
     475          22 :         GetXYDimensionIndices(poMainArray, poOpenInfo, iXDim, iYDim);
     476             : 
     477          22 :         if (poMainArray->GetDimensionCount() > 2)
     478             :         {
     479          11 :             if (anExtraDimIndices.empty())
     480             :             {
     481             :                 const uint64_t nExtraDimSamples =
     482           1 :                     GetExtraDimSampleCount(poMainArray, iXDim, iYDim);
     483           1 :                 if (bMultiband)
     484             :                 {
     485           0 :                     if (nExtraDimSamples > 65536)  // arbitrary limit
     486             :                     {
     487           0 :                         if (poMainArray->GetDimensionCount() == 3)
     488             :                         {
     489           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
     490             :                                      "Too many samples along the > 2D "
     491             :                                      "dimensions of %s. "
     492             :                                      "Use ZARR:\"%s\":%s:{i} syntax",
     493             :                                      osArrayOfInterest.c_str(),
     494             :                                      osFilename.c_str(),
     495             :                                      osArrayOfInterest.c_str());
     496             :                         }
     497             :                         else
     498             :                         {
     499           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
     500             :                                      "Too many samples along the > 2D "
     501             :                                      "dimensions of %s. "
     502             :                                      "Use ZARR:\"%s\":%s:{i}:{j} syntax",
     503             :                                      osArrayOfInterest.c_str(),
     504             :                                      osFilename.c_str(),
     505             :                                      osArrayOfInterest.c_str());
     506             :                         }
     507           0 :                         return nullptr;
     508             :                     }
     509             :                 }
     510           1 :                 else if (nExtraDimSamples != 1)
     511             :                 {
     512           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     513             :                              "Indices of extra dimensions must be specified");
     514           1 :                     return nullptr;
     515             :                 }
     516             :             }
     517          10 :             else if (anExtraDimIndices.size() !=
     518          10 :                      poMainArray->GetDimensionCount() - 2)
     519             :             {
     520           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     521             :                          "Wrong number of indices of extra dimensions");
     522           1 :                 return nullptr;
     523             :             }
     524             :             else
     525             :             {
     526          23 :                 for (const auto idx : anExtraDimIndices)
     527             :                 {
     528          15 :                     poMainArray = poMainArray->at(idx);
     529          15 :                     if (poMainArray == nullptr)
     530           1 :                         return nullptr;
     531             :                 }
     532           8 :                 GetXYDimensionIndices(poMainArray, poOpenInfo, iXDim, iYDim);
     533             :             }
     534             :         }
     535          11 :         else if (!anExtraDimIndices.empty())
     536             :         {
     537           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Unexpected extra indices");
     538           1 :             return nullptr;
     539             :         }
     540             :     }
     541             :     else
     542             :     {
     543         141 :         ExploreGroup(poRG, aosArrays, 0);
     544         141 :         if (aosArrays.empty())
     545           0 :             return nullptr;
     546             : 
     547         141 :         const bool bListAllArrays = CPLTestBool(CSLFetchNameValueDef(
     548         141 :             poOpenInfo->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
     549             : 
     550         141 :         if (!bListAllArrays)
     551             :         {
     552         139 :             if (aosArrays.size() == 1)
     553             :             {
     554          85 :                 poMainArray = poRG->OpenMDArrayFromFullname(aosArrays[0]);
     555          85 :                 if (poMainArray)
     556          85 :                     osMainArray = poMainArray->GetFullName();
     557             :             }
     558             :             else  // at least 2 arrays
     559             :             {
     560         210 :                 for (const auto &osArrayName : aosArrays)
     561             :                 {
     562         156 :                     auto poArray = poRG->OpenMDArrayFromFullname(osArrayName);
     563         215 :                     if (poArray && poArray->GetDimensionCount() >= 2 &&
     564          59 :                         osArrayName.find("/ovr_") == std::string::npos)
     565             :                     {
     566          54 :                         if (osMainArray.empty())
     567             :                         {
     568          54 :                             poMainArray = std::move(poArray);
     569          54 :                             osMainArray = osArrayName;
     570             :                         }
     571             :                         else
     572             :                         {
     573           0 :                             poMainArray.reset();
     574           0 :                             osMainArray.clear();
     575           0 :                             break;
     576             :                         }
     577             :                     }
     578             :                 }
     579             :             }
     580             : 
     581         139 :             if (poMainArray)
     582         139 :                 GetXYDimensionIndices(poMainArray, poOpenInfo, iXDim, iYDim);
     583             :         }
     584             : 
     585         141 :         int iCountSubDS = 1;
     586             : 
     587         141 :         if (poMainArray && poMainArray->GetDimensionCount() > 2)
     588             :         {
     589          47 :             const auto &apoDims = poMainArray->GetDimensions();
     590             :             const uint64_t nExtraDimSamples =
     591          47 :                 GetExtraDimSampleCount(poMainArray, iXDim, iYDim);
     592          47 :             if (nExtraDimSamples > 65536)  // arbitrary limit
     593             :             {
     594           3 :                 if (apoDims.size() == 3)
     595             :                 {
     596           2 :                     CPLError(
     597             :                         CE_Warning, CPLE_AppDefined,
     598             :                         "Too many samples along the > 2D dimensions of %s. "
     599             :                         "Use ZARR:\"%s\":%s:{i} syntax",
     600             :                         osMainArray.c_str(), osFilename.c_str(),
     601             :                         osMainArray.c_str());
     602             :                 }
     603             :                 else
     604             :                 {
     605           1 :                     CPLError(
     606             :                         CE_Warning, CPLE_AppDefined,
     607             :                         "Too many samples along the > 2D dimensions of %s. "
     608             :                         "Use ZARR:\"%s\":%s:{i}:{j} syntax",
     609             :                         osMainArray.c_str(), osFilename.c_str(),
     610             :                         osMainArray.c_str());
     611             :                 }
     612             :             }
     613          44 :             else if (nExtraDimSamples > 1 && bMultiband)
     614             :             {
     615             :                 // nothing to do
     616             :             }
     617           2 :             else if (nExtraDimSamples > 1 && apoDims.size() == 3)
     618             :             {
     619           3 :                 for (int i = 0; i < static_cast<int>(nExtraDimSamples); ++i)
     620             :                 {
     621           2 :                     poDS->m_aosSubdatasets.AddString(CPLSPrintf(
     622             :                         "SUBDATASET_%d_NAME=ZARR:\"%s\":%s:%d", iCountSubDS,
     623           2 :                         osFilename.c_str(), osMainArray.c_str(), i));
     624           2 :                     poDS->m_aosSubdatasets.AddString(CPLSPrintf(
     625             :                         "SUBDATASET_%d_DESC=Array %s at index %d of %s",
     626             :                         iCountSubDS, osMainArray.c_str(), i,
     627           2 :                         apoDims[0]->GetName().c_str()));
     628           2 :                     ++iCountSubDS;
     629             :                 }
     630             :             }
     631           1 :             else if (nExtraDimSamples > 1)
     632             :             {
     633           1 :                 int nDimIdxI = 0;
     634           1 :                 int nDimIdxJ = 0;
     635           7 :                 for (int i = 0; i < static_cast<int>(nExtraDimSamples); ++i)
     636             :                 {
     637           6 :                     poDS->m_aosSubdatasets.AddString(
     638             :                         CPLSPrintf("SUBDATASET_%d_NAME=ZARR:\"%s\":%s:%d:%d",
     639             :                                    iCountSubDS, osFilename.c_str(),
     640           6 :                                    osMainArray.c_str(), nDimIdxI, nDimIdxJ));
     641           6 :                     poDS->m_aosSubdatasets.AddString(
     642             :                         CPLSPrintf("SUBDATASET_%d_DESC=Array %s at "
     643             :                                    "index %d of %s and %d of %s",
     644             :                                    iCountSubDS, osMainArray.c_str(), nDimIdxI,
     645           6 :                                    apoDims[0]->GetName().c_str(), nDimIdxJ,
     646          12 :                                    apoDims[1]->GetName().c_str()));
     647           6 :                     ++iCountSubDS;
     648           6 :                     ++nDimIdxJ;
     649           6 :                     if (nDimIdxJ == static_cast<int>(apoDims[1]->GetSize()))
     650             :                     {
     651           3 :                         nDimIdxJ = 0;
     652           3 :                         ++nDimIdxI;
     653             :                     }
     654             :                 }
     655             :             }
     656             :         }
     657             : 
     658         141 :         if (bListAllArrays || aosArrays.size() >= 2)
     659             :         {
     660         217 :             for (size_t i = 0; i < aosArrays.size(); ++i)
     661             :             {
     662         322 :                 auto poArray = poRG->OpenMDArrayFromFullname(aosArrays[i]);
     663         161 :                 if (poArray && (bListAllArrays || aosArrays[i].find("/ovr_") ==
     664         161 :                                                       std::string::npos))
     665             :                 {
     666         156 :                     bool bAddSubDS = false;
     667         156 :                     if (bListAllArrays)
     668             :                     {
     669           5 :                         bAddSubDS = true;
     670             :                     }
     671         151 :                     else if (poArray->GetDimensionCount() >= 2)
     672             :                     {
     673          54 :                         bAddSubDS = true;
     674             :                     }
     675         156 :                     if (bAddSubDS)
     676             :                     {
     677         118 :                         std::string osDim;
     678          59 :                         const auto &apoDims = poArray->GetDimensions();
     679         196 :                         for (const auto &poDim : apoDims)
     680             :                         {
     681         137 :                             if (!osDim.empty())
     682          78 :                                 osDim += "x";
     683             :                             osDim += CPLSPrintf(
     684             :                                 "%" PRIu64,
     685         137 :                                 static_cast<uint64_t>(poDim->GetSize()));
     686             :                         }
     687             : 
     688          59 :                         std::string osDataType;
     689          59 :                         if (poArray->GetDataType().GetClass() == GEDTC_STRING)
     690             :                         {
     691           0 :                             osDataType = "string type";
     692             :                         }
     693          59 :                         else if (poArray->GetDataType().GetClass() ==
     694             :                                  GEDTC_NUMERIC)
     695             :                         {
     696             :                             osDataType = GDALGetDataTypeName(
     697          59 :                                 poArray->GetDataType().GetNumericDataType());
     698             :                         }
     699             :                         else
     700             :                         {
     701           0 :                             osDataType = "compound type";
     702             :                         }
     703             : 
     704          59 :                         poDS->m_aosSubdatasets.AddString(CPLSPrintf(
     705             :                             "SUBDATASET_%d_NAME=ZARR:\"%s\":%s", iCountSubDS,
     706          59 :                             osFilename.c_str(), aosArrays[i].c_str()));
     707          59 :                         poDS->m_aosSubdatasets.AddString(CPLSPrintf(
     708             :                             "SUBDATASET_%d_DESC=[%s] %s (%s)", iCountSubDS,
     709          59 :                             osDim.c_str(), aosArrays[i].c_str(),
     710         118 :                             osDataType.c_str()));
     711          59 :                         ++iCountSubDS;
     712             :                     }
     713             :                 }
     714             :             }
     715             :         }
     716             :     }
     717             : 
     718         159 :     if (poMainArray && (bMultiband || poMainArray->GetDimensionCount() <= 2))
     719             :     {
     720         153 :         PrefetchCoordArrays(poMainArray, iXDim, iYDim);
     721             : 
     722             :         // Pass papszOpenOptions for LOAD_EXTRA_DIM_METADATA_DELAY
     723             :         auto poNewDS =
     724         153 :             std::unique_ptr<GDALDataset>(poMainArray->AsClassicDataset(
     725         306 :                 iXDim, iYDim, poRG, poOpenInfo->papszOpenOptions));
     726         153 :         if (!poNewDS)
     727           3 :             return nullptr;
     728             : 
     729         150 :         if (poMainArray->GetDimensionCount() >= 2)
     730             :         {
     731             :             // If we have 3 arrays, check that the 2 ones that are not the main
     732             :             // 2D array are indexing variables of its dimensions. If so, don't
     733             :             // expose them as subdatasets
     734         134 :             if (aosArrays.size() == 3)
     735             :             {
     736          88 :                 std::vector<std::string> aosOtherArrays;
     737         176 :                 for (size_t i = 0; i < aosArrays.size(); ++i)
     738             :                 {
     739         132 :                     if (aosArrays[i] != osMainArray)
     740             :                     {
     741          88 :                         aosOtherArrays.emplace_back(aosArrays[i]);
     742             :                     }
     743             :                 }
     744          44 :                 bool bMatchFound[] = {false, false};
     745         132 :                 for (int i = 0; i < 2; i++)
     746             :                 {
     747             :                     auto poIndexingVar =
     748          88 :                         poMainArray->GetDimensions()[i == 0 ? iXDim : iYDim]
     749         176 :                             ->GetIndexingVariable();
     750          88 :                     if (poIndexingVar)
     751             :                     {
     752         132 :                         for (int j = 0; j < 2; j++)
     753             :                         {
     754         130 :                             if (aosOtherArrays[j] ==
     755         130 :                                 poIndexingVar->GetFullName())
     756             :                             {
     757          84 :                                 bMatchFound[i] = true;
     758          84 :                                 break;
     759             :                             }
     760             :                         }
     761             :                     }
     762             :                 }
     763          44 :                 if (bMatchFound[0] && bMatchFound[1])
     764             :                 {
     765          42 :                     poDS->m_aosSubdatasets.Clear();
     766             :                 }
     767             :             }
     768             :         }
     769         150 :         if (!poDS->m_aosSubdatasets.empty())
     770             :         {
     771          24 :             poNewDS->SetMetadata(poDS->m_aosSubdatasets.List(),
     772          12 :                                  GDAL_MDD_SUBDATASETS);
     773             :         }
     774         150 :         return poNewDS.release();
     775             :     }
     776             : 
     777           6 :     return poDS.release();
     778             : }
     779             : 
     780             : /************************************************************************/
     781             : /*                         ZarrDatasetDelete()                          */
     782             : /************************************************************************/
     783             : 
     784           5 : static CPLErr ZarrDatasetDelete(const char *pszFilename)
     785             : {
     786           5 :     if (STARTS_WITH(pszFilename, "ZARR:"))
     787             :     {
     788           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     789             :                  "Delete() only supported on ZARR connection names "
     790             :                  "not starting with the ZARR: prefix");
     791           0 :         return CE_Failure;
     792             :     }
     793           5 :     return VSIRmdirRecursive(pszFilename) == 0 ? CE_None : CE_Failure;
     794             : }
     795             : 
     796             : /************************************************************************/
     797             : /*                         ZarrDatasetRename()                          */
     798             : /************************************************************************/
     799             : 
     800           2 : static CPLErr ZarrDatasetRename(const char *pszNewName, const char *pszOldName)
     801             : {
     802           2 :     if (STARTS_WITH(pszNewName, "ZARR:") || STARTS_WITH(pszOldName, "ZARR:"))
     803             :     {
     804           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     805             :                  "Rename() only supported on ZARR connection names "
     806             :                  "not starting with the ZARR: prefix");
     807           0 :         return CE_Failure;
     808             :     }
     809           2 :     return VSIRename(pszOldName, pszNewName) == 0 ? CE_None : CE_Failure;
     810             : }
     811             : 
     812             : /************************************************************************/
     813             : /*                        ZarrDatasetCopyFiles()                        */
     814             : /************************************************************************/
     815             : 
     816           2 : static CPLErr ZarrDatasetCopyFiles(const char *pszNewName,
     817             :                                    const char *pszOldName)
     818             : {
     819           2 :     if (STARTS_WITH(pszNewName, "ZARR:") || STARTS_WITH(pszOldName, "ZARR:"))
     820             :     {
     821           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     822             :                  "CopyFiles() only supported on ZARR connection names "
     823             :                  "not starting with the ZARR: prefix");
     824           0 :         return CE_Failure;
     825             :     }
     826             :     // VSISync() returns true in case of success
     827           4 :     return VSISync((std::string(pszOldName) + '/').c_str(), pszNewName, nullptr,
     828             :                    nullptr, nullptr, nullptr)
     829           2 :                ? CE_None
     830           2 :                : CE_Failure;
     831             : }
     832             : 
     833             : /************************************************************************/
     834             : /*                       ZarrDriverClearCaches()                        */
     835             : /************************************************************************/
     836             : 
     837        1006 : static void ZarrDriverClearCaches(GDALDriver *)
     838             : {
     839        1006 :     ZarrClearCoordinateCache();
     840        1006 :     ZarrClearShardIndexCache();
     841        1006 : }
     842             : 
     843             : /************************************************************************/
     844             : /*                             ZarrDriver()                             */
     845             : /************************************************************************/
     846             : 
     847             : class ZarrDriver final : public GDALDriver
     848             : {
     849             :     std::recursive_mutex m_oMutex{};
     850             :     bool m_bMetadataInitialized = false;
     851             :     void InitMetadata();
     852             : 
     853             :   public:
     854             :     const char *GetMetadataItem(const char *pszName,
     855             :                                 const char *pszDomain) override;
     856             : 
     857         446 :     CSLConstList GetMetadata(const char *pszDomain) override
     858             :     {
     859         892 :         std::lock_guard oLock(m_oMutex);
     860         446 :         InitMetadata();
     861         892 :         return GDALDriver::GetMetadata(pszDomain);
     862             :     }
     863             : };
     864             : 
     865       32515 : const char *ZarrDriver::GetMetadataItem(const char *pszName,
     866             :                                         const char *pszDomain)
     867             : {
     868       65030 :     std::lock_guard oLock(m_oMutex);
     869       32515 :     if (EQUAL(pszName, "COMPRESSORS") || EQUAL(pszName, "BLOSC_COMPRESSORS") ||
     870       32478 :         EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST) ||
     871       32101 :         EQUAL(pszName, GDAL_DMD_MULTIDIM_ARRAY_CREATIONOPTIONLIST))
     872             :     {
     873         415 :         InitMetadata();
     874             :     }
     875       65030 :     return GDALDriver::GetMetadataItem(pszName, pszDomain);
     876             : }
     877             : 
     878         861 : void ZarrDriver::InitMetadata()
     879             : {
     880         861 :     if (m_bMetadataInitialized)
     881         651 :         return;
     882         210 :     m_bMetadataInitialized = true;
     883             : 
     884             :     {
     885             :         // A bit of a hack. Normally GetMetadata() should also return it,
     886             :         // but as this is only used for tests, just make GetMetadataItem()
     887             :         // handle it
     888         420 :         std::string osCompressors;
     889         420 :         std::string osFilters;
     890         210 :         char **decompressors = CPLGetDecompressors();
     891        1680 :         for (auto iter = decompressors; iter && *iter; ++iter)
     892             :         {
     893        1470 :             const auto psCompressor = CPLGetCompressor(*iter);
     894        1470 :             if (psCompressor)
     895             :             {
     896        1470 :                 if (psCompressor->eType == CCT_COMPRESSOR)
     897             :                 {
     898        1260 :                     if (!osCompressors.empty())
     899        1050 :                         osCompressors += ',';
     900        1260 :                     osCompressors += *iter;
     901             :                 }
     902         210 :                 else if (psCompressor->eType == CCT_FILTER)
     903             :                 {
     904         210 :                     if (!osFilters.empty())
     905           0 :                         osFilters += ',';
     906         210 :                     osFilters += *iter;
     907             :                 }
     908             :             }
     909             :         }
     910         210 :         CSLDestroy(decompressors);
     911         210 :         GDALDriver::SetMetadataItem("COMPRESSORS", osCompressors.c_str());
     912         210 :         GDALDriver::SetMetadataItem("FILTERS", osFilters.c_str());
     913             :     }
     914             : #ifdef HAVE_BLOSC
     915             :     {
     916         210 :         GDALDriver::SetMetadataItem("BLOSC_COMPRESSORS",
     917             :                                     blosc_list_compressors());
     918             :     }
     919             : #endif
     920             : 
     921             :     {
     922             :         CPLXMLTreeCloser oTree(
     923         420 :             CPLCreateXMLNode(nullptr, CXT_Element, "CreationOptionList"));
     924         210 :         char **compressors = CPLGetCompressors();
     925             : 
     926             :         auto psCompressNode =
     927         210 :             CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
     928         210 :         CPLAddXMLAttributeAndValue(psCompressNode, "name", "COMPRESS");
     929         210 :         CPLAddXMLAttributeAndValue(psCompressNode, "type", "string-select");
     930         210 :         CPLAddXMLAttributeAndValue(psCompressNode, "description",
     931             :                                    "Compression method");
     932         210 :         CPLAddXMLAttributeAndValue(psCompressNode, "default", "NONE");
     933             :         {
     934             :             auto poValueNode =
     935         210 :                 CPLCreateXMLNode(psCompressNode, CXT_Element, "Value");
     936         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "NONE");
     937             :         }
     938             : 
     939             :         auto psFilterNode =
     940         210 :             CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
     941         210 :         CPLAddXMLAttributeAndValue(psFilterNode, "name", "FILTER");
     942         210 :         CPLAddXMLAttributeAndValue(psFilterNode, "type", "string-select");
     943         210 :         CPLAddXMLAttributeAndValue(psFilterNode, "description",
     944             :                                    "Filter method (only for ZARR_V2)");
     945         210 :         CPLAddXMLAttributeAndValue(psFilterNode, "default", "NONE");
     946             :         {
     947             :             auto poValueNode =
     948         210 :                 CPLCreateXMLNode(psFilterNode, CXT_Element, "Value");
     949         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "NONE");
     950             :         }
     951             : 
     952             :         auto psBlockSizeNode =
     953         210 :             CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
     954         210 :         CPLAddXMLAttributeAndValue(psBlockSizeNode, "name", "BLOCKSIZE");
     955         210 :         CPLAddXMLAttributeAndValue(psBlockSizeNode, "type", "string");
     956         210 :         CPLAddXMLAttributeAndValue(
     957             :             psBlockSizeNode, "description",
     958             :             "Comma separated list of chunk size along each dimension");
     959             : 
     960             :         auto psChunkMemoryLayout =
     961         210 :             CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
     962         210 :         CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "name",
     963             :                                    "CHUNK_MEMORY_LAYOUT");
     964         210 :         CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "type",
     965             :                                    "string-select");
     966         210 :         CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "description",
     967             :                                    "Whether to use C (row-major) order or F "
     968             :                                    "(column-major) order in chunks");
     969         210 :         CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "default", "C");
     970             :         {
     971             :             auto poValueNode =
     972         210 :                 CPLCreateXMLNode(psChunkMemoryLayout, CXT_Element, "Value");
     973         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "C");
     974             :         }
     975             :         {
     976             :             auto poValueNode =
     977         210 :                 CPLCreateXMLNode(psChunkMemoryLayout, CXT_Element, "Value");
     978         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "F");
     979             :         }
     980             : 
     981             :         auto psStringFormat =
     982         210 :             CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
     983         210 :         CPLAddXMLAttributeAndValue(psStringFormat, "name", "STRING_FORMAT");
     984         210 :         CPLAddXMLAttributeAndValue(psStringFormat, "type", "string-select");
     985         210 :         CPLAddXMLAttributeAndValue(psStringFormat, "default", "STRING");
     986             :         {
     987             :             auto poValueNode =
     988         210 :                 CPLCreateXMLNode(psStringFormat, CXT_Element, "Value");
     989         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "STRING");
     990             :         }
     991             :         {
     992             :             auto poValueNode =
     993         210 :                 CPLCreateXMLNode(psStringFormat, CXT_Element, "Value");
     994         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "UNICODE");
     995             :         }
     996             : 
     997             :         auto psDimSeparatorNode =
     998         210 :             CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
     999         210 :         CPLAddXMLAttributeAndValue(psDimSeparatorNode, "name", "DIM_SEPARATOR");
    1000         210 :         CPLAddXMLAttributeAndValue(psDimSeparatorNode, "type", "string");
    1001         210 :         CPLAddXMLAttributeAndValue(
    1002             :             psDimSeparatorNode, "description",
    1003             :             "Dimension separator in chunk filenames. Default to decimal point "
    1004             :             "for ZarrV2 and slash for ZarrV3");
    1005             : 
    1006        1680 :         for (auto iter = compressors; iter && *iter; ++iter)
    1007             :         {
    1008        1470 :             const auto psCompressor = CPLGetCompressor(*iter);
    1009        1470 :             if (psCompressor)
    1010             :             {
    1011        1470 :                 auto poValueNode = CPLCreateXMLNode(
    1012        1470 :                     (psCompressor->eType == CCT_COMPRESSOR) ? psCompressNode
    1013             :                                                             : psFilterNode,
    1014             :                     CXT_Element, "Value");
    1015        1470 :                 CPLCreateXMLNode(poValueNode, CXT_Text,
    1016        2940 :                                  CPLString(*iter).toupper().c_str());
    1017             : 
    1018             :                 const char *pszOptions =
    1019        1470 :                     CSLFetchNameValue(psCompressor->papszMetadata, "OPTIONS");
    1020        1470 :                 if (pszOptions)
    1021             :                 {
    1022             :                     CPLXMLTreeCloser oTreeCompressor(
    1023        2940 :                         CPLParseXMLString(pszOptions));
    1024             :                     const auto psRoot =
    1025        1470 :                         oTreeCompressor.get()
    1026        1470 :                             ? CPLGetXMLNode(oTreeCompressor.get(), "=Options")
    1027        1470 :                             : nullptr;
    1028        1470 :                     if (psRoot)
    1029             :                     {
    1030        1470 :                         for (CPLXMLNode *psNode = psRoot->psChild;
    1031        4620 :                              psNode != nullptr; psNode = psNode->psNext)
    1032             :                         {
    1033        3150 :                             if (psNode->eType == CXT_Element)
    1034             :                             {
    1035             :                                 const char *pszName =
    1036        3150 :                                     CPLGetXMLValue(psNode, "name", nullptr);
    1037        3150 :                                 if (pszName &&
    1038        3150 :                                     !EQUAL(pszName, "TYPESIZE")   // Blosc
    1039        2940 :                                     && !EQUAL(pszName, "HEADER")  // LZ4
    1040             :                                 )
    1041             :                                 {
    1042        2730 :                                     CPLXMLNode *psNext = psNode->psNext;
    1043        2730 :                                     psNode->psNext = nullptr;
    1044             :                                     CPLXMLNode *psOption =
    1045        2730 :                                         CPLCloneXMLTree(psNode);
    1046             : 
    1047             :                                     CPLXMLNode *psName =
    1048        2730 :                                         CPLGetXMLNode(psOption, "name");
    1049        2730 :                                     if (psName &&
    1050        2730 :                                         psName->eType == CXT_Attribute &&
    1051        2730 :                                         psName->psChild &&
    1052        2730 :                                         psName->psChild->pszValue)
    1053             :                                     {
    1054        2730 :                                         CPLString osNewValue(*iter);
    1055        2730 :                                         osNewValue = osNewValue.toupper();
    1056        2730 :                                         osNewValue += '_';
    1057        2730 :                                         osNewValue += psName->psChild->pszValue;
    1058        2730 :                                         CPLFree(psName->psChild->pszValue);
    1059        5460 :                                         psName->psChild->pszValue =
    1060        2730 :                                             CPLStrdup(osNewValue.c_str());
    1061             :                                     }
    1062             : 
    1063             :                                     CPLXMLNode *psDescription =
    1064        2730 :                                         CPLGetXMLNode(psOption, "description");
    1065        2730 :                                     if (psDescription &&
    1066        2730 :                                         psDescription->eType == CXT_Attribute &&
    1067        2730 :                                         psDescription->psChild &&
    1068        2730 :                                         psDescription->psChild->pszValue)
    1069             :                                     {
    1070             :                                         std::string osNewValue(
    1071        2730 :                                             psDescription->psChild->pszValue);
    1072        2730 :                                         if (psCompressor->eType ==
    1073             :                                             CCT_COMPRESSOR)
    1074             :                                         {
    1075             :                                             osNewValue +=
    1076        2520 :                                                 ". Only used when COMPRESS=";
    1077             :                                         }
    1078             :                                         else
    1079             :                                         {
    1080             :                                             osNewValue +=
    1081         210 :                                                 ". Only used when FILTER=";
    1082             :                                         }
    1083             :                                         osNewValue +=
    1084        2730 :                                             CPLString(*iter).toupper();
    1085        2730 :                                         CPLFree(
    1086        2730 :                                             psDescription->psChild->pszValue);
    1087        5460 :                                         psDescription->psChild->pszValue =
    1088        2730 :                                             CPLStrdup(osNewValue.c_str());
    1089             :                                     }
    1090             : 
    1091        2730 :                                     CPLAddXMLChild(oTree.get(), psOption);
    1092        2730 :                                     psNode->psNext = psNext;
    1093             :                                 }
    1094             :                             }
    1095             :                         }
    1096             :                     }
    1097             :                 }
    1098             :             }
    1099             :         }
    1100         210 :         CSLDestroy(compressors);
    1101             : 
    1102             :         auto psGeoreferencingConvention =
    1103         210 :             CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1104         210 :         CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "name",
    1105             :                                    "GEOREFERENCING_CONVENTION");
    1106         210 :         CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "type",
    1107             :                                    "string-select");
    1108         210 :         CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "default",
    1109             :                                    "GDAL");
    1110         210 :         CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "description",
    1111             :                                    "Georeferencing convention to use");
    1112             : 
    1113             :         {
    1114         210 :             auto poValueNode = CPLCreateXMLNode(psGeoreferencingConvention,
    1115             :                                                 CXT_Element, "Value");
    1116         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "GDAL");
    1117             :         }
    1118             :         {
    1119         210 :             auto poValueNode = CPLCreateXMLNode(psGeoreferencingConvention,
    1120             :                                                 CXT_Element, "Value");
    1121         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "SPATIAL_PROJ");
    1122             :         }
    1123             : 
    1124             :         {
    1125         210 :             char *pszXML = CPLSerializeXMLTree(oTree.get());
    1126         210 :             GDALDriver::SetMetadataItem(
    1127             :                 GDAL_DMD_MULTIDIM_ARRAY_CREATIONOPTIONLIST,
    1128         210 :                 CPLString(pszXML)
    1129             :                     .replaceAll("CreationOptionList",
    1130         420 :                                 "MultiDimArrayCreationOptionList")
    1131             :                     .c_str());
    1132         210 :             CPLFree(pszXML);
    1133             :         }
    1134             : 
    1135             :         {
    1136             :             auto psArrayNameOption =
    1137         210 :                 CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1138         210 :             CPLAddXMLAttributeAndValue(psArrayNameOption, "name", "ARRAY_NAME");
    1139         210 :             CPLAddXMLAttributeAndValue(psArrayNameOption, "type", "string");
    1140         210 :             CPLAddXMLAttributeAndValue(
    1141             :                 psArrayNameOption, "description",
    1142             :                 "Array name. If not specified, deduced from the filename");
    1143             : 
    1144             :             auto psAppendSubDSOption =
    1145         210 :                 CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1146         210 :             CPLAddXMLAttributeAndValue(psAppendSubDSOption, "name",
    1147             :                                        "APPEND_SUBDATASET");
    1148         210 :             CPLAddXMLAttributeAndValue(psAppendSubDSOption, "type", "boolean");
    1149         210 :             CPLAddXMLAttributeAndValue(psAppendSubDSOption, "description",
    1150             :                                        "Whether to append the new dataset to "
    1151             :                                        "an existing Zarr hierarchy");
    1152         210 :             CPLAddXMLAttributeAndValue(psAppendSubDSOption, "default", "NO");
    1153             : 
    1154             :             auto psFormat =
    1155         210 :                 CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1156         210 :             CPLAddXMLAttributeAndValue(psFormat, "name", "FORMAT");
    1157         210 :             CPLAddXMLAttributeAndValue(psFormat, "type", "string-select");
    1158         210 :             CPLAddXMLAttributeAndValue(psFormat, "default", "ZARR_V3");
    1159             :             {
    1160             :                 auto poValueNode =
    1161         210 :                     CPLCreateXMLNode(psFormat, CXT_Element, "Value");
    1162         210 :                 CPLCreateXMLNode(poValueNode, CXT_Text, "ZARR_V2");
    1163             :             }
    1164             :             {
    1165             :                 auto poValueNode =
    1166         210 :                     CPLCreateXMLNode(psFormat, CXT_Element, "Value");
    1167         210 :                 CPLCreateXMLNode(poValueNode, CXT_Text, "ZARR_V3");
    1168             :             }
    1169             : 
    1170             :             auto psCreateZMetadata =
    1171         210 :                 CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1172         210 :             CPLAddXMLAttributeAndValue(psCreateZMetadata, "name",
    1173             :                                        "CREATE_CONSOLIDATED_METADATA");
    1174         210 :             CPLAddXMLAttributeAndValue(psCreateZMetadata, "alias",
    1175             :                                        "CREATE_ZMETADATA");
    1176         210 :             CPLAddXMLAttributeAndValue(psCreateZMetadata, "type", "boolean");
    1177         210 :             CPLAddXMLAttributeAndValue(
    1178             :                 psCreateZMetadata, "description",
    1179             :                 "Whether to create consolidated metadata");
    1180         210 :             CPLAddXMLAttributeAndValue(psCreateZMetadata, "default", "YES");
    1181             : 
    1182             :             auto psSingleArrayNode =
    1183         210 :                 CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1184         210 :             CPLAddXMLAttributeAndValue(psSingleArrayNode, "name",
    1185             :                                        "SINGLE_ARRAY");
    1186         210 :             CPLAddXMLAttributeAndValue(psSingleArrayNode, "type", "boolean");
    1187         210 :             CPLAddXMLAttributeAndValue(
    1188             :                 psSingleArrayNode, "description",
    1189             :                 "Whether to write a multi-band dataset as a single array, or "
    1190             :                 "one array per band");
    1191         210 :             CPLAddXMLAttributeAndValue(psSingleArrayNode, "default", "YES");
    1192             : 
    1193             :             auto psInterleaveNode =
    1194         210 :                 CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1195         210 :             CPLAddXMLAttributeAndValue(psInterleaveNode, "name",
    1196             :                                        GDALMD_INTERLEAVE);
    1197         210 :             CPLAddXMLAttributeAndValue(psInterleaveNode, "type",
    1198             :                                        "string-select");
    1199         210 :             CPLAddXMLAttributeAndValue(psInterleaveNode, "default", "BAND");
    1200             :             {
    1201             :                 auto poValueNode =
    1202         210 :                     CPLCreateXMLNode(psInterleaveNode, CXT_Element, "Value");
    1203         210 :                 CPLCreateXMLNode(poValueNode, CXT_Text, "BAND");
    1204             :             }
    1205             :             {
    1206             :                 auto poValueNode =
    1207         210 :                     CPLCreateXMLNode(psInterleaveNode, CXT_Element, "Value");
    1208         210 :                 CPLCreateXMLNode(poValueNode, CXT_Text, "PIXEL");
    1209             :             }
    1210             : 
    1211             :             auto psConvertToParquet =
    1212         210 :                 CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1213         210 :             CPLAddXMLAttributeAndValue(psConvertToParquet, "name",
    1214             :                                        "CONVERT_TO_KERCHUNK_PARQUET_REFERENCE");
    1215         210 :             CPLAddXMLAttributeAndValue(psConvertToParquet, "type", "boolean");
    1216         210 :             CPLAddXMLAttributeAndValue(
    1217             :                 psConvertToParquet, "description",
    1218             :                 "Whether to convert a Kerchunk JSON reference store to a "
    1219             :                 "Kerchunk Parquet reference store. (CreateCopy() only)");
    1220             : 
    1221         210 :             char *pszXML = CPLSerializeXMLTree(oTree.get());
    1222         210 :             GDALDriver::SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST, pszXML);
    1223         210 :             CPLFree(pszXML);
    1224             :         }
    1225             :     }
    1226             : }
    1227             : 
    1228             : /************************************************************************/
    1229             : /*                       CreateMultiDimensional()                       */
    1230             : /************************************************************************/
    1231             : 
    1232             : GDALDataset *
    1233         281 : ZarrDataset::CreateMultiDimensional(const char *pszFilename,
    1234             :                                     CSLConstList /*papszRootGroupOptions*/,
    1235             :                                     CSLConstList papszOptions)
    1236             : {
    1237             :     const char *pszFormat =
    1238         281 :         CSLFetchNameValueDef(papszOptions, "FORMAT", "ZARR_V3");
    1239         281 :     std::shared_ptr<ZarrGroupBase> poRG;
    1240             :     auto poSharedResource =
    1241         843 :         ZarrSharedResource::Create(pszFilename, /*bUpdatable=*/true);
    1242         281 :     const bool bCreateZMetadata = CPLTestBool(CSLFetchNameValueDef(
    1243             :         papszOptions, "CREATE_CONSOLIDATED_METADATA",
    1244             :         CSLFetchNameValueDef(papszOptions, "CREATE_ZMETADATA", "YES")));
    1245         281 :     if (bCreateZMetadata)
    1246             :     {
    1247         500 :         poSharedResource->EnableConsolidatedMetadata(
    1248         250 :             EQUAL(pszFormat, "ZARR_V3")
    1249             :                 ? ZarrSharedResource::ConsolidatedMetadataKind::INTERNAL
    1250             :                 : ZarrSharedResource::ConsolidatedMetadataKind::EXTERNAL);
    1251             :     }
    1252         281 :     if (EQUAL(pszFormat, "ZARR_V3"))
    1253             :     {
    1254         342 :         poRG = ZarrV3Group::CreateOnDisk(poSharedResource, std::string(), "/",
    1255         171 :                                          pszFilename);
    1256             :     }
    1257             :     else
    1258             :     {
    1259         220 :         poRG = ZarrV2Group::CreateOnDisk(poSharedResource, std::string(), "/",
    1260         110 :                                          pszFilename);
    1261             :     }
    1262         281 :     if (!poRG)
    1263           0 :         return nullptr;
    1264             : 
    1265         281 :     auto poDS = new ZarrDataset(poRG);
    1266         281 :     poDS->SetDescription(pszFilename);
    1267         281 :     return poDS;
    1268             : }
    1269             : 
    1270             : /************************************************************************/
    1271             : /*                               Create()                               */
    1272             : /************************************************************************/
    1273             : 
    1274         121 : GDALDataset *ZarrDataset::Create(const char *pszName, int nXSize, int nYSize,
    1275             :                                  int nBandsIn, GDALDataType eType,
    1276             :                                  CSLConstList papszOptions)
    1277             : {
    1278             :     // To avoid any issue with short-lived string that would be passed to us
    1279         242 :     const std::string osName = pszName;
    1280         121 :     pszName = osName.c_str();
    1281             : 
    1282         121 :     if (nBandsIn <= 0 || nXSize <= 0 || nYSize <= 0)
    1283             :     {
    1284           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    1285             :                  "nBands, nXSize, nYSize should be > 0");
    1286           1 :         return nullptr;
    1287             :     }
    1288             : 
    1289         120 :     const bool bAppendSubDS = CPLTestBool(
    1290             :         CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO"));
    1291         120 :     const char *pszArrayName = CSLFetchNameValue(papszOptions, "ARRAY_NAME");
    1292             : 
    1293         120 :     std::shared_ptr<ZarrGroupBase> poRG;
    1294         120 :     if (bAppendSubDS)
    1295             :     {
    1296           6 :         if (pszArrayName == nullptr)
    1297             :         {
    1298           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1299             :                      "ARRAY_NAME should be provided when "
    1300             :                      "APPEND_SUBDATASET is set to YES");
    1301           0 :             return nullptr;
    1302             :         }
    1303             :         auto poDS =
    1304           6 :             std::unique_ptr<GDALDataset>(OpenMultidim(pszName, true, nullptr));
    1305           6 :         if (poDS == nullptr)
    1306             :         {
    1307           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s", pszName);
    1308           0 :             return nullptr;
    1309             :         }
    1310           6 :         poRG = std::dynamic_pointer_cast<ZarrGroupBase>(poDS->GetRootGroup());
    1311             :     }
    1312             :     else
    1313             :     {
    1314             :         VSIStatBufL sStat;
    1315         114 :         const bool bExists = VSIStatL(pszName, &sStat) == 0;
    1316         114 :         const char *pszObjType = nullptr;
    1317         114 :         if (bExists && !VSI_ISDIR(sStat.st_mode))
    1318           0 :             pszObjType = "File";
    1319         228 :         else if ((bExists /* && VSI_ISDIR(sStat.st_mode)*/) ||
    1320         228 :                  !CPLStringList(VSIReadDirEx(pszName, 1)).empty())
    1321           0 :             pszObjType = "Directory";
    1322         114 :         if (pszObjType)
    1323             :         {
    1324           0 :             CPLError(CE_Failure, CPLE_FileIO, "%s %s already exists.",
    1325             :                      pszObjType, pszName);
    1326           0 :             return nullptr;
    1327             :         }
    1328             : 
    1329             :         const char *pszFormat =
    1330         114 :             CSLFetchNameValueDef(papszOptions, "FORMAT", "ZARR_V3");
    1331             :         auto poSharedResource =
    1332         342 :             ZarrSharedResource::Create(pszName, /*bUpdatable=*/true);
    1333         114 :         const bool bCreateZMetadata = CPLTestBool(CSLFetchNameValueDef(
    1334             :             papszOptions, "CREATE_CONSOLIDATED_METADATA",
    1335             :             CSLFetchNameValueDef(papszOptions, "CREATE_ZMETADATA", "YES")));
    1336         114 :         if (bCreateZMetadata)
    1337             :         {
    1338         228 :             poSharedResource->EnableConsolidatedMetadata(
    1339         114 :                 EQUAL(pszFormat, "ZARR_V3")
    1340             :                     ? ZarrSharedResource::ConsolidatedMetadataKind::INTERNAL
    1341             :                     : ZarrSharedResource::ConsolidatedMetadataKind::EXTERNAL);
    1342             :         }
    1343         114 :         if (EQUAL(pszFormat, "ZARR_V3"))
    1344             :         {
    1345         202 :             poRG = ZarrV3Group::CreateOnDisk(poSharedResource, std::string(),
    1346         101 :                                              "/", pszName);
    1347             :         }
    1348             :         else
    1349             :         {
    1350          26 :             poRG = ZarrV2Group::CreateOnDisk(poSharedResource, std::string(),
    1351          13 :                                              "/", pszName);
    1352             :         }
    1353         114 :         poSharedResource->SetRootGroup(poRG);
    1354             :     }
    1355         120 :     if (!poRG)
    1356           3 :         return nullptr;
    1357             : 
    1358         234 :     auto poDS = std::make_unique<ZarrDataset>(poRG);
    1359         117 :     poDS->SetDescription(pszName);
    1360         117 :     poDS->nRasterYSize = nYSize;
    1361         117 :     poDS->nRasterXSize = nXSize;
    1362         117 :     poDS->eAccess = GA_Update;
    1363             : 
    1364             :     const auto CleanupCreatedFiles =
    1365         136 :         [bAppendSubDS, pszName, pszArrayName, &poRG, &poDS]()
    1366             :     {
    1367             :         // Make sure all objects are released so that ZarrSharedResource
    1368             :         // is finalized and all files are serialized.
    1369          10 :         poRG.reset();
    1370          10 :         poDS.reset();
    1371             : 
    1372          10 :         if (bAppendSubDS)
    1373             :         {
    1374           2 :             VSIRmdir(
    1375           4 :                 CPLFormFilenameSafe(pszName, pszArrayName, nullptr).c_str());
    1376             :         }
    1377             :         else
    1378             :         {
    1379             :             // Be a bit careful before wiping too much stuff...
    1380             :             // At most 5 files expected for ZARR_V2: .zgroup, .zmetadata,
    1381             :             // one (empty) subdir, . and ..
    1382             :             // and for ZARR_V3: zarr.json, one (empty) subdir, . and ..
    1383          16 :             const CPLStringList aosFiles(VSIReadDirEx(pszName, 6));
    1384           8 :             if (aosFiles.size() < 6)
    1385             :             {
    1386          34 :                 for (const char *pszFile : aosFiles)
    1387             :                 {
    1388          26 :                     if (pszArrayName && strcmp(pszFile, pszArrayName) == 0)
    1389             :                     {
    1390           0 :                         VSIRmdir(CPLFormFilenameSafe(pszName, pszFile, nullptr)
    1391             :                                      .c_str());
    1392             :                     }
    1393          52 :                     else if (!pszArrayName &&
    1394          26 :                              strcmp(pszFile,
    1395          52 :                                     CPLGetBasenameSafe(pszName).c_str()) == 0)
    1396             :                     {
    1397           1 :                         VSIRmdir(CPLFormFilenameSafe(pszName, pszFile, nullptr)
    1398             :                                      .c_str());
    1399             :                     }
    1400          25 :                     else if (strcmp(pszFile, ".zgroup") == 0 ||
    1401          24 :                              strcmp(pszFile, ".zmetadata") == 0 ||
    1402          23 :                              strcmp(pszFile, "zarr.json") == 0)
    1403             :                     {
    1404           9 :                         VSIUnlink(CPLFormFilenameSafe(pszName, pszFile, nullptr)
    1405             :                                       .c_str());
    1406             :                     }
    1407             :                 }
    1408           8 :                 VSIRmdir(pszName);
    1409             :             }
    1410             :         }
    1411          10 :     };
    1412             : 
    1413         234 :     std::string osDimXType, osDimYType;
    1414         117 :     if (CPLTestBool(
    1415             :             CSLFetchNameValueDef(papszOptions, "@HAS_GEOTRANSFORM", "NO")))
    1416             :     {
    1417          46 :         osDimXType = GDAL_DIM_TYPE_HORIZONTAL_X;
    1418          46 :         osDimYType = GDAL_DIM_TYPE_HORIZONTAL_Y;
    1419             :     }
    1420         117 :     poDS->m_bSpatialProjConvention = EQUAL(
    1421             :         CSLFetchNameValueDef(papszOptions, "GEOREFERENCING_CONVENTION", "GDAL"),
    1422             :         "SPATIAL_PROJ");
    1423             : 
    1424         117 :     if (bAppendSubDS)
    1425             :     {
    1426          12 :         auto aoDims = poRG->GetDimensions();
    1427          18 :         for (const auto &poDim : aoDims)
    1428             :         {
    1429          18 :             if (poDim->GetName() == "Y" &&
    1430           6 :                 poDim->GetSize() == static_cast<GUInt64>(nYSize))
    1431             :             {
    1432           2 :                 poDS->m_poDimY = poDim;
    1433             :             }
    1434          16 :             else if (poDim->GetName() == "X" &&
    1435           6 :                      poDim->GetSize() == static_cast<GUInt64>(nXSize))
    1436             :             {
    1437           2 :                 poDS->m_poDimX = poDim;
    1438             :             }
    1439             :         }
    1440           6 :         if (poDS->m_poDimY == nullptr)
    1441             :         {
    1442           4 :             poDS->m_poDimY =
    1443          12 :                 poRG->CreateDimension(std::string(pszArrayName) + "_Y",
    1444          12 :                                       osDimYType, std::string(), nYSize);
    1445             :         }
    1446           6 :         if (poDS->m_poDimX == nullptr)
    1447             :         {
    1448           4 :             poDS->m_poDimX =
    1449          12 :                 poRG->CreateDimension(std::string(pszArrayName) + "_X",
    1450          12 :                                       osDimXType, std::string(), nXSize);
    1451             :         }
    1452             :     }
    1453             :     else
    1454             :     {
    1455         111 :         poDS->m_poDimY =
    1456         222 :             poRG->CreateDimension("Y", osDimYType, std::string(), nYSize);
    1457         111 :         poDS->m_poDimX =
    1458         222 :             poRG->CreateDimension("X", osDimXType, std::string(), nXSize);
    1459             :     }
    1460         117 :     if (poDS->m_poDimY == nullptr || poDS->m_poDimX == nullptr)
    1461             :     {
    1462           0 :         CleanupCreatedFiles();
    1463           0 :         return nullptr;
    1464             :     }
    1465             : 
    1466             :     const bool bSingleArray =
    1467         117 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "SINGLE_ARRAY", "YES"));
    1468         117 :     const bool bBandInterleave = EQUAL(
    1469             :         CSLFetchNameValueDef(papszOptions, GDALMD_INTERLEAVE, "BAND"), "BAND");
    1470             :     std::shared_ptr<GDALDimension> poBandDim(
    1471         115 :         (bSingleArray && nBandsIn > 1)
    1472         168 :             ? poRG->CreateDimension("Band", std::string(), std::string(),
    1473          51 :                                     nBandsIn)
    1474         502 :             : nullptr);
    1475             : 
    1476             :     const std::string osNonNullArrayName =
    1477         234 :         pszArrayName ? std::string(pszArrayName) : CPLGetBasenameSafe(pszName);
    1478         117 :     if (poBandDim)
    1479             :     {
    1480          51 :         std::vector<std::shared_ptr<GDALDimension>> apoDims;
    1481          51 :         if (bBandInterleave)
    1482             :         {
    1483         300 :             apoDims = std::vector<std::shared_ptr<GDALDimension>>{
    1484         250 :                 poBandDim, poDS->m_poDimY, poDS->m_poDimX};
    1485             :         }
    1486             :         else
    1487             :         {
    1488           5 :             apoDims = std::vector<std::shared_ptr<GDALDimension>>{
    1489           5 :                 poDS->m_poDimY, poDS->m_poDimX, poBandDim};
    1490             :         }
    1491          51 :         CPL_IGNORE_RET_VAL(poBandDim);
    1492          51 :         poDS->m_poSingleArray =
    1493         153 :             std::dynamic_pointer_cast<ZarrArray>(poRG->CreateMDArray(
    1494             :                 osNonNullArrayName.c_str(), apoDims,
    1495         153 :                 GDALExtendedDataType::Create(eType), papszOptions));
    1496          51 :         if (!poDS->m_poSingleArray)
    1497             :         {
    1498           2 :             CleanupCreatedFiles();
    1499           2 :             return nullptr;
    1500             :         }
    1501          98 :         poDS->SetMetadataItem(GDALMD_INTERLEAVE,
    1502             :                               bBandInterleave ? "BAND" : "PIXEL",
    1503          49 :                               GDAL_MDD_IMAGE_STRUCTURE);
    1504          49 :         if (bBandInterleave)
    1505             :         {
    1506             :             const char *pszBlockSize =
    1507          48 :                 CSLFetchNameValue(papszOptions, "BLOCKSIZE");
    1508          48 :             if (pszBlockSize)
    1509             :             {
    1510             :                 const CPLStringList aosTokens(
    1511          12 :                     CSLTokenizeString2(pszBlockSize, ",", 0));
    1512           6 :                 if (aosTokens.size() == 3 && atoi(aosTokens[0]) == nBandsIn)
    1513             :                 {
    1514             :                     // Actually expose as pixel interleaved
    1515           3 :                     poDS->SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
    1516           3 :                                           GDAL_MDD_IMAGE_STRUCTURE);
    1517             :                 }
    1518             :             }
    1519             :         }
    1520         178 :         for (int i = 0; i < nBandsIn; i++)
    1521             :         {
    1522             :             const std::string viewDef =
    1523         258 :                 CPLSPrintf(bBandInterleave ? "[%d,::,::]" : "[::,::,%d]", i);
    1524         129 :             auto poSlicedArray = poDS->m_poSingleArray->GetView(viewDef);
    1525         129 :             poDS->SetBand(i + 1, std::make_unique<ZarrRasterBand>(poSlicedArray,
    1526             :                                                                   viewDef));
    1527             :         }
    1528             :     }
    1529             :     else
    1530             :     {
    1531             :         const auto apoDims = std::vector<std::shared_ptr<GDALDimension>>{
    1532         264 :             poDS->m_poDimY, poDS->m_poDimX};
    1533         128 :         for (int i = 0; i < nBandsIn; i++)
    1534             :         {
    1535          70 :             auto poArray = poRG->CreateMDArray(
    1536          64 :                 nBandsIn == 1  ? osNonNullArrayName.c_str()
    1537           6 :                 : pszArrayName ? CPLSPrintf("%s_band%d", pszArrayName, i + 1)
    1538           0 :                                : CPLSPrintf("Band%d", i + 1),
    1539         210 :                 apoDims, GDALExtendedDataType::Create(eType), papszOptions);
    1540          70 :             if (poArray == nullptr)
    1541             :             {
    1542           8 :                 CleanupCreatedFiles();
    1543           8 :                 return nullptr;
    1544             :             }
    1545          62 :             poDS->SetBand(i + 1, std::make_unique<ZarrRasterBand>(poArray));
    1546             :         }
    1547             :     }
    1548             : 
    1549         107 :     return poDS.release();
    1550             : }
    1551             : 
    1552             : /************************************************************************/
    1553             : /*                            ~ZarrDataset()                            */
    1554             : /************************************************************************/
    1555             : 
    1556        4288 : ZarrDataset::~ZarrDataset()
    1557             : {
    1558        2144 :     ZarrDataset::FlushCache(true);
    1559        4288 : }
    1560             : 
    1561             : /************************************************************************/
    1562             : /*                             FlushCache()                             */
    1563             : /************************************************************************/
    1564             : 
    1565        2188 : CPLErr ZarrDataset::FlushCache(bool bAtClosing)
    1566             : {
    1567        2188 :     CPLErr eErr = GDALDataset::FlushCache(bAtClosing);
    1568             : 
    1569        2188 :     if (m_poSingleArray && !m_poSingleArray->Flush())
    1570             :     {
    1571           0 :         eErr = CE_Failure;
    1572             :     }
    1573             : 
    1574        2188 :     if (bAtClosing && m_poSingleArray)
    1575             :     {
    1576          49 :         auto poFirstBand = cpl::down_cast<ZarrRasterBand *>(papoBands[0]);
    1577          49 :         if (poFirstBand->m_dfNoData.has_value())
    1578             :         {
    1579             :             const double dfFirstBandNoData =
    1580           9 :                 poFirstBand->m_dfNoData.value_or(0);
    1581           9 :             bool bSameValue = true;
    1582          18 :             for (int i = 1; bSameValue && i < nBands; ++i)
    1583             :             {
    1584           9 :                 auto poBand = cpl::down_cast<ZarrRasterBand *>(papoBands[i]);
    1585           9 :                 const double dfNoData = poBand->m_dfNoData.value_or(0);
    1586           9 :                 bSameValue =
    1587          25 :                     poBand->m_dfNoData.has_value() &&
    1588          16 :                     ((std::isnan(dfFirstBandNoData) && std::isnan(dfNoData)) ||
    1589             :                      (dfFirstBandNoData == dfNoData));
    1590             :             }
    1591           9 :             if (!bSameValue)
    1592             :             {
    1593           3 :                 CPLError(CE_Failure, CPLE_NotSupported,
    1594             :                          "Not all bands have the same nodata value. It will be "
    1595             :                          "ignored as the array can only have a single nodata "
    1596             :                          "value for all bands.");
    1597           3 :                 eErr = CE_Failure;
    1598             :             }
    1599             :             else
    1600             :             {
    1601           6 :                 m_poSingleArray->SetNoDataValue(dfFirstBandNoData);
    1602             :             }
    1603             :         }
    1604          40 :         else if (poFirstBand->m_nNoDataInt64.has_value())
    1605             :         {
    1606             :             const auto nFirstBandNoData =
    1607           3 :                 poFirstBand->m_nNoDataInt64.value_or(0);
    1608           3 :             bool bSameValue = true;
    1609           6 :             for (int i = 1; bSameValue && i < nBands; ++i)
    1610             :             {
    1611           3 :                 auto poBand = cpl::down_cast<ZarrRasterBand *>(papoBands[i]);
    1612           3 :                 bSameValue =
    1613           4 :                     poBand->m_nNoDataInt64.has_value() &&
    1614           4 :                     nFirstBandNoData == poBand->m_nNoDataInt64.value_or(0);
    1615             :             }
    1616           3 :             if (!bSameValue)
    1617             :             {
    1618           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    1619             :                          "Not all bands have the same nodata value. It will be "
    1620             :                          "ignored as the array can only have a single nodata "
    1621             :                          "value for all bands.");
    1622           2 :                 eErr = CE_Failure;
    1623             :             }
    1624             :             else
    1625             :             {
    1626           1 :                 m_poSingleArray->SetNoDataValue(nFirstBandNoData);
    1627             :             }
    1628             :         }
    1629          37 :         else if (poFirstBand->m_nNoDataUInt64.has_value())
    1630             :         {
    1631             :             const auto nFirstBandNoData =
    1632           3 :                 poFirstBand->m_nNoDataUInt64.value_or(0);
    1633           3 :             bool bSameValue = true;
    1634           6 :             for (int i = 1; bSameValue && i < nBands; ++i)
    1635             :             {
    1636           3 :                 auto poBand = cpl::down_cast<ZarrRasterBand *>(papoBands[i]);
    1637           3 :                 bSameValue =
    1638           4 :                     poBand->m_nNoDataUInt64.has_value() &&
    1639           4 :                     nFirstBandNoData == poBand->m_nNoDataUInt64.value_or(0);
    1640             :             }
    1641           3 :             if (!bSameValue)
    1642             :             {
    1643           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    1644             :                          "Not all bands have the same nodata value. It will be "
    1645             :                          "ignored as the array can only have a single nodata "
    1646             :                          "value for all bands.");
    1647           2 :                 eErr = CE_Failure;
    1648             :             }
    1649             :             else
    1650             :             {
    1651           1 :                 m_poSingleArray->SetNoDataValue(nFirstBandNoData);
    1652             :             }
    1653             :         }
    1654             : 
    1655          49 :         if (poFirstBand->m_dfOffset.has_value())
    1656             :         {
    1657             :             const double dfOffsetFirstBand =
    1658           3 :                 poFirstBand->m_dfOffset.value_or(0);
    1659           3 :             bool bSameValue = true;
    1660           6 :             for (int i = 1; bSameValue && i < nBands; ++i)
    1661             :             {
    1662           3 :                 auto poBand = cpl::down_cast<ZarrRasterBand *>(papoBands[i]);
    1663           3 :                 bSameValue =
    1664           4 :                     poBand->m_dfOffset.has_value() &&
    1665           4 :                     dfOffsetFirstBand == poBand->m_dfOffset.value_or(0);
    1666             :             }
    1667           3 :             if (!bSameValue)
    1668             :             {
    1669           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    1670             :                          "Not all bands have the same offset value. It will be "
    1671             :                          "ignored as the array can only have a single offset "
    1672             :                          "value for all bands.");
    1673           2 :                 eErr = CE_Failure;
    1674             :             }
    1675             :             else
    1676             :             {
    1677           1 :                 m_poSingleArray->SetOffset(dfOffsetFirstBand);
    1678             :             }
    1679             :         }
    1680             : 
    1681          49 :         if (poFirstBand->m_dfScale.has_value())
    1682             :         {
    1683           3 :             const double dfScaleFirstBand = poFirstBand->m_dfScale.value_or(0);
    1684           3 :             bool bSameValue = true;
    1685           6 :             for (int i = 1; bSameValue && i < nBands; ++i)
    1686             :             {
    1687           3 :                 auto poBand = cpl::down_cast<ZarrRasterBand *>(papoBands[i]);
    1688           4 :                 bSameValue = poBand->m_dfScale.has_value() &&
    1689           4 :                              dfScaleFirstBand == poBand->m_dfScale.value_or(0);
    1690             :             }
    1691           3 :             if (!bSameValue)
    1692             :             {
    1693           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    1694             :                          "Not all bands have the same scale value. It will be "
    1695             :                          "ignored as the array can only have a single scale "
    1696             :                          "value for all bands.");
    1697           2 :                 eErr = CE_Failure;
    1698             :             }
    1699             :             else
    1700             :             {
    1701           1 :                 m_poSingleArray->SetScale(dfScaleFirstBand);
    1702             :             }
    1703             :         }
    1704             : 
    1705          49 :         bool bFoundColorInterp = false;
    1706         178 :         for (int i = 0; i < nBands; ++i)
    1707             :         {
    1708         129 :             if (papoBands[i]->GetColorInterpretation() != GCI_Undefined)
    1709          24 :                 bFoundColorInterp = true;
    1710             :         }
    1711          49 :         if (bFoundColorInterp)
    1712             :         {
    1713          16 :             const auto oStringDT = GDALExtendedDataType::CreateString();
    1714          24 :             auto poAttr = m_poSingleArray->GetAttribute("COLOR_INTERPRETATION");
    1715           8 :             if (!poAttr)
    1716          24 :                 poAttr = m_poSingleArray->CreateAttribute(
    1717           8 :                     "COLOR_INTERPRETATION", {static_cast<GUInt64>(nBands)},
    1718          16 :                     oStringDT);
    1719           8 :             if (poAttr)
    1720             :             {
    1721           8 :                 const GUInt64 nStartIndex = 0;
    1722           8 :                 const size_t nCount = nBands;
    1723           8 :                 const GInt64 arrayStep = 1;
    1724           8 :                 const GPtrDiff_t bufferStride = 1;
    1725          16 :                 std::vector<const char *> apszValues;
    1726          32 :                 for (int i = 0; i < nBands; ++i)
    1727             :                 {
    1728             :                     const auto eColorInterp =
    1729          24 :                         papoBands[i]->GetColorInterpretation();
    1730          24 :                     apszValues.push_back(
    1731          24 :                         GDALGetColorInterpretationName(eColorInterp));
    1732             :                 }
    1733          16 :                 poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
    1734           8 :                               oStringDT, apszValues.data());
    1735             :             }
    1736             :         }
    1737             :     }
    1738             : 
    1739        2188 :     if (m_poRootGroup)
    1740             :     {
    1741        2024 :         if (bAtClosing)
    1742             :         {
    1743        1980 :             if (!m_poRootGroup->Close())
    1744           6 :                 eErr = CE_Failure;
    1745             :         }
    1746             :         else
    1747             :         {
    1748          44 :             if (!m_poRootGroup->Flush())
    1749           4 :                 eErr = CE_Failure;
    1750             :         }
    1751             :     }
    1752             : 
    1753        2188 :     return eErr;
    1754             : }
    1755             : 
    1756             : /************************************************************************/
    1757             : /*                            GetRootGroup()                            */
    1758             : /************************************************************************/
    1759             : 
    1760        1865 : std::shared_ptr<GDALGroup> ZarrDataset::GetRootGroup() const
    1761             : {
    1762        1865 :     return m_poRootGroup;
    1763             : }
    1764             : 
    1765             : /************************************************************************/
    1766             : /*                           GetSpatialRef()                            */
    1767             : /************************************************************************/
    1768             : 
    1769           3 : const OGRSpatialReference *ZarrDataset::GetSpatialRef() const
    1770             : {
    1771           3 :     if (m_poSingleArray)
    1772             :     {
    1773           1 :         return m_poSingleArray->GetSpatialRef().get();
    1774             :     }
    1775           2 :     else if (nBands >= 1)
    1776             :     {
    1777           2 :         return cpl::down_cast<ZarrRasterBand *>(papoBands[0])
    1778           4 :             ->m_poArray->GetSpatialRef()
    1779           2 :             .get();
    1780             :     }
    1781             :     else
    1782             :     {
    1783           0 :         return nullptr;
    1784             :     }
    1785             : }
    1786             : 
    1787             : /************************************************************************/
    1788             : /*                           SetSpatialRef()                            */
    1789             : /************************************************************************/
    1790             : 
    1791          69 : CPLErr ZarrDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    1792             : {
    1793          69 :     if (m_poSingleArray)
    1794             :     {
    1795          28 :         m_poSingleArray->SetSpatialRef(poSRS);
    1796             :     }
    1797             :     else
    1798             :     {
    1799          82 :         for (int i = 0; i < nBands; ++i)
    1800             :         {
    1801          41 :             cpl::down_cast<ZarrRasterBand *>(papoBands[i])
    1802          41 :                 ->m_poArray->SetSpatialRef(poSRS);
    1803             :         }
    1804             :     }
    1805          69 :     return CE_None;
    1806             : }
    1807             : 
    1808             : /************************************************************************/
    1809             : /*                          GetGeoTransform()                           */
    1810             : /************************************************************************/
    1811             : 
    1812          29 : CPLErr ZarrDataset::GetGeoTransform(GDALGeoTransform &gt) const
    1813             : {
    1814          29 :     gt = m_gt;
    1815          29 :     return m_bHasGT ? CE_None : CE_Failure;
    1816             : }
    1817             : 
    1818             : /************************************************************************/
    1819             : /*                          SetGeoTransform()                           */
    1820             : /************************************************************************/
    1821             : 
    1822          73 : CPLErr ZarrDataset::SetGeoTransform(const GDALGeoTransform &gt)
    1823             : {
    1824          73 :     const bool bHasRotatedTerms = (gt.xrot != 0 || gt.yrot != 0);
    1825             : 
    1826          73 :     if (bHasRotatedTerms)
    1827             :     {
    1828           1 :         if (!m_bSpatialProjConvention)
    1829             :         {
    1830           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1831             :                      "Geotransform with rotated terms not supported with "
    1832             :                      "GEOREFERENCING_CONVENTION=GDAL, but would be with "
    1833             :                      "SPATIAL_PROJ");
    1834           0 :             return CE_Failure;
    1835             :         }
    1836             :     }
    1837          72 :     else if (m_poDimX == nullptr || m_poDimY == nullptr)
    1838             :     {
    1839           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1840             :                  "SetGeoTransform() failed because of missing X/Y dimension");
    1841           0 :         return CE_Failure;
    1842             :     }
    1843             : 
    1844          73 :     m_gt = gt;
    1845          73 :     m_bHasGT = true;
    1846             : 
    1847          73 :     if (m_bSpatialProjConvention)
    1848             :     {
    1849           2 :         const auto bSingleArray = m_poSingleArray != nullptr;
    1850           2 :         const int nIters = bSingleArray ? 1 : nBands;
    1851           4 :         for (int i = 0; i < nIters; ++i)
    1852             :         {
    1853             :             auto *poArray = bSingleArray
    1854           2 :                                 ? m_poSingleArray.get()
    1855           2 :                                 : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
    1856           2 :                                       ->m_poArray.get();
    1857           4 :             auto oAttrDT = GDALExtendedDataType::Create(GDT_Float64);
    1858             :             auto poAttr =
    1859           6 :                 poArray->CreateAttribute("gdal:geotransform", {6}, oAttrDT);
    1860           2 :             if (poAttr)
    1861             :             {
    1862           2 :                 const GUInt64 nStartIndex = 0;
    1863           2 :                 const size_t nCount = 6;
    1864           2 :                 const GInt64 arrayStep = 1;
    1865           2 :                 const GPtrDiff_t bufferStride = 1;
    1866           4 :                 poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
    1867           2 :                               oAttrDT, m_gt.data());
    1868             :             }
    1869             :         }
    1870             :     }
    1871             : 
    1872          73 :     if (!bHasRotatedTerms)
    1873             :     {
    1874          72 :         CPLAssert(m_poDimX);
    1875          72 :         CPLAssert(m_poDimY);
    1876             : 
    1877          72 :         const auto oDTFloat64 = GDALExtendedDataType::Create(GDT_Float64);
    1878             :         {
    1879          72 :             auto poX = m_poRootGroup->OpenMDArray(m_poDimX->GetName());
    1880          72 :             if (!poX)
    1881         350 :                 poX = m_poRootGroup->CreateMDArray(
    1882         280 :                     m_poDimX->GetName(), {m_poDimX}, oDTFloat64, nullptr);
    1883          72 :             if (!poX)
    1884           0 :                 return CE_Failure;
    1885          72 :             m_poDimX->SetIndexingVariable(poX);
    1886          72 :             std::vector<double> adfX;
    1887             :             try
    1888             :             {
    1889          72 :                 adfX.reserve(nRasterXSize);
    1890        5842 :                 for (int i = 0; i < nRasterXSize; ++i)
    1891        5770 :                     adfX.emplace_back(m_gt.xorig + m_gt.xscale * (i + 0.5));
    1892             :             }
    1893           0 :             catch (const std::exception &)
    1894             :             {
    1895           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory,
    1896             :                          "Out of memory when allocating X array");
    1897           0 :                 return CE_Failure;
    1898             :             }
    1899          72 :             const GUInt64 nStartIndex = 0;
    1900          72 :             const size_t nCount = adfX.size();
    1901          72 :             const GInt64 arrayStep = 1;
    1902          72 :             const GPtrDiff_t bufferStride = 1;
    1903         144 :             if (!poX->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
    1904          72 :                             poX->GetDataType(), adfX.data()))
    1905             :             {
    1906           0 :                 return CE_Failure;
    1907             :             }
    1908             :         }
    1909             : 
    1910          72 :         auto poY = m_poRootGroup->OpenMDArray(m_poDimY->GetName());
    1911          72 :         if (!poY)
    1912         280 :             poY = m_poRootGroup->CreateMDArray(m_poDimY->GetName(), {m_poDimY},
    1913         210 :                                                oDTFloat64, nullptr);
    1914          72 :         if (!poY)
    1915           0 :             return CE_Failure;
    1916          72 :         m_poDimY->SetIndexingVariable(poY);
    1917          72 :         std::vector<double> adfY;
    1918             :         try
    1919             :         {
    1920          72 :             adfY.reserve(nRasterYSize);
    1921        4618 :             for (int i = 0; i < nRasterYSize; ++i)
    1922        4546 :                 adfY.emplace_back(m_gt.yorig + m_gt.yscale * (i + 0.5));
    1923             :         }
    1924           0 :         catch (const std::exception &)
    1925             :         {
    1926           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
    1927             :                      "Out of memory when allocating Y array");
    1928           0 :             return CE_Failure;
    1929             :         }
    1930          72 :         const GUInt64 nStartIndex = 0;
    1931          72 :         const size_t nCount = adfY.size();
    1932          72 :         const GInt64 arrayStep = 1;
    1933          72 :         const GPtrDiff_t bufferStride = 1;
    1934         144 :         if (!poY->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
    1935          72 :                         poY->GetDataType(), adfY.data()))
    1936             :         {
    1937           0 :             return CE_Failure;
    1938             :         }
    1939             :     }
    1940             : 
    1941          73 :     return CE_None;
    1942             : }
    1943             : 
    1944             : /************************************************************************/
    1945             : /*                            SetMetadata()                             */
    1946             : /************************************************************************/
    1947             : 
    1948          41 : CPLErr ZarrDataset::SetMetadata(CSLConstList papszMetadata,
    1949             :                                 const char *pszDomain)
    1950             : {
    1951          41 :     if (nBands >= 1 && (pszDomain == nullptr || pszDomain[0] == '\0'))
    1952             :     {
    1953          82 :         const auto oStringDT = GDALExtendedDataType::CreateString();
    1954          41 :         const auto bSingleArray = m_poSingleArray != nullptr;
    1955          41 :         const int nIters = bSingleArray ? 1 : nBands;
    1956          86 :         for (int i = 0; i < nIters; ++i)
    1957             :         {
    1958             :             auto *poArray = bSingleArray
    1959          45 :                                 ? m_poSingleArray.get()
    1960          33 :                                 : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
    1961          33 :                                       ->m_poArray.get();
    1962          90 :             for (auto iter = papszMetadata; iter && *iter; ++iter)
    1963             :             {
    1964          45 :                 char *pszKey = nullptr;
    1965          45 :                 const char *pszValue = CPLParseNameValue(*iter, &pszKey);
    1966          45 :                 if (pszKey && pszValue)
    1967             :                 {
    1968             :                     auto poAttr =
    1969          66 :                         poArray->CreateAttribute(pszKey, {}, oStringDT);
    1970          22 :                     if (poAttr)
    1971             :                     {
    1972          22 :                         const GUInt64 nStartIndex = 0;
    1973          22 :                         const size_t nCount = 1;
    1974          22 :                         const GInt64 arrayStep = 1;
    1975          22 :                         const GPtrDiff_t bufferStride = 1;
    1976          22 :                         poAttr->Write(&nStartIndex, &nCount, &arrayStep,
    1977             :                                       &bufferStride, oStringDT, &pszValue);
    1978             :                     }
    1979             :                 }
    1980          45 :                 CPLFree(pszKey);
    1981             :             }
    1982             :         }
    1983             :     }
    1984          41 :     return GDALDataset::SetMetadata(papszMetadata, pszDomain);
    1985             : }
    1986             : 
    1987             : /************************************************************************/
    1988             : /*                   ZarrRasterBand::ZarrRasterBand()                   */
    1989             : /************************************************************************/
    1990             : 
    1991         192 : ZarrRasterBand::ZarrRasterBand(const std::shared_ptr<GDALMDArray> &poArray,
    1992         192 :                                const std::string &viewDef)
    1993         192 :     : m_poArray(poArray), m_osViewDef(viewDef)
    1994             : {
    1995         192 :     assert(poArray->GetDimensionCount() == 2);
    1996         192 :     eDataType = poArray->GetDataType().GetNumericDataType();
    1997         192 :     nRasterXSize = static_cast<int>(poArray->GetDimensions()[1]->GetSize());
    1998         192 :     nRasterYSize = static_cast<int>(poArray->GetDimensions()[0]->GetSize());
    1999         192 :     nBlockXSize = static_cast<int>(poArray->GetBlockSize()[1]);
    2000         192 :     nBlockYSize = static_cast<int>(poArray->GetBlockSize()[0]);
    2001         192 : }
    2002             : 
    2003             : /************************************************************************/
    2004             : /*                           GetNoDataValue()                           */
    2005             : /************************************************************************/
    2006             : 
    2007           8 : double ZarrRasterBand::GetNoDataValue(int *pbHasNoData)
    2008             : {
    2009           8 :     if (m_dfNoData.has_value())
    2010             :     {
    2011           1 :         if (pbHasNoData)
    2012           1 :             *pbHasNoData = true;
    2013           1 :         return m_dfNoData.value();
    2014             :     }
    2015           7 :     bool bHasNodata = false;
    2016           7 :     const auto res = m_poArray->GetNoDataValueAsDouble(&bHasNodata);
    2017           7 :     if (pbHasNoData)
    2018           7 :         *pbHasNoData = bHasNodata;
    2019           7 :     return res;
    2020             : }
    2021             : 
    2022             : /************************************************************************/
    2023             : /*                       GetNoDataValueAsInt64()                        */
    2024             : /************************************************************************/
    2025             : 
    2026           1 : int64_t ZarrRasterBand::GetNoDataValueAsInt64(int *pbHasNoData)
    2027             : {
    2028           1 :     if (m_nNoDataInt64.has_value())
    2029             :     {
    2030           1 :         if (pbHasNoData)
    2031           1 :             *pbHasNoData = true;
    2032           1 :         return m_nNoDataInt64.value();
    2033             :     }
    2034           0 :     bool bHasNodata = false;
    2035           0 :     const auto res = m_poArray->GetNoDataValueAsInt64(&bHasNodata);
    2036           0 :     if (pbHasNoData)
    2037           0 :         *pbHasNoData = bHasNodata;
    2038           0 :     return res;
    2039             : }
    2040             : 
    2041             : /************************************************************************/
    2042             : /*                       GetNoDataValueAsUInt64()                       */
    2043             : /************************************************************************/
    2044             : 
    2045           1 : uint64_t ZarrRasterBand::GetNoDataValueAsUInt64(int *pbHasNoData)
    2046             : {
    2047           1 :     if (m_nNoDataUInt64.has_value())
    2048             :     {
    2049           1 :         if (pbHasNoData)
    2050           1 :             *pbHasNoData = true;
    2051           1 :         return m_nNoDataUInt64.value();
    2052             :     }
    2053           0 :     bool bHasNodata = false;
    2054           0 :     const auto res = m_poArray->GetNoDataValueAsUInt64(&bHasNodata);
    2055           0 :     if (pbHasNoData)
    2056           0 :         *pbHasNoData = bHasNodata;
    2057           0 :     return res;
    2058             : }
    2059             : 
    2060             : /************************************************************************/
    2061             : /*                           SetNoDataValue()                           */
    2062             : /************************************************************************/
    2063             : 
    2064          19 : CPLErr ZarrRasterBand::SetNoDataValue(double dfNoData)
    2065             : {
    2066          19 :     auto poGDS = cpl::down_cast<ZarrDataset *>(poDS);
    2067          19 :     if (!poGDS->m_poSingleArray)
    2068             :     {
    2069           2 :         return m_poArray->SetNoDataValue(dfNoData) ? CE_None : CE_Failure;
    2070             :     }
    2071          17 :     m_dfNoData = dfNoData;
    2072          17 :     return CE_None;
    2073             : }
    2074             : 
    2075             : /************************************************************************/
    2076             : /*                       SetNoDataValueAsInt64()                        */
    2077             : /************************************************************************/
    2078             : 
    2079           5 : CPLErr ZarrRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
    2080             : {
    2081           5 :     auto poGDS = cpl::down_cast<ZarrDataset *>(poDS);
    2082           5 :     if (!poGDS->m_poSingleArray)
    2083             :     {
    2084           0 :         return m_poArray->SetNoDataValue(nNoData) ? CE_None : CE_Failure;
    2085             :     }
    2086           5 :     m_nNoDataInt64 = nNoData;
    2087           5 :     return CE_None;
    2088             : }
    2089             : 
    2090             : /************************************************************************/
    2091             : /*                       SetNoDataValueAsUInt64()                       */
    2092             : /************************************************************************/
    2093             : 
    2094           5 : CPLErr ZarrRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
    2095             : {
    2096           5 :     auto poGDS = cpl::down_cast<ZarrDataset *>(poDS);
    2097           5 :     if (!poGDS->m_poSingleArray)
    2098             :     {
    2099           0 :         return m_poArray->SetNoDataValue(nNoData) ? CE_None : CE_Failure;
    2100             :     }
    2101           5 :     m_nNoDataUInt64 = nNoData;
    2102           5 :     return CE_None;
    2103             : }
    2104             : 
    2105             : /************************************************************************/
    2106             : /*                             GetOffset()                              */
    2107             : /************************************************************************/
    2108             : 
    2109           3 : double ZarrRasterBand::GetOffset(int *pbSuccess)
    2110             : {
    2111           3 :     if (m_dfOffset.has_value())
    2112             :     {
    2113           1 :         if (pbSuccess)
    2114           1 :             *pbSuccess = true;
    2115           1 :         return m_dfOffset.value();
    2116             :     }
    2117           2 :     bool bHasValue = false;
    2118           2 :     double dfRet = m_poArray->GetOffset(&bHasValue);
    2119           2 :     if (pbSuccess)
    2120           2 :         *pbSuccess = bHasValue ? TRUE : FALSE;
    2121           2 :     return dfRet;
    2122             : }
    2123             : 
    2124             : /************************************************************************/
    2125             : /*                             SetOffset()                              */
    2126             : /************************************************************************/
    2127             : 
    2128           7 : CPLErr ZarrRasterBand::SetOffset(double dfNewOffset)
    2129             : {
    2130           7 :     auto poGDS = cpl::down_cast<ZarrDataset *>(poDS);
    2131           7 :     if (!poGDS->m_poSingleArray)
    2132             :     {
    2133           2 :         return m_poArray->SetOffset(dfNewOffset) ? CE_None : CE_Failure;
    2134             :     }
    2135           5 :     m_dfOffset = dfNewOffset;
    2136           5 :     return CE_None;
    2137             : }
    2138             : 
    2139             : /************************************************************************/
    2140             : /*                              GetScale()                              */
    2141             : /************************************************************************/
    2142             : 
    2143           3 : double ZarrRasterBand::GetScale(int *pbSuccess)
    2144             : {
    2145           3 :     if (m_dfScale.has_value())
    2146             :     {
    2147           1 :         if (pbSuccess)
    2148           1 :             *pbSuccess = true;
    2149           1 :         return m_dfScale.value();
    2150             :     }
    2151           2 :     bool bHasValue = false;
    2152           2 :     double dfRet = m_poArray->GetScale(&bHasValue);
    2153           2 :     if (pbSuccess)
    2154           2 :         *pbSuccess = bHasValue ? TRUE : FALSE;
    2155           2 :     return dfRet;
    2156             : }
    2157             : 
    2158             : /************************************************************************/
    2159             : /*                              SetScale()                              */
    2160             : /************************************************************************/
    2161             : 
    2162           7 : CPLErr ZarrRasterBand::SetScale(double dfNewScale)
    2163             : {
    2164           7 :     auto poGDS = cpl::down_cast<ZarrDataset *>(poDS);
    2165           7 :     if (!poGDS->m_poSingleArray)
    2166             :     {
    2167           2 :         return m_poArray->SetScale(dfNewScale) ? CE_None : CE_Failure;
    2168             :     }
    2169           5 :     m_dfScale = dfNewScale;
    2170           5 :     return CE_None;
    2171             : }
    2172             : 
    2173             : /************************************************************************/
    2174             : /*                            GetUnitType()                             */
    2175             : /************************************************************************/
    2176             : 
    2177           2 : const char *ZarrRasterBand::GetUnitType()
    2178             : {
    2179           2 :     return m_poArray->GetUnit().c_str();
    2180             : }
    2181             : 
    2182             : /************************************************************************/
    2183             : /*                            SetUnitType()                             */
    2184             : /************************************************************************/
    2185             : 
    2186           2 : CPLErr ZarrRasterBand::SetUnitType(const char *pszNewValue)
    2187             : {
    2188           4 :     return m_poArray->SetUnit(pszNewValue ? pszNewValue : "") ? CE_None
    2189           4 :                                                               : CE_Failure;
    2190             : }
    2191             : 
    2192             : /************************************************************************/
    2193             : /*                       GetColorInterpretation()                       */
    2194             : /************************************************************************/
    2195             : 
    2196         185 : GDALColorInterp ZarrRasterBand::GetColorInterpretation()
    2197             : {
    2198         185 :     return m_eColorInterp;
    2199             : }
    2200             : 
    2201             : /************************************************************************/
    2202             : /*                       SetColorInterpretation()                       */
    2203             : /************************************************************************/
    2204             : 
    2205          32 : CPLErr ZarrRasterBand::SetColorInterpretation(GDALColorInterp eColorInterp)
    2206             : {
    2207          32 :     auto poGDS = cpl::down_cast<ZarrDataset *>(poDS);
    2208          32 :     m_eColorInterp = eColorInterp;
    2209          32 :     if (!poGDS->m_poSingleArray)
    2210             :     {
    2211           8 :         const auto oStringDT = GDALExtendedDataType::CreateString();
    2212          16 :         auto poAttr = m_poArray->GetAttribute("COLOR_INTERPRETATION");
    2213           8 :         if (poAttr && (poAttr->GetDimensionCount() != 0 ||
    2214           8 :                        poAttr->GetDataType().GetClass() != GEDTC_STRING))
    2215           0 :             return CE_None;
    2216           8 :         if (!poAttr)
    2217          24 :             poAttr = m_poArray->CreateAttribute("COLOR_INTERPRETATION", {},
    2218          16 :                                                 oStringDT);
    2219           8 :         if (poAttr)
    2220             :         {
    2221           8 :             const GUInt64 nStartIndex = 0;
    2222           8 :             const size_t nCount = 1;
    2223           8 :             const GInt64 arrayStep = 1;
    2224           8 :             const GPtrDiff_t bufferStride = 1;
    2225           8 :             const char *pszValue = GDALGetColorInterpretationName(eColorInterp);
    2226           8 :             poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
    2227             :                           oStringDT, &pszValue);
    2228             :         }
    2229             :     }
    2230          32 :     return CE_None;
    2231             : }
    2232             : 
    2233             : /************************************************************************/
    2234             : /*                          GetOverviewCount()                          */
    2235             : /************************************************************************/
    2236             : 
    2237           2 : int ZarrRasterBand::GetOverviewCount()
    2238             : {
    2239           2 :     auto poGDS = cpl::down_cast<ZarrDataset *>(poDS);
    2240           2 :     if (poGDS->m_poSingleArray)
    2241           2 :         return poGDS->m_poSingleArray->GetOverviewCount();
    2242           0 :     return m_poArray->GetOverviewCount();
    2243             : }
    2244             : 
    2245             : /************************************************************************/
    2246             : /*                            GetOverview()                             */
    2247             : /************************************************************************/
    2248             : 
    2249           3 : GDALRasterBand *ZarrRasterBand::GetOverview(int idx)
    2250             : {
    2251           3 :     auto oIter = m_oMapOverview.find(idx);
    2252           3 :     if (oIter != m_oMapOverview.end())
    2253           1 :         return oIter->second.get();
    2254           2 :     auto poGDS = cpl::down_cast<ZarrDataset *>(poDS);
    2255           2 :     if (poGDS->m_poSingleArray)
    2256             :     {
    2257           4 :         auto ovrArray = poGDS->m_poSingleArray->GetOverview(idx);
    2258           2 :         if (!ovrArray)
    2259           1 :             return nullptr;
    2260           2 :         auto ovrArrayView = ovrArray->GetView(m_osViewDef);
    2261           1 :         if (!ovrArrayView)
    2262           0 :             return nullptr;  // not supposed to happen
    2263             :         return m_oMapOverview
    2264           1 :             .insert({idx, std::make_unique<ZarrRasterBand>(ovrArrayView,
    2265           2 :                                                            m_osViewDef)})
    2266           1 :             .first->second.get();
    2267             :     }
    2268             :     else
    2269             :     {
    2270           0 :         auto ovrArray = m_poArray->GetOverview(idx);
    2271           0 :         if (!ovrArray)
    2272           0 :             return nullptr;  // not supposed to happen
    2273             :         return m_oMapOverview
    2274           0 :             .insert({idx, std::make_unique<ZarrRasterBand>(ovrArray)})
    2275           0 :             .first->second.get();
    2276             :     }
    2277             : }
    2278             : 
    2279             : /************************************************************************/
    2280             : /*                     ZarrRasterBand::IReadBlock()                     */
    2281             : /************************************************************************/
    2282             : 
    2283           2 : CPLErr ZarrRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pData)
    2284             : {
    2285             : 
    2286           2 :     const int nXOff = nBlockXOff * nBlockXSize;
    2287           2 :     const int nYOff = nBlockYOff * nBlockYSize;
    2288           2 :     const int nReqXSize = std::min(nRasterXSize - nXOff, nBlockXSize);
    2289           2 :     const int nReqYSize = std::min(nRasterYSize - nYOff, nBlockYSize);
    2290           2 :     GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
    2291           2 :                                static_cast<GUInt64>(nXOff)};
    2292           2 :     size_t count[] = {static_cast<size_t>(nReqYSize),
    2293           2 :                       static_cast<size_t>(nReqXSize)};
    2294           2 :     constexpr GInt64 arrayStep[] = {1, 1};
    2295           2 :     GPtrDiff_t bufferStride[] = {nBlockXSize, 1};
    2296           4 :     return m_poArray->Read(arrayStartIdx, count, arrayStep, bufferStride,
    2297           2 :                            m_poArray->GetDataType(), pData)
    2298           2 :                ? CE_None
    2299           2 :                : CE_Failure;
    2300             : }
    2301             : 
    2302             : /************************************************************************/
    2303             : /*                    ZarrRasterBand::IWriteBlock()                     */
    2304             : /************************************************************************/
    2305             : 
    2306           0 : CPLErr ZarrRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff, void *pData)
    2307             : {
    2308           0 :     const int nXOff = nBlockXOff * nBlockXSize;
    2309           0 :     const int nYOff = nBlockYOff * nBlockYSize;
    2310           0 :     const int nReqXSize = std::min(nRasterXSize - nXOff, nBlockXSize);
    2311           0 :     const int nReqYSize = std::min(nRasterYSize - nYOff, nBlockYSize);
    2312           0 :     GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
    2313           0 :                                static_cast<GUInt64>(nXOff)};
    2314           0 :     size_t count[] = {static_cast<size_t>(nReqYSize),
    2315           0 :                       static_cast<size_t>(nReqXSize)};
    2316           0 :     constexpr GInt64 arrayStep[] = {1, 1};
    2317           0 :     GPtrDiff_t bufferStride[] = {nBlockXSize, 1};
    2318           0 :     return m_poArray->Write(arrayStartIdx, count, arrayStep, bufferStride,
    2319           0 :                             m_poArray->GetDataType(), pData)
    2320           0 :                ? CE_None
    2321           0 :                : CE_Failure;
    2322             : }
    2323             : 
    2324             : /************************************************************************/
    2325             : /*                             IRasterIO()                              */
    2326             : /************************************************************************/
    2327             : 
    2328          51 : CPLErr ZarrRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
    2329             :                                  int nXSize, int nYSize, void *pData,
    2330             :                                  int nBufXSize, int nBufYSize,
    2331             :                                  GDALDataType eBufType, GSpacing nPixelSpaceBuf,
    2332             :                                  GSpacing nLineSpaceBuf,
    2333             :                                  GDALRasterIOExtraArg *psExtraArg)
    2334             : {
    2335          51 :     const int nBufferDTSize(GDALGetDataTypeSizeBytes(eBufType));
    2336             :     // If reading/writing at full resolution and with proper stride, go
    2337             :     // directly to the array, but, for performance reasons,
    2338             :     // only if exactly on chunk boundaries, otherwise go through the block cache.
    2339          51 :     if (nXSize == nBufXSize && nYSize == nBufYSize && nBufferDTSize > 0 &&
    2340          51 :         (nPixelSpaceBuf % nBufferDTSize) == 0 &&
    2341          51 :         (nLineSpaceBuf % nBufferDTSize) == 0 && (nXOff % nBlockXSize) == 0 &&
    2342          51 :         (nYOff % nBlockYSize) == 0 &&
    2343          51 :         ((nXSize % nBlockXSize) == 0 || nXOff + nXSize == nRasterXSize) &&
    2344          51 :         ((nYSize % nBlockYSize) == 0 || nYOff + nYSize == nRasterYSize))
    2345             :     {
    2346          51 :         GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
    2347          51 :                                    static_cast<GUInt64>(nXOff)};
    2348          51 :         size_t count[] = {static_cast<size_t>(nYSize),
    2349          51 :                           static_cast<size_t>(nXSize)};
    2350          51 :         constexpr GInt64 arrayStep[] = {1, 1};
    2351             :         GPtrDiff_t bufferStride[] = {
    2352          51 :             static_cast<GPtrDiff_t>(nLineSpaceBuf / nBufferDTSize),
    2353          51 :             static_cast<GPtrDiff_t>(nPixelSpaceBuf / nBufferDTSize)};
    2354             : 
    2355          51 :         if (eRWFlag == GF_Read)
    2356             :         {
    2357           4 :             return m_poArray->Read(
    2358             :                        arrayStartIdx, count, arrayStep, bufferStride,
    2359           4 :                        GDALExtendedDataType::Create(eBufType), pData)
    2360           2 :                        ? CE_None
    2361           2 :                        : CE_Failure;
    2362             :         }
    2363             :         else
    2364             :         {
    2365          98 :             return m_poArray->Write(
    2366             :                        arrayStartIdx, count, arrayStep, bufferStride,
    2367          98 :                        GDALExtendedDataType::Create(eBufType), pData)
    2368          49 :                        ? CE_None
    2369          49 :                        : CE_Failure;
    2370             :         }
    2371             :     }
    2372             : 
    2373           0 :     return GDALRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
    2374             :                                      pData, nBufXSize, nBufYSize, eBufType,
    2375           0 :                                      nPixelSpaceBuf, nLineSpaceBuf, psExtraArg);
    2376             : }
    2377             : 
    2378             : /************************************************************************/
    2379             : /*                       ZarrDataset::IRasterIO()                       */
    2380             : /************************************************************************/
    2381             : 
    2382          59 : CPLErr ZarrDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
    2383             :                               int nXSize, int nYSize, void *pData,
    2384             :                               int nBufXSize, int nBufYSize,
    2385             :                               GDALDataType eBufType, int nBandCount,
    2386             :                               BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
    2387             :                               GSpacing nLineSpace, GSpacing nBandSpace,
    2388             :                               GDALRasterIOExtraArg *psExtraArg)
    2389             : {
    2390          59 :     const int nBufferDTSize(GDALGetDataTypeSizeBytes(eBufType));
    2391             :     // If reading/writing at full resolution and with proper stride, go
    2392             :     // directly to the array, but, for performance reasons,
    2393             :     // only if exactly on chunk boundaries, otherwise go through the block cache.
    2394          59 :     int nBlockXSize = 0, nBlockYSize = 0;
    2395          59 :     papoBands[0]->GetBlockSize(&nBlockXSize, &nBlockYSize);
    2396          91 :     if (m_poSingleArray && nXSize == nBufXSize && nYSize == nBufYSize &&
    2397          32 :         nBufferDTSize > 0 && (nPixelSpace % nBufferDTSize) == 0 &&
    2398          32 :         (nLineSpace % nBufferDTSize) == 0 &&
    2399          32 :         (nBandSpace % nBufferDTSize) == 0 && (nXOff % nBlockXSize) == 0 &&
    2400          32 :         (nYOff % nBlockYSize) == 0 &&
    2401          32 :         ((nXSize % nBlockXSize) == 0 || nXOff + nXSize == nRasterXSize) &&
    2402         123 :         ((nYSize % nBlockYSize) == 0 || nYOff + nYSize == nRasterYSize) &&
    2403          32 :         IsAllBands(nBandCount, panBandMap))
    2404             :     {
    2405          12 :         CPLAssert(m_poSingleArray->GetDimensionCount() == 3);
    2406          12 :         if (m_poSingleArray->GetDimensions().back().get() == m_poDimX.get())
    2407             :         {
    2408          11 :             GUInt64 arrayStartIdx[] = {0, static_cast<GUInt64>(nYOff),
    2409          11 :                                        static_cast<GUInt64>(nXOff)};
    2410          11 :             size_t count[] = {static_cast<size_t>(nBands),
    2411          11 :                               static_cast<size_t>(nYSize),
    2412          11 :                               static_cast<size_t>(nXSize)};
    2413          11 :             constexpr GInt64 arrayStep[] = {1, 1, 1};
    2414             :             GPtrDiff_t bufferStride[] = {
    2415          11 :                 static_cast<GPtrDiff_t>(nBandSpace / nBufferDTSize),
    2416          11 :                 static_cast<GPtrDiff_t>(nLineSpace / nBufferDTSize),
    2417          11 :                 static_cast<GPtrDiff_t>(nPixelSpace / nBufferDTSize)};
    2418             : 
    2419          11 :             if (eRWFlag == GF_Read)
    2420             :             {
    2421           8 :                 return m_poSingleArray->Read(
    2422             :                            arrayStartIdx, count, arrayStep, bufferStride,
    2423           8 :                            GDALExtendedDataType::Create(eBufType), pData)
    2424           4 :                            ? CE_None
    2425           4 :                            : CE_Failure;
    2426             :             }
    2427             :             else
    2428             :             {
    2429          14 :                 return m_poSingleArray->Write(
    2430             :                            arrayStartIdx, count, arrayStep, bufferStride,
    2431          14 :                            GDALExtendedDataType::Create(eBufType), pData)
    2432           7 :                            ? CE_None
    2433           7 :                            : CE_Failure;
    2434             :             }
    2435             :         }
    2436             :         else
    2437             :         {
    2438           1 :             GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
    2439           1 :                                        static_cast<GUInt64>(nXOff), 0};
    2440           1 :             size_t count[] = {static_cast<size_t>(nYSize),
    2441           1 :                               static_cast<size_t>(nXSize),
    2442           1 :                               static_cast<size_t>(nBands)};
    2443           1 :             constexpr GInt64 arrayStep[] = {1, 1, 1};
    2444             :             GPtrDiff_t bufferStride[] = {
    2445           1 :                 static_cast<GPtrDiff_t>(nLineSpace / nBufferDTSize),
    2446           1 :                 static_cast<GPtrDiff_t>(nPixelSpace / nBufferDTSize),
    2447           1 :                 static_cast<GPtrDiff_t>(nBandSpace / nBufferDTSize),
    2448           1 :             };
    2449             : 
    2450           1 :             if (eRWFlag == GF_Read)
    2451             :             {
    2452           0 :                 return m_poSingleArray->Read(
    2453             :                            arrayStartIdx, count, arrayStep, bufferStride,
    2454           0 :                            GDALExtendedDataType::Create(eBufType), pData)
    2455           0 :                            ? CE_None
    2456           0 :                            : CE_Failure;
    2457             :             }
    2458             :             else
    2459             :             {
    2460           2 :                 return m_poSingleArray->Write(
    2461             :                            arrayStartIdx, count, arrayStep, bufferStride,
    2462           2 :                            GDALExtendedDataType::Create(eBufType), pData)
    2463           1 :                            ? CE_None
    2464           1 :                            : CE_Failure;
    2465             :             }
    2466             :         }
    2467             :     }
    2468             : 
    2469          47 :     return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
    2470             :                                   nBufXSize, nBufYSize, eBufType, nBandCount,
    2471             :                                   panBandMap, nPixelSpace, nLineSpace,
    2472          47 :                                   nBandSpace, psExtraArg);
    2473             : }
    2474             : 
    2475             : /************************************************************************/
    2476             : /*                      ZarrDataset::CreateCopy()                       */
    2477             : /************************************************************************/
    2478             : 
    2479             : /* static */
    2480          52 : GDALDataset *ZarrDataset::CreateCopy(const char *pszFilename,
    2481             :                                      GDALDataset *poSrcDS, int bStrict,
    2482             :                                      CSLConstList papszOptions,
    2483             :                                      GDALProgressFunc pfnProgress,
    2484             :                                      void *pProgressData)
    2485             : {
    2486          52 :     if (CPLFetchBool(papszOptions, "CONVERT_TO_KERCHUNK_PARQUET_REFERENCE",
    2487             :                      false))
    2488             :     {
    2489           1 :         if (VSIKerchunkConvertJSONToParquet(poSrcDS->GetDescription(),
    2490             :                                             pszFilename, pfnProgress,
    2491             :                                             pProgressData))
    2492             :         {
    2493             :             GDALOpenInfo oOpenInfo(
    2494           2 :                 std::string("ZARR:\"").append(pszFilename).append("\"").c_str(),
    2495           2 :                 GA_ReadOnly);
    2496           1 :             return Open(&oOpenInfo);
    2497             :         }
    2498             :     }
    2499             :     else
    2500             :     {
    2501          51 :         auto poDriver = GetGDALDriverManager()->GetDriverByName(DRIVER_NAME);
    2502             :         CPLStringList aosCreationOptions(
    2503         102 :             const_cast<CSLConstList>(papszOptions));
    2504          51 :         GDALGeoTransform gt;
    2505          51 :         if (poSrcDS->GetGeoTransform(gt) == CE_None)
    2506             :         {
    2507          50 :             aosCreationOptions.SetNameValue("@HAS_GEOTRANSFORM", "YES");
    2508             :         }
    2509             :         auto poDS = std::unique_ptr<GDALDataset>(poDriver->DefaultCreateCopy(
    2510          51 :             pszFilename, poSrcDS, bStrict, aosCreationOptions.List(),
    2511         102 :             pfnProgress, pProgressData));
    2512          51 :         if (poDS)
    2513             :         {
    2514          39 :             if (poDS->FlushCache() != CE_None)
    2515           4 :                 poDS.reset();
    2516             :         }
    2517          51 :         return poDS.release();
    2518             :     }
    2519           0 :     return nullptr;
    2520             : }
    2521             : 
    2522             : /************************************************************************/
    2523             : /*               ZARRAddGeoreferencingConventionAlgorithm               */
    2524             : /************************************************************************/
    2525             : 
    2526             : #ifndef _
    2527             : #define _(x) (x)
    2528             : #endif
    2529             : 
    2530             : namespace
    2531             : {
    2532             : class ZARRAddGeoreferencingConventionAlgorithm final : public GDALAlgorithm
    2533             : {
    2534             :   public:
    2535         137 :     ZARRAddGeoreferencingConventionAlgorithm()
    2536         137 :         : GDALAlgorithm(
    2537             :               "add-georeferencing-convention",
    2538         274 :               std::string("Add a georeferencing convention to an existing ZARR "
    2539             :                           "dataset"),
    2540         411 :               "/programs/gdal_driver_zarr_add_georeferencing_convention.html")
    2541             :     {
    2542         137 :         AddProgressArg(/* hidden = */ true);
    2543             :         AddInputDatasetArg(&m_dataset,
    2544         137 :                            GDAL_OF_MULTIDIM_RASTER | GDAL_OF_UPDATE);
    2545             :         AddArg("convention", 0, _("Georeferencing convention"),
    2546         274 :                &m_georeferencingConvention)
    2547         137 :             .SetRequired()
    2548         137 :             .SetPositional()
    2549         137 :             .SetChoices("GDAL", "spatial_proj");
    2550         137 :     }
    2551             : 
    2552             :   protected:
    2553             :     bool RunImpl(GDALProgressFunc, void *) override;
    2554             : 
    2555             :   private:
    2556             :     GDALArgDatasetValue m_dataset{};
    2557             :     std::string m_georeferencingConvention{};
    2558             : };
    2559             : 
    2560           2 : bool ZARRAddGeoreferencingConventionAlgorithm::RunImpl(GDALProgressFunc, void *)
    2561             : {
    2562           2 :     auto poDS = dynamic_cast<ZarrDataset *>(m_dataset.GetDatasetRef());
    2563           2 :     if (!poDS)
    2564             :     {
    2565           1 :         ReportError(CE_Failure, CPLE_AppDefined, "%s is not a ZARR dataset",
    2566           1 :                     m_dataset.GetName().c_str());
    2567           1 :         return false;
    2568             :     }
    2569             : 
    2570           1 :     auto poRG = poDS->GetRootGroup();
    2571           1 :     CPLAssert(poRG);
    2572             : 
    2573           1 :     poRG->RecursivelyVisitArrays(
    2574           4 :         [this](const std::shared_ptr<GDALMDArray> &poArray)
    2575             :         {
    2576           3 :             ZarrArray *poZarrArray = dynamic_cast<ZarrArray *>(poArray.get());
    2577           3 :             if (poZarrArray && poZarrArray->GetSpatialRef())
    2578             :             {
    2579           2 :                 CPLStringList aosOptions;
    2580             :                 aosOptions.SetNameValue("GEOREFERENCING_CONVENTION",
    2581           1 :                                         m_georeferencingConvention.c_str());
    2582           1 :                 poZarrArray->SetCreationOptions(aosOptions.List());
    2583           1 :                 poZarrArray->InvalidateGeoreferencing();
    2584             :             }
    2585           3 :         });
    2586             : 
    2587           1 :     return true;
    2588             : }
    2589             : }  // namespace
    2590             : 
    2591             : /************************************************************************/
    2592             : /*                   ZarrDriverInstantiateAlgorithm()                   */
    2593             : /************************************************************************/
    2594             : 
    2595             : static GDALAlgorithm *
    2596         137 : ZarrDriverInstantiateAlgorithm(const std::vector<std::string> &aosPath)
    2597             : {
    2598         137 :     if (aosPath.size() == 1 && aosPath[0] == "add-georeferencing-convention")
    2599             :     {
    2600         274 :         return std::make_unique<ZARRAddGeoreferencingConventionAlgorithm>()
    2601         137 :             .release();
    2602             :     }
    2603             :     else
    2604             :     {
    2605           0 :         return nullptr;
    2606             :     }
    2607             : }
    2608             : 
    2609             : /************************************************************************/
    2610             : /*                         GDALRegister_Zarr()                          */
    2611             : /************************************************************************/
    2612             : 
    2613        2138 : void GDALRegister_Zarr()
    2614             : 
    2615             : {
    2616        2138 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
    2617         263 :         return;
    2618             : 
    2619        1875 :     VSIInstallKerchunkFileSystems();
    2620             : 
    2621        1875 :     GDALDriver *poDriver = new ZarrDriver();
    2622        1875 :     ZARRDriverSetCommonMetadata(poDriver);
    2623             : 
    2624             : #ifdef HAVE_PCODEC
    2625             :     poDriver->SetMetadataItem("HAVE_PCODEC", "YES");
    2626             : #endif
    2627             : 
    2628        1875 :     poDriver->pfnOpen = ZarrDataset::Open;
    2629        1875 :     poDriver->pfnCreateMultiDimensional = ZarrDataset::CreateMultiDimensional;
    2630        1875 :     poDriver->pfnCreate = ZarrDataset::Create;
    2631        1875 :     poDriver->pfnCreateCopy = ZarrDataset::CreateCopy;
    2632        1875 :     poDriver->pfnDelete = ZarrDatasetDelete;
    2633        1875 :     poDriver->pfnRename = ZarrDatasetRename;
    2634        1875 :     poDriver->pfnCopyFiles = ZarrDatasetCopyFiles;
    2635        1875 :     poDriver->pfnClearCaches = ZarrDriverClearCaches;
    2636        1875 :     poDriver->pfnInstantiateAlgorithm = ZarrDriverInstantiateAlgorithm;
    2637             : 
    2638        1875 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    2639             : }

Generated by: LCOV version 1.14