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