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 1651 : 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 1651 : poSharedResource, osParentName, osName, osRootDirectoryName));
33 1651 : poGroup->SetSelf(poGroup);
34 1651 : return poGroup;
35 : }
36 :
37 : /************************************************************************/
38 : /* OpenZarrArray() */
39 : /************************************************************************/
40 :
41 1640 : std::shared_ptr<ZarrArray> ZarrV3Group::OpenZarrArray(const std::string &osName,
42 : CSLConstList) const
43 : {
44 1640 : if (!CheckValidAndErrorOutIfNot())
45 0 : return nullptr;
46 :
47 1640 : auto oIter = m_oMapMDArrays.find(osName);
48 1640 : if (oIter != m_oMapMDArrays.end())
49 1365 : return oIter->second;
50 :
51 275 : if (m_bReadFromConsolidatedMetadata)
52 13 : return nullptr;
53 :
54 : const std::string osSubDir =
55 524 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
56 : const std::string osZarrayFilename =
57 524 : CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
58 :
59 : VSIStatBufL sStat;
60 262 : if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
61 : {
62 248 : CPLJSONDocument oDoc;
63 124 : if (!oDoc.Load(osZarrayFilename))
64 0 : return nullptr;
65 248 : const auto oRoot = oDoc.GetRoot();
66 124 : return LoadArray(osName, osZarrayFilename, oRoot);
67 : }
68 :
69 138 : return nullptr;
70 : }
71 :
72 : /************************************************************************/
73 : /* ZarrV3Group::LoadAttributes() */
74 : /************************************************************************/
75 :
76 629 : void ZarrV3Group::LoadAttributes() const
77 : {
78 629 : if (m_bAttributesLoaded)
79 565 : return;
80 64 : m_bAttributesLoaded = true;
81 :
82 : const std::string osFilename =
83 64 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), "zarr.json", nullptr);
84 :
85 : VSIStatBufL sStat;
86 64 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
87 : {
88 58 : CPLJSONDocument oDoc;
89 58 : if (!oDoc.Load(osFilename))
90 0 : return;
91 58 : auto oRoot = oDoc.GetRoot();
92 58 : m_oAttrGroup.Init(oRoot["attributes"], m_bUpdatable);
93 : }
94 : }
95 :
96 : /************************************************************************/
97 : /* ExploreDirectory() */
98 : /************************************************************************/
99 :
100 71 : void ZarrV3Group::ExploreDirectory() const
101 : {
102 71 : if (m_bDirectoryExplored)
103 0 : return;
104 71 : m_bDirectoryExplored = true;
105 :
106 71 : auto psDir = VSIOpenDir(m_osDirectoryName.c_str(), 0, nullptr);
107 71 : if (!psDir)
108 0 : return;
109 247 : while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir))
110 : {
111 176 : if (VSI_ISDIR(psEntry->nMode))
112 : {
113 210 : std::string osName(psEntry->pszName);
114 210 : while (!osName.empty() &&
115 105 : (osName.back() == '/' || osName.back() == '\\'))
116 0 : osName.pop_back();
117 105 : if (osName.empty())
118 0 : continue;
119 : const std::string osSubDir = CPLFormFilenameSafe(
120 105 : m_osDirectoryName.c_str(), osName.c_str(), nullptr);
121 : VSIStatBufL sStat;
122 : const std::string osZarrJsonFilename =
123 105 : CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
124 105 : if (VSIStatL(osZarrJsonFilename.c_str(), &sStat) == 0)
125 : {
126 103 : CPLJSONDocument oDoc;
127 103 : if (oDoc.Load(osZarrJsonFilename.c_str()))
128 : {
129 103 : const auto oRoot = oDoc.GetRoot();
130 103 : if (oRoot.GetInteger("zarr_format") != 3)
131 : {
132 0 : CPLError(CE_Warning, CPLE_AppDefined,
133 : "Unhandled zarr_format value");
134 0 : continue;
135 : }
136 206 : const std::string osNodeType = oRoot.GetString("node_type");
137 103 : if (osNodeType == "array")
138 : {
139 60 : if (!cpl::contains(m_oSetArrayNames, osName))
140 : {
141 58 : m_oSetArrayNames.insert(osName);
142 58 : 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 176 : }
172 71 : VSICloseDir(psDir);
173 : }
174 :
175 : /************************************************************************/
176 : /* ZarrV3Group::ZarrV3Group() */
177 : /************************************************************************/
178 :
179 1651 : ZarrV3Group::ZarrV3Group(
180 : const std::shared_ptr<ZarrSharedResource> &poSharedResource,
181 : const std::string &osParentName, const std::string &osName,
182 1651 : const std::string &osDirectoryName)
183 1651 : : ZarrGroupBase(poSharedResource, osParentName, osName)
184 : {
185 1651 : m_osDirectoryName = osDirectoryName;
186 1651 : }
187 :
188 : /************************************************************************/
189 : /* ZarrV3Group::~ZarrV3Group() */
190 : /************************************************************************/
191 :
192 3302 : ZarrV3Group::~ZarrV3Group()
193 : {
194 1651 : ZarrV3Group::Close();
195 3302 : }
196 :
197 : /************************************************************************/
198 : /* GenerateMultiscalesMetadata() */
199 : /************************************************************************/
200 :
201 14 : void ZarrV3Group::GenerateMultiscalesMetadata(const char *pszResampling)
202 : {
203 14 : const auto aosGroupNames = GetGroupNames();
204 14 : if (aosGroupNames.empty())
205 : {
206 : // No child groups - remove stale multiscales metadata if present.
207 1 : if (!m_bAttributesLoaded)
208 0 : LoadAttributes();
209 1 : if (m_oAttrGroup.GetAttribute("multiscales"))
210 1 : m_oAttrGroup.DeleteAttribute("multiscales");
211 2 : auto poExistingConv = m_oAttrGroup.GetAttribute("zarr_conventions");
212 1 : if (poExistingConv)
213 : {
214 : // Preserve non-multiscales entries.
215 1 : const char *pszExisting = poExistingConv->ReadAsString();
216 2 : CPLJSONArray oFiltered;
217 1 : if (pszExisting)
218 : {
219 2 : CPLJSONDocument oDoc;
220 1 : if (oDoc.LoadMemory(pszExisting))
221 : {
222 2 : for (const auto &oEntry : oDoc.GetRoot().ToArray())
223 : {
224 1 : if (oEntry.GetString("uuid") != ZARR_MULTISCALES_UUID)
225 0 : oFiltered.Add(oEntry);
226 : }
227 : }
228 : }
229 1 : m_oAttrGroup.DeleteAttribute("zarr_conventions");
230 1 : if (oFiltered.Size() > 0)
231 : {
232 : const auto oJsonDT =
233 0 : GDALExtendedDataType::CreateString(0, GEDTST_JSON);
234 : auto poAttr = m_oAttrGroup.CreateAttribute("zarr_conventions",
235 0 : {}, oJsonDT);
236 0 : if (poAttr)
237 0 : poAttr->Write(
238 0 : oFiltered.Format(CPLJSONObject::PrettyFormat::Plain)
239 : .c_str());
240 : }
241 : }
242 1 : return;
243 : }
244 :
245 : // Collect {arrayName -> [(groupName, array)]} across child groups.
246 : struct LevelInfo
247 : {
248 : std::string osGroupName; // empty for base (this group)
249 : std::shared_ptr<GDALMDArray> poArray;
250 : };
251 :
252 13 : std::map<std::string, std::vector<LevelInfo>> oMapArrayToLevels;
253 :
254 28 : for (const auto &osGroupName : aosGroupNames)
255 : {
256 15 : auto poChildGroup = OpenZarrGroup(osGroupName);
257 15 : if (!poChildGroup)
258 0 : continue;
259 34 : for (const auto &osArrayName : poChildGroup->GetMDArrayNames())
260 : {
261 38 : auto poArray = poChildGroup->OpenMDArray(osArrayName);
262 19 : if (poArray)
263 : {
264 38 : oMapArrayToLevels[osArrayName].push_back(
265 19 : {osGroupName, std::move(poArray)});
266 : }
267 : }
268 : }
269 :
270 13 : if (oMapArrayToLevels.empty())
271 0 : return;
272 :
273 : // For each array found in child groups, check if the base (this group)
274 : // also has an array with the same name. If so, prepend it as the base
275 : // level with an empty group name (meaning "this group").
276 30 : for (auto &[osArrayName, aoLevels] : oMapArrayToLevels)
277 : {
278 34 : auto poBaseArray = OpenMDArray(osArrayName);
279 17 : if (poBaseArray)
280 : {
281 17 : aoLevels.insert(aoLevels.begin(),
282 34 : LevelInfo{"", std::move(poBaseArray)});
283 : }
284 : }
285 :
286 : // Pick the first array name (alphabetical) with >= 2 levels
287 : // (base + at least one overview) and >= 2 dimensions (skip 1D
288 : // coordinate arrays).
289 : //
290 : // Expected hierarchy from BuildOverviews():
291 : // /group/
292 : // data <- base array (e.g. 10980 x 10980)
293 : // y, x <- 1D coordinate arrays (skipped)
294 : // ovr_2x/
295 : // data <- 2x overview (5490 x 5490)
296 : // y, x
297 : // ovr_4x/
298 : // data <- 4x overview (2745 x 2745)
299 : // y, x
300 : //
301 : // Multiple >=2D arrays sharing the same name across levels is
302 : // possible but unusual; we use the first alphabetically.
303 13 : std::string osCanonicalArrayName;
304 17 : for (const auto &[osArrayName, aoLevels] : oMapArrayToLevels)
305 : {
306 34 : if (aoLevels.size() >= 2 &&
307 17 : aoLevels[0].poArray->GetDimensionCount() >= 2)
308 : {
309 13 : osCanonicalArrayName = osArrayName;
310 13 : break;
311 : }
312 : }
313 :
314 13 : if (osCanonicalArrayName.empty())
315 : {
316 0 : CPLDebug("ZARR", "GenerateMultiscalesMetadata: no array with "
317 : ">=2 levels and >=2 dimensions found");
318 0 : return;
319 : }
320 :
321 13 : auto &aoLevels = oMapArrayToLevels[osCanonicalArrayName];
322 :
323 : // Sort by total element count, largest first (= full resolution).
324 13 : std::stable_sort(aoLevels.begin(), aoLevels.end(),
325 17 : [](const LevelInfo &a, const LevelInfo &b)
326 : {
327 17 : const auto &dimsA = a.poArray->GetDimensions();
328 17 : const auto &dimsB = b.poArray->GetDimensions();
329 17 : GUInt64 sizeA = 1, sizeB = 1;
330 56 : for (const auto &d : dimsA)
331 39 : sizeA *= d->GetSize();
332 56 : for (const auto &d : dimsB)
333 39 : sizeB *= d->GetSize();
334 17 : return sizeA > sizeB;
335 : });
336 :
337 13 : const auto &poBaseArray = aoLevels[0].poArray;
338 13 : const size_t nBaseDimCount = poBaseArray->GetDimensionCount();
339 13 : const auto &oBaseType = poBaseArray->GetDataType();
340 :
341 : // Asset path for a level. Empty group name means the base array lives
342 : // in this group - use the array name directly (LoadOverviews resolves
343 : // single-component paths as array names in the parent group).
344 : const auto assetPath =
345 43 : [&osCanonicalArrayName](const std::string &osGroupName) -> std::string
346 43 : { return osGroupName.empty() ? osCanonicalArrayName : osGroupName; };
347 :
348 : // Base level: identity scale, no translation, no derived_from.
349 13 : CPLJSONArray oLayout;
350 : {
351 26 : CPLJSONObject oBaseItem;
352 13 : oBaseItem.Add("asset", assetPath(aoLevels[0].osGroupName));
353 :
354 26 : CPLJSONArray oScale;
355 44 : for (size_t iDim = 0; iDim < nBaseDimCount; ++iDim)
356 31 : oScale.Add(1.0);
357 26 : CPLJSONObject oTransform;
358 13 : oTransform.Add("scale", oScale);
359 13 : oBaseItem.Add("transform", oTransform);
360 :
361 13 : oLayout.Add(oBaseItem);
362 : }
363 :
364 : // Overview levels: sequential derived_from chain.
365 28 : for (size_t iLevel = 1; iLevel < aoLevels.size(); ++iLevel)
366 : {
367 15 : const auto &info = aoLevels[iLevel];
368 15 : const auto &poArray = info.poArray;
369 :
370 30 : if (poArray->GetDimensionCount() != nBaseDimCount ||
371 15 : poArray->GetDataType() != oBaseType)
372 : {
373 0 : CPLDebug("ZARR",
374 : "GenerateMultiscalesMetadata: skipping level '%s' "
375 : "(dim count or data type mismatch with base)",
376 : info.osGroupName.c_str());
377 0 : continue;
378 : }
379 :
380 15 : const auto &apoDims = poArray->GetDimensions();
381 : // Previous valid level for sequential derived_from.
382 15 : const auto &oPrevDims = aoLevels[iLevel - 1].poArray->GetDimensions();
383 :
384 30 : CPLJSONObject oItem;
385 15 : oItem.Add("asset", assetPath(info.osGroupName));
386 15 : oItem.Add("derived_from", assetPath(aoLevels[iLevel - 1].osGroupName));
387 :
388 30 : CPLJSONArray oScale;
389 30 : CPLJSONArray oTranslation;
390 50 : for (size_t iDim = 0; iDim < nBaseDimCount; ++iDim)
391 : {
392 35 : const auto nOvSize = apoDims[iDim]->GetSize();
393 35 : const auto nPrevSize = oPrevDims[iDim]->GetSize();
394 35 : const double dfScale = nOvSize > 0
395 35 : ? static_cast<double>(nPrevSize) /
396 35 : static_cast<double>(nOvSize)
397 : : 0.0;
398 35 : oScale.Add(dfScale);
399 35 : oTranslation.Add(0.0);
400 : }
401 :
402 30 : CPLJSONObject oTransform;
403 15 : oTransform.Add("scale", oScale);
404 15 : oTransform.Add("translation", oTranslation);
405 15 : oItem.Add("transform", oTransform);
406 :
407 15 : if (pszResampling)
408 15 : oItem.Add("resampling_method", pszResampling);
409 :
410 15 : oLayout.Add(oItem);
411 : }
412 :
413 13 : if (oLayout.Size() < 2)
414 0 : return;
415 :
416 26 : CPLJSONObject oMultiscales;
417 13 : oMultiscales.Add("layout", oLayout);
418 :
419 : // Preserve existing zarr_conventions entries.
420 13 : if (!m_bAttributesLoaded)
421 12 : LoadAttributes();
422 :
423 26 : CPLJSONArray oZarrConventions;
424 39 : auto poExistingConv = GetAttribute("zarr_conventions");
425 13 : if (poExistingConv)
426 : {
427 1 : const char *pszExisting = poExistingConv->ReadAsString();
428 1 : if (pszExisting)
429 : {
430 2 : CPLJSONDocument oDoc;
431 1 : if (oDoc.LoadMemory(pszExisting))
432 : {
433 2 : for (const auto &oEntry : oDoc.GetRoot().ToArray())
434 : {
435 1 : if (oEntry.GetString("uuid") != ZARR_MULTISCALES_UUID)
436 0 : oZarrConventions.Add(oEntry);
437 : }
438 : }
439 : }
440 1 : DeleteAttribute("zarr_conventions");
441 : }
442 :
443 : {
444 26 : CPLJSONObject oConv;
445 13 : oConv.Set("uuid", ZARR_MULTISCALES_UUID);
446 13 : oConv.Set("schema_url",
447 : "https://raw.githubusercontent.com/zarr-conventions/"
448 : "multiscales/refs/tags/v1/schema.json");
449 13 : oConv.Set("spec_url", "https://github.com/zarr-conventions/"
450 : "multiscales/blob/v1/README.md");
451 13 : oConv.Set("name", "multiscales");
452 13 : oConv.Set("description", "Multiscale layout of zarr datasets");
453 13 : oZarrConventions.Add(oConv);
454 : }
455 :
456 13 : if (GetAttribute("multiscales"))
457 1 : DeleteAttribute("multiscales");
458 :
459 26 : const auto oJsonDT = GDALExtendedDataType::CreateString(0, GEDTST_JSON);
460 : {
461 39 : auto poAttr = CreateAttribute("zarr_conventions", {}, oJsonDT);
462 13 : if (poAttr)
463 26 : poAttr->Write(
464 26 : oZarrConventions.Format(CPLJSONObject::PrettyFormat::Plain)
465 : .c_str());
466 : }
467 : {
468 39 : auto poAttr = CreateAttribute("multiscales", {}, oJsonDT);
469 13 : if (poAttr)
470 26 : poAttr->Write(
471 26 : oMultiscales.Format(CPLJSONObject::PrettyFormat::Plain)
472 : .c_str());
473 : }
474 : }
475 :
476 : /************************************************************************/
477 : /* Close() */
478 : /************************************************************************/
479 :
480 3754 : bool ZarrV3Group::Close()
481 : {
482 3754 : bool bRet = ZarrGroupBase::Close();
483 :
484 7421 : if (m_bValid && (m_oAttrGroup.IsModified() ||
485 3763 : (m_bUpdatable && !m_bFileHasBeenWritten &&
486 96 : m_poSharedResource->IsConsolidatedMetadataEnabled())))
487 : {
488 124 : LoadAttributes();
489 :
490 248 : CPLJSONDocument oDoc;
491 248 : auto oRoot = oDoc.GetRoot();
492 124 : oRoot.Add("zarr_format", 3);
493 124 : oRoot.Add("node_type", "group");
494 124 : oRoot.Add("attributes", m_oAttrGroup.Serialize());
495 : const std::string osZarrJsonFilename = CPLFormFilenameSafe(
496 124 : m_osDirectoryName.c_str(), "zarr.json", nullptr);
497 124 : if (!m_bFileHasBeenWritten)
498 : {
499 49 : oRoot.Add("consolidated_metadata",
500 49 : m_poSharedResource->GetConsolidatedMetadataObj());
501 49 : bRet = oDoc.Save(osZarrJsonFilename) && bRet;
502 : }
503 : else
504 : {
505 75 : bRet = oDoc.Save(osZarrJsonFilename) && bRet;
506 75 : if (bRet)
507 74 : m_poSharedResource->SetZMetadataItem(osZarrJsonFilename, oRoot);
508 : }
509 124 : m_bFileHasBeenWritten = bRet;
510 : }
511 :
512 3754 : return bRet;
513 : }
514 :
515 : /************************************************************************/
516 : /* ZarrV3Group::GetOrCreateSubGroup() */
517 : /************************************************************************/
518 :
519 : std::shared_ptr<ZarrV3Group>
520 404 : ZarrV3Group::GetOrCreateSubGroup(const std::string &osSubGroupFullname)
521 : {
522 : auto poSubGroup = std::dynamic_pointer_cast<ZarrV3Group>(
523 404 : OpenGroupFromFullname(osSubGroupFullname));
524 404 : if (poSubGroup)
525 : {
526 252 : return poSubGroup;
527 : }
528 :
529 152 : const auto nLastSlashPos = osSubGroupFullname.rfind('/');
530 : auto poBelongingGroup =
531 : (nLastSlashPos == 0)
532 152 : ? this
533 166 : : GetOrCreateSubGroup(osSubGroupFullname.substr(0, nLastSlashPos))
534 152 : .get();
535 :
536 304 : poSubGroup = ZarrV3Group::Create(
537 152 : m_poSharedResource, poBelongingGroup->GetFullName(),
538 456 : osSubGroupFullname.substr(nLastSlashPos + 1), m_osDirectoryName);
539 304 : poSubGroup->m_poParent = std::dynamic_pointer_cast<ZarrGroupBase>(
540 456 : poBelongingGroup->m_pSelf.lock());
541 304 : poSubGroup->SetDirectoryName(
542 304 : CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
543 152 : poSubGroup->GetName().c_str(), nullptr));
544 152 : poSubGroup->m_bDirectoryExplored = true;
545 152 : poSubGroup->m_bReadFromConsolidatedMetadata = true;
546 152 : poSubGroup->m_bFileHasBeenWritten = true;
547 152 : poSubGroup->SetUpdatable(m_bUpdatable);
548 :
549 152 : poBelongingGroup->m_oMapGroups[poSubGroup->GetName()] = poSubGroup;
550 152 : poBelongingGroup->m_oSetGroupNames.insert(poSubGroup->GetName());
551 152 : poBelongingGroup->m_aosGroups.emplace_back(poSubGroup->GetName());
552 152 : return poSubGroup;
553 : }
554 :
555 : /************************************************************************/
556 : /* ZarrV3Group::InitFromConsolidatedMetadata() */
557 : /************************************************************************/
558 :
559 262 : void ZarrV3Group::InitFromConsolidatedMetadata(
560 : const CPLJSONObject &oConsolidatedMetadata,
561 : const CPLJSONObject &oRootAttributes)
562 : {
563 524 : const auto metadata = oConsolidatedMetadata["metadata"];
564 262 : if (metadata.GetType() != CPLJSONObject::Type::Object)
565 : {
566 0 : CPLError(CE_Warning, CPLE_AppDefined,
567 : "consolidated_metadata lacks 'metadata' object");
568 0 : return;
569 : }
570 262 : m_bDirectoryExplored = true;
571 262 : m_bAttributesLoaded = oRootAttributes.IsValid();
572 262 : m_bReadFromConsolidatedMetadata = true;
573 :
574 262 : if (oRootAttributes.IsValid())
575 : {
576 238 : m_oAttrGroup.Init(oRootAttributes, m_bUpdatable);
577 : }
578 :
579 524 : const auto children = metadata.GetChildren();
580 524 : std::map<std::string, const CPLJSONObject *> oMapArrays;
581 :
582 : // First pass to create groups and collect arrays
583 989 : for (const auto &child : children)
584 : {
585 727 : const std::string osName(child.GetName());
586 727 : if (std::count(osName.begin(), osName.end(), '/') > 32)
587 : {
588 : // Avoid too deep recursion in GetOrCreateSubGroup()
589 0 : continue;
590 : }
591 :
592 2181 : const std::string osNodeType = child.GetString("node_type");
593 727 : if (osNodeType == "group")
594 : {
595 304 : auto poGroup = GetOrCreateSubGroup("/" + osName);
596 152 : poGroup->m_bAttributesLoaded = oRootAttributes.IsValid();
597 456 : auto oAttributes = child["attributes"];
598 152 : if (oAttributes.IsValid())
599 : {
600 152 : poGroup->m_oAttrGroup.Init(oAttributes, m_bUpdatable);
601 : }
602 : }
603 575 : else if (osNodeType == "array")
604 : {
605 575 : oMapArrays[osName] = &child;
606 : }
607 : }
608 :
609 : const auto CreateArray =
610 813 : [this](const std::string &osArrayFullname, const CPLJSONObject &oArray)
611 : {
612 575 : const auto nLastSlashPos = osArrayFullname.rfind('/');
613 : auto poBelongingGroup =
614 : (nLastSlashPos == std::string::npos)
615 575 : ? this
616 813 : : GetOrCreateSubGroup("/" +
617 813 : osArrayFullname.substr(0, nLastSlashPos))
618 575 : .get();
619 : const auto osArrayName =
620 : nLastSlashPos == std::string::npos
621 : ? osArrayFullname
622 1150 : : osArrayFullname.substr(nLastSlashPos + 1);
623 : const std::string osZarrayFilename = CPLFormFilenameSafe(
624 575 : CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
625 : osArrayName.c_str(), nullptr)
626 : .c_str(),
627 575 : "zarr.json", nullptr);
628 575 : poBelongingGroup->LoadArray(osArrayName, osZarrayFilename, oArray);
629 575 : };
630 :
631 : struct ArrayDesc
632 : {
633 : std::string osArrayFullname{};
634 : const CPLJSONObject *poArray = nullptr;
635 : };
636 :
637 524 : std::vector<ArrayDesc> aoRegularArrays;
638 :
639 : // Second pass to read attributes and create arrays that are indexing
640 : // variable
641 989 : for (const auto &child : children)
642 : {
643 1454 : const std::string osName(child.GetName());
644 2181 : const std::string osNodeType = child.GetString("node_type");
645 727 : if (osNodeType == "array")
646 : {
647 575 : auto oIter = oMapArrays.find(osName);
648 575 : if (oIter != oMapArrays.end())
649 : {
650 575 : const auto nLastSlashPos = osName.rfind('/');
651 : const std::string osArrayName =
652 : (nLastSlashPos == std::string::npos)
653 : ? osName
654 1150 : : osName.substr(nLastSlashPos + 1);
655 1725 : const auto arrayDimensions = child["dimension_names"].ToArray();
656 754 : if (arrayDimensions.IsValid() && arrayDimensions.Size() == 1 &&
657 754 : arrayDimensions[0].ToString() == osArrayName)
658 : {
659 162 : CreateArray(osName, child);
660 162 : oMapArrays.erase(oIter);
661 : }
662 : else
663 : {
664 826 : ArrayDesc desc;
665 413 : desc.osArrayFullname = std::move(osName);
666 413 : desc.poArray = oIter->second;
667 413 : aoRegularArrays.emplace_back(std::move(desc));
668 : }
669 : }
670 : }
671 : }
672 :
673 : // Third pass to create non-indexing arrays with attributes
674 675 : for (const auto &desc : aoRegularArrays)
675 : {
676 413 : CreateArray(desc.osArrayFullname, *(desc.poArray));
677 413 : oMapArrays.erase(desc.osArrayFullname);
678 : }
679 :
680 : // Fourth pass to create arrays without attributes
681 262 : for (const auto &kv : oMapArrays)
682 : {
683 0 : CreateArray(kv.first, *(kv.second));
684 : }
685 : }
686 :
687 : /************************************************************************/
688 : /* OpenZarrGroup() */
689 : /************************************************************************/
690 :
691 : std::shared_ptr<ZarrGroupBase>
692 903 : ZarrV3Group::OpenZarrGroup(const std::string &osName, CSLConstList) const
693 : {
694 903 : if (!CheckValidAndErrorOutIfNot())
695 0 : return nullptr;
696 :
697 903 : auto oIter = m_oMapGroups.find(osName);
698 903 : if (oIter != m_oMapGroups.end())
699 615 : return oIter->second;
700 :
701 288 : if (m_bReadFromConsolidatedMetadata)
702 221 : return nullptr;
703 :
704 : const std::string osSubDir =
705 134 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
706 : const std::string osSubDirZarrJsonFilename =
707 134 : CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
708 :
709 : VSIStatBufL sStat;
710 : // Explicit group
711 67 : if (VSIStatL(osSubDirZarrJsonFilename.c_str(), &sStat) == 0)
712 : {
713 118 : CPLJSONDocument oDoc;
714 59 : if (oDoc.Load(osSubDirZarrJsonFilename.c_str()))
715 : {
716 118 : const auto oRoot = oDoc.GetRoot();
717 59 : if (oRoot.GetInteger("zarr_format") != 3)
718 : {
719 0 : CPLError(CE_Failure, CPLE_AppDefined,
720 : "Unhandled zarr_format value");
721 0 : return nullptr;
722 : }
723 177 : const std::string osNodeType = oRoot.GetString("node_type");
724 59 : if (osNodeType != "group")
725 : {
726 6 : CPLError(CE_Failure, CPLE_AppDefined, "%s is a %s, not a group",
727 : osName.c_str(), osNodeType.c_str());
728 6 : return nullptr;
729 : }
730 : auto poSubGroup = ZarrV3Group::Create(
731 106 : m_poSharedResource, GetFullName(), osName, osSubDir);
732 53 : poSubGroup->m_bFileHasBeenWritten = true;
733 53 : poSubGroup->m_poParent =
734 106 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
735 53 : poSubGroup->SetUpdatable(m_bUpdatable);
736 53 : m_oMapGroups[osName] = poSubGroup;
737 53 : return poSubGroup;
738 : }
739 0 : return nullptr;
740 : }
741 :
742 : // Implicit group
743 8 : if (VSIStatL(osSubDir.c_str(), &sStat) == 0 && VSI_ISDIR(sStat.st_mode))
744 : {
745 : // Note: Python zarr v3.0.2 still generates implicit groups
746 : // See https://github.com/zarr-developers/zarr-python/issues/2794
747 2 : CPLError(CE_Warning, CPLE_AppDefined,
748 : "Support for Zarr V3 implicit group is now deprecated, and "
749 : "may be removed in a future version");
750 2 : auto poSubGroup = ZarrV3Group::Create(m_poSharedResource, GetFullName(),
751 4 : osName, osSubDir);
752 2 : poSubGroup->m_bFileHasBeenWritten = true;
753 2 : poSubGroup->m_poParent =
754 4 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
755 2 : poSubGroup->SetUpdatable(m_bUpdatable);
756 2 : m_oMapGroups[osName] = poSubGroup;
757 2 : return poSubGroup;
758 : }
759 :
760 6 : return nullptr;
761 : }
762 :
763 : /************************************************************************/
764 : /* ZarrV3Group::CreateOnDisk() */
765 : /************************************************************************/
766 :
767 326 : std::shared_ptr<ZarrV3Group> ZarrV3Group::CreateOnDisk(
768 : const std::shared_ptr<ZarrSharedResource> &poSharedResource,
769 : const std::string &osParentFullName, const std::string &osName,
770 : const std::string &osDirectoryName)
771 : {
772 326 : if (VSIMkdir(osDirectoryName.c_str(), 0755) != 0)
773 : {
774 : VSIStatBufL sStat;
775 6 : if (VSIStatL(osDirectoryName.c_str(), &sStat) == 0)
776 : {
777 3 : CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
778 : osDirectoryName.c_str());
779 : }
780 : else
781 : {
782 3 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
783 : osDirectoryName.c_str());
784 : }
785 6 : return nullptr;
786 : }
787 :
788 : const std::string osZarrJsonFilename(
789 640 : CPLFormFilenameSafe(osDirectoryName.c_str(), "zarr.json", nullptr));
790 320 : VSILFILE *fp = nullptr;
791 601 : if (!(poSharedResource->IsConsolidatedMetadataEnabled() &&
792 601 : cpl::starts_with(osZarrJsonFilename, "/vsizip/") &&
793 1 : osParentFullName.empty() && osName == "/"))
794 : {
795 319 : fp = VSIFOpenL(osZarrJsonFilename.c_str(), "wb");
796 319 : if (!fp)
797 : {
798 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create file %s.",
799 : osZarrJsonFilename.c_str());
800 0 : return nullptr;
801 : }
802 319 : VSIFPrintfL(fp, "{\n"
803 : " \"zarr_format\": 3,\n"
804 : " \"node_type\": \"group\",\n"
805 : " \"attributes\": {}\n"
806 : "}\n");
807 319 : VSIFCloseL(fp);
808 : }
809 :
810 : auto poGroup = ZarrV3Group::Create(poSharedResource, osParentFullName,
811 640 : osName, osDirectoryName);
812 320 : poGroup->SetUpdatable(true);
813 320 : poGroup->m_bDirectoryExplored = true;
814 320 : poGroup->m_bFileHasBeenWritten = fp != nullptr;
815 :
816 640 : CPLJSONObject oObj;
817 320 : oObj.Add("zarr_format", 3);
818 320 : oObj.Add("node_type", "group");
819 320 : oObj.Add("attributes", CPLJSONObject());
820 320 : poSharedResource->SetZMetadataItem(osZarrJsonFilename, oObj);
821 :
822 320 : return poGroup;
823 : }
824 :
825 : /************************************************************************/
826 : /* ZarrV3Group::CreateGroup() */
827 : /************************************************************************/
828 :
829 : std::shared_ptr<GDALGroup>
830 72 : ZarrV3Group::CreateGroup(const std::string &osName,
831 : CSLConstList /* papszOptions */)
832 : {
833 72 : if (!CheckValidAndErrorOutIfNot())
834 0 : return nullptr;
835 :
836 72 : if (!m_bUpdatable)
837 : {
838 3 : CPLError(CE_Failure, CPLE_NotSupported,
839 : "Dataset not open in update mode");
840 3 : return nullptr;
841 : }
842 69 : if (!IsValidObjectName(osName))
843 : {
844 14 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid group name");
845 14 : return nullptr;
846 : }
847 :
848 55 : GetGroupNames();
849 :
850 55 : if (cpl::contains(m_oSetGroupNames, osName))
851 : {
852 1 : CPLError(CE_Failure, CPLE_AppDefined,
853 : "A group with same name (%s) already exists in group %s",
854 1 : osName.c_str(), GetFullName().c_str());
855 1 : return nullptr;
856 : }
857 :
858 : const std::string osDirectoryName =
859 108 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
860 54 : auto poGroup = CreateOnDisk(m_poSharedResource, GetFullName(), osName,
861 108 : osDirectoryName);
862 54 : if (!poGroup)
863 3 : return nullptr;
864 51 : poGroup->m_poParent =
865 102 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
866 51 : m_oMapGroups[osName] = poGroup;
867 51 : m_aosGroups.emplace_back(osName);
868 51 : return poGroup;
869 : }
870 :
871 : /************************************************************************/
872 : /* FillDTypeElts() */
873 : /************************************************************************/
874 :
875 426 : static CPLJSONObject FillDTypeElts(const GDALExtendedDataType &oDataType,
876 : std::vector<DtypeElt> &aoDtypeElts)
877 : {
878 426 : CPLJSONObject dtype;
879 852 : const std::string dummy("dummy");
880 :
881 426 : if (oDataType.GetClass() == GEDTC_STRING)
882 : {
883 : const int nMaxLen = std::max(
884 6 : 2, atoi(CPLGetConfigOption("ZARR_VLEN_STRING_MAX_LENGTH", "256")));
885 12 : DtypeElt elt;
886 6 : elt.nativeType = DtypeElt::NativeType::STRING_ASCII;
887 6 : elt.nativeOffset = 0;
888 6 : elt.nativeSize = static_cast<size_t>(nMaxLen);
889 6 : elt.gdalOffset = 0;
890 6 : elt.gdalSize = oDataType.GetSize();
891 6 : aoDtypeElts.emplace_back(elt);
892 6 : dtype.Set(dummy, "string");
893 6 : return dtype;
894 : }
895 :
896 420 : const auto eDT = oDataType.GetNumericDataType();
897 840 : DtypeElt elt;
898 420 : bool bUnsupported = false;
899 420 : switch (eDT)
900 : {
901 126 : case GDT_UInt8:
902 : {
903 126 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
904 126 : dtype.Set(dummy, "uint8");
905 126 : break;
906 : }
907 8 : case GDT_Int8:
908 : {
909 8 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
910 8 : dtype.Set(dummy, "int8");
911 8 : break;
912 : }
913 15 : case GDT_UInt16:
914 : {
915 15 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
916 15 : dtype.Set(dummy, "uint16");
917 15 : break;
918 : }
919 12 : case GDT_Int16:
920 : {
921 12 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
922 12 : dtype.Set(dummy, "int16");
923 12 : break;
924 : }
925 10 : case GDT_UInt32:
926 : {
927 10 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
928 10 : dtype.Set(dummy, "uint32");
929 10 : break;
930 : }
931 10 : case GDT_Int32:
932 : {
933 10 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
934 10 : dtype.Set(dummy, "int32");
935 10 : break;
936 : }
937 11 : case GDT_UInt64:
938 : {
939 11 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
940 11 : dtype.Set(dummy, "uint64");
941 11 : break;
942 : }
943 11 : case GDT_Int64:
944 : {
945 11 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
946 11 : dtype.Set(dummy, "int64");
947 11 : break;
948 : }
949 1 : case GDT_Float16:
950 : {
951 1 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
952 1 : dtype.Set(dummy, "float16");
953 1 : break;
954 : }
955 47 : case GDT_Float32:
956 : {
957 47 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
958 47 : dtype.Set(dummy, "float32");
959 47 : break;
960 : }
961 153 : case GDT_Float64:
962 : {
963 153 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
964 153 : dtype.Set(dummy, "float64");
965 153 : break;
966 : }
967 8 : case GDT_Unknown:
968 : case GDT_CInt16:
969 : case GDT_CInt32:
970 : {
971 8 : bUnsupported = true;
972 8 : break;
973 : }
974 0 : case GDT_CFloat16:
975 : {
976 0 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
977 0 : dtype.Set(dummy, "complex32");
978 0 : break;
979 : }
980 4 : case GDT_CFloat32:
981 : {
982 4 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
983 4 : dtype.Set(dummy, "complex64");
984 4 : break;
985 : }
986 4 : case GDT_CFloat64:
987 : {
988 4 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
989 4 : dtype.Set(dummy, "complex128");
990 4 : break;
991 : }
992 0 : case GDT_TypeCount:
993 : {
994 : static_assert(GDT_TypeCount == GDT_CFloat16 + 1,
995 : "GDT_TypeCount == GDT_CFloat16 + 1");
996 0 : break;
997 : }
998 : }
999 420 : if (bUnsupported)
1000 : {
1001 8 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %s",
1002 : GDALGetDataTypeName(eDT));
1003 8 : dtype = CPLJSONObject();
1004 8 : dtype.Deinit();
1005 8 : return dtype;
1006 : }
1007 412 : elt.nativeOffset = 0;
1008 412 : elt.nativeSize = GDALGetDataTypeSizeBytes(eDT);
1009 412 : elt.gdalOffset = 0;
1010 412 : elt.gdalSize = elt.nativeSize;
1011 : #ifdef CPL_MSB
1012 : elt.needByteSwapping = elt.nativeSize > 1;
1013 : #endif
1014 412 : aoDtypeElts.emplace_back(elt);
1015 :
1016 412 : return dtype;
1017 : }
1018 :
1019 : /************************************************************************/
1020 : /* ZarrV3Group::CreateMDArray() */
1021 : /************************************************************************/
1022 :
1023 442 : std::shared_ptr<GDALMDArray> ZarrV3Group::CreateMDArray(
1024 : const std::string &osName,
1025 : const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
1026 : const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
1027 : {
1028 442 : if (!CheckValidAndErrorOutIfNot())
1029 0 : return nullptr;
1030 :
1031 442 : if (!m_bUpdatable)
1032 : {
1033 0 : CPLError(CE_Failure, CPLE_NotSupported,
1034 : "Dataset not open in update mode");
1035 0 : return nullptr;
1036 : }
1037 442 : if (!IsValidObjectName(osName))
1038 : {
1039 14 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid array name");
1040 14 : return nullptr;
1041 : }
1042 :
1043 436 : if (oDataType.GetClass() != GEDTC_NUMERIC &&
1044 8 : oDataType.GetClass() != GEDTC_STRING)
1045 : {
1046 2 : CPLError(CE_Failure, CPLE_AppDefined,
1047 : "Unsupported data type with Zarr V3");
1048 2 : return nullptr;
1049 : }
1050 :
1051 426 : if (!EQUAL(CSLFetchNameValueDef(papszOptions, "FILTER", "NONE"), "NONE"))
1052 : {
1053 0 : CPLError(CE_Failure, CPLE_AppDefined,
1054 : "FILTER option not supported with Zarr V3");
1055 0 : return nullptr;
1056 : }
1057 :
1058 852 : std::vector<DtypeElt> aoDtypeElts;
1059 1278 : const auto dtype = FillDTypeElts(oDataType, aoDtypeElts)["dummy"];
1060 426 : if (!dtype.IsValid() || aoDtypeElts.empty())
1061 8 : return nullptr;
1062 :
1063 418 : GetMDArrayNames();
1064 :
1065 418 : if (cpl::contains(m_oSetArrayNames, osName))
1066 : {
1067 2 : CPLError(CE_Failure, CPLE_AppDefined,
1068 : "An array with same name (%s) already exists in group %s",
1069 2 : osName.c_str(), GetFullName().c_str());
1070 2 : return nullptr;
1071 : }
1072 :
1073 832 : std::vector<GUInt64> anOuterBlockSize;
1074 416 : if (!ZarrArray::FillBlockSize(aoDimensions, oDataType, anOuterBlockSize,
1075 : papszOptions))
1076 5 : return nullptr;
1077 :
1078 : const char *pszDimSeparator =
1079 411 : CSLFetchNameValueDef(papszOptions, "DIM_SEPARATOR", "/");
1080 :
1081 : const std::string osArrayDirectory =
1082 822 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
1083 411 : if (VSIMkdir(osArrayDirectory.c_str(), 0755) != 0)
1084 : {
1085 : VSIStatBufL sStat;
1086 2 : if (VSIStatL(osArrayDirectory.c_str(), &sStat) == 0)
1087 : {
1088 2 : CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
1089 : osArrayDirectory.c_str());
1090 : }
1091 : else
1092 : {
1093 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
1094 : osArrayDirectory.c_str());
1095 : }
1096 2 : return nullptr;
1097 : }
1098 :
1099 409 : std::unique_ptr<ZarrV3CodecSequence> poCodecs;
1100 818 : CPLJSONArray oCodecs;
1101 :
1102 409 : const bool bIsString = (oDataType.GetClass() == GEDTC_STRING);
1103 :
1104 409 : const bool bFortranOrder = EQUAL(
1105 : CSLFetchNameValueDef(papszOptions, "CHUNK_MEMORY_LAYOUT", "C"), "F");
1106 409 : if (!bIsString && bFortranOrder && aoDimensions.size() > 1)
1107 : {
1108 80 : CPLJSONObject oCodec;
1109 40 : oCodec.Add("name", "transpose");
1110 80 : std::vector<int> anOrder;
1111 40 : const int nDims = static_cast<int>(aoDimensions.size());
1112 130 : for (int i = 0; i < nDims; ++i)
1113 : {
1114 90 : anOrder.push_back(nDims - 1 - i);
1115 : }
1116 40 : oCodec.Add("configuration",
1117 80 : ZarrV3CodecTranspose::GetConfiguration(anOrder));
1118 40 : oCodecs.Add(oCodec);
1119 : }
1120 :
1121 : // Array-to-bytes codec: vlen-utf8 for strings, bytes for numeric
1122 409 : if (bIsString)
1123 : {
1124 12 : CPLJSONObject oCodec;
1125 6 : oCodec.Add("name", "vlen-utf8");
1126 6 : oCodecs.Add(oCodec);
1127 : }
1128 : else
1129 : {
1130 : // Not documented option, but 'bytes' codec is required
1131 : const char *pszEndian =
1132 403 : CSLFetchNameValueDef(papszOptions, "@ENDIAN", "little");
1133 806 : CPLJSONObject oCodec;
1134 403 : oCodec.Add("name", "bytes");
1135 403 : oCodec.Add("configuration", ZarrV3CodecBytes::GetConfiguration(
1136 403 : EQUAL(pszEndian, "little")));
1137 403 : oCodecs.Add(oCodec);
1138 : }
1139 :
1140 : const char *pszCompressor =
1141 409 : CSLFetchNameValueDef(papszOptions, "COMPRESS", "NONE");
1142 409 : if (EQUAL(pszCompressor, "GZIP"))
1143 : {
1144 62 : CPLJSONObject oCodec;
1145 31 : oCodec.Add("name", "gzip");
1146 : const char *pszLevel =
1147 31 : CSLFetchNameValueDef(papszOptions, "GZIP_LEVEL", "6");
1148 31 : oCodec.Add("configuration",
1149 62 : ZarrV3CodecGZip::GetConfiguration(atoi(pszLevel)));
1150 31 : oCodecs.Add(oCodec);
1151 : }
1152 378 : else if (EQUAL(pszCompressor, "BLOSC"))
1153 : {
1154 2 : const auto psCompressor = CPLGetCompressor("blosc");
1155 2 : if (!psCompressor)
1156 0 : return nullptr;
1157 : const char *pszOptions =
1158 2 : CSLFetchNameValueDef(psCompressor->papszMetadata, "OPTIONS", "");
1159 2 : CPLXMLTreeCloser oTreeCompressor(CPLParseXMLString(pszOptions));
1160 : const auto psRoot =
1161 2 : oTreeCompressor.get()
1162 2 : ? CPLGetXMLNode(oTreeCompressor.get(), "=Options")
1163 2 : : nullptr;
1164 2 : if (!psRoot)
1165 0 : return nullptr;
1166 :
1167 2 : const char *cname = "zlib";
1168 14 : for (const CPLXMLNode *psNode = psRoot->psChild; psNode != nullptr;
1169 12 : psNode = psNode->psNext)
1170 : {
1171 12 : if (psNode->eType == CXT_Element)
1172 : {
1173 12 : const char *pszName = CPLGetXMLValue(psNode, "name", "");
1174 12 : if (EQUAL(pszName, "CNAME"))
1175 : {
1176 2 : cname = CPLGetXMLValue(psNode, "default", cname);
1177 : }
1178 : }
1179 : }
1180 :
1181 4 : CPLJSONObject oCodec;
1182 2 : oCodec.Add("name", "blosc");
1183 2 : cname = CSLFetchNameValueDef(papszOptions, "BLOSC_CNAME", cname);
1184 : const int clevel =
1185 2 : atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_CLEVEL", "5"));
1186 : const char *shuffle =
1187 2 : CSLFetchNameValueDef(papszOptions, "BLOSC_SHUFFLE", "BYTE");
1188 3 : shuffle = (EQUAL(shuffle, "0") || EQUAL(shuffle, "NONE")) ? "noshuffle"
1189 1 : : (EQUAL(shuffle, "1") || EQUAL(shuffle, "BYTE")) ? "shuffle"
1190 0 : : (EQUAL(shuffle, "2") || EQUAL(shuffle, "BIT"))
1191 0 : ? "bitshuffle"
1192 : : "invalid";
1193 : const int nDefaultTypeSize =
1194 2 : bIsString ? 1
1195 2 : : GDALGetDataTypeSizeBytes(GDALGetNonComplexDataType(
1196 2 : oDataType.GetNumericDataType()));
1197 : const int typesize =
1198 2 : atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_TYPESIZE",
1199 : CPLSPrintf("%d", nDefaultTypeSize)));
1200 : const int blocksize =
1201 2 : atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_BLOCKSIZE", "0"));
1202 2 : oCodec.Add("configuration",
1203 4 : ZarrV3CodecBlosc::GetConfiguration(cname, clevel, shuffle,
1204 : typesize, blocksize));
1205 2 : oCodecs.Add(oCodec);
1206 : }
1207 376 : else if (EQUAL(pszCompressor, "ZSTD"))
1208 : {
1209 14 : CPLJSONObject oCodec;
1210 7 : oCodec.Add("name", "zstd");
1211 : const char *pszLevel =
1212 7 : CSLFetchNameValueDef(papszOptions, "ZSTD_LEVEL", "13");
1213 7 : const bool bChecksum = CPLTestBool(
1214 : CSLFetchNameValueDef(papszOptions, "ZSTD_CHECKSUM", "FALSE"));
1215 7 : oCodec.Add("configuration", ZarrV3CodecZstd::GetConfiguration(
1216 : atoi(pszLevel), bChecksum));
1217 7 : oCodecs.Add(oCodec);
1218 : }
1219 369 : else if (!EQUAL(pszCompressor, "NONE"))
1220 : {
1221 1 : CPLError(CE_Failure, CPLE_AppDefined,
1222 : "COMPRESS = %s not implemented with Zarr V3", pszCompressor);
1223 1 : return nullptr;
1224 : }
1225 :
1226 : // Sharding: wrap inner codecs into a sharding_indexed codec
1227 : const char *pszShardChunkShape =
1228 408 : CSLFetchNameValue(papszOptions, "SHARD_CHUNK_SHAPE");
1229 408 : if (pszShardChunkShape != nullptr)
1230 : {
1231 :
1232 : const CPLStringList aosChunkShape(
1233 13 : CSLTokenizeString2(pszShardChunkShape, ",", 0));
1234 13 : if (static_cast<size_t>(aosChunkShape.size()) != aoDimensions.size())
1235 : {
1236 1 : CPLError(CE_Failure, CPLE_AppDefined,
1237 : "SHARD_CHUNK_SHAPE has %d values, expected %d",
1238 : aosChunkShape.size(),
1239 1 : static_cast<int>(aoDimensions.size()));
1240 1 : return nullptr;
1241 : }
1242 :
1243 12 : CPLJSONArray oChunkShapeArray;
1244 35 : for (int i = 0; i < aosChunkShape.size(); ++i)
1245 : {
1246 24 : const auto nInner = static_cast<GUInt64>(atoll(aosChunkShape[i]));
1247 24 : if (nInner == 0 || anOuterBlockSize[i] % nInner != 0)
1248 : {
1249 1 : CPLError(CE_Failure, CPLE_AppDefined,
1250 : "SHARD_CHUNK_SHAPE[%d]=%s must divide "
1251 : "BLOCKSIZE[%d]=" CPL_FRMT_GUIB " evenly",
1252 1 : i, aosChunkShape[i], i, anOuterBlockSize[i]);
1253 1 : return nullptr;
1254 : }
1255 23 : oChunkShapeArray.Add(static_cast<uint64_t>(nInner));
1256 : }
1257 :
1258 : // Index codecs: always bytes(little) + crc32c
1259 22 : CPLJSONArray oIndexCodecs;
1260 : {
1261 22 : CPLJSONObject oBytesCodec;
1262 11 : oBytesCodec.Add("name", "bytes");
1263 11 : oBytesCodec.Add("configuration",
1264 22 : ZarrV3CodecBytes::GetConfiguration(true));
1265 11 : oIndexCodecs.Add(oBytesCodec);
1266 : }
1267 : {
1268 22 : CPLJSONObject oCRC32CCodec;
1269 11 : oCRC32CCodec.Add("name", "crc32c");
1270 11 : oIndexCodecs.Add(oCRC32CCodec);
1271 : }
1272 :
1273 22 : CPLJSONObject oShardingConfig;
1274 11 : oShardingConfig.Add("chunk_shape", oChunkShapeArray);
1275 11 : oShardingConfig.Add("codecs", oCodecs);
1276 11 : oShardingConfig.Add("index_codecs", oIndexCodecs);
1277 11 : oShardingConfig.Add("index_location", "end");
1278 :
1279 22 : CPLJSONObject oShardingCodec;
1280 11 : oShardingCodec.Add("name", "sharding_indexed");
1281 11 : oShardingCodec.Add("configuration", oShardingConfig);
1282 :
1283 : // Replace top-level codecs with just the sharding codec
1284 11 : oCodecs = CPLJSONArray();
1285 11 : oCodecs.Add(oShardingCodec);
1286 : }
1287 :
1288 812 : std::vector<GUInt64> anInnerBlockSize = anOuterBlockSize;
1289 406 : if (oCodecs.Size() > 0)
1290 : {
1291 406 : std::vector<GByte> abyNoData;
1292 812 : poCodecs = ZarrV3Array::SetupCodecs(
1293 812 : GetFullName() + "/" + osName, oCodecs, anOuterBlockSize,
1294 812 : anInnerBlockSize, aoDtypeElts.back(), abyNoData);
1295 406 : if (!poCodecs)
1296 : {
1297 0 : return nullptr;
1298 : }
1299 : }
1300 :
1301 406 : auto poArray = ZarrV3Array::Create(m_poSharedResource, Self(), osName,
1302 : aoDimensions, oDataType, aoDtypeElts,
1303 812 : anOuterBlockSize, anInnerBlockSize);
1304 :
1305 406 : if (!poArray)
1306 0 : return nullptr;
1307 406 : poArray->SetNew(true);
1308 : const std::string osFilename =
1309 812 : CPLFormFilenameSafe(osArrayDirectory.c_str(), "zarr.json", nullptr);
1310 406 : poArray->SetFilename(osFilename);
1311 406 : poArray->SetDimSeparator(pszDimSeparator);
1312 406 : poArray->SetDtype(dtype);
1313 : const std::string osLastCodecName =
1314 812 : oCodecs.Size() > 0 ? oCodecs[oCodecs.Size() - 1].GetString("name")
1315 2842 : : std::string();
1316 458 : if (!osLastCodecName.empty() && osLastCodecName != "bytes" &&
1317 52 : osLastCodecName != "vlen-utf8")
1318 : {
1319 94 : poArray->SetStructuralInfo(
1320 94 : "COMPRESSOR", oCodecs[oCodecs.Size() - 1].ToString().c_str());
1321 : }
1322 406 : if (poCodecs)
1323 406 : poArray->SetCodecs(oCodecs, std::move(poCodecs));
1324 :
1325 406 : poArray->SetCreationOptions(papszOptions);
1326 406 : poArray->SetUpdatable(true);
1327 406 : poArray->SetDefinitionModified(true);
1328 406 : if (!cpl::starts_with(osFilename, "/vsi") && !poArray->Flush())
1329 0 : return nullptr;
1330 406 : RegisterArray(poArray);
1331 :
1332 406 : return poArray;
1333 : }
|