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

Generated by: LCOV version 1.14