LCOV - code coverage report
Current view: top level - frmts/zarr - zarrdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 719 798 90.1 %
Date: 2024-05-14 13:00:50 Functions: 38 43 88.4 %

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

Generated by: LCOV version 1.14