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 "cpl_enumerate.h"
14 : #include "cpl_float.h"
15 : #include "cpl_vsi_virtual.h"
16 : #include "gdal_thread_pool.h"
17 : #include "zarr.h"
18 : #include "zarr_v3_codec.h"
19 :
20 : #include <algorithm>
21 : #include <cassert>
22 : #include <cinttypes>
23 : #include <cmath>
24 : #include <cstdlib>
25 : #include <limits>
26 : #include <map>
27 : #include <set>
28 :
29 : /************************************************************************/
30 : /* ZarrV3Array::ZarrV3Array() */
31 : /************************************************************************/
32 :
33 1176 : ZarrV3Array::ZarrV3Array(
34 : const std::shared_ptr<ZarrSharedResource> &poSharedResource,
35 : const std::shared_ptr<ZarrGroupBase> &poParent, const std::string &osName,
36 : const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
37 : const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
38 : const std::vector<GUInt64> &anOuterBlockSize,
39 1176 : const std::vector<GUInt64> &anInnerBlockSize)
40 1176 : : GDALAbstractMDArray(poParent->GetFullName(), osName),
41 : ZarrArray(poSharedResource, poParent, osName, aoDims, oType, aoDtypeElts,
42 1176 : anOuterBlockSize, anInnerBlockSize)
43 : {
44 1176 : }
45 :
46 : /************************************************************************/
47 : /* ZarrV3Array::Create() */
48 : /************************************************************************/
49 :
50 1176 : std::shared_ptr<ZarrV3Array> ZarrV3Array::Create(
51 : const std::shared_ptr<ZarrSharedResource> &poSharedResource,
52 : const std::shared_ptr<ZarrGroupBase> &poParent, const std::string &osName,
53 : const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
54 : const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
55 : const std::vector<GUInt64> &anOuterBlockSize,
56 : const std::vector<GUInt64> &anInnerBlockSize)
57 : {
58 : auto arr = std::shared_ptr<ZarrV3Array>(
59 : new ZarrV3Array(poSharedResource, poParent, osName, aoDims, oType,
60 2352 : aoDtypeElts, anOuterBlockSize, anInnerBlockSize));
61 1176 : if (arr->m_nTotalInnerChunkCount == 0)
62 1 : return nullptr;
63 1175 : arr->SetSelf(arr);
64 :
65 1175 : return arr;
66 : }
67 :
68 : /************************************************************************/
69 : /* ~ZarrV3Array() */
70 : /************************************************************************/
71 :
72 2352 : ZarrV3Array::~ZarrV3Array()
73 : {
74 1176 : ZarrV3Array::Flush();
75 2352 : }
76 :
77 : /************************************************************************/
78 : /* Flush() */
79 : /************************************************************************/
80 :
81 5217 : bool ZarrV3Array::Flush()
82 : {
83 5217 : if (!m_bValid)
84 4 : return true;
85 :
86 5213 : bool ret = ZarrV3Array::FlushDirtyBlock();
87 :
88 5213 : if (!m_aoDims.empty())
89 : {
90 9036 : for (const auto &poDim : m_aoDims)
91 : {
92 : const auto poZarrDim =
93 6752 : dynamic_cast<const ZarrDimension *>(poDim.get());
94 6752 : if (poZarrDim && poZarrDim->IsXArrayDimension())
95 : {
96 3995 : if (poZarrDim->IsModified())
97 8 : m_bDefinitionModified = true;
98 : }
99 : else
100 : {
101 2757 : break;
102 : }
103 : }
104 : }
105 :
106 5213 : CPLJSONObject oAttrs;
107 10384 : if (m_oAttrGroup.IsModified() || m_bUnitModified || m_bOffsetModified ||
108 10384 : m_bScaleModified || m_bSRSModified)
109 : {
110 42 : m_bNew = false;
111 :
112 42 : oAttrs = SerializeSpecialAttributes();
113 :
114 42 : m_bDefinitionModified = true;
115 : }
116 :
117 5213 : if (m_bDefinitionModified)
118 : {
119 163 : if (!Serialize(oAttrs))
120 1 : ret = false;
121 163 : m_bDefinitionModified = false;
122 : }
123 :
124 5213 : return ret;
125 : }
126 :
127 : /************************************************************************/
128 : /* ZarrV3Array::Serialize() */
129 : /************************************************************************/
130 :
131 163 : bool ZarrV3Array::Serialize(const CPLJSONObject &oAttrs)
132 : {
133 326 : CPLJSONDocument oDoc;
134 326 : CPLJSONObject oRoot = oDoc.GetRoot();
135 :
136 163 : oRoot.Add("zarr_format", 3);
137 163 : oRoot.Add("node_type", "array");
138 :
139 163 : CPLJSONArray oShape;
140 417 : for (const auto &poDim : m_aoDims)
141 : {
142 254 : oShape.Add(static_cast<GInt64>(poDim->GetSize()));
143 : }
144 163 : oRoot.Add("shape", oShape);
145 :
146 163 : oRoot.Add("data_type", m_dtype.ToString());
147 :
148 : {
149 326 : CPLJSONObject oChunkGrid;
150 163 : oRoot.Add("chunk_grid", oChunkGrid);
151 163 : oChunkGrid.Add("name", "regular");
152 326 : CPLJSONObject oConfiguration;
153 163 : oChunkGrid.Add("configuration", oConfiguration);
154 163 : CPLJSONArray oChunks;
155 417 : for (const auto nBlockSize : m_anOuterBlockSize)
156 : {
157 254 : oChunks.Add(static_cast<GInt64>(nBlockSize));
158 : }
159 163 : oConfiguration.Add("chunk_shape", oChunks);
160 : }
161 :
162 : {
163 326 : CPLJSONObject oChunkKeyEncoding;
164 163 : oRoot.Add("chunk_key_encoding", oChunkKeyEncoding);
165 163 : oChunkKeyEncoding.Add("name", m_bV2ChunkKeyEncoding ? "v2" : "default");
166 163 : CPLJSONObject oConfiguration;
167 163 : oChunkKeyEncoding.Add("configuration", oConfiguration);
168 163 : oConfiguration.Add("separator", m_osDimSeparator);
169 : }
170 :
171 163 : if (m_pabyNoData == nullptr)
172 : {
173 277 : if (m_oType.GetNumericDataType() == GDT_Float16 ||
174 277 : m_oType.GetNumericDataType() == GDT_Float32 ||
175 131 : m_oType.GetNumericDataType() == GDT_Float64)
176 : {
177 19 : oRoot.Add("fill_value", "NaN");
178 : }
179 : else
180 : {
181 120 : oRoot.AddNull("fill_value");
182 : }
183 : }
184 : else
185 : {
186 48 : if (m_oType.GetNumericDataType() == GDT_CFloat16 ||
187 48 : m_oType.GetNumericDataType() == GDT_CFloat32 ||
188 16 : m_oType.GetNumericDataType() == GDT_CFloat64)
189 : {
190 : double adfNoDataValue[2];
191 16 : GDALCopyWords(m_pabyNoData, m_oType.GetNumericDataType(), 0,
192 : adfNoDataValue, GDT_CFloat64, 0, 1);
193 16 : CPLJSONArray oArray;
194 48 : for (int i = 0; i < 2; ++i)
195 : {
196 32 : if (std::isnan(adfNoDataValue[i]))
197 6 : oArray.Add("NaN");
198 26 : else if (adfNoDataValue[i] ==
199 26 : std::numeric_limits<double>::infinity())
200 4 : oArray.Add("Infinity");
201 44 : else if (adfNoDataValue[i] ==
202 22 : -std::numeric_limits<double>::infinity())
203 4 : oArray.Add("-Infinity");
204 : else
205 18 : oArray.Add(adfNoDataValue[i]);
206 : }
207 16 : oRoot.Add("fill_value", oArray);
208 : }
209 : else
210 : {
211 8 : SerializeNumericNoData(oRoot);
212 : }
213 : }
214 :
215 163 : if (m_poCodecs)
216 : {
217 147 : oRoot.Add("codecs", m_poCodecs->GetJSon());
218 : }
219 :
220 163 : oRoot.Add("attributes", oAttrs);
221 :
222 : // Set dimension_names
223 163 : if (!m_aoDims.empty())
224 : {
225 288 : CPLJSONArray oDimensions;
226 382 : for (const auto &poDim : m_aoDims)
227 : {
228 : const auto poZarrDim =
229 254 : dynamic_cast<const ZarrDimension *>(poDim.get());
230 254 : if (poZarrDim && poZarrDim->IsXArrayDimension())
231 : {
232 238 : oDimensions.Add(poDim->GetName());
233 : }
234 : else
235 : {
236 16 : oDimensions = CPLJSONArray();
237 16 : break;
238 : }
239 : }
240 144 : if (oDimensions.Size() > 0)
241 : {
242 128 : oRoot.Add("dimension_names", oDimensions);
243 : }
244 : }
245 :
246 : // TODO: codecs
247 :
248 163 : const bool bRet = oDoc.Save(m_osFilename);
249 163 : if (bRet)
250 162 : m_poSharedResource->SetZMetadataItem(m_osFilename, oRoot);
251 326 : return bRet;
252 : }
253 :
254 : /************************************************************************/
255 : /* ZarrV3Array::NeedDecodedBuffer() */
256 : /************************************************************************/
257 :
258 15835 : bool ZarrV3Array::NeedDecodedBuffer() const
259 : {
260 31670 : for (const auto &elt : m_aoDtypeElts)
261 : {
262 15835 : if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative)
263 : {
264 0 : return true;
265 : }
266 : }
267 15835 : return false;
268 : }
269 :
270 : /************************************************************************/
271 : /* ZarrV3Array::AllocateWorkingBuffers() */
272 : /************************************************************************/
273 :
274 823 : bool ZarrV3Array::AllocateWorkingBuffers() const
275 : {
276 823 : if (m_bAllocateWorkingBuffersDone)
277 10 : return m_bWorkingBuffersOK;
278 :
279 813 : m_bAllocateWorkingBuffersDone = true;
280 :
281 813 : size_t nSizeNeeded = m_nInnerBlockSizeBytes;
282 813 : if (NeedDecodedBuffer())
283 : {
284 0 : size_t nDecodedBufferSize = m_oType.GetSize();
285 0 : for (const auto &nBlockSize : m_anInnerBlockSize)
286 : {
287 0 : if (nDecodedBufferSize > std::numeric_limits<size_t>::max() /
288 0 : static_cast<size_t>(nBlockSize))
289 : {
290 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
291 0 : return false;
292 : }
293 0 : nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
294 : }
295 0 : if (nSizeNeeded >
296 0 : std::numeric_limits<size_t>::max() - nDecodedBufferSize)
297 : {
298 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
299 0 : return false;
300 : }
301 0 : nSizeNeeded += nDecodedBufferSize;
302 : }
303 :
304 : // Reserve a buffer for tile content
305 813 : if (nSizeNeeded > 1024 * 1024 * 1024 &&
306 0 : !CPLTestBool(CPLGetConfigOption("ZARR_ALLOW_BIG_TILE_SIZE", "NO")))
307 : {
308 0 : CPLError(CE_Failure, CPLE_AppDefined,
309 : "Zarr tile allocation would require " CPL_FRMT_GUIB " bytes. "
310 : "By default the driver limits to 1 GB. To allow that memory "
311 : "allocation, set the ZARR_ALLOW_BIG_TILE_SIZE configuration "
312 : "option to YES.",
313 : static_cast<GUIntBig>(nSizeNeeded));
314 0 : return false;
315 : }
316 :
317 813 : m_bWorkingBuffersOK =
318 813 : AllocateWorkingBuffers(m_abyRawBlockData, m_abyDecodedBlockData);
319 813 : return m_bWorkingBuffersOK;
320 : }
321 :
322 11510 : bool ZarrV3Array::AllocateWorkingBuffers(
323 : ZarrByteVectorQuickResize &abyRawBlockData,
324 : ZarrByteVectorQuickResize &abyDecodedBlockData) const
325 : {
326 : // This method should NOT modify any ZarrArray member, as it is going to
327 : // be called concurrently from several threads.
328 :
329 : // Set those #define to avoid accidental use of some global variables
330 : #define m_abyRawBlockData cannot_use_here
331 : #define m_abyDecodedBlockData cannot_use_here
332 :
333 11510 : const size_t nSizeNeeded = m_nInnerBlockSizeBytes;
334 : try
335 : {
336 11510 : abyRawBlockData.resize(nSizeNeeded);
337 : }
338 0 : catch (const std::bad_alloc &e)
339 : {
340 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
341 0 : return false;
342 : }
343 :
344 11510 : if (NeedDecodedBuffer())
345 : {
346 0 : size_t nDecodedBufferSize = m_oType.GetSize();
347 0 : for (const auto &nBlockSize : m_anInnerBlockSize)
348 : {
349 0 : nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
350 : }
351 : try
352 : {
353 0 : abyDecodedBlockData.resize(nDecodedBufferSize);
354 : }
355 0 : catch (const std::bad_alloc &e)
356 : {
357 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
358 0 : return false;
359 : }
360 : }
361 :
362 11510 : return true;
363 : #undef m_abyRawBlockData
364 : #undef m_abyDecodedBlockData
365 : }
366 :
367 : /************************************************************************/
368 : /* ZarrV3Array::LoadBlockData() */
369 : /************************************************************************/
370 :
371 1687 : bool ZarrV3Array::LoadBlockData(const uint64_t *blockIndices,
372 : bool &bMissingBlockOut) const
373 : {
374 1687 : return LoadBlockData(blockIndices,
375 : false, // use mutex
376 1687 : m_poCodecs.get(), m_abyRawBlockData,
377 3374 : m_abyDecodedBlockData, bMissingBlockOut);
378 : }
379 :
380 12384 : bool ZarrV3Array::LoadBlockData(const uint64_t *blockIndices, bool bUseMutex,
381 : ZarrV3CodecSequence *poCodecs,
382 : ZarrByteVectorQuickResize &abyRawBlockData,
383 : ZarrByteVectorQuickResize &abyDecodedBlockData,
384 : bool &bMissingBlockOut) const
385 : {
386 : // This method should NOT modify any ZarrArray member, as it is going to
387 : // be called concurrently from several threads.
388 :
389 : // Set those #define to avoid accidental use of some global variables
390 : #define m_abyRawBlockData cannot_use_here
391 : #define m_abyDecodedBlockData cannot_use_here
392 : #define m_poCodecs cannot_use_here
393 :
394 12384 : bMissingBlockOut = false;
395 :
396 24768 : std::string osFilename;
397 12384 : if (poCodecs && poCodecs->SupportsPartialDecoding())
398 : {
399 1138 : std::vector<uint64_t> outerChunkIndices;
400 3414 : for (size_t i = 0; i < GetDimensionCount(); ++i)
401 : {
402 : // Note: m_anOuterBlockSize[i]/m_anInnerBlockSize[i] is an integer
403 6828 : outerChunkIndices.push_back(blockIndices[i] *
404 4552 : m_anInnerBlockSize[i] /
405 2276 : m_anOuterBlockSize[i]);
406 : }
407 :
408 1138 : osFilename = BuildChunkFilename(outerChunkIndices.data());
409 : }
410 : else
411 : {
412 11246 : osFilename = BuildChunkFilename(blockIndices);
413 : }
414 :
415 : // For network file systems, get the streaming version of the filename,
416 : // as we don't need arbitrary seeking in the file
417 : // ... unless we do partial decoding, in which case range requests within
418 : // a shard are much more efficient
419 12384 : if (!(poCodecs && poCodecs->SupportsPartialDecoding()))
420 : {
421 11246 : osFilename = VSIFileManager::GetHandler(osFilename.c_str())
422 11246 : ->GetStreamingFilename(osFilename);
423 : }
424 :
425 : // First if we have a tile presence cache, check tile presence from it
426 : bool bEarlyRet;
427 12384 : if (bUseMutex)
428 : {
429 10697 : std::lock_guard<std::mutex> oLock(m_oMutex);
430 10697 : bEarlyRet = IsBlockMissingFromCacheInfo(osFilename, blockIndices);
431 : }
432 : else
433 : {
434 1687 : bEarlyRet = IsBlockMissingFromCacheInfo(osFilename, blockIndices);
435 : }
436 12384 : if (bEarlyRet)
437 : {
438 13 : bMissingBlockOut = true;
439 13 : return true;
440 : }
441 12371 : VSIVirtualHandleUniquePtr fp;
442 : // This is the number of files returned in a S3 directory listing operation
443 12371 : constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
444 12371 : const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
445 : nullptr};
446 12371 : const auto nErrorBefore = CPLGetErrorCounter();
447 24733 : if ((m_osDimSeparator == "/" && !m_anOuterBlockSize.empty() &&
448 37113 : m_anOuterBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
449 12371 : (m_osDimSeparator != "/" &&
450 9 : m_nTotalInnerChunkCount > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING))
451 : {
452 : // Avoid issuing ReadDir() when a lot of files are expected
453 : CPLConfigOptionSetter optionSetter("GDAL_DISABLE_READDIR_ON_OPEN",
454 0 : "YES", true);
455 0 : fp.reset(VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions));
456 : }
457 : else
458 : {
459 12371 : fp.reset(VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions));
460 : }
461 12371 : if (fp == nullptr)
462 : {
463 306 : if (nErrorBefore != CPLGetErrorCounter())
464 : {
465 0 : return false;
466 : }
467 : else
468 : {
469 : // Missing files are OK and indicate nodata_value
470 306 : CPLDebugOnly(ZARR_DEBUG_KEY, "Block %s missing (=nodata)",
471 : osFilename.c_str());
472 306 : bMissingBlockOut = true;
473 306 : return true;
474 : }
475 : }
476 :
477 12065 : bMissingBlockOut = false;
478 :
479 12065 : if (poCodecs && poCodecs->SupportsPartialDecoding())
480 : {
481 1132 : std::vector<size_t> anStartIdx;
482 1132 : std::vector<size_t> anCount;
483 3396 : for (size_t i = 0; i < GetDimensionCount(); ++i)
484 : {
485 2264 : anStartIdx.push_back(
486 4528 : static_cast<size_t>((blockIndices[i] * m_anInnerBlockSize[i]) %
487 2264 : m_anOuterBlockSize[i]));
488 2264 : anCount.push_back(static_cast<size_t>(m_anInnerBlockSize[i]));
489 : }
490 1132 : if (!poCodecs->DecodePartial(fp.get(), abyRawBlockData, anStartIdx,
491 : anCount))
492 436 : return false;
493 : }
494 : else
495 : {
496 10933 : CPLAssert(abyRawBlockData.capacity() >= m_nInnerBlockSizeBytes);
497 : // should not fail
498 10933 : abyRawBlockData.resize(m_nInnerBlockSizeBytes);
499 :
500 10933 : bool bRet = true;
501 10933 : size_t nRawDataSize = abyRawBlockData.size();
502 10933 : if (poCodecs == nullptr)
503 : {
504 6 : nRawDataSize = fp->Read(&abyRawBlockData[0], 1, nRawDataSize);
505 : }
506 : else
507 : {
508 10927 : fp->Seek(0, SEEK_END);
509 10927 : const auto nSize = fp->Tell();
510 10927 : fp->Seek(0, SEEK_SET);
511 10927 : if (nSize >
512 10927 : static_cast<vsi_l_offset>(std::numeric_limits<int>::max()))
513 : {
514 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large tile %s",
515 : osFilename.c_str());
516 0 : bRet = false;
517 : }
518 : else
519 : {
520 : try
521 : {
522 10927 : abyRawBlockData.resize(static_cast<size_t>(nSize));
523 : }
524 0 : catch (const std::exception &)
525 : {
526 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
527 : "Cannot allocate memory for tile %s",
528 : osFilename.c_str());
529 0 : bRet = false;
530 : }
531 :
532 32780 : if (bRet &&
533 21853 : (abyRawBlockData.empty() ||
534 10926 : fp->Read(&abyRawBlockData[0], 1, abyRawBlockData.size()) !=
535 10926 : abyRawBlockData.size()))
536 : {
537 1 : CPLError(CE_Failure, CPLE_AppDefined,
538 : "Could not read tile %s correctly",
539 : osFilename.c_str());
540 1 : bRet = false;
541 : }
542 : else
543 : {
544 10926 : if (!poCodecs->Decode(abyRawBlockData))
545 : {
546 131 : CPLError(CE_Failure, CPLE_AppDefined,
547 : "Decompression of tile %s failed",
548 : osFilename.c_str());
549 131 : bRet = false;
550 : }
551 : }
552 : }
553 : }
554 10933 : if (!bRet)
555 132 : return false;
556 :
557 10801 : if (nRawDataSize != abyRawBlockData.size())
558 : {
559 0 : CPLError(CE_Failure, CPLE_AppDefined,
560 : "Decompressed tile %s has not expected size. "
561 : "Got %u instead of %u",
562 : osFilename.c_str(),
563 0 : static_cast<unsigned>(abyRawBlockData.size()),
564 : static_cast<unsigned>(nRawDataSize));
565 0 : return false;
566 : }
567 : }
568 :
569 11497 : if (!abyDecodedBlockData.empty())
570 : {
571 : const size_t nSourceSize =
572 0 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
573 0 : const auto nDTSize = m_oType.GetSize();
574 0 : const size_t nValues = abyDecodedBlockData.size() / nDTSize;
575 0 : CPLAssert(nValues == m_nInnerBlockSizeBytes / nSourceSize);
576 0 : const GByte *pSrc = abyRawBlockData.data();
577 0 : GByte *pDst = &abyDecodedBlockData[0];
578 0 : for (size_t i = 0; i < nValues;
579 0 : i++, pSrc += nSourceSize, pDst += nDTSize)
580 : {
581 0 : DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
582 : }
583 : }
584 :
585 11497 : return true;
586 :
587 : #undef m_abyRawBlockData
588 : #undef m_abyDecodedBlockData
589 : #undef m_poCodecs
590 : }
591 :
592 : /************************************************************************/
593 : /* ZarrV3Array::IRead() */
594 : /************************************************************************/
595 :
596 743 : bool ZarrV3Array::IRead(const GUInt64 *arrayStartIdx, const size_t *count,
597 : const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
598 : const GDALExtendedDataType &bufferDataType,
599 : void *pDstBuffer) const
600 : {
601 : // For sharded arrays, pre-populate the block cache via ReadMultiRange()
602 : // so that the base-class block-by-block loop hits memory, not HTTP.
603 743 : if (m_poCodecs && m_poCodecs->SupportsPartialDecoding())
604 : {
605 501 : PreloadShardedBlocks(arrayStartIdx, count);
606 : }
607 743 : return ZarrArray::IRead(arrayStartIdx, count, arrayStep, bufferStride,
608 743 : bufferDataType, pDstBuffer);
609 : }
610 :
611 : /************************************************************************/
612 : /* ZarrV3Array::PreloadShardedBlocks() */
613 : /************************************************************************/
614 :
615 501 : void ZarrV3Array::PreloadShardedBlocks(const GUInt64 *arrayStartIdx,
616 : const size_t *count) const
617 : {
618 501 : const size_t nDims = m_aoDims.size();
619 501 : if (nDims == 0)
620 2 : return;
621 :
622 : // Calculate needed block index range
623 1002 : std::vector<uint64_t> anBlockMin(nDims), anBlockMax(nDims);
624 501 : size_t nTotalBlocks = 1;
625 1503 : for (size_t i = 0; i < nDims; ++i)
626 : {
627 1002 : anBlockMin[i] = arrayStartIdx[i] / m_anInnerBlockSize[i];
628 2004 : anBlockMax[i] =
629 1002 : (arrayStartIdx[i] + count[i] - 1) / m_anInnerBlockSize[i];
630 1002 : nTotalBlocks *= static_cast<size_t>(anBlockMax[i] - anBlockMin[i] + 1);
631 : }
632 :
633 501 : if (nTotalBlocks <= 1)
634 2 : return; // single block — no batching benefit
635 :
636 499 : CPLDebugOnly("ZARR", "PreloadShardedBlocks: %" PRIu64 " blocks to batch",
637 : static_cast<uint64_t>(nTotalBlocks));
638 :
639 : // Enumerate all needed blocks, grouped by shard filename
640 : struct BlockInfo
641 : {
642 : std::vector<uint64_t> anBlockIndices{};
643 : std::vector<size_t> anStartIdx{};
644 : std::vector<size_t> anCount{};
645 : };
646 :
647 998 : std::map<std::string, std::vector<BlockInfo>> oShardToBlocks;
648 :
649 : // Iterate over all needed block indices
650 998 : std::vector<uint64_t> anCur(nDims);
651 499 : size_t dimIdx = 0;
652 15380 : lbl_next:
653 15380 : if (dimIdx == nDims)
654 : {
655 : // Skip blocks already in cache
656 24792 : const std::vector<uint64_t> cacheKey(anCur.begin(), anCur.end());
657 12396 : if (m_oChunkCache.find(cacheKey) == m_oChunkCache.end())
658 : {
659 : // Compute shard filename and inner chunk start/count
660 24686 : std::vector<uint64_t> outerIdx(nDims);
661 24686 : BlockInfo info;
662 12343 : info.anBlockIndices = anCur;
663 12343 : info.anStartIdx.resize(nDims);
664 12343 : info.anCount.resize(nDims);
665 37029 : for (size_t i = 0; i < nDims; ++i)
666 : {
667 49372 : outerIdx[i] =
668 24686 : anCur[i] * m_anInnerBlockSize[i] / m_anOuterBlockSize[i];
669 49372 : info.anStartIdx[i] = static_cast<size_t>(
670 24686 : (anCur[i] * m_anInnerBlockSize[i]) % m_anOuterBlockSize[i]);
671 24686 : info.anCount[i] = static_cast<size_t>(m_anInnerBlockSize[i]);
672 : }
673 :
674 24686 : std::string osFilename = BuildChunkFilename(outerIdx.data());
675 12343 : oShardToBlocks[osFilename].push_back(std::move(info));
676 : }
677 : }
678 : else
679 : {
680 2984 : anCur[dimIdx] = anBlockMin[dimIdx];
681 : while (true)
682 : {
683 14881 : dimIdx++;
684 14881 : goto lbl_next;
685 14881 : lbl_return:
686 14881 : dimIdx--;
687 14881 : if (anCur[dimIdx] == anBlockMax[dimIdx])
688 2984 : break;
689 11897 : ++anCur[dimIdx];
690 : }
691 : }
692 15380 : if (dimIdx > 0)
693 14881 : goto lbl_return;
694 :
695 : // For each shard with >1 uncached block, batch-read
696 499 : const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
697 : nullptr};
698 :
699 4944 : for (auto &[osFilename, aBlocks] : oShardToBlocks)
700 : {
701 4445 : if (aBlocks.size() <= 1)
702 933 : continue;
703 :
704 : VSIVirtualHandleUniquePtr fp(
705 3950 : VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions));
706 3950 : if (!fp)
707 2 : continue;
708 :
709 : // Build request list
710 : std::vector<std::pair<std::vector<size_t>, std::vector<size_t>>>
711 3948 : anRequests;
712 3948 : anRequests.reserve(aBlocks.size());
713 15790 : for (const auto &info : aBlocks)
714 : {
715 11842 : anRequests.push_back({info.anStartIdx, info.anCount});
716 : }
717 :
718 3948 : std::vector<ZarrByteVectorQuickResize> aResults;
719 3948 : if (!m_poCodecs->BatchDecodePartial(fp.get(), anRequests, aResults))
720 436 : continue;
721 :
722 : // Store results in block cache
723 3512 : const bool bNeedDecode = NeedDecodedBuffer();
724 13610 : for (size_t i = 0; i < aBlocks.size(); ++i)
725 : {
726 10098 : if (aResults[i].empty())
727 : {
728 0 : CachedBlock cachedBlock;
729 0 : m_oChunkCache[aBlocks[i].anBlockIndices] =
730 0 : std::move(cachedBlock);
731 0 : continue;
732 : }
733 :
734 20196 : CachedBlock cachedBlock;
735 10098 : if (bNeedDecode)
736 : {
737 0 : const size_t nSourceSize = m_aoDtypeElts.back().nativeOffset +
738 0 : m_aoDtypeElts.back().nativeSize;
739 0 : const auto nGDALDTSize = m_oType.GetSize();
740 0 : const size_t nValues = aResults[i].size() / nSourceSize;
741 0 : ZarrByteVectorQuickResize abyDecoded;
742 0 : abyDecoded.resize(nValues * nGDALDTSize);
743 0 : const GByte *pSrc = aResults[i].data();
744 0 : GByte *pDst = abyDecoded.data();
745 0 : for (size_t v = 0; v < nValues;
746 0 : v++, pSrc += nSourceSize, pDst += nGDALDTSize)
747 : {
748 0 : DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
749 : }
750 0 : std::swap(cachedBlock.abyDecoded, abyDecoded);
751 : }
752 : else
753 : {
754 10098 : std::swap(cachedBlock.abyDecoded, aResults[i]);
755 : }
756 10098 : m_oChunkCache[aBlocks[i].anBlockIndices] = std::move(cachedBlock);
757 : }
758 : }
759 : }
760 :
761 : /************************************************************************/
762 : /* ZarrV3Array::IAdviseRead() */
763 : /************************************************************************/
764 :
765 7 : bool ZarrV3Array::IAdviseRead(const GUInt64 *arrayStartIdx, const size_t *count,
766 : CSLConstList papszOptions) const
767 : {
768 14 : std::vector<uint64_t> anIndicesCur;
769 7 : int nThreadsMax = 0;
770 14 : std::vector<uint64_t> anReqBlocksIndices;
771 7 : size_t nReqBlocks = 0;
772 7 : if (!IAdviseReadCommon(arrayStartIdx, count, papszOptions, anIndicesCur,
773 : nThreadsMax, anReqBlocksIndices, nReqBlocks))
774 : {
775 2 : return false;
776 : }
777 5 : if (nThreadsMax <= 1)
778 : {
779 0 : return true;
780 : }
781 :
782 : const int nThreads = static_cast<int>(
783 5 : std::min(static_cast<size_t>(nThreadsMax), nReqBlocks));
784 :
785 5 : CPLWorkerThreadPool *wtp = GDALGetGlobalThreadPool(nThreadsMax);
786 5 : if (wtp == nullptr)
787 0 : return false;
788 :
789 : struct JobStruct
790 : {
791 : JobStruct() = default;
792 :
793 : JobStruct(const JobStruct &) = delete;
794 : JobStruct &operator=(const JobStruct &) = delete;
795 :
796 : JobStruct(JobStruct &&) = default;
797 : JobStruct &operator=(JobStruct &&) = default;
798 :
799 : const ZarrV3Array *poArray = nullptr;
800 : bool *pbGlobalStatus = nullptr;
801 : int *pnRemainingThreads = nullptr;
802 : const std::vector<uint64_t> *panReqBlocksIndices = nullptr;
803 : size_t nFirstIdx = 0;
804 : size_t nLastIdxNotIncluded = 0;
805 : };
806 :
807 5 : std::vector<JobStruct> asJobStructs;
808 :
809 5 : bool bGlobalStatus = true;
810 5 : int nRemainingThreads = nThreads;
811 : // Check for very highly overflow in below loop
812 5 : assert(static_cast<size_t>(nThreads) <
813 : std::numeric_limits<size_t>::max() / nReqBlocks);
814 :
815 : // Setup jobs
816 25 : for (int i = 0; i < nThreads; i++)
817 : {
818 20 : JobStruct jobStruct;
819 20 : jobStruct.poArray = this;
820 20 : jobStruct.pbGlobalStatus = &bGlobalStatus;
821 20 : jobStruct.pnRemainingThreads = &nRemainingThreads;
822 20 : jobStruct.panReqBlocksIndices = &anReqBlocksIndices;
823 20 : jobStruct.nFirstIdx = static_cast<size_t>(i * nReqBlocks / nThreads);
824 20 : jobStruct.nLastIdxNotIncluded = std::min(
825 20 : static_cast<size_t>((i + 1) * nReqBlocks / nThreads), nReqBlocks);
826 20 : asJobStructs.emplace_back(std::move(jobStruct));
827 : }
828 :
829 20 : const auto JobFunc = [](void *pThreadData)
830 : {
831 20 : const JobStruct *jobStruct =
832 : static_cast<const JobStruct *>(pThreadData);
833 :
834 20 : const auto poArray = jobStruct->poArray;
835 20 : const size_t l_nDims = poArray->GetDimensionCount();
836 20 : ZarrByteVectorQuickResize abyRawBlockData;
837 20 : ZarrByteVectorQuickResize abyDecodedBlockData;
838 0 : std::unique_ptr<ZarrV3CodecSequence> poCodecs;
839 20 : if (poArray->m_poCodecs)
840 : {
841 20 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
842 20 : poCodecs = poArray->m_poCodecs->Clone();
843 : }
844 :
845 10717 : for (size_t iReq = jobStruct->nFirstIdx;
846 10717 : iReq < jobStruct->nLastIdxNotIncluded; ++iReq)
847 : {
848 : // Check if we must early exit
849 : {
850 10697 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
851 10697 : if (!(*jobStruct->pbGlobalStatus))
852 0 : return;
853 : }
854 :
855 : const uint64_t *blockIndices =
856 10697 : jobStruct->panReqBlocksIndices->data() + iReq * l_nDims;
857 :
858 10697 : if (!poArray->AllocateWorkingBuffers(abyRawBlockData,
859 : abyDecodedBlockData))
860 : {
861 0 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
862 0 : *jobStruct->pbGlobalStatus = false;
863 0 : break;
864 : }
865 :
866 10697 : bool bIsEmpty = false;
867 10697 : bool success = poArray->LoadBlockData(
868 : blockIndices,
869 : true, // use mutex
870 : poCodecs.get(), abyRawBlockData, abyDecodedBlockData, bIsEmpty);
871 :
872 10697 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
873 10697 : if (!success)
874 : {
875 0 : *jobStruct->pbGlobalStatus = false;
876 0 : break;
877 : }
878 :
879 21394 : CachedBlock cachedBlock;
880 10697 : if (!bIsEmpty)
881 : {
882 10695 : if (!abyDecodedBlockData.empty())
883 0 : std::swap(cachedBlock.abyDecoded, abyDecodedBlockData);
884 : else
885 10695 : std::swap(cachedBlock.abyDecoded, abyRawBlockData);
886 : }
887 : const std::vector<uint64_t> cacheKey{blockIndices,
888 21394 : blockIndices + l_nDims};
889 10697 : poArray->m_oChunkCache[cacheKey] = std::move(cachedBlock);
890 : }
891 :
892 20 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
893 20 : (*jobStruct->pnRemainingThreads)--;
894 : };
895 :
896 : // Start jobs
897 25 : for (int i = 0; i < nThreads; i++)
898 : {
899 20 : if (!wtp->SubmitJob(JobFunc, &asJobStructs[i]))
900 : {
901 0 : std::lock_guard<std::mutex> oLock(m_oMutex);
902 0 : bGlobalStatus = false;
903 0 : nRemainingThreads = i;
904 0 : break;
905 : }
906 : }
907 :
908 : // Wait for all jobs to be finished
909 : while (true)
910 : {
911 : {
912 21 : std::lock_guard<std::mutex> oLock(m_oMutex);
913 21 : if (nRemainingThreads == 0)
914 5 : break;
915 : }
916 16 : wtp->WaitEvent();
917 16 : }
918 :
919 5 : return bGlobalStatus;
920 : }
921 :
922 : /************************************************************************/
923 : /* ZarrV3Array::FlushDirtyBlock() */
924 : /************************************************************************/
925 :
926 17356 : bool ZarrV3Array::FlushDirtyBlock() const
927 : {
928 17356 : if (!m_bDirtyBlock)
929 6603 : return true;
930 10753 : m_bDirtyBlock = false;
931 :
932 21506 : std::string osFilename = BuildChunkFilename(m_anCachedBlockIndices.data());
933 :
934 : const size_t nSourceSize =
935 10753 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
936 10753 : const auto &abyBlock = m_abyDecodedBlockData.empty()
937 : ? m_abyRawBlockData
938 10753 : : m_abyDecodedBlockData;
939 :
940 10753 : if (IsEmptyBlock(abyBlock))
941 : {
942 3 : m_bCachedBlockEmpty = true;
943 :
944 : VSIStatBufL sStat;
945 3 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
946 : {
947 0 : CPLDebugOnly(ZARR_DEBUG_KEY,
948 : "Deleting tile %s that has now empty content",
949 : osFilename.c_str());
950 0 : return VSIUnlink(osFilename.c_str()) == 0;
951 : }
952 3 : return true;
953 : }
954 :
955 10750 : if (!m_abyDecodedBlockData.empty())
956 : {
957 0 : const size_t nDTSize = m_oType.GetSize();
958 0 : const size_t nValues = m_abyDecodedBlockData.size() / nDTSize;
959 0 : GByte *pDst = &m_abyRawBlockData[0];
960 0 : const GByte *pSrc = m_abyDecodedBlockData.data();
961 0 : for (size_t i = 0; i < nValues;
962 0 : i++, pDst += nSourceSize, pSrc += nDTSize)
963 : {
964 0 : EncodeElt(m_aoDtypeElts, pSrc, pDst);
965 : }
966 : }
967 :
968 10750 : const size_t nSizeBefore = m_abyRawBlockData.size();
969 10750 : if (m_poCodecs)
970 : {
971 10750 : if (!m_poCodecs->Encode(m_abyRawBlockData))
972 : {
973 0 : m_abyRawBlockData.resize(nSizeBefore);
974 0 : return false;
975 : }
976 : }
977 :
978 10750 : if (m_osDimSeparator == "/")
979 : {
980 10750 : std::string osDir = CPLGetDirnameSafe(osFilename.c_str());
981 : VSIStatBufL sStat;
982 10750 : if (VSIStatL(osDir.c_str(), &sStat) != 0)
983 : {
984 212 : if (VSIMkdirRecursive(osDir.c_str(), 0755) != 0)
985 : {
986 0 : CPLError(CE_Failure, CPLE_AppDefined,
987 : "Cannot create directory %s", osDir.c_str());
988 0 : m_abyRawBlockData.resize(nSizeBefore);
989 0 : return false;
990 : }
991 : }
992 : }
993 :
994 10750 : VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
995 10750 : if (fp == nullptr)
996 : {
997 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot create tile %s",
998 : osFilename.c_str());
999 0 : m_abyRawBlockData.resize(nSizeBefore);
1000 0 : return false;
1001 : }
1002 :
1003 10750 : bool bRet = true;
1004 10750 : const size_t nRawDataSize = m_abyRawBlockData.size();
1005 10750 : if (VSIFWriteL(m_abyRawBlockData.data(), 1, nRawDataSize, fp) !=
1006 : nRawDataSize)
1007 : {
1008 0 : CPLError(CE_Failure, CPLE_AppDefined,
1009 : "Could not write tile %s correctly", osFilename.c_str());
1010 0 : bRet = false;
1011 : }
1012 10750 : VSIFCloseL(fp);
1013 :
1014 10750 : m_abyRawBlockData.resize(nSizeBefore);
1015 :
1016 10750 : return bRet;
1017 : }
1018 :
1019 : /************************************************************************/
1020 : /* ZarrV3Array::IWrite() */
1021 : /************************************************************************/
1022 :
1023 80 : bool ZarrV3Array::IWrite(const GUInt64 *arrayStartIdx, const size_t *count,
1024 : const GInt64 *arrayStep,
1025 : const GPtrDiff_t *bufferStride,
1026 : const GDALExtendedDataType &bufferDataType,
1027 : const void *pSrcBuffer)
1028 : {
1029 80 : if (m_poCodecs && m_poCodecs->SupportsPartialDecoding())
1030 : {
1031 0 : CPLError(CE_Failure, CPLE_NotSupported,
1032 : "Writing to sharded dataset is not supported");
1033 0 : return false;
1034 : }
1035 80 : return ZarrArray::IWrite(arrayStartIdx, count, arrayStep, bufferStride,
1036 80 : bufferDataType, pSrcBuffer);
1037 : }
1038 :
1039 : /************************************************************************/
1040 : /* BuildChunkFilename() */
1041 : /************************************************************************/
1042 :
1043 35486 : std::string ZarrV3Array::BuildChunkFilename(const uint64_t *blockIndices) const
1044 : {
1045 35486 : if (m_aoDims.empty())
1046 : {
1047 : return CPLFormFilenameSafe(
1048 0 : CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
1049 0 : m_bV2ChunkKeyEncoding ? "0" : "c", nullptr);
1050 : }
1051 : else
1052 : {
1053 70972 : std::string osFilename(CPLGetDirnameSafe(m_osFilename.c_str()));
1054 35486 : osFilename += '/';
1055 35486 : if (!m_bV2ChunkKeyEncoding)
1056 : {
1057 35477 : osFilename += 'c';
1058 : }
1059 106465 : for (size_t i = 0; i < m_aoDims.size(); ++i)
1060 : {
1061 70979 : if (i > 0 || !m_bV2ChunkKeyEncoding)
1062 70970 : osFilename += m_osDimSeparator;
1063 70979 : osFilename += std::to_string(blockIndices[i]);
1064 : }
1065 35486 : return osFilename;
1066 : }
1067 : }
1068 :
1069 : /************************************************************************/
1070 : /* GetDataDirectory() */
1071 : /************************************************************************/
1072 :
1073 3 : std::string ZarrV3Array::GetDataDirectory() const
1074 : {
1075 3 : return std::string(CPLGetDirnameSafe(m_osFilename.c_str()));
1076 : }
1077 :
1078 : /************************************************************************/
1079 : /* GetChunkIndicesFromFilename() */
1080 : /************************************************************************/
1081 :
1082 : CPLStringList
1083 15 : ZarrV3Array::GetChunkIndicesFromFilename(const char *pszFilename) const
1084 : {
1085 15 : if (!m_bV2ChunkKeyEncoding)
1086 : {
1087 15 : if (pszFilename[0] != 'c')
1088 4 : return CPLStringList();
1089 11 : if (m_osDimSeparator == "/")
1090 : {
1091 11 : if (pszFilename[1] != '/' && pszFilename[1] != '\\')
1092 0 : return CPLStringList();
1093 : }
1094 0 : else if (pszFilename[1] != m_osDimSeparator[0])
1095 : {
1096 0 : return CPLStringList();
1097 : }
1098 : }
1099 : return CPLStringList(
1100 11 : CSLTokenizeString2(pszFilename + (m_bV2ChunkKeyEncoding ? 0 : 2),
1101 22 : m_osDimSeparator.c_str(), 0));
1102 : }
1103 :
1104 : /************************************************************************/
1105 : /* ParseDtypeV3() */
1106 : /************************************************************************/
1107 :
1108 1083 : static GDALExtendedDataType ParseDtypeV3(const CPLJSONObject &obj,
1109 : std::vector<DtypeElt> &elts)
1110 : {
1111 : do
1112 : {
1113 1083 : if (obj.GetType() == CPLJSONObject::Type::String)
1114 : {
1115 2166 : const auto str = obj.ToString();
1116 1083 : DtypeElt elt;
1117 1083 : GDALDataType eDT = GDT_Unknown;
1118 :
1119 1083 : if (str == "bool") // boolean
1120 : {
1121 0 : elt.nativeType = DtypeElt::NativeType::BOOLEAN;
1122 0 : eDT = GDT_UInt8;
1123 : }
1124 1083 : else if (str == "int8")
1125 : {
1126 6 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1127 6 : eDT = GDT_Int8;
1128 : }
1129 1077 : else if (str == "uint8")
1130 : {
1131 104 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1132 104 : eDT = GDT_UInt8;
1133 : }
1134 973 : else if (str == "int16")
1135 : {
1136 11 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1137 11 : eDT = GDT_Int16;
1138 : }
1139 962 : else if (str == "uint16")
1140 : {
1141 9 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1142 9 : eDT = GDT_UInt16;
1143 : }
1144 953 : else if (str == "int32")
1145 : {
1146 7 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1147 7 : eDT = GDT_Int32;
1148 : }
1149 946 : else if (str == "uint32")
1150 : {
1151 7 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1152 7 : eDT = GDT_UInt32;
1153 : }
1154 939 : else if (str == "int64")
1155 : {
1156 146 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1157 146 : eDT = GDT_Int64;
1158 : }
1159 793 : else if (str == "uint64")
1160 : {
1161 6 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1162 6 : eDT = GDT_UInt64;
1163 : }
1164 787 : else if (str == "float16")
1165 : {
1166 3 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
1167 3 : eDT = GDT_Float16;
1168 : }
1169 784 : else if (str == "float32")
1170 : {
1171 734 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
1172 734 : eDT = GDT_Float32;
1173 : }
1174 50 : else if (str == "float64")
1175 : {
1176 19 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
1177 19 : eDT = GDT_Float64;
1178 : }
1179 31 : else if (str == "complex64")
1180 : {
1181 15 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
1182 15 : eDT = GDT_CFloat32;
1183 : }
1184 16 : else if (str == "complex128")
1185 : {
1186 15 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
1187 15 : eDT = GDT_CFloat64;
1188 : }
1189 : else
1190 1 : break;
1191 :
1192 1082 : elt.gdalType = GDALExtendedDataType::Create(eDT);
1193 1082 : elt.gdalSize = elt.gdalType.GetSize();
1194 1082 : if (!elt.gdalTypeIsApproxOfNative)
1195 1082 : elt.nativeSize = elt.gdalSize;
1196 :
1197 1082 : if (elt.nativeSize > 1)
1198 : {
1199 972 : elt.needByteSwapping = (CPL_IS_LSB == 0);
1200 : }
1201 :
1202 1082 : elts.emplace_back(elt);
1203 1082 : return GDALExtendedDataType::Create(eDT);
1204 : }
1205 : } while (false);
1206 1 : CPLError(CE_Failure, CPLE_AppDefined,
1207 : "Invalid or unsupported format for data_type: %s",
1208 2 : obj.ToString().c_str());
1209 1 : return GDALExtendedDataType::Create(GDT_Unknown);
1210 : }
1211 :
1212 : /************************************************************************/
1213 : /* ParseNoDataStringAsDouble() */
1214 : /************************************************************************/
1215 :
1216 255 : static double ParseNoDataStringAsDouble(const std::string &osVal, bool &bOK)
1217 : {
1218 255 : double dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
1219 255 : if (osVal == "NaN")
1220 : {
1221 : // initialized above
1222 : }
1223 15 : else if (osVal == "Infinity" || osVal == "+Infinity")
1224 : {
1225 5 : dfNoDataValue = std::numeric_limits<double>::infinity();
1226 : }
1227 10 : else if (osVal == "-Infinity")
1228 : {
1229 5 : dfNoDataValue = -std::numeric_limits<double>::infinity();
1230 : }
1231 : else
1232 : {
1233 5 : bOK = false;
1234 : }
1235 255 : return dfNoDataValue;
1236 : }
1237 :
1238 : /************************************************************************/
1239 : /* ParseNoDataComponent() */
1240 : /************************************************************************/
1241 :
1242 : template <typename T, typename Tint>
1243 40 : static T ParseNoDataComponent(const CPLJSONObject &oObj, bool &bOK)
1244 : {
1245 40 : if (oObj.GetType() == CPLJSONObject::Type::Integer ||
1246 62 : oObj.GetType() == CPLJSONObject::Type::Long ||
1247 22 : oObj.GetType() == CPLJSONObject::Type::Double)
1248 : {
1249 22 : return static_cast<T>(oObj.ToDouble());
1250 : }
1251 18 : else if (oObj.GetType() == CPLJSONObject::Type::String)
1252 : {
1253 54 : const auto osVal = oObj.ToString();
1254 18 : if (STARTS_WITH(osVal.c_str(), "0x"))
1255 : {
1256 2 : if (osVal.size() > 2 + 2 * sizeof(T))
1257 : {
1258 0 : bOK = false;
1259 0 : return 0;
1260 : }
1261 2 : Tint nVal = static_cast<Tint>(
1262 2 : std::strtoull(osVal.c_str() + 2, nullptr, 16));
1263 : T fVal;
1264 : static_assert(sizeof(nVal) == sizeof(fVal),
1265 : "sizeof(nVal) == sizeof(dfVal)");
1266 2 : memcpy(&fVal, &nVal, sizeof(nVal));
1267 2 : return fVal;
1268 : }
1269 : else
1270 : {
1271 16 : return static_cast<T>(ParseNoDataStringAsDouble(osVal, bOK));
1272 : }
1273 : }
1274 : else
1275 : {
1276 0 : bOK = false;
1277 0 : return 0;
1278 : }
1279 : }
1280 :
1281 : /************************************************************************/
1282 : /* ZarrV3Group::LoadArray() */
1283 : /************************************************************************/
1284 :
1285 : std::shared_ptr<ZarrArray>
1286 1096 : ZarrV3Group::LoadArray(const std::string &osArrayName,
1287 : const std::string &osZarrayFilename,
1288 : const CPLJSONObject &oRoot) const
1289 : {
1290 : // Add osZarrayFilename to m_poSharedResource during the scope
1291 : // of this function call.
1292 1096 : ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
1293 2192 : osZarrayFilename);
1294 1096 : if (!filenameAdder.ok())
1295 0 : return nullptr;
1296 :
1297 : // Warn about unknown members (the spec suggests to error out, but let be
1298 : // a bit more lenient)
1299 12035 : for (const auto &oNode : oRoot.GetChildren())
1300 : {
1301 21878 : const auto osName = oNode.GetName();
1302 29529 : if (osName != "zarr_format" && osName != "node_type" &&
1303 22956 : osName != "shape" && osName != "chunk_grid" &&
1304 16386 : osName != "data_type" && osName != "chunk_key_encoding" &&
1305 7638 : osName != "fill_value" &&
1306 : // Below are optional
1307 8090 : osName != "dimension_names" && osName != "codecs" &&
1308 22674 : osName != "storage_transformers" && osName != "attributes")
1309 : {
1310 4 : CPLError(CE_Warning, CPLE_AppDefined,
1311 : "%s array definition contains a unknown member (%s). "
1312 : "Interpretation of the array might be wrong.",
1313 : osZarrayFilename.c_str(), osName.c_str());
1314 : }
1315 : }
1316 :
1317 3288 : const auto oStorageTransformers = oRoot["storage_transformers"].ToArray();
1318 1096 : if (oStorageTransformers.Size() > 0)
1319 : {
1320 1 : CPLError(CE_Failure, CPLE_AppDefined,
1321 : "storage_transformers are not supported.");
1322 1 : return nullptr;
1323 : }
1324 :
1325 3285 : const auto oShape = oRoot["shape"].ToArray();
1326 1095 : if (!oShape.IsValid())
1327 : {
1328 2 : CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
1329 2 : return nullptr;
1330 : }
1331 :
1332 : // Parse chunk_grid
1333 3279 : const auto oChunkGrid = oRoot["chunk_grid"];
1334 1093 : if (oChunkGrid.GetType() != CPLJSONObject::Type::Object)
1335 : {
1336 1 : CPLError(CE_Failure, CPLE_AppDefined,
1337 : "chunk_grid missing or not an object");
1338 1 : return nullptr;
1339 : }
1340 :
1341 3276 : const auto oChunkGridName = oChunkGrid["name"];
1342 1092 : if (oChunkGridName.ToString() != "regular")
1343 : {
1344 1 : CPLError(CE_Failure, CPLE_AppDefined,
1345 : "Only chunk_grid.name = regular supported");
1346 1 : return nullptr;
1347 : }
1348 :
1349 3273 : const auto oChunks = oChunkGrid["configuration"]["chunk_shape"].ToArray();
1350 1091 : if (!oChunks.IsValid())
1351 : {
1352 1 : CPLError(
1353 : CE_Failure, CPLE_AppDefined,
1354 : "chunk_grid.configuration.chunk_shape missing or not an array");
1355 1 : return nullptr;
1356 : }
1357 :
1358 1090 : if (oShape.Size() != oChunks.Size())
1359 : {
1360 1 : CPLError(CE_Failure, CPLE_AppDefined,
1361 : "shape and chunks arrays are of different size");
1362 1 : return nullptr;
1363 : }
1364 :
1365 : // Parse chunk_key_encoding
1366 3267 : const auto oChunkKeyEncoding = oRoot["chunk_key_encoding"];
1367 1089 : if (oChunkKeyEncoding.GetType() != CPLJSONObject::Type::Object)
1368 : {
1369 1 : CPLError(CE_Failure, CPLE_AppDefined,
1370 : "chunk_key_encoding missing or not an object");
1371 1 : return nullptr;
1372 : }
1373 :
1374 2176 : std::string osDimSeparator;
1375 1088 : bool bV2ChunkKeyEncoding = false;
1376 3264 : const auto oChunkKeyEncodingName = oChunkKeyEncoding["name"];
1377 1088 : if (oChunkKeyEncodingName.ToString() == "default")
1378 : {
1379 1075 : osDimSeparator = "/";
1380 : }
1381 13 : else if (oChunkKeyEncodingName.ToString() == "v2")
1382 : {
1383 12 : osDimSeparator = ".";
1384 12 : bV2ChunkKeyEncoding = true;
1385 : }
1386 : else
1387 : {
1388 1 : CPLError(CE_Failure, CPLE_AppDefined,
1389 : "Unsupported chunk_key_encoding.name");
1390 1 : return nullptr;
1391 : }
1392 :
1393 : {
1394 2174 : auto oConfiguration = oChunkKeyEncoding["configuration"];
1395 1087 : if (oConfiguration.GetType() == CPLJSONObject::Type::Object)
1396 : {
1397 2064 : auto oSeparator = oConfiguration["separator"];
1398 1032 : if (oSeparator.IsValid())
1399 : {
1400 1032 : osDimSeparator = oSeparator.ToString();
1401 1032 : if (osDimSeparator != "/" && osDimSeparator != ".")
1402 : {
1403 1 : CPLError(CE_Failure, CPLE_AppDefined,
1404 : "Separator can only be '/' or '.'");
1405 1 : return nullptr;
1406 : }
1407 : }
1408 : }
1409 : }
1410 :
1411 3258 : CPLJSONObject oAttributes = oRoot["attributes"];
1412 :
1413 : // Deep-clone of oAttributes
1414 1086 : if (oAttributes.IsValid())
1415 : {
1416 1018 : oAttributes = oAttributes.Clone();
1417 : }
1418 :
1419 2172 : std::vector<std::shared_ptr<GDALDimension>> aoDims;
1420 3078 : for (int i = 0; i < oShape.Size(); ++i)
1421 : {
1422 1992 : const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
1423 1992 : if (nSize == 0)
1424 : {
1425 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
1426 0 : return nullptr;
1427 : }
1428 1992 : aoDims.emplace_back(std::make_shared<ZarrDimension>(
1429 1992 : m_poSharedResource,
1430 3984 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1431 3984 : std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
1432 1992 : nSize));
1433 : }
1434 :
1435 : // Deal with dimension_names
1436 3258 : const auto dimensionNames = oRoot["dimension_names"];
1437 :
1438 599 : const auto FindDimension = [this, &aoDims, &osArrayName, &oAttributes](
1439 : const std::string &osDimName,
1440 6241 : std::shared_ptr<GDALDimension> &poDim, int i)
1441 : {
1442 599 : auto oIter = m_oMapDimensions.find(osDimName);
1443 599 : if (oIter != m_oMapDimensions.end())
1444 : {
1445 296 : if (m_bDimSizeInUpdate ||
1446 148 : oIter->second->GetSize() == poDim->GetSize())
1447 : {
1448 148 : poDim = oIter->second;
1449 148 : return true;
1450 : }
1451 : else
1452 : {
1453 0 : CPLError(CE_Warning, CPLE_AppDefined,
1454 : "Size of _ARRAY_DIMENSIONS[%d] different "
1455 : "from the one of shape",
1456 : i);
1457 0 : return false;
1458 : }
1459 : }
1460 :
1461 : // Try to load the indexing variable.
1462 : // Not in m_oMapMDArrays,
1463 : // then stat() the indexing variable.
1464 826 : else if (osArrayName != osDimName &&
1465 826 : m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
1466 : {
1467 750 : std::string osDirName = m_osDirectoryName;
1468 : while (true)
1469 : {
1470 : const std::string osArrayFilenameDim = CPLFormFilenameSafe(
1471 1623 : CPLFormFilenameSafe(osDirName.c_str(), osDimName.c_str(),
1472 : nullptr)
1473 : .c_str(),
1474 1623 : "zarr.json", nullptr);
1475 : VSIStatBufL sStat;
1476 1623 : if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
1477 : {
1478 4 : CPLJSONDocument oDoc;
1479 2 : if (oDoc.Load(osArrayFilenameDim))
1480 : {
1481 2 : LoadArray(osDimName, osArrayFilenameDim,
1482 4 : oDoc.GetRoot());
1483 : }
1484 : }
1485 : else
1486 : {
1487 : // Recurse to upper level for datasets such as
1488 : // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
1489 : std::string osDirNameNew =
1490 1621 : CPLGetPathSafe(osDirName.c_str());
1491 1621 : if (!osDirNameNew.empty() && osDirNameNew != osDirName)
1492 : {
1493 1248 : osDirName = std::move(osDirNameNew);
1494 1248 : continue;
1495 : }
1496 : }
1497 375 : break;
1498 1248 : }
1499 : }
1500 :
1501 451 : oIter = m_oMapDimensions.find(osDimName);
1502 : // cppcheck-suppress knownConditionTrueFalse
1503 453 : if (oIter != m_oMapDimensions.end() &&
1504 2 : oIter->second->GetSize() == poDim->GetSize())
1505 : {
1506 2 : poDim = oIter->second;
1507 2 : return true;
1508 : }
1509 :
1510 898 : std::string osType;
1511 898 : std::string osDirection;
1512 449 : if (aoDims.size() == 1 && osArrayName == osDimName)
1513 : {
1514 76 : ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
1515 : osDirection);
1516 : }
1517 :
1518 : auto poDimLocal = std::make_shared<ZarrDimension>(
1519 449 : m_poSharedResource,
1520 898 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1521 898 : GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
1522 449 : poDimLocal->SetXArrayDimension();
1523 449 : m_oMapDimensions[osDimName] = poDimLocal;
1524 449 : poDim = poDimLocal;
1525 449 : return true;
1526 1086 : };
1527 :
1528 1086 : if (dimensionNames.GetType() == CPLJSONObject::Type::Array)
1529 : {
1530 343 : const auto arrayDims = dimensionNames.ToArray();
1531 343 : if (arrayDims.Size() == oShape.Size())
1532 : {
1533 941 : for (int i = 0; i < oShape.Size(); ++i)
1534 : {
1535 599 : if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
1536 : {
1537 1797 : const auto osDimName = arrayDims[i].ToString();
1538 599 : FindDimension(osDimName, aoDims[i], i);
1539 : }
1540 : }
1541 : }
1542 : else
1543 : {
1544 1 : CPLError(
1545 : CE_Failure, CPLE_AppDefined,
1546 : "Size of dimension_names[] different from the one of shape");
1547 1 : return nullptr;
1548 : }
1549 : }
1550 743 : else if (dimensionNames.IsValid())
1551 : {
1552 1 : CPLError(CE_Failure, CPLE_AppDefined,
1553 : "dimension_names should be an array");
1554 1 : return nullptr;
1555 : }
1556 :
1557 3252 : auto oDtype = oRoot["data_type"];
1558 1084 : if (!oDtype.IsValid())
1559 : {
1560 1 : CPLError(CE_Failure, CPLE_NotSupported, "data_type missing");
1561 1 : return nullptr;
1562 : }
1563 1083 : if (oDtype["fallback"].IsValid())
1564 1 : oDtype = oDtype["fallback"];
1565 2166 : std::vector<DtypeElt> aoDtypeElts;
1566 2166 : const auto oType = ParseDtypeV3(oDtype, aoDtypeElts);
1567 2166 : if (oType.GetClass() == GEDTC_NUMERIC &&
1568 1083 : oType.GetNumericDataType() == GDT_Unknown)
1569 1 : return nullptr;
1570 :
1571 2164 : std::vector<GUInt64> anOuterBlockSize;
1572 1082 : if (!ZarrArray::ParseChunkSize(oChunks, oType, anOuterBlockSize))
1573 1 : return nullptr;
1574 :
1575 2162 : std::vector<GByte> abyNoData;
1576 :
1577 3243 : auto oFillValue = oRoot["fill_value"];
1578 1081 : auto eFillValueType = oFillValue.GetType();
1579 :
1580 1081 : if (!oFillValue.IsValid())
1581 : {
1582 0 : CPLError(CE_Warning, CPLE_AppDefined, "Missing fill_value is invalid");
1583 : }
1584 1081 : else if (eFillValueType == CPLJSONObject::Type::Null)
1585 : {
1586 113 : CPLError(CE_Warning, CPLE_AppDefined, "fill_value = null is invalid");
1587 : }
1588 968 : else if (GDALDataTypeIsComplex(oType.GetNumericDataType()) &&
1589 : eFillValueType != CPLJSONObject::Type::Array)
1590 : {
1591 4 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1592 4 : return nullptr;
1593 : }
1594 964 : else if (eFillValueType == CPLJSONObject::Type::String)
1595 : {
1596 490 : const auto osFillValue = oFillValue.ToString();
1597 245 : if (STARTS_WITH(osFillValue.c_str(), "0x"))
1598 : {
1599 3 : if (osFillValue.size() > 2 + 2 * oType.GetSize())
1600 : {
1601 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1602 1 : return nullptr;
1603 : }
1604 : uint64_t nVal = static_cast<uint64_t>(
1605 3 : std::strtoull(osFillValue.c_str() + 2, nullptr, 16));
1606 3 : if (oType.GetSize() == 4)
1607 : {
1608 1 : abyNoData.resize(oType.GetSize());
1609 1 : uint32_t nTmp = static_cast<uint32_t>(nVal);
1610 1 : memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
1611 : }
1612 2 : else if (oType.GetSize() == 8)
1613 : {
1614 1 : abyNoData.resize(oType.GetSize());
1615 1 : memcpy(&abyNoData[0], &nVal, sizeof(nVal));
1616 : }
1617 : else
1618 : {
1619 1 : CPLError(CE_Failure, CPLE_AppDefined,
1620 : "Hexadecimal representation of fill_value no "
1621 : "supported for this data type");
1622 1 : return nullptr;
1623 : }
1624 : }
1625 242 : else if (STARTS_WITH(osFillValue.c_str(), "0b"))
1626 : {
1627 3 : if (osFillValue.size() > 2 + 8 * oType.GetSize())
1628 : {
1629 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1630 1 : return nullptr;
1631 : }
1632 : uint64_t nVal = static_cast<uint64_t>(
1633 3 : std::strtoull(osFillValue.c_str() + 2, nullptr, 2));
1634 3 : if (oType.GetSize() == 4)
1635 : {
1636 1 : abyNoData.resize(oType.GetSize());
1637 1 : uint32_t nTmp = static_cast<uint32_t>(nVal);
1638 1 : memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
1639 : }
1640 2 : else if (oType.GetSize() == 8)
1641 : {
1642 1 : abyNoData.resize(oType.GetSize());
1643 1 : memcpy(&abyNoData[0], &nVal, sizeof(nVal));
1644 : }
1645 : else
1646 : {
1647 1 : CPLError(CE_Failure, CPLE_AppDefined,
1648 : "Binary representation of fill_value no supported for "
1649 : "this data type");
1650 1 : return nullptr;
1651 : }
1652 : }
1653 : else
1654 : {
1655 239 : bool bOK = true;
1656 239 : double dfNoDataValue = ParseNoDataStringAsDouble(osFillValue, bOK);
1657 239 : if (!bOK)
1658 : {
1659 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1660 2 : return nullptr;
1661 : }
1662 238 : else if (oType.GetNumericDataType() == GDT_Float16)
1663 : {
1664 : const GFloat16 hfNoDataValue =
1665 1 : static_cast<GFloat16>(dfNoDataValue);
1666 1 : abyNoData.resize(sizeof(hfNoDataValue));
1667 1 : memcpy(&abyNoData[0], &hfNoDataValue, sizeof(hfNoDataValue));
1668 : }
1669 237 : else if (oType.GetNumericDataType() == GDT_Float32)
1670 : {
1671 220 : const float fNoDataValue = static_cast<float>(dfNoDataValue);
1672 220 : abyNoData.resize(sizeof(fNoDataValue));
1673 220 : memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
1674 : }
1675 17 : else if (oType.GetNumericDataType() == GDT_Float64)
1676 : {
1677 16 : abyNoData.resize(sizeof(dfNoDataValue));
1678 16 : memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
1679 : }
1680 : else
1681 : {
1682 1 : CPLError(CE_Failure, CPLE_AppDefined,
1683 : "Invalid fill_value for this data type");
1684 1 : return nullptr;
1685 : }
1686 : }
1687 : }
1688 719 : else if (eFillValueType == CPLJSONObject::Type::Boolean ||
1689 521 : eFillValueType == CPLJSONObject::Type::Integer ||
1690 521 : eFillValueType == CPLJSONObject::Type::Long ||
1691 : eFillValueType == CPLJSONObject::Type::Double)
1692 : {
1693 695 : const double dfNoDataValue = oFillValue.ToDouble();
1694 695 : if (oType.GetNumericDataType() == GDT_Int64)
1695 : {
1696 : const int64_t nNoDataValue =
1697 140 : static_cast<int64_t>(oFillValue.ToLong());
1698 140 : abyNoData.resize(oType.GetSize());
1699 140 : GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1700 : oType.GetNumericDataType(), 0, 1);
1701 : }
1702 555 : else if (oType.GetNumericDataType() == GDT_UInt64 &&
1703 : /* we can't really deal with nodata value between */
1704 : /* int64::max and uint64::max due to json-c limitations */
1705 0 : dfNoDataValue >= 0)
1706 : {
1707 : const int64_t nNoDataValue =
1708 0 : static_cast<int64_t>(oFillValue.ToLong());
1709 0 : abyNoData.resize(oType.GetSize());
1710 0 : GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1711 : oType.GetNumericDataType(), 0, 1);
1712 : }
1713 : else
1714 : {
1715 555 : abyNoData.resize(oType.GetSize());
1716 555 : GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
1717 : oType.GetNumericDataType(), 0, 1);
1718 695 : }
1719 : }
1720 24 : else if (eFillValueType == CPLJSONObject::Type::Array)
1721 : {
1722 24 : const auto oFillValueArray = oFillValue.ToArray();
1723 44 : if (oFillValueArray.Size() == 2 &&
1724 20 : GDALDataTypeIsComplex(oType.GetNumericDataType()))
1725 : {
1726 20 : if (oType.GetNumericDataType() == GDT_CFloat64)
1727 : {
1728 10 : bool bOK = true;
1729 : const double adfNoDataValue[2] = {
1730 10 : ParseNoDataComponent<double, uint64_t>(oFillValueArray[0],
1731 : bOK),
1732 10 : ParseNoDataComponent<double, uint64_t>(oFillValueArray[1],
1733 : bOK),
1734 20 : };
1735 10 : if (!bOK)
1736 : {
1737 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1738 2 : return nullptr;
1739 : }
1740 8 : abyNoData.resize(oType.GetSize());
1741 8 : CPLAssert(sizeof(adfNoDataValue) == oType.GetSize());
1742 8 : memcpy(abyNoData.data(), adfNoDataValue,
1743 : sizeof(adfNoDataValue));
1744 : }
1745 : else
1746 : {
1747 10 : CPLAssert(oType.GetNumericDataType() == GDT_CFloat32);
1748 10 : bool bOK = true;
1749 : const float afNoDataValue[2] = {
1750 10 : ParseNoDataComponent<float, uint32_t>(oFillValueArray[0],
1751 : bOK),
1752 10 : ParseNoDataComponent<float, uint32_t>(oFillValueArray[1],
1753 : bOK),
1754 20 : };
1755 10 : if (!bOK)
1756 : {
1757 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1758 2 : return nullptr;
1759 : }
1760 8 : abyNoData.resize(oType.GetSize());
1761 8 : CPLAssert(sizeof(afNoDataValue) == oType.GetSize());
1762 8 : memcpy(abyNoData.data(), afNoDataValue, sizeof(afNoDataValue));
1763 : }
1764 : }
1765 : else
1766 : {
1767 4 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1768 4 : return nullptr;
1769 : }
1770 : }
1771 : else
1772 : {
1773 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1774 0 : return nullptr;
1775 : }
1776 :
1777 3195 : const auto oCodecs = oRoot["codecs"].ToArray();
1778 1065 : std::unique_ptr<ZarrV3CodecSequence> poCodecs;
1779 2130 : std::vector<GUInt64> anInnerBlockSize = anOuterBlockSize;
1780 1065 : if (oCodecs.Size() > 0)
1781 : {
1782 2070 : poCodecs = ZarrV3Array::SetupCodecs(oCodecs, anOuterBlockSize,
1783 : anInnerBlockSize,
1784 2070 : aoDtypeElts.back(), abyNoData);
1785 1035 : if (!poCodecs)
1786 : {
1787 18 : return nullptr;
1788 : }
1789 : }
1790 :
1791 1047 : auto poArray = ZarrV3Array::Create(m_poSharedResource, Self(), osArrayName,
1792 : aoDims, oType, aoDtypeElts,
1793 2094 : anOuterBlockSize, anInnerBlockSize);
1794 1047 : if (!poArray)
1795 1 : return nullptr;
1796 1046 : poArray->SetUpdatable(m_bUpdatable); // must be set before SetAttributes()
1797 1046 : poArray->SetFilename(osZarrayFilename);
1798 1046 : poArray->SetIsV2ChunkKeyEncoding(bV2ChunkKeyEncoding);
1799 1046 : poArray->SetDimSeparator(osDimSeparator);
1800 1046 : if (!abyNoData.empty())
1801 : {
1802 933 : poArray->RegisterNoDataValue(abyNoData.data());
1803 : }
1804 1046 : poArray->SetAttributes(Self(), oAttributes);
1805 1046 : poArray->SetDtype(oDtype);
1806 2063 : if (oCodecs.Size() > 0 &&
1807 2063 : oCodecs[oCodecs.Size() - 1].GetString("name") != "bytes")
1808 : {
1809 1332 : poArray->SetStructuralInfo(
1810 1332 : "COMPRESSOR", oCodecs[oCodecs.Size() - 1].ToString().c_str());
1811 : }
1812 1046 : if (poCodecs)
1813 1017 : poArray->SetCodecs(oCodecs, std::move(poCodecs));
1814 1046 : RegisterArray(poArray);
1815 :
1816 : // If this is an indexing variable, attach it to the dimension.
1817 1046 : if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
1818 : {
1819 76 : auto oIter = m_oMapDimensions.find(poArray->GetName());
1820 76 : if (oIter != m_oMapDimensions.end())
1821 : {
1822 76 : oIter->second->SetIndexingVariable(poArray);
1823 : }
1824 : }
1825 :
1826 1046 : if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
1827 : "CACHE_TILE_PRESENCE", "NO")))
1828 : {
1829 3 : poArray->BlockCachePresence();
1830 : }
1831 :
1832 1046 : return poArray;
1833 : }
1834 :
1835 : /************************************************************************/
1836 : /* ZarrV3Array::GetRawBlockInfoInfo() */
1837 : /************************************************************************/
1838 :
1839 6 : CPLStringList ZarrV3Array::GetRawBlockInfoInfo() const
1840 : {
1841 6 : CPLStringList aosInfo(m_aosStructuralInfo);
1842 6 : if (m_oType.GetSize() > 1)
1843 : {
1844 : // By default, assume that the ENDIANNESS is the native one.
1845 : // Otherwise there will be a ZarrV3CodecBytes instance.
1846 : if constexpr (CPL_IS_LSB)
1847 5 : aosInfo.SetNameValue("ENDIANNESS", "LITTLE");
1848 : else
1849 : aosInfo.SetNameValue("ENDIANNESS", "BIG");
1850 : }
1851 :
1852 6 : if (m_poCodecs)
1853 : {
1854 6 : bool bHasOtherCodec = false;
1855 11 : for (const auto &poCodec : m_poCodecs->GetCodecs())
1856 : {
1857 6 : if (poCodec->GetName() == ZarrV3CodecBytes::NAME &&
1858 1 : m_oType.GetSize() > 1)
1859 : {
1860 : auto poBytesCodec =
1861 1 : dynamic_cast<const ZarrV3CodecBytes *>(poCodec.get());
1862 1 : if (poBytesCodec)
1863 : {
1864 1 : if (poBytesCodec->IsLittle())
1865 0 : aosInfo.SetNameValue("ENDIANNESS", "LITTLE");
1866 : else
1867 1 : aosInfo.SetNameValue("ENDIANNESS", "BIG");
1868 : }
1869 : }
1870 5 : else if (poCodec->GetName() == ZarrV3CodecTranspose::NAME &&
1871 1 : m_aoDims.size() > 1)
1872 : {
1873 : auto poTransposeCodec =
1874 1 : dynamic_cast<const ZarrV3CodecTranspose *>(poCodec.get());
1875 1 : if (poTransposeCodec && !poTransposeCodec->IsNoOp())
1876 : {
1877 1 : const auto &anOrder = poTransposeCodec->GetOrder();
1878 1 : const int nDims = static_cast<int>(anOrder.size());
1879 2 : std::string osOrder("[");
1880 3 : for (int i = 0; i < nDims; ++i)
1881 : {
1882 2 : if (i > 0)
1883 1 : osOrder += ',';
1884 2 : osOrder += std::to_string(anOrder[i]);
1885 : }
1886 1 : osOrder += ']';
1887 1 : aosInfo.SetNameValue("TRANSPOSE_ORDER", osOrder.c_str());
1888 : }
1889 : }
1890 5 : else if (poCodec->GetName() != ZarrV3CodecGZip::NAME &&
1891 5 : poCodec->GetName() != ZarrV3CodecBlosc::NAME &&
1892 2 : poCodec->GetName() != ZarrV3CodecZstd::NAME)
1893 : {
1894 2 : bHasOtherCodec = true;
1895 : }
1896 : }
1897 6 : if (bHasOtherCodec)
1898 : {
1899 2 : aosInfo.SetNameValue("CODECS", m_oJSONCodecs.ToString().c_str());
1900 : }
1901 :
1902 6 : if (m_poCodecs->SupportsPartialDecoding())
1903 : {
1904 2 : aosInfo.SetNameValue("CHUNK_TYPE", "INNER");
1905 : }
1906 : }
1907 :
1908 6 : return aosInfo;
1909 : }
1910 :
1911 : /************************************************************************/
1912 : /* ZarrV3Array::SetupCodecs() */
1913 : /************************************************************************/
1914 :
1915 1164 : /* static */ std::unique_ptr<ZarrV3CodecSequence> ZarrV3Array::SetupCodecs(
1916 : const CPLJSONArray &oCodecs, const std::vector<GUInt64> &anOuterBlockSize,
1917 : std::vector<GUInt64> &anInnerBlockSize, DtypeElt &zarrDataType,
1918 : const std::vector<GByte> &abyNoData)
1919 :
1920 : {
1921 : // Byte swapping will be done by the codec chain
1922 1164 : zarrDataType.needByteSwapping = false;
1923 :
1924 2328 : ZarrArrayMetadata oInputArrayMetadata;
1925 1164 : if (!abyNoData.empty() && zarrDataType.gdalTypeIsApproxOfNative)
1926 : {
1927 : // This cannot happen today with the data types we support, but
1928 : // might in the future. In which case we'll have to translate the
1929 : // nodata value from its GDAL representation to the native one
1930 : // (since that's what zarr_v3_codec_sharding::FillWithNoData()
1931 : // expects
1932 0 : CPLError(CE_Warning, CPLE_AppDefined,
1933 : "Zarr driver issue: gdalTypeIsApproxOfNative is not taken "
1934 : "into account by codecs. Nodata will be assumed to be zero by "
1935 : "sharding codec");
1936 : }
1937 : else
1938 : {
1939 1164 : oInputArrayMetadata.abyNoData = abyNoData;
1940 : }
1941 3313 : for (auto &nSize : anOuterBlockSize)
1942 : {
1943 2149 : oInputArrayMetadata.anBlockSizes.push_back(static_cast<size_t>(nSize));
1944 : }
1945 1164 : oInputArrayMetadata.oElt = zarrDataType;
1946 2328 : auto poCodecs = std::make_unique<ZarrV3CodecSequence>(oInputArrayMetadata);
1947 2328 : ZarrArrayMetadata oOutputArrayMetadata;
1948 1164 : if (!poCodecs->InitFromJson(oCodecs, oOutputArrayMetadata))
1949 : {
1950 18 : return nullptr;
1951 : }
1952 2292 : std::vector<size_t> anOuterBlockSizeSizet;
1953 3259 : for (auto nVal : oOutputArrayMetadata.anBlockSizes)
1954 : {
1955 2113 : anOuterBlockSizeSizet.push_back(static_cast<size_t>(nVal));
1956 : }
1957 1146 : anInnerBlockSize.clear();
1958 3259 : for (size_t nVal : poCodecs->GetInnerMostBlockSize(anOuterBlockSizeSizet))
1959 : {
1960 2113 : anInnerBlockSize.push_back(nVal);
1961 : }
1962 1146 : return poCodecs;
1963 : }
1964 :
1965 : /************************************************************************/
1966 : /* ZarrV3Array::SetCodecs() */
1967 : /************************************************************************/
1968 :
1969 1146 : void ZarrV3Array::SetCodecs(const CPLJSONArray &oJSONCodecs,
1970 : std::unique_ptr<ZarrV3CodecSequence> &&poCodecs)
1971 : {
1972 1146 : m_oJSONCodecs = oJSONCodecs;
1973 1146 : m_poCodecs = std::move(poCodecs);
1974 1146 : }
1975 :
1976 : /************************************************************************/
1977 : /* ZarrV3Array::LoadOverviews() */
1978 : /************************************************************************/
1979 :
1980 78 : void ZarrV3Array::LoadOverviews() const
1981 : {
1982 78 : if (m_bOverviewsLoaded)
1983 36 : return;
1984 44 : m_bOverviewsLoaded = true;
1985 :
1986 : // Cf https://github.com/zarr-conventions/multiscales
1987 : // and https://github.com/zarr-conventions/spatial
1988 :
1989 44 : const auto poRG = GetRootGroup();
1990 44 : if (!poRG)
1991 : {
1992 1 : CPLError(CE_Warning, CPLE_AppDefined,
1993 : "LoadOverviews(): cannot access root group");
1994 1 : return;
1995 : }
1996 :
1997 43 : auto poGroup = GetParentGroup();
1998 43 : if (!poGroup)
1999 : {
2000 0 : CPLDebugOnly(ZARR_DEBUG_KEY,
2001 : "LoadOverviews(): cannot access parent group");
2002 0 : return;
2003 : }
2004 :
2005 : // Look for "zarr_conventions" and "multiscales" attributes in our
2006 : // immediate parent, or in our grandparent if not found.
2007 86 : auto poAttrZarrConventions = poGroup->GetAttribute("zarr_conventions");
2008 86 : auto poAttrMultiscales = poGroup->GetAttribute("multiscales");
2009 43 : if (!poAttrZarrConventions || !poAttrMultiscales)
2010 : {
2011 43 : poGroup = poGroup->GetParentGroup();
2012 43 : if (poGroup)
2013 : {
2014 43 : poAttrZarrConventions = poGroup->GetAttribute("zarr_conventions");
2015 43 : poAttrMultiscales = poGroup->GetAttribute("multiscales");
2016 : }
2017 43 : if (!poAttrZarrConventions || !poAttrMultiscales)
2018 : {
2019 0 : return;
2020 : }
2021 : }
2022 :
2023 43 : const char *pszZarrConventions = poAttrZarrConventions->ReadAsString();
2024 43 : const char *pszMultiscales = poAttrMultiscales->ReadAsString();
2025 43 : if (!pszZarrConventions || !pszMultiscales)
2026 0 : return;
2027 :
2028 43 : CPLJSONDocument oDoc;
2029 43 : if (!oDoc.LoadMemory(pszZarrConventions))
2030 0 : return;
2031 43 : const auto oZarrConventions = oDoc.GetRoot();
2032 :
2033 43 : if (!oDoc.LoadMemory(pszMultiscales))
2034 0 : return;
2035 43 : const auto oMultiscales = oDoc.GetRoot();
2036 :
2037 43 : if (!oZarrConventions.IsValid() ||
2038 43 : oZarrConventions.GetType() != CPLJSONObject::Type::Array ||
2039 129 : !oMultiscales.IsValid() ||
2040 43 : oMultiscales.GetType() != CPLJSONObject::Type::Object)
2041 : {
2042 0 : return;
2043 : }
2044 :
2045 43 : const auto oZarrConventionsArray = oZarrConventions.ToArray();
2046 43 : const auto hasMultiscalesUUIDLambda = [](const CPLJSONObject &obj)
2047 : {
2048 43 : constexpr const char *MULTISCALES_UUID =
2049 : "d35379db-88df-4056-af3a-620245f8e347";
2050 43 : return obj.GetString("uuid") == MULTISCALES_UUID;
2051 : };
2052 : const bool bFoundMultiScalesUUID =
2053 86 : std::find_if(oZarrConventionsArray.begin(), oZarrConventionsArray.end(),
2054 129 : hasMultiscalesUUIDLambda) != oZarrConventionsArray.end();
2055 43 : if (!bFoundMultiScalesUUID)
2056 0 : return;
2057 :
2058 57 : const auto hasSpatialUUIDLambda = [](const CPLJSONObject &obj)
2059 : {
2060 57 : constexpr const char *SPATIAL_UUID =
2061 : "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4";
2062 57 : return obj.GetString("uuid") == SPATIAL_UUID;
2063 : };
2064 : const bool bFoundSpatialUUID =
2065 86 : std::find_if(oZarrConventionsArray.begin(), oZarrConventionsArray.end(),
2066 129 : hasSpatialUUIDLambda) != oZarrConventionsArray.end();
2067 :
2068 86 : const auto oLayout = oMultiscales["layout"];
2069 43 : if (!oLayout.IsValid() && oLayout.GetType() != CPLJSONObject::Type::Array)
2070 : {
2071 1 : CPLError(CE_Warning, CPLE_AppDefined,
2072 : "layout not found in multiscales");
2073 1 : return;
2074 : }
2075 :
2076 : // is pixel-is-area ?
2077 126 : auto poSpatialRegistration = poGroup->GetAttribute("spatial:registration");
2078 : const char *pszSpatialRegistration =
2079 42 : poSpatialRegistration ? poSpatialRegistration->ReadAsString() : nullptr;
2080 42 : const bool bHasExplicitPixelSpatialRegistration =
2081 56 : bFoundSpatialUUID && pszSpatialRegistration &&
2082 14 : strcmp(pszSpatialRegistration, "pixel") == 0;
2083 :
2084 84 : std::vector<std::string> aosSpatialDimensions;
2085 84 : std::set<std::string> oSetSpatialDimensionNames;
2086 126 : auto poSpatialDimensions = poGroup->GetAttribute("spatial:dimensions");
2087 42 : if (bFoundSpatialUUID && poSpatialDimensions)
2088 : {
2089 14 : aosSpatialDimensions = poSpatialDimensions->ReadAsStringArray();
2090 42 : for (const auto &osDimName : aosSpatialDimensions)
2091 : {
2092 28 : oSetSpatialDimensionNames.insert(osDimName);
2093 : }
2094 : }
2095 :
2096 : // Multiscales convention: asset/derived_from paths are relative to
2097 : // the group holding the convention metadata, not the store root.
2098 42 : const std::string osGroupPrefix = poGroup->GetFullName().back() == '/'
2099 40 : ? poGroup->GetFullName()
2100 124 : : poGroup->GetFullName() + '/';
2101 : const auto resolveAssetPath =
2102 170 : [&osGroupPrefix](const std::string &osRelative) -> std::string
2103 170 : { return osGroupPrefix + osRelative; };
2104 :
2105 164 : for (const auto &oLayoutItem : oLayout.ToArray())
2106 : {
2107 244 : const std::string osAsset = oLayoutItem.GetString("asset");
2108 122 : if (osAsset.empty())
2109 : {
2110 1 : CPLError(CE_Warning, CPLE_AppDefined,
2111 : "multiscales.layout[].asset not found");
2112 1 : continue;
2113 : }
2114 :
2115 : // Resolve "asset" to a MDArray
2116 0 : std::shared_ptr<GDALGroup> poAssetGroup;
2117 : {
2118 121 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
2119 : poAssetGroup =
2120 121 : poRG->OpenGroupFromFullname(resolveAssetPath(osAsset));
2121 : }
2122 0 : std::shared_ptr<GDALMDArray> poAssetArray;
2123 121 : if (poAssetGroup)
2124 : {
2125 99 : poAssetArray = poAssetGroup->OpenMDArray(GetName());
2126 : }
2127 22 : else if (osAsset.find('/') == std::string::npos)
2128 : {
2129 1 : poAssetArray = poGroup->OpenMDArray(osAsset);
2130 1 : if (!poAssetArray)
2131 : {
2132 1 : CPLError(CE_Warning, CPLE_AppDefined,
2133 : "multiscales.layout[].asset=%s ignored, because it is "
2134 : "not a valid group or array name",
2135 : osAsset.c_str());
2136 1 : continue;
2137 : }
2138 : }
2139 : else
2140 : {
2141 : poAssetArray =
2142 21 : poRG->OpenMDArrayFromFullname(resolveAssetPath(osAsset));
2143 21 : if (poAssetArray && poAssetArray->GetName() != GetName())
2144 : {
2145 6 : continue;
2146 : }
2147 : }
2148 114 : if (!poAssetArray)
2149 : {
2150 10 : continue;
2151 : }
2152 104 : if (poAssetArray->GetDimensionCount() != GetDimensionCount())
2153 : {
2154 3 : CPLError(
2155 : CE_Warning, CPLE_AppDefined,
2156 : "multiscales.layout[].asset=%s (%s) ignored, because it has "
2157 : "not the same dimension count as %s (%s)",
2158 1 : osAsset.c_str(), poAssetArray->GetFullName().c_str(),
2159 2 : GetName().c_str(), GetFullName().c_str());
2160 1 : continue;
2161 : }
2162 103 : if (poAssetArray->GetDataType() != GetDataType())
2163 : {
2164 3 : CPLError(
2165 : CE_Warning, CPLE_AppDefined,
2166 : "multiscales.layout[].asset=%s (%s) ignored, because it has "
2167 : "not the same data type as %s (%s)",
2168 1 : osAsset.c_str(), poAssetArray->GetFullName().c_str(),
2169 2 : GetName().c_str(), GetFullName().c_str());
2170 1 : continue;
2171 : }
2172 :
2173 102 : bool bAssetIsDownsampledOfThis = false;
2174 199 : for (size_t iDim = 0; iDim < GetDimensionCount(); ++iDim)
2175 : {
2176 296 : if (poAssetArray->GetDimensions()[iDim]->GetSize() <
2177 148 : GetDimensions()[iDim]->GetSize())
2178 : {
2179 51 : bAssetIsDownsampledOfThis = true;
2180 51 : break;
2181 : }
2182 : }
2183 102 : if (!bAssetIsDownsampledOfThis)
2184 : {
2185 : // not an error
2186 51 : continue;
2187 : }
2188 :
2189 : // Inspect dimensions of the asset
2190 51 : std::map<std::string, size_t> oMapAssetDimNameToIdx;
2191 51 : const auto &apoAssetDims = poAssetArray->GetDimensions();
2192 51 : size_t nCountSpatialDimsFoundInAsset = 0;
2193 150 : for (const auto &[idx, poDim] : cpl::enumerate(apoAssetDims))
2194 : {
2195 99 : oMapAssetDimNameToIdx[poDim->GetName()] = idx;
2196 99 : if (cpl::contains(oSetSpatialDimensionNames, poDim->GetName()))
2197 43 : ++nCountSpatialDimsFoundInAsset;
2198 : }
2199 : const bool bAssetHasAllSpatialDims =
2200 51 : (nCountSpatialDimsFoundInAsset == aosSpatialDimensions.size());
2201 :
2202 : // Consistency checks on "derived_from" and "transform"
2203 102 : const auto oDerivedFrom = oLayoutItem["derived_from"];
2204 102 : const auto oTransform = oLayoutItem["transform"];
2205 75 : if (oDerivedFrom.IsValid() && oTransform.IsValid() &&
2206 99 : oDerivedFrom.GetType() == CPLJSONObject::Type::String &&
2207 24 : oTransform.GetType() == CPLJSONObject::Type::Object)
2208 : {
2209 48 : const std::string osDerivedFrom = oDerivedFrom.ToString();
2210 : // Resolve "derived_from" to a MDArray
2211 0 : std::shared_ptr<GDALGroup> poDerivedFromGroup;
2212 : {
2213 24 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
2214 72 : poDerivedFromGroup = poRG->OpenGroupFromFullname(
2215 72 : resolveAssetPath(osDerivedFrom));
2216 : }
2217 0 : std::shared_ptr<GDALMDArray> poDerivedFromArray;
2218 24 : if (poDerivedFromGroup)
2219 : {
2220 19 : poDerivedFromArray = poDerivedFromGroup->OpenMDArray(GetName());
2221 : }
2222 5 : else if (osDerivedFrom.find('/') == std::string::npos)
2223 : {
2224 1 : poDerivedFromArray = poGroup->OpenMDArray(osDerivedFrom);
2225 1 : if (!poDerivedFromArray)
2226 : {
2227 1 : CPLError(CE_Warning, CPLE_AppDefined,
2228 : "multiscales.layout[].asset=%s refers to "
2229 : "derived_from=%s which does not exist",
2230 : osAsset.c_str(), osDerivedFrom.c_str());
2231 1 : poDerivedFromArray.reset();
2232 : }
2233 : }
2234 : else
2235 : {
2236 12 : poDerivedFromArray = poRG->OpenMDArrayFromFullname(
2237 12 : resolveAssetPath(osDerivedFrom));
2238 : }
2239 24 : if (poDerivedFromArray && bAssetHasAllSpatialDims)
2240 : {
2241 22 : if (poDerivedFromArray->GetDimensionCount() !=
2242 22 : GetDimensionCount())
2243 : {
2244 1 : CPLError(CE_Warning, CPLE_AppDefined,
2245 : "multiscales.layout[].asset=%s refers to "
2246 : "derived_from=%s that does not have the expected "
2247 : "number of dimensions. Ignoring that asset",
2248 : osAsset.c_str(), osDerivedFrom.c_str());
2249 3 : continue;
2250 : }
2251 :
2252 42 : const auto oScale = oTransform["scale"];
2253 21 : if (oScale.GetType() == CPLJSONObject::Type::Array &&
2254 : bHasExplicitPixelSpatialRegistration)
2255 : {
2256 10 : const auto oScaleArray = oScale.ToArray();
2257 10 : if (oScaleArray.size() != GetDimensionCount())
2258 : {
2259 :
2260 1 : CPLError(CE_Warning, CPLE_AppDefined,
2261 : "multiscales.layout[].asset=%s has a "
2262 : "transform.scale array with an unexpected "
2263 : "number of values. Ignoring the asset",
2264 : osAsset.c_str());
2265 1 : continue;
2266 : }
2267 :
2268 27 : for (size_t iDim = 0; iDim < GetDimensionCount(); ++iDim)
2269 : {
2270 18 : const double dfScale = oScaleArray[iDim].ToDouble();
2271 : const double dfExpectedScale =
2272 18 : static_cast<double>(
2273 18 : poDerivedFromArray->GetDimensions()[iDim]
2274 18 : ->GetSize()) /
2275 18 : static_cast<double>(
2276 18 : poAssetArray->GetDimensions()[iDim]->GetSize());
2277 18 : constexpr double EPSILON = 1e-3;
2278 18 : if (std::fabs(dfScale - dfExpectedScale) >
2279 18 : EPSILON * dfExpectedScale)
2280 : {
2281 2 : CPLError(CE_Warning, CPLE_AppDefined,
2282 : "multiscales.layout[].asset=%s has a "
2283 : "transform.scale[%d]=%f value whereas %f "
2284 : "was expected. "
2285 : "Assuming that later value as the scale.",
2286 : osAsset.c_str(), static_cast<int>(iDim),
2287 : dfScale, dfExpectedScale);
2288 : }
2289 : }
2290 : }
2291 :
2292 40 : const auto oTranslation = oTransform["translation"];
2293 20 : if (oTranslation.GetType() == CPLJSONObject::Type::Array &&
2294 : bHasExplicitPixelSpatialRegistration)
2295 : {
2296 9 : const auto oTranslationArray = oTranslation.ToArray();
2297 9 : if (oTranslationArray.size() != GetDimensionCount())
2298 : {
2299 1 : CPLError(CE_Warning, CPLE_AppDefined,
2300 : "multiscales.layout[].asset=%s has a "
2301 : "transform.translation array with an "
2302 : "unexpected number of values. "
2303 : "Ignoring the asset",
2304 : osAsset.c_str());
2305 1 : continue;
2306 : }
2307 :
2308 24 : for (size_t iDim = 0; iDim < GetDimensionCount(); ++iDim)
2309 : {
2310 : const double dfOffset =
2311 16 : oTranslationArray[iDim].ToDouble();
2312 16 : if (dfOffset != 0)
2313 : {
2314 1 : CPLError(CE_Warning, CPLE_AppDefined,
2315 : "multiscales.layout[].asset=%s has a "
2316 : "transform.translation[%d]=%f value. "
2317 : "Ignoring that offset.",
2318 : osAsset.c_str(), static_cast<int>(iDim),
2319 : dfOffset);
2320 : }
2321 : }
2322 : }
2323 : }
2324 : }
2325 :
2326 48 : if (bFoundSpatialUUID && bAssetHasAllSpatialDims)
2327 : {
2328 38 : const auto oSpatialShape = oLayoutItem["spatial:shape"];
2329 19 : if (oSpatialShape.IsValid())
2330 : {
2331 19 : if (oSpatialShape.GetType() != CPLJSONObject::Type::Array)
2332 : {
2333 1 : CPLError(
2334 : CE_Warning, CPLE_AppDefined,
2335 : "multiscales.layout[].asset=%s ignored, because its "
2336 : "spatial:shape property is not an array",
2337 : osAsset.c_str());
2338 3 : continue;
2339 : }
2340 18 : const auto oSpatialShapeArray = oSpatialShape.ToArray();
2341 18 : if (oSpatialShapeArray.size() != aosSpatialDimensions.size())
2342 : {
2343 1 : CPLError(
2344 : CE_Warning, CPLE_AppDefined,
2345 : "multiscales.layout[].asset=%s ignored, because its "
2346 : "spatial:shape property has not the expected number "
2347 : "of values",
2348 : osAsset.c_str());
2349 1 : continue;
2350 : }
2351 :
2352 17 : bool bSkip = false;
2353 68 : for (const auto &[idx, oShapeVal] :
2354 85 : cpl::enumerate(oSpatialShapeArray))
2355 : {
2356 : const auto oIter =
2357 34 : oMapAssetDimNameToIdx.find(aosSpatialDimensions[idx]);
2358 34 : if (oIter != oMapAssetDimNameToIdx.end())
2359 : {
2360 68 : const auto poDim = apoAssetDims[oIter->second];
2361 34 : if (poDim->GetSize() !=
2362 34 : static_cast<uint64_t>(oShapeVal.ToLong()))
2363 : {
2364 1 : bSkip = true;
2365 1 : CPLError(CE_Warning, CPLE_AppDefined,
2366 : "multiscales.layout[].asset=%s ignored, "
2367 : "because its "
2368 : "spatial:shape[%d] value is %" PRIu64
2369 : " whereas %" PRIu64 " was expected.",
2370 1 : osAsset.c_str(), static_cast<int>(idx),
2371 1 : static_cast<uint64_t>(oShapeVal.ToLong()),
2372 1 : static_cast<uint64_t>(poDim->GetSize()));
2373 : }
2374 : }
2375 : }
2376 17 : if (bSkip)
2377 1 : continue;
2378 : }
2379 : }
2380 :
2381 45 : m_apoOverviews.push_back(std::move(poAssetArray));
2382 : }
2383 : }
2384 :
2385 : /************************************************************************/
2386 : /* ZarrV3Array::GetOverviewCount() */
2387 : /************************************************************************/
2388 :
2389 78 : int ZarrV3Array::GetOverviewCount() const
2390 : {
2391 78 : LoadOverviews();
2392 78 : return static_cast<int>(m_apoOverviews.size());
2393 : }
2394 :
2395 : /************************************************************************/
2396 : /* ZarrV3Array::GetOverview() */
2397 : /************************************************************************/
2398 :
2399 43 : std::shared_ptr<GDALMDArray> ZarrV3Array::GetOverview(int idx) const
2400 : {
2401 43 : if (idx < 0 || idx >= GetOverviewCount())
2402 12 : return nullptr;
2403 31 : return m_apoOverviews[idx];
2404 : }
|