LCOV - code coverage report
Current view: top level - frmts/zarr - zarrdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 836 923 90.6 %
Date: 2025-06-19 12:30:01 Functions: 41 46 89.1 %

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

Generated by: LCOV version 1.14