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

Generated by: LCOV version 1.14