LCOV - code coverage report
Current view: top level - frmts/zarr - zarrdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 769 856 89.8 %
Date: 2025-03-27 19:00:23 Functions: 39 44 88.6 %

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

Generated by: LCOV version 1.14