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