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