LCOV - code coverage report
Current view: top level - frmts/zarr - zarrdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 889 977 91.0 %
Date: 2026-02-11 08:43:47 Functions: 43 48 89.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 "gdal_frmts.h"
      20             : 
      21             : #include <algorithm>
      22             : #include <cassert>
      23             : #include <cinttypes>
      24             : #include <limits>
      25             : #include <mutex>
      26             : 
      27             : #ifdef HAVE_BLOSC
      28             : #include <blosc.h>
      29             : #endif
      30             : 
      31             : /************************************************************************/
      32             : /*                            ZarrDataset()                             */
      33             : /************************************************************************/
      34             : 
      35        1832 : ZarrDataset::ZarrDataset(const std::shared_ptr<ZarrGroupBase> &poRootGroup)
      36        1832 :     : m_poRootGroup(poRootGroup)
      37             : {
      38        1832 : }
      39             : 
      40             : /************************************************************************/
      41             : /*                            OpenMultidim()                            */
      42             : /************************************************************************/
      43             : 
      44        1478 : GDALDataset *ZarrDataset::OpenMultidim(const char *pszFilename,
      45             :                                        bool bUpdateMode,
      46             :                                        CSLConstList papszOpenOptionsIn)
      47             : {
      48        2956 :     CPLString osFilename(pszFilename);
      49        1478 :     if (osFilename.back() == '/')
      50           0 :         osFilename.pop_back();
      51             : 
      52             :     // Syntaxic sugar to detect Parquet reference files automatically
      53        1478 :     if (!STARTS_WITH(pszFilename, "/vsikerchunk"))
      54             :     {
      55             :         const std::string osZmetadataFilename(
      56        1441 :             CPLFormFilenameSafe(osFilename.c_str(), ".zmetadata", nullptr));
      57        1441 :         CPLJSONDocument oDoc;
      58             :         bool bOK;
      59             :         {
      60        1441 :             CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
      61        1441 :             bOK = oDoc.Load(osZmetadataFilename);
      62             :         }
      63        1441 :         if (bOK && oDoc.GetRoot().GetObj("record_size").IsValid())
      64             :         {
      65             :             const std::string osKerchunkParquetRefFilename =
      66          10 :                 CPLSPrintf("%s{%s}", PARQUET_REF_FS_PREFIX, osFilename.c_str());
      67           5 :             CPLDebugOnly("ZARR", "Opening %s",
      68             :                          osKerchunkParquetRefFilename.c_str());
      69           5 :             return OpenMultidim(osKerchunkParquetRefFilename.c_str(),
      70           5 :                                 bUpdateMode, papszOpenOptionsIn);
      71             :         }
      72             :     }
      73             : 
      74        2946 :     auto poSharedResource = ZarrSharedResource::Create(osFilename, bUpdateMode);
      75        1473 :     poSharedResource->SetOpenOptions(papszOpenOptionsIn);
      76             : 
      77        2946 :     auto poRG = poSharedResource->GetRootGroup();
      78        1473 :     if (!poRG)
      79         102 :         return nullptr;
      80        1371 :     return new ZarrDataset(poRG);
      81             : }
      82             : 
      83             : /************************************************************************/
      84             : /*                            ExploreGroup()                            */
      85             : /************************************************************************/
      86             : 
      87         127 : static bool ExploreGroup(const std::shared_ptr<GDALGroup> &poGroup,
      88             :                          std::vector<std::string> &aosArrays, int nRecCount)
      89             : {
      90         127 :     if (nRecCount == 32)
      91             :     {
      92           0 :         CPLError(CE_Failure, CPLE_NotSupported,
      93             :                  "Too deep recursion level in ExploreGroup()");
      94           0 :         return false;
      95             :     }
      96         254 :     const auto aosGroupArrayNames = poGroup->GetMDArrayNames();
      97         337 :     for (const auto &osArrayName : aosGroupArrayNames)
      98             :     {
      99         210 :         std::string osArrayFullname = poGroup->GetFullName();
     100         210 :         if (osArrayName != "/")
     101             :         {
     102         210 :             if (osArrayFullname != "/")
     103           7 :                 osArrayFullname += '/';
     104         210 :             osArrayFullname += osArrayName;
     105             :         }
     106         210 :         aosArrays.emplace_back(std::move(osArrayFullname));
     107         210 :         if (aosArrays.size() == 10000)
     108             :         {
     109           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     110             :                      "Too many arrays found by ExploreGroup()");
     111           0 :             return false;
     112             :         }
     113             :     }
     114             : 
     115         254 :     const auto aosSubGroups = poGroup->GetGroupNames();
     116         140 :     for (const auto &osSubGroup : aosSubGroups)
     117             :     {
     118          13 :         const auto poSubGroup = poGroup->OpenGroup(osSubGroup);
     119          13 :         if (poSubGroup)
     120             :         {
     121          13 :             if (!ExploreGroup(poSubGroup, aosArrays, nRecCount + 1))
     122           0 :                 return false;
     123             :         }
     124             :     }
     125         127 :     return true;
     126             : }
     127             : 
     128             : /************************************************************************/
     129             : /*                          GetMetadataItem()                           */
     130             : /************************************************************************/
     131             : 
     132          48 : const char *ZarrDataset::GetMetadataItem(const char *pszName,
     133             :                                          const char *pszDomain)
     134             : {
     135          48 :     if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
     136           0 :         return m_aosSubdatasets.FetchNameValue(pszName);
     137          48 :     return nullptr;
     138             : }
     139             : 
     140             : /************************************************************************/
     141             : /*                            GetMetadata()                             */
     142             : /************************************************************************/
     143             : 
     144          24 : CSLConstList ZarrDataset::GetMetadata(const char *pszDomain)
     145             : {
     146          24 :     if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
     147          11 :         return m_aosSubdatasets.List();
     148          13 :     return nullptr;
     149             : }
     150             : 
     151             : /************************************************************************/
     152             : /*                       GetXYDimensionIndices()                        */
     153             : /************************************************************************/
     154             : 
     155         140 : static void GetXYDimensionIndices(const std::shared_ptr<GDALMDArray> &poArray,
     156             :                                   const GDALOpenInfo *poOpenInfo, size_t &iXDim,
     157             :                                   size_t &iYDim)
     158             : {
     159         140 :     const size_t nDims = poArray->GetDimensionCount();
     160         140 :     iYDim = nDims >= 2 ? nDims - 2 : 0;
     161         140 :     iXDim = nDims >= 2 ? nDims - 1 : 0;
     162             : 
     163         140 :     if (nDims >= 2)
     164             :     {
     165             :         const char *pszDimX =
     166         126 :             CSLFetchNameValue(poOpenInfo->papszOpenOptions, "DIM_X");
     167             :         const char *pszDimY =
     168         126 :             CSLFetchNameValue(poOpenInfo->papszOpenOptions, "DIM_Y");
     169         126 :         bool bFoundX = false;
     170         126 :         bool bFoundY = false;
     171         126 :         const auto &apoDims = poArray->GetDimensions();
     172         427 :         for (size_t i = 0; i < nDims; ++i)
     173             :         {
     174         301 :             if (pszDimX && apoDims[i]->GetName() == pszDimX)
     175             :             {
     176           1 :                 bFoundX = true;
     177           1 :                 iXDim = i;
     178             :             }
     179         300 :             else if (pszDimY && apoDims[i]->GetName() == pszDimY)
     180             :             {
     181           1 :                 bFoundY = true;
     182           1 :                 iYDim = i;
     183             :             }
     184         875 :             else if (!pszDimX &&
     185         576 :                      (apoDims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X ||
     186         287 :                       apoDims[i]->GetName() == "X"))
     187          61 :                 iXDim = i;
     188         692 :             else if (!pszDimY &&
     189         454 :                      (apoDims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y ||
     190         226 :                       apoDims[i]->GetName() == "Y"))
     191          61 :                 iYDim = i;
     192             :         }
     193         126 :         if (pszDimX)
     194             :         {
     195           4 :             if (!bFoundX && CPLGetValueType(pszDimX) == CPL_VALUE_INTEGER)
     196             :             {
     197           2 :                 const int nTmp = atoi(pszDimX);
     198           2 :                 if (nTmp >= 0 && nTmp <= static_cast<int>(nDims))
     199             :                 {
     200           2 :                     iXDim = nTmp;
     201           2 :                     bFoundX = true;
     202             :                 }
     203             :             }
     204           4 :             if (!bFoundX)
     205             :             {
     206           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
     207             :                          "Cannot find dimension DIM_X=%s", pszDimX);
     208             :             }
     209             :         }
     210         126 :         if (pszDimY)
     211             :         {
     212           4 :             if (!bFoundY && CPLGetValueType(pszDimY) == CPL_VALUE_INTEGER)
     213             :             {
     214           2 :                 const int nTmp = atoi(pszDimY);
     215           2 :                 if (nTmp >= 0 && nTmp <= static_cast<int>(nDims))
     216             :                 {
     217           2 :                     iYDim = nTmp;
     218           2 :                     bFoundY = true;
     219             :                 }
     220             :             }
     221           4 :             if (!bFoundY)
     222             :             {
     223           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
     224             :                          "Cannot find dimension DIM_Y=%s", pszDimY);
     225             :             }
     226             :         }
     227             :     }
     228         140 : }
     229             : 
     230             : /************************************************************************/
     231             : /*                       GetExtraDimSampleCount()                       */
     232             : /************************************************************************/
     233             : 
     234             : static uint64_t
     235          31 : GetExtraDimSampleCount(const std::shared_ptr<GDALMDArray> &poArray,
     236             :                        size_t iXDim, size_t iYDim)
     237             : {
     238          31 :     uint64_t nExtraDimSamples = 1;
     239          31 :     const auto &apoDims = poArray->GetDimensions();
     240         126 :     for (size_t i = 0; i < apoDims.size(); ++i)
     241             :     {
     242          95 :         if (i != iXDim && i != iYDim)
     243          35 :             nExtraDimSamples *= apoDims[i]->GetSize();
     244             :     }
     245          31 :     return nExtraDimSamples;
     246             : }
     247             : 
     248             : /************************************************************************/
     249             : /*                                Open()                                */
     250             : /************************************************************************/
     251             : 
     252        1496 : GDALDataset *ZarrDataset::Open(GDALOpenInfo *poOpenInfo)
     253             : {
     254        1496 :     if (!ZARRDriverIdentify(poOpenInfo))
     255             :     {
     256           0 :         return nullptr;
     257             :     }
     258             : 
     259             :     // Used by gdal_translate kerchunk_ref.json kerchunk_parq.parq -of ZARR -co CONVERT_TO_KERCHUNK_PARQUET_REFERENCE=YES
     260        1496 :     if (STARTS_WITH(poOpenInfo->pszFilename, "ZARR_DUMMY:"))
     261             :     {
     262             :         class ZarrDummyDataset final : public GDALDataset
     263             :         {
     264             :           public:
     265           1 :             ZarrDummyDataset()
     266           1 :             {
     267           1 :                 nRasterXSize = 0;
     268           1 :                 nRasterYSize = 0;
     269           1 :             }
     270             :         };
     271             : 
     272           2 :         auto poDS = std::make_unique<ZarrDummyDataset>();
     273           1 :         poDS->SetDescription(poOpenInfo->pszFilename + strlen("ZARR_DUMMY:"));
     274           1 :         return poDS.release();
     275             :     }
     276             : 
     277        1495 :     const bool bKerchunkCached = CPLFetchBool(poOpenInfo->papszOpenOptions,
     278             :                                               "CACHE_KERCHUNK_JSON", false);
     279             : 
     280        1495 :     if (ZARRIsLikelyKerchunkJSONRef(poOpenInfo))
     281             :     {
     282          38 :         GDALOpenInfo oOpenInfo(std::string("ZARR:\"")
     283             :                                    .append(bKerchunkCached
     284             :                                                ? JSON_REF_CACHED_FS_PREFIX
     285          19 :                                                : JSON_REF_FS_PREFIX)
     286          19 :                                    .append("{")
     287          19 :                                    .append(poOpenInfo->pszFilename)
     288          19 :                                    .append("}\"")
     289             :                                    .c_str(),
     290          38 :                                GA_ReadOnly);
     291          19 :         oOpenInfo.nOpenFlags = poOpenInfo->nOpenFlags;
     292          19 :         oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
     293          19 :         return Open(&oOpenInfo);
     294             :     }
     295        1476 :     else if (STARTS_WITH(poOpenInfo->pszFilename, JSON_REF_FS_PREFIX) ||
     296        1474 :              STARTS_WITH(poOpenInfo->pszFilename, JSON_REF_CACHED_FS_PREFIX))
     297             :     {
     298             :         GDALOpenInfo oOpenInfo(
     299           4 :             std::string("ZARR:").append(poOpenInfo->pszFilename).c_str(),
     300           4 :             GA_ReadOnly);
     301           2 :         oOpenInfo.nOpenFlags = poOpenInfo->nOpenFlags;
     302           2 :         oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
     303           2 :         return Open(&oOpenInfo);
     304             :     }
     305             : 
     306        2948 :     CPLString osFilename(poOpenInfo->pszFilename);
     307        1474 :     if (!poOpenInfo->bIsDirectory)
     308             :     {
     309         113 :         osFilename = CPLGetPathSafe(osFilename);
     310             :     }
     311        2948 :     CPLString osArrayOfInterest;
     312        2948 :     std::vector<uint64_t> anExtraDimIndices;
     313        1474 :     if (STARTS_WITH(poOpenInfo->pszFilename, "ZARR:"))
     314             :     {
     315             :         const CPLStringList aosTokens(CSLTokenizeString2(
     316          68 :             poOpenInfo->pszFilename, ":", CSLT_HONOURSTRINGS));
     317          68 :         if (aosTokens.size() < 2)
     318           2 :             return nullptr;
     319          66 :         osFilename = aosTokens[1];
     320             : 
     321         252 :         if (!cpl::starts_with(osFilename, JSON_REF_FS_PREFIX) &&
     322         107 :             !cpl::starts_with(osFilename, JSON_REF_CACHED_FS_PREFIX) &&
     323         107 :             CPLGetExtensionSafe(osFilename) == "json")
     324             :         {
     325             :             VSIStatBufL sStat;
     326           4 :             if (VSIStatL(osFilename.c_str(), &sStat) == 0 &&
     327           2 :                 !VSI_ISDIR(sStat.st_mode))
     328             :             {
     329             :                 osFilename =
     330           4 :                     std::string(bKerchunkCached ? JSON_REF_CACHED_FS_PREFIX
     331             :                                                 : JSON_REF_FS_PREFIX)
     332           2 :                         .append("{")
     333           2 :                         .append(osFilename)
     334           2 :                         .append("}");
     335             :             }
     336             :         }
     337             : 
     338          66 :         std::string osErrorMsg;
     339          66 :         if (osFilename == "http" || osFilename == "https")
     340             :         {
     341             :             osErrorMsg = "There is likely a quoting error of the whole "
     342             :                          "connection string, and the filename should "
     343           1 :                          "likely be prefixed with /vsicurl/";
     344             :         }
     345         130 :         else if (osFilename == "/vsicurl/http" ||
     346          65 :                  osFilename == "/vsicurl/https")
     347             :         {
     348             :             osErrorMsg = "There is likely a quoting error of the whole "
     349           1 :                          "connection string.";
     350             :         }
     351         128 :         else if (STARTS_WITH(osFilename.c_str(), "http://") ||
     352          64 :                  STARTS_WITH(osFilename.c_str(), "https://"))
     353             :         {
     354             :             osErrorMsg =
     355           1 :                 "The filename should likely be prefixed with /vsicurl/";
     356             :         }
     357          66 :         if (!osErrorMsg.empty())
     358             :         {
     359           3 :             CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrorMsg.c_str());
     360           3 :             return nullptr;
     361             :         }
     362          63 :         if (aosTokens.size() >= 3)
     363             :         {
     364          21 :             osArrayOfInterest = aosTokens[2];
     365          39 :             for (int i = 3; i < aosTokens.size(); ++i)
     366             :             {
     367          18 :                 anExtraDimIndices.push_back(
     368          18 :                     static_cast<uint64_t>(CPLAtoGIntBig(aosTokens[i])));
     369             :             }
     370             :         }
     371             :     }
     372             : 
     373             :     auto poDSMultiDim = std::unique_ptr<GDALDataset>(
     374        1469 :         OpenMultidim(osFilename.c_str(), poOpenInfo->eAccess == GA_Update,
     375        2938 :                      poOpenInfo->papszOpenOptions));
     376        2836 :     if (poDSMultiDim == nullptr ||
     377        1367 :         (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER) != 0)
     378             :     {
     379        1334 :         return poDSMultiDim.release();
     380             :     }
     381             : 
     382         270 :     auto poRG = poDSMultiDim->GetRootGroup();
     383             : 
     384         270 :     auto poDS = std::make_unique<ZarrDataset>(nullptr);
     385         135 :     std::shared_ptr<GDALMDArray> poMainArray;
     386         270 :     std::vector<std::string> aosArrays;
     387         270 :     std::string osMainArray;
     388         135 :     const bool bMultiband = CPLTestBool(
     389         135 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MULTIBAND", "YES"));
     390         135 :     size_t iXDim = 0;
     391         135 :     size_t iYDim = 0;
     392             : 
     393         135 :     if (!osArrayOfInterest.empty())
     394             :     {
     395          21 :         poMainArray = osArrayOfInterest == "/"
     396          42 :                           ? poRG->OpenMDArray("/")
     397          21 :                           : poRG->OpenMDArrayFromFullname(osArrayOfInterest);
     398          21 :         if (poMainArray == nullptr)
     399           1 :             return nullptr;
     400          20 :         GetXYDimensionIndices(poMainArray, poOpenInfo, iXDim, iYDim);
     401             : 
     402          20 :         if (poMainArray->GetDimensionCount() > 2)
     403             :         {
     404          11 :             if (anExtraDimIndices.empty())
     405             :             {
     406             :                 const uint64_t nExtraDimSamples =
     407           1 :                     GetExtraDimSampleCount(poMainArray, iXDim, iYDim);
     408           1 :                 if (bMultiband)
     409             :                 {
     410           0 :                     if (nExtraDimSamples > 65536)  // arbitrary limit
     411             :                     {
     412           0 :                         if (poMainArray->GetDimensionCount() == 3)
     413             :                         {
     414           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
     415             :                                      "Too many samples along the > 2D "
     416             :                                      "dimensions of %s. "
     417             :                                      "Use ZARR:\"%s\":%s:{i} syntax",
     418             :                                      osArrayOfInterest.c_str(),
     419             :                                      osFilename.c_str(),
     420             :                                      osArrayOfInterest.c_str());
     421             :                         }
     422             :                         else
     423             :                         {
     424           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
     425             :                                      "Too many samples along the > 2D "
     426             :                                      "dimensions of %s. "
     427             :                                      "Use ZARR:\"%s\":%s:{i}:{j} syntax",
     428             :                                      osArrayOfInterest.c_str(),
     429             :                                      osFilename.c_str(),
     430             :                                      osArrayOfInterest.c_str());
     431             :                         }
     432           0 :                         return nullptr;
     433             :                     }
     434             :                 }
     435           1 :                 else if (nExtraDimSamples != 1)
     436             :                 {
     437           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     438             :                              "Indices of extra dimensions must be specified");
     439           1 :                     return nullptr;
     440             :                 }
     441             :             }
     442          10 :             else if (anExtraDimIndices.size() !=
     443          10 :                      poMainArray->GetDimensionCount() - 2)
     444             :             {
     445           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     446             :                          "Wrong number of indices of extra dimensions");
     447           1 :                 return nullptr;
     448             :             }
     449             :             else
     450             :             {
     451          23 :                 for (const auto idx : anExtraDimIndices)
     452             :                 {
     453          15 :                     poMainArray = poMainArray->at(idx);
     454          15 :                     if (poMainArray == nullptr)
     455           1 :                         return nullptr;
     456             :                 }
     457           8 :                 GetXYDimensionIndices(poMainArray, poOpenInfo, iXDim, iYDim);
     458             :             }
     459             :         }
     460           9 :         else if (!anExtraDimIndices.empty())
     461             :         {
     462           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Unexpected extra indices");
     463           1 :             return nullptr;
     464             :         }
     465             :     }
     466             :     else
     467             :     {
     468         114 :         ExploreGroup(poRG, aosArrays, 0);
     469         114 :         if (aosArrays.empty())
     470           0 :             return nullptr;
     471             : 
     472         114 :         const bool bListAllArrays = CPLTestBool(CSLFetchNameValueDef(
     473         114 :             poOpenInfo->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
     474             : 
     475         114 :         if (!bListAllArrays)
     476             :         {
     477         112 :             if (aosArrays.size() == 1)
     478             :             {
     479          65 :                 poMainArray = poRG->OpenMDArrayFromFullname(aosArrays[0]);
     480          65 :                 if (poMainArray)
     481          65 :                     osMainArray = poMainArray->GetFullName();
     482             :             }
     483             :             else  // at least 2 arrays
     484             :             {
     485         187 :                 for (const auto &osArrayName : aosArrays)
     486             :                 {
     487         140 :                     auto poArray = poRG->OpenMDArrayFromFullname(osArrayName);
     488         140 :                     if (poArray && poArray->GetDimensionCount() >= 2)
     489             :                     {
     490          47 :                         if (osMainArray.empty())
     491             :                         {
     492          47 :                             poMainArray = std::move(poArray);
     493          47 :                             osMainArray = osArrayName;
     494             :                         }
     495             :                         else
     496             :                         {
     497           0 :                             poMainArray.reset();
     498           0 :                             osMainArray.clear();
     499           0 :                             break;
     500             :                         }
     501             :                     }
     502             :                 }
     503             :             }
     504             : 
     505         112 :             if (poMainArray)
     506         112 :                 GetXYDimensionIndices(poMainArray, poOpenInfo, iXDim, iYDim);
     507             :         }
     508             : 
     509         114 :         int iCountSubDS = 1;
     510             : 
     511         114 :         if (poMainArray && poMainArray->GetDimensionCount() > 2)
     512             :         {
     513          30 :             const auto &apoDims = poMainArray->GetDimensions();
     514             :             const uint64_t nExtraDimSamples =
     515          30 :                 GetExtraDimSampleCount(poMainArray, iXDim, iYDim);
     516          30 :             if (nExtraDimSamples > 65536)  // arbitrary limit
     517             :             {
     518           3 :                 if (apoDims.size() == 3)
     519             :                 {
     520           2 :                     CPLError(
     521             :                         CE_Warning, CPLE_AppDefined,
     522             :                         "Too many samples along the > 2D dimensions of %s. "
     523             :                         "Use ZARR:\"%s\":%s:{i} syntax",
     524             :                         osMainArray.c_str(), osFilename.c_str(),
     525             :                         osMainArray.c_str());
     526             :                 }
     527             :                 else
     528             :                 {
     529           1 :                     CPLError(
     530             :                         CE_Warning, CPLE_AppDefined,
     531             :                         "Too many samples along the > 2D dimensions of %s. "
     532             :                         "Use ZARR:\"%s\":%s:{i}:{j} syntax",
     533             :                         osMainArray.c_str(), osFilename.c_str(),
     534             :                         osMainArray.c_str());
     535             :                 }
     536             :             }
     537          27 :             else if (nExtraDimSamples > 1 && bMultiband)
     538             :             {
     539             :                 // nothing to do
     540             :             }
     541           2 :             else if (nExtraDimSamples > 1 && apoDims.size() == 3)
     542             :             {
     543           3 :                 for (int i = 0; i < static_cast<int>(nExtraDimSamples); ++i)
     544             :                 {
     545           2 :                     poDS->m_aosSubdatasets.AddString(CPLSPrintf(
     546             :                         "SUBDATASET_%d_NAME=ZARR:\"%s\":%s:%d", iCountSubDS,
     547           2 :                         osFilename.c_str(), osMainArray.c_str(), i));
     548           2 :                     poDS->m_aosSubdatasets.AddString(CPLSPrintf(
     549             :                         "SUBDATASET_%d_DESC=Array %s at index %d of %s",
     550             :                         iCountSubDS, osMainArray.c_str(), i,
     551           2 :                         apoDims[0]->GetName().c_str()));
     552           2 :                     ++iCountSubDS;
     553             :                 }
     554             :             }
     555           1 :             else if (nExtraDimSamples > 1)
     556             :             {
     557           1 :                 int nDimIdxI = 0;
     558           1 :                 int nDimIdxJ = 0;
     559           7 :                 for (int i = 0; i < static_cast<int>(nExtraDimSamples); ++i)
     560             :                 {
     561           6 :                     poDS->m_aosSubdatasets.AddString(
     562             :                         CPLSPrintf("SUBDATASET_%d_NAME=ZARR:\"%s\":%s:%d:%d",
     563             :                                    iCountSubDS, osFilename.c_str(),
     564           6 :                                    osMainArray.c_str(), nDimIdxI, nDimIdxJ));
     565           6 :                     poDS->m_aosSubdatasets.AddString(
     566             :                         CPLSPrintf("SUBDATASET_%d_DESC=Array %s at "
     567             :                                    "index %d of %s and %d of %s",
     568             :                                    iCountSubDS, osMainArray.c_str(), nDimIdxI,
     569           6 :                                    apoDims[0]->GetName().c_str(), nDimIdxJ,
     570          12 :                                    apoDims[1]->GetName().c_str()));
     571           6 :                     ++iCountSubDS;
     572           6 :                     ++nDimIdxJ;
     573           6 :                     if (nDimIdxJ == static_cast<int>(apoDims[1]->GetSize()))
     574             :                     {
     575           3 :                         nDimIdxJ = 0;
     576           3 :                         ++nDimIdxI;
     577             :                     }
     578             :                 }
     579             :             }
     580             :         }
     581             : 
     582         114 :         if (bListAllArrays || aosArrays.size() >= 2)
     583             :         {
     584         194 :             for (size_t i = 0; i < aosArrays.size(); ++i)
     585             :             {
     586         290 :                 auto poArray = poRG->OpenMDArrayFromFullname(aosArrays[i]);
     587         145 :                 if (poArray)
     588             :                 {
     589         145 :                     bool bAddSubDS = false;
     590         145 :                     if (bListAllArrays)
     591             :                     {
     592           5 :                         bAddSubDS = true;
     593             :                     }
     594         140 :                     else if (poArray->GetDimensionCount() >= 2)
     595             :                     {
     596          47 :                         bAddSubDS = true;
     597             :                     }
     598         145 :                     if (bAddSubDS)
     599             :                     {
     600         104 :                         std::string osDim;
     601          52 :                         const auto &apoDims = poArray->GetDimensions();
     602         171 :                         for (const auto &poDim : apoDims)
     603             :                         {
     604         119 :                             if (!osDim.empty())
     605          67 :                                 osDim += "x";
     606             :                             osDim += CPLSPrintf(
     607             :                                 "%" PRIu64,
     608         119 :                                 static_cast<uint64_t>(poDim->GetSize()));
     609             :                         }
     610             : 
     611          52 :                         std::string osDataType;
     612          52 :                         if (poArray->GetDataType().GetClass() == GEDTC_STRING)
     613             :                         {
     614           0 :                             osDataType = "string type";
     615             :                         }
     616          52 :                         else if (poArray->GetDataType().GetClass() ==
     617             :                                  GEDTC_NUMERIC)
     618             :                         {
     619             :                             osDataType = GDALGetDataTypeName(
     620          52 :                                 poArray->GetDataType().GetNumericDataType());
     621             :                         }
     622             :                         else
     623             :                         {
     624           0 :                             osDataType = "compound type";
     625             :                         }
     626             : 
     627          52 :                         poDS->m_aosSubdatasets.AddString(CPLSPrintf(
     628             :                             "SUBDATASET_%d_NAME=ZARR:\"%s\":%s", iCountSubDS,
     629          52 :                             osFilename.c_str(), aosArrays[i].c_str()));
     630          52 :                         poDS->m_aosSubdatasets.AddString(CPLSPrintf(
     631             :                             "SUBDATASET_%d_DESC=[%s] %s (%s)", iCountSubDS,
     632          52 :                             osDim.c_str(), aosArrays[i].c_str(),
     633         104 :                             osDataType.c_str()));
     634          52 :                         ++iCountSubDS;
     635             :                     }
     636             :                 }
     637             :             }
     638             :         }
     639             :     }
     640             : 
     641         130 :     if (poMainArray && (bMultiband || poMainArray->GetDimensionCount() <= 2))
     642             :     {
     643             :         // Pass papszOpenOptions for LOAD_EXTRA_DIM_METADATA_DELAY
     644             :         auto poNewDS =
     645         124 :             std::unique_ptr<GDALDataset>(poMainArray->AsClassicDataset(
     646         248 :                 iXDim, iYDim, poRG, poOpenInfo->papszOpenOptions));
     647         124 :         if (!poNewDS)
     648           3 :             return nullptr;
     649             : 
     650         121 :         if (poMainArray->GetDimensionCount() >= 2)
     651             :         {
     652             :             // If we have 3 arrays, check that the 2 ones that are not the main
     653             :             // 2D array are indexing variables of its dimensions. If so, don't
     654             :             // expose them as subdatasets
     655         108 :             if (aosArrays.size() == 3)
     656             :             {
     657          84 :                 std::vector<std::string> aosOtherArrays;
     658         168 :                 for (size_t i = 0; i < aosArrays.size(); ++i)
     659             :                 {
     660         126 :                     if (aosArrays[i] != osMainArray)
     661             :                     {
     662          84 :                         aosOtherArrays.emplace_back(aosArrays[i]);
     663             :                     }
     664             :                 }
     665          42 :                 bool bMatchFound[] = {false, false};
     666         126 :                 for (int i = 0; i < 2; i++)
     667             :                 {
     668             :                     auto poIndexingVar =
     669          84 :                         poMainArray->GetDimensions()[i == 0 ? iXDim : iYDim]
     670         168 :                             ->GetIndexingVariable();
     671          84 :                     if (poIndexingVar)
     672             :                     {
     673         126 :                         for (int j = 0; j < 2; j++)
     674             :                         {
     675         124 :                             if (aosOtherArrays[j] ==
     676         124 :                                 poIndexingVar->GetFullName())
     677             :                             {
     678          80 :                                 bMatchFound[i] = true;
     679          80 :                                 break;
     680             :                             }
     681             :                         }
     682             :                     }
     683             :                 }
     684          42 :                 if (bMatchFound[0] && bMatchFound[1])
     685             :                 {
     686          40 :                     poDS->m_aosSubdatasets.Clear();
     687             :                 }
     688             :             }
     689             :         }
     690         121 :         if (!poDS->m_aosSubdatasets.empty())
     691             :         {
     692           7 :             poNewDS->SetMetadata(poDS->m_aosSubdatasets.List(), "SUBDATASETS");
     693             :         }
     694         121 :         return poNewDS.release();
     695             :     }
     696             : 
     697           6 :     return poDS.release();
     698             : }
     699             : 
     700             : /************************************************************************/
     701             : /*                         ZarrDatasetDelete()                          */
     702             : /************************************************************************/
     703             : 
     704           4 : static CPLErr ZarrDatasetDelete(const char *pszFilename)
     705             : {
     706           4 :     if (STARTS_WITH(pszFilename, "ZARR:"))
     707             :     {
     708           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     709             :                  "Delete() only supported on ZARR connection names "
     710             :                  "not starting with the ZARR: prefix");
     711           0 :         return CE_Failure;
     712             :     }
     713           4 :     return VSIRmdirRecursive(pszFilename) == 0 ? CE_None : CE_Failure;
     714             : }
     715             : 
     716             : /************************************************************************/
     717             : /*                         ZarrDatasetRename()                          */
     718             : /************************************************************************/
     719             : 
     720           2 : static CPLErr ZarrDatasetRename(const char *pszNewName, const char *pszOldName)
     721             : {
     722           2 :     if (STARTS_WITH(pszNewName, "ZARR:") || STARTS_WITH(pszOldName, "ZARR:"))
     723             :     {
     724           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     725             :                  "Rename() only supported on ZARR connection names "
     726             :                  "not starting with the ZARR: prefix");
     727           0 :         return CE_Failure;
     728             :     }
     729           2 :     return VSIRename(pszOldName, pszNewName) == 0 ? CE_None : CE_Failure;
     730             : }
     731             : 
     732             : /************************************************************************/
     733             : /*                        ZarrDatasetCopyFiles()                        */
     734             : /************************************************************************/
     735             : 
     736           2 : static CPLErr ZarrDatasetCopyFiles(const char *pszNewName,
     737             :                                    const char *pszOldName)
     738             : {
     739           2 :     if (STARTS_WITH(pszNewName, "ZARR:") || STARTS_WITH(pszOldName, "ZARR:"))
     740             :     {
     741           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     742             :                  "CopyFiles() only supported on ZARR connection names "
     743             :                  "not starting with the ZARR: prefix");
     744           0 :         return CE_Failure;
     745             :     }
     746             :     // VSISync() returns true in case of success
     747           4 :     return VSISync((std::string(pszOldName) + '/').c_str(), pszNewName, nullptr,
     748             :                    nullptr, nullptr, nullptr)
     749           2 :                ? CE_None
     750           2 :                : CE_Failure;
     751             : }
     752             : 
     753             : /************************************************************************/
     754             : /*                       ZarrDriverClearCaches()                        */
     755             : /************************************************************************/
     756             : 
     757           2 : static void ZarrDriverClearCaches(GDALDriver *)
     758             : {
     759           2 :     ZarrClearCoordinateCache();
     760           2 : }
     761             : 
     762             : /************************************************************************/
     763             : /*                             ZarrDriver()                             */
     764             : /************************************************************************/
     765             : 
     766             : class ZarrDriver final : public GDALDriver
     767             : {
     768             :     std::recursive_mutex m_oMutex{};
     769             :     bool m_bMetadataInitialized = false;
     770             :     void InitMetadata();
     771             : 
     772             :   public:
     773             :     const char *GetMetadataItem(const char *pszName,
     774             :                                 const char *pszDomain) override;
     775             : 
     776         445 :     CSLConstList GetMetadata(const char *pszDomain) override
     777             :     {
     778         890 :         std::lock_guard oLock(m_oMutex);
     779         445 :         InitMetadata();
     780         890 :         return GDALDriver::GetMetadata(pszDomain);
     781             :     }
     782             : };
     783             : 
     784       35780 : const char *ZarrDriver::GetMetadataItem(const char *pszName,
     785             :                                         const char *pszDomain)
     786             : {
     787       71560 :     std::lock_guard oLock(m_oMutex);
     788       35780 :     if (EQUAL(pszName, "COMPRESSORS") || EQUAL(pszName, "BLOSC_COMPRESSORS") ||
     789       35753 :         EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST) ||
     790       35412 :         EQUAL(pszName, GDAL_DMD_MULTIDIM_ARRAY_CREATIONOPTIONLIST))
     791             :     {
     792         369 :         InitMetadata();
     793             :     }
     794       71560 :     return GDALDriver::GetMetadataItem(pszName, pszDomain);
     795             : }
     796             : 
     797         814 : void ZarrDriver::InitMetadata()
     798             : {
     799         814 :     if (m_bMetadataInitialized)
     800         604 :         return;
     801         210 :     m_bMetadataInitialized = true;
     802             : 
     803             :     {
     804             :         // A bit of a hack. Normally GetMetadata() should also return it,
     805             :         // but as this is only used for tests, just make GetMetadataItem()
     806             :         // handle it
     807         420 :         std::string osCompressors;
     808         420 :         std::string osFilters;
     809         210 :         char **decompressors = CPLGetDecompressors();
     810        1680 :         for (auto iter = decompressors; iter && *iter; ++iter)
     811             :         {
     812        1470 :             const auto psCompressor = CPLGetCompressor(*iter);
     813        1470 :             if (psCompressor)
     814             :             {
     815        1470 :                 if (psCompressor->eType == CCT_COMPRESSOR)
     816             :                 {
     817        1260 :                     if (!osCompressors.empty())
     818        1050 :                         osCompressors += ',';
     819        1260 :                     osCompressors += *iter;
     820             :                 }
     821         210 :                 else if (psCompressor->eType == CCT_FILTER)
     822             :                 {
     823         210 :                     if (!osFilters.empty())
     824           0 :                         osFilters += ',';
     825         210 :                     osFilters += *iter;
     826             :                 }
     827             :             }
     828             :         }
     829         210 :         CSLDestroy(decompressors);
     830         210 :         GDALDriver::SetMetadataItem("COMPRESSORS", osCompressors.c_str());
     831         210 :         GDALDriver::SetMetadataItem("FILTERS", osFilters.c_str());
     832             :     }
     833             : #ifdef HAVE_BLOSC
     834             :     {
     835         210 :         GDALDriver::SetMetadataItem("BLOSC_COMPRESSORS",
     836             :                                     blosc_list_compressors());
     837             :     }
     838             : #endif
     839             : 
     840             :     {
     841             :         CPLXMLTreeCloser oTree(
     842         420 :             CPLCreateXMLNode(nullptr, CXT_Element, "CreationOptionList"));
     843         210 :         char **compressors = CPLGetCompressors();
     844             : 
     845             :         auto psCompressNode =
     846         210 :             CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
     847         210 :         CPLAddXMLAttributeAndValue(psCompressNode, "name", "COMPRESS");
     848         210 :         CPLAddXMLAttributeAndValue(psCompressNode, "type", "string-select");
     849         210 :         CPLAddXMLAttributeAndValue(psCompressNode, "description",
     850             :                                    "Compression method");
     851         210 :         CPLAddXMLAttributeAndValue(psCompressNode, "default", "NONE");
     852             :         {
     853             :             auto poValueNode =
     854         210 :                 CPLCreateXMLNode(psCompressNode, CXT_Element, "Value");
     855         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "NONE");
     856             :         }
     857             : 
     858             :         auto psFilterNode =
     859         210 :             CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
     860         210 :         CPLAddXMLAttributeAndValue(psFilterNode, "name", "FILTER");
     861         210 :         CPLAddXMLAttributeAndValue(psFilterNode, "type", "string-select");
     862         210 :         CPLAddXMLAttributeAndValue(psFilterNode, "description",
     863             :                                    "Filter method (only for ZARR_V2)");
     864         210 :         CPLAddXMLAttributeAndValue(psFilterNode, "default", "NONE");
     865             :         {
     866             :             auto poValueNode =
     867         210 :                 CPLCreateXMLNode(psFilterNode, CXT_Element, "Value");
     868         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "NONE");
     869             :         }
     870             : 
     871             :         auto psBlockSizeNode =
     872         210 :             CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
     873         210 :         CPLAddXMLAttributeAndValue(psBlockSizeNode, "name", "BLOCKSIZE");
     874         210 :         CPLAddXMLAttributeAndValue(psBlockSizeNode, "type", "string");
     875         210 :         CPLAddXMLAttributeAndValue(
     876             :             psBlockSizeNode, "description",
     877             :             "Comma separated list of chunk size along each dimension");
     878             : 
     879             :         auto psChunkMemoryLayout =
     880         210 :             CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
     881         210 :         CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "name",
     882             :                                    "CHUNK_MEMORY_LAYOUT");
     883         210 :         CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "type",
     884             :                                    "string-select");
     885         210 :         CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "description",
     886             :                                    "Whether to use C (row-major) order or F "
     887             :                                    "(column-major) order in chunks");
     888         210 :         CPLAddXMLAttributeAndValue(psChunkMemoryLayout, "default", "C");
     889             :         {
     890             :             auto poValueNode =
     891         210 :                 CPLCreateXMLNode(psChunkMemoryLayout, CXT_Element, "Value");
     892         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "C");
     893             :         }
     894             :         {
     895             :             auto poValueNode =
     896         210 :                 CPLCreateXMLNode(psChunkMemoryLayout, CXT_Element, "Value");
     897         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "F");
     898             :         }
     899             : 
     900             :         auto psStringFormat =
     901         210 :             CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
     902         210 :         CPLAddXMLAttributeAndValue(psStringFormat, "name", "STRING_FORMAT");
     903         210 :         CPLAddXMLAttributeAndValue(psStringFormat, "type", "string-select");
     904         210 :         CPLAddXMLAttributeAndValue(psStringFormat, "default", "STRING");
     905             :         {
     906             :             auto poValueNode =
     907         210 :                 CPLCreateXMLNode(psStringFormat, CXT_Element, "Value");
     908         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "STRING");
     909             :         }
     910             :         {
     911             :             auto poValueNode =
     912         210 :                 CPLCreateXMLNode(psStringFormat, CXT_Element, "Value");
     913         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "UNICODE");
     914             :         }
     915             : 
     916             :         auto psDimSeparatorNode =
     917         210 :             CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
     918         210 :         CPLAddXMLAttributeAndValue(psDimSeparatorNode, "name", "DIM_SEPARATOR");
     919         210 :         CPLAddXMLAttributeAndValue(psDimSeparatorNode, "type", "string");
     920         210 :         CPLAddXMLAttributeAndValue(
     921             :             psDimSeparatorNode, "description",
     922             :             "Dimension separator in chunk filenames. Default to decimal point "
     923             :             "for ZarrV2 and slash for ZarrV3");
     924             : 
     925        1680 :         for (auto iter = compressors; iter && *iter; ++iter)
     926             :         {
     927        1470 :             const auto psCompressor = CPLGetCompressor(*iter);
     928        1470 :             if (psCompressor)
     929             :             {
     930        1470 :                 auto poValueNode = CPLCreateXMLNode(
     931        1470 :                     (psCompressor->eType == CCT_COMPRESSOR) ? psCompressNode
     932             :                                                             : psFilterNode,
     933             :                     CXT_Element, "Value");
     934        1470 :                 CPLCreateXMLNode(poValueNode, CXT_Text,
     935        2940 :                                  CPLString(*iter).toupper().c_str());
     936             : 
     937             :                 const char *pszOptions =
     938        1470 :                     CSLFetchNameValue(psCompressor->papszMetadata, "OPTIONS");
     939        1470 :                 if (pszOptions)
     940             :                 {
     941             :                     CPLXMLTreeCloser oTreeCompressor(
     942        2940 :                         CPLParseXMLString(pszOptions));
     943             :                     const auto psRoot =
     944        1470 :                         oTreeCompressor.get()
     945        1470 :                             ? CPLGetXMLNode(oTreeCompressor.get(), "=Options")
     946        1470 :                             : nullptr;
     947        1470 :                     if (psRoot)
     948             :                     {
     949        1470 :                         for (CPLXMLNode *psNode = psRoot->psChild;
     950        4620 :                              psNode != nullptr; psNode = psNode->psNext)
     951             :                         {
     952        3150 :                             if (psNode->eType == CXT_Element)
     953             :                             {
     954             :                                 const char *pszName =
     955        3150 :                                     CPLGetXMLValue(psNode, "name", nullptr);
     956        3150 :                                 if (pszName &&
     957        3150 :                                     !EQUAL(pszName, "TYPESIZE")   // Blosc
     958        2940 :                                     && !EQUAL(pszName, "HEADER")  // LZ4
     959             :                                 )
     960             :                                 {
     961        2730 :                                     CPLXMLNode *psNext = psNode->psNext;
     962        2730 :                                     psNode->psNext = nullptr;
     963             :                                     CPLXMLNode *psOption =
     964        2730 :                                         CPLCloneXMLTree(psNode);
     965             : 
     966             :                                     CPLXMLNode *psName =
     967        2730 :                                         CPLGetXMLNode(psOption, "name");
     968        2730 :                                     if (psName &&
     969        2730 :                                         psName->eType == CXT_Attribute &&
     970        2730 :                                         psName->psChild &&
     971        2730 :                                         psName->psChild->pszValue)
     972             :                                     {
     973        2730 :                                         CPLString osNewValue(*iter);
     974        2730 :                                         osNewValue = osNewValue.toupper();
     975        2730 :                                         osNewValue += '_';
     976        2730 :                                         osNewValue += psName->psChild->pszValue;
     977        2730 :                                         CPLFree(psName->psChild->pszValue);
     978        5460 :                                         psName->psChild->pszValue =
     979        2730 :                                             CPLStrdup(osNewValue.c_str());
     980             :                                     }
     981             : 
     982             :                                     CPLXMLNode *psDescription =
     983        2730 :                                         CPLGetXMLNode(psOption, "description");
     984        2730 :                                     if (psDescription &&
     985        2730 :                                         psDescription->eType == CXT_Attribute &&
     986        2730 :                                         psDescription->psChild &&
     987        2730 :                                         psDescription->psChild->pszValue)
     988             :                                     {
     989             :                                         std::string osNewValue(
     990        2730 :                                             psDescription->psChild->pszValue);
     991        2730 :                                         if (psCompressor->eType ==
     992             :                                             CCT_COMPRESSOR)
     993             :                                         {
     994             :                                             osNewValue +=
     995        2520 :                                                 ". Only used when COMPRESS=";
     996             :                                         }
     997             :                                         else
     998             :                                         {
     999             :                                             osNewValue +=
    1000         210 :                                                 ". Only used when FILTER=";
    1001             :                                         }
    1002             :                                         osNewValue +=
    1003        2730 :                                             CPLString(*iter).toupper();
    1004        2730 :                                         CPLFree(
    1005        2730 :                                             psDescription->psChild->pszValue);
    1006        5460 :                                         psDescription->psChild->pszValue =
    1007        2730 :                                             CPLStrdup(osNewValue.c_str());
    1008             :                                     }
    1009             : 
    1010        2730 :                                     CPLAddXMLChild(oTree.get(), psOption);
    1011        2730 :                                     psNode->psNext = psNext;
    1012             :                                 }
    1013             :                             }
    1014             :                         }
    1015             :                     }
    1016             :                 }
    1017             :             }
    1018             :         }
    1019         210 :         CSLDestroy(compressors);
    1020             : 
    1021             :         auto psGeoreferencingConvention =
    1022         210 :             CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1023         210 :         CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "name",
    1024             :                                    "GEOREFERENCING_CONVENTION");
    1025         210 :         CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "type",
    1026             :                                    "string-select");
    1027         210 :         CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "default",
    1028             :                                    "GDAL");
    1029         210 :         CPLAddXMLAttributeAndValue(psGeoreferencingConvention, "description",
    1030             :                                    "Georeferencing convention to use");
    1031             : 
    1032             :         {
    1033         210 :             auto poValueNode = CPLCreateXMLNode(psGeoreferencingConvention,
    1034             :                                                 CXT_Element, "Value");
    1035         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "GDAL");
    1036             :         }
    1037             :         {
    1038         210 :             auto poValueNode = CPLCreateXMLNode(psGeoreferencingConvention,
    1039             :                                                 CXT_Element, "Value");
    1040         210 :             CPLCreateXMLNode(poValueNode, CXT_Text, "SPATIAL_PROJ");
    1041             :         }
    1042             : 
    1043             :         {
    1044         210 :             char *pszXML = CPLSerializeXMLTree(oTree.get());
    1045         210 :             GDALDriver::SetMetadataItem(
    1046             :                 GDAL_DMD_MULTIDIM_ARRAY_CREATIONOPTIONLIST,
    1047         210 :                 CPLString(pszXML)
    1048             :                     .replaceAll("CreationOptionList",
    1049         420 :                                 "MultiDimArrayCreationOptionList")
    1050             :                     .c_str());
    1051         210 :             CPLFree(pszXML);
    1052             :         }
    1053             : 
    1054             :         {
    1055             :             auto psArrayNameOption =
    1056         210 :                 CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1057         210 :             CPLAddXMLAttributeAndValue(psArrayNameOption, "name", "ARRAY_NAME");
    1058         210 :             CPLAddXMLAttributeAndValue(psArrayNameOption, "type", "string");
    1059         210 :             CPLAddXMLAttributeAndValue(
    1060             :                 psArrayNameOption, "description",
    1061             :                 "Array name. If not specified, deduced from the filename");
    1062             : 
    1063             :             auto psAppendSubDSOption =
    1064         210 :                 CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1065         210 :             CPLAddXMLAttributeAndValue(psAppendSubDSOption, "name",
    1066             :                                        "APPEND_SUBDATASET");
    1067         210 :             CPLAddXMLAttributeAndValue(psAppendSubDSOption, "type", "boolean");
    1068         210 :             CPLAddXMLAttributeAndValue(psAppendSubDSOption, "description",
    1069             :                                        "Whether to append the new dataset to "
    1070             :                                        "an existing Zarr hierarchy");
    1071         210 :             CPLAddXMLAttributeAndValue(psAppendSubDSOption, "default", "NO");
    1072             : 
    1073             :             auto psFormat =
    1074         210 :                 CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1075         210 :             CPLAddXMLAttributeAndValue(psFormat, "name", "FORMAT");
    1076         210 :             CPLAddXMLAttributeAndValue(psFormat, "type", "string-select");
    1077         210 :             CPLAddXMLAttributeAndValue(psFormat, "default", "ZARR_V2");
    1078             :             {
    1079             :                 auto poValueNode =
    1080         210 :                     CPLCreateXMLNode(psFormat, CXT_Element, "Value");
    1081         210 :                 CPLCreateXMLNode(poValueNode, CXT_Text, "ZARR_V2");
    1082             :             }
    1083             :             {
    1084             :                 auto poValueNode =
    1085         210 :                     CPLCreateXMLNode(psFormat, CXT_Element, "Value");
    1086         210 :                 CPLCreateXMLNode(poValueNode, CXT_Text, "ZARR_V3");
    1087             :             }
    1088             : 
    1089             :             auto psCreateZMetadata =
    1090         210 :                 CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1091         210 :             CPLAddXMLAttributeAndValue(psCreateZMetadata, "name",
    1092             :                                        "CREATE_CONSOLIDATED_METADATA");
    1093         210 :             CPLAddXMLAttributeAndValue(psCreateZMetadata, "alias",
    1094             :                                        "CREATE_ZMETADATA");
    1095         210 :             CPLAddXMLAttributeAndValue(psCreateZMetadata, "type", "boolean");
    1096         210 :             CPLAddXMLAttributeAndValue(
    1097             :                 psCreateZMetadata, "description",
    1098             :                 "Whether to create consolidated metadata");
    1099         210 :             CPLAddXMLAttributeAndValue(psCreateZMetadata, "default", "YES");
    1100             : 
    1101             :             auto psSingleArrayNode =
    1102         210 :                 CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1103         210 :             CPLAddXMLAttributeAndValue(psSingleArrayNode, "name",
    1104             :                                        "SINGLE_ARRAY");
    1105         210 :             CPLAddXMLAttributeAndValue(psSingleArrayNode, "type", "boolean");
    1106         210 :             CPLAddXMLAttributeAndValue(
    1107             :                 psSingleArrayNode, "description",
    1108             :                 "Whether to write a multi-band dataset as a single array, or "
    1109             :                 "one array per band");
    1110         210 :             CPLAddXMLAttributeAndValue(psSingleArrayNode, "default", "YES");
    1111             : 
    1112             :             auto psInterleaveNode =
    1113         210 :                 CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1114         210 :             CPLAddXMLAttributeAndValue(psInterleaveNode, "name", "INTERLEAVE");
    1115         210 :             CPLAddXMLAttributeAndValue(psInterleaveNode, "type",
    1116             :                                        "string-select");
    1117         210 :             CPLAddXMLAttributeAndValue(psInterleaveNode, "default", "BAND");
    1118             :             {
    1119             :                 auto poValueNode =
    1120         210 :                     CPLCreateXMLNode(psInterleaveNode, CXT_Element, "Value");
    1121         210 :                 CPLCreateXMLNode(poValueNode, CXT_Text, "BAND");
    1122             :             }
    1123             :             {
    1124             :                 auto poValueNode =
    1125         210 :                     CPLCreateXMLNode(psInterleaveNode, CXT_Element, "Value");
    1126         210 :                 CPLCreateXMLNode(poValueNode, CXT_Text, "PIXEL");
    1127             :             }
    1128             : 
    1129             :             auto psConvertToParquet =
    1130         210 :                 CPLCreateXMLNode(oTree.get(), CXT_Element, "Option");
    1131         210 :             CPLAddXMLAttributeAndValue(psConvertToParquet, "name",
    1132             :                                        "CONVERT_TO_KERCHUNK_PARQUET_REFERENCE");
    1133         210 :             CPLAddXMLAttributeAndValue(psConvertToParquet, "type", "boolean");
    1134         210 :             CPLAddXMLAttributeAndValue(
    1135             :                 psConvertToParquet, "description",
    1136             :                 "Whether to convert a Kerchunk JSON reference store to a "
    1137             :                 "Kerchunk Parquet reference store. (CreateCopy() only)");
    1138             : 
    1139         210 :             char *pszXML = CPLSerializeXMLTree(oTree.get());
    1140         210 :             GDALDriver::SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST, pszXML);
    1141         210 :             CPLFree(pszXML);
    1142             :         }
    1143             :     }
    1144             : }
    1145             : 
    1146             : /************************************************************************/
    1147             : /*                       CreateMultiDimensional()                       */
    1148             : /************************************************************************/
    1149             : 
    1150             : GDALDataset *
    1151         240 : ZarrDataset::CreateMultiDimensional(const char *pszFilename,
    1152             :                                     CSLConstList /*papszRootGroupOptions*/,
    1153             :                                     CSLConstList papszOptions)
    1154             : {
    1155             :     const char *pszFormat =
    1156         240 :         CSLFetchNameValueDef(papszOptions, "FORMAT", "ZARR_V2");
    1157         240 :     std::shared_ptr<ZarrGroupBase> poRG;
    1158             :     auto poSharedResource =
    1159         720 :         ZarrSharedResource::Create(pszFilename, /*bUpdatable=*/true);
    1160         240 :     const bool bCreateZMetadata = CPLTestBool(CSLFetchNameValueDef(
    1161             :         papszOptions, "CREATE_CONSOLIDATED_METADATA",
    1162             :         CSLFetchNameValueDef(papszOptions, "CREATE_ZMETADATA", "YES")));
    1163         240 :     if (bCreateZMetadata)
    1164             :     {
    1165         418 :         poSharedResource->EnableConsolidatedMetadata(
    1166         209 :             EQUAL(pszFormat, "ZARR_V3")
    1167             :                 ? ZarrSharedResource::ConsolidatedMetadataKind::INTERNAL
    1168             :                 : ZarrSharedResource::ConsolidatedMetadataKind::EXTERNAL);
    1169             :     }
    1170         240 :     if (EQUAL(pszFormat, "ZARR_V3"))
    1171             :     {
    1172         258 :         poRG = ZarrV3Group::CreateOnDisk(poSharedResource, std::string(), "/",
    1173         129 :                                          pszFilename);
    1174             :     }
    1175             :     else
    1176             :     {
    1177         222 :         poRG = ZarrV2Group::CreateOnDisk(poSharedResource, std::string(), "/",
    1178         111 :                                          pszFilename);
    1179             :     }
    1180         240 :     if (!poRG)
    1181           0 :         return nullptr;
    1182             : 
    1183         240 :     auto poDS = new ZarrDataset(poRG);
    1184         240 :     poDS->SetDescription(pszFilename);
    1185         240 :     return poDS;
    1186             : }
    1187             : 
    1188             : /************************************************************************/
    1189             : /*                               Create()                               */
    1190             : /************************************************************************/
    1191             : 
    1192          90 : GDALDataset *ZarrDataset::Create(const char *pszName, int nXSize, int nYSize,
    1193             :                                  int nBandsIn, GDALDataType eType,
    1194             :                                  CSLConstList papszOptions)
    1195             : {
    1196             :     // To avoid any issue with short-lived string that would be passed to us
    1197         180 :     const std::string osName = pszName;
    1198          90 :     pszName = osName.c_str();
    1199             : 
    1200          90 :     if (nBandsIn <= 0 || nXSize <= 0 || nYSize <= 0)
    1201             :     {
    1202           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    1203             :                  "nBands, nXSize, nYSize should be > 0");
    1204           1 :         return nullptr;
    1205             :     }
    1206             : 
    1207          89 :     const bool bAppendSubDS = CPLTestBool(
    1208             :         CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO"));
    1209          89 :     const char *pszArrayName = CSLFetchNameValue(papszOptions, "ARRAY_NAME");
    1210             : 
    1211          89 :     std::shared_ptr<ZarrGroupBase> poRG;
    1212          89 :     if (bAppendSubDS)
    1213             :     {
    1214           4 :         if (pszArrayName == nullptr)
    1215             :         {
    1216           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1217             :                      "ARRAY_NAME should be provided when "
    1218             :                      "APPEND_SUBDATASET is set to YES");
    1219           0 :             return nullptr;
    1220             :         }
    1221             :         auto poDS =
    1222           4 :             std::unique_ptr<GDALDataset>(OpenMultidim(pszName, true, nullptr));
    1223           4 :         if (poDS == nullptr)
    1224             :         {
    1225           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s", pszName);
    1226           0 :             return nullptr;
    1227             :         }
    1228           4 :         poRG = std::dynamic_pointer_cast<ZarrGroupBase>(poDS->GetRootGroup());
    1229             :     }
    1230             :     else
    1231             :     {
    1232             :         VSIStatBufL sStat;
    1233          85 :         const bool bExists = VSIStatL(pszName, &sStat) == 0;
    1234          85 :         const bool bIsFile = bExists && !VSI_ISDIR(sStat.st_mode);
    1235             :         const bool bIsDirectory =
    1236         170 :             !bIsFile && ((bExists && VSI_ISDIR(sStat.st_mode)) ||
    1237         170 :                          !CPLStringList(VSIReadDirEx(pszName, 1)).empty());
    1238          85 :         if (bIsFile || bIsDirectory || bExists)
    1239             :         {
    1240           0 :             CPLError(CE_Failure, CPLE_FileIO, "%s %s already exists.",
    1241             :                      bIsFile        ? "File"
    1242           0 :                      : bIsDirectory ? "Directory"
    1243             :                                     : "Object",
    1244             :                      pszName);
    1245           0 :             return nullptr;
    1246             :         }
    1247             : 
    1248             :         const char *pszFormat =
    1249          85 :             CSLFetchNameValueDef(papszOptions, "FORMAT", "ZARR_V2");
    1250             :         auto poSharedResource =
    1251         255 :             ZarrSharedResource::Create(pszName, /*bUpdatable=*/true);
    1252          85 :         const bool bCreateZMetadata = CPLTestBool(CSLFetchNameValueDef(
    1253             :             papszOptions, "CREATE_CONSOLIDATED_METADATA",
    1254             :             CSLFetchNameValueDef(papszOptions, "CREATE_ZMETADATA", "YES")));
    1255          85 :         if (bCreateZMetadata)
    1256             :         {
    1257         170 :             poSharedResource->EnableConsolidatedMetadata(
    1258          85 :                 EQUAL(pszFormat, "ZARR_V3")
    1259             :                     ? ZarrSharedResource::ConsolidatedMetadataKind::INTERNAL
    1260             :                     : ZarrSharedResource::ConsolidatedMetadataKind::EXTERNAL);
    1261             :         }
    1262          85 :         if (EQUAL(pszFormat, "ZARR_V3"))
    1263             :         {
    1264          16 :             poRG = ZarrV3Group::CreateOnDisk(poSharedResource, std::string(),
    1265           8 :                                              "/", pszName);
    1266             :         }
    1267             :         else
    1268             :         {
    1269         154 :             poRG = ZarrV2Group::CreateOnDisk(poSharedResource, std::string(),
    1270          77 :                                              "/", pszName);
    1271             :         }
    1272          85 :         poSharedResource->SetRootGroup(poRG);
    1273             :     }
    1274          89 :     if (!poRG)
    1275           3 :         return nullptr;
    1276             : 
    1277         172 :     auto poDS = std::make_unique<ZarrDataset>(poRG);
    1278          86 :     poDS->SetDescription(pszName);
    1279          86 :     poDS->nRasterYSize = nYSize;
    1280          86 :     poDS->nRasterXSize = nXSize;
    1281          86 :     poDS->eAccess = GA_Update;
    1282             : 
    1283             :     const auto CleanupCreatedFiles =
    1284         160 :         [bAppendSubDS, pszName, pszArrayName, &poRG, &poDS]()
    1285             :     {
    1286             :         // Make sure all objects are released so that ZarrSharedResource
    1287             :         // is finalized and all files are serialized.
    1288          10 :         poRG.reset();
    1289          10 :         poDS.reset();
    1290             : 
    1291          10 :         if (bAppendSubDS)
    1292             :         {
    1293           2 :             VSIRmdir(
    1294           4 :                 CPLFormFilenameSafe(pszName, pszArrayName, nullptr).c_str());
    1295             :         }
    1296             :         else
    1297             :         {
    1298             :             // Be a bit careful before wiping too much stuff...
    1299             :             // At most 5 files expected for ZARR_V2: .zgroup, .zmetadata,
    1300             :             // one (empty) subdir, . and ..
    1301             :             // and for ZARR_V3: zarr.json, one (empty) subdir, . and ..
    1302          16 :             const CPLStringList aosFiles(VSIReadDirEx(pszName, 6));
    1303           8 :             if (aosFiles.size() < 6)
    1304             :             {
    1305          40 :                 for (const char *pszFile : aosFiles)
    1306             :                 {
    1307          32 :                     if (pszArrayName && strcmp(pszFile, pszArrayName) == 0)
    1308             :                     {
    1309           0 :                         VSIRmdir(CPLFormFilenameSafe(pszName, pszFile, nullptr)
    1310             :                                      .c_str());
    1311             :                     }
    1312          64 :                     else if (!pszArrayName &&
    1313          32 :                              strcmp(pszFile,
    1314          64 :                                     CPLGetBasenameSafe(pszName).c_str()) == 0)
    1315             :                     {
    1316           1 :                         VSIRmdir(CPLFormFilenameSafe(pszName, pszFile, nullptr)
    1317             :                                      .c_str());
    1318             :                     }
    1319          31 :                     else if (strcmp(pszFile, ".zgroup") == 0 ||
    1320          24 :                              strcmp(pszFile, ".zmetadata") == 0 ||
    1321          17 :                              strcmp(pszFile, "zarr.json") == 0)
    1322             :                     {
    1323          15 :                         VSIUnlink(CPLFormFilenameSafe(pszName, pszFile, nullptr)
    1324             :                                       .c_str());
    1325             :                     }
    1326             :                 }
    1327           8 :                 VSIRmdir(pszName);
    1328             :             }
    1329             :         }
    1330          10 :     };
    1331             : 
    1332         172 :     std::string osDimXType, osDimYType;
    1333          86 :     if (CPLTestBool(
    1334             :             CSLFetchNameValueDef(papszOptions, "@HAS_GEOTRANSFORM", "NO")))
    1335             :     {
    1336          40 :         osDimXType = GDAL_DIM_TYPE_HORIZONTAL_X;
    1337          40 :         osDimYType = GDAL_DIM_TYPE_HORIZONTAL_Y;
    1338             :     }
    1339          86 :     poDS->m_bSpatialProjConvention = EQUAL(
    1340             :         CSLFetchNameValueDef(papszOptions, "GEOREFERENCING_CONVENTION", "GDAL"),
    1341             :         "SPATIAL_PROJ");
    1342             : 
    1343          86 :     if (bAppendSubDS)
    1344             :     {
    1345           8 :         auto aoDims = poRG->GetDimensions();
    1346          12 :         for (const auto &poDim : aoDims)
    1347             :         {
    1348          12 :             if (poDim->GetName() == "Y" &&
    1349           4 :                 poDim->GetSize() == static_cast<GUInt64>(nYSize))
    1350             :             {
    1351           1 :                 poDS->m_poDimY = poDim;
    1352             :             }
    1353          11 :             else if (poDim->GetName() == "X" &&
    1354           4 :                      poDim->GetSize() == static_cast<GUInt64>(nXSize))
    1355             :             {
    1356           1 :                 poDS->m_poDimX = poDim;
    1357             :             }
    1358             :         }
    1359           4 :         if (poDS->m_poDimY == nullptr)
    1360             :         {
    1361           3 :             poDS->m_poDimY =
    1362           9 :                 poRG->CreateDimension(std::string(pszArrayName) + "_Y",
    1363           9 :                                       osDimYType, std::string(), nYSize);
    1364             :         }
    1365           4 :         if (poDS->m_poDimX == nullptr)
    1366             :         {
    1367           3 :             poDS->m_poDimX =
    1368           9 :                 poRG->CreateDimension(std::string(pszArrayName) + "_X",
    1369           9 :                                       osDimXType, std::string(), nXSize);
    1370             :         }
    1371             :     }
    1372             :     else
    1373             :     {
    1374          82 :         poDS->m_poDimY =
    1375         164 :             poRG->CreateDimension("Y", osDimYType, std::string(), nYSize);
    1376          82 :         poDS->m_poDimX =
    1377         164 :             poRG->CreateDimension("X", osDimXType, std::string(), nXSize);
    1378             :     }
    1379          86 :     if (poDS->m_poDimY == nullptr || poDS->m_poDimX == nullptr)
    1380             :     {
    1381           0 :         CleanupCreatedFiles();
    1382           0 :         return nullptr;
    1383             :     }
    1384             : 
    1385             :     const bool bSingleArray =
    1386          86 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "SINGLE_ARRAY", "YES"));
    1387             :     const bool bBandInterleave =
    1388          86 :         EQUAL(CSLFetchNameValueDef(papszOptions, "INTERLEAVE", "BAND"), "BAND");
    1389             :     std::shared_ptr<GDALDimension> poBandDim(
    1390          84 :         (bSingleArray && nBandsIn > 1)
    1391         110 :             ? poRG->CreateDimension("Band", std::string(), std::string(),
    1392          24 :                                     nBandsIn)
    1393         328 :             : nullptr);
    1394             : 
    1395             :     const std::string osNonNullArrayName =
    1396         172 :         pszArrayName ? std::string(pszArrayName) : CPLGetBasenameSafe(pszName);
    1397          86 :     if (poBandDim)
    1398             :     {
    1399          24 :         std::vector<std::shared_ptr<GDALDimension>> apoDims;
    1400          24 :         if (bBandInterleave)
    1401             :         {
    1402         138 :             apoDims = std::vector<std::shared_ptr<GDALDimension>>{
    1403         115 :                 poBandDim, poDS->m_poDimY, poDS->m_poDimX};
    1404             :         }
    1405             :         else
    1406             :         {
    1407           5 :             apoDims = std::vector<std::shared_ptr<GDALDimension>>{
    1408           5 :                 poDS->m_poDimY, poDS->m_poDimX, poBandDim};
    1409             :         }
    1410          24 :         CPL_IGNORE_RET_VAL(poBandDim);
    1411          72 :         poDS->m_poSingleArray = poRG->CreateMDArray(
    1412             :             osNonNullArrayName.c_str(), apoDims,
    1413          72 :             GDALExtendedDataType::Create(eType), papszOptions);
    1414          24 :         if (!poDS->m_poSingleArray)
    1415             :         {
    1416           2 :             CleanupCreatedFiles();
    1417           2 :             return nullptr;
    1418             :         }
    1419          44 :         poDS->SetMetadataItem("INTERLEAVE", bBandInterleave ? "BAND" : "PIXEL",
    1420          22 :                               "IMAGE_STRUCTURE");
    1421          92 :         for (int i = 0; i < nBandsIn; i++)
    1422             :         {
    1423          70 :             auto poSlicedArray = poDS->m_poSingleArray->GetView(
    1424         140 :                 CPLSPrintf(bBandInterleave ? "[%d,::,::]" : "[::,::,%d]", i));
    1425         140 :             poDS->SetBand(i + 1,
    1426         140 :                           std::make_unique<ZarrRasterBand>(poSlicedArray));
    1427             :         }
    1428             :     }
    1429             :     else
    1430             :     {
    1431             :         const auto apoDims = std::vector<std::shared_ptr<GDALDimension>>{
    1432         248 :             poDS->m_poDimY, poDS->m_poDimX};
    1433         120 :         for (int i = 0; i < nBandsIn; i++)
    1434             :         {
    1435          66 :             auto poArray = poRG->CreateMDArray(
    1436          60 :                 nBandsIn == 1  ? osNonNullArrayName.c_str()
    1437           6 :                 : pszArrayName ? CPLSPrintf("%s_band%d", pszArrayName, i + 1)
    1438           0 :                                : CPLSPrintf("Band%d", i + 1),
    1439         198 :                 apoDims, GDALExtendedDataType::Create(eType), papszOptions);
    1440          66 :             if (poArray == nullptr)
    1441             :             {
    1442           8 :                 CleanupCreatedFiles();
    1443           8 :                 return nullptr;
    1444             :             }
    1445          58 :             poDS->SetBand(i + 1, std::make_unique<ZarrRasterBand>(poArray));
    1446             :         }
    1447             :     }
    1448             : 
    1449          76 :     return poDS.release();
    1450             : }
    1451             : 
    1452             : /************************************************************************/
    1453             : /*                            ~ZarrDataset()                            */
    1454             : /************************************************************************/
    1455             : 
    1456        3664 : ZarrDataset::~ZarrDataset()
    1457             : {
    1458        1832 :     ZarrDataset::FlushCache(true);
    1459        3664 : }
    1460             : 
    1461             : /************************************************************************/
    1462             : /*                             FlushCache()                             */
    1463             : /************************************************************************/
    1464             : 
    1465        1836 : CPLErr ZarrDataset::FlushCache(bool bAtClosing)
    1466             : {
    1467        1836 :     CPLErr eErr = GDALDataset::FlushCache(bAtClosing);
    1468        1836 :     if (m_poRootGroup)
    1469             :     {
    1470        1701 :         if (!m_poRootGroup->Close())
    1471           9 :             eErr = CE_Failure;
    1472             :     }
    1473        1836 :     if (m_poSingleArray)
    1474             :     {
    1475          23 :         bool bFound = false;
    1476          96 :         for (int i = 0; i < nBands; ++i)
    1477             :         {
    1478          73 :             if (papoBands[i]->GetColorInterpretation() != GCI_Undefined)
    1479          12 :                 bFound = true;
    1480             :         }
    1481          23 :         if (bFound)
    1482             :         {
    1483           8 :             const auto oStringDT = GDALExtendedDataType::CreateString();
    1484          12 :             auto poAttr = m_poSingleArray->GetAttribute("COLOR_INTERPRETATION");
    1485           4 :             if (!poAttr)
    1486           9 :                 poAttr = m_poSingleArray->CreateAttribute(
    1487           3 :                     "COLOR_INTERPRETATION", {static_cast<GUInt64>(nBands)},
    1488           6 :                     oStringDT);
    1489           4 :             if (poAttr)
    1490             :             {
    1491           4 :                 const GUInt64 nStartIndex = 0;
    1492           4 :                 const size_t nCount = nBands;
    1493           4 :                 const GInt64 arrayStep = 1;
    1494           4 :                 const GPtrDiff_t bufferStride = 1;
    1495           8 :                 std::vector<const char *> apszValues;
    1496          16 :                 for (int i = 0; i < nBands; ++i)
    1497             :                 {
    1498             :                     const auto eColorInterp =
    1499          12 :                         papoBands[i]->GetColorInterpretation();
    1500          12 :                     apszValues.push_back(
    1501          12 :                         GDALGetColorInterpretationName(eColorInterp));
    1502             :                 }
    1503           8 :                 poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
    1504           4 :                               oStringDT, apszValues.data());
    1505             :             }
    1506             :         }
    1507             :     }
    1508        1836 :     return eErr;
    1509             : }
    1510             : 
    1511             : /************************************************************************/
    1512             : /*                            GetRootGroup()                            */
    1513             : /************************************************************************/
    1514             : 
    1515        1619 : std::shared_ptr<GDALGroup> ZarrDataset::GetRootGroup() const
    1516             : {
    1517        1619 :     return m_poRootGroup;
    1518             : }
    1519             : 
    1520             : /************************************************************************/
    1521             : /*                           GetSpatialRef()                            */
    1522             : /************************************************************************/
    1523             : 
    1524           2 : const OGRSpatialReference *ZarrDataset::GetSpatialRef() const
    1525             : {
    1526           2 :     if (nBands >= 1)
    1527           2 :         return cpl::down_cast<ZarrRasterBand *>(papoBands[0])
    1528           4 :             ->m_poArray->GetSpatialRef()
    1529           2 :             .get();
    1530           0 :     return nullptr;
    1531             : }
    1532             : 
    1533             : /************************************************************************/
    1534             : /*                           SetSpatialRef()                            */
    1535             : /************************************************************************/
    1536             : 
    1537          62 : CPLErr ZarrDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    1538             : {
    1539         172 :     for (int i = 0; i < nBands; ++i)
    1540             :     {
    1541         110 :         cpl::down_cast<ZarrRasterBand *>(papoBands[i])
    1542         110 :             ->m_poArray->SetSpatialRef(poSRS);
    1543             :     }
    1544          62 :     return CE_None;
    1545             : }
    1546             : 
    1547             : /************************************************************************/
    1548             : /*                          GetGeoTransform()                           */
    1549             : /************************************************************************/
    1550             : 
    1551          29 : CPLErr ZarrDataset::GetGeoTransform(GDALGeoTransform &gt) const
    1552             : {
    1553          29 :     gt = m_gt;
    1554          29 :     return m_bHasGT ? CE_None : CE_Failure;
    1555             : }
    1556             : 
    1557             : /************************************************************************/
    1558             : /*                          SetGeoTransform()                           */
    1559             : /************************************************************************/
    1560             : 
    1561          64 : CPLErr ZarrDataset::SetGeoTransform(const GDALGeoTransform &gt)
    1562             : {
    1563          64 :     const bool bHasRotatedTerms = (gt.xrot != 0 || gt.yrot != 0);
    1564             : 
    1565          64 :     if (bHasRotatedTerms)
    1566             :     {
    1567           1 :         if (!m_bSpatialProjConvention)
    1568             :         {
    1569           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1570             :                      "Geotransform with rotated terms not supported with "
    1571             :                      "GEOREFERENCING_CONVENTION=GDAL, but would be with "
    1572             :                      "SPATIAL_PROJ");
    1573           0 :             return CE_Failure;
    1574             :         }
    1575             :     }
    1576          63 :     else if (m_poDimX == nullptr || m_poDimY == nullptr)
    1577             :     {
    1578           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1579             :                  "SetGeoTransform() failed because of missing X/Y dimension");
    1580           0 :         return CE_Failure;
    1581             :     }
    1582             : 
    1583          64 :     m_gt = gt;
    1584          64 :     m_bHasGT = true;
    1585             : 
    1586          64 :     if (m_bSpatialProjConvention)
    1587             :     {
    1588           2 :         const auto bSingleArray = m_poSingleArray != nullptr;
    1589           2 :         const int nIters = bSingleArray ? 1 : nBands;
    1590           4 :         for (int i = 0; i < nIters; ++i)
    1591             :         {
    1592             :             auto *poArray = bSingleArray
    1593           2 :                                 ? m_poSingleArray.get()
    1594           2 :                                 : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
    1595           2 :                                       ->m_poArray.get();
    1596           4 :             auto oAttrDT = GDALExtendedDataType::Create(GDT_Float64);
    1597             :             auto poAttr =
    1598           6 :                 poArray->CreateAttribute("gdal:geotransform", {6}, oAttrDT);
    1599           2 :             if (poAttr)
    1600             :             {
    1601           2 :                 const GUInt64 nStartIndex = 0;
    1602           2 :                 const size_t nCount = 6;
    1603           2 :                 const GInt64 arrayStep = 1;
    1604           2 :                 const GPtrDiff_t bufferStride = 1;
    1605           4 :                 poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
    1606           2 :                               oAttrDT, m_gt.data());
    1607             :             }
    1608             :         }
    1609             :     }
    1610             : 
    1611          64 :     if (!bHasRotatedTerms)
    1612             :     {
    1613          63 :         CPLAssert(m_poDimX);
    1614          63 :         CPLAssert(m_poDimY);
    1615             : 
    1616          63 :         const auto oDTFloat64 = GDALExtendedDataType::Create(GDT_Float64);
    1617             :         {
    1618          63 :             auto poX = m_poRootGroup->OpenMDArray(m_poDimX->GetName());
    1619          63 :             if (!poX)
    1620         310 :                 poX = m_poRootGroup->CreateMDArray(
    1621         248 :                     m_poDimX->GetName(), {m_poDimX}, oDTFloat64, nullptr);
    1622          63 :             if (!poX)
    1623           0 :                 return CE_Failure;
    1624          63 :             m_poDimX->SetIndexingVariable(poX);
    1625          63 :             std::vector<double> adfX;
    1626             :             try
    1627             :             {
    1628          63 :                 adfX.reserve(nRasterXSize);
    1629        3777 :                 for (int i = 0; i < nRasterXSize; ++i)
    1630        3714 :                     adfX.emplace_back(m_gt.xorig + m_gt.xscale * (i + 0.5));
    1631             :             }
    1632           0 :             catch (const std::exception &)
    1633             :             {
    1634           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory,
    1635             :                          "Out of memory when allocating X array");
    1636           0 :                 return CE_Failure;
    1637             :             }
    1638          63 :             const GUInt64 nStartIndex = 0;
    1639          63 :             const size_t nCount = adfX.size();
    1640          63 :             const GInt64 arrayStep = 1;
    1641          63 :             const GPtrDiff_t bufferStride = 1;
    1642         126 :             if (!poX->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
    1643          63 :                             poX->GetDataType(), adfX.data()))
    1644             :             {
    1645           0 :                 return CE_Failure;
    1646             :             }
    1647             :         }
    1648             : 
    1649          63 :         auto poY = m_poRootGroup->OpenMDArray(m_poDimY->GetName());
    1650          63 :         if (!poY)
    1651         248 :             poY = m_poRootGroup->CreateMDArray(m_poDimY->GetName(), {m_poDimY},
    1652         186 :                                                oDTFloat64, nullptr);
    1653          63 :         if (!poY)
    1654           0 :             return CE_Failure;
    1655          63 :         m_poDimY->SetIndexingVariable(poY);
    1656          63 :         std::vector<double> adfY;
    1657             :         try
    1658             :         {
    1659          63 :             adfY.reserve(nRasterYSize);
    1660        3565 :             for (int i = 0; i < nRasterYSize; ++i)
    1661        3502 :                 adfY.emplace_back(m_gt.yorig + m_gt.yscale * (i + 0.5));
    1662             :         }
    1663           0 :         catch (const std::exception &)
    1664             :         {
    1665           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
    1666             :                      "Out of memory when allocating Y array");
    1667           0 :             return CE_Failure;
    1668             :         }
    1669          63 :         const GUInt64 nStartIndex = 0;
    1670          63 :         const size_t nCount = adfY.size();
    1671          63 :         const GInt64 arrayStep = 1;
    1672          63 :         const GPtrDiff_t bufferStride = 1;
    1673         126 :         if (!poY->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
    1674          63 :                         poY->GetDataType(), adfY.data()))
    1675             :         {
    1676           0 :             return CE_Failure;
    1677             :         }
    1678             :     }
    1679             : 
    1680          64 :     return CE_None;
    1681             : }
    1682             : 
    1683             : /************************************************************************/
    1684             : /*                            SetMetadata()                             */
    1685             : /************************************************************************/
    1686             : 
    1687          35 : CPLErr ZarrDataset::SetMetadata(CSLConstList papszMetadata,
    1688             :                                 const char *pszDomain)
    1689             : {
    1690          35 :     if (nBands >= 1 && (pszDomain == nullptr || pszDomain[0] == '\0'))
    1691             :     {
    1692          70 :         const auto oStringDT = GDALExtendedDataType::CreateString();
    1693          35 :         const auto bSingleArray = m_poSingleArray != nullptr;
    1694          35 :         const int nIters = bSingleArray ? 1 : nBands;
    1695          74 :         for (int i = 0; i < nIters; ++i)
    1696             :         {
    1697             :             auto *poArray = bSingleArray
    1698          39 :                                 ? m_poSingleArray.get()
    1699          32 :                                 : cpl::down_cast<ZarrRasterBand *>(papoBands[i])
    1700          32 :                                       ->m_poArray.get();
    1701          78 :             for (auto iter = papszMetadata; iter && *iter; ++iter)
    1702             :             {
    1703          39 :                 char *pszKey = nullptr;
    1704          39 :                 const char *pszValue = CPLParseNameValue(*iter, &pszKey);
    1705          39 :                 if (pszKey && pszValue)
    1706             :                 {
    1707             :                     auto poAttr =
    1708          48 :                         poArray->CreateAttribute(pszKey, {}, oStringDT);
    1709          16 :                     if (poAttr)
    1710             :                     {
    1711          16 :                         const GUInt64 nStartIndex = 0;
    1712          16 :                         const size_t nCount = 1;
    1713          16 :                         const GInt64 arrayStep = 1;
    1714          16 :                         const GPtrDiff_t bufferStride = 1;
    1715          16 :                         poAttr->Write(&nStartIndex, &nCount, &arrayStep,
    1716             :                                       &bufferStride, oStringDT, &pszValue);
    1717             :                     }
    1718             :                 }
    1719          39 :                 CPLFree(pszKey);
    1720             :             }
    1721             :         }
    1722             :     }
    1723          35 :     return GDALDataset::SetMetadata(papszMetadata, pszDomain);
    1724             : }
    1725             : 
    1726             : /************************************************************************/
    1727             : /*                   ZarrRasterBand::ZarrRasterBand()                   */
    1728             : /************************************************************************/
    1729             : 
    1730         128 : ZarrRasterBand::ZarrRasterBand(const std::shared_ptr<GDALMDArray> &poArray)
    1731         128 :     : m_poArray(poArray)
    1732             : {
    1733         128 :     assert(poArray->GetDimensionCount() == 2);
    1734         128 :     eDataType = poArray->GetDataType().GetNumericDataType();
    1735         128 :     nBlockXSize = static_cast<int>(poArray->GetBlockSize()[1]);
    1736         128 :     nBlockYSize = static_cast<int>(poArray->GetBlockSize()[0]);
    1737         128 : }
    1738             : 
    1739             : /************************************************************************/
    1740             : /*                           GetNoDataValue()                           */
    1741             : /************************************************************************/
    1742             : 
    1743           6 : double ZarrRasterBand::GetNoDataValue(int *pbHasNoData)
    1744             : {
    1745           6 :     bool bHasNodata = false;
    1746           6 :     const auto res = m_poArray->GetNoDataValueAsDouble(&bHasNodata);
    1747           6 :     if (pbHasNoData)
    1748           6 :         *pbHasNoData = bHasNodata;
    1749           6 :     return res;
    1750             : }
    1751             : 
    1752             : /************************************************************************/
    1753             : /*                       GetNoDataValueAsInt64()                        */
    1754             : /************************************************************************/
    1755             : 
    1756           0 : int64_t ZarrRasterBand::GetNoDataValueAsInt64(int *pbHasNoData)
    1757             : {
    1758           0 :     bool bHasNodata = false;
    1759           0 :     const auto res = m_poArray->GetNoDataValueAsInt64(&bHasNodata);
    1760           0 :     if (pbHasNoData)
    1761           0 :         *pbHasNoData = bHasNodata;
    1762           0 :     return res;
    1763             : }
    1764             : 
    1765             : /************************************************************************/
    1766             : /*                       GetNoDataValueAsUInt64()                       */
    1767             : /************************************************************************/
    1768             : 
    1769           0 : uint64_t ZarrRasterBand::GetNoDataValueAsUInt64(int *pbHasNoData)
    1770             : {
    1771           0 :     bool bHasNodata = false;
    1772           0 :     const auto res = m_poArray->GetNoDataValueAsUInt64(&bHasNodata);
    1773           0 :     if (pbHasNoData)
    1774           0 :         *pbHasNoData = bHasNodata;
    1775           0 :     return res;
    1776             : }
    1777             : 
    1778             : /************************************************************************/
    1779             : /*                           SetNoDataValue()                           */
    1780             : /************************************************************************/
    1781             : 
    1782           2 : CPLErr ZarrRasterBand::SetNoDataValue(double dfNoData)
    1783             : {
    1784           2 :     return m_poArray->SetNoDataValue(dfNoData) ? CE_None : CE_Failure;
    1785             : }
    1786             : 
    1787             : /************************************************************************/
    1788             : /*                       SetNoDataValueAsInt64()                        */
    1789             : /************************************************************************/
    1790             : 
    1791           0 : CPLErr ZarrRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
    1792             : {
    1793           0 :     return m_poArray->SetNoDataValue(nNoData) ? CE_None : CE_Failure;
    1794             : }
    1795             : 
    1796             : /************************************************************************/
    1797             : /*                       SetNoDataValueAsUInt64()                       */
    1798             : /************************************************************************/
    1799             : 
    1800           0 : CPLErr ZarrRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
    1801             : {
    1802           0 :     return m_poArray->SetNoDataValue(nNoData) ? CE_None : CE_Failure;
    1803             : }
    1804             : 
    1805             : /************************************************************************/
    1806             : /*                             GetOffset()                              */
    1807             : /************************************************************************/
    1808             : 
    1809           2 : double ZarrRasterBand::GetOffset(int *pbSuccess)
    1810             : {
    1811           2 :     bool bHasValue = false;
    1812           2 :     double dfRet = m_poArray->GetOffset(&bHasValue);
    1813           2 :     if (pbSuccess)
    1814           2 :         *pbSuccess = bHasValue ? TRUE : FALSE;
    1815           2 :     return dfRet;
    1816             : }
    1817             : 
    1818             : /************************************************************************/
    1819             : /*                             SetOffset()                              */
    1820             : /************************************************************************/
    1821             : 
    1822           2 : CPLErr ZarrRasterBand::SetOffset(double dfNewOffset)
    1823             : {
    1824           2 :     return m_poArray->SetOffset(dfNewOffset) ? CE_None : CE_Failure;
    1825             : }
    1826             : 
    1827             : /************************************************************************/
    1828             : /*                              GetScale()                              */
    1829             : /************************************************************************/
    1830             : 
    1831           2 : double ZarrRasterBand::GetScale(int *pbSuccess)
    1832             : {
    1833           2 :     bool bHasValue = false;
    1834           2 :     double dfRet = m_poArray->GetScale(&bHasValue);
    1835           2 :     if (pbSuccess)
    1836           2 :         *pbSuccess = bHasValue ? TRUE : FALSE;
    1837           2 :     return dfRet;
    1838             : }
    1839             : 
    1840             : /************************************************************************/
    1841             : /*                              SetScale()                              */
    1842             : /************************************************************************/
    1843             : 
    1844           2 : CPLErr ZarrRasterBand::SetScale(double dfNewScale)
    1845             : {
    1846           2 :     return m_poArray->SetScale(dfNewScale) ? CE_None : CE_Failure;
    1847             : }
    1848             : 
    1849             : /************************************************************************/
    1850             : /*                            GetUnitType()                             */
    1851             : /************************************************************************/
    1852             : 
    1853           2 : const char *ZarrRasterBand::GetUnitType()
    1854             : {
    1855           2 :     return m_poArray->GetUnit().c_str();
    1856             : }
    1857             : 
    1858             : /************************************************************************/
    1859             : /*                            SetUnitType()                             */
    1860             : /************************************************************************/
    1861             : 
    1862           2 : CPLErr ZarrRasterBand::SetUnitType(const char *pszNewValue)
    1863             : {
    1864           4 :     return m_poArray->SetUnit(pszNewValue ? pszNewValue : "") ? CE_None
    1865           4 :                                                               : CE_Failure;
    1866             : }
    1867             : 
    1868             : /************************************************************************/
    1869             : /*                       GetColorInterpretation()                       */
    1870             : /************************************************************************/
    1871             : 
    1872         101 : GDALColorInterp ZarrRasterBand::GetColorInterpretation()
    1873             : {
    1874         101 :     return m_eColorInterp;
    1875             : }
    1876             : 
    1877             : /************************************************************************/
    1878             : /*                       SetColorInterpretation()                       */
    1879             : /************************************************************************/
    1880             : 
    1881          16 : CPLErr ZarrRasterBand::SetColorInterpretation(GDALColorInterp eColorInterp)
    1882             : {
    1883          16 :     auto poGDS = cpl::down_cast<ZarrDataset *>(poDS);
    1884          16 :     m_eColorInterp = eColorInterp;
    1885          16 :     if (!poGDS->m_poSingleArray)
    1886             :     {
    1887           7 :         const auto oStringDT = GDALExtendedDataType::CreateString();
    1888          14 :         auto poAttr = m_poArray->GetAttribute("COLOR_INTERPRETATION");
    1889           7 :         if (poAttr && (poAttr->GetDimensionCount() != 0 ||
    1890           7 :                        poAttr->GetDataType().GetClass() != GEDTC_STRING))
    1891           0 :             return CE_None;
    1892           7 :         if (!poAttr)
    1893          21 :             poAttr = m_poArray->CreateAttribute("COLOR_INTERPRETATION", {},
    1894          14 :                                                 oStringDT);
    1895           7 :         if (poAttr)
    1896             :         {
    1897           7 :             const GUInt64 nStartIndex = 0;
    1898           7 :             const size_t nCount = 1;
    1899           7 :             const GInt64 arrayStep = 1;
    1900           7 :             const GPtrDiff_t bufferStride = 1;
    1901           7 :             const char *pszValue = GDALGetColorInterpretationName(eColorInterp);
    1902           7 :             poAttr->Write(&nStartIndex, &nCount, &arrayStep, &bufferStride,
    1903             :                           oStringDT, &pszValue);
    1904             :         }
    1905             :     }
    1906          16 :     return CE_None;
    1907             : }
    1908             : 
    1909             : /************************************************************************/
    1910             : /*                     ZarrRasterBand::IReadBlock()                     */
    1911             : /************************************************************************/
    1912             : 
    1913           2 : CPLErr ZarrRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pData)
    1914             : {
    1915             : 
    1916           2 :     const int nXOff = nBlockXOff * nBlockXSize;
    1917           2 :     const int nYOff = nBlockYOff * nBlockYSize;
    1918           2 :     const int nReqXSize = std::min(nRasterXSize - nXOff, nBlockXSize);
    1919           2 :     const int nReqYSize = std::min(nRasterYSize - nYOff, nBlockYSize);
    1920           2 :     GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
    1921           2 :                                static_cast<GUInt64>(nXOff)};
    1922           2 :     size_t count[] = {static_cast<size_t>(nReqYSize),
    1923           2 :                       static_cast<size_t>(nReqXSize)};
    1924           2 :     constexpr GInt64 arrayStep[] = {1, 1};
    1925           2 :     GPtrDiff_t bufferStride[] = {nBlockXSize, 1};
    1926           4 :     return m_poArray->Read(arrayStartIdx, count, arrayStep, bufferStride,
    1927           2 :                            m_poArray->GetDataType(), pData)
    1928           2 :                ? CE_None
    1929           2 :                : CE_Failure;
    1930             : }
    1931             : 
    1932             : /************************************************************************/
    1933             : /*                    ZarrRasterBand::IWriteBlock()                     */
    1934             : /************************************************************************/
    1935             : 
    1936           0 : CPLErr ZarrRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff, void *pData)
    1937             : {
    1938           0 :     const int nXOff = nBlockXOff * nBlockXSize;
    1939           0 :     const int nYOff = nBlockYOff * nBlockYSize;
    1940           0 :     const int nReqXSize = std::min(nRasterXSize - nXOff, nBlockXSize);
    1941           0 :     const int nReqYSize = std::min(nRasterYSize - nYOff, nBlockYSize);
    1942           0 :     GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
    1943           0 :                                static_cast<GUInt64>(nXOff)};
    1944           0 :     size_t count[] = {static_cast<size_t>(nReqYSize),
    1945           0 :                       static_cast<size_t>(nReqXSize)};
    1946           0 :     constexpr GInt64 arrayStep[] = {1, 1};
    1947           0 :     GPtrDiff_t bufferStride[] = {nBlockXSize, 1};
    1948           0 :     return m_poArray->Write(arrayStartIdx, count, arrayStep, bufferStride,
    1949           0 :                             m_poArray->GetDataType(), pData)
    1950           0 :                ? CE_None
    1951           0 :                : CE_Failure;
    1952             : }
    1953             : 
    1954             : /************************************************************************/
    1955             : /*                             IRasterIO()                              */
    1956             : /************************************************************************/
    1957             : 
    1958          53 : CPLErr ZarrRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
    1959             :                                  int nXSize, int nYSize, void *pData,
    1960             :                                  int nBufXSize, int nBufYSize,
    1961             :                                  GDALDataType eBufType, GSpacing nPixelSpaceBuf,
    1962             :                                  GSpacing nLineSpaceBuf,
    1963             :                                  GDALRasterIOExtraArg *psExtraArg)
    1964             : {
    1965          53 :     const int nBufferDTSize(GDALGetDataTypeSizeBytes(eBufType));
    1966          53 :     if (nXSize == nBufXSize && nYSize == nBufYSize && nBufferDTSize > 0 &&
    1967          53 :         (nPixelSpaceBuf % nBufferDTSize) == 0 &&
    1968          53 :         (nLineSpaceBuf % nBufferDTSize) == 0)
    1969             :     {
    1970          53 :         GUInt64 arrayStartIdx[] = {static_cast<GUInt64>(nYOff),
    1971          53 :                                    static_cast<GUInt64>(nXOff)};
    1972          53 :         size_t count[] = {static_cast<size_t>(nYSize),
    1973          53 :                           static_cast<size_t>(nXSize)};
    1974          53 :         constexpr GInt64 arrayStep[] = {1, 1};
    1975             :         GPtrDiff_t bufferStride[] = {
    1976          53 :             static_cast<GPtrDiff_t>(nLineSpaceBuf / nBufferDTSize),
    1977          53 :             static_cast<GPtrDiff_t>(nPixelSpaceBuf / nBufferDTSize)};
    1978             : 
    1979          53 :         if (eRWFlag == GF_Read)
    1980             :         {
    1981           4 :             return m_poArray->Read(
    1982             :                        arrayStartIdx, count, arrayStep, bufferStride,
    1983           4 :                        GDALExtendedDataType::Create(eBufType), pData)
    1984           2 :                        ? CE_None
    1985           2 :                        : CE_Failure;
    1986             :         }
    1987             :         else
    1988             :         {
    1989         102 :             return m_poArray->Write(
    1990             :                        arrayStartIdx, count, arrayStep, bufferStride,
    1991         102 :                        GDALExtendedDataType::Create(eBufType), pData)
    1992          51 :                        ? CE_None
    1993          51 :                        : CE_Failure;
    1994             :         }
    1995             :     }
    1996           0 :     return GDALRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
    1997             :                                      pData, nBufXSize, nBufYSize, eBufType,
    1998           0 :                                      nPixelSpaceBuf, nLineSpaceBuf, psExtraArg);
    1999             : }
    2000             : 
    2001             : /************************************************************************/
    2002             : /*                      ZarrDataset::CreateCopy()                       */
    2003             : /************************************************************************/
    2004             : 
    2005             : /* static */
    2006          46 : GDALDataset *ZarrDataset::CreateCopy(const char *pszFilename,
    2007             :                                      GDALDataset *poSrcDS, int bStrict,
    2008             :                                      CSLConstList papszOptions,
    2009             :                                      GDALProgressFunc pfnProgress,
    2010             :                                      void *pProgressData)
    2011             : {
    2012          46 :     if (CPLFetchBool(papszOptions, "CONVERT_TO_KERCHUNK_PARQUET_REFERENCE",
    2013             :                      false))
    2014             :     {
    2015           1 :         if (VSIKerchunkConvertJSONToParquet(poSrcDS->GetDescription(),
    2016             :                                             pszFilename, pfnProgress,
    2017             :                                             pProgressData))
    2018             :         {
    2019             :             GDALOpenInfo oOpenInfo(
    2020           2 :                 std::string("ZARR:\"").append(pszFilename).append("\"").c_str(),
    2021           2 :                 GA_ReadOnly);
    2022           1 :             return Open(&oOpenInfo);
    2023             :         }
    2024             :     }
    2025             :     else
    2026             :     {
    2027          45 :         auto poDriver = GetGDALDriverManager()->GetDriverByName(DRIVER_NAME);
    2028             :         CPLStringList aosCreationOptions(
    2029          90 :             const_cast<CSLConstList>(papszOptions));
    2030          45 :         GDALGeoTransform gt;
    2031          45 :         if (poSrcDS->GetGeoTransform(gt) == CE_None)
    2032             :         {
    2033          44 :             aosCreationOptions.SetNameValue("@HAS_GEOTRANSFORM", "YES");
    2034             :         }
    2035          45 :         return poDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
    2036          45 :                                            aosCreationOptions.List(),
    2037          45 :                                            pfnProgress, pProgressData);
    2038             :     }
    2039           0 :     return nullptr;
    2040             : }
    2041             : 
    2042             : /************************************************************************/
    2043             : /*                         GDALRegister_Zarr()                          */
    2044             : /************************************************************************/
    2045             : 
    2046        2059 : void GDALRegister_Zarr()
    2047             : 
    2048             : {
    2049        2059 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
    2050         283 :         return;
    2051             : 
    2052        1776 :     VSIInstallKerchunkFileSystems();
    2053             : 
    2054        1776 :     GDALDriver *poDriver = new ZarrDriver();
    2055        1776 :     ZARRDriverSetCommonMetadata(poDriver);
    2056             : 
    2057        1776 :     poDriver->pfnOpen = ZarrDataset::Open;
    2058        1776 :     poDriver->pfnCreateMultiDimensional = ZarrDataset::CreateMultiDimensional;
    2059        1776 :     poDriver->pfnCreate = ZarrDataset::Create;
    2060        1776 :     poDriver->pfnCreateCopy = ZarrDataset::CreateCopy;
    2061        1776 :     poDriver->pfnDelete = ZarrDatasetDelete;
    2062        1776 :     poDriver->pfnRename = ZarrDatasetRename;
    2063        1776 :     poDriver->pfnCopyFiles = ZarrDatasetCopyFiles;
    2064        1776 :     poDriver->pfnClearCaches = ZarrDriverClearCaches;
    2065             : 
    2066        1776 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    2067             : }

Generated by: LCOV version 1.14