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