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