LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v2_group.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 493 568 86.8 %
Date: 2024-05-13 13:33:37 Functions: 16 16 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Zarr driver
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * Permission is hereby granted, free of charge, to any person obtaining a
      11             :  * copy of this software and associated documentation files (the "Software"),
      12             :  * to deal in the Software without restriction, including without limitation
      13             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      14             :  * and/or sell copies of the Software, and to permit persons to whom the
      15             :  * Software is furnished to do so, subject to the following conditions:
      16             :  *
      17             :  * The above copyright notice and this permission notice shall be included
      18             :  * in all copies or substantial portions of the Software.
      19             :  *
      20             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      21             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      22             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      23             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      24             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      25             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      26             :  * DEALINGS IN THE SOFTWARE.
      27             :  ****************************************************************************/
      28             : 
      29             : #include "zarr.h"
      30             : 
      31             : #include "cpl_minixml.h"
      32             : 
      33             : #include <algorithm>
      34             : #include <cassert>
      35             : #include <limits>
      36             : #include <map>
      37             : #include <set>
      38             : 
      39             : /************************************************************************/
      40             : /*                      ZarrV2Group::Create()                           */
      41             : /************************************************************************/
      42             : 
      43             : std::shared_ptr<ZarrV2Group>
      44        1015 : ZarrV2Group::Create(const std::shared_ptr<ZarrSharedResource> &poSharedResource,
      45             :                     const std::string &osParentName, const std::string &osName)
      46             : {
      47             :     auto poGroup = std::shared_ptr<ZarrV2Group>(
      48        1015 :         new ZarrV2Group(poSharedResource, osParentName, osName));
      49        1015 :     poGroup->SetSelf(poGroup);
      50        1015 :     return poGroup;
      51             : }
      52             : 
      53             : /************************************************************************/
      54             : /*                      ZarrV2Group::~ZarrV2Group()                     */
      55             : /************************************************************************/
      56             : 
      57        2030 : ZarrV2Group::~ZarrV2Group()
      58             : {
      59        1015 :     if (m_bValid && m_oAttrGroup.IsModified())
      60             :     {
      61          54 :         CPLJSONDocument oDoc;
      62          27 :         oDoc.SetRoot(m_oAttrGroup.Serialize());
      63             :         const std::string osAttrFilename =
      64          27 :             CPLFormFilename(m_osDirectoryName.c_str(), ".zattrs", nullptr);
      65          27 :         oDoc.Save(osAttrFilename);
      66          27 :         m_poSharedResource->SetZMetadataItem(osAttrFilename, oDoc.GetRoot());
      67             :     }
      68        2030 : }
      69             : 
      70             : /************************************************************************/
      71             : /*                        ExploreDirectory()                            */
      72             : /************************************************************************/
      73             : 
      74         227 : void ZarrV2Group::ExploreDirectory() const
      75             : {
      76         227 :     if (m_bDirectoryExplored || m_osDirectoryName.empty())
      77         185 :         return;
      78         227 :     m_bDirectoryExplored = true;
      79             : 
      80         227 :     const CPLStringList aosFiles(VSIReadDir(m_osDirectoryName.c_str()));
      81             :     // If the directory contains a .zarray, no need to recurse.
      82         387 :     for (int i = 0; i < aosFiles.size(); ++i)
      83             :     {
      84         345 :         if (strcmp(aosFiles[i], ".zarray") == 0)
      85         185 :             return;
      86             :     }
      87             : 
      88         202 :     for (int i = 0; i < aosFiles.size(); ++i)
      89             :     {
      90         320 :         if (aosFiles[i][0] != 0 && strcmp(aosFiles[i], ".") != 0 &&
      91         145 :             strcmp(aosFiles[i], "..") != 0 &&
      92         130 :             strcmp(aosFiles[i], ".zgroup") != 0 &&
      93         402 :             strcmp(aosFiles[i], ".zattrs") != 0 &&
      94             :             // Exclude filenames ending with '/'. This can happen on some
      95             :             // object storage like S3 where a "foo" file and a "foo/" directory
      96             :             // can coexist. The ending slash is only appended in that situation
      97             :             // where both a file and directory have the same name. So we can
      98             :             // safely ignore the one with an ending slash, as we will also
      99             :             // encounter its version without slash. Cf use case of
     100             :             // https://github.com/OSGeo/gdal/issues/8192
     101          82 :             aosFiles[i][strlen(aosFiles[i]) - 1] != '/')
     102             :         {
     103             :             const std::string osSubDir = CPLFormFilename(
     104         164 :                 m_osDirectoryName.c_str(), aosFiles[i], nullptr);
     105             :             VSIStatBufL sStat;
     106             :             std::string osFilename =
     107         164 :                 CPLFormFilename(osSubDir.c_str(), ".zarray", nullptr);
     108          82 :             if (VSIStatL(osFilename.c_str(), &sStat) == 0)
     109             :             {
     110          45 :                 if (std::find(m_aosArrays.begin(), m_aosArrays.end(),
     111          45 :                               aosFiles[i]) == m_aosArrays.end())
     112             :                 {
     113          35 :                     m_aosArrays.emplace_back(aosFiles[i]);
     114             :                 }
     115             :             }
     116             :             else
     117             :             {
     118             :                 osFilename =
     119          37 :                     CPLFormFilename(osSubDir.c_str(), ".zgroup", nullptr);
     120          37 :                 if (VSIStatL(osFilename.c_str(), &sStat) == 0)
     121          31 :                     m_aosGroups.emplace_back(aosFiles[i]);
     122             :             }
     123             :         }
     124             :     }
     125             : }
     126             : 
     127             : /************************************************************************/
     128             : /*                             OpenZarrArray()                          */
     129             : /************************************************************************/
     130             : 
     131         634 : std::shared_ptr<ZarrArray> ZarrV2Group::OpenZarrArray(const std::string &osName,
     132             :                                                       CSLConstList) const
     133             : {
     134         634 :     if (!CheckValidAndErrorOutIfNot())
     135           0 :         return nullptr;
     136             : 
     137         634 :     auto oIter = m_oMapMDArrays.find(osName);
     138         634 :     if (oIter != m_oMapMDArrays.end())
     139         449 :         return oIter->second;
     140             : 
     141         185 :     if (!m_bReadFromZMetadata && !m_osDirectoryName.empty())
     142             :     {
     143             :         const std::string osSubDir =
     144         179 :             CPLFormFilename(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     145             :         VSIStatBufL sStat;
     146             :         const std::string osZarrayFilename =
     147         179 :             CPLFormFilename(osSubDir.c_str(), ".zarray", nullptr);
     148         179 :         if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
     149             :         {
     150         126 :             CPLJSONDocument oDoc;
     151          63 :             if (!oDoc.Load(osZarrayFilename))
     152           0 :                 return nullptr;
     153          63 :             const auto oRoot = oDoc.GetRoot();
     154             :             return LoadArray(osName, osZarrayFilename, oRoot, false,
     155         126 :                              CPLJSONObject());
     156             :         }
     157             :     }
     158             : 
     159         122 :     return nullptr;
     160             : }
     161             : 
     162             : /************************************************************************/
     163             : /*                              OpenZarrGroup()                             */
     164             : /************************************************************************/
     165             : 
     166             : std::shared_ptr<ZarrGroupBase>
     167         529 : ZarrV2Group::OpenZarrGroup(const std::string &osName, CSLConstList) const
     168             : {
     169         529 :     if (!CheckValidAndErrorOutIfNot())
     170           0 :         return nullptr;
     171             : 
     172         529 :     auto oIter = m_oMapGroups.find(osName);
     173         529 :     if (oIter != m_oMapGroups.end())
     174         174 :         return oIter->second;
     175             : 
     176         355 :     if (!m_bReadFromZMetadata && !m_osDirectoryName.empty())
     177             :     {
     178             :         const std::string osSubDir =
     179          46 :             CPLFormFilename(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     180             :         VSIStatBufL sStat;
     181             :         const std::string osZgroupFilename =
     182          46 :             CPLFormFilename(osSubDir.c_str(), ".zgroup", nullptr);
     183          46 :         if (VSIStatL(osZgroupFilename.c_str(), &sStat) == 0)
     184             :         {
     185          84 :             CPLJSONDocument oDoc;
     186          42 :             if (!oDoc.Load(osZgroupFilename))
     187           0 :                 return nullptr;
     188             : 
     189             :             auto poSubGroup =
     190          84 :                 ZarrV2Group::Create(m_poSharedResource, GetFullName(), osName);
     191          42 :             poSubGroup->m_poParent =
     192          84 :                 std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
     193          42 :             poSubGroup->SetUpdatable(m_bUpdatable);
     194          42 :             poSubGroup->SetDirectoryName(osSubDir);
     195          42 :             m_oMapGroups[osName] = poSubGroup;
     196             : 
     197             :             // Must be done after setting m_oMapGroups, to avoid infinite
     198             :             // recursion when opening NCZarr datasets with indexing variables
     199             :             // of dimensions
     200          42 :             poSubGroup->InitFromZGroup(oDoc.GetRoot());
     201             : 
     202          42 :             return poSubGroup;
     203             :         }
     204             :     }
     205             : 
     206         313 :     return nullptr;
     207             : }
     208             : 
     209             : /************************************************************************/
     210             : /*                   ZarrV2Group::LoadAttributes()                      */
     211             : /************************************************************************/
     212             : 
     213         103 : void ZarrV2Group::LoadAttributes() const
     214             : {
     215         103 :     if (m_bAttributesLoaded || m_osDirectoryName.empty())
     216          86 :         return;
     217          41 :     m_bAttributesLoaded = true;
     218             : 
     219          41 :     CPLJSONDocument oDoc;
     220             :     const std::string osZattrsFilename(
     221          41 :         CPLFormFilename(m_osDirectoryName.c_str(), ".zattrs", nullptr));
     222          41 :     CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
     223          41 :     if (!oDoc.Load(osZattrsFilename))
     224          24 :         return;
     225          34 :     auto oRoot = oDoc.GetRoot();
     226          17 :     m_oAttrGroup.Init(oRoot, m_bUpdatable);
     227             : }
     228             : 
     229             : /************************************************************************/
     230             : /*                   ZarrV2Group::GetOrCreateSubGroup()                 */
     231             : /************************************************************************/
     232             : 
     233             : std::shared_ptr<ZarrV2Group>
     234         115 : ZarrV2Group::GetOrCreateSubGroup(const std::string &osSubGroupFullname)
     235             : {
     236             :     auto poSubGroup = std::dynamic_pointer_cast<ZarrV2Group>(
     237         115 :         OpenGroupFromFullname(osSubGroupFullname));
     238         115 :     if (poSubGroup)
     239             :     {
     240          52 :         return poSubGroup;
     241             :     }
     242             : 
     243          63 :     const auto nLastSlashPos = osSubGroupFullname.rfind('/');
     244             :     auto poBelongingGroup =
     245             :         (nLastSlashPos == 0)
     246          63 :             ? this
     247          74 :             : GetOrCreateSubGroup(osSubGroupFullname.substr(0, nLastSlashPos))
     248          63 :                   .get();
     249             : 
     250             :     poSubGroup =
     251         126 :         ZarrV2Group::Create(m_poSharedResource, poBelongingGroup->GetFullName(),
     252         189 :                             osSubGroupFullname.substr(nLastSlashPos + 1));
     253         126 :     poSubGroup->m_poParent = std::dynamic_pointer_cast<ZarrGroupBase>(
     254         189 :         poBelongingGroup->m_pSelf.lock());
     255         126 :     poSubGroup->SetDirectoryName(
     256             :         CPLFormFilename(poBelongingGroup->m_osDirectoryName.c_str(),
     257          63 :                         poSubGroup->GetName().c_str(), nullptr));
     258          63 :     poSubGroup->m_bDirectoryExplored = true;
     259          63 :     poSubGroup->m_bAttributesLoaded = true;
     260          63 :     poSubGroup->m_bReadFromZMetadata = true;
     261          63 :     poSubGroup->SetUpdatable(m_bUpdatable);
     262             : 
     263          63 :     poBelongingGroup->m_oMapGroups[poSubGroup->GetName()] = poSubGroup;
     264          63 :     poBelongingGroup->m_aosGroups.emplace_back(poSubGroup->GetName());
     265          63 :     return poSubGroup;
     266             : }
     267             : 
     268             : /************************************************************************/
     269             : /*                   ZarrV2Group::InitFromZMetadata()                   */
     270             : /************************************************************************/
     271             : 
     272         165 : void ZarrV2Group::InitFromZMetadata(const CPLJSONObject &obj)
     273             : {
     274         165 :     m_bDirectoryExplored = true;
     275         165 :     m_bAttributesLoaded = true;
     276         165 :     m_bReadFromZMetadata = true;
     277             : 
     278         330 :     const auto metadata = obj["metadata"];
     279         165 :     if (metadata.GetType() != CPLJSONObject::Type::Object)
     280           0 :         return;
     281         330 :     const auto children = metadata.GetChildren();
     282         330 :     std::map<std::string, const CPLJSONObject *> oMapArrays;
     283             : 
     284             :     // First pass to create groups and collect arrays
     285         915 :     for (const auto &child : children)
     286             :     {
     287         750 :         const std::string osName(child.GetName());
     288         750 :         if (std::count(osName.begin(), osName.end(), '/') > 32)
     289             :         {
     290             :             // Avoid too deep recursion in GetOrCreateSubGroup()
     291           0 :             continue;
     292             :         }
     293         750 :         if (osName == ".zattrs")
     294             :         {
     295           4 :             m_oAttrGroup.Init(child, m_bUpdatable);
     296             :         }
     297        1327 :         else if (osName.size() > strlen("/.zgroup") &&
     298        1327 :                  osName.substr(osName.size() - strlen("/.zgroup")) ==
     299             :                      "/.zgroup")
     300             :         {
     301          63 :             GetOrCreateSubGroup(
     302         126 :                 "/" + osName.substr(0, osName.size() - strlen("/.zgroup")));
     303             :         }
     304        1201 :         else if (osName.size() > strlen("/.zarray") &&
     305        1201 :                  osName.substr(osName.size() - strlen("/.zarray")) ==
     306             :                      "/.zarray")
     307             :         {
     308             :             auto osArrayFullname =
     309         257 :                 osName.substr(0, osName.size() - strlen("/.zarray"));
     310         257 :             oMapArrays[osArrayFullname] = &child;
     311             :         }
     312             :     }
     313             : 
     314         257 :     const auto CreateArray = [this](const std::string &osArrayFullname,
     315             :                                     const CPLJSONObject &oArray,
     316          41 :                                     const CPLJSONObject &oAttributes)
     317             :     {
     318         257 :         const auto nLastSlashPos = osArrayFullname.rfind('/');
     319             :         auto poBelongingGroup =
     320             :             (nLastSlashPos == std::string::npos)
     321         257 :                 ? this
     322         298 :                 : GetOrCreateSubGroup("/" +
     323         298 :                                       osArrayFullname.substr(0, nLastSlashPos))
     324         257 :                       .get();
     325             :         const auto osArrayName =
     326             :             nLastSlashPos == std::string::npos
     327             :                 ? osArrayFullname
     328         514 :                 : osArrayFullname.substr(nLastSlashPos + 1);
     329             :         const std::string osZarrayFilename = CPLFormFilename(
     330             :             CPLFormFilename(poBelongingGroup->m_osDirectoryName.c_str(),
     331             :                             osArrayName.c_str(), nullptr),
     332         257 :             ".zarray", nullptr);
     333         257 :         poBelongingGroup->LoadArray(osArrayName, osZarrayFilename, oArray, true,
     334             :                                     oAttributes);
     335         257 :     };
     336             : 
     337             :     struct ArrayDesc
     338             :     {
     339             :         std::string osArrayFullname{};
     340             :         const CPLJSONObject *poArray = nullptr;
     341             :         const CPLJSONObject *poAttrs = nullptr;
     342             :     };
     343             : 
     344         330 :     std::vector<ArrayDesc> aoRegularArrays;
     345             : 
     346             :     // Second pass to read attributes and create arrays that are indexing
     347             :     // variable
     348         915 :     for (const auto &child : children)
     349             :     {
     350        1500 :         const std::string osName(child.GetName());
     351        1331 :         if (osName.size() > strlen("/.zattrs") &&
     352        1331 :             osName.substr(osName.size() - strlen("/.zattrs")) == "/.zattrs")
     353             :         {
     354             :             const auto osObjectFullnameNoLeadingSlash =
     355         522 :                 osName.substr(0, osName.size() - strlen("/.zattrs"));
     356             :             auto poSubGroup = std::dynamic_pointer_cast<ZarrV2Group>(
     357         783 :                 OpenGroupFromFullname('/' + osObjectFullnameNoLeadingSlash));
     358         261 :             if (poSubGroup)
     359             :             {
     360          18 :                 poSubGroup->m_oAttrGroup.Init(child, m_bUpdatable);
     361             :             }
     362             :             else
     363             :             {
     364         243 :                 auto oIter = oMapArrays.find(osObjectFullnameNoLeadingSlash);
     365         243 :                 if (oIter != oMapArrays.end())
     366             :                 {
     367             :                     const auto nLastSlashPos =
     368         242 :                         osObjectFullnameNoLeadingSlash.rfind('/');
     369             :                     const auto osArrayName =
     370             :                         (nLastSlashPos == std::string::npos)
     371             :                             ? osObjectFullnameNoLeadingSlash
     372             :                             : osObjectFullnameNoLeadingSlash.substr(
     373         484 :                                   nLastSlashPos + 1);
     374             :                     const auto arrayDimensions =
     375         726 :                         child["_ARRAY_DIMENSIONS"].ToArray();
     376         953 :                     if (arrayDimensions.IsValid() &&
     377         356 :                         arrayDimensions.Size() == 1 &&
     378         356 :                         arrayDimensions[0].ToString() == osArrayName)
     379             :                     {
     380          88 :                         CreateArray(osObjectFullnameNoLeadingSlash,
     381          88 :                                     *(oIter->second), child);
     382          88 :                         oMapArrays.erase(oIter);
     383             :                     }
     384             :                     else
     385             :                     {
     386         308 :                         ArrayDesc desc;
     387         154 :                         desc.osArrayFullname = osObjectFullnameNoLeadingSlash;
     388         154 :                         desc.poArray = oIter->second;
     389         154 :                         desc.poAttrs = &child;
     390         154 :                         aoRegularArrays.emplace_back(std::move(desc));
     391             :                     }
     392             :                 }
     393             :             }
     394             :         }
     395             :     }
     396             : 
     397             :     // Third pass to create non-indexing arrays with attributes
     398         319 :     for (const auto &desc : aoRegularArrays)
     399             :     {
     400         154 :         CreateArray(desc.osArrayFullname, *(desc.poArray), *(desc.poAttrs));
     401         154 :         oMapArrays.erase(desc.osArrayFullname);
     402             :     }
     403             : 
     404             :     // Fourth pass to create arrays without attributes
     405         180 :     for (const auto &kv : oMapArrays)
     406             :     {
     407          15 :         CreateArray(kv.first, *(kv.second), CPLJSONObject());
     408             :     }
     409             : }
     410             : 
     411             : /************************************************************************/
     412             : /*                   ZarrV2Group::InitFromZGroup()                      */
     413             : /************************************************************************/
     414             : 
     415         105 : bool ZarrV2Group::InitFromZGroup(const CPLJSONObject &obj)
     416             : {
     417             :     // Parse potential NCZarr (V2) extensions:
     418             :     // https://www.unidata.ucar.edu/software/netcdf/documentation/NUG/nczarr_head.html
     419         315 :     const auto nczarrGroup = obj["_NCZARR_GROUP"];
     420         105 :     if (nczarrGroup.GetType() == CPLJSONObject::Type::Object)
     421             :     {
     422          24 :         if (m_bUpdatable)
     423             :         {
     424           4 :             CPLError(CE_Failure, CPLE_NotSupported,
     425             :                      "Update of NCZarr datasets is not supported");
     426           4 :             return false;
     427             :         }
     428          20 :         m_bDirectoryExplored = true;
     429             : 
     430             :         // If not opening from the root of the dataset, walk up to it
     431          56 :         if (!obj["_NCZARR_SUPERBLOCK"].IsValid() &&
     432          36 :             m_poParent.lock() == nullptr)
     433             :         {
     434             :             const std::string osParentGroupFilename(CPLFormFilename(
     435          14 :                 CPLGetPath(m_osDirectoryName.c_str()), ".zgroup", 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           6 :                     poParent->SetDirectoryName(
     446             :                         CPLGetPath(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         101 :     return true;
     542             : }
     543             : 
     544             : /************************************************************************/
     545             : /*                   ZarrV2Group::CreateOnDisk()                        */
     546             : /************************************************************************/
     547             : 
     548         233 : 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         233 :     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         456 :         CPLFormFilename(osDirectoryName.c_str(), ".zgroup", nullptr));
     571         228 :     VSILFILE *fp = VSIFOpenL(osZgroupFilename.c_str(), "wb");
     572         228 :     if (!fp)
     573             :     {
     574           0 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot create file %s.",
     575             :                  osZgroupFilename.c_str());
     576           0 :         return nullptr;
     577             :     }
     578         228 :     VSIFPrintfL(fp, "{\n  \"zarr_format\": 2\n}\n");
     579         228 :     VSIFCloseL(fp);
     580             : 
     581         456 :     auto poGroup = ZarrV2Group::Create(poSharedResource, osParentName, osName);
     582         228 :     poGroup->SetDirectoryName(osDirectoryName);
     583         228 :     poGroup->SetUpdatable(true);
     584         228 :     poGroup->m_bDirectoryExplored = true;
     585             : 
     586         456 :     CPLJSONObject oObj;
     587         228 :     oObj.Add("zarr_format", 2);
     588         228 :     poSharedResource->SetZMetadataItem(osZgroupFilename, oObj);
     589             : 
     590         228 :     return poGroup;
     591             : }
     592             : 
     593             : /************************************************************************/
     594             : /*                      ZarrV2Group::CreateGroup()                      */
     595             : /************************************************************************/
     596             : 
     597             : std::shared_ptr<GDALGroup>
     598          75 : ZarrV2Group::CreateGroup(const std::string &osName,
     599             :                          CSLConstList /* papszOptions */)
     600             : {
     601          75 :     if (!CheckValidAndErrorOutIfNot())
     602           0 :         return nullptr;
     603             : 
     604          75 :     if (!m_bUpdatable)
     605             :     {
     606           2 :         CPLError(CE_Failure, CPLE_NotSupported,
     607             :                  "Dataset not open in update mode");
     608           2 :         return nullptr;
     609             :     }
     610          73 :     if (!IsValidObjectName(osName))
     611             :     {
     612          14 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid group name");
     613          14 :         return nullptr;
     614             :     }
     615             : 
     616          59 :     GetGroupNames();
     617             : 
     618          59 :     if (std::find(m_aosGroups.begin(), m_aosGroups.end(), osName) !=
     619         118 :         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         114 :         CPLFormFilename(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
     628          57 :     auto poGroup = CreateOnDisk(m_poSharedResource, GetFullName(), osName,
     629         114 :                                 osDirectoryName);
     630          57 :     if (!poGroup)
     631           2 :         return nullptr;
     632          55 :     poGroup->m_poParent =
     633         110 :         std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
     634          55 :     m_oMapGroups[osName] = poGroup;
     635          55 :     m_aosGroups.emplace_back(osName);
     636          55 :     return poGroup;
     637             : }
     638             : 
     639             : /************************************************************************/
     640             : /*                          FillDTypeElts()                             */
     641             : /************************************************************************/
     642             : 
     643         313 : static CPLJSONObject FillDTypeElts(const GDALExtendedDataType &oDataType,
     644             :                                    size_t nGDALStartOffset,
     645             :                                    std::vector<DtypeElt> &aoDtypeElts,
     646             :                                    bool bUseUnicode)
     647             : {
     648         313 :     CPLJSONObject dtype;
     649         313 :     const auto eClass = oDataType.GetClass();
     650             :     const size_t nNativeStartOffset =
     651         313 :         aoDtypeElts.empty()
     652         313 :             ? 0
     653           4 :             : aoDtypeElts.back().nativeOffset + aoDtypeElts.back().nativeSize;
     654         626 :     const std::string dummy("dummy");
     655             : 
     656         313 :     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             :                     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             :                     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         305 :         case GEDTC_NUMERIC:
     698             :         {
     699         305 :             const auto eDT = oDataType.GetNumericDataType();
     700         305 :             DtypeElt elt;
     701         305 :             bool bUnsupported = false;
     702         305 :             switch (eDT)
     703             :             {
     704         112 :                 case GDT_Byte:
     705             :                 {
     706         112 :                     elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
     707         112 :                     dtype.Set(dummy, "|u1");
     708         112 :                     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           7 :                 case GDT_Float32:
     753             :                 {
     754           7 :                     elt.nativeType = DtypeElt::NativeType::IEEEFP;
     755           7 :                     dtype.Set(dummy, "<f4");
     756           7 :                     break;
     757             :                 }
     758         127 :                 case GDT_Float64:
     759             :                 {
     760         127 :                     elt.nativeType = DtypeElt::NativeType::IEEEFP;
     761         127 :                     dtype.Set(dummy, "<f8");
     762         127 :                     break;
     763             :                 }
     764           8 :                 case GDT_Unknown:
     765             :                 case GDT_CInt16:
     766             :                 case GDT_CInt32:
     767             :                 {
     768           8 :                     bUnsupported = true;
     769           8 :                     break;
     770             :                 }
     771           4 :                 case GDT_CFloat32:
     772             :                 {
     773           4 :                     elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     774           4 :                     dtype.Set(dummy, "<c8");
     775           4 :                     break;
     776             :                 }
     777           5 :                 case GDT_CFloat64:
     778             :                 {
     779           5 :                     elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
     780           5 :                     dtype.Set(dummy, "<c16");
     781           5 :                     break;
     782             :                 }
     783           0 :                 case GDT_TypeCount:
     784             :                 {
     785             :                     static_assert(GDT_TypeCount == GDT_Int8 + 1,
     786             :                                   "GDT_TypeCount == GDT_Int8 + 1");
     787           0 :                     break;
     788             :                 }
     789             :             }
     790         305 :             if (bUnsupported)
     791             :             {
     792           8 :                 CPLError(CE_Failure, CPLE_NotSupported,
     793             :                          "Unsupported data type: %s", GDALGetDataTypeName(eDT));
     794           8 :                 dtype = CPLJSONObject();
     795           8 :                 dtype.Deinit();
     796           8 :                 return dtype;
     797             :             }
     798         297 :             elt.nativeOffset = nNativeStartOffset;
     799         297 :             elt.nativeSize = GDALGetDataTypeSizeBytes(eDT);
     800         297 :             elt.gdalOffset = nGDALStartOffset;
     801         297 :             elt.gdalSize = elt.nativeSize;
     802             : #ifdef CPL_MSB
     803             :             elt.needByteSwapping = elt.nativeSize > 1;
     804             : #endif
     805         297 :             aoDtypeElts.emplace_back(elt);
     806         297 :             break;
     807             :         }
     808             : 
     809           4 :         case GEDTC_COMPOUND:
     810             :         {
     811           4 :             const auto &comps = oDataType.GetComponents();
     812           4 :             CPLJSONArray array;
     813          10 :             for (const auto &comp : comps)
     814             :             {
     815           6 :                 CPLJSONArray subArray;
     816           6 :                 subArray.Add(comp->GetName());
     817             :                 const auto subdtype = FillDTypeElts(
     818           6 :                     comp->GetType(), nGDALStartOffset + comp->GetOffset(),
     819          12 :                     aoDtypeElts, bUseUnicode);
     820           6 :                 if (!subdtype.IsValid())
     821             :                 {
     822           0 :                     dtype = CPLJSONObject();
     823           0 :                     dtype.Deinit();
     824           0 :                     return dtype;
     825             :                 }
     826           6 :                 if (subdtype.GetType() == CPLJSONObject::Type::Object)
     827           4 :                     subArray.Add(subdtype["dummy"]);
     828             :                 else
     829           2 :                     subArray.Add(subdtype);
     830           6 :                 array.Add(subArray);
     831             :             }
     832           4 :             dtype = std::move(array);
     833           4 :             break;
     834             :         }
     835             :     }
     836         305 :     return dtype;
     837             : }
     838             : 
     839             : /************************************************************************/
     840             : /*                     ZarrV2Group::CreateMDArray()                     */
     841             : /************************************************************************/
     842             : 
     843         321 : std::shared_ptr<GDALMDArray> ZarrV2Group::CreateMDArray(
     844             :     const std::string &osName,
     845             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
     846             :     const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
     847             : {
     848         321 :     if (!CheckValidAndErrorOutIfNot())
     849           0 :         return nullptr;
     850             : 
     851         321 :     if (!m_bUpdatable)
     852             :     {
     853           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     854             :                  "Dataset not open in update mode");
     855           0 :         return nullptr;
     856             :     }
     857         321 :     if (!IsValidObjectName(osName))
     858             :     {
     859          14 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid array name");
     860          14 :         return nullptr;
     861             :     }
     862             : 
     863         614 :     std::vector<DtypeElt> aoDtypeElts;
     864             :     const bool bUseUnicode =
     865         307 :         EQUAL(CSLFetchNameValueDef(papszOptions, "STRING_FORMAT", "ASCII"),
     866             :               "UNICODE");
     867         614 :     const auto dtype = FillDTypeElts(oDataType, 0, aoDtypeElts, bUseUnicode);
     868         307 :     if (!dtype.IsValid() || aoDtypeElts.empty())
     869           8 :         return nullptr;
     870             : 
     871         299 :     GetMDArrayNames();
     872             : 
     873         299 :     if (std::find(m_aosArrays.begin(), m_aosArrays.end(), osName) !=
     874         598 :         m_aosArrays.end())
     875             :     {
     876           2 :         CPLError(CE_Failure, CPLE_AppDefined,
     877             :                  "An array with same name already exists");
     878           2 :         return nullptr;
     879             :     }
     880             : 
     881         594 :     CPLJSONObject oCompressor;
     882         297 :     oCompressor.Deinit();
     883             :     const char *pszCompressor =
     884         297 :         CSLFetchNameValueDef(papszOptions, "COMPRESS", "NONE");
     885         297 :     const CPLCompressor *psCompressor = nullptr;
     886         297 :     const CPLCompressor *psDecompressor = nullptr;
     887         297 :     if (!EQUAL(pszCompressor, "NONE"))
     888             :     {
     889          17 :         psCompressor = CPLGetCompressor(pszCompressor);
     890          17 :         psDecompressor = CPLGetCompressor(pszCompressor);
     891          17 :         if (psCompressor == nullptr || psDecompressor == nullptr)
     892             :         {
     893           1 :             CPLError(CE_Failure, CPLE_NotSupported,
     894             :                      "Compressor/decompressor for %s not available",
     895             :                      pszCompressor);
     896           1 :             return nullptr;
     897             :         }
     898             :         const char *pszOptions =
     899          16 :             CSLFetchNameValue(psCompressor->papszMetadata, "OPTIONS");
     900          16 :         if (pszOptions)
     901             :         {
     902          32 :             CPLXMLTreeCloser oTree(CPLParseXMLString(pszOptions));
     903             :             const auto psRoot =
     904          16 :                 oTree.get() ? CPLGetXMLNode(oTree.get(), "=Options") : nullptr;
     905          16 :             if (psRoot)
     906             :             {
     907          16 :                 for (const CPLXMLNode *psNode = psRoot->psChild;
     908          37 :                      psNode != nullptr; psNode = psNode->psNext)
     909             :                 {
     910          21 :                     if (psNode->eType == CXT_Element &&
     911          21 :                         strcmp(psNode->pszValue, "Option") == 0)
     912             :                     {
     913             :                         const char *pszName =
     914          21 :                             CPLGetXMLValue(psNode, "name", nullptr);
     915             :                         const char *pszType =
     916          21 :                             CPLGetXMLValue(psNode, "type", nullptr);
     917          21 :                         if (pszName && pszType)
     918             :                         {
     919          42 :                             const char *pszVal = CSLFetchNameValueDef(
     920             :                                 papszOptions,
     921          42 :                                 (std::string(pszCompressor) + '_' + pszName)
     922             :                                     .c_str(),
     923             :                                 CPLGetXMLValue(psNode, "default", nullptr));
     924          21 :                             if (pszVal)
     925             :                             {
     926          21 :                                 if (EQUAL(pszName, "SHUFFLE") &&
     927           1 :                                     EQUAL(pszVal, "BYTE"))
     928             :                                 {
     929           1 :                                     pszVal = "1";
     930           1 :                                     pszType = "integer";
     931             :                                 }
     932             : 
     933          21 :                                 if (!oCompressor.IsValid())
     934             :                                 {
     935          16 :                                     oCompressor = CPLJSONObject();
     936          16 :                                     oCompressor.Add(
     937             :                                         "id",
     938          32 :                                         CPLString(pszCompressor).tolower());
     939             :                                 }
     940             : 
     941             :                                 std::string osOptName(
     942          42 :                                     CPLString(pszName).tolower());
     943          21 :                                 if (STARTS_WITH(pszType, "int"))
     944          19 :                                     oCompressor.Add(osOptName, atoi(pszVal));
     945             :                                 else
     946           2 :                                     oCompressor.Add(osOptName, pszVal);
     947             :                             }
     948             :                         }
     949             :                     }
     950             :                 }
     951             :             }
     952             :         }
     953             :     }
     954             : 
     955         592 :     CPLJSONArray oFilters;
     956             :     const char *pszFilter =
     957         296 :         CSLFetchNameValueDef(papszOptions, "FILTER", "NONE");
     958         296 :     if (!EQUAL(pszFilter, "NONE"))
     959             :     {
     960           1 :         const auto psFilterCompressor = CPLGetCompressor(pszFilter);
     961           1 :         const auto psFilterDecompressor = CPLGetCompressor(pszFilter);
     962           1 :         if (psFilterCompressor == nullptr || psFilterDecompressor == nullptr)
     963             :         {
     964           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     965             :                      "Compressor/decompressor for filter %s not available",
     966             :                      pszFilter);
     967           0 :             return nullptr;
     968             :         }
     969             : 
     970           1 :         CPLJSONObject oFilter;
     971           1 :         oFilter.Add("id", CPLString(pszFilter).tolower());
     972           1 :         oFilters.Add(oFilter);
     973             : 
     974             :         const char *pszOptions =
     975           1 :             CSLFetchNameValue(psFilterCompressor->papszMetadata, "OPTIONS");
     976           1 :         if (pszOptions)
     977             :         {
     978           2 :             CPLXMLTreeCloser oTree(CPLParseXMLString(pszOptions));
     979             :             const auto psRoot =
     980           1 :                 oTree.get() ? CPLGetXMLNode(oTree.get(), "=Options") : nullptr;
     981           1 :             if (psRoot)
     982             :             {
     983           1 :                 for (const CPLXMLNode *psNode = psRoot->psChild;
     984           2 :                      psNode != nullptr; psNode = psNode->psNext)
     985             :                 {
     986           1 :                     if (psNode->eType == CXT_Element &&
     987           1 :                         strcmp(psNode->pszValue, "Option") == 0)
     988             :                     {
     989             :                         const char *pszName =
     990           1 :                             CPLGetXMLValue(psNode, "name", nullptr);
     991             :                         const char *pszType =
     992           1 :                             CPLGetXMLValue(psNode, "type", nullptr);
     993           1 :                         if (pszName && pszType)
     994             :                         {
     995           2 :                             const char *pszVal = CSLFetchNameValueDef(
     996             :                                 papszOptions,
     997           2 :                                 (std::string(pszFilter) + '_' + pszName)
     998             :                                     .c_str(),
     999             :                                 CPLGetXMLValue(psNode, "default", nullptr));
    1000           1 :                             if (pszVal)
    1001             :                             {
    1002             :                                 std::string osOptName(
    1003           0 :                                     CPLString(pszName).tolower());
    1004           0 :                                 if (STARTS_WITH(pszType, "int"))
    1005           0 :                                     oFilter.Add(osOptName, atoi(pszVal));
    1006             :                                 else
    1007           0 :                                     oFilter.Add(osOptName, pszVal);
    1008             :                             }
    1009             :                         }
    1010             :                     }
    1011             :                 }
    1012             :             }
    1013             :         }
    1014             : 
    1015           2 :         if (EQUAL(pszFilter, "delta") &&
    1016           1 :             CSLFetchNameValue(papszOptions, "DELTA_DTYPE") == nullptr)
    1017             :         {
    1018           1 :             if (oDataType.GetClass() != GEDTC_NUMERIC)
    1019             :             {
    1020           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
    1021             :                          "DELTA_DTYPE option must be specified");
    1022           0 :                 return nullptr;
    1023             :             }
    1024           1 :             switch (oDataType.GetNumericDataType())
    1025             :             {
    1026           0 :                 case GDT_Unknown:
    1027           0 :                     break;
    1028           0 :                 case GDT_Byte:
    1029           0 :                     oFilter.Add("dtype", "u1");
    1030           0 :                     break;
    1031           0 :                 case GDT_Int8:
    1032           0 :                     oFilter.Add("dtype", "i1");
    1033           0 :                     break;
    1034           1 :                 case GDT_UInt16:
    1035           1 :                     oFilter.Add("dtype", "<u2");
    1036           1 :                     break;
    1037           0 :                 case GDT_Int16:
    1038           0 :                     oFilter.Add("dtype", "<i2");
    1039           0 :                     break;
    1040           0 :                 case GDT_UInt32:
    1041           0 :                     oFilter.Add("dtype", "<u4");
    1042           0 :                     break;
    1043           0 :                 case GDT_Int32:
    1044           0 :                     oFilter.Add("dtype", "<i4");
    1045           0 :                     break;
    1046           0 :                 case GDT_UInt64:
    1047           0 :                     oFilter.Add("dtype", "<u8");
    1048           0 :                     break;
    1049           0 :                 case GDT_Int64:
    1050           0 :                     oFilter.Add("dtype", "<i8");
    1051           0 :                     break;
    1052           0 :                 case GDT_Float32:
    1053           0 :                     oFilter.Add("dtype", "<f4");
    1054           0 :                     break;
    1055           0 :                 case GDT_Float64:
    1056           0 :                     oFilter.Add("dtype", "<f8");
    1057           0 :                     break;
    1058           0 :                 case GDT_CInt16:
    1059           0 :                     oFilter.Add("dtype", "<i2");
    1060           0 :                     break;
    1061           0 :                 case GDT_CInt32:
    1062           0 :                     oFilter.Add("dtype", "<i4");
    1063           0 :                     break;
    1064           0 :                 case GDT_CFloat32:
    1065           0 :                     oFilter.Add("dtype", "<f4");
    1066           0 :                     break;
    1067           0 :                 case GDT_CFloat64:
    1068           0 :                     oFilter.Add("dtype", "<f8");
    1069           0 :                     break;
    1070           0 :                 case GDT_TypeCount:
    1071           0 :                     break;
    1072             :             }
    1073             :         }
    1074             :     }
    1075             : 
    1076             :     const std::string osZarrayDirectory =
    1077         592 :         CPLFormFilename(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
    1078         296 :     if (VSIMkdir(osZarrayDirectory.c_str(), 0755) != 0)
    1079             :     {
    1080             :         VSIStatBufL sStat;
    1081           2 :         if (VSIStatL(osZarrayDirectory.c_str(), &sStat) == 0)
    1082             :         {
    1083           2 :             CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
    1084             :                      osZarrayDirectory.c_str());
    1085             :         }
    1086             :         else
    1087             :         {
    1088           0 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
    1089             :                      osZarrayDirectory.c_str());
    1090             :         }
    1091           2 :         return nullptr;
    1092             :     }
    1093             : 
    1094         588 :     std::vector<GUInt64> anBlockSize;
    1095         294 :     if (!ZarrArray::FillBlockSize(aoDimensions, oDataType, anBlockSize,
    1096             :                                   papszOptions))
    1097           3 :         return nullptr;
    1098             : 
    1099         291 :     const bool bFortranOrder = EQUAL(
    1100             :         CSLFetchNameValueDef(papszOptions, "CHUNK_MEMORY_LAYOUT", "C"), "F");
    1101             : 
    1102             :     const char *pszDimSeparator =
    1103         291 :         CSLFetchNameValueDef(papszOptions, "DIM_SEPARATOR", ".");
    1104             : 
    1105         291 :     auto poArray = ZarrV2Array::Create(m_poSharedResource, GetFullName(),
    1106             :                                        osName, aoDimensions, oDataType,
    1107         582 :                                        aoDtypeElts, anBlockSize, bFortranOrder);
    1108             : 
    1109         291 :     if (!poArray)
    1110           0 :         return nullptr;
    1111             :     const std::string osZarrayFilename =
    1112         582 :         CPLFormFilename(osZarrayDirectory.c_str(), ".zarray", nullptr);
    1113         291 :     poArray->SetNew(true);
    1114         291 :     poArray->SetFilename(osZarrayFilename);
    1115         291 :     poArray->SetDimSeparator(pszDimSeparator);
    1116         291 :     poArray->SetDtype(dtype);
    1117         291 :     poArray->SetCompressorDecompressor(pszCompressor, psCompressor,
    1118             :                                        psDecompressor);
    1119         291 :     if (oCompressor.IsValid())
    1120          16 :         poArray->SetCompressorJson(oCompressor);
    1121         291 :     poArray->SetFilters(oFilters);
    1122         291 :     poArray->SetUpdatable(true);
    1123         291 :     poArray->SetDefinitionModified(true);
    1124         291 :     poArray->Flush();
    1125         291 :     RegisterArray(poArray);
    1126             : 
    1127         291 :     return poArray;
    1128             : }

Generated by: LCOV version 1.14