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