LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v3_group.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 623 680 91.6 %
Date: 2026-03-26 23:25:44 Functions: 19 19 100.0 %

          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 "zarr_v3_codec.h"
      15             : 
      16             : #include <algorithm>
      17             : #include <cassert>
      18             : #include <limits>
      19             : #include <map>
      20             : #include <set>
      21             : 
      22             : /************************************************************************/
      23             : /*                        ZarrV3Group::Create()                         */
      24             : /************************************************************************/
      25             : 
      26             : std::shared_ptr<ZarrV3Group>
      27        1409 : ZarrV3Group::Create(const std::shared_ptr<ZarrSharedResource> &poSharedResource,
      28             :                     const std::string &osParentName, const std::string &osName,
      29             :                     const std::string &osRootDirectoryName)
      30             : {
      31             :     auto poGroup = std::shared_ptr<ZarrV3Group>(new ZarrV3Group(
      32        1409 :         poSharedResource, osParentName, osName, osRootDirectoryName));
      33        1409 :     poGroup->SetSelf(poGroup);
      34        1409 :     return poGroup;
      35             : }
      36             : 
      37             : /************************************************************************/
      38             : /*                           OpenZarrArray()                            */
      39             : /************************************************************************/
      40             : 
      41        1222 : std::shared_ptr<ZarrArray> ZarrV3Group::OpenZarrArray(const std::string &osName,
      42             :                                                       CSLConstList) const
      43             : {
      44        1222 :     if (!CheckValidAndErrorOutIfNot())
      45           0 :         return nullptr;
      46             : 
      47        1222 :     auto oIter = m_oMapMDArrays.find(osName);
      48        1222 :     if (oIter != m_oMapMDArrays.end())
      49        1102 :         return oIter->second;
      50             : 
      51         120 :     if (m_bReadFromConsolidatedMetadata)
      52          13 :         return nullptr;
      53             : 
      54             :     const std::string osSubDir =
      55         214 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
      56             :     const std::string osZarrayFilename =
      57         214 :         CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
      58             : 
      59             :     VSIStatBufL sStat;
      60         107 :     if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
      61             :     {
      62         170 :         CPLJSONDocument oDoc;
      63          85 :         if (!oDoc.Load(osZarrayFilename))
      64           0 :             return nullptr;
      65         170 :         const auto oRoot = oDoc.GetRoot();
      66          85 :         return LoadArray(osName, osZarrayFilename, oRoot);
      67             :     }
      68             : 
      69          22 :     return nullptr;
      70             : }
      71             : 
      72             : /************************************************************************/
      73             : /*                    ZarrV3Group::LoadAttributes()                     */
      74             : /************************************************************************/
      75             : 
      76         422 : void ZarrV3Group::LoadAttributes() const
      77             : {
      78         422 :     if (m_bAttributesLoaded)
      79         380 :         return;
      80          42 :     m_bAttributesLoaded = true;
      81             : 
      82             :     const std::string osFilename =
      83          42 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), "zarr.json", nullptr);
      84             : 
      85             :     VSIStatBufL sStat;
      86          42 :     if (VSIStatL(osFilename.c_str(), &sStat) == 0)
      87             :     {
      88          42 :         CPLJSONDocument oDoc;
      89          42 :         if (!oDoc.Load(osFilename))
      90           0 :             return;
      91          42 :         auto oRoot = oDoc.GetRoot();
      92          42 :         m_oAttrGroup.Init(oRoot["attributes"], m_bUpdatable);
      93             :     }
      94             : }
      95             : 
      96             : /************************************************************************/
      97             : /*                          ExploreDirectory()                          */
      98             : /************************************************************************/
      99             : 
     100          68 : void ZarrV3Group::ExploreDirectory() const
     101             : {
     102          68 :     if (m_bDirectoryExplored)
     103           0 :         return;
     104          68 :     m_bDirectoryExplored = true;
     105             : 
     106          68 :     auto psDir = VSIOpenDir(m_osDirectoryName.c_str(), 0, nullptr);
     107          68 :     if (!psDir)
     108           0 :         return;
     109         238 :     while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir))
     110             :     {
     111         170 :         if (VSI_ISDIR(psEntry->nMode))
     112             :         {
     113         204 :             std::string osName(psEntry->pszName);
     114         204 :             while (!osName.empty() &&
     115         102 :                    (osName.back() == '/' || osName.back() == '\\'))
     116           0 :                 osName.pop_back();
     117         102 :             if (osName.empty())
     118           0 :                 continue;
     119             :             const std::string osSubDir = CPLFormFilenameSafe(
     120         102 :                 m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     121             :             VSIStatBufL sStat;
     122             :             const std::string osZarrJsonFilename =
     123         102 :                 CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
     124         102 :             if (VSIStatL(osZarrJsonFilename.c_str(), &sStat) == 0)
     125             :             {
     126         100 :                 CPLJSONDocument oDoc;
     127         100 :                 if (oDoc.Load(osZarrJsonFilename.c_str()))
     128             :                 {
     129         100 :                     const auto oRoot = oDoc.GetRoot();
     130         100 :                     if (oRoot.GetInteger("zarr_format") != 3)
     131             :                     {
     132           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     133             :                                  "Unhandled zarr_format value");
     134           0 :                         continue;
     135             :                     }
     136         200 :                     const std::string osNodeType = oRoot.GetString("node_type");
     137         100 :                     if (osNodeType == "array")
     138             :                     {
     139          57 :                         if (!cpl::contains(m_oSetArrayNames, osName))
     140             :                         {
     141          55 :                             m_oSetArrayNames.insert(osName);
     142          55 :                             m_aosArrays.emplace_back(std::move(osName));
     143             :                         }
     144             :                     }
     145          43 :                     else if (osNodeType == "group")
     146             :                     {
     147          43 :                         if (!cpl::contains(m_oSetGroupNames, osName))
     148             :                         {
     149          43 :                             m_oSetGroupNames.insert(osName);
     150          43 :                             m_aosGroups.emplace_back(std::move(osName));
     151             :                         }
     152             :                     }
     153             :                     else
     154             :                     {
     155           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     156             :                                  "Unhandled node_type value");
     157           0 :                         continue;
     158             :                     }
     159             :                 }
     160             :             }
     161             :             else
     162             :             {
     163             :                 // Implicit group (deprecated)
     164           2 :                 if (!cpl::contains(m_oSetGroupNames, osName))
     165             :                 {
     166           2 :                     m_oSetGroupNames.insert(osName);
     167           2 :                     m_aosGroups.emplace_back(std::move(osName));
     168             :                 }
     169             :             }
     170             :         }
     171         170 :     }
     172          68 :     VSICloseDir(psDir);
     173             : }
     174             : 
     175             : /************************************************************************/
     176             : /*                      ZarrV3Group::ZarrV3Group()                      */
     177             : /************************************************************************/
     178             : 
     179        1409 : ZarrV3Group::ZarrV3Group(
     180             :     const std::shared_ptr<ZarrSharedResource> &poSharedResource,
     181             :     const std::string &osParentName, const std::string &osName,
     182        1409 :     const std::string &osDirectoryName)
     183        1409 :     : ZarrGroupBase(poSharedResource, osParentName, osName)
     184             : {
     185        1409 :     m_osDirectoryName = osDirectoryName;
     186        1409 : }
     187             : 
     188             : /************************************************************************/
     189             : /*                     ZarrV3Group::~ZarrV3Group()                      */
     190             : /************************************************************************/
     191             : 
     192        2818 : ZarrV3Group::~ZarrV3Group()
     193             : {
     194        1409 :     ZarrV3Group::Close();
     195        2818 : }
     196             : 
     197             : /************************************************************************/
     198             : /*                    GenerateMultiscalesMetadata()                     */
     199             : /************************************************************************/
     200             : 
     201          10 : void ZarrV3Group::GenerateMultiscalesMetadata(const char *pszResampling)
     202             : {
     203          10 :     const auto aosGroupNames = GetGroupNames();
     204          10 :     if (aosGroupNames.empty())
     205             :     {
     206             :         // No child groups - remove stale multiscales metadata if present.
     207           1 :         if (!m_bAttributesLoaded)
     208           0 :             LoadAttributes();
     209           1 :         if (m_oAttrGroup.GetAttribute("multiscales"))
     210           1 :             m_oAttrGroup.DeleteAttribute("multiscales");
     211           2 :         auto poExistingConv = m_oAttrGroup.GetAttribute("zarr_conventions");
     212           1 :         if (poExistingConv)
     213             :         {
     214             :             // Preserve non-multiscales entries.
     215           1 :             const char *pszExisting = poExistingConv->ReadAsString();
     216           2 :             CPLJSONArray oFiltered;
     217           1 :             if (pszExisting)
     218             :             {
     219           2 :                 CPLJSONDocument oDoc;
     220           1 :                 if (oDoc.LoadMemory(pszExisting))
     221             :                 {
     222           2 :                     for (const auto &oEntry : oDoc.GetRoot().ToArray())
     223             :                     {
     224           1 :                         if (oEntry.GetString("uuid") != ZARR_MULTISCALES_UUID)
     225           0 :                             oFiltered.Add(oEntry);
     226             :                     }
     227             :                 }
     228             :             }
     229           1 :             m_oAttrGroup.DeleteAttribute("zarr_conventions");
     230           1 :             if (oFiltered.Size() > 0)
     231             :             {
     232             :                 const auto oJsonDT =
     233           0 :                     GDALExtendedDataType::CreateString(0, GEDTST_JSON);
     234             :                 auto poAttr = m_oAttrGroup.CreateAttribute("zarr_conventions",
     235           0 :                                                            {}, oJsonDT);
     236           0 :                 if (poAttr)
     237           0 :                     poAttr->Write(
     238           0 :                         oFiltered.Format(CPLJSONObject::PrettyFormat::Plain)
     239             :                             .c_str());
     240             :             }
     241             :         }
     242           1 :         return;
     243             :     }
     244             : 
     245             :     // Collect {arrayName -> [(groupName, array)]} across child groups.
     246             :     struct LevelInfo
     247             :     {
     248             :         std::string osGroupName;  // empty for base (this group)
     249             :         std::shared_ptr<GDALMDArray> poArray;
     250             :     };
     251             : 
     252           9 :     std::map<std::string, std::vector<LevelInfo>> oMapArrayToLevels;
     253             : 
     254          20 :     for (const auto &osGroupName : aosGroupNames)
     255             :     {
     256          11 :         auto poChildGroup = OpenZarrGroup(osGroupName);
     257          11 :         if (!poChildGroup)
     258           0 :             continue;
     259          26 :         for (const auto &osArrayName : poChildGroup->GetMDArrayNames())
     260             :         {
     261          30 :             auto poArray = poChildGroup->OpenMDArray(osArrayName);
     262          15 :             if (poArray)
     263             :             {
     264          30 :                 oMapArrayToLevels[osArrayName].push_back(
     265          15 :                     {osGroupName, std::move(poArray)});
     266             :             }
     267             :         }
     268             :     }
     269             : 
     270           9 :     if (oMapArrayToLevels.empty())
     271           0 :         return;
     272             : 
     273             :     // For each array found in child groups, check if the base (this group)
     274             :     // also has an array with the same name. If so, prepend it as the base
     275             :     // level with an empty group name (meaning "this group").
     276          22 :     for (auto &[osArrayName, aoLevels] : oMapArrayToLevels)
     277             :     {
     278          26 :         auto poBaseArray = OpenMDArray(osArrayName);
     279          13 :         if (poBaseArray)
     280             :         {
     281          13 :             aoLevels.insert(aoLevels.begin(),
     282          26 :                             LevelInfo{"", std::move(poBaseArray)});
     283             :         }
     284             :     }
     285             : 
     286             :     // Pick the first array name (alphabetical) with >= 2 levels
     287             :     // (base + at least one overview) and >= 2 dimensions (skip 1D
     288             :     // coordinate arrays).
     289             :     //
     290             :     // Expected hierarchy from BuildOverviews():
     291             :     //   /group/
     292             :     //     data        <- base array (e.g. 10980 x 10980)
     293             :     //     y, x        <- 1D coordinate arrays (skipped)
     294             :     //     ovr_2x/
     295             :     //       data      <- 2x overview  (5490 x 5490)
     296             :     //       y, x
     297             :     //     ovr_4x/
     298             :     //       data      <- 4x overview  (2745 x 2745)
     299             :     //       y, x
     300             :     //
     301             :     // Multiple >=2D arrays sharing the same name across levels is
     302             :     // possible but unusual; we use the first alphabetically.
     303           9 :     std::string osCanonicalArrayName;
     304          13 :     for (const auto &[osArrayName, aoLevels] : oMapArrayToLevels)
     305             :     {
     306          26 :         if (aoLevels.size() >= 2 &&
     307          13 :             aoLevels[0].poArray->GetDimensionCount() >= 2)
     308             :         {
     309           9 :             osCanonicalArrayName = osArrayName;
     310           9 :             break;
     311             :         }
     312             :     }
     313             : 
     314           9 :     if (osCanonicalArrayName.empty())
     315             :     {
     316           0 :         CPLDebug("ZARR", "GenerateMultiscalesMetadata: no array with "
     317             :                          ">=2 levels and >=2 dimensions found");
     318           0 :         return;
     319             :     }
     320             : 
     321           9 :     auto &aoLevels = oMapArrayToLevels[osCanonicalArrayName];
     322             : 
     323             :     // Sort by total element count, largest first (= full resolution).
     324           9 :     std::stable_sort(aoLevels.begin(), aoLevels.end(),
     325          13 :                      [](const LevelInfo &a, const LevelInfo &b)
     326             :                      {
     327          13 :                          const auto &dimsA = a.poArray->GetDimensions();
     328          13 :                          const auto &dimsB = b.poArray->GetDimensions();
     329          13 :                          GUInt64 sizeA = 1, sizeB = 1;
     330          40 :                          for (const auto &d : dimsA)
     331          27 :                              sizeA *= d->GetSize();
     332          40 :                          for (const auto &d : dimsB)
     333          27 :                              sizeB *= d->GetSize();
     334          13 :                          return sizeA > sizeB;
     335             :                      });
     336             : 
     337           9 :     const auto &poBaseArray = aoLevels[0].poArray;
     338           9 :     const size_t nBaseDimCount = poBaseArray->GetDimensionCount();
     339           9 :     const auto &oBaseType = poBaseArray->GetDataType();
     340             : 
     341             :     // Asset path for a level. Empty group name means the base array lives
     342             :     // in this group - use the array name directly (LoadOverviews resolves
     343             :     // single-component paths as array names in the parent group).
     344             :     const auto assetPath =
     345          31 :         [&osCanonicalArrayName](const std::string &osGroupName) -> std::string
     346          31 :     { return osGroupName.empty() ? osCanonicalArrayName : osGroupName; };
     347             : 
     348             :     // Base level: identity scale, no translation, no derived_from.
     349           9 :     CPLJSONArray oLayout;
     350             :     {
     351          18 :         CPLJSONObject oBaseItem;
     352           9 :         oBaseItem.Add("asset", assetPath(aoLevels[0].osGroupName));
     353             : 
     354          18 :         CPLJSONArray oScale;
     355          28 :         for (size_t iDim = 0; iDim < nBaseDimCount; ++iDim)
     356          19 :             oScale.Add(1.0);
     357          18 :         CPLJSONObject oTransform;
     358           9 :         oTransform.Add("scale", oScale);
     359           9 :         oBaseItem.Add("transform", oTransform);
     360             : 
     361           9 :         oLayout.Add(oBaseItem);
     362             :     }
     363             : 
     364             :     // Overview levels: sequential derived_from chain.
     365          20 :     for (size_t iLevel = 1; iLevel < aoLevels.size(); ++iLevel)
     366             :     {
     367          11 :         const auto &info = aoLevels[iLevel];
     368          11 :         const auto &poArray = info.poArray;
     369             : 
     370          22 :         if (poArray->GetDimensionCount() != nBaseDimCount ||
     371          11 :             poArray->GetDataType() != oBaseType)
     372             :         {
     373           0 :             CPLDebug("ZARR",
     374             :                      "GenerateMultiscalesMetadata: skipping level '%s' "
     375             :                      "(dim count or data type mismatch with base)",
     376             :                      info.osGroupName.c_str());
     377           0 :             continue;
     378             :         }
     379             : 
     380          11 :         const auto &apoDims = poArray->GetDimensions();
     381             :         // Previous valid level for sequential derived_from.
     382          11 :         const auto &oPrevDims = aoLevels[iLevel - 1].poArray->GetDimensions();
     383             : 
     384          22 :         CPLJSONObject oItem;
     385          11 :         oItem.Add("asset", assetPath(info.osGroupName));
     386          11 :         oItem.Add("derived_from", assetPath(aoLevels[iLevel - 1].osGroupName));
     387             : 
     388          22 :         CPLJSONArray oScale;
     389          22 :         CPLJSONArray oTranslation;
     390          34 :         for (size_t iDim = 0; iDim < nBaseDimCount; ++iDim)
     391             :         {
     392          23 :             const auto nOvSize = apoDims[iDim]->GetSize();
     393          23 :             const auto nPrevSize = oPrevDims[iDim]->GetSize();
     394          23 :             const double dfScale = nOvSize > 0
     395          23 :                                        ? static_cast<double>(nPrevSize) /
     396          23 :                                              static_cast<double>(nOvSize)
     397             :                                        : 0.0;
     398          23 :             oScale.Add(dfScale);
     399          23 :             oTranslation.Add(0.0);
     400             :         }
     401             : 
     402          22 :         CPLJSONObject oTransform;
     403          11 :         oTransform.Add("scale", oScale);
     404          11 :         oTransform.Add("translation", oTranslation);
     405          11 :         oItem.Add("transform", oTransform);
     406             : 
     407          11 :         if (pszResampling)
     408          11 :             oItem.Add("resampling_method", pszResampling);
     409             : 
     410          11 :         oLayout.Add(oItem);
     411             :     }
     412             : 
     413           9 :     if (oLayout.Size() < 2)
     414           0 :         return;
     415             : 
     416          18 :     CPLJSONObject oMultiscales;
     417           9 :     oMultiscales.Add("layout", oLayout);
     418             : 
     419             :     // Preserve existing zarr_conventions entries.
     420           9 :     if (!m_bAttributesLoaded)
     421           8 :         LoadAttributes();
     422             : 
     423          18 :     CPLJSONArray oZarrConventions;
     424          27 :     auto poExistingConv = m_oAttrGroup.GetAttribute("zarr_conventions");
     425           9 :     if (poExistingConv)
     426             :     {
     427           1 :         const char *pszExisting = poExistingConv->ReadAsString();
     428           1 :         if (pszExisting)
     429             :         {
     430           2 :             CPLJSONDocument oDoc;
     431           1 :             if (oDoc.LoadMemory(pszExisting))
     432             :             {
     433           2 :                 for (const auto &oEntry : oDoc.GetRoot().ToArray())
     434             :                 {
     435           1 :                     if (oEntry.GetString("uuid") != ZARR_MULTISCALES_UUID)
     436           0 :                         oZarrConventions.Add(oEntry);
     437             :                 }
     438             :             }
     439             :         }
     440           1 :         m_oAttrGroup.DeleteAttribute("zarr_conventions");
     441             :     }
     442             : 
     443             :     {
     444          18 :         CPLJSONObject oConv;
     445           9 :         oConv.Set("uuid", ZARR_MULTISCALES_UUID);
     446           9 :         oConv.Set("schema_url",
     447             :                   "https://raw.githubusercontent.com/zarr-conventions/"
     448             :                   "multiscales/refs/tags/v1/schema.json");
     449           9 :         oConv.Set("spec_url", "https://github.com/zarr-conventions/"
     450             :                               "multiscales/blob/v1/README.md");
     451           9 :         oConv.Set("name", "multiscales");
     452           9 :         oConv.Set("description", "Multiscale layout of zarr datasets");
     453           9 :         oZarrConventions.Add(oConv);
     454             :     }
     455             : 
     456           9 :     if (m_oAttrGroup.GetAttribute("multiscales"))
     457           1 :         m_oAttrGroup.DeleteAttribute("multiscales");
     458             : 
     459          18 :     const auto oJsonDT = GDALExtendedDataType::CreateString(0, GEDTST_JSON);
     460             :     {
     461             :         auto poAttr =
     462          27 :             m_oAttrGroup.CreateAttribute("zarr_conventions", {}, oJsonDT);
     463           9 :         if (poAttr)
     464          18 :             poAttr->Write(
     465          18 :                 oZarrConventions.Format(CPLJSONObject::PrettyFormat::Plain)
     466             :                     .c_str());
     467             :     }
     468             :     {
     469          27 :         auto poAttr = m_oAttrGroup.CreateAttribute("multiscales", {}, oJsonDT);
     470           9 :         if (poAttr)
     471          18 :             poAttr->Write(
     472          18 :                 oMultiscales.Format(CPLJSONObject::PrettyFormat::Plain)
     473             :                     .c_str());
     474             :     }
     475             : }
     476             : 
     477             : /************************************************************************/
     478             : /*                               Close()                                */
     479             : /************************************************************************/
     480             : 
     481        3252 : bool ZarrV3Group::Close()
     482             : {
     483        3252 :     bool bRet = ZarrGroupBase::Close();
     484             : 
     485        6425 :     if (m_bValid && (m_oAttrGroup.IsModified() ||
     486        3259 :                      (m_bUpdatable && !m_bFileHasBeenWritten &&
     487          86 :                       m_poSharedResource->IsConsolidatedMetadataEnabled())))
     488             :     {
     489         212 :         CPLJSONDocument oDoc;
     490         212 :         auto oRoot = oDoc.GetRoot();
     491         106 :         oRoot.Add("zarr_format", 3);
     492         106 :         oRoot.Add("node_type", "group");
     493         106 :         oRoot.Add("attributes", m_oAttrGroup.Serialize());
     494             :         const std::string osZarrJsonFilename = CPLFormFilenameSafe(
     495         106 :             m_osDirectoryName.c_str(), "zarr.json", nullptr);
     496         106 :         if (!m_bFileHasBeenWritten)
     497             :         {
     498          37 :             oRoot.Add("consolidated_metadata",
     499          37 :                       m_poSharedResource->GetConsolidatedMetadataObj());
     500          37 :             bRet = oDoc.Save(osZarrJsonFilename) && bRet;
     501             :         }
     502             :         else
     503             :         {
     504          69 :             bRet = oDoc.Save(osZarrJsonFilename) && bRet;
     505          69 :             if (bRet)
     506          68 :                 m_poSharedResource->SetZMetadataItem(osZarrJsonFilename, oRoot);
     507             :         }
     508         106 :         m_bFileHasBeenWritten = bRet;
     509             :     }
     510             : 
     511        3252 :     return bRet;
     512             : }
     513             : 
     514             : /************************************************************************/
     515             : /*                  ZarrV3Group::GetOrCreateSubGroup()                  */
     516             : /************************************************************************/
     517             : 
     518             : std::shared_ptr<ZarrV3Group>
     519         376 : ZarrV3Group::GetOrCreateSubGroup(const std::string &osSubGroupFullname)
     520             : {
     521             :     auto poSubGroup = std::dynamic_pointer_cast<ZarrV3Group>(
     522         376 :         OpenGroupFromFullname(osSubGroupFullname));
     523         376 :     if (poSubGroup)
     524             :     {
     525         238 :         return poSubGroup;
     526             :     }
     527             : 
     528         138 :     const auto nLastSlashPos = osSubGroupFullname.rfind('/');
     529             :     auto poBelongingGroup =
     530             :         (nLastSlashPos == 0)
     531         138 :             ? this
     532         152 :             : GetOrCreateSubGroup(osSubGroupFullname.substr(0, nLastSlashPos))
     533         138 :                   .get();
     534             : 
     535         276 :     poSubGroup = ZarrV3Group::Create(
     536         138 :         m_poSharedResource, poBelongingGroup->GetFullName(),
     537         414 :         osSubGroupFullname.substr(nLastSlashPos + 1), m_osDirectoryName);
     538         276 :     poSubGroup->m_poParent = std::dynamic_pointer_cast<ZarrGroupBase>(
     539         414 :         poBelongingGroup->m_pSelf.lock());
     540         276 :     poSubGroup->SetDirectoryName(
     541         276 :         CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
     542         138 :                             poSubGroup->GetName().c_str(), nullptr));
     543         138 :     poSubGroup->m_bDirectoryExplored = true;
     544         138 :     poSubGroup->m_bAttributesLoaded = true;
     545         138 :     poSubGroup->m_bReadFromConsolidatedMetadata = true;
     546         138 :     poSubGroup->m_bFileHasBeenWritten = true;
     547         138 :     poSubGroup->SetUpdatable(m_bUpdatable);
     548             : 
     549         138 :     poBelongingGroup->m_oMapGroups[poSubGroup->GetName()] = poSubGroup;
     550         138 :     poBelongingGroup->m_oSetGroupNames.insert(poSubGroup->GetName());
     551         138 :     poBelongingGroup->m_aosGroups.emplace_back(poSubGroup->GetName());
     552         138 :     return poSubGroup;
     553             : }
     554             : 
     555             : /************************************************************************/
     556             : /*             ZarrV3Group::InitFromConsolidatedMetadata()              */
     557             : /************************************************************************/
     558             : 
     559         183 : void ZarrV3Group::InitFromConsolidatedMetadata(
     560             :     const CPLJSONObject &oConsolidatedMetadata,
     561             :     const CPLJSONObject &oRootAttributes)
     562             : {
     563         366 :     const auto metadata = oConsolidatedMetadata["metadata"];
     564         183 :     if (metadata.GetType() != CPLJSONObject::Type::Object)
     565             :     {
     566           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     567             :                  "consolidated_metadata lacks 'metadata' object");
     568           0 :         return;
     569             :     }
     570         183 :     m_bDirectoryExplored = true;
     571         183 :     m_bAttributesLoaded = true;
     572         183 :     m_bReadFromConsolidatedMetadata = true;
     573             : 
     574         183 :     if (oRootAttributes.IsValid())
     575             :     {
     576         183 :         m_oAttrGroup.Init(oRootAttributes, m_bUpdatable);
     577             :     }
     578             : 
     579         366 :     const auto children = metadata.GetChildren();
     580         366 :     std::map<std::string, const CPLJSONObject *> oMapArrays;
     581             : 
     582             :     // First pass to create groups and collect arrays
     583         692 :     for (const auto &child : children)
     584             :     {
     585         509 :         const std::string osName(child.GetName());
     586         509 :         if (std::count(osName.begin(), osName.end(), '/') > 32)
     587             :         {
     588             :             // Avoid too deep recursion in GetOrCreateSubGroup()
     589           0 :             continue;
     590             :         }
     591             : 
     592        1527 :         const std::string osNodeType = child.GetString("node_type");
     593         509 :         if (osNodeType == "group")
     594             :         {
     595         276 :             auto poGroup = GetOrCreateSubGroup("/" + osName);
     596         414 :             auto oAttributes = child["attributes"];
     597         138 :             if (oAttributes.IsValid())
     598             :             {
     599         138 :                 poGroup->m_oAttrGroup.Init(oAttributes, m_bUpdatable);
     600             :             }
     601             :         }
     602         371 :         else if (osNodeType == "array")
     603             :         {
     604         371 :             oMapArrays[osName] = &child;
     605             :         }
     606             :     }
     607             : 
     608             :     const auto CreateArray =
     609         595 :         [this](const std::string &osArrayFullname, const CPLJSONObject &oArray)
     610             :     {
     611         371 :         const auto nLastSlashPos = osArrayFullname.rfind('/');
     612             :         auto poBelongingGroup =
     613             :             (nLastSlashPos == std::string::npos)
     614         371 :                 ? this
     615         595 :                 : GetOrCreateSubGroup("/" +
     616         595 :                                       osArrayFullname.substr(0, nLastSlashPos))
     617         371 :                       .get();
     618             :         const auto osArrayName =
     619             :             nLastSlashPos == std::string::npos
     620             :                 ? osArrayFullname
     621         742 :                 : osArrayFullname.substr(nLastSlashPos + 1);
     622             :         const std::string osZarrayFilename = CPLFormFilenameSafe(
     623         371 :             CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
     624             :                                 osArrayName.c_str(), nullptr)
     625             :                 .c_str(),
     626         371 :             "zarr.json", nullptr);
     627         371 :         poBelongingGroup->LoadArray(osArrayName, osZarrayFilename, oArray);
     628         371 :     };
     629             : 
     630             :     struct ArrayDesc
     631             :     {
     632             :         std::string osArrayFullname{};
     633             :         const CPLJSONObject *poArray = nullptr;
     634             :     };
     635             : 
     636         366 :     std::vector<ArrayDesc> aoRegularArrays;
     637             : 
     638             :     // Second pass to read attributes and create arrays that are indexing
     639             :     // variable
     640         692 :     for (const auto &child : children)
     641             :     {
     642        1018 :         const std::string osName(child.GetName());
     643        1527 :         const std::string osNodeType = child.GetString("node_type");
     644         509 :         if (osNodeType == "array")
     645             :         {
     646         371 :             auto oIter = oMapArrays.find(osName);
     647         371 :             if (oIter != oMapArrays.end())
     648             :             {
     649         371 :                 const auto nLastSlashPos = osName.rfind('/');
     650             :                 const std::string osArrayName =
     651             :                     (nLastSlashPos == std::string::npos)
     652             :                         ? osName
     653         742 :                         : osName.substr(nLastSlashPos + 1);
     654        1113 :                 const auto arrayDimensions = child["dimension_names"].ToArray();
     655         459 :                 if (arrayDimensions.IsValid() && arrayDimensions.Size() == 1 &&
     656         459 :                     arrayDimensions[0].ToString() == osArrayName)
     657             :                 {
     658          80 :                     CreateArray(osName, child);
     659          80 :                     oMapArrays.erase(oIter);
     660             :                 }
     661             :                 else
     662             :                 {
     663         582 :                     ArrayDesc desc;
     664         291 :                     desc.osArrayFullname = std::move(osName);
     665         291 :                     desc.poArray = oIter->second;
     666         291 :                     aoRegularArrays.emplace_back(std::move(desc));
     667             :                 }
     668             :             }
     669             :         }
     670             :     }
     671             : 
     672             :     // Third pass to create non-indexing arrays with attributes
     673         474 :     for (const auto &desc : aoRegularArrays)
     674             :     {
     675         291 :         CreateArray(desc.osArrayFullname, *(desc.poArray));
     676         291 :         oMapArrays.erase(desc.osArrayFullname);
     677             :     }
     678             : 
     679             :     // Fourth pass to create arrays without attributes
     680         183 :     for (const auto &kv : oMapArrays)
     681             :     {
     682           0 :         CreateArray(kv.first, *(kv.second));
     683             :     }
     684             : }
     685             : 
     686             : /************************************************************************/
     687             : /*                           OpenZarrGroup()                            */
     688             : /************************************************************************/
     689             : 
     690             : std::shared_ptr<ZarrGroupBase>
     691         832 : ZarrV3Group::OpenZarrGroup(const std::string &osName, CSLConstList) const
     692             : {
     693         832 :     if (!CheckValidAndErrorOutIfNot())
     694           0 :         return nullptr;
     695             : 
     696         832 :     auto oIter = m_oMapGroups.find(osName);
     697         832 :     if (oIter != m_oMapGroups.end())
     698         577 :         return oIter->second;
     699             : 
     700         255 :     if (m_bReadFromConsolidatedMetadata)
     701         192 :         return nullptr;
     702             : 
     703             :     const std::string osSubDir =
     704         126 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     705             :     const std::string osSubDirZarrJsonFilename =
     706         126 :         CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
     707             : 
     708             :     VSIStatBufL sStat;
     709             :     // Explicit group
     710          63 :     if (VSIStatL(osSubDirZarrJsonFilename.c_str(), &sStat) == 0)
     711             :     {
     712         110 :         CPLJSONDocument oDoc;
     713          55 :         if (oDoc.Load(osSubDirZarrJsonFilename.c_str()))
     714             :         {
     715         110 :             const auto oRoot = oDoc.GetRoot();
     716          55 :             if (oRoot.GetInteger("zarr_format") != 3)
     717             :             {
     718           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     719             :                          "Unhandled zarr_format value");
     720           0 :                 return nullptr;
     721             :             }
     722         165 :             const std::string osNodeType = oRoot.GetString("node_type");
     723          55 :             if (osNodeType != "group")
     724             :             {
     725           3 :                 CPLError(CE_Failure, CPLE_AppDefined, "%s is a %s, not a group",
     726             :                          osName.c_str(), osNodeType.c_str());
     727           3 :                 return nullptr;
     728             :             }
     729             :             auto poSubGroup = ZarrV3Group::Create(
     730         104 :                 m_poSharedResource, GetFullName(), osName, osSubDir);
     731          52 :             poSubGroup->m_bFileHasBeenWritten = true;
     732          52 :             poSubGroup->m_poParent =
     733         104 :                 std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
     734          52 :             poSubGroup->SetUpdatable(m_bUpdatable);
     735          52 :             m_oMapGroups[osName] = poSubGroup;
     736          52 :             return poSubGroup;
     737             :         }
     738           0 :         return nullptr;
     739             :     }
     740             : 
     741             :     // Implicit group
     742           8 :     if (VSIStatL(osSubDir.c_str(), &sStat) == 0 && VSI_ISDIR(sStat.st_mode))
     743             :     {
     744             :         // Note: Python zarr v3.0.2 still generates implicit groups
     745             :         // See https://github.com/zarr-developers/zarr-python/issues/2794
     746           2 :         CPLError(CE_Warning, CPLE_AppDefined,
     747             :                  "Support for Zarr V3 implicit group is now deprecated, and "
     748             :                  "may be removed in a future version");
     749           2 :         auto poSubGroup = ZarrV3Group::Create(m_poSharedResource, GetFullName(),
     750           4 :                                               osName, osSubDir);
     751           2 :         poSubGroup->m_bFileHasBeenWritten = true;
     752           2 :         poSubGroup->m_poParent =
     753           4 :             std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
     754           2 :         poSubGroup->SetUpdatable(m_bUpdatable);
     755           2 :         m_oMapGroups[osName] = poSubGroup;
     756           2 :         return poSubGroup;
     757             :     }
     758             : 
     759           6 :     return nullptr;
     760             : }
     761             : 
     762             : /************************************************************************/
     763             : /*                     ZarrV3Group::CreateOnDisk()                      */
     764             : /************************************************************************/
     765             : 
     766         222 : std::shared_ptr<ZarrV3Group> ZarrV3Group::CreateOnDisk(
     767             :     const std::shared_ptr<ZarrSharedResource> &poSharedResource,
     768             :     const std::string &osParentFullName, const std::string &osName,
     769             :     const std::string &osDirectoryName)
     770             : {
     771         222 :     if (VSIMkdir(osDirectoryName.c_str(), 0755) != 0)
     772             :     {
     773             :         VSIStatBufL sStat;
     774           3 :         if (VSIStatL(osDirectoryName.c_str(), &sStat) == 0)
     775             :         {
     776           3 :             CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
     777             :                      osDirectoryName.c_str());
     778             :         }
     779             :         else
     780             :         {
     781           0 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
     782             :                      osDirectoryName.c_str());
     783             :         }
     784           3 :         return nullptr;
     785             :     }
     786             : 
     787             :     const std::string osZarrJsonFilename(
     788         438 :         CPLFormFilenameSafe(osDirectoryName.c_str(), "zarr.json", nullptr));
     789         219 :     VSILFILE *fp = nullptr;
     790         399 :     if (!(poSharedResource->IsConsolidatedMetadataEnabled() &&
     791         180 :           cpl::starts_with(osZarrJsonFilename, "/vsizip/") &&
     792           1 :           osParentFullName.empty() && osName == "/"))
     793             :     {
     794         218 :         fp = VSIFOpenL(osZarrJsonFilename.c_str(), "wb");
     795         218 :         if (!fp)
     796             :         {
     797           0 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create file %s.",
     798             :                      osZarrJsonFilename.c_str());
     799           0 :             return nullptr;
     800             :         }
     801         218 :         VSIFPrintfL(fp, "{\n"
     802             :                         "    \"zarr_format\": 3,\n"
     803             :                         "    \"node_type\": \"group\",\n"
     804             :                         "    \"attributes\": {}\n"
     805             :                         "}\n");
     806         218 :         VSIFCloseL(fp);
     807             :     }
     808             : 
     809             :     auto poGroup = ZarrV3Group::Create(poSharedResource, osParentFullName,
     810         438 :                                        osName, osDirectoryName);
     811         219 :     poGroup->SetUpdatable(true);
     812         219 :     poGroup->m_bDirectoryExplored = true;
     813         219 :     poGroup->m_bFileHasBeenWritten = fp != nullptr;
     814             : 
     815         438 :     CPLJSONObject oObj;
     816         219 :     oObj.Add("zarr_format", 3);
     817         219 :     oObj.Add("node_type", "group");
     818         219 :     oObj.Add("attributes", CPLJSONObject());
     819         219 :     poSharedResource->SetZMetadataItem(osZarrJsonFilename, oObj);
     820             : 
     821         219 :     return poGroup;
     822             : }
     823             : 
     824             : /************************************************************************/
     825             : /*                      ZarrV3Group::CreateGroup()                      */
     826             : /************************************************************************/
     827             : 
     828             : std::shared_ptr<GDALGroup>
     829          68 : ZarrV3Group::CreateGroup(const std::string &osName,
     830             :                          CSLConstList /* papszOptions */)
     831             : {
     832          68 :     if (!CheckValidAndErrorOutIfNot())
     833           0 :         return nullptr;
     834             : 
     835          68 :     if (!m_bUpdatable)
     836             :     {
     837           3 :         CPLError(CE_Failure, CPLE_NotSupported,
     838             :                  "Dataset not open in update mode");
     839           3 :         return nullptr;
     840             :     }
     841          65 :     if (!IsValidObjectName(osName))
     842             :     {
     843          14 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid group name");
     844          14 :         return nullptr;
     845             :     }
     846             : 
     847          51 :     GetGroupNames();
     848             : 
     849          51 :     if (cpl::contains(m_oSetGroupNames, osName))
     850             :     {
     851           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     852             :                  "A group with same name already exists");
     853           1 :         return nullptr;
     854             :     }
     855             : 
     856             :     const std::string osDirectoryName =
     857         100 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     858          50 :     auto poGroup = CreateOnDisk(m_poSharedResource, GetFullName(), osName,
     859         100 :                                 osDirectoryName);
     860          50 :     if (!poGroup)
     861           3 :         return nullptr;
     862          47 :     poGroup->m_poParent =
     863          94 :         std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
     864          47 :     m_oMapGroups[osName] = poGroup;
     865          47 :     m_aosGroups.emplace_back(osName);
     866          47 :     return poGroup;
     867             : }
     868             : 
     869             : /************************************************************************/
     870             : /*                           FillDTypeElts()                            */
     871             : /************************************************************************/
     872             : 
     873         204 : static CPLJSONObject FillDTypeElts(const GDALExtendedDataType &oDataType,
     874             :                                    std::vector<DtypeElt> &aoDtypeElts)
     875             : {
     876         204 :     CPLJSONObject dtype;
     877         408 :     const std::string dummy("dummy");
     878             : 
     879         204 :     if (oDataType.GetClass() == GEDTC_STRING)
     880             :     {
     881             :         const int nMaxLen = std::max(
     882           6 :             2, atoi(CPLGetConfigOption("ZARR_VLEN_STRING_MAX_LENGTH", "256")));
     883          12 :         DtypeElt elt;
     884           6 :         elt.nativeType = DtypeElt::NativeType::STRING_ASCII;
     885           6 :         elt.nativeOffset = 0;
     886           6 :         elt.nativeSize = static_cast<size_t>(nMaxLen);
     887           6 :         elt.gdalOffset = 0;
     888           6 :         elt.gdalSize = oDataType.GetSize();
     889           6 :         aoDtypeElts.emplace_back(elt);
     890           6 :         dtype.Set(dummy, "string");
     891           6 :         return dtype;
     892             :     }
     893             : 
     894         198 :     const auto eDT = oDataType.GetNumericDataType();
     895         396 :     DtypeElt elt;
     896         198 :     bool bUnsupported = false;
     897         198 :     switch (eDT)
     898             :     {
     899          68 :         case GDT_UInt8:
     900             :         {
     901          68 :             elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     902          68 :             dtype.Set(dummy, "uint8");
     903          68 :             break;
     904             :         }
     905           6 :         case GDT_Int8:
     906             :         {
     907           6 :             elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     908           6 :             dtype.Set(dummy, "int8");
     909           6 :             break;
     910             :         }
     911          11 :         case GDT_UInt16:
     912             :         {
     913          11 :             elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     914          11 :             dtype.Set(dummy, "uint16");
     915          11 :             break;
     916             :         }
     917           9 :         case GDT_Int16:
     918             :         {
     919           9 :             elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     920           9 :             dtype.Set(dummy, "int16");
     921           9 :             break;
     922             :         }
     923           7 :         case GDT_UInt32:
     924             :         {
     925           7 :             elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     926           7 :             dtype.Set(dummy, "uint32");
     927           7 :             break;
     928             :         }
     929           7 :         case GDT_Int32:
     930             :         {
     931           7 :             elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     932           7 :             dtype.Set(dummy, "int32");
     933           7 :             break;
     934             :         }
     935           6 :         case GDT_UInt64:
     936             :         {
     937           6 :             elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     938           6 :             dtype.Set(dummy, "uint64");
     939           6 :             break;
     940             :         }
     941           6 :         case GDT_Int64:
     942             :         {
     943           6 :             elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     944           6 :             dtype.Set(dummy, "int64");
     945           6 :             break;
     946             :         }
     947           1 :         case GDT_Float16:
     948             :         {
     949           1 :             elt.nativeType = DtypeElt::NativeType::IEEEFP;
     950           1 :             dtype.Set(dummy, "float16");
     951           1 :             break;
     952             :         }
     953          40 :         case GDT_Float32:
     954             :         {
     955          40 :             elt.nativeType = DtypeElt::NativeType::IEEEFP;
     956          40 :             dtype.Set(dummy, "float32");
     957          40 :             break;
     958             :         }
     959          33 :         case GDT_Float64:
     960             :         {
     961          33 :             elt.nativeType = DtypeElt::NativeType::IEEEFP;
     962          33 :             dtype.Set(dummy, "float64");
     963          33 :             break;
     964             :         }
     965           2 :         case GDT_Unknown:
     966             :         case GDT_CInt16:
     967             :         case GDT_CInt32:
     968             :         {
     969           2 :             bUnsupported = true;
     970           2 :             break;
     971             :         }
     972           0 :         case GDT_CFloat16:
     973             :         {
     974           0 :             elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     975           0 :             dtype.Set(dummy, "complex32");
     976           0 :             break;
     977             :         }
     978           1 :         case GDT_CFloat32:
     979             :         {
     980           1 :             elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     981           1 :             dtype.Set(dummy, "complex64");
     982           1 :             break;
     983             :         }
     984           1 :         case GDT_CFloat64:
     985             :         {
     986           1 :             elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     987           1 :             dtype.Set(dummy, "complex128");
     988           1 :             break;
     989             :         }
     990           0 :         case GDT_TypeCount:
     991             :         {
     992             :             static_assert(GDT_TypeCount == GDT_CFloat16 + 1,
     993             :                           "GDT_TypeCount == GDT_CFloat16 + 1");
     994           0 :             break;
     995             :         }
     996             :     }
     997         198 :     if (bUnsupported)
     998             :     {
     999           2 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %s",
    1000             :                  GDALGetDataTypeName(eDT));
    1001           2 :         dtype = CPLJSONObject();
    1002           2 :         dtype.Deinit();
    1003           2 :         return dtype;
    1004             :     }
    1005         196 :     elt.nativeOffset = 0;
    1006         196 :     elt.nativeSize = GDALGetDataTypeSizeBytes(eDT);
    1007         196 :     elt.gdalOffset = 0;
    1008         196 :     elt.gdalSize = elt.nativeSize;
    1009             : #ifdef CPL_MSB
    1010             :     elt.needByteSwapping = elt.nativeSize > 1;
    1011             : #endif
    1012         196 :     aoDtypeElts.emplace_back(elt);
    1013             : 
    1014         196 :     return dtype;
    1015             : }
    1016             : 
    1017             : /************************************************************************/
    1018             : /*                     ZarrV3Group::CreateMDArray()                     */
    1019             : /************************************************************************/
    1020             : 
    1021         220 : std::shared_ptr<GDALMDArray> ZarrV3Group::CreateMDArray(
    1022             :     const std::string &osName,
    1023             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
    1024             :     const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
    1025             : {
    1026         220 :     if (!CheckValidAndErrorOutIfNot())
    1027           0 :         return nullptr;
    1028             : 
    1029         220 :     if (!m_bUpdatable)
    1030             :     {
    1031           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1032             :                  "Dataset not open in update mode");
    1033           0 :         return nullptr;
    1034             :     }
    1035         220 :     if (!IsValidObjectName(osName))
    1036             :     {
    1037          14 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid array name");
    1038          14 :         return nullptr;
    1039             :     }
    1040             : 
    1041         214 :     if (oDataType.GetClass() != GEDTC_NUMERIC &&
    1042           8 :         oDataType.GetClass() != GEDTC_STRING)
    1043             :     {
    1044           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    1045             :                  "Unsupported data type with Zarr V3");
    1046           2 :         return nullptr;
    1047             :     }
    1048             : 
    1049         204 :     if (!EQUAL(CSLFetchNameValueDef(papszOptions, "FILTER", "NONE"), "NONE"))
    1050             :     {
    1051           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1052             :                  "FILTER option not supported with Zarr V3");
    1053           0 :         return nullptr;
    1054             :     }
    1055             : 
    1056         408 :     std::vector<DtypeElt> aoDtypeElts;
    1057         612 :     const auto dtype = FillDTypeElts(oDataType, aoDtypeElts)["dummy"];
    1058         204 :     if (!dtype.IsValid() || aoDtypeElts.empty())
    1059           2 :         return nullptr;
    1060             : 
    1061         202 :     GetMDArrayNames();
    1062             : 
    1063         202 :     if (cpl::contains(m_oSetArrayNames, osName))
    1064             :     {
    1065           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    1066             :                  "An array with same name already exists");
    1067           2 :         return nullptr;
    1068             :     }
    1069             : 
    1070         400 :     std::vector<GUInt64> anOuterBlockSize;
    1071         200 :     if (!ZarrArray::FillBlockSize(aoDimensions, oDataType, anOuterBlockSize,
    1072             :                                   papszOptions))
    1073           1 :         return nullptr;
    1074             : 
    1075             :     const char *pszDimSeparator =
    1076         199 :         CSLFetchNameValueDef(papszOptions, "DIM_SEPARATOR", "/");
    1077             : 
    1078             :     const std::string osArrayDirectory =
    1079         398 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
    1080         199 :     if (VSIMkdir(osArrayDirectory.c_str(), 0755) != 0)
    1081             :     {
    1082             :         VSIStatBufL sStat;
    1083           2 :         if (VSIStatL(osArrayDirectory.c_str(), &sStat) == 0)
    1084             :         {
    1085           2 :             CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
    1086             :                      osArrayDirectory.c_str());
    1087             :         }
    1088             :         else
    1089             :         {
    1090           0 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
    1091             :                      osArrayDirectory.c_str());
    1092             :         }
    1093           2 :         return nullptr;
    1094             :     }
    1095             : 
    1096         197 :     std::unique_ptr<ZarrV3CodecSequence> poCodecs;
    1097         394 :     CPLJSONArray oCodecs;
    1098             : 
    1099         197 :     const bool bIsString = (oDataType.GetClass() == GEDTC_STRING);
    1100             : 
    1101         197 :     const bool bFortranOrder = EQUAL(
    1102             :         CSLFetchNameValueDef(papszOptions, "CHUNK_MEMORY_LAYOUT", "C"), "F");
    1103         197 :     if (!bIsString && bFortranOrder && aoDimensions.size() > 1)
    1104             :     {
    1105          80 :         CPLJSONObject oCodec;
    1106          40 :         oCodec.Add("name", "transpose");
    1107          80 :         std::vector<int> anOrder;
    1108          40 :         const int nDims = static_cast<int>(aoDimensions.size());
    1109         130 :         for (int i = 0; i < nDims; ++i)
    1110             :         {
    1111          90 :             anOrder.push_back(nDims - 1 - i);
    1112             :         }
    1113          40 :         oCodec.Add("configuration",
    1114          80 :                    ZarrV3CodecTranspose::GetConfiguration(anOrder));
    1115          40 :         oCodecs.Add(oCodec);
    1116             :     }
    1117             : 
    1118             :     // Array-to-bytes codec: vlen-utf8 for strings, bytes for numeric
    1119         197 :     if (bIsString)
    1120             :     {
    1121          12 :         CPLJSONObject oCodec;
    1122           6 :         oCodec.Add("name", "vlen-utf8");
    1123           6 :         oCodecs.Add(oCodec);
    1124             :     }
    1125             :     else
    1126             :     {
    1127             :         // Not documented option, but 'bytes' codec is required
    1128             :         const char *pszEndian =
    1129         191 :             CSLFetchNameValueDef(papszOptions, "@ENDIAN", "little");
    1130         382 :         CPLJSONObject oCodec;
    1131         191 :         oCodec.Add("name", "bytes");
    1132         191 :         oCodec.Add("configuration", ZarrV3CodecBytes::GetConfiguration(
    1133         191 :                                         EQUAL(pszEndian, "little")));
    1134         191 :         oCodecs.Add(oCodec);
    1135             :     }
    1136             : 
    1137             :     const char *pszCompressor =
    1138         197 :         CSLFetchNameValueDef(papszOptions, "COMPRESS", "NONE");
    1139         197 :     if (EQUAL(pszCompressor, "GZIP"))
    1140             :     {
    1141          62 :         CPLJSONObject oCodec;
    1142          31 :         oCodec.Add("name", "gzip");
    1143             :         const char *pszLevel =
    1144          31 :             CSLFetchNameValueDef(papszOptions, "GZIP_LEVEL", "6");
    1145          31 :         oCodec.Add("configuration",
    1146          62 :                    ZarrV3CodecGZip::GetConfiguration(atoi(pszLevel)));
    1147          31 :         oCodecs.Add(oCodec);
    1148             :     }
    1149         166 :     else if (EQUAL(pszCompressor, "BLOSC"))
    1150             :     {
    1151           2 :         const auto psCompressor = CPLGetCompressor("blosc");
    1152           2 :         if (!psCompressor)
    1153           0 :             return nullptr;
    1154             :         const char *pszOptions =
    1155           2 :             CSLFetchNameValueDef(psCompressor->papszMetadata, "OPTIONS", "");
    1156           2 :         CPLXMLTreeCloser oTreeCompressor(CPLParseXMLString(pszOptions));
    1157             :         const auto psRoot =
    1158           2 :             oTreeCompressor.get()
    1159           2 :                 ? CPLGetXMLNode(oTreeCompressor.get(), "=Options")
    1160           2 :                 : nullptr;
    1161           2 :         if (!psRoot)
    1162           0 :             return nullptr;
    1163             : 
    1164           2 :         const char *cname = "zlib";
    1165          14 :         for (const CPLXMLNode *psNode = psRoot->psChild; psNode != nullptr;
    1166          12 :              psNode = psNode->psNext)
    1167             :         {
    1168          12 :             if (psNode->eType == CXT_Element)
    1169             :             {
    1170          12 :                 const char *pszName = CPLGetXMLValue(psNode, "name", "");
    1171          12 :                 if (EQUAL(pszName, "CNAME"))
    1172             :                 {
    1173           2 :                     cname = CPLGetXMLValue(psNode, "default", cname);
    1174             :                 }
    1175             :             }
    1176             :         }
    1177             : 
    1178           4 :         CPLJSONObject oCodec;
    1179           2 :         oCodec.Add("name", "blosc");
    1180           2 :         cname = CSLFetchNameValueDef(papszOptions, "BLOSC_CNAME", cname);
    1181             :         const int clevel =
    1182           2 :             atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_CLEVEL", "5"));
    1183             :         const char *shuffle =
    1184           2 :             CSLFetchNameValueDef(papszOptions, "BLOSC_SHUFFLE", "BYTE");
    1185           3 :         shuffle = (EQUAL(shuffle, "0") || EQUAL(shuffle, "NONE")) ? "noshuffle"
    1186           1 :                   : (EQUAL(shuffle, "1") || EQUAL(shuffle, "BYTE")) ? "shuffle"
    1187           0 :                   : (EQUAL(shuffle, "2") || EQUAL(shuffle, "BIT"))
    1188           0 :                       ? "bitshuffle"
    1189             :                       : "invalid";
    1190             :         const int nDefaultTypeSize =
    1191           2 :             bIsString ? 1
    1192           2 :                       : GDALGetDataTypeSizeBytes(GDALGetNonComplexDataType(
    1193           2 :                             oDataType.GetNumericDataType()));
    1194             :         const int typesize =
    1195           2 :             atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_TYPESIZE",
    1196             :                                       CPLSPrintf("%d", nDefaultTypeSize)));
    1197             :         const int blocksize =
    1198           2 :             atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_BLOCKSIZE", "0"));
    1199           2 :         oCodec.Add("configuration",
    1200           4 :                    ZarrV3CodecBlosc::GetConfiguration(cname, clevel, shuffle,
    1201             :                                                       typesize, blocksize));
    1202           2 :         oCodecs.Add(oCodec);
    1203             :     }
    1204         164 :     else if (EQUAL(pszCompressor, "ZSTD"))
    1205             :     {
    1206          14 :         CPLJSONObject oCodec;
    1207           7 :         oCodec.Add("name", "zstd");
    1208             :         const char *pszLevel =
    1209           7 :             CSLFetchNameValueDef(papszOptions, "ZSTD_LEVEL", "13");
    1210           7 :         const bool bChecksum = CPLTestBool(
    1211             :             CSLFetchNameValueDef(papszOptions, "ZSTD_CHECKSUM", "FALSE"));
    1212           7 :         oCodec.Add("configuration", ZarrV3CodecZstd::GetConfiguration(
    1213             :                                         atoi(pszLevel), bChecksum));
    1214           7 :         oCodecs.Add(oCodec);
    1215             :     }
    1216         157 :     else if (!EQUAL(pszCompressor, "NONE"))
    1217             :     {
    1218           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1219             :                  "COMPRESS = %s not implemented with Zarr V3", pszCompressor);
    1220           1 :         return nullptr;
    1221             :     }
    1222             : 
    1223             :     // Sharding: wrap inner codecs into a sharding_indexed codec
    1224             :     const char *pszShardChunkShape =
    1225         196 :         CSLFetchNameValue(papszOptions, "SHARD_CHUNK_SHAPE");
    1226         196 :     if (pszShardChunkShape != nullptr)
    1227             :     {
    1228             : 
    1229             :         const CPLStringList aosChunkShape(
    1230          13 :             CSLTokenizeString2(pszShardChunkShape, ",", 0));
    1231          13 :         if (static_cast<size_t>(aosChunkShape.size()) != aoDimensions.size())
    1232             :         {
    1233           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1234             :                      "SHARD_CHUNK_SHAPE has %d values, expected %d",
    1235             :                      aosChunkShape.size(),
    1236           1 :                      static_cast<int>(aoDimensions.size()));
    1237           1 :             return nullptr;
    1238             :         }
    1239             : 
    1240          12 :         CPLJSONArray oChunkShapeArray;
    1241          35 :         for (int i = 0; i < aosChunkShape.size(); ++i)
    1242             :         {
    1243          24 :             const auto nInner = static_cast<GUInt64>(atoll(aosChunkShape[i]));
    1244          24 :             if (nInner == 0 || anOuterBlockSize[i] % nInner != 0)
    1245             :             {
    1246           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1247             :                          "SHARD_CHUNK_SHAPE[%d]=%s must divide "
    1248             :                          "BLOCKSIZE[%d]=" CPL_FRMT_GUIB " evenly",
    1249           1 :                          i, aosChunkShape[i], i, anOuterBlockSize[i]);
    1250           1 :                 return nullptr;
    1251             :             }
    1252          23 :             oChunkShapeArray.Add(static_cast<uint64_t>(nInner));
    1253             :         }
    1254             : 
    1255             :         // Index codecs: always bytes(little) + crc32c
    1256          22 :         CPLJSONArray oIndexCodecs;
    1257             :         {
    1258          22 :             CPLJSONObject oBytesCodec;
    1259          11 :             oBytesCodec.Add("name", "bytes");
    1260          11 :             oBytesCodec.Add("configuration",
    1261          22 :                             ZarrV3CodecBytes::GetConfiguration(true));
    1262          11 :             oIndexCodecs.Add(oBytesCodec);
    1263             :         }
    1264             :         {
    1265          22 :             CPLJSONObject oCRC32CCodec;
    1266          11 :             oCRC32CCodec.Add("name", "crc32c");
    1267          11 :             oIndexCodecs.Add(oCRC32CCodec);
    1268             :         }
    1269             : 
    1270          22 :         CPLJSONObject oShardingConfig;
    1271          11 :         oShardingConfig.Add("chunk_shape", oChunkShapeArray);
    1272          11 :         oShardingConfig.Add("codecs", oCodecs);
    1273          11 :         oShardingConfig.Add("index_codecs", oIndexCodecs);
    1274          11 :         oShardingConfig.Add("index_location", "end");
    1275             : 
    1276          22 :         CPLJSONObject oShardingCodec;
    1277          11 :         oShardingCodec.Add("name", "sharding_indexed");
    1278          11 :         oShardingCodec.Add("configuration", oShardingConfig);
    1279             : 
    1280             :         // Replace top-level codecs with just the sharding codec
    1281          11 :         oCodecs = CPLJSONArray();
    1282          11 :         oCodecs.Add(oShardingCodec);
    1283             :     }
    1284             : 
    1285         388 :     std::vector<GUInt64> anInnerBlockSize = anOuterBlockSize;
    1286         194 :     if (oCodecs.Size() > 0)
    1287             :     {
    1288         194 :         std::vector<GByte> abyNoData;
    1289         388 :         poCodecs = ZarrV3Array::SetupCodecs(oCodecs, anOuterBlockSize,
    1290             :                                             anInnerBlockSize,
    1291         388 :                                             aoDtypeElts.back(), abyNoData);
    1292         194 :         if (!poCodecs)
    1293             :         {
    1294           0 :             return nullptr;
    1295             :         }
    1296             :     }
    1297             : 
    1298         194 :     auto poArray = ZarrV3Array::Create(m_poSharedResource, Self(), osName,
    1299             :                                        aoDimensions, oDataType, aoDtypeElts,
    1300         388 :                                        anOuterBlockSize, anInnerBlockSize);
    1301             : 
    1302         194 :     if (!poArray)
    1303           0 :         return nullptr;
    1304         194 :     poArray->SetNew(true);
    1305             :     const std::string osFilename =
    1306         388 :         CPLFormFilenameSafe(osArrayDirectory.c_str(), "zarr.json", nullptr);
    1307         194 :     poArray->SetFilename(osFilename);
    1308         194 :     poArray->SetDimSeparator(pszDimSeparator);
    1309         194 :     poArray->SetDtype(dtype);
    1310             :     const std::string osLastCodecName =
    1311         388 :         oCodecs.Size() > 0 ? oCodecs[oCodecs.Size() - 1].GetString("name")
    1312        1358 :                            : std::string();
    1313         246 :     if (!osLastCodecName.empty() && osLastCodecName != "bytes" &&
    1314          52 :         osLastCodecName != "vlen-utf8")
    1315             :     {
    1316          94 :         poArray->SetStructuralInfo(
    1317          94 :             "COMPRESSOR", oCodecs[oCodecs.Size() - 1].ToString().c_str());
    1318             :     }
    1319         194 :     if (poCodecs)
    1320         194 :         poArray->SetCodecs(oCodecs, std::move(poCodecs));
    1321             : 
    1322         194 :     poArray->SetCreationOptions(papszOptions);
    1323         194 :     poArray->SetUpdatable(true);
    1324         194 :     poArray->SetDefinitionModified(true);
    1325         194 :     if (!cpl::starts_with(osFilename, "/vsi") && !poArray->Flush())
    1326           0 :         return nullptr;
    1327         194 :     RegisterArray(poArray);
    1328             : 
    1329         194 :     return poArray;
    1330             : }

Generated by: LCOV version 1.14