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