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 1117 : 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 1117 : new ZarrV2Group(poSharedResource, osParentName, osName));
33 1116 : poGroup->SetSelf(poGroup);
34 1117 : return poGroup;
35 : }
36 :
37 : /************************************************************************/
38 : /* ZarrV2Group::~ZarrV2Group() */
39 : /************************************************************************/
40 :
41 2234 : ZarrV2Group::~ZarrV2Group()
42 : {
43 1117 : ZarrV2Group::Close();
44 2234 : }
45 :
46 : /************************************************************************/
47 : /* Close() */
48 : /************************************************************************/
49 :
50 2334 : bool ZarrV2Group::Close()
51 : {
52 2334 : bool bRet = ZarrGroupBase::Close();
53 :
54 2334 : 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 2334 : return bRet;
65 : }
66 :
67 : /************************************************************************/
68 : /* ExploreDirectory() */
69 : /************************************************************************/
70 :
71 252 : void ZarrV2Group::ExploreDirectory() const
72 : {
73 252 : if (m_bDirectoryExplored || m_osDirectoryName.empty())
74 201 : return;
75 252 : m_bDirectoryExplored = true;
76 :
77 252 : const CPLStringList aosFiles(VSIReadDir(m_osDirectoryName.c_str()));
78 : // If the directory contains a .zarray, no need to recurse.
79 520 : for (int i = 0; i < aosFiles.size(); ++i)
80 : {
81 469 : if (strcmp(aosFiles[i], ".zarray") == 0)
82 201 : return;
83 : }
84 :
85 253 : for (int i = 0; i < aosFiles.size(); ++i)
86 : {
87 404 : if (aosFiles[i][0] != 0 && strcmp(aosFiles[i], ".") != 0 &&
88 178 : strcmp(aosFiles[i], "..") != 0 &&
89 154 : strcmp(aosFiles[i], ".zgroup") != 0 &&
90 499 : 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 95 : aosFiles[i][strlen(aosFiles[i]) - 1] != '/')
99 : {
100 : const std::string osSubDir = CPLFormFilenameSafe(
101 190 : m_osDirectoryName.c_str(), aosFiles[i], nullptr);
102 : VSIStatBufL sStat;
103 : std::string osFilename =
104 190 : CPLFormFilenameSafe(osSubDir.c_str(), ".zarray", nullptr);
105 95 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
106 : {
107 56 : if (!cpl::contains(m_oSetArrayNames, aosFiles[i]))
108 : {
109 46 : m_oSetArrayNames.insert(aosFiles[i]);
110 46 : 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 851 : std::shared_ptr<ZarrArray> ZarrV2Group::OpenZarrArray(const std::string &osName,
133 : CSLConstList) const
134 : {
135 851 : if (!CheckValidAndErrorOutIfNot())
136 0 : return nullptr;
137 :
138 851 : auto oIter = m_oMapMDArrays.find(osName);
139 851 : if (oIter != m_oMapMDArrays.end())
140 622 : return oIter->second;
141 :
142 229 : if (!m_bReadFromZMetadata && !m_osDirectoryName.empty())
143 : {
144 : const std::string osSubDir = CPLFormFilenameSafe(
145 223 : m_osDirectoryName.c_str(), osName.c_str(), nullptr);
146 : VSIStatBufL sStat;
147 : const std::string osZarrayFilename =
148 223 : CPLFormFilenameSafe(osSubDir.c_str(), ".zarray", nullptr);
149 223 : if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
150 : {
151 194 : CPLJSONDocument oDoc;
152 97 : if (!oDoc.Load(osZarrayFilename))
153 0 : return nullptr;
154 97 : const auto oRoot = oDoc.GetRoot();
155 : return LoadArray(osName, osZarrayFilename, oRoot, false,
156 194 : CPLJSONObject());
157 : }
158 : }
159 :
160 132 : return nullptr;
161 : }
162 :
163 : /************************************************************************/
164 : /* OpenZarrGroup() */
165 : /************************************************************************/
166 :
167 : std::shared_ptr<ZarrGroupBase>
168 586 : ZarrV2Group::OpenZarrGroup(const std::string &osName, CSLConstList) const
169 : {
170 586 : if (!CheckValidAndErrorOutIfNot())
171 0 : return nullptr;
172 :
173 586 : auto oIter = m_oMapGroups.find(osName);
174 586 : if (oIter != m_oMapGroups.end())
175 189 : return oIter->second;
176 :
177 397 : if (!m_bReadFromZMetadata && !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 353 : return nullptr;
208 : }
209 :
210 : /************************************************************************/
211 : /* ZarrV2Group::LoadAttributes() */
212 : /************************************************************************/
213 :
214 125 : void ZarrV2Group::LoadAttributes() const
215 : {
216 125 : if (m_bAttributesLoaded || m_osDirectoryName.empty())
217 108 : 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 119 : ZarrV2Group::GetOrCreateSubGroup(const std::string &osSubGroupFullname)
236 : {
237 : auto poSubGroup = std::dynamic_pointer_cast<ZarrV2Group>(
238 119 : OpenGroupFromFullname(osSubGroupFullname));
239 119 : if (poSubGroup)
240 : {
241 54 : return poSubGroup;
242 : }
243 :
244 65 : const auto nLastSlashPos = osSubGroupFullname.rfind('/');
245 : auto poBelongingGroup =
246 : (nLastSlashPos == 0)
247 65 : ? this
248 77 : : GetOrCreateSubGroup(osSubGroupFullname.substr(0, nLastSlashPos))
249 65 : .get();
250 :
251 : poSubGroup =
252 130 : ZarrV2Group::Create(m_poSharedResource, poBelongingGroup->GetFullName(),
253 195 : osSubGroupFullname.substr(nLastSlashPos + 1));
254 130 : poSubGroup->m_poParent = std::dynamic_pointer_cast<ZarrGroupBase>(
255 195 : poBelongingGroup->m_pSelf.lock());
256 130 : poSubGroup->SetDirectoryName(
257 130 : CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
258 65 : poSubGroup->GetName().c_str(), nullptr));
259 65 : poSubGroup->m_bDirectoryExplored = true;
260 65 : poSubGroup->m_bAttributesLoaded = true;
261 65 : poSubGroup->m_bReadFromZMetadata = true;
262 65 : poSubGroup->SetUpdatable(m_bUpdatable);
263 :
264 65 : poBelongingGroup->m_oMapGroups[poSubGroup->GetName()] = poSubGroup;
265 65 : poBelongingGroup->m_oSetGroupNames.insert(poSubGroup->GetName());
266 65 : poBelongingGroup->m_aosGroups.emplace_back(poSubGroup->GetName());
267 65 : return poSubGroup;
268 : }
269 :
270 : /************************************************************************/
271 : /* ZarrV2Group::InitFromZMetadata() */
272 : /************************************************************************/
273 :
274 176 : void ZarrV2Group::InitFromZMetadata(const CPLJSONObject &obj)
275 : {
276 176 : m_bDirectoryExplored = true;
277 176 : m_bAttributesLoaded = true;
278 176 : m_bReadFromZMetadata = true;
279 :
280 352 : const auto metadata = obj["metadata"];
281 176 : if (metadata.GetType() != CPLJSONObject::Type::Object)
282 0 : return;
283 352 : const auto children = metadata.GetChildren();
284 352 : std::map<std::string, const CPLJSONObject *> oMapArrays;
285 :
286 : // First pass to create groups and collect arrays
287 1016 : for (const auto &child : children)
288 : {
289 840 : const std::string osName(child.GetName());
290 840 : if (std::count(osName.begin(), osName.end(), '/') > 32)
291 : {
292 : // Avoid too deep recursion in GetOrCreateSubGroup()
293 0 : continue;
294 : }
295 840 : if (osName == ".zattrs")
296 : {
297 5 : m_oAttrGroup.Init(child, m_bUpdatable);
298 : }
299 1494 : else if (osName.size() > strlen("/.zgroup") &&
300 1494 : osName.substr(osName.size() - strlen("/.zgroup")) ==
301 : "/.zgroup")
302 : {
303 65 : GetOrCreateSubGroup(
304 130 : "/" + osName.substr(0, osName.size() - strlen("/.zgroup")));
305 : }
306 1364 : else if (osName.size() > strlen("/.zarray") &&
307 1364 : osName.substr(osName.size() - strlen("/.zarray")) ==
308 : "/.zarray")
309 : {
310 : auto osArrayFullname =
311 294 : osName.substr(0, osName.size() - strlen("/.zarray"));
312 294 : oMapArrays[osArrayFullname] = &child;
313 : }
314 : }
315 :
316 294 : const auto CreateArray = [this](const std::string &osArrayFullname,
317 : const CPLJSONObject &oArray,
318 42 : const CPLJSONObject &oAttributes)
319 : {
320 294 : const auto nLastSlashPos = osArrayFullname.rfind('/');
321 : auto poBelongingGroup =
322 : (nLastSlashPos == std::string::npos)
323 294 : ? this
324 336 : : GetOrCreateSubGroup("/" +
325 336 : osArrayFullname.substr(0, nLastSlashPos))
326 294 : .get();
327 : const auto osArrayName =
328 : nLastSlashPos == std::string::npos
329 : ? osArrayFullname
330 588 : : osArrayFullname.substr(nLastSlashPos + 1);
331 : const std::string osZarrayFilename = CPLFormFilenameSafe(
332 294 : CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
333 : osArrayName.c_str(), nullptr)
334 : .c_str(),
335 294 : ".zarray", nullptr);
336 294 : poBelongingGroup->LoadArray(osArrayName, osZarrayFilename, oArray, true,
337 : oAttributes);
338 294 : };
339 :
340 : struct ArrayDesc
341 : {
342 : std::string osArrayFullname{};
343 : const CPLJSONObject *poArray = nullptr;
344 : const CPLJSONObject *poAttrs = nullptr;
345 : };
346 :
347 352 : std::vector<ArrayDesc> aoRegularArrays;
348 :
349 : // Second pass to read attributes and create arrays that are indexing
350 : // variable
351 1016 : for (const auto &child : children)
352 : {
353 1680 : const std::string osName(child.GetName());
354 1499 : if (osName.size() > strlen("/.zattrs") &&
355 1499 : osName.substr(osName.size() - strlen("/.zattrs")) == "/.zattrs")
356 : {
357 : std::string osObjectFullnameNoLeadingSlash =
358 600 : osName.substr(0, osName.size() - strlen("/.zattrs"));
359 : auto poSubGroup = std::dynamic_pointer_cast<ZarrV2Group>(
360 900 : OpenGroupFromFullname('/' + osObjectFullnameNoLeadingSlash));
361 300 : if (poSubGroup)
362 : {
363 19 : poSubGroup->m_oAttrGroup.Init(child, m_bUpdatable);
364 : }
365 : else
366 : {
367 281 : auto oIter = oMapArrays.find(osObjectFullnameNoLeadingSlash);
368 281 : if (oIter != oMapArrays.end())
369 : {
370 : const auto nLastSlashPos =
371 279 : osObjectFullnameNoLeadingSlash.rfind('/');
372 : const std::string osArrayName =
373 : (nLastSlashPos == std::string::npos)
374 : ? osObjectFullnameNoLeadingSlash
375 : : osObjectFullnameNoLeadingSlash.substr(
376 558 : nLastSlashPos + 1);
377 : const auto arrayDimensions =
378 837 : child["_ARRAY_DIMENSIONS"].ToArray();
379 1100 : if (arrayDimensions.IsValid() &&
380 416 : arrayDimensions.Size() == 1 &&
381 416 : arrayDimensions[0].ToString() == osArrayName)
382 : {
383 108 : CreateArray(osObjectFullnameNoLeadingSlash,
384 108 : *(oIter->second), child);
385 108 : oMapArrays.erase(oIter);
386 : }
387 : else
388 : {
389 342 : ArrayDesc desc;
390 : desc.osArrayFullname =
391 171 : std::move(osObjectFullnameNoLeadingSlash);
392 171 : desc.poArray = oIter->second;
393 171 : desc.poAttrs = &child;
394 171 : aoRegularArrays.emplace_back(std::move(desc));
395 : }
396 : }
397 : }
398 : }
399 : }
400 :
401 : // Third pass to create non-indexing arrays with attributes
402 347 : for (const auto &desc : aoRegularArrays)
403 : {
404 171 : CreateArray(desc.osArrayFullname, *(desc.poArray), *(desc.poAttrs));
405 171 : oMapArrays.erase(desc.osArrayFullname);
406 : }
407 :
408 : // Fourth pass to create arrays without attributes
409 191 : for (const auto &kv : oMapArrays)
410 : {
411 15 : CreateArray(kv.first, *(kv.second), CPLJSONObject());
412 : }
413 : }
414 :
415 : /************************************************************************/
416 : /* ZarrV2Group::InitFromZGroup() */
417 : /************************************************************************/
418 :
419 143 : 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 429 : const auto nczarrGroup = obj["_NCZARR_GROUP"];
424 143 : 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 139 : return true;
556 : }
557 :
558 : /************************************************************************/
559 : /* ZarrV2Group::CreateOnDisk() */
560 : /************************************************************************/
561 :
562 246 : 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 246 : 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 482 : CPLFormFilenameSafe(osDirectoryName.c_str(), ".zgroup", nullptr));
585 241 : VSILFILE *fp = VSIFOpenL(osZgroupFilename.c_str(), "wb");
586 241 : if (!fp)
587 : {
588 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create file %s.",
589 : osZgroupFilename.c_str());
590 0 : return nullptr;
591 : }
592 241 : VSIFPrintfL(fp, "{\n \"zarr_format\": 2\n}\n");
593 241 : VSIFCloseL(fp);
594 :
595 482 : auto poGroup = ZarrV2Group::Create(poSharedResource, osParentName, osName);
596 241 : poGroup->SetDirectoryName(osDirectoryName);
597 241 : poGroup->SetUpdatable(true);
598 241 : poGroup->m_bDirectoryExplored = true;
599 :
600 482 : CPLJSONObject oObj;
601 241 : oObj.Add("zarr_format", 2);
602 241 : poSharedResource->SetZMetadataItem(osZgroupFilename, oObj);
603 :
604 241 : return poGroup;
605 : }
606 :
607 : /************************************************************************/
608 : /* ZarrV2Group::CreateGroup() */
609 : /************************************************************************/
610 :
611 : std::shared_ptr<GDALGroup>
612 76 : ZarrV2Group::CreateGroup(const std::string &osName,
613 : CSLConstList /* papszOptions */)
614 : {
615 76 : if (!CheckValidAndErrorOutIfNot())
616 0 : return nullptr;
617 :
618 76 : if (!m_bUpdatable)
619 : {
620 2 : CPLError(CE_Failure, CPLE_NotSupported,
621 : "Dataset not open in update mode");
622 2 : 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 already exists");
636 2 : return nullptr;
637 : }
638 :
639 : const std::string osDirectoryName =
640 116 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
641 58 : auto poGroup = CreateOnDisk(m_poSharedResource, GetFullName(), osName,
642 116 : osDirectoryName);
643 58 : if (!poGroup)
644 2 : return nullptr;
645 56 : poGroup->m_poParent =
646 112 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
647 56 : m_oMapGroups[osName] = poGroup;
648 56 : m_oSetGroupNames.insert(osName);
649 56 : m_aosGroups.emplace_back(osName);
650 56 : return poGroup;
651 : }
652 :
653 : /************************************************************************/
654 : /* FillDTypeElts() */
655 : /************************************************************************/
656 :
657 336 : static CPLJSONObject FillDTypeElts(const GDALExtendedDataType &oDataType,
658 : size_t nGDALStartOffset,
659 : std::vector<DtypeElt> &aoDtypeElts,
660 : bool bUseUnicode)
661 : {
662 336 : CPLJSONObject dtype;
663 336 : const auto eClass = oDataType.GetClass();
664 : const size_t nNativeStartOffset =
665 336 : aoDtypeElts.empty()
666 336 : ? 0
667 4 : : aoDtypeElts.back().nativeOffset + aoDtypeElts.back().nativeSize;
668 672 : const std::string dummy("dummy");
669 :
670 336 : switch (eClass)
671 : {
672 4 : case GEDTC_STRING:
673 : {
674 4 : if (oDataType.GetMaxStringLength() == 0)
675 : {
676 0 : CPLError(CE_Failure, CPLE_NotSupported,
677 : "String arrays of unlimited size are not supported");
678 0 : dtype = CPLJSONObject();
679 0 : dtype.Deinit();
680 0 : return dtype;
681 : }
682 8 : DtypeElt elt;
683 4 : elt.nativeOffset = nNativeStartOffset;
684 4 : if (bUseUnicode)
685 : {
686 1 : elt.nativeType = DtypeElt::NativeType::STRING_UNICODE;
687 1 : elt.nativeSize = oDataType.GetMaxStringLength() * 4;
688 : #ifdef CPL_MSB
689 : elt.needByteSwapping = true;
690 : #endif
691 1 : dtype.Set(
692 : dummy,
693 1 : CPLSPrintf("<U%d", static_cast<int>(
694 1 : oDataType.GetMaxStringLength())));
695 : }
696 : else
697 : {
698 3 : elt.nativeType = DtypeElt::NativeType::STRING_ASCII;
699 3 : elt.nativeSize = oDataType.GetMaxStringLength();
700 3 : dtype.Set(
701 : dummy,
702 3 : CPLSPrintf("|S%d", static_cast<int>(
703 3 : oDataType.GetMaxStringLength())));
704 : }
705 4 : elt.gdalOffset = nGDALStartOffset;
706 4 : elt.gdalSize = sizeof(char *);
707 4 : aoDtypeElts.emplace_back(elt);
708 4 : break;
709 : }
710 :
711 328 : case GEDTC_NUMERIC:
712 : {
713 328 : const auto eDT = oDataType.GetNumericDataType();
714 328 : DtypeElt elt;
715 328 : bool bUnsupported = false;
716 328 : switch (eDT)
717 : {
718 124 : case GDT_Byte:
719 : {
720 124 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
721 124 : dtype.Set(dummy, "|u1");
722 124 : break;
723 : }
724 3 : case GDT_Int8:
725 : {
726 3 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
727 3 : dtype.Set(dummy, "|i1");
728 3 : break;
729 : }
730 8 : case GDT_UInt16:
731 : {
732 8 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
733 8 : dtype.Set(dummy, "<u2");
734 8 : break;
735 : }
736 11 : case GDT_Int16:
737 : {
738 11 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
739 11 : dtype.Set(dummy, "<i2");
740 11 : break;
741 : }
742 6 : case GDT_UInt32:
743 : {
744 6 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
745 6 : dtype.Set(dummy, "<u4");
746 6 : break;
747 : }
748 7 : case GDT_Int32:
749 : {
750 7 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
751 7 : dtype.Set(dummy, "<i4");
752 7 : break;
753 : }
754 4 : case GDT_UInt64:
755 : {
756 4 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
757 4 : dtype.Set(dummy, "<u8");
758 4 : break;
759 : }
760 3 : case GDT_Int64:
761 : {
762 3 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
763 3 : dtype.Set(dummy, "<i8");
764 3 : break;
765 : }
766 1 : case GDT_Float16:
767 : {
768 1 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
769 1 : dtype.Set(dummy, "<f2");
770 1 : break;
771 : }
772 7 : case GDT_Float32:
773 : {
774 7 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
775 7 : dtype.Set(dummy, "<f4");
776 7 : break;
777 : }
778 137 : case GDT_Float64:
779 : {
780 137 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
781 137 : dtype.Set(dummy, "<f8");
782 137 : break;
783 : }
784 8 : case GDT_Unknown:
785 : case GDT_CInt16:
786 : case GDT_CInt32:
787 : {
788 8 : bUnsupported = true;
789 8 : break;
790 : }
791 0 : case GDT_CFloat16:
792 : {
793 0 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
794 0 : dtype.Set(dummy, "<c4");
795 0 : break;
796 : }
797 4 : case GDT_CFloat32:
798 : {
799 4 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
800 4 : dtype.Set(dummy, "<c8");
801 4 : break;
802 : }
803 5 : case GDT_CFloat64:
804 : {
805 5 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
806 5 : dtype.Set(dummy, "<c16");
807 5 : break;
808 : }
809 0 : case GDT_TypeCount:
810 : {
811 : static_assert(GDT_TypeCount == GDT_CFloat16 + 1,
812 : "GDT_TypeCount == GDT_CFloat16 + 1");
813 0 : break;
814 : }
815 : }
816 328 : if (bUnsupported)
817 : {
818 8 : CPLError(CE_Failure, CPLE_NotSupported,
819 : "Unsupported data type: %s", GDALGetDataTypeName(eDT));
820 8 : dtype = CPLJSONObject();
821 8 : dtype.Deinit();
822 8 : return dtype;
823 : }
824 320 : elt.nativeOffset = nNativeStartOffset;
825 320 : elt.nativeSize = GDALGetDataTypeSizeBytes(eDT);
826 320 : elt.gdalOffset = nGDALStartOffset;
827 320 : elt.gdalSize = elt.nativeSize;
828 : #ifdef CPL_MSB
829 : elt.needByteSwapping = elt.nativeSize > 1;
830 : #endif
831 320 : aoDtypeElts.emplace_back(elt);
832 320 : break;
833 : }
834 :
835 4 : case GEDTC_COMPOUND:
836 : {
837 4 : const auto &comps = oDataType.GetComponents();
838 4 : CPLJSONArray array;
839 10 : for (const auto &comp : comps)
840 : {
841 6 : CPLJSONArray subArray;
842 6 : subArray.Add(comp->GetName());
843 : const auto subdtype = FillDTypeElts(
844 6 : comp->GetType(), nGDALStartOffset + comp->GetOffset(),
845 12 : aoDtypeElts, bUseUnicode);
846 6 : if (!subdtype.IsValid())
847 : {
848 0 : dtype = CPLJSONObject();
849 0 : dtype.Deinit();
850 0 : return dtype;
851 : }
852 6 : if (subdtype.GetType() == CPLJSONObject::Type::Object)
853 4 : subArray.Add(subdtype["dummy"]);
854 : else
855 2 : subArray.Add(subdtype);
856 6 : array.Add(subArray);
857 : }
858 4 : dtype = std::move(array);
859 4 : break;
860 : }
861 : }
862 328 : return dtype;
863 : }
864 :
865 : /************************************************************************/
866 : /* ZarrV2Group::CreateMDArray() */
867 : /************************************************************************/
868 :
869 344 : std::shared_ptr<GDALMDArray> ZarrV2Group::CreateMDArray(
870 : const std::string &osName,
871 : const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
872 : const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
873 : {
874 344 : if (!CheckValidAndErrorOutIfNot())
875 0 : return nullptr;
876 :
877 344 : if (!m_bUpdatable)
878 : {
879 0 : CPLError(CE_Failure, CPLE_NotSupported,
880 : "Dataset not open in update mode");
881 0 : return nullptr;
882 : }
883 344 : if (!IsValidObjectName(osName))
884 : {
885 14 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid array name");
886 14 : return nullptr;
887 : }
888 :
889 660 : std::vector<DtypeElt> aoDtypeElts;
890 : const bool bUseUnicode =
891 330 : EQUAL(CSLFetchNameValueDef(papszOptions, "STRING_FORMAT", "ASCII"),
892 : "UNICODE");
893 660 : const auto dtype = FillDTypeElts(oDataType, 0, aoDtypeElts, bUseUnicode);
894 330 : if (!dtype.IsValid() || aoDtypeElts.empty())
895 8 : return nullptr;
896 :
897 322 : GetMDArrayNames();
898 :
899 322 : if (cpl::contains(m_oSetArrayNames, osName))
900 : {
901 2 : CPLError(CE_Failure, CPLE_AppDefined,
902 : "An array with same name already exists");
903 2 : return nullptr;
904 : }
905 :
906 640 : CPLJSONObject oCompressor;
907 320 : oCompressor.Deinit();
908 : const char *pszCompressor =
909 320 : CSLFetchNameValueDef(papszOptions, "COMPRESS", "NONE");
910 320 : const CPLCompressor *psCompressor = nullptr;
911 320 : const CPLCompressor *psDecompressor = nullptr;
912 320 : if (!EQUAL(pszCompressor, "NONE"))
913 : {
914 18 : psCompressor = CPLGetCompressor(pszCompressor);
915 18 : psDecompressor = CPLGetDecompressor(pszCompressor);
916 18 : if (psCompressor == nullptr || psDecompressor == nullptr)
917 : {
918 1 : CPLError(CE_Failure, CPLE_NotSupported,
919 : "Compressor/decompressor for %s not available",
920 : pszCompressor);
921 1 : return nullptr;
922 : }
923 : const char *pszOptions =
924 17 : CSLFetchNameValue(psCompressor->papszMetadata, "OPTIONS");
925 17 : if (pszOptions)
926 : {
927 34 : CPLXMLTreeCloser oTree(CPLParseXMLString(pszOptions));
928 : const auto psRoot =
929 17 : oTree.get() ? CPLGetXMLNode(oTree.get(), "=Options") : nullptr;
930 17 : if (psRoot)
931 : {
932 17 : for (const CPLXMLNode *psNode = psRoot->psChild;
933 39 : psNode != nullptr; psNode = psNode->psNext)
934 : {
935 22 : if (psNode->eType == CXT_Element &&
936 22 : strcmp(psNode->pszValue, "Option") == 0)
937 : {
938 : const char *pszName =
939 22 : CPLGetXMLValue(psNode, "name", nullptr);
940 : const char *pszType =
941 22 : CPLGetXMLValue(psNode, "type", nullptr);
942 22 : if (pszName && pszType)
943 : {
944 44 : const char *pszVal = CSLFetchNameValueDef(
945 : papszOptions,
946 44 : (std::string(pszCompressor) + '_' + pszName)
947 : .c_str(),
948 : CPLGetXMLValue(psNode, "default", nullptr));
949 22 : if (pszVal)
950 : {
951 22 : if (EQUAL(pszName, "SHUFFLE") &&
952 1 : EQUAL(pszVal, "BYTE"))
953 : {
954 1 : pszVal = "1";
955 1 : pszType = "integer";
956 : }
957 :
958 22 : if (!oCompressor.IsValid())
959 : {
960 17 : oCompressor = CPLJSONObject();
961 17 : oCompressor.Add(
962 : "id",
963 34 : CPLString(pszCompressor).tolower());
964 : }
965 :
966 : std::string osOptName(
967 44 : CPLString(pszName).tolower());
968 22 : if (STARTS_WITH(pszType, "int"))
969 20 : oCompressor.Add(osOptName, atoi(pszVal));
970 : else
971 2 : oCompressor.Add(osOptName, pszVal);
972 : }
973 : }
974 : }
975 : }
976 : }
977 : }
978 : }
979 :
980 638 : CPLJSONArray oFilters;
981 : const char *pszFilter =
982 319 : CSLFetchNameValueDef(papszOptions, "FILTER", "NONE");
983 319 : if (!EQUAL(pszFilter, "NONE"))
984 : {
985 1 : const auto psFilterCompressor = CPLGetCompressor(pszFilter);
986 1 : const auto psFilterDecompressor = CPLGetCompressor(pszFilter);
987 1 : if (psFilterCompressor == nullptr || psFilterDecompressor == nullptr)
988 : {
989 0 : CPLError(CE_Failure, CPLE_NotSupported,
990 : "Compressor/decompressor for filter %s not available",
991 : pszFilter);
992 0 : return nullptr;
993 : }
994 :
995 1 : CPLJSONObject oFilter;
996 1 : oFilter.Add("id", CPLString(pszFilter).tolower());
997 1 : oFilters.Add(oFilter);
998 :
999 : const char *pszOptions =
1000 1 : CSLFetchNameValue(psFilterCompressor->papszMetadata, "OPTIONS");
1001 1 : if (pszOptions)
1002 : {
1003 2 : CPLXMLTreeCloser oTree(CPLParseXMLString(pszOptions));
1004 : const auto psRoot =
1005 1 : oTree.get() ? CPLGetXMLNode(oTree.get(), "=Options") : nullptr;
1006 1 : if (psRoot)
1007 : {
1008 1 : for (const CPLXMLNode *psNode = psRoot->psChild;
1009 2 : psNode != nullptr; psNode = psNode->psNext)
1010 : {
1011 1 : if (psNode->eType == CXT_Element &&
1012 1 : strcmp(psNode->pszValue, "Option") == 0)
1013 : {
1014 : const char *pszName =
1015 1 : CPLGetXMLValue(psNode, "name", nullptr);
1016 : const char *pszType =
1017 1 : CPLGetXMLValue(psNode, "type", nullptr);
1018 1 : if (pszName && pszType)
1019 : {
1020 2 : const char *pszVal = CSLFetchNameValueDef(
1021 : papszOptions,
1022 2 : (std::string(pszFilter) + '_' + pszName)
1023 : .c_str(),
1024 : CPLGetXMLValue(psNode, "default", nullptr));
1025 1 : if (pszVal)
1026 : {
1027 : std::string osOptName(
1028 0 : CPLString(pszName).tolower());
1029 0 : if (STARTS_WITH(pszType, "int"))
1030 0 : oFilter.Add(osOptName, atoi(pszVal));
1031 : else
1032 0 : oFilter.Add(osOptName, pszVal);
1033 : }
1034 : }
1035 : }
1036 : }
1037 : }
1038 : }
1039 :
1040 2 : if (EQUAL(pszFilter, "delta") &&
1041 1 : CSLFetchNameValue(papszOptions, "DELTA_DTYPE") == nullptr)
1042 : {
1043 1 : if (oDataType.GetClass() != GEDTC_NUMERIC)
1044 : {
1045 0 : CPLError(CE_Failure, CPLE_NotSupported,
1046 : "DELTA_DTYPE option must be specified");
1047 0 : return nullptr;
1048 : }
1049 1 : switch (oDataType.GetNumericDataType())
1050 : {
1051 0 : case GDT_Unknown:
1052 0 : break;
1053 0 : case GDT_Byte:
1054 0 : oFilter.Add("dtype", "u1");
1055 0 : break;
1056 0 : case GDT_Int8:
1057 0 : oFilter.Add("dtype", "i1");
1058 0 : break;
1059 1 : case GDT_UInt16:
1060 1 : oFilter.Add("dtype", "<u2");
1061 1 : break;
1062 0 : case GDT_Int16:
1063 0 : oFilter.Add("dtype", "<i2");
1064 0 : break;
1065 0 : case GDT_UInt32:
1066 0 : oFilter.Add("dtype", "<u4");
1067 0 : break;
1068 0 : case GDT_Int32:
1069 0 : oFilter.Add("dtype", "<i4");
1070 0 : break;
1071 0 : case GDT_UInt64:
1072 0 : oFilter.Add("dtype", "<u8");
1073 0 : break;
1074 0 : case GDT_Int64:
1075 0 : oFilter.Add("dtype", "<i8");
1076 0 : break;
1077 0 : case GDT_Float16:
1078 0 : oFilter.Add("dtype", "<f2");
1079 0 : break;
1080 0 : case GDT_Float32:
1081 0 : oFilter.Add("dtype", "<f4");
1082 0 : break;
1083 0 : case GDT_Float64:
1084 0 : oFilter.Add("dtype", "<f8");
1085 0 : break;
1086 0 : case GDT_CInt16:
1087 0 : oFilter.Add("dtype", "<i2");
1088 0 : break;
1089 0 : case GDT_CInt32:
1090 0 : oFilter.Add("dtype", "<i4");
1091 0 : break;
1092 0 : case GDT_CFloat16:
1093 0 : oFilter.Add("dtype", "<f2");
1094 0 : break;
1095 0 : case GDT_CFloat32:
1096 0 : oFilter.Add("dtype", "<f4");
1097 0 : break;
1098 0 : case GDT_CFloat64:
1099 0 : oFilter.Add("dtype", "<f8");
1100 0 : break;
1101 0 : case GDT_TypeCount:
1102 0 : break;
1103 : }
1104 : }
1105 : }
1106 :
1107 : const std::string osZarrayDirectory =
1108 638 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
1109 319 : if (VSIMkdir(osZarrayDirectory.c_str(), 0755) != 0)
1110 : {
1111 : VSIStatBufL sStat;
1112 2 : if (VSIStatL(osZarrayDirectory.c_str(), &sStat) == 0)
1113 : {
1114 2 : CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
1115 : osZarrayDirectory.c_str());
1116 : }
1117 : else
1118 : {
1119 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
1120 : osZarrayDirectory.c_str());
1121 : }
1122 2 : return nullptr;
1123 : }
1124 :
1125 634 : std::vector<GUInt64> anBlockSize;
1126 317 : if (!ZarrArray::FillBlockSize(aoDimensions, oDataType, anBlockSize,
1127 : papszOptions))
1128 5 : return nullptr;
1129 :
1130 312 : const bool bFortranOrder = EQUAL(
1131 : CSLFetchNameValueDef(papszOptions, "CHUNK_MEMORY_LAYOUT", "C"), "F");
1132 :
1133 : const char *pszDimSeparator =
1134 312 : CSLFetchNameValueDef(papszOptions, "DIM_SEPARATOR", ".");
1135 :
1136 312 : auto poArray = ZarrV2Array::Create(m_poSharedResource, GetFullName(),
1137 : osName, aoDimensions, oDataType,
1138 624 : aoDtypeElts, anBlockSize, bFortranOrder);
1139 :
1140 312 : if (!poArray)
1141 0 : return nullptr;
1142 : const std::string osZarrayFilename =
1143 624 : CPLFormFilenameSafe(osZarrayDirectory.c_str(), ".zarray", nullptr);
1144 312 : poArray->SetNew(true);
1145 312 : poArray->SetFilename(osZarrayFilename);
1146 312 : poArray->SetDimSeparator(pszDimSeparator);
1147 312 : poArray->SetDtype(dtype);
1148 312 : poArray->SetCompressorDecompressor(pszCompressor, psCompressor,
1149 : psDecompressor);
1150 312 : if (oCompressor.IsValid())
1151 17 : poArray->SetCompressorJson(oCompressor);
1152 312 : poArray->SetFilters(oFilters);
1153 312 : poArray->SetUpdatable(true);
1154 312 : poArray->SetDefinitionModified(true);
1155 312 : if (!cpl::starts_with(osZarrayFilename, "/vsi") && !poArray->Flush())
1156 0 : return nullptr;
1157 312 : RegisterArray(poArray);
1158 :
1159 312 : return poArray;
1160 : }
|