LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v3_group.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 601 658 91.3 %
Date: 2026-02-27 12:28:37 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        1392 : 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        1392 :         poSharedResource, osParentName, osName, osRootDirectoryName));
      33        1392 :     poGroup->SetSelf(poGroup);
      34        1392 :     return poGroup;
      35             : }
      36             : 
      37             : /************************************************************************/
      38             : /*                           OpenZarrArray()                            */
      39             : /************************************************************************/
      40             : 
      41        1211 : std::shared_ptr<ZarrArray> ZarrV3Group::OpenZarrArray(const std::string &osName,
      42             :                                                       CSLConstList) const
      43             : {
      44        1211 :     if (!CheckValidAndErrorOutIfNot())
      45           0 :         return nullptr;
      46             : 
      47        1211 :     auto oIter = m_oMapMDArrays.find(osName);
      48        1211 :     if (oIter != m_oMapMDArrays.end())
      49        1101 :         return oIter->second;
      50             : 
      51         110 :     if (m_bReadFromConsolidatedMetadata)
      52          13 :         return nullptr;
      53             : 
      54             :     const std::string osSubDir =
      55         194 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
      56             :     const std::string osZarrayFilename =
      57         194 :         CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
      58             : 
      59             :     VSIStatBufL sStat;
      60          97 :     if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
      61             :     {
      62         150 :         CPLJSONDocument oDoc;
      63          75 :         if (!oDoc.Load(osZarrayFilename))
      64           0 :             return nullptr;
      65         150 :         const auto oRoot = oDoc.GetRoot();
      66          75 :         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          64 : void ZarrV3Group::ExploreDirectory() const
     101             : {
     102          64 :     if (m_bDirectoryExplored)
     103           0 :         return;
     104          64 :     m_bDirectoryExplored = true;
     105             : 
     106          64 :     auto psDir = VSIOpenDir(m_osDirectoryName.c_str(), 0, nullptr);
     107          64 :     if (!psDir)
     108           0 :         return;
     109         226 :     while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir))
     110             :     {
     111         162 :         if (VSI_ISDIR(psEntry->nMode))
     112             :         {
     113         196 :             std::string osName(psEntry->pszName);
     114         196 :             while (!osName.empty() &&
     115          98 :                    (osName.back() == '/' || osName.back() == '\\'))
     116           0 :                 osName.pop_back();
     117          98 :             if (osName.empty())
     118           0 :                 continue;
     119             :             const std::string osSubDir = CPLFormFilenameSafe(
     120          98 :                 m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     121             :             VSIStatBufL sStat;
     122             :             const std::string osZarrJsonFilename =
     123          98 :                 CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
     124          98 :             if (VSIStatL(osZarrJsonFilename.c_str(), &sStat) == 0)
     125             :             {
     126          96 :                 CPLJSONDocument oDoc;
     127          96 :                 if (oDoc.Load(osZarrJsonFilename.c_str()))
     128             :                 {
     129          96 :                     const auto oRoot = oDoc.GetRoot();
     130          96 :                     if (oRoot.GetInteger("zarr_format") != 3)
     131             :                     {
     132           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     133             :                                  "Unhandled zarr_format value");
     134           0 :                         continue;
     135             :                     }
     136         192 :                     const std::string osNodeType = oRoot.GetString("node_type");
     137          96 :                     if (osNodeType == "array")
     138             :                     {
     139          53 :                         if (!cpl::contains(m_oSetArrayNames, osName))
     140             :                         {
     141          51 :                             m_oSetArrayNames.insert(osName);
     142          51 :                             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         162 :     }
     172          64 :     VSICloseDir(psDir);
     173             : }
     174             : 
     175             : /************************************************************************/
     176             : /*                      ZarrV3Group::ZarrV3Group()                      */
     177             : /************************************************************************/
     178             : 
     179        1392 : ZarrV3Group::ZarrV3Group(
     180             :     const std::shared_ptr<ZarrSharedResource> &poSharedResource,
     181             :     const std::string &osParentName, const std::string &osName,
     182        1392 :     const std::string &osDirectoryName)
     183        1392 :     : ZarrGroupBase(poSharedResource, osParentName, osName)
     184             : {
     185        1392 :     m_osDirectoryName = osDirectoryName;
     186        1392 : }
     187             : 
     188             : /************************************************************************/
     189             : /*                     ZarrV3Group::~ZarrV3Group()                      */
     190             : /************************************************************************/
     191             : 
     192        2784 : ZarrV3Group::~ZarrV3Group()
     193             : {
     194        1392 :     ZarrV3Group::Close();
     195        2784 : }
     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        3218 : bool ZarrV3Group::Close()
     482             : {
     483        3218 :     bool bRet = ZarrGroupBase::Close();
     484             : 
     485        6357 :     if (m_bValid && (m_oAttrGroup.IsModified() ||
     486        3225 :                      (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        3218 :     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         181 : void ZarrV3Group::InitFromConsolidatedMetadata(
     560             :     const CPLJSONObject &oConsolidatedMetadata,
     561             :     const CPLJSONObject &oRootAttributes)
     562             : {
     563         362 :     const auto metadata = oConsolidatedMetadata["metadata"];
     564         181 :     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         181 :     m_bDirectoryExplored = true;
     571         181 :     m_bAttributesLoaded = true;
     572         181 :     m_bReadFromConsolidatedMetadata = true;
     573             : 
     574         181 :     if (oRootAttributes.IsValid())
     575             :     {
     576         181 :         m_oAttrGroup.Init(oRootAttributes, m_bUpdatable);
     577             :     }
     578             : 
     579         362 :     const auto children = metadata.GetChildren();
     580         362 :     std::map<std::string, const CPLJSONObject *> oMapArrays;
     581             : 
     582             :     // First pass to create groups and collect arrays
     583         688 :     for (const auto &child : children)
     584             :     {
     585         507 :         const std::string osName(child.GetName());
     586         507 :         if (std::count(osName.begin(), osName.end(), '/') > 32)
     587             :         {
     588             :             // Avoid too deep recursion in GetOrCreateSubGroup()
     589           0 :             continue;
     590             :         }
     591             : 
     592        1521 :         const std::string osNodeType = child.GetString("node_type");
     593         507 :         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         369 :         else if (osNodeType == "array")
     603             :         {
     604         369 :             oMapArrays[osName] = &child;
     605             :         }
     606             :     }
     607             : 
     608             :     const auto CreateArray =
     609         593 :         [this](const std::string &osArrayFullname, const CPLJSONObject &oArray)
     610             :     {
     611         369 :         const auto nLastSlashPos = osArrayFullname.rfind('/');
     612             :         auto poBelongingGroup =
     613             :             (nLastSlashPos == std::string::npos)
     614         369 :                 ? this
     615         593 :                 : GetOrCreateSubGroup("/" +
     616         593 :                                       osArrayFullname.substr(0, nLastSlashPos))
     617         369 :                       .get();
     618             :         const auto osArrayName =
     619             :             nLastSlashPos == std::string::npos
     620             :                 ? osArrayFullname
     621         738 :                 : osArrayFullname.substr(nLastSlashPos + 1);
     622             :         const std::string osZarrayFilename = CPLFormFilenameSafe(
     623         369 :             CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
     624             :                                 osArrayName.c_str(), nullptr)
     625             :                 .c_str(),
     626         369 :             "zarr.json", nullptr);
     627         369 :         poBelongingGroup->LoadArray(osArrayName, osZarrayFilename, oArray);
     628         369 :     };
     629             : 
     630             :     struct ArrayDesc
     631             :     {
     632             :         std::string osArrayFullname{};
     633             :         const CPLJSONObject *poArray = nullptr;
     634             :     };
     635             : 
     636         362 :     std::vector<ArrayDesc> aoRegularArrays;
     637             : 
     638             :     // Second pass to read attributes and create arrays that are indexing
     639             :     // variable
     640         688 :     for (const auto &child : children)
     641             :     {
     642        1014 :         const std::string osName(child.GetName());
     643        1521 :         const std::string osNodeType = child.GetString("node_type");
     644         507 :         if (osNodeType == "array")
     645             :         {
     646         369 :             auto oIter = oMapArrays.find(osName);
     647         369 :             if (oIter != oMapArrays.end())
     648             :             {
     649         369 :                 const auto nLastSlashPos = osName.rfind('/');
     650             :                 const std::string osArrayName =
     651             :                     (nLastSlashPos == std::string::npos)
     652             :                         ? osName
     653         738 :                         : osName.substr(nLastSlashPos + 1);
     654        1107 :                 const auto arrayDimensions = child["dimension_names"].ToArray();
     655         457 :                 if (arrayDimensions.IsValid() && arrayDimensions.Size() == 1 &&
     656         457 :                     arrayDimensions[0].ToString() == osArrayName)
     657             :                 {
     658          80 :                     CreateArray(osName, child);
     659          80 :                     oMapArrays.erase(oIter);
     660             :                 }
     661             :                 else
     662             :                 {
     663         578 :                     ArrayDesc desc;
     664         289 :                     desc.osArrayFullname = std::move(osName);
     665         289 :                     desc.poArray = oIter->second;
     666         289 :                     aoRegularArrays.emplace_back(std::move(desc));
     667             :                 }
     668             :             }
     669             :         }
     670             :     }
     671             : 
     672             :     // Third pass to create non-indexing arrays with attributes
     673         470 :     for (const auto &desc : aoRegularArrays)
     674             :     {
     675         289 :         CreateArray(desc.osArrayFullname, *(desc.poArray));
     676         289 :         oMapArrays.erase(desc.osArrayFullname);
     677             :     }
     678             : 
     679             :     // Fourth pass to create arrays without attributes
     680         181 :     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         216 : 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         216 :     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         426 :         CPLFormFilenameSafe(osDirectoryName.c_str(), "zarr.json", nullptr));
     789         213 :     VSILFILE *fp = nullptr;
     790         387 :     if (!(poSharedResource->IsConsolidatedMetadataEnabled() &&
     791         174 :           cpl::starts_with(osZarrJsonFilename, "/vsizip/") &&
     792           1 :           osParentFullName.empty() && osName == "/"))
     793             :     {
     794         212 :         fp = VSIFOpenL(osZarrJsonFilename.c_str(), "wb");
     795         212 :         if (!fp)
     796             :         {
     797           0 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create file %s.",
     798             :                      osZarrJsonFilename.c_str());
     799           0 :             return nullptr;
     800             :         }
     801         212 :         VSIFPrintfL(fp, "{\n"
     802             :                         "    \"zarr_format\": 3,\n"
     803             :                         "    \"node_type\": \"group\",\n"
     804             :                         "    \"attributes\": {}\n"
     805             :                         "}\n");
     806         212 :         VSIFCloseL(fp);
     807             :     }
     808             : 
     809             :     auto poGroup = ZarrV3Group::Create(poSharedResource, osParentFullName,
     810         426 :                                        osName, osDirectoryName);
     811         213 :     poGroup->SetUpdatable(true);
     812         213 :     poGroup->m_bDirectoryExplored = true;
     813         213 :     poGroup->m_bFileHasBeenWritten = fp != nullptr;
     814             : 
     815         426 :     CPLJSONObject oObj;
     816         213 :     oObj.Add("zarr_format", 3);
     817         213 :     oObj.Add("node_type", "group");
     818         213 :     oObj.Add("attributes", CPLJSONObject());
     819         213 :     poSharedResource->SetZMetadataItem(osZarrJsonFilename, oObj);
     820             : 
     821         213 :     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         196 : static CPLJSONObject FillDTypeElts(const GDALExtendedDataType &oDataType,
     874             :                                    std::vector<DtypeElt> &aoDtypeElts)
     875             : {
     876         196 :     CPLJSONObject dtype;
     877         392 :     const std::string dummy("dummy");
     878             : 
     879         196 :     const auto eDT = oDataType.GetNumericDataType();
     880         392 :     DtypeElt elt;
     881         196 :     bool bUnsupported = false;
     882         196 :     switch (eDT)
     883             :     {
     884          66 :         case GDT_UInt8:
     885             :         {
     886          66 :             elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     887          66 :             dtype.Set(dummy, "uint8");
     888          66 :             break;
     889             :         }
     890           6 :         case GDT_Int8:
     891             :         {
     892           6 :             elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     893           6 :             dtype.Set(dummy, "int8");
     894           6 :             break;
     895             :         }
     896          11 :         case GDT_UInt16:
     897             :         {
     898          11 :             elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     899          11 :             dtype.Set(dummy, "uint16");
     900          11 :             break;
     901             :         }
     902           9 :         case GDT_Int16:
     903             :         {
     904           9 :             elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     905           9 :             dtype.Set(dummy, "int16");
     906           9 :             break;
     907             :         }
     908           7 :         case GDT_UInt32:
     909             :         {
     910           7 :             elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     911           7 :             dtype.Set(dummy, "uint32");
     912           7 :             break;
     913             :         }
     914           7 :         case GDT_Int32:
     915             :         {
     916           7 :             elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     917           7 :             dtype.Set(dummy, "int32");
     918           7 :             break;
     919             :         }
     920           6 :         case GDT_UInt64:
     921             :         {
     922           6 :             elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     923           6 :             dtype.Set(dummy, "uint64");
     924           6 :             break;
     925             :         }
     926           6 :         case GDT_Int64:
     927             :         {
     928           6 :             elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     929           6 :             dtype.Set(dummy, "int64");
     930           6 :             break;
     931             :         }
     932           1 :         case GDT_Float16:
     933             :         {
     934           1 :             elt.nativeType = DtypeElt::NativeType::IEEEFP;
     935           1 :             dtype.Set(dummy, "float16");
     936           1 :             break;
     937             :         }
     938          40 :         case GDT_Float32:
     939             :         {
     940          40 :             elt.nativeType = DtypeElt::NativeType::IEEEFP;
     941          40 :             dtype.Set(dummy, "float32");
     942          40 :             break;
     943             :         }
     944          33 :         case GDT_Float64:
     945             :         {
     946          33 :             elt.nativeType = DtypeElt::NativeType::IEEEFP;
     947          33 :             dtype.Set(dummy, "float64");
     948          33 :             break;
     949             :         }
     950           2 :         case GDT_Unknown:
     951             :         case GDT_CInt16:
     952             :         case GDT_CInt32:
     953             :         {
     954           2 :             bUnsupported = true;
     955           2 :             break;
     956             :         }
     957           0 :         case GDT_CFloat16:
     958             :         {
     959           0 :             elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     960           0 :             dtype.Set(dummy, "complex32");
     961           0 :             break;
     962             :         }
     963           1 :         case GDT_CFloat32:
     964             :         {
     965           1 :             elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     966           1 :             dtype.Set(dummy, "complex64");
     967           1 :             break;
     968             :         }
     969           1 :         case GDT_CFloat64:
     970             :         {
     971           1 :             elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     972           1 :             dtype.Set(dummy, "complex128");
     973           1 :             break;
     974             :         }
     975           0 :         case GDT_TypeCount:
     976             :         {
     977             :             static_assert(GDT_TypeCount == GDT_CFloat16 + 1,
     978             :                           "GDT_TypeCount == GDT_CFloat16 + 1");
     979           0 :             break;
     980             :         }
     981             :     }
     982         196 :     if (bUnsupported)
     983             :     {
     984           2 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %s",
     985             :                  GDALGetDataTypeName(eDT));
     986           2 :         dtype = CPLJSONObject();
     987           2 :         dtype.Deinit();
     988           2 :         return dtype;
     989             :     }
     990         194 :     elt.nativeOffset = 0;
     991         194 :     elt.nativeSize = GDALGetDataTypeSizeBytes(eDT);
     992         194 :     elt.gdalOffset = 0;
     993         194 :     elt.gdalSize = elt.nativeSize;
     994             : #ifdef CPL_MSB
     995             :     elt.needByteSwapping = elt.nativeSize > 1;
     996             : #endif
     997         194 :     aoDtypeElts.emplace_back(elt);
     998             : 
     999         194 :     return dtype;
    1000             : }
    1001             : 
    1002             : /************************************************************************/
    1003             : /*                     ZarrV3Group::CreateMDArray()                     */
    1004             : /************************************************************************/
    1005             : 
    1006         214 : std::shared_ptr<GDALMDArray> ZarrV3Group::CreateMDArray(
    1007             :     const std::string &osName,
    1008             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
    1009             :     const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
    1010             : {
    1011         214 :     if (!CheckValidAndErrorOutIfNot())
    1012           0 :         return nullptr;
    1013             : 
    1014         214 :     if (!m_bUpdatable)
    1015             :     {
    1016           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1017             :                  "Dataset not open in update mode");
    1018           0 :         return nullptr;
    1019             :     }
    1020         214 :     if (!IsValidObjectName(osName))
    1021             :     {
    1022          14 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid array name");
    1023          14 :         return nullptr;
    1024             :     }
    1025             : 
    1026         200 :     if (oDataType.GetClass() != GEDTC_NUMERIC)
    1027             :     {
    1028           4 :         CPLError(CE_Failure, CPLE_AppDefined,
    1029             :                  "Unsupported data type with Zarr V3");
    1030           4 :         return nullptr;
    1031             :     }
    1032             : 
    1033         196 :     if (!EQUAL(CSLFetchNameValueDef(papszOptions, "FILTER", "NONE"), "NONE"))
    1034             :     {
    1035           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1036             :                  "FILTER option not supported with Zarr V3");
    1037           0 :         return nullptr;
    1038             :     }
    1039             : 
    1040         392 :     std::vector<DtypeElt> aoDtypeElts;
    1041         588 :     const auto dtype = FillDTypeElts(oDataType, aoDtypeElts)["dummy"];
    1042         196 :     if (!dtype.IsValid() || aoDtypeElts.empty())
    1043           2 :         return nullptr;
    1044             : 
    1045         194 :     GetMDArrayNames();
    1046             : 
    1047         194 :     if (cpl::contains(m_oSetArrayNames, osName))
    1048             :     {
    1049           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    1050             :                  "An array with same name already exists");
    1051           2 :         return nullptr;
    1052             :     }
    1053             : 
    1054         384 :     std::vector<GUInt64> anOuterBlockSize;
    1055         192 :     if (!ZarrArray::FillBlockSize(aoDimensions, oDataType, anOuterBlockSize,
    1056             :                                   papszOptions))
    1057           1 :         return nullptr;
    1058             : 
    1059             :     const char *pszDimSeparator =
    1060         191 :         CSLFetchNameValueDef(papszOptions, "DIM_SEPARATOR", "/");
    1061             : 
    1062             :     const std::string osArrayDirectory =
    1063         382 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
    1064         191 :     if (VSIMkdir(osArrayDirectory.c_str(), 0755) != 0)
    1065             :     {
    1066             :         VSIStatBufL sStat;
    1067           2 :         if (VSIStatL(osArrayDirectory.c_str(), &sStat) == 0)
    1068             :         {
    1069           2 :             CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
    1070             :                      osArrayDirectory.c_str());
    1071             :         }
    1072             :         else
    1073             :         {
    1074           0 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
    1075             :                      osArrayDirectory.c_str());
    1076             :         }
    1077           2 :         return nullptr;
    1078             :     }
    1079             : 
    1080         189 :     std::unique_ptr<ZarrV3CodecSequence> poCodecs;
    1081         378 :     CPLJSONArray oCodecs;
    1082             : 
    1083         189 :     const bool bFortranOrder = EQUAL(
    1084             :         CSLFetchNameValueDef(papszOptions, "CHUNK_MEMORY_LAYOUT", "C"), "F");
    1085         189 :     if (bFortranOrder && aoDimensions.size() > 1)
    1086             :     {
    1087          80 :         CPLJSONObject oCodec;
    1088          40 :         oCodec.Add("name", "transpose");
    1089          80 :         std::vector<int> anOrder;
    1090          40 :         const int nDims = static_cast<int>(aoDimensions.size());
    1091         130 :         for (int i = 0; i < nDims; ++i)
    1092             :         {
    1093          90 :             anOrder.push_back(nDims - 1 - i);
    1094             :         }
    1095          40 :         oCodec.Add("configuration",
    1096          80 :                    ZarrV3CodecTranspose::GetConfiguration(anOrder));
    1097          40 :         oCodecs.Add(oCodec);
    1098             :     }
    1099             : 
    1100             :     // Not documented option, but 'bytes' codec is required
    1101             :     const char *pszEndian =
    1102         189 :         CSLFetchNameValueDef(papszOptions, "@ENDIAN", "little");
    1103             :     {
    1104         378 :         CPLJSONObject oCodec;
    1105         189 :         oCodec.Add("name", "bytes");
    1106         189 :         oCodec.Add("configuration", ZarrV3CodecBytes::GetConfiguration(
    1107         189 :                                         EQUAL(pszEndian, "little")));
    1108         189 :         oCodecs.Add(oCodec);
    1109             :     }
    1110             : 
    1111             :     const char *pszCompressor =
    1112         189 :         CSLFetchNameValueDef(papszOptions, "COMPRESS", "NONE");
    1113         189 :     if (EQUAL(pszCompressor, "GZIP"))
    1114             :     {
    1115          62 :         CPLJSONObject oCodec;
    1116          31 :         oCodec.Add("name", "gzip");
    1117             :         const char *pszLevel =
    1118          31 :             CSLFetchNameValueDef(papszOptions, "GZIP_LEVEL", "6");
    1119          31 :         oCodec.Add("configuration",
    1120          62 :                    ZarrV3CodecGZip::GetConfiguration(atoi(pszLevel)));
    1121          31 :         oCodecs.Add(oCodec);
    1122             :     }
    1123         158 :     else if (EQUAL(pszCompressor, "BLOSC"))
    1124             :     {
    1125           2 :         const auto psCompressor = CPLGetCompressor("blosc");
    1126           2 :         if (!psCompressor)
    1127           0 :             return nullptr;
    1128             :         const char *pszOptions =
    1129           2 :             CSLFetchNameValueDef(psCompressor->papszMetadata, "OPTIONS", "");
    1130           2 :         CPLXMLTreeCloser oTreeCompressor(CPLParseXMLString(pszOptions));
    1131             :         const auto psRoot =
    1132           2 :             oTreeCompressor.get()
    1133           2 :                 ? CPLGetXMLNode(oTreeCompressor.get(), "=Options")
    1134           2 :                 : nullptr;
    1135           2 :         if (!psRoot)
    1136           0 :             return nullptr;
    1137             : 
    1138           2 :         const char *cname = "zlib";
    1139          14 :         for (const CPLXMLNode *psNode = psRoot->psChild; psNode != nullptr;
    1140          12 :              psNode = psNode->psNext)
    1141             :         {
    1142          12 :             if (psNode->eType == CXT_Element)
    1143             :             {
    1144          12 :                 const char *pszName = CPLGetXMLValue(psNode, "name", "");
    1145          12 :                 if (EQUAL(pszName, "CNAME"))
    1146             :                 {
    1147           2 :                     cname = CPLGetXMLValue(psNode, "default", cname);
    1148             :                 }
    1149             :             }
    1150             :         }
    1151             : 
    1152           4 :         CPLJSONObject oCodec;
    1153           2 :         oCodec.Add("name", "blosc");
    1154           2 :         cname = CSLFetchNameValueDef(papszOptions, "BLOSC_CNAME", cname);
    1155             :         const int clevel =
    1156           2 :             atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_CLEVEL", "5"));
    1157             :         const char *shuffle =
    1158           2 :             CSLFetchNameValueDef(papszOptions, "BLOSC_SHUFFLE", "BYTE");
    1159           3 :         shuffle = (EQUAL(shuffle, "0") || EQUAL(shuffle, "NONE")) ? "noshuffle"
    1160           1 :                   : (EQUAL(shuffle, "1") || EQUAL(shuffle, "BYTE")) ? "shuffle"
    1161           0 :                   : (EQUAL(shuffle, "2") || EQUAL(shuffle, "BIT"))
    1162           0 :                       ? "bitshuffle"
    1163             :                       : "invalid";
    1164           2 :         const int typesize = atoi(CSLFetchNameValueDef(
    1165             :             papszOptions, "BLOSC_TYPESIZE",
    1166             :             CPLSPrintf("%d", GDALGetDataTypeSizeBytes(GDALGetNonComplexDataType(
    1167             :                                  oDataType.GetNumericDataType())))));
    1168             :         const int blocksize =
    1169           2 :             atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_BLOCKSIZE", "0"));
    1170           2 :         oCodec.Add("configuration",
    1171           4 :                    ZarrV3CodecBlosc::GetConfiguration(cname, clevel, shuffle,
    1172             :                                                       typesize, blocksize));
    1173           2 :         oCodecs.Add(oCodec);
    1174             :     }
    1175         156 :     else if (EQUAL(pszCompressor, "ZSTD"))
    1176             :     {
    1177          12 :         CPLJSONObject oCodec;
    1178           6 :         oCodec.Add("name", "zstd");
    1179             :         const char *pszLevel =
    1180           6 :             CSLFetchNameValueDef(papszOptions, "ZSTD_LEVEL", "13");
    1181           6 :         const bool bChecksum = CPLTestBool(
    1182             :             CSLFetchNameValueDef(papszOptions, "ZSTD_CHECKSUM", "FALSE"));
    1183           6 :         oCodec.Add("configuration", ZarrV3CodecZstd::GetConfiguration(
    1184             :                                         atoi(pszLevel), bChecksum));
    1185           6 :         oCodecs.Add(oCodec);
    1186             :     }
    1187         150 :     else if (!EQUAL(pszCompressor, "NONE"))
    1188             :     {
    1189           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1190             :                  "COMPRESS = %s not implemented with Zarr V3", pszCompressor);
    1191           1 :         return nullptr;
    1192             :     }
    1193             : 
    1194             :     // Sharding: wrap inner codecs into a sharding_indexed codec
    1195             :     const char *pszShardChunkShape =
    1196         188 :         CSLFetchNameValue(papszOptions, "SHARD_CHUNK_SHAPE");
    1197         188 :     if (pszShardChunkShape != nullptr)
    1198             :     {
    1199             : 
    1200             :         const CPLStringList aosChunkShape(
    1201          13 :             CSLTokenizeString2(pszShardChunkShape, ",", 0));
    1202          13 :         if (static_cast<size_t>(aosChunkShape.size()) != aoDimensions.size())
    1203             :         {
    1204           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1205             :                      "SHARD_CHUNK_SHAPE has %d values, expected %d",
    1206             :                      aosChunkShape.size(),
    1207           1 :                      static_cast<int>(aoDimensions.size()));
    1208           1 :             return nullptr;
    1209             :         }
    1210             : 
    1211          12 :         CPLJSONArray oChunkShapeArray;
    1212          35 :         for (int i = 0; i < aosChunkShape.size(); ++i)
    1213             :         {
    1214          24 :             const auto nInner = static_cast<GUInt64>(atoll(aosChunkShape[i]));
    1215          24 :             if (nInner == 0 || anOuterBlockSize[i] % nInner != 0)
    1216             :             {
    1217           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1218             :                          "SHARD_CHUNK_SHAPE[%d]=%s must divide "
    1219             :                          "BLOCKSIZE[%d]=" CPL_FRMT_GUIB " evenly",
    1220           1 :                          i, aosChunkShape[i], i, anOuterBlockSize[i]);
    1221           1 :                 return nullptr;
    1222             :             }
    1223          23 :             oChunkShapeArray.Add(static_cast<uint64_t>(nInner));
    1224             :         }
    1225             : 
    1226             :         // Index codecs: always bytes(little) + crc32c
    1227          22 :         CPLJSONArray oIndexCodecs;
    1228             :         {
    1229          22 :             CPLJSONObject oBytesCodec;
    1230          11 :             oBytesCodec.Add("name", "bytes");
    1231          11 :             oBytesCodec.Add("configuration",
    1232          22 :                             ZarrV3CodecBytes::GetConfiguration(true));
    1233          11 :             oIndexCodecs.Add(oBytesCodec);
    1234             :         }
    1235             :         {
    1236          22 :             CPLJSONObject oCRC32CCodec;
    1237          11 :             oCRC32CCodec.Add("name", "crc32c");
    1238          11 :             oIndexCodecs.Add(oCRC32CCodec);
    1239             :         }
    1240             : 
    1241          22 :         CPLJSONObject oShardingConfig;
    1242          11 :         oShardingConfig.Add("chunk_shape", oChunkShapeArray);
    1243          11 :         oShardingConfig.Add("codecs", oCodecs);
    1244          11 :         oShardingConfig.Add("index_codecs", oIndexCodecs);
    1245          11 :         oShardingConfig.Add("index_location", "end");
    1246             : 
    1247          22 :         CPLJSONObject oShardingCodec;
    1248          11 :         oShardingCodec.Add("name", "sharding_indexed");
    1249          11 :         oShardingCodec.Add("configuration", oShardingConfig);
    1250             : 
    1251             :         // Replace top-level codecs with just the sharding codec
    1252          11 :         oCodecs = CPLJSONArray();
    1253          11 :         oCodecs.Add(oShardingCodec);
    1254             :     }
    1255             : 
    1256         372 :     std::vector<GUInt64> anInnerBlockSize = anOuterBlockSize;
    1257         186 :     if (oCodecs.Size() > 0)
    1258             :     {
    1259         186 :         std::vector<GByte> abyNoData;
    1260         372 :         poCodecs = ZarrV3Array::SetupCodecs(oCodecs, anOuterBlockSize,
    1261             :                                             anInnerBlockSize,
    1262         372 :                                             aoDtypeElts.back(), abyNoData);
    1263         186 :         if (!poCodecs)
    1264             :         {
    1265           0 :             return nullptr;
    1266             :         }
    1267             :     }
    1268             : 
    1269         186 :     auto poArray = ZarrV3Array::Create(m_poSharedResource, Self(), osName,
    1270             :                                        aoDimensions, oDataType, aoDtypeElts,
    1271         372 :                                        anOuterBlockSize, anInnerBlockSize);
    1272             : 
    1273         186 :     if (!poArray)
    1274           0 :         return nullptr;
    1275         186 :     poArray->SetNew(true);
    1276             :     const std::string osFilename =
    1277         372 :         CPLFormFilenameSafe(osArrayDirectory.c_str(), "zarr.json", nullptr);
    1278         186 :     poArray->SetFilename(osFilename);
    1279         186 :     poArray->SetDimSeparator(pszDimSeparator);
    1280         186 :     poArray->SetDtype(dtype);
    1281         372 :     if (oCodecs.Size() > 0 &&
    1282         372 :         oCodecs[oCodecs.Size() - 1].GetString("name") != "bytes")
    1283             :     {
    1284          92 :         poArray->SetStructuralInfo(
    1285          92 :             "COMPRESSOR", oCodecs[oCodecs.Size() - 1].ToString().c_str());
    1286             :     }
    1287         186 :     if (poCodecs)
    1288         186 :         poArray->SetCodecs(oCodecs, std::move(poCodecs));
    1289             : 
    1290         186 :     poArray->SetCreationOptions(papszOptions);
    1291         186 :     poArray->SetUpdatable(true);
    1292         186 :     poArray->SetDefinitionModified(true);
    1293         186 :     if (!cpl::starts_with(osFilename, "/vsi") && !poArray->Flush())
    1294           0 :         return nullptr;
    1295         186 :     RegisterArray(poArray);
    1296             : 
    1297         186 :     return poArray;
    1298             : }

Generated by: LCOV version 1.14