LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v3_group.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 439 483 90.9 %
Date: 2026-02-11 08:43:47 Functions: 16 16 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        1286 : 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        1286 :         poSharedResource, osParentName, osName, osRootDirectoryName));
      33        1286 :     poGroup->SetSelf(poGroup);
      34        1286 :     return poGroup;
      35             : }
      36             : 
      37             : /************************************************************************/
      38             : /*                           OpenZarrArray()                            */
      39             : /************************************************************************/
      40             : 
      41        1061 : std::shared_ptr<ZarrArray> ZarrV3Group::OpenZarrArray(const std::string &osName,
      42             :                                                       CSLConstList) const
      43             : {
      44        1061 :     if (!CheckValidAndErrorOutIfNot())
      45           0 :         return nullptr;
      46             : 
      47        1061 :     auto oIter = m_oMapMDArrays.find(osName);
      48        1061 :     if (oIter != m_oMapMDArrays.end())
      49         979 :         return oIter->second;
      50             : 
      51          82 :     if (m_bReadFromConsolidatedMetadata)
      52          14 :         return nullptr;
      53             : 
      54             :     const std::string osSubDir =
      55         136 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
      56             :     const std::string osZarrayFilename =
      57         136 :         CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
      58             : 
      59             :     VSIStatBufL sStat;
      60          68 :     if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
      61             :     {
      62         112 :         CPLJSONDocument oDoc;
      63          56 :         if (!oDoc.Load(osZarrayFilename))
      64           0 :             return nullptr;
      65         112 :         const auto oRoot = oDoc.GetRoot();
      66          56 :         return LoadArray(osName, osZarrayFilename, oRoot);
      67             :     }
      68             : 
      69          12 :     return nullptr;
      70             : }
      71             : 
      72             : /************************************************************************/
      73             : /*                    ZarrV3Group::LoadAttributes()                     */
      74             : /************************************************************************/
      75             : 
      76         367 : void ZarrV3Group::LoadAttributes() const
      77             : {
      78         367 :     if (m_bAttributesLoaded)
      79         334 :         return;
      80          33 :     m_bAttributesLoaded = true;
      81             : 
      82             :     const std::string osFilename =
      83          33 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), "zarr.json", nullptr);
      84             : 
      85             :     VSIStatBufL sStat;
      86          33 :     if (VSIStatL(osFilename.c_str(), &sStat) == 0)
      87             :     {
      88          33 :         CPLJSONDocument oDoc;
      89          33 :         if (!oDoc.Load(osFilename))
      90           0 :             return;
      91          33 :         auto oRoot = oDoc.GetRoot();
      92          33 :         m_oAttrGroup.Init(oRoot["attributes"], m_bUpdatable);
      93             :     }
      94             : }
      95             : 
      96             : /************************************************************************/
      97             : /*                          ExploreDirectory()                          */
      98             : /************************************************************************/
      99             : 
     100          63 : void ZarrV3Group::ExploreDirectory() const
     101             : {
     102          63 :     if (m_bDirectoryExplored)
     103           0 :         return;
     104          63 :     m_bDirectoryExplored = true;
     105             : 
     106          63 :     auto psDir = VSIOpenDir(m_osDirectoryName.c_str(), 0, nullptr);
     107          63 :     if (!psDir)
     108           0 :         return;
     109         223 :     while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir))
     110             :     {
     111         160 :         if (VSI_ISDIR(psEntry->nMode))
     112             :         {
     113         194 :             std::string osName(psEntry->pszName);
     114         194 :             while (!osName.empty() &&
     115          97 :                    (osName.back() == '/' || osName.back() == '\\'))
     116           0 :                 osName.pop_back();
     117          97 :             if (osName.empty())
     118           0 :                 continue;
     119             :             const std::string osSubDir = CPLFormFilenameSafe(
     120          97 :                 m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     121             :             VSIStatBufL sStat;
     122             :             const std::string osZarrJsonFilename =
     123          97 :                 CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
     124          97 :             if (VSIStatL(osZarrJsonFilename.c_str(), &sStat) == 0)
     125             :             {
     126          95 :                 CPLJSONDocument oDoc;
     127          95 :                 if (oDoc.Load(osZarrJsonFilename.c_str()))
     128             :                 {
     129          95 :                     const auto oRoot = oDoc.GetRoot();
     130          95 :                     if (oRoot.GetInteger("zarr_format") != 3)
     131             :                     {
     132           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     133             :                                  "Unhandled zarr_format value");
     134           0 :                         continue;
     135             :                     }
     136         190 :                     const std::string osNodeType = oRoot.GetString("node_type");
     137          95 :                     if (osNodeType == "array")
     138             :                     {
     139          52 :                         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         160 :     }
     172          63 :     VSICloseDir(psDir);
     173             : }
     174             : 
     175             : /************************************************************************/
     176             : /*                      ZarrV3Group::ZarrV3Group()                      */
     177             : /************************************************************************/
     178             : 
     179        1286 : ZarrV3Group::ZarrV3Group(
     180             :     const std::shared_ptr<ZarrSharedResource> &poSharedResource,
     181             :     const std::string &osParentName, const std::string &osName,
     182        1286 :     const std::string &osDirectoryName)
     183        1286 :     : ZarrGroupBase(poSharedResource, osParentName, osName)
     184             : {
     185        1286 :     m_osDirectoryName = osDirectoryName;
     186        1286 : }
     187             : 
     188             : /************************************************************************/
     189             : /*                     ZarrV3Group::~ZarrV3Group()                      */
     190             : /************************************************************************/
     191             : 
     192        2572 : ZarrV3Group::~ZarrV3Group()
     193             : {
     194        1286 :     ZarrV3Group::Close();
     195        2572 : }
     196             : 
     197             : /************************************************************************/
     198             : /*                               Close()                                */
     199             : /************************************************************************/
     200             : 
     201        2971 : bool ZarrV3Group::Close()
     202             : {
     203        2971 :     bool bRet = ZarrGroupBase::Close();
     204             : 
     205        5883 :     if (m_bValid && (m_oAttrGroup.IsModified() ||
     206        2996 :                      (m_bUpdatable && !m_bFileHasBeenWritten &&
     207          84 :                       m_poSharedResource->IsConsolidatedMetadataEnabled())))
     208             :     {
     209         176 :         CPLJSONDocument oDoc;
     210         176 :         auto oRoot = oDoc.GetRoot();
     211          88 :         oRoot.Add("zarr_format", 3);
     212          88 :         oRoot.Add("node_type", "group");
     213          88 :         oRoot.Add("attributes", m_oAttrGroup.Serialize());
     214             :         const std::string osZarrJsonFilename = CPLFormFilenameSafe(
     215          88 :             m_osDirectoryName.c_str(), "zarr.json", nullptr);
     216          88 :         if (!m_bFileHasBeenWritten)
     217             :         {
     218          35 :             oRoot.Add("consolidated_metadata",
     219          35 :                       m_poSharedResource->GetConsolidatedMetadataObj());
     220          35 :             bRet = oDoc.Save(osZarrJsonFilename) && bRet;
     221             :         }
     222             :         else
     223             :         {
     224          53 :             bRet = oDoc.Save(osZarrJsonFilename) && bRet;
     225          53 :             if (bRet)
     226          52 :                 m_poSharedResource->SetZMetadataItem(osZarrJsonFilename, oRoot);
     227             :         }
     228          88 :         m_bFileHasBeenWritten = bRet;
     229             :     }
     230             : 
     231        2971 :     return bRet;
     232             : }
     233             : 
     234             : /************************************************************************/
     235             : /*                  ZarrV3Group::GetOrCreateSubGroup()                  */
     236             : /************************************************************************/
     237             : 
     238             : std::shared_ptr<ZarrV3Group>
     239         351 : ZarrV3Group::GetOrCreateSubGroup(const std::string &osSubGroupFullname)
     240             : {
     241             :     auto poSubGroup = std::dynamic_pointer_cast<ZarrV3Group>(
     242         351 :         OpenGroupFromFullname(osSubGroupFullname));
     243         351 :     if (poSubGroup)
     244             :     {
     245         225 :         return poSubGroup;
     246             :     }
     247             : 
     248         126 :     const auto nLastSlashPos = osSubGroupFullname.rfind('/');
     249             :     auto poBelongingGroup =
     250             :         (nLastSlashPos == 0)
     251         126 :             ? this
     252         140 :             : GetOrCreateSubGroup(osSubGroupFullname.substr(0, nLastSlashPos))
     253         126 :                   .get();
     254             : 
     255         252 :     poSubGroup = ZarrV3Group::Create(
     256         126 :         m_poSharedResource, poBelongingGroup->GetFullName(),
     257         378 :         osSubGroupFullname.substr(nLastSlashPos + 1), m_osDirectoryName);
     258         252 :     poSubGroup->m_poParent = std::dynamic_pointer_cast<ZarrGroupBase>(
     259         378 :         poBelongingGroup->m_pSelf.lock());
     260         252 :     poSubGroup->SetDirectoryName(
     261         252 :         CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
     262         126 :                             poSubGroup->GetName().c_str(), nullptr));
     263         126 :     poSubGroup->m_bDirectoryExplored = true;
     264         126 :     poSubGroup->m_bAttributesLoaded = true;
     265         126 :     poSubGroup->m_bReadFromConsolidatedMetadata = true;
     266         126 :     poSubGroup->m_bFileHasBeenWritten = true;
     267         126 :     poSubGroup->SetUpdatable(m_bUpdatable);
     268             : 
     269         126 :     poBelongingGroup->m_oMapGroups[poSubGroup->GetName()] = poSubGroup;
     270         126 :     poBelongingGroup->m_oSetGroupNames.insert(poSubGroup->GetName());
     271         126 :     poBelongingGroup->m_aosGroups.emplace_back(poSubGroup->GetName());
     272         126 :     return poSubGroup;
     273             : }
     274             : 
     275             : /************************************************************************/
     276             : /*             ZarrV3Group::InitFromConsolidatedMetadata()              */
     277             : /************************************************************************/
     278             : 
     279         161 : void ZarrV3Group::InitFromConsolidatedMetadata(
     280             :     const CPLJSONObject &oConsolidatedMetadata,
     281             :     const CPLJSONObject &oRootAttributes)
     282             : {
     283         322 :     const auto metadata = oConsolidatedMetadata["metadata"];
     284         161 :     if (metadata.GetType() != CPLJSONObject::Type::Object)
     285             :     {
     286           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     287             :                  "consolidated_metadata lacks 'metadata' object");
     288           0 :         return;
     289             :     }
     290         161 :     m_bDirectoryExplored = true;
     291         161 :     m_bAttributesLoaded = true;
     292         161 :     m_bReadFromConsolidatedMetadata = true;
     293             : 
     294         161 :     if (oRootAttributes.IsValid())
     295             :     {
     296         161 :         m_oAttrGroup.Init(oRootAttributes, m_bUpdatable);
     297             :     }
     298             : 
     299         322 :     const auto children = metadata.GetChildren();
     300         322 :     std::map<std::string, const CPLJSONObject *> oMapArrays;
     301             : 
     302             :     // First pass to create groups and collect arrays
     303         617 :     for (const auto &child : children)
     304             :     {
     305         456 :         const std::string osName(child.GetName());
     306         456 :         if (std::count(osName.begin(), osName.end(), '/') > 32)
     307             :         {
     308             :             // Avoid too deep recursion in GetOrCreateSubGroup()
     309           0 :             continue;
     310             :         }
     311             : 
     312        1368 :         const std::string osNodeType = child.GetString("node_type");
     313         456 :         if (osNodeType == "group")
     314             :         {
     315         252 :             auto poGroup = GetOrCreateSubGroup("/" + osName);
     316         378 :             auto oAttributes = child["attributes"];
     317         126 :             if (oAttributes.IsValid())
     318             :             {
     319         126 :                 poGroup->m_oAttrGroup.Init(oAttributes, m_bUpdatable);
     320             :             }
     321             :         }
     322         330 :         else if (osNodeType == "array")
     323             :         {
     324         330 :             oMapArrays[osName] = &child;
     325             :         }
     326             :     }
     327             : 
     328             :     const auto CreateArray =
     329         541 :         [this](const std::string &osArrayFullname, const CPLJSONObject &oArray)
     330             :     {
     331         330 :         const auto nLastSlashPos = osArrayFullname.rfind('/');
     332             :         auto poBelongingGroup =
     333             :             (nLastSlashPos == std::string::npos)
     334         330 :                 ? this
     335         541 :                 : GetOrCreateSubGroup("/" +
     336         541 :                                       osArrayFullname.substr(0, nLastSlashPos))
     337         330 :                       .get();
     338             :         const auto osArrayName =
     339             :             nLastSlashPos == std::string::npos
     340             :                 ? osArrayFullname
     341         660 :                 : osArrayFullname.substr(nLastSlashPos + 1);
     342             :         const std::string osZarrayFilename = CPLFormFilenameSafe(
     343         330 :             CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
     344             :                                 osArrayName.c_str(), nullptr)
     345             :                 .c_str(),
     346         330 :             "zarr.json", nullptr);
     347         330 :         poBelongingGroup->LoadArray(osArrayName, osZarrayFilename, oArray);
     348         330 :     };
     349             : 
     350             :     struct ArrayDesc
     351             :     {
     352             :         std::string osArrayFullname{};
     353             :         const CPLJSONObject *poArray = nullptr;
     354             :     };
     355             : 
     356         322 :     std::vector<ArrayDesc> aoRegularArrays;
     357             : 
     358             :     // Second pass to read attributes and create arrays that are indexing
     359             :     // variable
     360         617 :     for (const auto &child : children)
     361             :     {
     362         912 :         const std::string osName(child.GetName());
     363        1368 :         const std::string osNodeType = child.GetString("node_type");
     364         456 :         if (osNodeType == "array")
     365             :         {
     366         330 :             auto oIter = oMapArrays.find(osName);
     367         330 :             if (oIter != oMapArrays.end())
     368             :             {
     369         330 :                 const auto nLastSlashPos = osName.rfind('/');
     370             :                 const std::string osArrayName =
     371             :                     (nLastSlashPos == std::string::npos)
     372             :                         ? osName
     373         660 :                         : osName.substr(nLastSlashPos + 1);
     374         990 :                 const auto arrayDimensions = child["dimension_names"].ToArray();
     375         412 :                 if (arrayDimensions.IsValid() && arrayDimensions.Size() == 1 &&
     376         412 :                     arrayDimensions[0].ToString() == osArrayName)
     377             :                 {
     378          74 :                     CreateArray(osName, child);
     379          74 :                     oMapArrays.erase(oIter);
     380             :                 }
     381             :                 else
     382             :                 {
     383         512 :                     ArrayDesc desc;
     384         256 :                     desc.osArrayFullname = std::move(osName);
     385         256 :                     desc.poArray = oIter->second;
     386         256 :                     aoRegularArrays.emplace_back(std::move(desc));
     387             :                 }
     388             :             }
     389             :         }
     390             :     }
     391             : 
     392             :     // Third pass to create non-indexing arrays with attributes
     393         417 :     for (const auto &desc : aoRegularArrays)
     394             :     {
     395         256 :         CreateArray(desc.osArrayFullname, *(desc.poArray));
     396         256 :         oMapArrays.erase(desc.osArrayFullname);
     397             :     }
     398             : 
     399             :     // Fourth pass to create arrays without attributes
     400         161 :     for (const auto &kv : oMapArrays)
     401             :     {
     402           0 :         CreateArray(kv.first, *(kv.second));
     403             :     }
     404             : }
     405             : 
     406             : /************************************************************************/
     407             : /*                           OpenZarrGroup()                            */
     408             : /************************************************************************/
     409             : 
     410             : std::shared_ptr<ZarrGroupBase>
     411         720 : ZarrV3Group::OpenZarrGroup(const std::string &osName, CSLConstList) const
     412             : {
     413         720 :     if (!CheckValidAndErrorOutIfNot())
     414           0 :         return nullptr;
     415             : 
     416         720 :     auto oIter = m_oMapGroups.find(osName);
     417         720 :     if (oIter != m_oMapGroups.end())
     418         509 :         return oIter->second;
     419             : 
     420         211 :     if (m_bReadFromConsolidatedMetadata)
     421         153 :         return nullptr;
     422             : 
     423             :     const std::string osSubDir =
     424         116 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     425             :     const std::string osSubDirZarrJsonFilename =
     426         116 :         CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
     427             : 
     428             :     VSIStatBufL sStat;
     429             :     // Explicit group
     430          58 :     if (VSIStatL(osSubDirZarrJsonFilename.c_str(), &sStat) == 0)
     431             :     {
     432         100 :         CPLJSONDocument oDoc;
     433          50 :         if (oDoc.Load(osSubDirZarrJsonFilename.c_str()))
     434             :         {
     435         100 :             const auto oRoot = oDoc.GetRoot();
     436          50 :             if (oRoot.GetInteger("zarr_format") != 3)
     437             :             {
     438           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     439             :                          "Unhandled zarr_format value");
     440           0 :                 return nullptr;
     441             :             }
     442         150 :             const std::string osNodeType = oRoot.GetString("node_type");
     443          50 :             if (osNodeType != "group")
     444             :             {
     445           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "%s is a %s, not a group",
     446             :                          osName.c_str(), osNodeType.c_str());
     447           0 :                 return nullptr;
     448             :             }
     449             :             auto poSubGroup = ZarrV3Group::Create(
     450         100 :                 m_poSharedResource, GetFullName(), osName, osSubDir);
     451          50 :             poSubGroup->m_bFileHasBeenWritten = true;
     452          50 :             poSubGroup->m_poParent =
     453         100 :                 std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
     454          50 :             poSubGroup->SetUpdatable(m_bUpdatable);
     455          50 :             m_oMapGroups[osName] = poSubGroup;
     456          50 :             return poSubGroup;
     457             :         }
     458           0 :         return nullptr;
     459             :     }
     460             : 
     461             :     // Implicit group
     462           8 :     if (VSIStatL(osSubDir.c_str(), &sStat) == 0 && VSI_ISDIR(sStat.st_mode))
     463             :     {
     464             :         // Note: Python zarr v3.0.2 still generates implicit groups
     465             :         // See https://github.com/zarr-developers/zarr-python/issues/2794
     466           2 :         CPLError(CE_Warning, CPLE_AppDefined,
     467             :                  "Support for Zarr V3 implicit group is now deprecated, and "
     468             :                  "may be removed in a future version");
     469           2 :         auto poSubGroup = ZarrV3Group::Create(m_poSharedResource, GetFullName(),
     470           4 :                                               osName, osSubDir);
     471           2 :         poSubGroup->m_bFileHasBeenWritten = true;
     472           2 :         poSubGroup->m_poParent =
     473           4 :             std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
     474           2 :         poSubGroup->SetUpdatable(m_bUpdatable);
     475           2 :         m_oMapGroups[osName] = poSubGroup;
     476           2 :         return poSubGroup;
     477             :     }
     478             : 
     479           6 :     return nullptr;
     480             : }
     481             : 
     482             : /************************************************************************/
     483             : /*                     ZarrV3Group::CreateOnDisk()                      */
     484             : /************************************************************************/
     485             : 
     486         176 : std::shared_ptr<ZarrV3Group> ZarrV3Group::CreateOnDisk(
     487             :     const std::shared_ptr<ZarrSharedResource> &poSharedResource,
     488             :     const std::string &osParentFullName, const std::string &osName,
     489             :     const std::string &osDirectoryName)
     490             : {
     491         176 :     if (VSIMkdir(osDirectoryName.c_str(), 0755) != 0)
     492             :     {
     493             :         VSIStatBufL sStat;
     494           3 :         if (VSIStatL(osDirectoryName.c_str(), &sStat) == 0)
     495             :         {
     496           3 :             CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
     497             :                      osDirectoryName.c_str());
     498             :         }
     499             :         else
     500             :         {
     501           0 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
     502             :                      osDirectoryName.c_str());
     503             :         }
     504           3 :         return nullptr;
     505             :     }
     506             : 
     507             :     const std::string osZarrJsonFilename(
     508         346 :         CPLFormFilenameSafe(osDirectoryName.c_str(), "zarr.json", nullptr));
     509         173 :     VSILFILE *fp = nullptr;
     510         308 :     if (!(poSharedResource->IsConsolidatedMetadataEnabled() &&
     511         135 :           cpl::starts_with(osZarrJsonFilename, "/vsizip/") &&
     512           1 :           osParentFullName.empty() && osName == "/"))
     513             :     {
     514         172 :         fp = VSIFOpenL(osZarrJsonFilename.c_str(), "wb");
     515         172 :         if (!fp)
     516             :         {
     517           0 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create file %s.",
     518             :                      osZarrJsonFilename.c_str());
     519           0 :             return nullptr;
     520             :         }
     521         172 :         VSIFPrintfL(fp, "{\n"
     522             :                         "    \"zarr_format\": 3,\n"
     523             :                         "    \"node_type\": \"group\",\n"
     524             :                         "    \"attributes\": {}\n"
     525             :                         "}\n");
     526         172 :         VSIFCloseL(fp);
     527             :     }
     528             : 
     529             :     auto poGroup = ZarrV3Group::Create(poSharedResource, osParentFullName,
     530         346 :                                        osName, osDirectoryName);
     531         173 :     poGroup->SetUpdatable(true);
     532         173 :     poGroup->m_bDirectoryExplored = true;
     533         173 :     poGroup->m_bFileHasBeenWritten = fp != nullptr;
     534             : 
     535         346 :     CPLJSONObject oObj;
     536         173 :     oObj.Add("zarr_format", 3);
     537         173 :     oObj.Add("node_type", "group");
     538         173 :     oObj.Add("attributes", CPLJSONObject());
     539         173 :     poSharedResource->SetZMetadataItem(osZarrJsonFilename, oObj);
     540             : 
     541         173 :     return poGroup;
     542             : }
     543             : 
     544             : /************************************************************************/
     545             : /*                      ZarrV3Group::CreateGroup()                      */
     546             : /************************************************************************/
     547             : 
     548             : std::shared_ptr<GDALGroup>
     549          57 : ZarrV3Group::CreateGroup(const std::string &osName,
     550             :                          CSLConstList /* papszOptions */)
     551             : {
     552          57 :     if (!CheckValidAndErrorOutIfNot())
     553           0 :         return nullptr;
     554             : 
     555          57 :     if (!m_bUpdatable)
     556             :     {
     557           3 :         CPLError(CE_Failure, CPLE_NotSupported,
     558             :                  "Dataset not open in update mode");
     559           3 :         return nullptr;
     560             :     }
     561          54 :     if (!IsValidObjectName(osName))
     562             :     {
     563          14 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid group name");
     564          14 :         return nullptr;
     565             :     }
     566             : 
     567          40 :     GetGroupNames();
     568             : 
     569          40 :     if (cpl::contains(m_oSetGroupNames, osName))
     570             :     {
     571           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     572             :                  "A group with same name already exists");
     573           1 :         return nullptr;
     574             :     }
     575             : 
     576             :     const std::string osDirectoryName =
     577          78 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     578          39 :     auto poGroup = CreateOnDisk(m_poSharedResource, GetFullName(), osName,
     579          78 :                                 osDirectoryName);
     580          39 :     if (!poGroup)
     581           3 :         return nullptr;
     582          36 :     poGroup->m_poParent =
     583          72 :         std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
     584          36 :     m_oMapGroups[osName] = poGroup;
     585          36 :     m_aosGroups.emplace_back(osName);
     586          36 :     return poGroup;
     587             : }
     588             : 
     589             : /************************************************************************/
     590             : /*                           FillDTypeElts()                            */
     591             : /************************************************************************/
     592             : 
     593         137 : static CPLJSONObject FillDTypeElts(const GDALExtendedDataType &oDataType,
     594             :                                    std::vector<DtypeElt> &aoDtypeElts)
     595             : {
     596         137 :     CPLJSONObject dtype;
     597         274 :     const std::string dummy("dummy");
     598             : 
     599         137 :     const auto eDT = oDataType.GetNumericDataType();
     600         274 :     DtypeElt elt;
     601         137 :     bool bUnsupported = false;
     602         137 :     switch (eDT)
     603             :     {
     604          62 :         case GDT_UInt8:
     605             :         {
     606          62 :             elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     607          62 :             dtype.Set(dummy, "uint8");
     608          62 :             break;
     609             :         }
     610           6 :         case GDT_Int8:
     611             :         {
     612           6 :             elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     613           6 :             dtype.Set(dummy, "int8");
     614           6 :             break;
     615             :         }
     616           7 :         case GDT_UInt16:
     617             :         {
     618           7 :             elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     619           7 :             dtype.Set(dummy, "uint16");
     620           7 :             break;
     621             :         }
     622           9 :         case GDT_Int16:
     623             :         {
     624           9 :             elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     625           9 :             dtype.Set(dummy, "int16");
     626           9 :             break;
     627             :         }
     628           7 :         case GDT_UInt32:
     629             :         {
     630           7 :             elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     631           7 :             dtype.Set(dummy, "uint32");
     632           7 :             break;
     633             :         }
     634           7 :         case GDT_Int32:
     635             :         {
     636           7 :             elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     637           7 :             dtype.Set(dummy, "int32");
     638           7 :             break;
     639             :         }
     640           6 :         case GDT_UInt64:
     641             :         {
     642           6 :             elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     643           6 :             dtype.Set(dummy, "uint64");
     644           6 :             break;
     645             :         }
     646           6 :         case GDT_Int64:
     647             :         {
     648           6 :             elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     649           6 :             dtype.Set(dummy, "int64");
     650           6 :             break;
     651             :         }
     652           1 :         case GDT_Float16:
     653             :         {
     654           1 :             elt.nativeType = DtypeElt::NativeType::IEEEFP;
     655           1 :             dtype.Set(dummy, "float16");
     656           1 :             break;
     657             :         }
     658           7 :         case GDT_Float32:
     659             :         {
     660           7 :             elt.nativeType = DtypeElt::NativeType::IEEEFP;
     661           7 :             dtype.Set(dummy, "float32");
     662           7 :             break;
     663             :         }
     664          15 :         case GDT_Float64:
     665             :         {
     666          15 :             elt.nativeType = DtypeElt::NativeType::IEEEFP;
     667          15 :             dtype.Set(dummy, "float64");
     668          15 :             break;
     669             :         }
     670           2 :         case GDT_Unknown:
     671             :         case GDT_CInt16:
     672             :         case GDT_CInt32:
     673             :         {
     674           2 :             bUnsupported = true;
     675           2 :             break;
     676             :         }
     677           0 :         case GDT_CFloat16:
     678             :         {
     679           0 :             elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     680           0 :             dtype.Set(dummy, "complex32");
     681           0 :             break;
     682             :         }
     683           1 :         case GDT_CFloat32:
     684             :         {
     685           1 :             elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     686           1 :             dtype.Set(dummy, "complex64");
     687           1 :             break;
     688             :         }
     689           1 :         case GDT_CFloat64:
     690             :         {
     691           1 :             elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     692           1 :             dtype.Set(dummy, "complex128");
     693           1 :             break;
     694             :         }
     695           0 :         case GDT_TypeCount:
     696             :         {
     697             :             static_assert(GDT_TypeCount == GDT_CFloat16 + 1,
     698             :                           "GDT_TypeCount == GDT_CFloat16 + 1");
     699           0 :             break;
     700             :         }
     701             :     }
     702         137 :     if (bUnsupported)
     703             :     {
     704           2 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %s",
     705             :                  GDALGetDataTypeName(eDT));
     706           2 :         dtype = CPLJSONObject();
     707           2 :         dtype.Deinit();
     708           2 :         return dtype;
     709             :     }
     710         135 :     elt.nativeOffset = 0;
     711         135 :     elt.nativeSize = GDALGetDataTypeSizeBytes(eDT);
     712         135 :     elt.gdalOffset = 0;
     713         135 :     elt.gdalSize = elt.nativeSize;
     714             : #ifdef CPL_MSB
     715             :     elt.needByteSwapping = elt.nativeSize > 1;
     716             : #endif
     717         135 :     aoDtypeElts.emplace_back(elt);
     718             : 
     719         135 :     return dtype;
     720             : }
     721             : 
     722             : /************************************************************************/
     723             : /*                     ZarrV3Group::CreateMDArray()                     */
     724             : /************************************************************************/
     725             : 
     726         155 : std::shared_ptr<GDALMDArray> ZarrV3Group::CreateMDArray(
     727             :     const std::string &osName,
     728             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
     729             :     const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
     730             : {
     731         155 :     if (!CheckValidAndErrorOutIfNot())
     732           0 :         return nullptr;
     733             : 
     734         155 :     if (!m_bUpdatable)
     735             :     {
     736           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     737             :                  "Dataset not open in update mode");
     738           0 :         return nullptr;
     739             :     }
     740         155 :     if (!IsValidObjectName(osName))
     741             :     {
     742          14 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid array name");
     743          14 :         return nullptr;
     744             :     }
     745             : 
     746         141 :     if (oDataType.GetClass() != GEDTC_NUMERIC)
     747             :     {
     748           4 :         CPLError(CE_Failure, CPLE_AppDefined,
     749             :                  "Unsupported data type with Zarr V3");
     750           4 :         return nullptr;
     751             :     }
     752             : 
     753         137 :     if (!EQUAL(CSLFetchNameValueDef(papszOptions, "FILTER", "NONE"), "NONE"))
     754             :     {
     755           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     756             :                  "FILTER option not supported with Zarr V3");
     757           0 :         return nullptr;
     758             :     }
     759             : 
     760         274 :     std::vector<DtypeElt> aoDtypeElts;
     761         411 :     const auto dtype = FillDTypeElts(oDataType, aoDtypeElts)["dummy"];
     762         137 :     if (!dtype.IsValid() || aoDtypeElts.empty())
     763           2 :         return nullptr;
     764             : 
     765         135 :     GetMDArrayNames();
     766             : 
     767         135 :     if (cpl::contains(m_oSetArrayNames, osName))
     768             :     {
     769           2 :         CPLError(CE_Failure, CPLE_AppDefined,
     770             :                  "An array with same name already exists");
     771           2 :         return nullptr;
     772             :     }
     773             : 
     774         266 :     std::vector<GUInt64> anOuterBlockSize;
     775         133 :     if (!ZarrArray::FillBlockSize(aoDimensions, oDataType, anOuterBlockSize,
     776             :                                   papszOptions))
     777           1 :         return nullptr;
     778             : 
     779             :     const char *pszDimSeparator =
     780         132 :         CSLFetchNameValueDef(papszOptions, "DIM_SEPARATOR", "/");
     781             : 
     782             :     const std::string osArrayDirectory =
     783         264 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     784         132 :     if (VSIMkdir(osArrayDirectory.c_str(), 0755) != 0)
     785             :     {
     786             :         VSIStatBufL sStat;
     787           2 :         if (VSIStatL(osArrayDirectory.c_str(), &sStat) == 0)
     788             :         {
     789           2 :             CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
     790             :                      osArrayDirectory.c_str());
     791             :         }
     792             :         else
     793             :         {
     794           0 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
     795             :                      osArrayDirectory.c_str());
     796             :         }
     797           2 :         return nullptr;
     798             :     }
     799             : 
     800         130 :     std::unique_ptr<ZarrV3CodecSequence> poCodecs;
     801         260 :     CPLJSONArray oCodecs;
     802             : 
     803         130 :     const bool bFortranOrder = EQUAL(
     804             :         CSLFetchNameValueDef(papszOptions, "CHUNK_MEMORY_LAYOUT", "C"), "F");
     805         130 :     if (bFortranOrder && aoDimensions.size() > 1)
     806             :     {
     807          80 :         CPLJSONObject oCodec;
     808          40 :         oCodec.Add("name", "transpose");
     809          80 :         std::vector<int> anOrder;
     810          40 :         const int nDims = static_cast<int>(aoDimensions.size());
     811         130 :         for (int i = 0; i < nDims; ++i)
     812             :         {
     813          90 :             anOrder.push_back(nDims - 1 - i);
     814             :         }
     815          40 :         oCodec.Add("configuration",
     816          80 :                    ZarrV3CodecTranspose::GetConfiguration(anOrder));
     817          40 :         oCodecs.Add(oCodec);
     818             :     }
     819             : 
     820             :     // Not documented option, but 'bytes' codec is required
     821             :     const char *pszEndian =
     822         130 :         CSLFetchNameValueDef(papszOptions, "@ENDIAN", "little");
     823             :     {
     824         260 :         CPLJSONObject oCodec;
     825         130 :         oCodec.Add("name", "bytes");
     826         130 :         oCodec.Add("configuration", ZarrV3CodecBytes::GetConfiguration(
     827         130 :                                         EQUAL(pszEndian, "little")));
     828         130 :         oCodecs.Add(oCodec);
     829             :     }
     830             : 
     831             :     const char *pszCompressor =
     832         130 :         CSLFetchNameValueDef(papszOptions, "COMPRESS", "NONE");
     833         130 :     if (EQUAL(pszCompressor, "GZIP"))
     834             :     {
     835          46 :         CPLJSONObject oCodec;
     836          23 :         oCodec.Add("name", "gzip");
     837             :         const char *pszLevel =
     838          23 :             CSLFetchNameValueDef(papszOptions, "GZIP_LEVEL", "6");
     839          23 :         oCodec.Add("configuration",
     840          46 :                    ZarrV3CodecGZip::GetConfiguration(atoi(pszLevel)));
     841          23 :         oCodecs.Add(oCodec);
     842             :     }
     843         107 :     else if (EQUAL(pszCompressor, "BLOSC"))
     844             :     {
     845           2 :         const auto psCompressor = CPLGetCompressor("blosc");
     846           2 :         if (!psCompressor)
     847           0 :             return nullptr;
     848             :         const char *pszOptions =
     849           2 :             CSLFetchNameValueDef(psCompressor->papszMetadata, "OPTIONS", "");
     850           2 :         CPLXMLTreeCloser oTreeCompressor(CPLParseXMLString(pszOptions));
     851             :         const auto psRoot =
     852           2 :             oTreeCompressor.get()
     853           2 :                 ? CPLGetXMLNode(oTreeCompressor.get(), "=Options")
     854           2 :                 : nullptr;
     855           2 :         if (!psRoot)
     856           0 :             return nullptr;
     857             : 
     858           2 :         const char *cname = "zlib";
     859          14 :         for (const CPLXMLNode *psNode = psRoot->psChild; psNode != nullptr;
     860          12 :              psNode = psNode->psNext)
     861             :         {
     862          12 :             if (psNode->eType == CXT_Element)
     863             :             {
     864          12 :                 const char *pszName = CPLGetXMLValue(psNode, "name", "");
     865          12 :                 if (EQUAL(pszName, "CNAME"))
     866             :                 {
     867           2 :                     cname = CPLGetXMLValue(psNode, "default", cname);
     868             :                 }
     869             :             }
     870             :         }
     871             : 
     872           4 :         CPLJSONObject oCodec;
     873           2 :         oCodec.Add("name", "blosc");
     874           2 :         cname = CSLFetchNameValueDef(papszOptions, "BLOSC_CNAME", cname);
     875             :         const int clevel =
     876           2 :             atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_CLEVEL", "5"));
     877             :         const char *shuffle =
     878           2 :             CSLFetchNameValueDef(papszOptions, "BLOSC_SHUFFLE", "BYTE");
     879           3 :         shuffle = (EQUAL(shuffle, "0") || EQUAL(shuffle, "NONE")) ? "noshuffle"
     880           1 :                   : (EQUAL(shuffle, "1") || EQUAL(shuffle, "BYTE")) ? "shuffle"
     881           0 :                   : (EQUAL(shuffle, "2") || EQUAL(shuffle, "BIT"))
     882           0 :                       ? "bitshuffle"
     883             :                       : "invalid";
     884           2 :         const int typesize = atoi(CSLFetchNameValueDef(
     885             :             papszOptions, "BLOSC_TYPESIZE",
     886             :             CPLSPrintf("%d", GDALGetDataTypeSizeBytes(GDALGetNonComplexDataType(
     887             :                                  oDataType.GetNumericDataType())))));
     888             :         const int blocksize =
     889           2 :             atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_BLOCKSIZE", "0"));
     890           2 :         oCodec.Add("configuration",
     891           4 :                    ZarrV3CodecBlosc::GetConfiguration(cname, clevel, shuffle,
     892             :                                                       typesize, blocksize));
     893           2 :         oCodecs.Add(oCodec);
     894             :     }
     895         105 :     else if (EQUAL(pszCompressor, "ZSTD"))
     896             :     {
     897           4 :         CPLJSONObject oCodec;
     898           2 :         oCodec.Add("name", "zstd");
     899             :         const char *pszLevel =
     900           2 :             CSLFetchNameValueDef(papszOptions, "ZSTD_LEVEL", "13");
     901           2 :         const bool bChecksum = CPLTestBool(
     902             :             CSLFetchNameValueDef(papszOptions, "ZSTD_CHECKSUM", "FALSE"));
     903           2 :         oCodec.Add("configuration", ZarrV3CodecZstd::GetConfiguration(
     904             :                                         atoi(pszLevel), bChecksum));
     905           2 :         oCodecs.Add(oCodec);
     906             :     }
     907         103 :     else if (!EQUAL(pszCompressor, "NONE"))
     908             :     {
     909           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     910             :                  "COMPRESS = %s not implemented with Zarr V3", pszCompressor);
     911           1 :         return nullptr;
     912             :     }
     913             : 
     914         258 :     std::vector<GUInt64> anInnerBlockSize = anOuterBlockSize;
     915         129 :     if (oCodecs.Size() > 0)
     916             :     {
     917         129 :         std::vector<GByte> abyNoData;
     918         258 :         poCodecs = ZarrV3Array::SetupCodecs(oCodecs, anOuterBlockSize,
     919             :                                             anInnerBlockSize,
     920         258 :                                             aoDtypeElts.back(), abyNoData);
     921         129 :         if (!poCodecs)
     922             :         {
     923           0 :             return nullptr;
     924             :         }
     925             :     }
     926             : 
     927         129 :     auto poArray = ZarrV3Array::Create(m_poSharedResource, Self(), osName,
     928             :                                        aoDimensions, oDataType, aoDtypeElts,
     929         258 :                                        anOuterBlockSize, anInnerBlockSize);
     930             : 
     931         129 :     if (!poArray)
     932           0 :         return nullptr;
     933         129 :     poArray->SetNew(true);
     934             :     const std::string osFilename =
     935         258 :         CPLFormFilenameSafe(osArrayDirectory.c_str(), "zarr.json", nullptr);
     936         129 :     poArray->SetFilename(osFilename);
     937         129 :     poArray->SetDimSeparator(pszDimSeparator);
     938         129 :     poArray->SetDtype(dtype);
     939         258 :     if (oCodecs.Size() > 0 &&
     940         258 :         oCodecs[oCodecs.Size() - 1].GetString("name") != "bytes")
     941             :     {
     942          54 :         poArray->SetStructuralInfo(
     943          54 :             "COMPRESSOR", oCodecs[oCodecs.Size() - 1].ToString().c_str());
     944             :     }
     945         129 :     if (poCodecs)
     946         129 :         poArray->SetCodecs(oCodecs, std::move(poCodecs));
     947             : 
     948         129 :     poArray->SetCreationOptions(papszOptions);
     949         129 :     poArray->SetUpdatable(true);
     950         129 :     poArray->SetDefinitionModified(true);
     951         129 :     if (!cpl::starts_with(osFilename, "/vsi") && !poArray->Flush())
     952           0 :         return nullptr;
     953         129 :     RegisterArray(poArray);
     954             : 
     955         129 :     return poArray;
     956             : }

Generated by: LCOV version 1.14