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