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