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