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

Generated by: LCOV version 1.14