LCOV - code coverage report
Current view: top level - frmts/zarr - zarrdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 970 1070 90.7 %
Date: 2026-05-08 18:52:02 Functions: 45 51 88.2 %

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

Generated by: LCOV version 1.14