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 <algorithm>
16 : #include <cassert>
17 : #include <limits>
18 : #include <map>
19 : #include <set>
20 :
21 : /************************************************************************/
22 : /* ZarrV3Group::Create() */
23 : /************************************************************************/
24 :
25 : std::shared_ptr<ZarrV3Group>
26 415 : ZarrV3Group::Create(const std::shared_ptr<ZarrSharedResource> &poSharedResource,
27 : const std::string &osParentName, const std::string &osName,
28 : const std::string &osRootDirectoryName)
29 : {
30 : auto poGroup = std::shared_ptr<ZarrV3Group>(new ZarrV3Group(
31 415 : poSharedResource, osParentName, osName, osRootDirectoryName));
32 415 : poGroup->SetSelf(poGroup);
33 415 : return poGroup;
34 : }
35 :
36 : /************************************************************************/
37 : /* OpenZarrArray() */
38 : /************************************************************************/
39 :
40 171 : std::shared_ptr<ZarrArray> ZarrV3Group::OpenZarrArray(const std::string &osName,
41 : CSLConstList) const
42 : {
43 171 : if (!CheckValidAndErrorOutIfNot())
44 0 : return nullptr;
45 :
46 171 : auto oIter = m_oMapMDArrays.find(osName);
47 171 : if (oIter != m_oMapMDArrays.end())
48 28 : return oIter->second;
49 :
50 : const std::string osSubDir =
51 286 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
52 : const std::string osZarrayFilename =
53 286 : CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
54 :
55 : VSIStatBufL sStat;
56 143 : if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
57 : {
58 270 : CPLJSONDocument oDoc;
59 135 : if (!oDoc.Load(osZarrayFilename))
60 0 : return nullptr;
61 270 : const auto oRoot = oDoc.GetRoot();
62 135 : return LoadArray(osName, osZarrayFilename, oRoot);
63 : }
64 :
65 8 : return nullptr;
66 : }
67 :
68 : /************************************************************************/
69 : /* ZarrV3Group::LoadAttributes() */
70 : /************************************************************************/
71 :
72 66 : void ZarrV3Group::LoadAttributes() const
73 : {
74 66 : if (m_bAttributesLoaded)
75 35 : return;
76 31 : m_bAttributesLoaded = true;
77 :
78 : const std::string osFilename =
79 31 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), "zarr.json", nullptr);
80 :
81 : VSIStatBufL sStat;
82 31 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
83 : {
84 31 : CPLJSONDocument oDoc;
85 31 : if (!oDoc.Load(osFilename))
86 0 : return;
87 31 : auto oRoot = oDoc.GetRoot();
88 31 : m_oAttrGroup.Init(oRoot["attributes"], m_bUpdatable);
89 : }
90 : }
91 :
92 : /************************************************************************/
93 : /* ExploreDirectory() */
94 : /************************************************************************/
95 :
96 57 : void ZarrV3Group::ExploreDirectory() const
97 : {
98 57 : if (m_bDirectoryExplored)
99 0 : return;
100 57 : m_bDirectoryExplored = true;
101 :
102 57 : auto psDir = VSIOpenDir(m_osDirectoryName.c_str(), 0, nullptr);
103 57 : if (!psDir)
104 0 : return;
105 202 : while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir))
106 : {
107 145 : if (VSI_ISDIR(psEntry->nMode))
108 : {
109 : const std::string osSubDir = CPLFormFilenameSafe(
110 88 : m_osDirectoryName.c_str(), psEntry->pszName, nullptr);
111 : VSIStatBufL sStat;
112 : const std::string osZarrJsonFilename =
113 88 : CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
114 88 : if (VSIStatL(osZarrJsonFilename.c_str(), &sStat) == 0)
115 : {
116 83 : CPLJSONDocument oDoc;
117 83 : if (oDoc.Load(osZarrJsonFilename.c_str()))
118 : {
119 83 : const auto oRoot = oDoc.GetRoot();
120 83 : if (oRoot.GetInteger("zarr_format") != 3)
121 : {
122 0 : CPLError(CE_Warning, CPLE_AppDefined,
123 : "Unhandled zarr_format value");
124 0 : continue;
125 : }
126 166 : const std::string osNodeType = oRoot.GetString("node_type");
127 83 : if (osNodeType == "array")
128 : {
129 52 : if (std::find(m_aosArrays.begin(), m_aosArrays.end(),
130 52 : psEntry->pszName) == m_aosArrays.end())
131 : {
132 51 : m_aosArrays.emplace_back(psEntry->pszName);
133 : }
134 : }
135 31 : else if (osNodeType == "group")
136 : {
137 31 : if (std::find(m_aosGroups.begin(), m_aosGroups.end(),
138 31 : psEntry->pszName) == m_aosGroups.end())
139 : {
140 31 : m_aosGroups.emplace_back(psEntry->pszName);
141 : }
142 : }
143 : else
144 : {
145 0 : CPLError(CE_Warning, CPLE_AppDefined,
146 : "Unhandled node_type value");
147 0 : continue;
148 : }
149 : }
150 : }
151 : else
152 : {
153 : // Implicit group
154 5 : if (std::find(m_aosGroups.begin(), m_aosGroups.end(),
155 5 : psEntry->pszName) == m_aosGroups.end())
156 : {
157 5 : m_aosGroups.emplace_back(psEntry->pszName);
158 : }
159 : }
160 : }
161 145 : }
162 57 : VSICloseDir(psDir);
163 : }
164 :
165 : /************************************************************************/
166 : /* ZarrV3Group::ZarrV3Group() */
167 : /************************************************************************/
168 :
169 415 : ZarrV3Group::ZarrV3Group(
170 : const std::shared_ptr<ZarrSharedResource> &poSharedResource,
171 : const std::string &osParentName, const std::string &osName,
172 415 : const std::string &osDirectoryName)
173 415 : : ZarrGroupBase(poSharedResource, osParentName, osName)
174 : {
175 415 : m_osDirectoryName = osDirectoryName;
176 415 : }
177 :
178 : /************************************************************************/
179 : /* ZarrV3Group::~ZarrV3Group() */
180 : /************************************************************************/
181 :
182 830 : ZarrV3Group::~ZarrV3Group()
183 : {
184 415 : if (m_bValid && m_oAttrGroup.IsModified())
185 : {
186 26 : CPLJSONDocument oDoc;
187 26 : auto oRoot = oDoc.GetRoot();
188 13 : oRoot.Add("zarr_format", 3);
189 13 : oRoot.Add("node_type", "group");
190 13 : oRoot.Add("attributes", m_oAttrGroup.Serialize());
191 : const std::string osZarrJsonFilename = CPLFormFilenameSafe(
192 26 : m_osDirectoryName.c_str(), "zarr.json", nullptr);
193 13 : oDoc.Save(osZarrJsonFilename);
194 : }
195 830 : }
196 :
197 : /************************************************************************/
198 : /* OpenZarrGroup() */
199 : /************************************************************************/
200 :
201 : std::shared_ptr<ZarrGroupBase>
202 47 : ZarrV3Group::OpenZarrGroup(const std::string &osName, CSLConstList) const
203 : {
204 47 : if (!CheckValidAndErrorOutIfNot())
205 0 : return nullptr;
206 :
207 47 : auto oIter = m_oMapGroups.find(osName);
208 47 : if (oIter != m_oMapGroups.end())
209 3 : return oIter->second;
210 :
211 : const std::string osSubDir =
212 88 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
213 : const std::string osSubDirZarrJsonFilename =
214 88 : CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
215 :
216 : VSIStatBufL sStat;
217 : // Explicit group
218 44 : if (VSIStatL(osSubDirZarrJsonFilename.c_str(), &sStat) == 0)
219 : {
220 72 : CPLJSONDocument oDoc;
221 36 : if (oDoc.Load(osSubDirZarrJsonFilename.c_str()))
222 : {
223 72 : const auto oRoot = oDoc.GetRoot();
224 36 : if (oRoot.GetInteger("zarr_format") != 3)
225 : {
226 0 : CPLError(CE_Failure, CPLE_AppDefined,
227 : "Unhandled zarr_format value");
228 0 : return nullptr;
229 : }
230 108 : const std::string osNodeType = oRoot.GetString("node_type");
231 36 : if (osNodeType != "group")
232 : {
233 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s is a %s, not a group",
234 : osName.c_str(), osNodeType.c_str());
235 0 : return nullptr;
236 : }
237 : auto poSubGroup = ZarrV3Group::Create(
238 72 : m_poSharedResource, GetFullName(), osName, osSubDir);
239 36 : poSubGroup->m_poParent =
240 72 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
241 36 : poSubGroup->SetUpdatable(m_bUpdatable);
242 36 : m_oMapGroups[osName] = poSubGroup;
243 36 : return poSubGroup;
244 : }
245 0 : return nullptr;
246 : }
247 :
248 : // Implicit group
249 8 : if (VSIStatL(osSubDir.c_str(), &sStat) == 0 && VSI_ISDIR(sStat.st_mode))
250 : {
251 : // Note: Python zarr v3.0.2 still generates implicit groups
252 : // See https://github.com/zarr-developers/zarr-python/issues/2794
253 2 : CPLError(CE_Warning, CPLE_AppDefined,
254 : "Support for Zarr V3 implicit group is now deprecated, and "
255 : "may be removed in a future version");
256 2 : auto poSubGroup = ZarrV3Group::Create(m_poSharedResource, GetFullName(),
257 4 : osName, osSubDir);
258 2 : poSubGroup->m_poParent =
259 4 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
260 2 : poSubGroup->SetUpdatable(m_bUpdatable);
261 2 : m_oMapGroups[osName] = poSubGroup;
262 2 : return poSubGroup;
263 : }
264 :
265 6 : return nullptr;
266 : }
267 :
268 : /************************************************************************/
269 : /* ZarrV3Group::CreateOnDisk() */
270 : /************************************************************************/
271 :
272 164 : std::shared_ptr<ZarrV3Group> ZarrV3Group::CreateOnDisk(
273 : const std::shared_ptr<ZarrSharedResource> &poSharedResource,
274 : const std::string &osParentFullName, const std::string &osName,
275 : const std::string &osDirectoryName)
276 : {
277 164 : if (VSIMkdir(osDirectoryName.c_str(), 0755) != 0)
278 : {
279 : VSIStatBufL sStat;
280 1 : if (VSIStatL(osDirectoryName.c_str(), &sStat) == 0)
281 : {
282 1 : CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
283 : osDirectoryName.c_str());
284 : }
285 : else
286 : {
287 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
288 : osDirectoryName.c_str());
289 : }
290 1 : return nullptr;
291 : }
292 :
293 : const std::string osZarrJsonFilename(
294 326 : CPLFormFilenameSafe(osDirectoryName.c_str(), "zarr.json", nullptr));
295 163 : VSILFILE *fp = VSIFOpenL(osZarrJsonFilename.c_str(), "wb");
296 163 : if (!fp)
297 : {
298 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create file %s.",
299 : osZarrJsonFilename.c_str());
300 0 : return nullptr;
301 : }
302 163 : VSIFPrintfL(fp, "{\n"
303 : " \"zarr_format\": 3,\n"
304 : " \"node_type\": \"group\",\n"
305 : " \"attributes\": {}\n"
306 : "}\n");
307 163 : VSIFCloseL(fp);
308 :
309 : auto poGroup = ZarrV3Group::Create(poSharedResource, osParentFullName,
310 326 : osName, osDirectoryName);
311 163 : poGroup->SetUpdatable(true);
312 163 : poGroup->m_bDirectoryExplored = true;
313 163 : return poGroup;
314 : }
315 :
316 : /************************************************************************/
317 : /* ZarrV3Group::CreateGroup() */
318 : /************************************************************************/
319 :
320 : std::shared_ptr<GDALGroup>
321 51 : ZarrV3Group::CreateGroup(const std::string &osName,
322 : CSLConstList /* papszOptions */)
323 : {
324 51 : if (!CheckValidAndErrorOutIfNot())
325 0 : return nullptr;
326 :
327 51 : if (!m_bUpdatable)
328 : {
329 1 : CPLError(CE_Failure, CPLE_NotSupported,
330 : "Dataset not open in update mode");
331 1 : return nullptr;
332 : }
333 50 : if (!IsValidObjectName(osName))
334 : {
335 14 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid group name");
336 14 : return nullptr;
337 : }
338 :
339 36 : GetGroupNames();
340 :
341 36 : if (std::find(m_aosGroups.begin(), m_aosGroups.end(), osName) !=
342 72 : m_aosGroups.end())
343 : {
344 3 : CPLError(CE_Failure, CPLE_AppDefined,
345 : "A group with same name already exists");
346 3 : return nullptr;
347 : }
348 :
349 : const std::string osDirectoryName =
350 66 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
351 33 : auto poGroup = CreateOnDisk(m_poSharedResource, GetFullName(), osName,
352 66 : osDirectoryName);
353 33 : if (!poGroup)
354 1 : return nullptr;
355 32 : poGroup->m_poParent =
356 64 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
357 32 : m_oMapGroups[osName] = poGroup;
358 32 : m_aosGroups.emplace_back(osName);
359 32 : return poGroup;
360 : }
361 :
362 : /************************************************************************/
363 : /* FillDTypeElts() */
364 : /************************************************************************/
365 :
366 129 : static CPLJSONObject FillDTypeElts(const GDALExtendedDataType &oDataType,
367 : std::vector<DtypeElt> &aoDtypeElts)
368 : {
369 129 : CPLJSONObject dtype;
370 258 : const std::string dummy("dummy");
371 :
372 129 : const auto eDT = oDataType.GetNumericDataType();
373 258 : DtypeElt elt;
374 129 : bool bUnsupported = false;
375 129 : switch (eDT)
376 : {
377 58 : case GDT_Byte:
378 : {
379 58 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
380 58 : dtype.Set(dummy, "uint8");
381 58 : break;
382 : }
383 6 : case GDT_Int8:
384 : {
385 6 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
386 6 : dtype.Set(dummy, "int8");
387 6 : break;
388 : }
389 7 : case GDT_UInt16:
390 : {
391 7 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
392 7 : dtype.Set(dummy, "uint16");
393 7 : break;
394 : }
395 9 : case GDT_Int16:
396 : {
397 9 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
398 9 : dtype.Set(dummy, "int16");
399 9 : break;
400 : }
401 7 : case GDT_UInt32:
402 : {
403 7 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
404 7 : dtype.Set(dummy, "uint32");
405 7 : break;
406 : }
407 7 : case GDT_Int32:
408 : {
409 7 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
410 7 : dtype.Set(dummy, "int32");
411 7 : break;
412 : }
413 6 : case GDT_UInt64:
414 : {
415 6 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
416 6 : dtype.Set(dummy, "uint64");
417 6 : break;
418 : }
419 6 : case GDT_Int64:
420 : {
421 6 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
422 6 : dtype.Set(dummy, "int64");
423 6 : break;
424 : }
425 1 : case GDT_Float16:
426 : {
427 1 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
428 1 : dtype.Set(dummy, "float16");
429 1 : break;
430 : }
431 7 : case GDT_Float32:
432 : {
433 7 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
434 7 : dtype.Set(dummy, "float32");
435 7 : break;
436 : }
437 11 : case GDT_Float64:
438 : {
439 11 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
440 11 : dtype.Set(dummy, "float64");
441 11 : break;
442 : }
443 2 : case GDT_Unknown:
444 : case GDT_CInt16:
445 : case GDT_CInt32:
446 : {
447 2 : bUnsupported = true;
448 2 : break;
449 : }
450 0 : case GDT_CFloat16:
451 : {
452 0 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
453 0 : dtype.Set(dummy, "complex32");
454 0 : break;
455 : }
456 1 : case GDT_CFloat32:
457 : {
458 1 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
459 1 : dtype.Set(dummy, "complex64");
460 1 : break;
461 : }
462 1 : case GDT_CFloat64:
463 : {
464 1 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
465 1 : dtype.Set(dummy, "complex128");
466 1 : break;
467 : }
468 0 : case GDT_TypeCount:
469 : {
470 : static_assert(GDT_TypeCount == GDT_CFloat16 + 1,
471 : "GDT_TypeCount == GDT_CFloat16 + 1");
472 0 : break;
473 : }
474 : }
475 129 : if (bUnsupported)
476 : {
477 2 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %s",
478 : GDALGetDataTypeName(eDT));
479 2 : dtype = CPLJSONObject();
480 2 : dtype.Deinit();
481 2 : return dtype;
482 : }
483 127 : elt.nativeOffset = 0;
484 127 : elt.nativeSize = GDALGetDataTypeSizeBytes(eDT);
485 127 : elt.gdalOffset = 0;
486 127 : elt.gdalSize = elt.nativeSize;
487 : #ifdef CPL_MSB
488 : elt.needByteSwapping = elt.nativeSize > 1;
489 : #endif
490 127 : aoDtypeElts.emplace_back(elt);
491 :
492 127 : return dtype;
493 : }
494 :
495 : /************************************************************************/
496 : /* ZarrV3Group::CreateMDArray() */
497 : /************************************************************************/
498 :
499 147 : std::shared_ptr<GDALMDArray> ZarrV3Group::CreateMDArray(
500 : const std::string &osName,
501 : const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
502 : const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
503 : {
504 147 : if (!CheckValidAndErrorOutIfNot())
505 0 : return nullptr;
506 :
507 147 : if (!m_bUpdatable)
508 : {
509 0 : CPLError(CE_Failure, CPLE_NotSupported,
510 : "Dataset not open in update mode");
511 0 : return nullptr;
512 : }
513 147 : if (!IsValidObjectName(osName))
514 : {
515 14 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid array name");
516 14 : return nullptr;
517 : }
518 :
519 133 : if (oDataType.GetClass() != GEDTC_NUMERIC)
520 : {
521 4 : CPLError(CE_Failure, CPLE_AppDefined,
522 : "Unsupported data type with Zarr V3");
523 4 : return nullptr;
524 : }
525 :
526 129 : if (!EQUAL(CSLFetchNameValueDef(papszOptions, "FILTER", "NONE"), "NONE"))
527 : {
528 0 : CPLError(CE_Failure, CPLE_AppDefined,
529 : "FILTER option not supported with Zarr V3");
530 0 : return nullptr;
531 : }
532 :
533 258 : std::vector<DtypeElt> aoDtypeElts;
534 387 : const auto dtype = FillDTypeElts(oDataType, aoDtypeElts)["dummy"];
535 129 : if (!dtype.IsValid() || aoDtypeElts.empty())
536 2 : return nullptr;
537 :
538 127 : GetMDArrayNames();
539 :
540 127 : if (std::find(m_aosArrays.begin(), m_aosArrays.end(), osName) !=
541 254 : m_aosArrays.end())
542 : {
543 2 : CPLError(CE_Failure, CPLE_AppDefined,
544 : "An array with same name already exists");
545 2 : return nullptr;
546 : }
547 :
548 250 : std::vector<GUInt64> anBlockSize;
549 125 : if (!ZarrArray::FillBlockSize(aoDimensions, oDataType, anBlockSize,
550 : papszOptions))
551 1 : return nullptr;
552 :
553 : const char *pszDimSeparator =
554 124 : CSLFetchNameValueDef(papszOptions, "DIM_SEPARATOR", "/");
555 :
556 : const std::string osArrayDirectory =
557 248 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
558 124 : if (VSIMkdir(osArrayDirectory.c_str(), 0755) != 0)
559 : {
560 : VSIStatBufL sStat;
561 2 : if (VSIStatL(osArrayDirectory.c_str(), &sStat) == 0)
562 : {
563 2 : CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
564 : osArrayDirectory.c_str());
565 : }
566 : else
567 : {
568 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
569 : osArrayDirectory.c_str());
570 : }
571 2 : return nullptr;
572 : }
573 :
574 122 : std::unique_ptr<ZarrV3CodecSequence> poCodecs;
575 244 : CPLJSONArray oCodecs;
576 :
577 122 : const bool bFortranOrder = EQUAL(
578 : CSLFetchNameValueDef(papszOptions, "CHUNK_MEMORY_LAYOUT", "C"), "F");
579 122 : if (bFortranOrder)
580 : {
581 80 : CPLJSONObject oCodec;
582 40 : oCodec.Add("name", "transpose");
583 40 : oCodec.Add("configuration",
584 80 : ZarrV3CodecTranspose::GetConfiguration("F"));
585 40 : oCodecs.Add(oCodec);
586 : }
587 :
588 : // Not documented option, but 'bytes' codec is required
589 : const char *pszEndian =
590 122 : CSLFetchNameValueDef(papszOptions, "@ENDIAN", "little");
591 : {
592 244 : CPLJSONObject oCodec;
593 122 : oCodec.Add("name", "bytes");
594 122 : oCodec.Add("configuration", ZarrV3CodecBytes::GetConfiguration(
595 122 : EQUAL(pszEndian, "little")));
596 122 : oCodecs.Add(oCodec);
597 : }
598 :
599 : const char *pszCompressor =
600 122 : CSLFetchNameValueDef(papszOptions, "COMPRESS", "NONE");
601 122 : if (EQUAL(pszCompressor, "GZIP"))
602 : {
603 46 : CPLJSONObject oCodec;
604 23 : oCodec.Add("name", "gzip");
605 : const char *pszLevel =
606 23 : CSLFetchNameValueDef(papszOptions, "GZIP_LEVEL", "6");
607 23 : oCodec.Add("configuration",
608 46 : ZarrV3CodecGZip::GetConfiguration(atoi(pszLevel)));
609 23 : oCodecs.Add(oCodec);
610 : }
611 99 : else if (EQUAL(pszCompressor, "BLOSC"))
612 : {
613 2 : const auto psCompressor = CPLGetCompressor("blosc");
614 2 : if (!psCompressor)
615 0 : return nullptr;
616 : const char *pszOptions =
617 2 : CSLFetchNameValueDef(psCompressor->papszMetadata, "OPTIONS", "");
618 2 : CPLXMLTreeCloser oTreeCompressor(CPLParseXMLString(pszOptions));
619 : const auto psRoot =
620 2 : oTreeCompressor.get()
621 2 : ? CPLGetXMLNode(oTreeCompressor.get(), "=Options")
622 2 : : nullptr;
623 2 : if (!psRoot)
624 0 : return nullptr;
625 :
626 2 : const char *cname = "zlib";
627 14 : for (const CPLXMLNode *psNode = psRoot->psChild; psNode != nullptr;
628 12 : psNode = psNode->psNext)
629 : {
630 12 : if (psNode->eType == CXT_Element)
631 : {
632 12 : const char *pszName = CPLGetXMLValue(psNode, "name", "");
633 12 : if (EQUAL(pszName, "CNAME"))
634 : {
635 2 : cname = CPLGetXMLValue(psNode, "default", cname);
636 : }
637 : }
638 : }
639 :
640 4 : CPLJSONObject oCodec;
641 2 : oCodec.Add("name", "blosc");
642 2 : cname = CSLFetchNameValueDef(papszOptions, "BLOSC_CNAME", cname);
643 : const int clevel =
644 2 : atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_CLEVEL", "5"));
645 : const char *shuffle =
646 2 : CSLFetchNameValueDef(papszOptions, "BLOSC_SHUFFLE", "BYTE");
647 3 : shuffle = (EQUAL(shuffle, "0") || EQUAL(shuffle, "NONE")) ? "noshuffle"
648 1 : : (EQUAL(shuffle, "1") || EQUAL(shuffle, "BYTE")) ? "shuffle"
649 0 : : (EQUAL(shuffle, "2") || EQUAL(shuffle, "BIT"))
650 0 : ? "bitshuffle"
651 : : "invalid";
652 2 : const int typesize = atoi(CSLFetchNameValueDef(
653 : papszOptions, "BLOSC_TYPESIZE",
654 : CPLSPrintf("%d", GDALGetDataTypeSizeBytes(GDALGetNonComplexDataType(
655 : oDataType.GetNumericDataType())))));
656 : const int blocksize =
657 2 : atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_BLOCKSIZE", "0"));
658 2 : oCodec.Add("configuration",
659 4 : ZarrV3CodecBlosc::GetConfiguration(cname, clevel, shuffle,
660 : typesize, blocksize));
661 2 : oCodecs.Add(oCodec);
662 : }
663 97 : else if (EQUAL(pszCompressor, "ZSTD"))
664 : {
665 4 : CPLJSONObject oCodec;
666 2 : oCodec.Add("name", "zstd");
667 : const char *pszLevel =
668 2 : CSLFetchNameValueDef(papszOptions, "ZSTD_LEVEL", "13");
669 2 : const bool bChecksum = CPLTestBool(
670 : CSLFetchNameValueDef(papszOptions, "ZSTD_CHECKSUM", "FALSE"));
671 2 : oCodec.Add("configuration", ZarrV3CodecZstd::GetConfiguration(
672 : atoi(pszLevel), bChecksum));
673 2 : oCodecs.Add(oCodec);
674 : }
675 95 : else if (!EQUAL(pszCompressor, "NONE"))
676 : {
677 1 : CPLError(CE_Failure, CPLE_AppDefined,
678 : "COMPRESS = %s not implemented with Zarr V3", pszCompressor);
679 1 : return nullptr;
680 : }
681 :
682 121 : if (oCodecs.Size() > 0)
683 : {
684 : // Byte swapping will be done by the codec chain
685 121 : aoDtypeElts.back().needByteSwapping = false;
686 :
687 121 : ZarrArrayMetadata oInputArrayMetadata;
688 273 : for (auto &nSize : anBlockSize)
689 152 : oInputArrayMetadata.anBlockSizes.push_back(
690 152 : static_cast<size_t>(nSize));
691 121 : oInputArrayMetadata.oElt = aoDtypeElts.back();
692 121 : poCodecs = std::make_unique<ZarrV3CodecSequence>(oInputArrayMetadata);
693 121 : if (!poCodecs->InitFromJson(oCodecs))
694 0 : return nullptr;
695 : }
696 :
697 : auto poArray =
698 121 : ZarrV3Array::Create(m_poSharedResource, GetFullName(), osName,
699 242 : aoDimensions, oDataType, aoDtypeElts, anBlockSize);
700 :
701 121 : if (!poArray)
702 0 : return nullptr;
703 121 : poArray->SetNew(true);
704 : const std::string osFilename =
705 242 : CPLFormFilenameSafe(osArrayDirectory.c_str(), "zarr.json", nullptr);
706 121 : poArray->SetFilename(osFilename);
707 121 : poArray->SetDimSeparator(pszDimSeparator);
708 121 : poArray->SetDtype(dtype);
709 121 : if (poCodecs)
710 121 : poArray->SetCodecs(std::move(poCodecs));
711 121 : poArray->SetUpdatable(true);
712 121 : poArray->SetDefinitionModified(true);
713 121 : poArray->Flush();
714 121 : RegisterArray(poArray);
715 :
716 121 : return poArray;
717 : }
|