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

Generated by: LCOV version 1.14