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 : #include "zarr_v3_codec.h"
15 :
16 : #include <algorithm>
17 : #include <cassert>
18 : #include <limits>
19 : #include <map>
20 : #include <set>
21 :
22 : /************************************************************************/
23 : /* ZarrV3Group::Create() */
24 : /************************************************************************/
25 :
26 : std::shared_ptr<ZarrV3Group>
27 1286 : ZarrV3Group::Create(const std::shared_ptr<ZarrSharedResource> &poSharedResource,
28 : const std::string &osParentName, const std::string &osName,
29 : const std::string &osRootDirectoryName)
30 : {
31 : auto poGroup = std::shared_ptr<ZarrV3Group>(new ZarrV3Group(
32 1286 : poSharedResource, osParentName, osName, osRootDirectoryName));
33 1286 : poGroup->SetSelf(poGroup);
34 1286 : return poGroup;
35 : }
36 :
37 : /************************************************************************/
38 : /* OpenZarrArray() */
39 : /************************************************************************/
40 :
41 1061 : std::shared_ptr<ZarrArray> ZarrV3Group::OpenZarrArray(const std::string &osName,
42 : CSLConstList) const
43 : {
44 1061 : if (!CheckValidAndErrorOutIfNot())
45 0 : return nullptr;
46 :
47 1061 : auto oIter = m_oMapMDArrays.find(osName);
48 1061 : if (oIter != m_oMapMDArrays.end())
49 979 : return oIter->second;
50 :
51 82 : if (m_bReadFromConsolidatedMetadata)
52 14 : return nullptr;
53 :
54 : const std::string osSubDir =
55 136 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
56 : const std::string osZarrayFilename =
57 136 : CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
58 :
59 : VSIStatBufL sStat;
60 68 : if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
61 : {
62 112 : CPLJSONDocument oDoc;
63 56 : if (!oDoc.Load(osZarrayFilename))
64 0 : return nullptr;
65 112 : const auto oRoot = oDoc.GetRoot();
66 56 : return LoadArray(osName, osZarrayFilename, oRoot);
67 : }
68 :
69 12 : return nullptr;
70 : }
71 :
72 : /************************************************************************/
73 : /* ZarrV3Group::LoadAttributes() */
74 : /************************************************************************/
75 :
76 367 : void ZarrV3Group::LoadAttributes() const
77 : {
78 367 : if (m_bAttributesLoaded)
79 334 : return;
80 33 : m_bAttributesLoaded = true;
81 :
82 : const std::string osFilename =
83 33 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), "zarr.json", nullptr);
84 :
85 : VSIStatBufL sStat;
86 33 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
87 : {
88 33 : CPLJSONDocument oDoc;
89 33 : if (!oDoc.Load(osFilename))
90 0 : return;
91 33 : auto oRoot = oDoc.GetRoot();
92 33 : m_oAttrGroup.Init(oRoot["attributes"], m_bUpdatable);
93 : }
94 : }
95 :
96 : /************************************************************************/
97 : /* ExploreDirectory() */
98 : /************************************************************************/
99 :
100 63 : void ZarrV3Group::ExploreDirectory() const
101 : {
102 63 : if (m_bDirectoryExplored)
103 0 : return;
104 63 : m_bDirectoryExplored = true;
105 :
106 63 : auto psDir = VSIOpenDir(m_osDirectoryName.c_str(), 0, nullptr);
107 63 : if (!psDir)
108 0 : return;
109 223 : while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir))
110 : {
111 160 : if (VSI_ISDIR(psEntry->nMode))
112 : {
113 194 : std::string osName(psEntry->pszName);
114 194 : while (!osName.empty() &&
115 97 : (osName.back() == '/' || osName.back() == '\\'))
116 0 : osName.pop_back();
117 97 : if (osName.empty())
118 0 : continue;
119 : const std::string osSubDir = CPLFormFilenameSafe(
120 97 : m_osDirectoryName.c_str(), osName.c_str(), nullptr);
121 : VSIStatBufL sStat;
122 : const std::string osZarrJsonFilename =
123 97 : CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
124 97 : if (VSIStatL(osZarrJsonFilename.c_str(), &sStat) == 0)
125 : {
126 95 : CPLJSONDocument oDoc;
127 95 : if (oDoc.Load(osZarrJsonFilename.c_str()))
128 : {
129 95 : const auto oRoot = oDoc.GetRoot();
130 95 : if (oRoot.GetInteger("zarr_format") != 3)
131 : {
132 0 : CPLError(CE_Warning, CPLE_AppDefined,
133 : "Unhandled zarr_format value");
134 0 : continue;
135 : }
136 190 : const std::string osNodeType = oRoot.GetString("node_type");
137 95 : if (osNodeType == "array")
138 : {
139 52 : if (!cpl::contains(m_oSetArrayNames, osName))
140 : {
141 51 : m_oSetArrayNames.insert(osName);
142 51 : m_aosArrays.emplace_back(std::move(osName));
143 : }
144 : }
145 43 : else if (osNodeType == "group")
146 : {
147 43 : if (!cpl::contains(m_oSetGroupNames, osName))
148 : {
149 43 : m_oSetGroupNames.insert(osName);
150 43 : m_aosGroups.emplace_back(std::move(osName));
151 : }
152 : }
153 : else
154 : {
155 0 : CPLError(CE_Warning, CPLE_AppDefined,
156 : "Unhandled node_type value");
157 0 : continue;
158 : }
159 : }
160 : }
161 : else
162 : {
163 : // Implicit group (deprecated)
164 2 : if (!cpl::contains(m_oSetGroupNames, osName))
165 : {
166 2 : m_oSetGroupNames.insert(osName);
167 2 : m_aosGroups.emplace_back(std::move(osName));
168 : }
169 : }
170 : }
171 160 : }
172 63 : VSICloseDir(psDir);
173 : }
174 :
175 : /************************************************************************/
176 : /* ZarrV3Group::ZarrV3Group() */
177 : /************************************************************************/
178 :
179 1286 : ZarrV3Group::ZarrV3Group(
180 : const std::shared_ptr<ZarrSharedResource> &poSharedResource,
181 : const std::string &osParentName, const std::string &osName,
182 1286 : const std::string &osDirectoryName)
183 1286 : : ZarrGroupBase(poSharedResource, osParentName, osName)
184 : {
185 1286 : m_osDirectoryName = osDirectoryName;
186 1286 : }
187 :
188 : /************************************************************************/
189 : /* ZarrV3Group::~ZarrV3Group() */
190 : /************************************************************************/
191 :
192 2572 : ZarrV3Group::~ZarrV3Group()
193 : {
194 1286 : ZarrV3Group::Close();
195 2572 : }
196 :
197 : /************************************************************************/
198 : /* Close() */
199 : /************************************************************************/
200 :
201 2971 : bool ZarrV3Group::Close()
202 : {
203 2971 : bool bRet = ZarrGroupBase::Close();
204 :
205 5883 : if (m_bValid && (m_oAttrGroup.IsModified() ||
206 2996 : (m_bUpdatable && !m_bFileHasBeenWritten &&
207 84 : m_poSharedResource->IsConsolidatedMetadataEnabled())))
208 : {
209 176 : CPLJSONDocument oDoc;
210 176 : auto oRoot = oDoc.GetRoot();
211 88 : oRoot.Add("zarr_format", 3);
212 88 : oRoot.Add("node_type", "group");
213 88 : oRoot.Add("attributes", m_oAttrGroup.Serialize());
214 : const std::string osZarrJsonFilename = CPLFormFilenameSafe(
215 88 : m_osDirectoryName.c_str(), "zarr.json", nullptr);
216 88 : if (!m_bFileHasBeenWritten)
217 : {
218 35 : oRoot.Add("consolidated_metadata",
219 35 : m_poSharedResource->GetConsolidatedMetadataObj());
220 35 : bRet = oDoc.Save(osZarrJsonFilename) && bRet;
221 : }
222 : else
223 : {
224 53 : bRet = oDoc.Save(osZarrJsonFilename) && bRet;
225 53 : if (bRet)
226 52 : m_poSharedResource->SetZMetadataItem(osZarrJsonFilename, oRoot);
227 : }
228 88 : m_bFileHasBeenWritten = bRet;
229 : }
230 :
231 2971 : return bRet;
232 : }
233 :
234 : /************************************************************************/
235 : /* ZarrV3Group::GetOrCreateSubGroup() */
236 : /************************************************************************/
237 :
238 : std::shared_ptr<ZarrV3Group>
239 351 : ZarrV3Group::GetOrCreateSubGroup(const std::string &osSubGroupFullname)
240 : {
241 : auto poSubGroup = std::dynamic_pointer_cast<ZarrV3Group>(
242 351 : OpenGroupFromFullname(osSubGroupFullname));
243 351 : if (poSubGroup)
244 : {
245 225 : return poSubGroup;
246 : }
247 :
248 126 : const auto nLastSlashPos = osSubGroupFullname.rfind('/');
249 : auto poBelongingGroup =
250 : (nLastSlashPos == 0)
251 126 : ? this
252 140 : : GetOrCreateSubGroup(osSubGroupFullname.substr(0, nLastSlashPos))
253 126 : .get();
254 :
255 252 : poSubGroup = ZarrV3Group::Create(
256 126 : m_poSharedResource, poBelongingGroup->GetFullName(),
257 378 : osSubGroupFullname.substr(nLastSlashPos + 1), m_osDirectoryName);
258 252 : poSubGroup->m_poParent = std::dynamic_pointer_cast<ZarrGroupBase>(
259 378 : poBelongingGroup->m_pSelf.lock());
260 252 : poSubGroup->SetDirectoryName(
261 252 : CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
262 126 : poSubGroup->GetName().c_str(), nullptr));
263 126 : poSubGroup->m_bDirectoryExplored = true;
264 126 : poSubGroup->m_bAttributesLoaded = true;
265 126 : poSubGroup->m_bReadFromConsolidatedMetadata = true;
266 126 : poSubGroup->m_bFileHasBeenWritten = true;
267 126 : poSubGroup->SetUpdatable(m_bUpdatable);
268 :
269 126 : poBelongingGroup->m_oMapGroups[poSubGroup->GetName()] = poSubGroup;
270 126 : poBelongingGroup->m_oSetGroupNames.insert(poSubGroup->GetName());
271 126 : poBelongingGroup->m_aosGroups.emplace_back(poSubGroup->GetName());
272 126 : return poSubGroup;
273 : }
274 :
275 : /************************************************************************/
276 : /* ZarrV3Group::InitFromConsolidatedMetadata() */
277 : /************************************************************************/
278 :
279 161 : void ZarrV3Group::InitFromConsolidatedMetadata(
280 : const CPLJSONObject &oConsolidatedMetadata,
281 : const CPLJSONObject &oRootAttributes)
282 : {
283 322 : const auto metadata = oConsolidatedMetadata["metadata"];
284 161 : if (metadata.GetType() != CPLJSONObject::Type::Object)
285 : {
286 0 : CPLError(CE_Warning, CPLE_AppDefined,
287 : "consolidated_metadata lacks 'metadata' object");
288 0 : return;
289 : }
290 161 : m_bDirectoryExplored = true;
291 161 : m_bAttributesLoaded = true;
292 161 : m_bReadFromConsolidatedMetadata = true;
293 :
294 161 : if (oRootAttributes.IsValid())
295 : {
296 161 : m_oAttrGroup.Init(oRootAttributes, m_bUpdatable);
297 : }
298 :
299 322 : const auto children = metadata.GetChildren();
300 322 : std::map<std::string, const CPLJSONObject *> oMapArrays;
301 :
302 : // First pass to create groups and collect arrays
303 617 : for (const auto &child : children)
304 : {
305 456 : const std::string osName(child.GetName());
306 456 : if (std::count(osName.begin(), osName.end(), '/') > 32)
307 : {
308 : // Avoid too deep recursion in GetOrCreateSubGroup()
309 0 : continue;
310 : }
311 :
312 1368 : const std::string osNodeType = child.GetString("node_type");
313 456 : if (osNodeType == "group")
314 : {
315 252 : auto poGroup = GetOrCreateSubGroup("/" + osName);
316 378 : auto oAttributes = child["attributes"];
317 126 : if (oAttributes.IsValid())
318 : {
319 126 : poGroup->m_oAttrGroup.Init(oAttributes, m_bUpdatable);
320 : }
321 : }
322 330 : else if (osNodeType == "array")
323 : {
324 330 : oMapArrays[osName] = &child;
325 : }
326 : }
327 :
328 : const auto CreateArray =
329 541 : [this](const std::string &osArrayFullname, const CPLJSONObject &oArray)
330 : {
331 330 : const auto nLastSlashPos = osArrayFullname.rfind('/');
332 : auto poBelongingGroup =
333 : (nLastSlashPos == std::string::npos)
334 330 : ? this
335 541 : : GetOrCreateSubGroup("/" +
336 541 : osArrayFullname.substr(0, nLastSlashPos))
337 330 : .get();
338 : const auto osArrayName =
339 : nLastSlashPos == std::string::npos
340 : ? osArrayFullname
341 660 : : osArrayFullname.substr(nLastSlashPos + 1);
342 : const std::string osZarrayFilename = CPLFormFilenameSafe(
343 330 : CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
344 : osArrayName.c_str(), nullptr)
345 : .c_str(),
346 330 : "zarr.json", nullptr);
347 330 : poBelongingGroup->LoadArray(osArrayName, osZarrayFilename, oArray);
348 330 : };
349 :
350 : struct ArrayDesc
351 : {
352 : std::string osArrayFullname{};
353 : const CPLJSONObject *poArray = nullptr;
354 : };
355 :
356 322 : std::vector<ArrayDesc> aoRegularArrays;
357 :
358 : // Second pass to read attributes and create arrays that are indexing
359 : // variable
360 617 : for (const auto &child : children)
361 : {
362 912 : const std::string osName(child.GetName());
363 1368 : const std::string osNodeType = child.GetString("node_type");
364 456 : if (osNodeType == "array")
365 : {
366 330 : auto oIter = oMapArrays.find(osName);
367 330 : if (oIter != oMapArrays.end())
368 : {
369 330 : const auto nLastSlashPos = osName.rfind('/');
370 : const std::string osArrayName =
371 : (nLastSlashPos == std::string::npos)
372 : ? osName
373 660 : : osName.substr(nLastSlashPos + 1);
374 990 : const auto arrayDimensions = child["dimension_names"].ToArray();
375 412 : if (arrayDimensions.IsValid() && arrayDimensions.Size() == 1 &&
376 412 : arrayDimensions[0].ToString() == osArrayName)
377 : {
378 74 : CreateArray(osName, child);
379 74 : oMapArrays.erase(oIter);
380 : }
381 : else
382 : {
383 512 : ArrayDesc desc;
384 256 : desc.osArrayFullname = std::move(osName);
385 256 : desc.poArray = oIter->second;
386 256 : aoRegularArrays.emplace_back(std::move(desc));
387 : }
388 : }
389 : }
390 : }
391 :
392 : // Third pass to create non-indexing arrays with attributes
393 417 : for (const auto &desc : aoRegularArrays)
394 : {
395 256 : CreateArray(desc.osArrayFullname, *(desc.poArray));
396 256 : oMapArrays.erase(desc.osArrayFullname);
397 : }
398 :
399 : // Fourth pass to create arrays without attributes
400 161 : for (const auto &kv : oMapArrays)
401 : {
402 0 : CreateArray(kv.first, *(kv.second));
403 : }
404 : }
405 :
406 : /************************************************************************/
407 : /* OpenZarrGroup() */
408 : /************************************************************************/
409 :
410 : std::shared_ptr<ZarrGroupBase>
411 720 : ZarrV3Group::OpenZarrGroup(const std::string &osName, CSLConstList) const
412 : {
413 720 : if (!CheckValidAndErrorOutIfNot())
414 0 : return nullptr;
415 :
416 720 : auto oIter = m_oMapGroups.find(osName);
417 720 : if (oIter != m_oMapGroups.end())
418 509 : return oIter->second;
419 :
420 211 : if (m_bReadFromConsolidatedMetadata)
421 153 : return nullptr;
422 :
423 : const std::string osSubDir =
424 116 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
425 : const std::string osSubDirZarrJsonFilename =
426 116 : CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
427 :
428 : VSIStatBufL sStat;
429 : // Explicit group
430 58 : if (VSIStatL(osSubDirZarrJsonFilename.c_str(), &sStat) == 0)
431 : {
432 100 : CPLJSONDocument oDoc;
433 50 : if (oDoc.Load(osSubDirZarrJsonFilename.c_str()))
434 : {
435 100 : const auto oRoot = oDoc.GetRoot();
436 50 : if (oRoot.GetInteger("zarr_format") != 3)
437 : {
438 0 : CPLError(CE_Failure, CPLE_AppDefined,
439 : "Unhandled zarr_format value");
440 0 : return nullptr;
441 : }
442 150 : const std::string osNodeType = oRoot.GetString("node_type");
443 50 : if (osNodeType != "group")
444 : {
445 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s is a %s, not a group",
446 : osName.c_str(), osNodeType.c_str());
447 0 : return nullptr;
448 : }
449 : auto poSubGroup = ZarrV3Group::Create(
450 100 : m_poSharedResource, GetFullName(), osName, osSubDir);
451 50 : poSubGroup->m_bFileHasBeenWritten = true;
452 50 : poSubGroup->m_poParent =
453 100 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
454 50 : poSubGroup->SetUpdatable(m_bUpdatable);
455 50 : m_oMapGroups[osName] = poSubGroup;
456 50 : return poSubGroup;
457 : }
458 0 : return nullptr;
459 : }
460 :
461 : // Implicit group
462 8 : if (VSIStatL(osSubDir.c_str(), &sStat) == 0 && VSI_ISDIR(sStat.st_mode))
463 : {
464 : // Note: Python zarr v3.0.2 still generates implicit groups
465 : // See https://github.com/zarr-developers/zarr-python/issues/2794
466 2 : CPLError(CE_Warning, CPLE_AppDefined,
467 : "Support for Zarr V3 implicit group is now deprecated, and "
468 : "may be removed in a future version");
469 2 : auto poSubGroup = ZarrV3Group::Create(m_poSharedResource, GetFullName(),
470 4 : osName, osSubDir);
471 2 : poSubGroup->m_bFileHasBeenWritten = true;
472 2 : poSubGroup->m_poParent =
473 4 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
474 2 : poSubGroup->SetUpdatable(m_bUpdatable);
475 2 : m_oMapGroups[osName] = poSubGroup;
476 2 : return poSubGroup;
477 : }
478 :
479 6 : return nullptr;
480 : }
481 :
482 : /************************************************************************/
483 : /* ZarrV3Group::CreateOnDisk() */
484 : /************************************************************************/
485 :
486 176 : std::shared_ptr<ZarrV3Group> ZarrV3Group::CreateOnDisk(
487 : const std::shared_ptr<ZarrSharedResource> &poSharedResource,
488 : const std::string &osParentFullName, const std::string &osName,
489 : const std::string &osDirectoryName)
490 : {
491 176 : if (VSIMkdir(osDirectoryName.c_str(), 0755) != 0)
492 : {
493 : VSIStatBufL sStat;
494 3 : if (VSIStatL(osDirectoryName.c_str(), &sStat) == 0)
495 : {
496 3 : CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
497 : osDirectoryName.c_str());
498 : }
499 : else
500 : {
501 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
502 : osDirectoryName.c_str());
503 : }
504 3 : return nullptr;
505 : }
506 :
507 : const std::string osZarrJsonFilename(
508 346 : CPLFormFilenameSafe(osDirectoryName.c_str(), "zarr.json", nullptr));
509 173 : VSILFILE *fp = nullptr;
510 308 : if (!(poSharedResource->IsConsolidatedMetadataEnabled() &&
511 135 : cpl::starts_with(osZarrJsonFilename, "/vsizip/") &&
512 1 : osParentFullName.empty() && osName == "/"))
513 : {
514 172 : fp = VSIFOpenL(osZarrJsonFilename.c_str(), "wb");
515 172 : if (!fp)
516 : {
517 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create file %s.",
518 : osZarrJsonFilename.c_str());
519 0 : return nullptr;
520 : }
521 172 : VSIFPrintfL(fp, "{\n"
522 : " \"zarr_format\": 3,\n"
523 : " \"node_type\": \"group\",\n"
524 : " \"attributes\": {}\n"
525 : "}\n");
526 172 : VSIFCloseL(fp);
527 : }
528 :
529 : auto poGroup = ZarrV3Group::Create(poSharedResource, osParentFullName,
530 346 : osName, osDirectoryName);
531 173 : poGroup->SetUpdatable(true);
532 173 : poGroup->m_bDirectoryExplored = true;
533 173 : poGroup->m_bFileHasBeenWritten = fp != nullptr;
534 :
535 346 : CPLJSONObject oObj;
536 173 : oObj.Add("zarr_format", 3);
537 173 : oObj.Add("node_type", "group");
538 173 : oObj.Add("attributes", CPLJSONObject());
539 173 : poSharedResource->SetZMetadataItem(osZarrJsonFilename, oObj);
540 :
541 173 : return poGroup;
542 : }
543 :
544 : /************************************************************************/
545 : /* ZarrV3Group::CreateGroup() */
546 : /************************************************************************/
547 :
548 : std::shared_ptr<GDALGroup>
549 57 : ZarrV3Group::CreateGroup(const std::string &osName,
550 : CSLConstList /* papszOptions */)
551 : {
552 57 : if (!CheckValidAndErrorOutIfNot())
553 0 : return nullptr;
554 :
555 57 : if (!m_bUpdatable)
556 : {
557 3 : CPLError(CE_Failure, CPLE_NotSupported,
558 : "Dataset not open in update mode");
559 3 : return nullptr;
560 : }
561 54 : if (!IsValidObjectName(osName))
562 : {
563 14 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid group name");
564 14 : return nullptr;
565 : }
566 :
567 40 : GetGroupNames();
568 :
569 40 : if (cpl::contains(m_oSetGroupNames, osName))
570 : {
571 1 : CPLError(CE_Failure, CPLE_AppDefined,
572 : "A group with same name already exists");
573 1 : return nullptr;
574 : }
575 :
576 : const std::string osDirectoryName =
577 78 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
578 39 : auto poGroup = CreateOnDisk(m_poSharedResource, GetFullName(), osName,
579 78 : osDirectoryName);
580 39 : if (!poGroup)
581 3 : return nullptr;
582 36 : poGroup->m_poParent =
583 72 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
584 36 : m_oMapGroups[osName] = poGroup;
585 36 : m_aosGroups.emplace_back(osName);
586 36 : return poGroup;
587 : }
588 :
589 : /************************************************************************/
590 : /* FillDTypeElts() */
591 : /************************************************************************/
592 :
593 137 : static CPLJSONObject FillDTypeElts(const GDALExtendedDataType &oDataType,
594 : std::vector<DtypeElt> &aoDtypeElts)
595 : {
596 137 : CPLJSONObject dtype;
597 274 : const std::string dummy("dummy");
598 :
599 137 : const auto eDT = oDataType.GetNumericDataType();
600 274 : DtypeElt elt;
601 137 : bool bUnsupported = false;
602 137 : switch (eDT)
603 : {
604 62 : case GDT_UInt8:
605 : {
606 62 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
607 62 : dtype.Set(dummy, "uint8");
608 62 : break;
609 : }
610 6 : case GDT_Int8:
611 : {
612 6 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
613 6 : dtype.Set(dummy, "int8");
614 6 : break;
615 : }
616 7 : case GDT_UInt16:
617 : {
618 7 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
619 7 : dtype.Set(dummy, "uint16");
620 7 : break;
621 : }
622 9 : case GDT_Int16:
623 : {
624 9 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
625 9 : dtype.Set(dummy, "int16");
626 9 : break;
627 : }
628 7 : case GDT_UInt32:
629 : {
630 7 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
631 7 : dtype.Set(dummy, "uint32");
632 7 : break;
633 : }
634 7 : case GDT_Int32:
635 : {
636 7 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
637 7 : dtype.Set(dummy, "int32");
638 7 : break;
639 : }
640 6 : case GDT_UInt64:
641 : {
642 6 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
643 6 : dtype.Set(dummy, "uint64");
644 6 : break;
645 : }
646 6 : case GDT_Int64:
647 : {
648 6 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
649 6 : dtype.Set(dummy, "int64");
650 6 : break;
651 : }
652 1 : case GDT_Float16:
653 : {
654 1 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
655 1 : dtype.Set(dummy, "float16");
656 1 : break;
657 : }
658 7 : case GDT_Float32:
659 : {
660 7 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
661 7 : dtype.Set(dummy, "float32");
662 7 : break;
663 : }
664 15 : case GDT_Float64:
665 : {
666 15 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
667 15 : dtype.Set(dummy, "float64");
668 15 : break;
669 : }
670 2 : case GDT_Unknown:
671 : case GDT_CInt16:
672 : case GDT_CInt32:
673 : {
674 2 : bUnsupported = true;
675 2 : break;
676 : }
677 0 : case GDT_CFloat16:
678 : {
679 0 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
680 0 : dtype.Set(dummy, "complex32");
681 0 : break;
682 : }
683 1 : case GDT_CFloat32:
684 : {
685 1 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
686 1 : dtype.Set(dummy, "complex64");
687 1 : break;
688 : }
689 1 : case GDT_CFloat64:
690 : {
691 1 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
692 1 : dtype.Set(dummy, "complex128");
693 1 : break;
694 : }
695 0 : case GDT_TypeCount:
696 : {
697 : static_assert(GDT_TypeCount == GDT_CFloat16 + 1,
698 : "GDT_TypeCount == GDT_CFloat16 + 1");
699 0 : break;
700 : }
701 : }
702 137 : if (bUnsupported)
703 : {
704 2 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %s",
705 : GDALGetDataTypeName(eDT));
706 2 : dtype = CPLJSONObject();
707 2 : dtype.Deinit();
708 2 : return dtype;
709 : }
710 135 : elt.nativeOffset = 0;
711 135 : elt.nativeSize = GDALGetDataTypeSizeBytes(eDT);
712 135 : elt.gdalOffset = 0;
713 135 : elt.gdalSize = elt.nativeSize;
714 : #ifdef CPL_MSB
715 : elt.needByteSwapping = elt.nativeSize > 1;
716 : #endif
717 135 : aoDtypeElts.emplace_back(elt);
718 :
719 135 : return dtype;
720 : }
721 :
722 : /************************************************************************/
723 : /* ZarrV3Group::CreateMDArray() */
724 : /************************************************************************/
725 :
726 155 : std::shared_ptr<GDALMDArray> ZarrV3Group::CreateMDArray(
727 : const std::string &osName,
728 : const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
729 : const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
730 : {
731 155 : if (!CheckValidAndErrorOutIfNot())
732 0 : return nullptr;
733 :
734 155 : if (!m_bUpdatable)
735 : {
736 0 : CPLError(CE_Failure, CPLE_NotSupported,
737 : "Dataset not open in update mode");
738 0 : return nullptr;
739 : }
740 155 : if (!IsValidObjectName(osName))
741 : {
742 14 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid array name");
743 14 : return nullptr;
744 : }
745 :
746 141 : if (oDataType.GetClass() != GEDTC_NUMERIC)
747 : {
748 4 : CPLError(CE_Failure, CPLE_AppDefined,
749 : "Unsupported data type with Zarr V3");
750 4 : return nullptr;
751 : }
752 :
753 137 : if (!EQUAL(CSLFetchNameValueDef(papszOptions, "FILTER", "NONE"), "NONE"))
754 : {
755 0 : CPLError(CE_Failure, CPLE_AppDefined,
756 : "FILTER option not supported with Zarr V3");
757 0 : return nullptr;
758 : }
759 :
760 274 : std::vector<DtypeElt> aoDtypeElts;
761 411 : const auto dtype = FillDTypeElts(oDataType, aoDtypeElts)["dummy"];
762 137 : if (!dtype.IsValid() || aoDtypeElts.empty())
763 2 : return nullptr;
764 :
765 135 : GetMDArrayNames();
766 :
767 135 : if (cpl::contains(m_oSetArrayNames, osName))
768 : {
769 2 : CPLError(CE_Failure, CPLE_AppDefined,
770 : "An array with same name already exists");
771 2 : return nullptr;
772 : }
773 :
774 266 : std::vector<GUInt64> anOuterBlockSize;
775 133 : if (!ZarrArray::FillBlockSize(aoDimensions, oDataType, anOuterBlockSize,
776 : papszOptions))
777 1 : return nullptr;
778 :
779 : const char *pszDimSeparator =
780 132 : CSLFetchNameValueDef(papszOptions, "DIM_SEPARATOR", "/");
781 :
782 : const std::string osArrayDirectory =
783 264 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
784 132 : if (VSIMkdir(osArrayDirectory.c_str(), 0755) != 0)
785 : {
786 : VSIStatBufL sStat;
787 2 : if (VSIStatL(osArrayDirectory.c_str(), &sStat) == 0)
788 : {
789 2 : CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
790 : osArrayDirectory.c_str());
791 : }
792 : else
793 : {
794 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
795 : osArrayDirectory.c_str());
796 : }
797 2 : return nullptr;
798 : }
799 :
800 130 : std::unique_ptr<ZarrV3CodecSequence> poCodecs;
801 260 : CPLJSONArray oCodecs;
802 :
803 130 : const bool bFortranOrder = EQUAL(
804 : CSLFetchNameValueDef(papszOptions, "CHUNK_MEMORY_LAYOUT", "C"), "F");
805 130 : if (bFortranOrder && aoDimensions.size() > 1)
806 : {
807 80 : CPLJSONObject oCodec;
808 40 : oCodec.Add("name", "transpose");
809 80 : std::vector<int> anOrder;
810 40 : const int nDims = static_cast<int>(aoDimensions.size());
811 130 : for (int i = 0; i < nDims; ++i)
812 : {
813 90 : anOrder.push_back(nDims - 1 - i);
814 : }
815 40 : oCodec.Add("configuration",
816 80 : ZarrV3CodecTranspose::GetConfiguration(anOrder));
817 40 : oCodecs.Add(oCodec);
818 : }
819 :
820 : // Not documented option, but 'bytes' codec is required
821 : const char *pszEndian =
822 130 : CSLFetchNameValueDef(papszOptions, "@ENDIAN", "little");
823 : {
824 260 : CPLJSONObject oCodec;
825 130 : oCodec.Add("name", "bytes");
826 130 : oCodec.Add("configuration", ZarrV3CodecBytes::GetConfiguration(
827 130 : EQUAL(pszEndian, "little")));
828 130 : oCodecs.Add(oCodec);
829 : }
830 :
831 : const char *pszCompressor =
832 130 : CSLFetchNameValueDef(papszOptions, "COMPRESS", "NONE");
833 130 : if (EQUAL(pszCompressor, "GZIP"))
834 : {
835 46 : CPLJSONObject oCodec;
836 23 : oCodec.Add("name", "gzip");
837 : const char *pszLevel =
838 23 : CSLFetchNameValueDef(papszOptions, "GZIP_LEVEL", "6");
839 23 : oCodec.Add("configuration",
840 46 : ZarrV3CodecGZip::GetConfiguration(atoi(pszLevel)));
841 23 : oCodecs.Add(oCodec);
842 : }
843 107 : else if (EQUAL(pszCompressor, "BLOSC"))
844 : {
845 2 : const auto psCompressor = CPLGetCompressor("blosc");
846 2 : if (!psCompressor)
847 0 : return nullptr;
848 : const char *pszOptions =
849 2 : CSLFetchNameValueDef(psCompressor->papszMetadata, "OPTIONS", "");
850 2 : CPLXMLTreeCloser oTreeCompressor(CPLParseXMLString(pszOptions));
851 : const auto psRoot =
852 2 : oTreeCompressor.get()
853 2 : ? CPLGetXMLNode(oTreeCompressor.get(), "=Options")
854 2 : : nullptr;
855 2 : if (!psRoot)
856 0 : return nullptr;
857 :
858 2 : const char *cname = "zlib";
859 14 : for (const CPLXMLNode *psNode = psRoot->psChild; psNode != nullptr;
860 12 : psNode = psNode->psNext)
861 : {
862 12 : if (psNode->eType == CXT_Element)
863 : {
864 12 : const char *pszName = CPLGetXMLValue(psNode, "name", "");
865 12 : if (EQUAL(pszName, "CNAME"))
866 : {
867 2 : cname = CPLGetXMLValue(psNode, "default", cname);
868 : }
869 : }
870 : }
871 :
872 4 : CPLJSONObject oCodec;
873 2 : oCodec.Add("name", "blosc");
874 2 : cname = CSLFetchNameValueDef(papszOptions, "BLOSC_CNAME", cname);
875 : const int clevel =
876 2 : atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_CLEVEL", "5"));
877 : const char *shuffle =
878 2 : CSLFetchNameValueDef(papszOptions, "BLOSC_SHUFFLE", "BYTE");
879 3 : shuffle = (EQUAL(shuffle, "0") || EQUAL(shuffle, "NONE")) ? "noshuffle"
880 1 : : (EQUAL(shuffle, "1") || EQUAL(shuffle, "BYTE")) ? "shuffle"
881 0 : : (EQUAL(shuffle, "2") || EQUAL(shuffle, "BIT"))
882 0 : ? "bitshuffle"
883 : : "invalid";
884 2 : const int typesize = atoi(CSLFetchNameValueDef(
885 : papszOptions, "BLOSC_TYPESIZE",
886 : CPLSPrintf("%d", GDALGetDataTypeSizeBytes(GDALGetNonComplexDataType(
887 : oDataType.GetNumericDataType())))));
888 : const int blocksize =
889 2 : atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_BLOCKSIZE", "0"));
890 2 : oCodec.Add("configuration",
891 4 : ZarrV3CodecBlosc::GetConfiguration(cname, clevel, shuffle,
892 : typesize, blocksize));
893 2 : oCodecs.Add(oCodec);
894 : }
895 105 : else if (EQUAL(pszCompressor, "ZSTD"))
896 : {
897 4 : CPLJSONObject oCodec;
898 2 : oCodec.Add("name", "zstd");
899 : const char *pszLevel =
900 2 : CSLFetchNameValueDef(papszOptions, "ZSTD_LEVEL", "13");
901 2 : const bool bChecksum = CPLTestBool(
902 : CSLFetchNameValueDef(papszOptions, "ZSTD_CHECKSUM", "FALSE"));
903 2 : oCodec.Add("configuration", ZarrV3CodecZstd::GetConfiguration(
904 : atoi(pszLevel), bChecksum));
905 2 : oCodecs.Add(oCodec);
906 : }
907 103 : else if (!EQUAL(pszCompressor, "NONE"))
908 : {
909 1 : CPLError(CE_Failure, CPLE_AppDefined,
910 : "COMPRESS = %s not implemented with Zarr V3", pszCompressor);
911 1 : return nullptr;
912 : }
913 :
914 258 : std::vector<GUInt64> anInnerBlockSize = anOuterBlockSize;
915 129 : if (oCodecs.Size() > 0)
916 : {
917 129 : std::vector<GByte> abyNoData;
918 258 : poCodecs = ZarrV3Array::SetupCodecs(oCodecs, anOuterBlockSize,
919 : anInnerBlockSize,
920 258 : aoDtypeElts.back(), abyNoData);
921 129 : if (!poCodecs)
922 : {
923 0 : return nullptr;
924 : }
925 : }
926 :
927 129 : auto poArray = ZarrV3Array::Create(m_poSharedResource, Self(), osName,
928 : aoDimensions, oDataType, aoDtypeElts,
929 258 : anOuterBlockSize, anInnerBlockSize);
930 :
931 129 : if (!poArray)
932 0 : return nullptr;
933 129 : poArray->SetNew(true);
934 : const std::string osFilename =
935 258 : CPLFormFilenameSafe(osArrayDirectory.c_str(), "zarr.json", nullptr);
936 129 : poArray->SetFilename(osFilename);
937 129 : poArray->SetDimSeparator(pszDimSeparator);
938 129 : poArray->SetDtype(dtype);
939 258 : if (oCodecs.Size() > 0 &&
940 258 : oCodecs[oCodecs.Size() - 1].GetString("name") != "bytes")
941 : {
942 54 : poArray->SetStructuralInfo(
943 54 : "COMPRESSOR", oCodecs[oCodecs.Size() - 1].ToString().c_str());
944 : }
945 129 : if (poCodecs)
946 129 : poArray->SetCodecs(oCodecs, std::move(poCodecs));
947 :
948 129 : poArray->SetCreationOptions(papszOptions);
949 129 : poArray->SetUpdatable(true);
950 129 : poArray->SetDefinitionModified(true);
951 129 : if (!cpl::starts_with(osFilename, "/vsi") && !poArray->Flush())
952 0 : return nullptr;
953 129 : RegisterArray(poArray);
954 :
955 129 : return poArray;
956 : }
|