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

Generated by: LCOV version 1.14