LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v2_group.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 501 586 85.5 %
Date: 2025-07-08 21:33:46 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             : 
      15             : #include "cpl_minixml.h"
      16             : 
      17             : #include <algorithm>
      18             : #include <cassert>
      19             : #include <limits>
      20             : #include <map>
      21             : #include <set>
      22             : 
      23             : /************************************************************************/
      24             : /*                      ZarrV2Group::Create()                           */
      25             : /************************************************************************/
      26             : 
      27             : std::shared_ptr<ZarrV2Group>
      28        1087 : ZarrV2Group::Create(const std::shared_ptr<ZarrSharedResource> &poSharedResource,
      29             :                     const std::string &osParentName, const std::string &osName)
      30             : {
      31             :     auto poGroup = std::shared_ptr<ZarrV2Group>(
      32        1087 :         new ZarrV2Group(poSharedResource, osParentName, osName));
      33        1087 :     poGroup->SetSelf(poGroup);
      34        1087 :     return poGroup;
      35             : }
      36             : 
      37             : /************************************************************************/
      38             : /*                      ZarrV2Group::~ZarrV2Group()                     */
      39             : /************************************************************************/
      40             : 
      41        2174 : ZarrV2Group::~ZarrV2Group()
      42             : {
      43        1087 :     if (m_bValid && m_oAttrGroup.IsModified())
      44             :     {
      45          54 :         CPLJSONDocument oDoc;
      46          27 :         oDoc.SetRoot(m_oAttrGroup.Serialize());
      47             :         const std::string osAttrFilename =
      48          27 :             CPLFormFilenameSafe(m_osDirectoryName.c_str(), ".zattrs", nullptr);
      49          27 :         oDoc.Save(osAttrFilename);
      50          27 :         m_poSharedResource->SetZMetadataItem(osAttrFilename, oDoc.GetRoot());
      51             :     }
      52        2174 : }
      53             : 
      54             : /************************************************************************/
      55             : /*                        ExploreDirectory()                            */
      56             : /************************************************************************/
      57             : 
      58         245 : void ZarrV2Group::ExploreDirectory() const
      59             : {
      60         245 :     if (m_bDirectoryExplored || m_osDirectoryName.empty())
      61         197 :         return;
      62         245 :     m_bDirectoryExplored = true;
      63             : 
      64         245 :     const CPLStringList aosFiles(VSIReadDir(m_osDirectoryName.c_str()));
      65             :     // If the directory contains a .zarray, no need to recurse.
      66         576 :     for (int i = 0; i < aosFiles.size(); ++i)
      67             :     {
      68         528 :         if (strcmp(aosFiles[i], ".zarray") == 0)
      69         197 :             return;
      70             :     }
      71             : 
      72         235 :     for (int i = 0; i < aosFiles.size(); ++i)
      73             :     {
      74         374 :         if (aosFiles[i][0] != 0 && strcmp(aosFiles[i], ".") != 0 &&
      75         167 :             strcmp(aosFiles[i], "..") != 0 &&
      76         147 :             strcmp(aosFiles[i], ".zgroup") != 0 &&
      77         466 :             strcmp(aosFiles[i], ".zattrs") != 0 &&
      78             :             // Exclude filenames ending with '/'. This can happen on some
      79             :             // object storage like S3 where a "foo" file and a "foo/" directory
      80             :             // can coexist. The ending slash is only appended in that situation
      81             :             // where both a file and directory have the same name. So we can
      82             :             // safely ignore the one with an ending slash, as we will also
      83             :             // encounter its version without slash. Cf use case of
      84             :             // https://github.com/OSGeo/gdal/issues/8192
      85          92 :             aosFiles[i][strlen(aosFiles[i]) - 1] != '/')
      86             :         {
      87             :             const std::string osSubDir = CPLFormFilenameSafe(
      88         184 :                 m_osDirectoryName.c_str(), aosFiles[i], nullptr);
      89             :             VSIStatBufL sStat;
      90             :             std::string osFilename =
      91         184 :                 CPLFormFilenameSafe(osSubDir.c_str(), ".zarray", nullptr);
      92          92 :             if (VSIStatL(osFilename.c_str(), &sStat) == 0)
      93             :             {
      94          55 :                 if (std::find(m_aosArrays.begin(), m_aosArrays.end(),
      95          55 :                               aosFiles[i]) == m_aosArrays.end())
      96             :                 {
      97          45 :                     m_aosArrays.emplace_back(aosFiles[i]);
      98             :                 }
      99             :             }
     100             :             else
     101             :             {
     102             :                 osFilename =
     103          37 :                     CPLFormFilenameSafe(osSubDir.c_str(), ".zgroup", nullptr);
     104          37 :                 if (VSIStatL(osFilename.c_str(), &sStat) == 0)
     105          31 :                     m_aosGroups.emplace_back(aosFiles[i]);
     106             :             }
     107             :         }
     108             :     }
     109             : }
     110             : 
     111             : /************************************************************************/
     112             : /*                             OpenZarrArray()                          */
     113             : /************************************************************************/
     114             : 
     115         820 : std::shared_ptr<ZarrArray> ZarrV2Group::OpenZarrArray(const std::string &osName,
     116             :                                                       CSLConstList) const
     117             : {
     118         820 :     if (!CheckValidAndErrorOutIfNot())
     119           0 :         return nullptr;
     120             : 
     121         820 :     auto oIter = m_oMapMDArrays.find(osName);
     122         820 :     if (oIter != m_oMapMDArrays.end())
     123         590 :         return oIter->second;
     124             : 
     125         230 :     if (!m_bReadFromZMetadata && !m_osDirectoryName.empty())
     126             :     {
     127             :         const std::string osSubDir = CPLFormFilenameSafe(
     128         224 :             m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     129             :         VSIStatBufL sStat;
     130             :         const std::string osZarrayFilename =
     131         224 :             CPLFormFilenameSafe(osSubDir.c_str(), ".zarray", nullptr);
     132         224 :         if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
     133             :         {
     134         200 :             CPLJSONDocument oDoc;
     135         100 :             if (!oDoc.Load(osZarrayFilename))
     136           0 :                 return nullptr;
     137         100 :             const auto oRoot = oDoc.GetRoot();
     138             :             return LoadArray(osName, osZarrayFilename, oRoot, false,
     139         200 :                              CPLJSONObject());
     140             :         }
     141             :     }
     142             : 
     143         130 :     return nullptr;
     144             : }
     145             : 
     146             : /************************************************************************/
     147             : /*                              OpenZarrGroup()                             */
     148             : /************************************************************************/
     149             : 
     150             : std::shared_ptr<ZarrGroupBase>
     151         555 : ZarrV2Group::OpenZarrGroup(const std::string &osName, CSLConstList) const
     152             : {
     153         555 :     if (!CheckValidAndErrorOutIfNot())
     154           0 :         return nullptr;
     155             : 
     156         555 :     auto oIter = m_oMapGroups.find(osName);
     157         555 :     if (oIter != m_oMapGroups.end())
     158         174 :         return oIter->second;
     159             : 
     160         381 :     if (!m_bReadFromZMetadata && !m_osDirectoryName.empty())
     161             :     {
     162             :         const std::string osSubDir = CPLFormFilenameSafe(
     163          46 :             m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     164             :         VSIStatBufL sStat;
     165             :         const std::string osZgroupFilename =
     166          46 :             CPLFormFilenameSafe(osSubDir.c_str(), ".zgroup", nullptr);
     167          46 :         if (VSIStatL(osZgroupFilename.c_str(), &sStat) == 0)
     168             :         {
     169          84 :             CPLJSONDocument oDoc;
     170          42 :             if (!oDoc.Load(osZgroupFilename))
     171           0 :                 return nullptr;
     172             : 
     173             :             auto poSubGroup =
     174          84 :                 ZarrV2Group::Create(m_poSharedResource, GetFullName(), osName);
     175          42 :             poSubGroup->m_poParent =
     176          84 :                 std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
     177          42 :             poSubGroup->SetUpdatable(m_bUpdatable);
     178          42 :             poSubGroup->SetDirectoryName(osSubDir);
     179          42 :             m_oMapGroups[osName] = poSubGroup;
     180             : 
     181             :             // Must be done after setting m_oMapGroups, to avoid infinite
     182             :             // recursion when opening NCZarr datasets with indexing variables
     183             :             // of dimensions
     184          42 :             poSubGroup->InitFromZGroup(oDoc.GetRoot());
     185             : 
     186          42 :             return poSubGroup;
     187             :         }
     188             :     }
     189             : 
     190         339 :     return nullptr;
     191             : }
     192             : 
     193             : /************************************************************************/
     194             : /*                   ZarrV2Group::LoadAttributes()                      */
     195             : /************************************************************************/
     196             : 
     197         124 : void ZarrV2Group::LoadAttributes() const
     198             : {
     199         124 :     if (m_bAttributesLoaded || m_osDirectoryName.empty())
     200         107 :         return;
     201          41 :     m_bAttributesLoaded = true;
     202             : 
     203          41 :     CPLJSONDocument oDoc;
     204             :     const std::string osZattrsFilename(
     205          41 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), ".zattrs", nullptr));
     206          41 :     CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
     207          41 :     if (!oDoc.Load(osZattrsFilename))
     208          24 :         return;
     209          34 :     auto oRoot = oDoc.GetRoot();
     210          17 :     m_oAttrGroup.Init(oRoot, m_bUpdatable);
     211             : }
     212             : 
     213             : /************************************************************************/
     214             : /*                   ZarrV2Group::GetOrCreateSubGroup()                 */
     215             : /************************************************************************/
     216             : 
     217             : std::shared_ptr<ZarrV2Group>
     218         115 : ZarrV2Group::GetOrCreateSubGroup(const std::string &osSubGroupFullname)
     219             : {
     220             :     auto poSubGroup = std::dynamic_pointer_cast<ZarrV2Group>(
     221         115 :         OpenGroupFromFullname(osSubGroupFullname));
     222         115 :     if (poSubGroup)
     223             :     {
     224          52 :         return poSubGroup;
     225             :     }
     226             : 
     227          63 :     const auto nLastSlashPos = osSubGroupFullname.rfind('/');
     228             :     auto poBelongingGroup =
     229             :         (nLastSlashPos == 0)
     230          63 :             ? this
     231          74 :             : GetOrCreateSubGroup(osSubGroupFullname.substr(0, nLastSlashPos))
     232          63 :                   .get();
     233             : 
     234             :     poSubGroup =
     235         126 :         ZarrV2Group::Create(m_poSharedResource, poBelongingGroup->GetFullName(),
     236         189 :                             osSubGroupFullname.substr(nLastSlashPos + 1));
     237         126 :     poSubGroup->m_poParent = std::dynamic_pointer_cast<ZarrGroupBase>(
     238         189 :         poBelongingGroup->m_pSelf.lock());
     239         126 :     poSubGroup->SetDirectoryName(
     240         126 :         CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
     241          63 :                             poSubGroup->GetName().c_str(), nullptr));
     242          63 :     poSubGroup->m_bDirectoryExplored = true;
     243          63 :     poSubGroup->m_bAttributesLoaded = true;
     244          63 :     poSubGroup->m_bReadFromZMetadata = true;
     245          63 :     poSubGroup->SetUpdatable(m_bUpdatable);
     246             : 
     247          63 :     poBelongingGroup->m_oMapGroups[poSubGroup->GetName()] = poSubGroup;
     248          63 :     poBelongingGroup->m_aosGroups.emplace_back(poSubGroup->GetName());
     249          63 :     return poSubGroup;
     250             : }
     251             : 
     252             : /************************************************************************/
     253             : /*                   ZarrV2Group::InitFromZMetadata()                   */
     254             : /************************************************************************/
     255             : 
     256         171 : void ZarrV2Group::InitFromZMetadata(const CPLJSONObject &obj)
     257             : {
     258         171 :     m_bDirectoryExplored = true;
     259         171 :     m_bAttributesLoaded = true;
     260         171 :     m_bReadFromZMetadata = true;
     261             : 
     262         342 :     const auto metadata = obj["metadata"];
     263         171 :     if (metadata.GetType() != CPLJSONObject::Type::Object)
     264           0 :         return;
     265         342 :     const auto children = metadata.GetChildren();
     266         342 :     std::map<std::string, const CPLJSONObject *> oMapArrays;
     267             : 
     268             :     // First pass to create groups and collect arrays
     269         979 :     for (const auto &child : children)
     270             :     {
     271         808 :         const std::string osName(child.GetName());
     272         808 :         if (std::count(osName.begin(), osName.end(), '/') > 32)
     273             :         {
     274             :             // Avoid too deep recursion in GetOrCreateSubGroup()
     275           0 :             continue;
     276             :         }
     277         808 :         if (osName == ".zattrs")
     278             :         {
     279           4 :             m_oAttrGroup.Init(child, m_bUpdatable);
     280             :         }
     281        1437 :         else if (osName.size() > strlen("/.zgroup") &&
     282        1437 :                  osName.substr(osName.size() - strlen("/.zgroup")) ==
     283             :                      "/.zgroup")
     284             :         {
     285          63 :             GetOrCreateSubGroup(
     286         126 :                 "/" + osName.substr(0, osName.size() - strlen("/.zgroup")));
     287             :         }
     288        1311 :         else if (osName.size() > strlen("/.zarray") &&
     289        1311 :                  osName.substr(osName.size() - strlen("/.zarray")) ==
     290             :                      "/.zarray")
     291             :         {
     292             :             auto osArrayFullname =
     293         283 :                 osName.substr(0, osName.size() - strlen("/.zarray"));
     294         283 :             oMapArrays[osArrayFullname] = &child;
     295             :         }
     296             :     }
     297             : 
     298         283 :     const auto CreateArray = [this](const std::string &osArrayFullname,
     299             :                                     const CPLJSONObject &oArray,
     300          41 :                                     const CPLJSONObject &oAttributes)
     301             :     {
     302         283 :         const auto nLastSlashPos = osArrayFullname.rfind('/');
     303             :         auto poBelongingGroup =
     304             :             (nLastSlashPos == std::string::npos)
     305         283 :                 ? this
     306         324 :                 : GetOrCreateSubGroup("/" +
     307         324 :                                       osArrayFullname.substr(0, nLastSlashPos))
     308         283 :                       .get();
     309             :         const auto osArrayName =
     310             :             nLastSlashPos == std::string::npos
     311             :                 ? osArrayFullname
     312         566 :                 : osArrayFullname.substr(nLastSlashPos + 1);
     313             :         const std::string osZarrayFilename = CPLFormFilenameSafe(
     314         283 :             CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
     315             :                                 osArrayName.c_str(), nullptr)
     316             :                 .c_str(),
     317         283 :             ".zarray", nullptr);
     318         283 :         poBelongingGroup->LoadArray(osArrayName, osZarrayFilename, oArray, true,
     319             :                                     oAttributes);
     320         283 :     };
     321             : 
     322             :     struct ArrayDesc
     323             :     {
     324             :         std::string osArrayFullname{};
     325             :         const CPLJSONObject *poArray = nullptr;
     326             :         const CPLJSONObject *poAttrs = nullptr;
     327             :     };
     328             : 
     329         342 :     std::vector<ArrayDesc> aoRegularArrays;
     330             : 
     331             :     // Second pass to read attributes and create arrays that are indexing
     332             :     // variable
     333         979 :     for (const auto &child : children)
     334             :     {
     335        1616 :         const std::string osName(child.GetName());
     336        1441 :         if (osName.size() > strlen("/.zattrs") &&
     337        1441 :             osName.substr(osName.size() - strlen("/.zattrs")) == "/.zattrs")
     338             :         {
     339             :             std::string osObjectFullnameNoLeadingSlash =
     340         574 :                 osName.substr(0, osName.size() - strlen("/.zattrs"));
     341             :             auto poSubGroup = std::dynamic_pointer_cast<ZarrV2Group>(
     342         861 :                 OpenGroupFromFullname('/' + osObjectFullnameNoLeadingSlash));
     343         287 :             if (poSubGroup)
     344             :             {
     345          18 :                 poSubGroup->m_oAttrGroup.Init(child, m_bUpdatable);
     346             :             }
     347             :             else
     348             :             {
     349         269 :                 auto oIter = oMapArrays.find(osObjectFullnameNoLeadingSlash);
     350         269 :                 if (oIter != oMapArrays.end())
     351             :                 {
     352             :                     const auto nLastSlashPos =
     353         268 :                         osObjectFullnameNoLeadingSlash.rfind('/');
     354             :                     const std::string osArrayName =
     355             :                         (nLastSlashPos == std::string::npos)
     356             :                             ? osObjectFullnameNoLeadingSlash
     357             :                             : osObjectFullnameNoLeadingSlash.substr(
     358         536 :                                   nLastSlashPos + 1);
     359             :                     const auto arrayDimensions =
     360         804 :                         child["_ARRAY_DIMENSIONS"].ToArray();
     361        1057 :                     if (arrayDimensions.IsValid() &&
     362         398 :                         arrayDimensions.Size() == 1 &&
     363         398 :                         arrayDimensions[0].ToString() == osArrayName)
     364             :                     {
     365         102 :                         CreateArray(osObjectFullnameNoLeadingSlash,
     366         102 :                                     *(oIter->second), child);
     367         102 :                         oMapArrays.erase(oIter);
     368             :                     }
     369             :                     else
     370             :                     {
     371         332 :                         ArrayDesc desc;
     372             :                         desc.osArrayFullname =
     373         166 :                             std::move(osObjectFullnameNoLeadingSlash);
     374         166 :                         desc.poArray = oIter->second;
     375         166 :                         desc.poAttrs = &child;
     376         166 :                         aoRegularArrays.emplace_back(std::move(desc));
     377             :                     }
     378             :                 }
     379             :             }
     380             :         }
     381             :     }
     382             : 
     383             :     // Third pass to create non-indexing arrays with attributes
     384         337 :     for (const auto &desc : aoRegularArrays)
     385             :     {
     386         166 :         CreateArray(desc.osArrayFullname, *(desc.poArray), *(desc.poAttrs));
     387         166 :         oMapArrays.erase(desc.osArrayFullname);
     388             :     }
     389             : 
     390             :     // Fourth pass to create arrays without attributes
     391         186 :     for (const auto &kv : oMapArrays)
     392             :     {
     393          15 :         CreateArray(kv.first, *(kv.second), CPLJSONObject());
     394             :     }
     395             : }
     396             : 
     397             : /************************************************************************/
     398             : /*                   ZarrV2Group::InitFromZGroup()                      */
     399             : /************************************************************************/
     400             : 
     401         137 : bool ZarrV2Group::InitFromZGroup(const CPLJSONObject &obj)
     402             : {
     403             :     // Parse potential NCZarr (V2) extensions:
     404             :     // https://www.unidata.ucar.edu/software/netcdf/documentation/NUG/nczarr_head.html
     405         411 :     const auto nczarrGroup = obj["_NCZARR_GROUP"];
     406         137 :     if (nczarrGroup.GetType() == CPLJSONObject::Type::Object)
     407             :     {
     408          24 :         if (m_bUpdatable)
     409             :         {
     410           4 :             CPLError(CE_Failure, CPLE_NotSupported,
     411             :                      "Update of NCZarr datasets is not supported");
     412           4 :             return false;
     413             :         }
     414          20 :         m_bDirectoryExplored = true;
     415             : 
     416             :         // If not opening from the root of the dataset, walk up to it
     417          56 :         if (!obj["_NCZARR_SUPERBLOCK"].IsValid() &&
     418          36 :             m_poParent.lock() == nullptr)
     419             :         {
     420             :             const std::string osParentGroupFilename(CPLFormFilenameSafe(
     421           7 :                 CPLGetPathSafe(m_osDirectoryName.c_str()).c_str(), ".zgroup",
     422          14 :                 nullptr));
     423             :             VSIStatBufL sStat;
     424           7 :             if (VSIStatL(osParentGroupFilename.c_str(), &sStat) == 0)
     425             :             {
     426          12 :                 CPLJSONDocument oDoc;
     427           6 :                 if (oDoc.Load(osParentGroupFilename))
     428             :                 {
     429             :                     auto poParent = ZarrV2Group::Create(
     430          12 :                         m_poSharedResource, std::string(), std::string());
     431           6 :                     poParent->m_bDirectoryExplored = true;
     432          12 :                     poParent->SetDirectoryName(
     433          12 :                         CPLGetPathSafe(m_osDirectoryName.c_str()));
     434           6 :                     poParent->InitFromZGroup(oDoc.GetRoot());
     435           6 :                     m_poParentStrongRef = poParent;
     436           6 :                     m_poParent = poParent;
     437             : 
     438             :                     // Patch our name and fullname
     439           6 :                     m_osName = CPLGetFilename(m_osDirectoryName.c_str());
     440             :                     m_osFullName =
     441           6 :                         poParent->GetFullName() == "/"
     442          15 :                             ? m_osName
     443          15 :                             : poParent->GetFullName() + "/" + m_osName;
     444             :                 }
     445             :             }
     446             :         }
     447             : 
     448         117 :         const auto IsValidName = [](const std::string &s)
     449             :         {
     450         351 :             return !s.empty() && s != "." && s != ".." &&
     451         351 :                    s.find("/") == std::string::npos &&
     452         234 :                    s.find("\\") == std::string::npos;
     453             :         };
     454             : 
     455             :         // Create dimensions first, as they will be potentially patched
     456             :         // by the OpenMDArray() later
     457          60 :         const auto dims = nczarrGroup["dims"];
     458          50 :         for (const auto &jDim : dims.GetChildren())
     459             :         {
     460          60 :             const auto osName = jDim.GetName();
     461          30 :             const GUInt64 nSize = jDim.ToLong();
     462          30 :             if (!IsValidName(osName))
     463             :             {
     464           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     465             :                          "Invalid dimension name for %s", osName.c_str());
     466             :             }
     467          30 :             else if (nSize == 0)
     468             :             {
     469           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     470             :                          "Invalid dimension size for %s", osName.c_str());
     471             :             }
     472             :             else
     473             :             {
     474          29 :                 CreateDimension(osName,
     475          58 :                                 std::string(),  // type
     476          29 :                                 std::string(),  // direction,
     477          29 :                                 nSize, nullptr);
     478             :             }
     479             :         }
     480             : 
     481          60 :         const auto vars = nczarrGroup["vars"].ToArray();
     482             :         // open first indexing variables
     483          40 :         std::set<std::string> oSetIndexingArrayNames;
     484          57 :         for (const auto &var : vars)
     485             :         {
     486         111 :             const auto osVarName = var.ToString();
     487          37 :             if (IsValidName(osVarName) &&
     488          90 :                 m_oMapDimensions.find(osVarName) != m_oMapDimensions.end() &&
     489         127 :                 m_oMapMDArrays.find(osVarName) == m_oMapMDArrays.end() &&
     490          15 :                 oSetIndexingArrayNames.find(osVarName) ==
     491          52 :                     oSetIndexingArrayNames.end())
     492             :             {
     493          15 :                 oSetIndexingArrayNames.insert(osVarName);
     494          15 :                 OpenMDArray(osVarName);
     495             :             }
     496             :         }
     497             : 
     498             :         // add regular arrays
     499          40 :         std::set<std::string> oSetRegularArrayNames;
     500          57 :         for (const auto &var : vars)
     501             :         {
     502         111 :             const auto osVarName = var.ToString();
     503          37 :             if (IsValidName(osVarName) &&
     504          95 :                 m_oMapDimensions.find(osVarName) == m_oMapDimensions.end() &&
     505         132 :                 m_oMapMDArrays.find(osVarName) == m_oMapMDArrays.end() &&
     506          21 :                 oSetRegularArrayNames.find(osVarName) ==
     507          58 :                     oSetRegularArrayNames.end())
     508             :             {
     509          20 :                 oSetRegularArrayNames.insert(osVarName);
     510          20 :                 m_aosArrays.emplace_back(osVarName);
     511             :             }
     512             :         }
     513             : 
     514             :         // Finally list groups
     515          40 :         std::set<std::string> oSetGroups;
     516          60 :         const auto groups = nczarrGroup["groups"].ToArray();
     517          33 :         for (const auto &group : groups)
     518             :         {
     519          39 :             const auto osGroupName = group.ToString();
     520          26 :             if (IsValidName(osGroupName) &&
     521          26 :                 oSetGroups.find(osGroupName) == oSetGroups.end())
     522             :             {
     523          12 :                 oSetGroups.insert(osGroupName);
     524          12 :                 m_aosGroups.emplace_back(osGroupName);
     525             :             }
     526             :         }
     527             :     }
     528         133 :     return true;
     529             : }
     530             : 
     531             : /************************************************************************/
     532             : /*                   ZarrV2Group::CreateOnDisk()                        */
     533             : /************************************************************************/
     534             : 
     535         241 : std::shared_ptr<ZarrV2Group> ZarrV2Group::CreateOnDisk(
     536             :     const std::shared_ptr<ZarrSharedResource> &poSharedResource,
     537             :     const std::string &osParentName, const std::string &osName,
     538             :     const std::string &osDirectoryName)
     539             : {
     540         241 :     if (VSIMkdir(osDirectoryName.c_str(), 0755) != 0)
     541             :     {
     542             :         VSIStatBufL sStat;
     543           5 :         if (VSIStatL(osDirectoryName.c_str(), &sStat) == 0)
     544             :         {
     545           2 :             CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
     546             :                      osDirectoryName.c_str());
     547             :         }
     548             :         else
     549             :         {
     550           3 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
     551             :                      osDirectoryName.c_str());
     552             :         }
     553           5 :         return nullptr;
     554             :     }
     555             : 
     556             :     const std::string osZgroupFilename(
     557         472 :         CPLFormFilenameSafe(osDirectoryName.c_str(), ".zgroup", nullptr));
     558         236 :     VSILFILE *fp = VSIFOpenL(osZgroupFilename.c_str(), "wb");
     559         236 :     if (!fp)
     560             :     {
     561           0 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot create file %s.",
     562             :                  osZgroupFilename.c_str());
     563           0 :         return nullptr;
     564             :     }
     565         236 :     VSIFPrintfL(fp, "{\n  \"zarr_format\": 2\n}\n");
     566         236 :     VSIFCloseL(fp);
     567             : 
     568         472 :     auto poGroup = ZarrV2Group::Create(poSharedResource, osParentName, osName);
     569         236 :     poGroup->SetDirectoryName(osDirectoryName);
     570         236 :     poGroup->SetUpdatable(true);
     571         236 :     poGroup->m_bDirectoryExplored = true;
     572             : 
     573         472 :     CPLJSONObject oObj;
     574         236 :     oObj.Add("zarr_format", 2);
     575         236 :     poSharedResource->SetZMetadataItem(osZgroupFilename, oObj);
     576             : 
     577         236 :     return poGroup;
     578             : }
     579             : 
     580             : /************************************************************************/
     581             : /*                      ZarrV2Group::CreateGroup()                      */
     582             : /************************************************************************/
     583             : 
     584             : std::shared_ptr<GDALGroup>
     585          75 : ZarrV2Group::CreateGroup(const std::string &osName,
     586             :                          CSLConstList /* papszOptions */)
     587             : {
     588          75 :     if (!CheckValidAndErrorOutIfNot())
     589           0 :         return nullptr;
     590             : 
     591          75 :     if (!m_bUpdatable)
     592             :     {
     593           2 :         CPLError(CE_Failure, CPLE_NotSupported,
     594             :                  "Dataset not open in update mode");
     595           2 :         return nullptr;
     596             :     }
     597          73 :     if (!IsValidObjectName(osName))
     598             :     {
     599          14 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid group name");
     600          14 :         return nullptr;
     601             :     }
     602             : 
     603          59 :     GetGroupNames();
     604             : 
     605          59 :     if (std::find(m_aosGroups.begin(), m_aosGroups.end(), osName) !=
     606         118 :         m_aosGroups.end())
     607             :     {
     608           2 :         CPLError(CE_Failure, CPLE_AppDefined,
     609             :                  "A group with same name already exists");
     610           2 :         return nullptr;
     611             :     }
     612             : 
     613             :     const std::string osDirectoryName =
     614         114 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     615          57 :     auto poGroup = CreateOnDisk(m_poSharedResource, GetFullName(), osName,
     616         114 :                                 osDirectoryName);
     617          57 :     if (!poGroup)
     618           2 :         return nullptr;
     619          55 :     poGroup->m_poParent =
     620         110 :         std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
     621          55 :     m_oMapGroups[osName] = poGroup;
     622          55 :     m_aosGroups.emplace_back(osName);
     623          55 :     return poGroup;
     624             : }
     625             : 
     626             : /************************************************************************/
     627             : /*                          FillDTypeElts()                             */
     628             : /************************************************************************/
     629             : 
     630         331 : static CPLJSONObject FillDTypeElts(const GDALExtendedDataType &oDataType,
     631             :                                    size_t nGDALStartOffset,
     632             :                                    std::vector<DtypeElt> &aoDtypeElts,
     633             :                                    bool bUseUnicode)
     634             : {
     635         331 :     CPLJSONObject dtype;
     636         331 :     const auto eClass = oDataType.GetClass();
     637             :     const size_t nNativeStartOffset =
     638         331 :         aoDtypeElts.empty()
     639         331 :             ? 0
     640           4 :             : aoDtypeElts.back().nativeOffset + aoDtypeElts.back().nativeSize;
     641         662 :     const std::string dummy("dummy");
     642             : 
     643         331 :     switch (eClass)
     644             :     {
     645           4 :         case GEDTC_STRING:
     646             :         {
     647           4 :             if (oDataType.GetMaxStringLength() == 0)
     648             :             {
     649           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
     650             :                          "String arrays of unlimited size are not supported");
     651           0 :                 dtype = CPLJSONObject();
     652           0 :                 dtype.Deinit();
     653           0 :                 return dtype;
     654             :             }
     655           8 :             DtypeElt elt;
     656           4 :             elt.nativeOffset = nNativeStartOffset;
     657           4 :             if (bUseUnicode)
     658             :             {
     659           1 :                 elt.nativeType = DtypeElt::NativeType::STRING_UNICODE;
     660           1 :                 elt.nativeSize = oDataType.GetMaxStringLength() * 4;
     661             : #ifdef CPL_MSB
     662             :                 elt.needByteSwapping = true;
     663             : #endif
     664           1 :                 dtype.Set(
     665             :                     dummy,
     666             :                     CPLSPrintf("<U%d", static_cast<int>(
     667           1 :                                            oDataType.GetMaxStringLength())));
     668             :             }
     669             :             else
     670             :             {
     671           3 :                 elt.nativeType = DtypeElt::NativeType::STRING_ASCII;
     672           3 :                 elt.nativeSize = oDataType.GetMaxStringLength();
     673           3 :                 dtype.Set(
     674             :                     dummy,
     675             :                     CPLSPrintf("|S%d", static_cast<int>(
     676           3 :                                            oDataType.GetMaxStringLength())));
     677             :             }
     678           4 :             elt.gdalOffset = nGDALStartOffset;
     679           4 :             elt.gdalSize = sizeof(char *);
     680           4 :             aoDtypeElts.emplace_back(elt);
     681           4 :             break;
     682             :         }
     683             : 
     684         323 :         case GEDTC_NUMERIC:
     685             :         {
     686         323 :             const auto eDT = oDataType.GetNumericDataType();
     687         323 :             DtypeElt elt;
     688         323 :             bool bUnsupported = false;
     689         323 :             switch (eDT)
     690             :             {
     691         121 :                 case GDT_Byte:
     692             :                 {
     693         121 :                     elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     694         121 :                     dtype.Set(dummy, "|u1");
     695         121 :                     break;
     696             :                 }
     697           3 :                 case GDT_Int8:
     698             :                 {
     699           3 :                     elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     700           3 :                     dtype.Set(dummy, "|i1");
     701           3 :                     break;
     702             :                 }
     703           8 :                 case GDT_UInt16:
     704             :                 {
     705           8 :                     elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     706           8 :                     dtype.Set(dummy, "<u2");
     707           8 :                     break;
     708             :                 }
     709          11 :                 case GDT_Int16:
     710             :                 {
     711          11 :                     elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     712          11 :                     dtype.Set(dummy, "<i2");
     713          11 :                     break;
     714             :                 }
     715           6 :                 case GDT_UInt32:
     716             :                 {
     717           6 :                     elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     718           6 :                     dtype.Set(dummy, "<u4");
     719           6 :                     break;
     720             :                 }
     721           7 :                 case GDT_Int32:
     722             :                 {
     723           7 :                     elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     724           7 :                     dtype.Set(dummy, "<i4");
     725           7 :                     break;
     726             :                 }
     727           4 :                 case GDT_UInt64:
     728             :                 {
     729           4 :                     elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     730           4 :                     dtype.Set(dummy, "<u8");
     731           4 :                     break;
     732             :                 }
     733           3 :                 case GDT_Int64:
     734             :                 {
     735           3 :                     elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
     736           3 :                     dtype.Set(dummy, "<i8");
     737           3 :                     break;
     738             :                 }
     739           1 :                 case GDT_Float16:
     740             :                 {
     741           1 :                     elt.nativeType = DtypeElt::NativeType::IEEEFP;
     742           1 :                     dtype.Set(dummy, "<f2");
     743           1 :                     break;
     744             :                 }
     745           7 :                 case GDT_Float32:
     746             :                 {
     747           7 :                     elt.nativeType = DtypeElt::NativeType::IEEEFP;
     748           7 :                     dtype.Set(dummy, "<f4");
     749           7 :                     break;
     750             :                 }
     751         135 :                 case GDT_Float64:
     752             :                 {
     753         135 :                     elt.nativeType = DtypeElt::NativeType::IEEEFP;
     754         135 :                     dtype.Set(dummy, "<f8");
     755         135 :                     break;
     756             :                 }
     757           8 :                 case GDT_Unknown:
     758             :                 case GDT_CInt16:
     759             :                 case GDT_CInt32:
     760             :                 {
     761           8 :                     bUnsupported = true;
     762           8 :                     break;
     763             :                 }
     764           0 :                 case GDT_CFloat16:
     765             :                 {
     766           0 :                     elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     767           0 :                     dtype.Set(dummy, "<c4");
     768           0 :                     break;
     769             :                 }
     770           4 :                 case GDT_CFloat32:
     771             :                 {
     772           4 :                     elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     773           4 :                     dtype.Set(dummy, "<c8");
     774           4 :                     break;
     775             :                 }
     776           5 :                 case GDT_CFloat64:
     777             :                 {
     778           5 :                     elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     779           5 :                     dtype.Set(dummy, "<c16");
     780           5 :                     break;
     781             :                 }
     782           0 :                 case GDT_TypeCount:
     783             :                 {
     784             :                     static_assert(GDT_TypeCount == GDT_CFloat16 + 1,
     785             :                                   "GDT_TypeCount == GDT_CFloat16 + 1");
     786           0 :                     break;
     787             :                 }
     788             :             }
     789         323 :             if (bUnsupported)
     790             :             {
     791           8 :                 CPLError(CE_Failure, CPLE_NotSupported,
     792             :                          "Unsupported data type: %s", GDALGetDataTypeName(eDT));
     793           8 :                 dtype = CPLJSONObject();
     794           8 :                 dtype.Deinit();
     795           8 :                 return dtype;
     796             :             }
     797         315 :             elt.nativeOffset = nNativeStartOffset;
     798         315 :             elt.nativeSize = GDALGetDataTypeSizeBytes(eDT);
     799         315 :             elt.gdalOffset = nGDALStartOffset;
     800         315 :             elt.gdalSize = elt.nativeSize;
     801             : #ifdef CPL_MSB
     802             :             elt.needByteSwapping = elt.nativeSize > 1;
     803             : #endif
     804         315 :             aoDtypeElts.emplace_back(elt);
     805         315 :             break;
     806             :         }
     807             : 
     808           4 :         case GEDTC_COMPOUND:
     809             :         {
     810           4 :             const auto &comps = oDataType.GetComponents();
     811           4 :             CPLJSONArray array;
     812          10 :             for (const auto &comp : comps)
     813             :             {
     814           6 :                 CPLJSONArray subArray;
     815           6 :                 subArray.Add(comp->GetName());
     816             :                 const auto subdtype = FillDTypeElts(
     817           6 :                     comp->GetType(), nGDALStartOffset + comp->GetOffset(),
     818          12 :                     aoDtypeElts, bUseUnicode);
     819           6 :                 if (!subdtype.IsValid())
     820             :                 {
     821           0 :                     dtype = CPLJSONObject();
     822           0 :                     dtype.Deinit();
     823           0 :                     return dtype;
     824             :                 }
     825           6 :                 if (subdtype.GetType() == CPLJSONObject::Type::Object)
     826           4 :                     subArray.Add(subdtype["dummy"]);
     827             :                 else
     828           2 :                     subArray.Add(subdtype);
     829           6 :                 array.Add(subArray);
     830             :             }
     831           4 :             dtype = std::move(array);
     832           4 :             break;
     833             :         }
     834             :     }
     835         323 :     return dtype;
     836             : }
     837             : 
     838             : /************************************************************************/
     839             : /*                     ZarrV2Group::CreateMDArray()                     */
     840             : /************************************************************************/
     841             : 
     842         339 : std::shared_ptr<GDALMDArray> ZarrV2Group::CreateMDArray(
     843             :     const std::string &osName,
     844             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
     845             :     const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
     846             : {
     847         339 :     if (!CheckValidAndErrorOutIfNot())
     848           0 :         return nullptr;
     849             : 
     850         339 :     if (!m_bUpdatable)
     851             :     {
     852           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     853             :                  "Dataset not open in update mode");
     854           0 :         return nullptr;
     855             :     }
     856         339 :     if (!IsValidObjectName(osName))
     857             :     {
     858          14 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid array name");
     859          14 :         return nullptr;
     860             :     }
     861             : 
     862         650 :     std::vector<DtypeElt> aoDtypeElts;
     863             :     const bool bUseUnicode =
     864         325 :         EQUAL(CSLFetchNameValueDef(papszOptions, "STRING_FORMAT", "ASCII"),
     865             :               "UNICODE");
     866         650 :     const auto dtype = FillDTypeElts(oDataType, 0, aoDtypeElts, bUseUnicode);
     867         325 :     if (!dtype.IsValid() || aoDtypeElts.empty())
     868           8 :         return nullptr;
     869             : 
     870         317 :     GetMDArrayNames();
     871             : 
     872         317 :     if (std::find(m_aosArrays.begin(), m_aosArrays.end(), osName) !=
     873         634 :         m_aosArrays.end())
     874             :     {
     875           2 :         CPLError(CE_Failure, CPLE_AppDefined,
     876             :                  "An array with same name already exists");
     877           2 :         return nullptr;
     878             :     }
     879             : 
     880         630 :     CPLJSONObject oCompressor;
     881         315 :     oCompressor.Deinit();
     882             :     const char *pszCompressor =
     883         315 :         CSLFetchNameValueDef(papszOptions, "COMPRESS", "NONE");
     884         315 :     const CPLCompressor *psCompressor = nullptr;
     885         315 :     const CPLCompressor *psDecompressor = nullptr;
     886         315 :     if (!EQUAL(pszCompressor, "NONE"))
     887             :     {
     888          18 :         psCompressor = CPLGetCompressor(pszCompressor);
     889          18 :         psDecompressor = CPLGetDecompressor(pszCompressor);
     890          18 :         if (psCompressor == nullptr || psDecompressor == nullptr)
     891             :         {
     892           1 :             CPLError(CE_Failure, CPLE_NotSupported,
     893             :                      "Compressor/decompressor for %s not available",
     894             :                      pszCompressor);
     895           1 :             return nullptr;
     896             :         }
     897             :         const char *pszOptions =
     898          17 :             CSLFetchNameValue(psCompressor->papszMetadata, "OPTIONS");
     899          17 :         if (pszOptions)
     900             :         {
     901          34 :             CPLXMLTreeCloser oTree(CPLParseXMLString(pszOptions));
     902             :             const auto psRoot =
     903          17 :                 oTree.get() ? CPLGetXMLNode(oTree.get(), "=Options") : nullptr;
     904          17 :             if (psRoot)
     905             :             {
     906          17 :                 for (const CPLXMLNode *psNode = psRoot->psChild;
     907          39 :                      psNode != nullptr; psNode = psNode->psNext)
     908             :                 {
     909          22 :                     if (psNode->eType == CXT_Element &&
     910          22 :                         strcmp(psNode->pszValue, "Option") == 0)
     911             :                     {
     912             :                         const char *pszName =
     913          22 :                             CPLGetXMLValue(psNode, "name", nullptr);
     914             :                         const char *pszType =
     915          22 :                             CPLGetXMLValue(psNode, "type", nullptr);
     916          22 :                         if (pszName && pszType)
     917             :                         {
     918          44 :                             const char *pszVal = CSLFetchNameValueDef(
     919             :                                 papszOptions,
     920          44 :                                 (std::string(pszCompressor) + '_' + pszName)
     921             :                                     .c_str(),
     922             :                                 CPLGetXMLValue(psNode, "default", nullptr));
     923          22 :                             if (pszVal)
     924             :                             {
     925          22 :                                 if (EQUAL(pszName, "SHUFFLE") &&
     926           1 :                                     EQUAL(pszVal, "BYTE"))
     927             :                                 {
     928           1 :                                     pszVal = "1";
     929           1 :                                     pszType = "integer";
     930             :                                 }
     931             : 
     932          22 :                                 if (!oCompressor.IsValid())
     933             :                                 {
     934          17 :                                     oCompressor = CPLJSONObject();
     935          17 :                                     oCompressor.Add(
     936             :                                         "id",
     937          34 :                                         CPLString(pszCompressor).tolower());
     938             :                                 }
     939             : 
     940             :                                 std::string osOptName(
     941          44 :                                     CPLString(pszName).tolower());
     942          22 :                                 if (STARTS_WITH(pszType, "int"))
     943          20 :                                     oCompressor.Add(osOptName, atoi(pszVal));
     944             :                                 else
     945           2 :                                     oCompressor.Add(osOptName, pszVal);
     946             :                             }
     947             :                         }
     948             :                     }
     949             :                 }
     950             :             }
     951             :         }
     952             :     }
     953             : 
     954         628 :     CPLJSONArray oFilters;
     955             :     const char *pszFilter =
     956         314 :         CSLFetchNameValueDef(papszOptions, "FILTER", "NONE");
     957         314 :     if (!EQUAL(pszFilter, "NONE"))
     958             :     {
     959           1 :         const auto psFilterCompressor = CPLGetCompressor(pszFilter);
     960           1 :         const auto psFilterDecompressor = CPLGetCompressor(pszFilter);
     961           1 :         if (psFilterCompressor == nullptr || psFilterDecompressor == nullptr)
     962             :         {
     963           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     964             :                      "Compressor/decompressor for filter %s not available",
     965             :                      pszFilter);
     966           0 :             return nullptr;
     967             :         }
     968             : 
     969           1 :         CPLJSONObject oFilter;
     970           1 :         oFilter.Add("id", CPLString(pszFilter).tolower());
     971           1 :         oFilters.Add(oFilter);
     972             : 
     973             :         const char *pszOptions =
     974           1 :             CSLFetchNameValue(psFilterCompressor->papszMetadata, "OPTIONS");
     975           1 :         if (pszOptions)
     976             :         {
     977           2 :             CPLXMLTreeCloser oTree(CPLParseXMLString(pszOptions));
     978             :             const auto psRoot =
     979           1 :                 oTree.get() ? CPLGetXMLNode(oTree.get(), "=Options") : nullptr;
     980           1 :             if (psRoot)
     981             :             {
     982           1 :                 for (const CPLXMLNode *psNode = psRoot->psChild;
     983           2 :                      psNode != nullptr; psNode = psNode->psNext)
     984             :                 {
     985           1 :                     if (psNode->eType == CXT_Element &&
     986           1 :                         strcmp(psNode->pszValue, "Option") == 0)
     987             :                     {
     988             :                         const char *pszName =
     989           1 :                             CPLGetXMLValue(psNode, "name", nullptr);
     990             :                         const char *pszType =
     991           1 :                             CPLGetXMLValue(psNode, "type", nullptr);
     992           1 :                         if (pszName && pszType)
     993             :                         {
     994           2 :                             const char *pszVal = CSLFetchNameValueDef(
     995             :                                 papszOptions,
     996           2 :                                 (std::string(pszFilter) + '_' + pszName)
     997             :                                     .c_str(),
     998             :                                 CPLGetXMLValue(psNode, "default", nullptr));
     999           1 :                             if (pszVal)
    1000             :                             {
    1001             :                                 std::string osOptName(
    1002           0 :                                     CPLString(pszName).tolower());
    1003           0 :                                 if (STARTS_WITH(pszType, "int"))
    1004           0 :                                     oFilter.Add(osOptName, atoi(pszVal));
    1005             :                                 else
    1006           0 :                                     oFilter.Add(osOptName, pszVal);
    1007             :                             }
    1008             :                         }
    1009             :                     }
    1010             :                 }
    1011             :             }
    1012             :         }
    1013             : 
    1014           2 :         if (EQUAL(pszFilter, "delta") &&
    1015           1 :             CSLFetchNameValue(papszOptions, "DELTA_DTYPE") == nullptr)
    1016             :         {
    1017           1 :             if (oDataType.GetClass() != GEDTC_NUMERIC)
    1018             :             {
    1019           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
    1020             :                          "DELTA_DTYPE option must be specified");
    1021           0 :                 return nullptr;
    1022             :             }
    1023           1 :             switch (oDataType.GetNumericDataType())
    1024             :             {
    1025           0 :                 case GDT_Unknown:
    1026           0 :                     break;
    1027           0 :                 case GDT_Byte:
    1028           0 :                     oFilter.Add("dtype", "u1");
    1029           0 :                     break;
    1030           0 :                 case GDT_Int8:
    1031           0 :                     oFilter.Add("dtype", "i1");
    1032           0 :                     break;
    1033           1 :                 case GDT_UInt16:
    1034           1 :                     oFilter.Add("dtype", "<u2");
    1035           1 :                     break;
    1036           0 :                 case GDT_Int16:
    1037           0 :                     oFilter.Add("dtype", "<i2");
    1038           0 :                     break;
    1039           0 :                 case GDT_UInt32:
    1040           0 :                     oFilter.Add("dtype", "<u4");
    1041           0 :                     break;
    1042           0 :                 case GDT_Int32:
    1043           0 :                     oFilter.Add("dtype", "<i4");
    1044           0 :                     break;
    1045           0 :                 case GDT_UInt64:
    1046           0 :                     oFilter.Add("dtype", "<u8");
    1047           0 :                     break;
    1048           0 :                 case GDT_Int64:
    1049           0 :                     oFilter.Add("dtype", "<i8");
    1050           0 :                     break;
    1051           0 :                 case GDT_Float16:
    1052           0 :                     oFilter.Add("dtype", "<f2");
    1053           0 :                     break;
    1054           0 :                 case GDT_Float32:
    1055           0 :                     oFilter.Add("dtype", "<f4");
    1056           0 :                     break;
    1057           0 :                 case GDT_Float64:
    1058           0 :                     oFilter.Add("dtype", "<f8");
    1059           0 :                     break;
    1060           0 :                 case GDT_CInt16:
    1061           0 :                     oFilter.Add("dtype", "<i2");
    1062           0 :                     break;
    1063           0 :                 case GDT_CInt32:
    1064           0 :                     oFilter.Add("dtype", "<i4");
    1065           0 :                     break;
    1066           0 :                 case GDT_CFloat16:
    1067           0 :                     oFilter.Add("dtype", "<f2");
    1068           0 :                     break;
    1069           0 :                 case GDT_CFloat32:
    1070           0 :                     oFilter.Add("dtype", "<f4");
    1071           0 :                     break;
    1072           0 :                 case GDT_CFloat64:
    1073           0 :                     oFilter.Add("dtype", "<f8");
    1074           0 :                     break;
    1075           0 :                 case GDT_TypeCount:
    1076           0 :                     break;
    1077             :             }
    1078             :         }
    1079             :     }
    1080             : 
    1081             :     const std::string osZarrayDirectory =
    1082         628 :         CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
    1083         314 :     if (VSIMkdir(osZarrayDirectory.c_str(), 0755) != 0)
    1084             :     {
    1085             :         VSIStatBufL sStat;
    1086           2 :         if (VSIStatL(osZarrayDirectory.c_str(), &sStat) == 0)
    1087             :         {
    1088           2 :             CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
    1089             :                      osZarrayDirectory.c_str());
    1090             :         }
    1091             :         else
    1092             :         {
    1093           0 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
    1094             :                      osZarrayDirectory.c_str());
    1095             :         }
    1096           2 :         return nullptr;
    1097             :     }
    1098             : 
    1099         624 :     std::vector<GUInt64> anBlockSize;
    1100         312 :     if (!ZarrArray::FillBlockSize(aoDimensions, oDataType, anBlockSize,
    1101             :                                   papszOptions))
    1102           6 :         return nullptr;
    1103             : 
    1104         306 :     const bool bFortranOrder = EQUAL(
    1105             :         CSLFetchNameValueDef(papszOptions, "CHUNK_MEMORY_LAYOUT", "C"), "F");
    1106             : 
    1107             :     const char *pszDimSeparator =
    1108         306 :         CSLFetchNameValueDef(papszOptions, "DIM_SEPARATOR", ".");
    1109             : 
    1110         306 :     auto poArray = ZarrV2Array::Create(m_poSharedResource, GetFullName(),
    1111             :                                        osName, aoDimensions, oDataType,
    1112         612 :                                        aoDtypeElts, anBlockSize, bFortranOrder);
    1113             : 
    1114         306 :     if (!poArray)
    1115           0 :         return nullptr;
    1116             :     const std::string osZarrayFilename =
    1117         612 :         CPLFormFilenameSafe(osZarrayDirectory.c_str(), ".zarray", nullptr);
    1118         306 :     poArray->SetNew(true);
    1119         306 :     poArray->SetFilename(osZarrayFilename);
    1120         306 :     poArray->SetDimSeparator(pszDimSeparator);
    1121         306 :     poArray->SetDtype(dtype);
    1122         306 :     poArray->SetCompressorDecompressor(pszCompressor, psCompressor,
    1123             :                                        psDecompressor);
    1124         306 :     if (oCompressor.IsValid())
    1125          17 :         poArray->SetCompressorJson(oCompressor);
    1126         306 :     poArray->SetFilters(oFilters);
    1127         306 :     poArray->SetUpdatable(true);
    1128         306 :     poArray->SetDefinitionModified(true);
    1129         306 :     poArray->Flush();
    1130         306 :     RegisterArray(poArray);
    1131             : 
    1132         306 :     return poArray;
    1133             : }

Generated by: LCOV version 1.14