LCOV - code coverage report
Current view: top level - frmts/zarr - zarrdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1113 1211 91.9 %
Date: 2026-07-24 18:27:47 Functions: 53 55 96.4 %

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

Generated by: LCOV version 1.14