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