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 1409 : 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 1409 : poSharedResource, osParentName, osName, osRootDirectoryName));
33 1409 : poGroup->SetSelf(poGroup);
34 1409 : return poGroup;
35 : }
36 :
37 : /************************************************************************/
38 : /* OpenZarrArray() */
39 : /************************************************************************/
40 :
41 1222 : std::shared_ptr<ZarrArray> ZarrV3Group::OpenZarrArray(const std::string &osName,
42 : CSLConstList) const
43 : {
44 1222 : if (!CheckValidAndErrorOutIfNot())
45 0 : return nullptr;
46 :
47 1222 : auto oIter = m_oMapMDArrays.find(osName);
48 1222 : if (oIter != m_oMapMDArrays.end())
49 1102 : return oIter->second;
50 :
51 120 : if (m_bReadFromConsolidatedMetadata)
52 13 : return nullptr;
53 :
54 : const std::string osSubDir =
55 214 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
56 : const std::string osZarrayFilename =
57 214 : CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
58 :
59 : VSIStatBufL sStat;
60 107 : if (VSIStatL(osZarrayFilename.c_str(), &sStat) == 0)
61 : {
62 170 : CPLJSONDocument oDoc;
63 85 : if (!oDoc.Load(osZarrayFilename))
64 0 : return nullptr;
65 170 : const auto oRoot = oDoc.GetRoot();
66 85 : 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 68 : void ZarrV3Group::ExploreDirectory() const
101 : {
102 68 : if (m_bDirectoryExplored)
103 0 : return;
104 68 : m_bDirectoryExplored = true;
105 :
106 68 : auto psDir = VSIOpenDir(m_osDirectoryName.c_str(), 0, nullptr);
107 68 : if (!psDir)
108 0 : return;
109 238 : while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir))
110 : {
111 170 : if (VSI_ISDIR(psEntry->nMode))
112 : {
113 204 : std::string osName(psEntry->pszName);
114 204 : while (!osName.empty() &&
115 102 : (osName.back() == '/' || osName.back() == '\\'))
116 0 : osName.pop_back();
117 102 : if (osName.empty())
118 0 : continue;
119 : const std::string osSubDir = CPLFormFilenameSafe(
120 102 : m_osDirectoryName.c_str(), osName.c_str(), nullptr);
121 : VSIStatBufL sStat;
122 : const std::string osZarrJsonFilename =
123 102 : CPLFormFilenameSafe(osSubDir.c_str(), "zarr.json", nullptr);
124 102 : if (VSIStatL(osZarrJsonFilename.c_str(), &sStat) == 0)
125 : {
126 100 : CPLJSONDocument oDoc;
127 100 : if (oDoc.Load(osZarrJsonFilename.c_str()))
128 : {
129 100 : const auto oRoot = oDoc.GetRoot();
130 100 : if (oRoot.GetInteger("zarr_format") != 3)
131 : {
132 0 : CPLError(CE_Warning, CPLE_AppDefined,
133 : "Unhandled zarr_format value");
134 0 : continue;
135 : }
136 200 : const std::string osNodeType = oRoot.GetString("node_type");
137 100 : if (osNodeType == "array")
138 : {
139 57 : if (!cpl::contains(m_oSetArrayNames, osName))
140 : {
141 55 : m_oSetArrayNames.insert(osName);
142 55 : 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 170 : }
172 68 : VSICloseDir(psDir);
173 : }
174 :
175 : /************************************************************************/
176 : /* ZarrV3Group::ZarrV3Group() */
177 : /************************************************************************/
178 :
179 1409 : ZarrV3Group::ZarrV3Group(
180 : const std::shared_ptr<ZarrSharedResource> &poSharedResource,
181 : const std::string &osParentName, const std::string &osName,
182 1409 : const std::string &osDirectoryName)
183 1409 : : ZarrGroupBase(poSharedResource, osParentName, osName)
184 : {
185 1409 : m_osDirectoryName = osDirectoryName;
186 1409 : }
187 :
188 : /************************************************************************/
189 : /* ZarrV3Group::~ZarrV3Group() */
190 : /************************************************************************/
191 :
192 2818 : ZarrV3Group::~ZarrV3Group()
193 : {
194 1409 : ZarrV3Group::Close();
195 2818 : }
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 3252 : bool ZarrV3Group::Close()
482 : {
483 3252 : bool bRet = ZarrGroupBase::Close();
484 :
485 6425 : if (m_bValid && (m_oAttrGroup.IsModified() ||
486 3259 : (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 3252 : 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 183 : void ZarrV3Group::InitFromConsolidatedMetadata(
560 : const CPLJSONObject &oConsolidatedMetadata,
561 : const CPLJSONObject &oRootAttributes)
562 : {
563 366 : const auto metadata = oConsolidatedMetadata["metadata"];
564 183 : 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 183 : m_bDirectoryExplored = true;
571 183 : m_bAttributesLoaded = true;
572 183 : m_bReadFromConsolidatedMetadata = true;
573 :
574 183 : if (oRootAttributes.IsValid())
575 : {
576 183 : m_oAttrGroup.Init(oRootAttributes, m_bUpdatable);
577 : }
578 :
579 366 : const auto children = metadata.GetChildren();
580 366 : std::map<std::string, const CPLJSONObject *> oMapArrays;
581 :
582 : // First pass to create groups and collect arrays
583 692 : for (const auto &child : children)
584 : {
585 509 : const std::string osName(child.GetName());
586 509 : if (std::count(osName.begin(), osName.end(), '/') > 32)
587 : {
588 : // Avoid too deep recursion in GetOrCreateSubGroup()
589 0 : continue;
590 : }
591 :
592 1527 : const std::string osNodeType = child.GetString("node_type");
593 509 : 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 371 : else if (osNodeType == "array")
603 : {
604 371 : oMapArrays[osName] = &child;
605 : }
606 : }
607 :
608 : const auto CreateArray =
609 595 : [this](const std::string &osArrayFullname, const CPLJSONObject &oArray)
610 : {
611 371 : const auto nLastSlashPos = osArrayFullname.rfind('/');
612 : auto poBelongingGroup =
613 : (nLastSlashPos == std::string::npos)
614 371 : ? this
615 595 : : GetOrCreateSubGroup("/" +
616 595 : osArrayFullname.substr(0, nLastSlashPos))
617 371 : .get();
618 : const auto osArrayName =
619 : nLastSlashPos == std::string::npos
620 : ? osArrayFullname
621 742 : : osArrayFullname.substr(nLastSlashPos + 1);
622 : const std::string osZarrayFilename = CPLFormFilenameSafe(
623 371 : CPLFormFilenameSafe(poBelongingGroup->m_osDirectoryName.c_str(),
624 : osArrayName.c_str(), nullptr)
625 : .c_str(),
626 371 : "zarr.json", nullptr);
627 371 : poBelongingGroup->LoadArray(osArrayName, osZarrayFilename, oArray);
628 371 : };
629 :
630 : struct ArrayDesc
631 : {
632 : std::string osArrayFullname{};
633 : const CPLJSONObject *poArray = nullptr;
634 : };
635 :
636 366 : std::vector<ArrayDesc> aoRegularArrays;
637 :
638 : // Second pass to read attributes and create arrays that are indexing
639 : // variable
640 692 : for (const auto &child : children)
641 : {
642 1018 : const std::string osName(child.GetName());
643 1527 : const std::string osNodeType = child.GetString("node_type");
644 509 : if (osNodeType == "array")
645 : {
646 371 : auto oIter = oMapArrays.find(osName);
647 371 : if (oIter != oMapArrays.end())
648 : {
649 371 : const auto nLastSlashPos = osName.rfind('/');
650 : const std::string osArrayName =
651 : (nLastSlashPos == std::string::npos)
652 : ? osName
653 742 : : osName.substr(nLastSlashPos + 1);
654 1113 : const auto arrayDimensions = child["dimension_names"].ToArray();
655 459 : if (arrayDimensions.IsValid() && arrayDimensions.Size() == 1 &&
656 459 : arrayDimensions[0].ToString() == osArrayName)
657 : {
658 80 : CreateArray(osName, child);
659 80 : oMapArrays.erase(oIter);
660 : }
661 : else
662 : {
663 582 : ArrayDesc desc;
664 291 : desc.osArrayFullname = std::move(osName);
665 291 : desc.poArray = oIter->second;
666 291 : aoRegularArrays.emplace_back(std::move(desc));
667 : }
668 : }
669 : }
670 : }
671 :
672 : // Third pass to create non-indexing arrays with attributes
673 474 : for (const auto &desc : aoRegularArrays)
674 : {
675 291 : CreateArray(desc.osArrayFullname, *(desc.poArray));
676 291 : oMapArrays.erase(desc.osArrayFullname);
677 : }
678 :
679 : // Fourth pass to create arrays without attributes
680 183 : 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 222 : 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 222 : 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 438 : CPLFormFilenameSafe(osDirectoryName.c_str(), "zarr.json", nullptr));
789 219 : VSILFILE *fp = nullptr;
790 399 : if (!(poSharedResource->IsConsolidatedMetadataEnabled() &&
791 180 : cpl::starts_with(osZarrJsonFilename, "/vsizip/") &&
792 1 : osParentFullName.empty() && osName == "/"))
793 : {
794 218 : fp = VSIFOpenL(osZarrJsonFilename.c_str(), "wb");
795 218 : if (!fp)
796 : {
797 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create file %s.",
798 : osZarrJsonFilename.c_str());
799 0 : return nullptr;
800 : }
801 218 : VSIFPrintfL(fp, "{\n"
802 : " \"zarr_format\": 3,\n"
803 : " \"node_type\": \"group\",\n"
804 : " \"attributes\": {}\n"
805 : "}\n");
806 218 : VSIFCloseL(fp);
807 : }
808 :
809 : auto poGroup = ZarrV3Group::Create(poSharedResource, osParentFullName,
810 438 : osName, osDirectoryName);
811 219 : poGroup->SetUpdatable(true);
812 219 : poGroup->m_bDirectoryExplored = true;
813 219 : poGroup->m_bFileHasBeenWritten = fp != nullptr;
814 :
815 438 : CPLJSONObject oObj;
816 219 : oObj.Add("zarr_format", 3);
817 219 : oObj.Add("node_type", "group");
818 219 : oObj.Add("attributes", CPLJSONObject());
819 219 : poSharedResource->SetZMetadataItem(osZarrJsonFilename, oObj);
820 :
821 219 : 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 204 : static CPLJSONObject FillDTypeElts(const GDALExtendedDataType &oDataType,
874 : std::vector<DtypeElt> &aoDtypeElts)
875 : {
876 204 : CPLJSONObject dtype;
877 408 : const std::string dummy("dummy");
878 :
879 204 : if (oDataType.GetClass() == GEDTC_STRING)
880 : {
881 : const int nMaxLen = std::max(
882 6 : 2, atoi(CPLGetConfigOption("ZARR_VLEN_STRING_MAX_LENGTH", "256")));
883 12 : DtypeElt elt;
884 6 : elt.nativeType = DtypeElt::NativeType::STRING_ASCII;
885 6 : elt.nativeOffset = 0;
886 6 : elt.nativeSize = static_cast<size_t>(nMaxLen);
887 6 : elt.gdalOffset = 0;
888 6 : elt.gdalSize = oDataType.GetSize();
889 6 : aoDtypeElts.emplace_back(elt);
890 6 : dtype.Set(dummy, "string");
891 6 : return dtype;
892 : }
893 :
894 198 : const auto eDT = oDataType.GetNumericDataType();
895 396 : DtypeElt elt;
896 198 : bool bUnsupported = false;
897 198 : switch (eDT)
898 : {
899 68 : case GDT_UInt8:
900 : {
901 68 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
902 68 : dtype.Set(dummy, "uint8");
903 68 : break;
904 : }
905 6 : case GDT_Int8:
906 : {
907 6 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
908 6 : dtype.Set(dummy, "int8");
909 6 : break;
910 : }
911 11 : case GDT_UInt16:
912 : {
913 11 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
914 11 : dtype.Set(dummy, "uint16");
915 11 : break;
916 : }
917 9 : case GDT_Int16:
918 : {
919 9 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
920 9 : dtype.Set(dummy, "int16");
921 9 : break;
922 : }
923 7 : case GDT_UInt32:
924 : {
925 7 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
926 7 : dtype.Set(dummy, "uint32");
927 7 : break;
928 : }
929 7 : case GDT_Int32:
930 : {
931 7 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
932 7 : dtype.Set(dummy, "int32");
933 7 : break;
934 : }
935 6 : case GDT_UInt64:
936 : {
937 6 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
938 6 : dtype.Set(dummy, "uint64");
939 6 : break;
940 : }
941 6 : case GDT_Int64:
942 : {
943 6 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
944 6 : dtype.Set(dummy, "int64");
945 6 : break;
946 : }
947 1 : case GDT_Float16:
948 : {
949 1 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
950 1 : dtype.Set(dummy, "float16");
951 1 : break;
952 : }
953 40 : case GDT_Float32:
954 : {
955 40 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
956 40 : dtype.Set(dummy, "float32");
957 40 : break;
958 : }
959 33 : case GDT_Float64:
960 : {
961 33 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
962 33 : dtype.Set(dummy, "float64");
963 33 : break;
964 : }
965 2 : case GDT_Unknown:
966 : case GDT_CInt16:
967 : case GDT_CInt32:
968 : {
969 2 : bUnsupported = true;
970 2 : break;
971 : }
972 0 : case GDT_CFloat16:
973 : {
974 0 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
975 0 : dtype.Set(dummy, "complex32");
976 0 : break;
977 : }
978 1 : case GDT_CFloat32:
979 : {
980 1 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
981 1 : dtype.Set(dummy, "complex64");
982 1 : break;
983 : }
984 1 : case GDT_CFloat64:
985 : {
986 1 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
987 1 : dtype.Set(dummy, "complex128");
988 1 : break;
989 : }
990 0 : case GDT_TypeCount:
991 : {
992 : static_assert(GDT_TypeCount == GDT_CFloat16 + 1,
993 : "GDT_TypeCount == GDT_CFloat16 + 1");
994 0 : break;
995 : }
996 : }
997 198 : if (bUnsupported)
998 : {
999 2 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %s",
1000 : GDALGetDataTypeName(eDT));
1001 2 : dtype = CPLJSONObject();
1002 2 : dtype.Deinit();
1003 2 : return dtype;
1004 : }
1005 196 : elt.nativeOffset = 0;
1006 196 : elt.nativeSize = GDALGetDataTypeSizeBytes(eDT);
1007 196 : elt.gdalOffset = 0;
1008 196 : elt.gdalSize = elt.nativeSize;
1009 : #ifdef CPL_MSB
1010 : elt.needByteSwapping = elt.nativeSize > 1;
1011 : #endif
1012 196 : aoDtypeElts.emplace_back(elt);
1013 :
1014 196 : return dtype;
1015 : }
1016 :
1017 : /************************************************************************/
1018 : /* ZarrV3Group::CreateMDArray() */
1019 : /************************************************************************/
1020 :
1021 220 : std::shared_ptr<GDALMDArray> ZarrV3Group::CreateMDArray(
1022 : const std::string &osName,
1023 : const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
1024 : const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
1025 : {
1026 220 : if (!CheckValidAndErrorOutIfNot())
1027 0 : return nullptr;
1028 :
1029 220 : if (!m_bUpdatable)
1030 : {
1031 0 : CPLError(CE_Failure, CPLE_NotSupported,
1032 : "Dataset not open in update mode");
1033 0 : return nullptr;
1034 : }
1035 220 : if (!IsValidObjectName(osName))
1036 : {
1037 14 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid array name");
1038 14 : return nullptr;
1039 : }
1040 :
1041 214 : if (oDataType.GetClass() != GEDTC_NUMERIC &&
1042 8 : oDataType.GetClass() != GEDTC_STRING)
1043 : {
1044 2 : CPLError(CE_Failure, CPLE_AppDefined,
1045 : "Unsupported data type with Zarr V3");
1046 2 : return nullptr;
1047 : }
1048 :
1049 204 : if (!EQUAL(CSLFetchNameValueDef(papszOptions, "FILTER", "NONE"), "NONE"))
1050 : {
1051 0 : CPLError(CE_Failure, CPLE_AppDefined,
1052 : "FILTER option not supported with Zarr V3");
1053 0 : return nullptr;
1054 : }
1055 :
1056 408 : std::vector<DtypeElt> aoDtypeElts;
1057 612 : const auto dtype = FillDTypeElts(oDataType, aoDtypeElts)["dummy"];
1058 204 : if (!dtype.IsValid() || aoDtypeElts.empty())
1059 2 : return nullptr;
1060 :
1061 202 : GetMDArrayNames();
1062 :
1063 202 : if (cpl::contains(m_oSetArrayNames, osName))
1064 : {
1065 2 : CPLError(CE_Failure, CPLE_AppDefined,
1066 : "An array with same name already exists");
1067 2 : return nullptr;
1068 : }
1069 :
1070 400 : std::vector<GUInt64> anOuterBlockSize;
1071 200 : if (!ZarrArray::FillBlockSize(aoDimensions, oDataType, anOuterBlockSize,
1072 : papszOptions))
1073 1 : return nullptr;
1074 :
1075 : const char *pszDimSeparator =
1076 199 : CSLFetchNameValueDef(papszOptions, "DIM_SEPARATOR", "/");
1077 :
1078 : const std::string osArrayDirectory =
1079 398 : CPLFormFilenameSafe(m_osDirectoryName.c_str(), osName.c_str(), nullptr);
1080 199 : if (VSIMkdir(osArrayDirectory.c_str(), 0755) != 0)
1081 : {
1082 : VSIStatBufL sStat;
1083 2 : if (VSIStatL(osArrayDirectory.c_str(), &sStat) == 0)
1084 : {
1085 2 : CPLError(CE_Failure, CPLE_FileIO, "Directory %s already exists.",
1086 : osArrayDirectory.c_str());
1087 : }
1088 : else
1089 : {
1090 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s.",
1091 : osArrayDirectory.c_str());
1092 : }
1093 2 : return nullptr;
1094 : }
1095 :
1096 197 : std::unique_ptr<ZarrV3CodecSequence> poCodecs;
1097 394 : CPLJSONArray oCodecs;
1098 :
1099 197 : const bool bIsString = (oDataType.GetClass() == GEDTC_STRING);
1100 :
1101 197 : const bool bFortranOrder = EQUAL(
1102 : CSLFetchNameValueDef(papszOptions, "CHUNK_MEMORY_LAYOUT", "C"), "F");
1103 197 : if (!bIsString && bFortranOrder && aoDimensions.size() > 1)
1104 : {
1105 80 : CPLJSONObject oCodec;
1106 40 : oCodec.Add("name", "transpose");
1107 80 : std::vector<int> anOrder;
1108 40 : const int nDims = static_cast<int>(aoDimensions.size());
1109 130 : for (int i = 0; i < nDims; ++i)
1110 : {
1111 90 : anOrder.push_back(nDims - 1 - i);
1112 : }
1113 40 : oCodec.Add("configuration",
1114 80 : ZarrV3CodecTranspose::GetConfiguration(anOrder));
1115 40 : oCodecs.Add(oCodec);
1116 : }
1117 :
1118 : // Array-to-bytes codec: vlen-utf8 for strings, bytes for numeric
1119 197 : if (bIsString)
1120 : {
1121 12 : CPLJSONObject oCodec;
1122 6 : oCodec.Add("name", "vlen-utf8");
1123 6 : oCodecs.Add(oCodec);
1124 : }
1125 : else
1126 : {
1127 : // Not documented option, but 'bytes' codec is required
1128 : const char *pszEndian =
1129 191 : CSLFetchNameValueDef(papszOptions, "@ENDIAN", "little");
1130 382 : CPLJSONObject oCodec;
1131 191 : oCodec.Add("name", "bytes");
1132 191 : oCodec.Add("configuration", ZarrV3CodecBytes::GetConfiguration(
1133 191 : EQUAL(pszEndian, "little")));
1134 191 : oCodecs.Add(oCodec);
1135 : }
1136 :
1137 : const char *pszCompressor =
1138 197 : CSLFetchNameValueDef(papszOptions, "COMPRESS", "NONE");
1139 197 : if (EQUAL(pszCompressor, "GZIP"))
1140 : {
1141 62 : CPLJSONObject oCodec;
1142 31 : oCodec.Add("name", "gzip");
1143 : const char *pszLevel =
1144 31 : CSLFetchNameValueDef(papszOptions, "GZIP_LEVEL", "6");
1145 31 : oCodec.Add("configuration",
1146 62 : ZarrV3CodecGZip::GetConfiguration(atoi(pszLevel)));
1147 31 : oCodecs.Add(oCodec);
1148 : }
1149 166 : else if (EQUAL(pszCompressor, "BLOSC"))
1150 : {
1151 2 : const auto psCompressor = CPLGetCompressor("blosc");
1152 2 : if (!psCompressor)
1153 0 : return nullptr;
1154 : const char *pszOptions =
1155 2 : CSLFetchNameValueDef(psCompressor->papszMetadata, "OPTIONS", "");
1156 2 : CPLXMLTreeCloser oTreeCompressor(CPLParseXMLString(pszOptions));
1157 : const auto psRoot =
1158 2 : oTreeCompressor.get()
1159 2 : ? CPLGetXMLNode(oTreeCompressor.get(), "=Options")
1160 2 : : nullptr;
1161 2 : if (!psRoot)
1162 0 : return nullptr;
1163 :
1164 2 : const char *cname = "zlib";
1165 14 : for (const CPLXMLNode *psNode = psRoot->psChild; psNode != nullptr;
1166 12 : psNode = psNode->psNext)
1167 : {
1168 12 : if (psNode->eType == CXT_Element)
1169 : {
1170 12 : const char *pszName = CPLGetXMLValue(psNode, "name", "");
1171 12 : if (EQUAL(pszName, "CNAME"))
1172 : {
1173 2 : cname = CPLGetXMLValue(psNode, "default", cname);
1174 : }
1175 : }
1176 : }
1177 :
1178 4 : CPLJSONObject oCodec;
1179 2 : oCodec.Add("name", "blosc");
1180 2 : cname = CSLFetchNameValueDef(papszOptions, "BLOSC_CNAME", cname);
1181 : const int clevel =
1182 2 : atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_CLEVEL", "5"));
1183 : const char *shuffle =
1184 2 : CSLFetchNameValueDef(papszOptions, "BLOSC_SHUFFLE", "BYTE");
1185 3 : shuffle = (EQUAL(shuffle, "0") || EQUAL(shuffle, "NONE")) ? "noshuffle"
1186 1 : : (EQUAL(shuffle, "1") || EQUAL(shuffle, "BYTE")) ? "shuffle"
1187 0 : : (EQUAL(shuffle, "2") || EQUAL(shuffle, "BIT"))
1188 0 : ? "bitshuffle"
1189 : : "invalid";
1190 : const int nDefaultTypeSize =
1191 2 : bIsString ? 1
1192 2 : : GDALGetDataTypeSizeBytes(GDALGetNonComplexDataType(
1193 2 : oDataType.GetNumericDataType()));
1194 : const int typesize =
1195 2 : atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_TYPESIZE",
1196 : CPLSPrintf("%d", nDefaultTypeSize)));
1197 : const int blocksize =
1198 2 : atoi(CSLFetchNameValueDef(papszOptions, "BLOSC_BLOCKSIZE", "0"));
1199 2 : oCodec.Add("configuration",
1200 4 : ZarrV3CodecBlosc::GetConfiguration(cname, clevel, shuffle,
1201 : typesize, blocksize));
1202 2 : oCodecs.Add(oCodec);
1203 : }
1204 164 : else if (EQUAL(pszCompressor, "ZSTD"))
1205 : {
1206 14 : CPLJSONObject oCodec;
1207 7 : oCodec.Add("name", "zstd");
1208 : const char *pszLevel =
1209 7 : CSLFetchNameValueDef(papszOptions, "ZSTD_LEVEL", "13");
1210 7 : const bool bChecksum = CPLTestBool(
1211 : CSLFetchNameValueDef(papszOptions, "ZSTD_CHECKSUM", "FALSE"));
1212 7 : oCodec.Add("configuration", ZarrV3CodecZstd::GetConfiguration(
1213 : atoi(pszLevel), bChecksum));
1214 7 : oCodecs.Add(oCodec);
1215 : }
1216 157 : else if (!EQUAL(pszCompressor, "NONE"))
1217 : {
1218 1 : CPLError(CE_Failure, CPLE_AppDefined,
1219 : "COMPRESS = %s not implemented with Zarr V3", pszCompressor);
1220 1 : return nullptr;
1221 : }
1222 :
1223 : // Sharding: wrap inner codecs into a sharding_indexed codec
1224 : const char *pszShardChunkShape =
1225 196 : CSLFetchNameValue(papszOptions, "SHARD_CHUNK_SHAPE");
1226 196 : if (pszShardChunkShape != nullptr)
1227 : {
1228 :
1229 : const CPLStringList aosChunkShape(
1230 13 : CSLTokenizeString2(pszShardChunkShape, ",", 0));
1231 13 : if (static_cast<size_t>(aosChunkShape.size()) != aoDimensions.size())
1232 : {
1233 1 : CPLError(CE_Failure, CPLE_AppDefined,
1234 : "SHARD_CHUNK_SHAPE has %d values, expected %d",
1235 : aosChunkShape.size(),
1236 1 : static_cast<int>(aoDimensions.size()));
1237 1 : return nullptr;
1238 : }
1239 :
1240 12 : CPLJSONArray oChunkShapeArray;
1241 35 : for (int i = 0; i < aosChunkShape.size(); ++i)
1242 : {
1243 24 : const auto nInner = static_cast<GUInt64>(atoll(aosChunkShape[i]));
1244 24 : if (nInner == 0 || anOuterBlockSize[i] % nInner != 0)
1245 : {
1246 1 : CPLError(CE_Failure, CPLE_AppDefined,
1247 : "SHARD_CHUNK_SHAPE[%d]=%s must divide "
1248 : "BLOCKSIZE[%d]=" CPL_FRMT_GUIB " evenly",
1249 1 : i, aosChunkShape[i], i, anOuterBlockSize[i]);
1250 1 : return nullptr;
1251 : }
1252 23 : oChunkShapeArray.Add(static_cast<uint64_t>(nInner));
1253 : }
1254 :
1255 : // Index codecs: always bytes(little) + crc32c
1256 22 : CPLJSONArray oIndexCodecs;
1257 : {
1258 22 : CPLJSONObject oBytesCodec;
1259 11 : oBytesCodec.Add("name", "bytes");
1260 11 : oBytesCodec.Add("configuration",
1261 22 : ZarrV3CodecBytes::GetConfiguration(true));
1262 11 : oIndexCodecs.Add(oBytesCodec);
1263 : }
1264 : {
1265 22 : CPLJSONObject oCRC32CCodec;
1266 11 : oCRC32CCodec.Add("name", "crc32c");
1267 11 : oIndexCodecs.Add(oCRC32CCodec);
1268 : }
1269 :
1270 22 : CPLJSONObject oShardingConfig;
1271 11 : oShardingConfig.Add("chunk_shape", oChunkShapeArray);
1272 11 : oShardingConfig.Add("codecs", oCodecs);
1273 11 : oShardingConfig.Add("index_codecs", oIndexCodecs);
1274 11 : oShardingConfig.Add("index_location", "end");
1275 :
1276 22 : CPLJSONObject oShardingCodec;
1277 11 : oShardingCodec.Add("name", "sharding_indexed");
1278 11 : oShardingCodec.Add("configuration", oShardingConfig);
1279 :
1280 : // Replace top-level codecs with just the sharding codec
1281 11 : oCodecs = CPLJSONArray();
1282 11 : oCodecs.Add(oShardingCodec);
1283 : }
1284 :
1285 388 : std::vector<GUInt64> anInnerBlockSize = anOuterBlockSize;
1286 194 : if (oCodecs.Size() > 0)
1287 : {
1288 194 : std::vector<GByte> abyNoData;
1289 388 : poCodecs = ZarrV3Array::SetupCodecs(oCodecs, anOuterBlockSize,
1290 : anInnerBlockSize,
1291 388 : aoDtypeElts.back(), abyNoData);
1292 194 : if (!poCodecs)
1293 : {
1294 0 : return nullptr;
1295 : }
1296 : }
1297 :
1298 194 : auto poArray = ZarrV3Array::Create(m_poSharedResource, Self(), osName,
1299 : aoDimensions, oDataType, aoDtypeElts,
1300 388 : anOuterBlockSize, anInnerBlockSize);
1301 :
1302 194 : if (!poArray)
1303 0 : return nullptr;
1304 194 : poArray->SetNew(true);
1305 : const std::string osFilename =
1306 388 : CPLFormFilenameSafe(osArrayDirectory.c_str(), "zarr.json", nullptr);
1307 194 : poArray->SetFilename(osFilename);
1308 194 : poArray->SetDimSeparator(pszDimSeparator);
1309 194 : poArray->SetDtype(dtype);
1310 : const std::string osLastCodecName =
1311 388 : oCodecs.Size() > 0 ? oCodecs[oCodecs.Size() - 1].GetString("name")
1312 1358 : : std::string();
1313 246 : if (!osLastCodecName.empty() && osLastCodecName != "bytes" &&
1314 52 : osLastCodecName != "vlen-utf8")
1315 : {
1316 94 : poArray->SetStructuralInfo(
1317 94 : "COMPRESSOR", oCodecs[oCodecs.Size() - 1].ToString().c_str());
1318 : }
1319 194 : if (poCodecs)
1320 194 : poArray->SetCodecs(oCodecs, std::move(poCodecs));
1321 :
1322 194 : poArray->SetCreationOptions(papszOptions);
1323 194 : poArray->SetUpdatable(true);
1324 194 : poArray->SetDefinitionModified(true);
1325 194 : if (!cpl::starts_with(osFilename, "/vsi") && !poArray->Flush())
1326 0 : return nullptr;
1327 194 : RegisterArray(poArray);
1328 :
1329 194 : return poArray;
1330 : }
|