LCOV - code coverage report
Current view: top level - frmts/zarr - zarrdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 961 1055 91.1 %
Date: 2026-02-24 19:16:26 Functions: 44 49 89.8 %

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

Generated by: LCOV version 1.14