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_float.h"
14 : #include "cpl_vsi_virtual.h"
15 : #include "gdal_thread_pool.h"
16 : #include "zarr.h"
17 : #include "vsikerchunk.h"
18 :
19 : #include "netcdf_cf_constants.h" // for CF_UNITS, etc
20 :
21 : #include <algorithm>
22 : #include <cassert>
23 : #include <cstdlib>
24 : #include <limits>
25 : #include <map>
26 : #include <set>
27 :
28 : /************************************************************************/
29 : /* ZarrV2Array::ZarrV2Array() */
30 : /************************************************************************/
31 :
32 997 : ZarrV2Array::ZarrV2Array(
33 : const std::shared_ptr<ZarrSharedResource> &poSharedResource,
34 : const std::shared_ptr<ZarrGroupBase> &poParent, const std::string &osName,
35 : const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
36 : const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
37 997 : const std::vector<GUInt64> &anBlockSize, bool bFortranOrder)
38 997 : : GDALAbstractMDArray(poParent->GetFullName(), osName),
39 : ZarrArray(poSharedResource, poParent, osName, aoDims, oType, aoDtypeElts,
40 : anBlockSize, anBlockSize),
41 997 : m_bFortranOrder(bFortranOrder)
42 : {
43 997 : m_oCompressorJSon.Deinit();
44 997 : }
45 :
46 : /************************************************************************/
47 : /* ZarrV2Array::Create() */
48 : /************************************************************************/
49 :
50 997 : std::shared_ptr<ZarrV2Array> ZarrV2Array::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> &anBlockSize, bool bFortranOrder)
56 : {
57 : auto arr = std::shared_ptr<ZarrV2Array>(
58 : new ZarrV2Array(poSharedResource, poParent, osName, aoDims, oType,
59 1994 : aoDtypeElts, anBlockSize, bFortranOrder));
60 997 : if (arr->m_nTotalInnerChunkCount == 0)
61 1 : return nullptr;
62 996 : arr->SetSelf(arr);
63 :
64 996 : return arr;
65 : }
66 :
67 : /************************************************************************/
68 : /* ~ZarrV2Array() */
69 : /************************************************************************/
70 :
71 1994 : ZarrV2Array::~ZarrV2Array()
72 : {
73 997 : ZarrV2Array::Flush();
74 1994 : }
75 :
76 : /************************************************************************/
77 : /* Flush() */
78 : /************************************************************************/
79 :
80 4352 : bool ZarrV2Array::Flush()
81 : {
82 4352 : if (!m_bValid)
83 12 : return true;
84 :
85 4340 : bool ret = ZarrV2Array::FlushDirtyBlock();
86 :
87 4340 : if (m_bDefinitionModified)
88 : {
89 324 : if (!Serialize())
90 3 : ret = false;
91 324 : m_bDefinitionModified = false;
92 : }
93 :
94 4340 : CPLJSONArray j_ARRAY_DIMENSIONS;
95 4340 : bool bDimensionsModified = false;
96 4340 : if (!m_aoDims.empty())
97 : {
98 8245 : for (const auto &poDim : m_aoDims)
99 : {
100 : const auto poZarrDim =
101 5411 : dynamic_cast<const ZarrDimension *>(poDim.get());
102 5411 : if (poZarrDim && poZarrDim->IsXArrayDimension())
103 : {
104 4246 : if (poZarrDim->IsModified())
105 16 : bDimensionsModified = true;
106 4246 : j_ARRAY_DIMENSIONS.Add(poDim->GetName());
107 : }
108 : else
109 : {
110 1165 : j_ARRAY_DIMENSIONS = CPLJSONArray();
111 1165 : break;
112 : }
113 : }
114 : }
115 :
116 8621 : if (m_oAttrGroup.IsModified() || bDimensionsModified ||
117 4265 : (m_bNew && j_ARRAY_DIMENSIONS.Size() != 0) || m_bUnitModified ||
118 8621 : m_bOffsetModified || m_bScaleModified || m_bSRSModified)
119 : {
120 350 : m_bNew = false;
121 :
122 700 : auto oAttrs = SerializeSpecialAttributes();
123 :
124 350 : if (j_ARRAY_DIMENSIONS.Size() != 0)
125 : {
126 334 : oAttrs.Delete("_ARRAY_DIMENSIONS");
127 334 : oAttrs.Add("_ARRAY_DIMENSIONS", j_ARRAY_DIMENSIONS);
128 : }
129 :
130 700 : CPLJSONDocument oDoc;
131 350 : oDoc.SetRoot(oAttrs);
132 : const std::string osAttrFilename =
133 350 : CPLFormFilenameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
134 700 : ".zattrs", nullptr);
135 350 : if (!oDoc.Save(osAttrFilename))
136 7 : ret = false;
137 350 : m_poSharedResource->SetZMetadataItem(osAttrFilename, oAttrs);
138 : }
139 :
140 4340 : return ret;
141 : }
142 :
143 : /************************************************************************/
144 : /* StripUselessItemsFromCompressorConfiguration() */
145 : /************************************************************************/
146 :
147 28 : static void StripUselessItemsFromCompressorConfiguration(CPLJSONObject &o)
148 : {
149 28 : if (o.GetType() == CPLJSONObject::Type::Object)
150 : {
151 18 : o.Delete("num_threads"); // Blosc
152 18 : o.Delete("typesize"); // Blosc
153 18 : o.Delete("header"); // LZ4
154 : }
155 28 : }
156 :
157 : /************************************************************************/
158 : /* ZarrV2Array::Serialize() */
159 : /************************************************************************/
160 :
161 324 : bool ZarrV2Array::Serialize()
162 : {
163 648 : CPLJSONDocument oDoc;
164 648 : CPLJSONObject oRoot = oDoc.GetRoot();
165 :
166 648 : CPLJSONArray oChunks;
167 786 : for (const auto nBlockSize : m_anOuterBlockSize)
168 : {
169 462 : oChunks.Add(static_cast<GInt64>(nBlockSize));
170 : }
171 324 : oRoot.Add("chunks", oChunks);
172 :
173 324 : if (m_oCompressorJSon.IsValid())
174 : {
175 28 : oRoot.Add("compressor", m_oCompressorJSon);
176 84 : CPLJSONObject compressor = oRoot["compressor"];
177 28 : StripUselessItemsFromCompressorConfiguration(compressor);
178 : }
179 : else
180 : {
181 296 : oRoot.AddNull("compressor");
182 : }
183 :
184 324 : if (m_dtype.GetType() == CPLJSONObject::Type::Object)
185 312 : oRoot.Add("dtype", m_dtype["dummy"]);
186 : else
187 12 : oRoot.Add("dtype", m_dtype);
188 :
189 324 : if (m_pabyNoData == nullptr)
190 : {
191 314 : oRoot.AddNull("fill_value");
192 : }
193 : else
194 : {
195 10 : switch (m_oType.GetClass())
196 : {
197 8 : case GEDTC_NUMERIC:
198 : {
199 8 : SerializeNumericNoData(oRoot);
200 8 : break;
201 : }
202 :
203 1 : case GEDTC_STRING:
204 : {
205 : char *pszStr;
206 1 : char **ppszStr = reinterpret_cast<char **>(m_pabyNoData);
207 1 : memcpy(&pszStr, ppszStr, sizeof(pszStr));
208 1 : if (pszStr)
209 : {
210 : const size_t nNativeSize =
211 1 : m_aoDtypeElts.back().nativeOffset +
212 1 : m_aoDtypeElts.back().nativeSize;
213 3 : char *base64 = CPLBase64Encode(
214 1 : static_cast<int>(std::min(nNativeSize, strlen(pszStr))),
215 : reinterpret_cast<const GByte *>(pszStr));
216 1 : oRoot.Add("fill_value", base64);
217 1 : CPLFree(base64);
218 : }
219 : else
220 : {
221 0 : oRoot.AddNull("fill_value");
222 : }
223 1 : break;
224 : }
225 :
226 1 : case GEDTC_COMPOUND:
227 : {
228 1 : const size_t nNativeSize = m_aoDtypeElts.back().nativeOffset +
229 1 : m_aoDtypeElts.back().nativeSize;
230 2 : std::vector<GByte> nativeNoData(nNativeSize);
231 1 : EncodeElt(m_aoDtypeElts, m_pabyNoData, &nativeNoData[0]);
232 1 : char *base64 = CPLBase64Encode(static_cast<int>(nNativeSize),
233 1 : nativeNoData.data());
234 1 : oRoot.Add("fill_value", base64);
235 1 : CPLFree(base64);
236 : }
237 : }
238 : }
239 :
240 324 : if (m_oFiltersArray.Size() == 0)
241 323 : oRoot.AddNull("filters");
242 : else
243 1 : oRoot.Add("filters", m_oFiltersArray);
244 :
245 324 : oRoot.Add("order", m_bFortranOrder ? "F" : "C");
246 :
247 324 : CPLJSONArray oShape;
248 786 : for (const auto &poDim : m_aoDims)
249 : {
250 462 : oShape.Add(static_cast<GInt64>(poDim->GetSize()));
251 : }
252 324 : oRoot.Add("shape", oShape);
253 :
254 324 : oRoot.Add("zarr_format", 2);
255 :
256 324 : if (m_osDimSeparator != ".")
257 : {
258 10 : oRoot.Add("dimension_separator", m_osDimSeparator);
259 : }
260 :
261 324 : bool ret = oDoc.Save(m_osFilename);
262 :
263 324 : m_poSharedResource->SetZMetadataItem(m_osFilename, oRoot);
264 :
265 648 : return ret;
266 : }
267 :
268 : /************************************************************************/
269 : /* ZarrV2Array::NeedDecodedBuffer() */
270 : /************************************************************************/
271 :
272 11718 : bool ZarrV2Array::NeedDecodedBuffer() const
273 : {
274 : const size_t nSourceSize =
275 11718 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
276 11724 : if (m_oType.GetClass() == GEDTC_COMPOUND &&
277 6 : nSourceSize != m_oType.GetSize())
278 : {
279 4 : return true;
280 : }
281 11714 : else if (m_oType.GetClass() != GEDTC_STRING)
282 : {
283 23300 : for (const auto &elt : m_aoDtypeElts)
284 : {
285 11692 : if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative ||
286 11610 : elt.nativeType == DtypeElt::NativeType::STRING_ASCII ||
287 11610 : elt.nativeType == DtypeElt::NativeType::STRING_UNICODE)
288 : {
289 82 : return true;
290 : }
291 : }
292 : }
293 11632 : return false;
294 : }
295 :
296 : /************************************************************************/
297 : /* ZarrV2Array::AllocateWorkingBuffers() */
298 : /************************************************************************/
299 :
300 1960 : bool ZarrV2Array::AllocateWorkingBuffers() const
301 : {
302 1960 : if (m_bAllocateWorkingBuffersDone)
303 1435 : return m_bWorkingBuffersOK;
304 :
305 525 : m_bAllocateWorkingBuffersDone = true;
306 :
307 525 : size_t nSizeNeeded = m_nInnerBlockSizeBytes;
308 525 : if (m_bFortranOrder || m_oFiltersArray.Size() != 0)
309 : {
310 47 : if (nSizeNeeded > std::numeric_limits<size_t>::max() / 2)
311 : {
312 1 : CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
313 1 : return false;
314 : }
315 46 : nSizeNeeded *= 2;
316 : }
317 524 : if (NeedDecodedBuffer())
318 : {
319 43 : size_t nDecodedBufferSize = m_oType.GetSize();
320 126 : for (const auto &nBlockSize : m_anOuterBlockSize)
321 : {
322 83 : if (nDecodedBufferSize > std::numeric_limits<size_t>::max() /
323 83 : static_cast<size_t>(nBlockSize))
324 : {
325 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
326 0 : return false;
327 : }
328 83 : nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
329 : }
330 43 : if (nSizeNeeded >
331 43 : std::numeric_limits<size_t>::max() - nDecodedBufferSize)
332 : {
333 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
334 0 : return false;
335 : }
336 43 : nSizeNeeded += nDecodedBufferSize;
337 : }
338 :
339 : // Reserve a buffer for tile content
340 526 : if (nSizeNeeded > 1024 * 1024 * 1024 &&
341 2 : !CPLTestBool(CPLGetConfigOption("ZARR_ALLOW_BIG_TILE_SIZE", "NO")))
342 : {
343 2 : CPLError(CE_Failure, CPLE_AppDefined,
344 : "Zarr tile allocation would require " CPL_FRMT_GUIB " bytes. "
345 : "By default the driver limits to 1 GB. To allow that memory "
346 : "allocation, set the ZARR_ALLOW_BIG_TILE_SIZE configuration "
347 : "option to YES.",
348 : static_cast<GUIntBig>(nSizeNeeded));
349 2 : return false;
350 : }
351 :
352 1044 : m_bWorkingBuffersOK = AllocateWorkingBuffers(
353 522 : m_abyRawBlockData, m_abyTmpRawBlockData, m_abyDecodedBlockData);
354 522 : return m_bWorkingBuffersOK;
355 : }
356 :
357 11194 : bool ZarrV2Array::AllocateWorkingBuffers(
358 : ZarrByteVectorQuickResize &abyRawBlockData,
359 : ZarrByteVectorQuickResize &abyTmpRawBlockData,
360 : ZarrByteVectorQuickResize &abyDecodedBlockData) const
361 : {
362 : // This method should NOT modify any ZarrArray member, as it is going to
363 : // be called concurrently from several threads.
364 :
365 : // Set those #define to avoid accidental use of some global variables
366 : #define m_abyTmpRawBlockData cannot_use_here
367 : #define m_abyRawBlockData cannot_use_here
368 : #define m_abyDecodedBlockData cannot_use_here
369 :
370 : try
371 : {
372 11194 : abyRawBlockData.resize(m_nInnerBlockSizeBytes);
373 11194 : if (m_bFortranOrder || m_oFiltersArray.Size() != 0)
374 46 : abyTmpRawBlockData.resize(m_nInnerBlockSizeBytes);
375 : }
376 0 : catch (const std::bad_alloc &e)
377 : {
378 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
379 0 : return false;
380 : }
381 :
382 11194 : if (NeedDecodedBuffer())
383 : {
384 43 : size_t nDecodedBufferSize = m_oType.GetSize();
385 126 : for (const auto &nBlockSize : m_anOuterBlockSize)
386 : {
387 83 : nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
388 : }
389 : try
390 : {
391 43 : abyDecodedBlockData.resize(nDecodedBufferSize);
392 : }
393 0 : catch (const std::bad_alloc &e)
394 : {
395 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
396 0 : return false;
397 : }
398 : }
399 :
400 11194 : return true;
401 : #undef m_abyTmpRawBlockData
402 : #undef m_abyRawBlockData
403 : #undef m_abyDecodedBlockData
404 : }
405 :
406 : /************************************************************************/
407 : /* ZarrV2Array::BlockTranspose() */
408 : /************************************************************************/
409 :
410 66 : void ZarrV2Array::BlockTranspose(const ZarrByteVectorQuickResize &abySrc,
411 : ZarrByteVectorQuickResize &abyDst,
412 : bool bDecode) const
413 : {
414 : // Perform transposition
415 66 : const size_t nDims = m_anOuterBlockSize.size();
416 : const size_t nSourceSize =
417 66 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
418 :
419 : struct Stack
420 : {
421 : size_t nIters = 0;
422 : const GByte *src_ptr = nullptr;
423 : GByte *dst_ptr = nullptr;
424 : size_t src_inc_offset = 0;
425 : size_t dst_inc_offset = 0;
426 : };
427 :
428 132 : std::vector<Stack> stack(nDims);
429 : #if defined(__GNUC__)
430 : #pragma GCC diagnostic push
431 : #pragma GCC diagnostic ignored "-Wnull-dereference"
432 : #endif
433 : stack.emplace_back(
434 66 : Stack()); // to make gcc 9.3 -O2 -Wnull-dereference happy
435 : #if defined(__GNUC__)
436 : #pragma GCC diagnostic pop
437 : #endif
438 :
439 66 : if (bDecode)
440 : {
441 46 : stack[0].src_inc_offset = nSourceSize;
442 118 : for (size_t i = 1; i < nDims; ++i)
443 : {
444 72 : stack[i].src_inc_offset =
445 72 : stack[i - 1].src_inc_offset *
446 72 : static_cast<size_t>(m_anOuterBlockSize[i - 1]);
447 : }
448 :
449 46 : stack[nDims - 1].dst_inc_offset = nSourceSize;
450 118 : for (size_t i = nDims - 1; i > 0;)
451 : {
452 72 : --i;
453 72 : stack[i].dst_inc_offset =
454 72 : stack[i + 1].dst_inc_offset *
455 72 : static_cast<size_t>(m_anOuterBlockSize[i + 1]);
456 : }
457 : }
458 : else
459 : {
460 20 : stack[0].dst_inc_offset = nSourceSize;
461 60 : for (size_t i = 1; i < nDims; ++i)
462 : {
463 40 : stack[i].dst_inc_offset =
464 40 : stack[i - 1].dst_inc_offset *
465 40 : static_cast<size_t>(m_anOuterBlockSize[i - 1]);
466 : }
467 :
468 20 : stack[nDims - 1].src_inc_offset = nSourceSize;
469 60 : for (size_t i = nDims - 1; i > 0;)
470 : {
471 40 : --i;
472 40 : stack[i].src_inc_offset =
473 40 : stack[i + 1].src_inc_offset *
474 40 : static_cast<size_t>(m_anOuterBlockSize[i + 1]);
475 : }
476 : }
477 :
478 66 : stack[0].src_ptr = abySrc.data();
479 66 : stack[0].dst_ptr = &abyDst[0];
480 :
481 66 : size_t dimIdx = 0;
482 1058 : lbl_next_depth:
483 1058 : if (dimIdx == nDims)
484 : {
485 744 : void *dst_ptr = stack[nDims].dst_ptr;
486 744 : const void *src_ptr = stack[nDims].src_ptr;
487 744 : if (nSourceSize == 1)
488 264 : *stack[nDims].dst_ptr = *stack[nDims].src_ptr;
489 480 : else if (nSourceSize == 2)
490 120 : *static_cast<uint16_t *>(dst_ptr) =
491 120 : *static_cast<const uint16_t *>(src_ptr);
492 360 : else if (nSourceSize == 4)
493 168 : *static_cast<uint32_t *>(dst_ptr) =
494 168 : *static_cast<const uint32_t *>(src_ptr);
495 192 : else if (nSourceSize == 8)
496 168 : *static_cast<uint64_t *>(dst_ptr) =
497 168 : *static_cast<const uint64_t *>(src_ptr);
498 : else
499 24 : memcpy(dst_ptr, src_ptr, nSourceSize);
500 : }
501 : else
502 : {
503 314 : stack[dimIdx].nIters = static_cast<size_t>(m_anOuterBlockSize[dimIdx]);
504 : while (true)
505 : {
506 992 : dimIdx++;
507 992 : stack[dimIdx].src_ptr = stack[dimIdx - 1].src_ptr;
508 992 : stack[dimIdx].dst_ptr = stack[dimIdx - 1].dst_ptr;
509 992 : goto lbl_next_depth;
510 992 : lbl_return_to_caller:
511 992 : dimIdx--;
512 992 : if ((--stack[dimIdx].nIters) == 0)
513 314 : break;
514 678 : stack[dimIdx].src_ptr += stack[dimIdx].src_inc_offset;
515 678 : stack[dimIdx].dst_ptr += stack[dimIdx].dst_inc_offset;
516 : }
517 : }
518 1058 : if (dimIdx > 0)
519 992 : goto lbl_return_to_caller;
520 66 : }
521 :
522 : /************************************************************************/
523 : /* ZarrV2Array::LoadBlockData() */
524 : /************************************************************************/
525 :
526 4893 : bool ZarrV2Array::LoadBlockData(const uint64_t *blockIndices,
527 : bool &bMissingBlockOut) const
528 : {
529 9786 : return LoadBlockData(blockIndices,
530 : false, // use mutex
531 4893 : m_psDecompressor, m_abyRawBlockData,
532 4893 : m_abyTmpRawBlockData, m_abyDecodedBlockData,
533 4893 : bMissingBlockOut);
534 : }
535 :
536 15565 : bool ZarrV2Array::LoadBlockData(const uint64_t *blockIndices, bool bUseMutex,
537 : const CPLCompressor *psDecompressor,
538 : ZarrByteVectorQuickResize &abyRawBlockData,
539 : ZarrByteVectorQuickResize &abyTmpRawBlockData,
540 : ZarrByteVectorQuickResize &abyDecodedBlockData,
541 : bool &bMissingBlockOut) const
542 : {
543 : // This method should NOT modify any ZarrArray member, as it is going to
544 : // be called concurrently from several threads.
545 :
546 : // Set those #define to avoid accidental use of some global variables
547 : #define m_abyTmpRawBlockData cannot_use_here
548 : #define m_abyRawBlockData cannot_use_here
549 : #define m_abyDecodedBlockData cannot_use_here
550 : #define m_psDecompressor cannot_use_here
551 :
552 15565 : bMissingBlockOut = false;
553 :
554 31130 : std::string osFilename = BuildChunkFilename(blockIndices);
555 :
556 : // For network file systems, get the streaming version of the filename,
557 : // as we don't need arbitrary seeking in the file
558 15565 : osFilename = VSIFileManager::GetHandler(osFilename.c_str())
559 15565 : ->GetStreamingFilename(osFilename);
560 :
561 : // First if we have a tile presence cache, check tile presence from it
562 : bool bEarlyRet;
563 15565 : if (bUseMutex)
564 : {
565 10672 : std::lock_guard<std::mutex> oLock(m_oMutex);
566 10672 : bEarlyRet = IsBlockMissingFromCacheInfo(osFilename, blockIndices);
567 : }
568 : else
569 : {
570 4893 : bEarlyRet = IsBlockMissingFromCacheInfo(osFilename, blockIndices);
571 : }
572 15565 : if (bEarlyRet)
573 : {
574 13 : bMissingBlockOut = true;
575 13 : return true;
576 : }
577 :
578 15552 : VSILFILE *fp = nullptr;
579 : // This is the number of files returned in a S3 directory listing operation
580 15552 : constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
581 15552 : const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
582 : nullptr};
583 15552 : const auto nErrorBefore = CPLGetErrorCounter();
584 15572 : if ((m_osDimSeparator == "/" && !m_anOuterBlockSize.empty() &&
585 31124 : m_anOuterBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
586 15552 : (m_osDimSeparator != "/" &&
587 15532 : m_nTotalInnerChunkCount > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING))
588 : {
589 : // Avoid issuing ReadDir() when a lot of files are expected
590 : CPLConfigOptionSetter optionSetter("GDAL_DISABLE_READDIR_ON_OPEN",
591 10982 : "YES", true);
592 10982 : fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
593 : }
594 : else
595 : {
596 4570 : fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
597 : }
598 15552 : if (fp == nullptr)
599 : {
600 2715 : if (nErrorBefore != CPLGetErrorCounter())
601 : {
602 4 : return false;
603 : }
604 : else
605 : {
606 : // Missing files are OK and indicate nodata_value
607 2711 : CPLDebugOnly(ZARR_DEBUG_KEY, "Block %s missing (=nodata)",
608 : osFilename.c_str());
609 2711 : bMissingBlockOut = true;
610 2711 : return true;
611 : }
612 : }
613 :
614 12837 : bMissingBlockOut = false;
615 12837 : bool bRet = true;
616 12837 : size_t nRawDataSize = abyRawBlockData.size();
617 12837 : if (psDecompressor == nullptr)
618 : {
619 7295 : nRawDataSize = VSIFReadL(&abyRawBlockData[0], 1, nRawDataSize, fp);
620 : }
621 : else
622 : {
623 5542 : VSIFSeekL(fp, 0, SEEK_END);
624 5542 : const auto nSize = VSIFTellL(fp);
625 5542 : VSIFSeekL(fp, 0, SEEK_SET);
626 5542 : if (nSize > static_cast<vsi_l_offset>(std::numeric_limits<int>::max()))
627 : {
628 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large tile %s",
629 : osFilename.c_str());
630 0 : bRet = false;
631 : }
632 : else
633 : {
634 11084 : ZarrByteVectorQuickResize abyCompressedData;
635 : try
636 : {
637 5542 : abyCompressedData.resize(static_cast<size_t>(nSize));
638 : }
639 0 : catch (const std::exception &)
640 : {
641 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
642 : "Cannot allocate memory for tile %s",
643 : osFilename.c_str());
644 0 : bRet = false;
645 : }
646 :
647 16626 : if (bRet &&
648 11084 : (abyCompressedData.empty() ||
649 5542 : VSIFReadL(&abyCompressedData[0], 1, abyCompressedData.size(),
650 5542 : fp) != abyCompressedData.size()))
651 : {
652 0 : CPLError(CE_Failure, CPLE_AppDefined,
653 : "Could not read tile %s correctly",
654 : osFilename.c_str());
655 0 : bRet = false;
656 : }
657 : else
658 : {
659 5542 : void *out_buffer = &abyRawBlockData[0];
660 5542 : if (!psDecompressor->pfnFunc(
661 5542 : abyCompressedData.data(), abyCompressedData.size(),
662 : &out_buffer, &nRawDataSize, nullptr,
663 5542 : psDecompressor->user_data))
664 : {
665 3 : CPLError(CE_Failure, CPLE_AppDefined,
666 : "Decompression of tile %s failed",
667 : osFilename.c_str());
668 3 : bRet = false;
669 : }
670 : }
671 : }
672 : }
673 12837 : VSIFCloseL(fp);
674 12837 : if (!bRet)
675 3 : return false;
676 :
677 12847 : for (int i = m_oFiltersArray.Size(); i > 0;)
678 : {
679 13 : --i;
680 13 : const auto &oFilter = m_oFiltersArray[i];
681 26 : const auto osFilterId = oFilter["id"].ToString();
682 : const auto psFilterDecompressor =
683 24 : EQUAL(osFilterId.c_str(), "shuffle") ? ZarrGetShuffleDecompressor()
684 11 : : EQUAL(osFilterId.c_str(), "quantize")
685 20 : ? ZarrGetQuantizeDecompressor()
686 9 : : EQUAL(osFilterId.c_str(), "fixedscaleoffset")
687 9 : ? ZarrGetFixedScaleOffsetDecompressor()
688 4 : : CPLGetDecompressor(osFilterId.c_str());
689 13 : CPLAssert(psFilterDecompressor);
690 :
691 13 : CPLStringList aosOptions;
692 58 : for (const auto &obj : oFilter.GetChildren())
693 : {
694 90 : aosOptions.SetNameValue(obj.GetName().c_str(),
695 135 : obj.ToString().c_str());
696 : }
697 13 : void *out_buffer = &abyTmpRawBlockData[0];
698 13 : size_t nOutSize = abyTmpRawBlockData.size();
699 13 : if (!psFilterDecompressor->pfnFunc(
700 13 : abyRawBlockData.data(), nRawDataSize, &out_buffer, &nOutSize,
701 13 : aosOptions.List(), psFilterDecompressor->user_data))
702 : {
703 0 : CPLError(CE_Failure, CPLE_AppDefined,
704 : "Filter %s for tile %s failed", osFilterId.c_str(),
705 : osFilename.c_str());
706 0 : return false;
707 : }
708 :
709 13 : nRawDataSize = nOutSize;
710 13 : std::swap(abyRawBlockData, abyTmpRawBlockData);
711 : }
712 12834 : if (nRawDataSize != abyRawBlockData.size())
713 : {
714 0 : CPLError(CE_Failure, CPLE_AppDefined,
715 : "Decompressed tile %s has not expected size after filters",
716 : osFilename.c_str());
717 0 : return false;
718 : }
719 :
720 12834 : if (m_bFortranOrder && !m_aoDims.empty())
721 : {
722 46 : BlockTranspose(abyRawBlockData, abyTmpRawBlockData, true);
723 46 : std::swap(abyRawBlockData, abyTmpRawBlockData);
724 : }
725 :
726 12834 : if (!abyDecodedBlockData.empty())
727 : {
728 : const size_t nSourceSize =
729 326 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
730 326 : const auto nDTSize = m_oType.GetSize();
731 326 : const size_t nValues = abyDecodedBlockData.size() / nDTSize;
732 326 : const GByte *pSrc = abyRawBlockData.data();
733 326 : GByte *pDst = &abyDecodedBlockData[0];
734 2257 : for (size_t i = 0; i < nValues;
735 1931 : i++, pSrc += nSourceSize, pDst += nDTSize)
736 : {
737 1931 : DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
738 : }
739 : }
740 :
741 12834 : return true;
742 :
743 : #undef m_abyTmpRawBlockData
744 : #undef m_abyRawBlockData
745 : #undef m_abyDecodedBlockData
746 : #undef m_psDecompressor
747 : }
748 :
749 : /************************************************************************/
750 : /* ZarrV2Array::IAdviseRead() */
751 : /************************************************************************/
752 :
753 6 : bool ZarrV2Array::IAdviseRead(const GUInt64 *arrayStartIdx, const size_t *count,
754 : CSLConstList papszOptions) const
755 : {
756 12 : std::vector<uint64_t> anIndicesCur;
757 6 : int nThreadsMax = 0;
758 12 : std::vector<uint64_t> anReqBlocksIndices;
759 6 : size_t nReqBlocks = 0;
760 6 : if (!IAdviseReadCommon(arrayStartIdx, count, papszOptions, anIndicesCur,
761 : nThreadsMax, anReqBlocksIndices, nReqBlocks))
762 : {
763 2 : return false;
764 : }
765 4 : if (nThreadsMax <= 1)
766 : {
767 0 : return true;
768 : }
769 :
770 : const int nThreads = static_cast<int>(
771 4 : std::min(static_cast<size_t>(nThreadsMax), nReqBlocks));
772 :
773 4 : CPLWorkerThreadPool *wtp = GDALGetGlobalThreadPool(nThreadsMax);
774 4 : if (wtp == nullptr)
775 0 : return false;
776 :
777 : struct JobStruct
778 : {
779 : JobStruct() = default;
780 :
781 : JobStruct(const JobStruct &) = delete;
782 : JobStruct &operator=(const JobStruct &) = delete;
783 :
784 : JobStruct(JobStruct &&) = default;
785 : JobStruct &operator=(JobStruct &&) = default;
786 :
787 : const ZarrV2Array *poArray = nullptr;
788 : bool *pbGlobalStatus = nullptr;
789 : int *pnRemainingThreads = nullptr;
790 : const std::vector<uint64_t> *panReqBlocksIndices = nullptr;
791 : size_t nFirstIdx = 0;
792 : size_t nLastIdxNotIncluded = 0;
793 : };
794 :
795 4 : std::vector<JobStruct> asJobStructs;
796 :
797 4 : bool bGlobalStatus = true;
798 4 : int nRemainingThreads = nThreads;
799 : // Check for very highly overflow in below loop
800 4 : assert(static_cast<size_t>(nThreads) <
801 : std::numeric_limits<size_t>::max() / nReqBlocks);
802 :
803 : // Setup jobs
804 20 : for (int i = 0; i < nThreads; i++)
805 : {
806 16 : JobStruct jobStruct;
807 16 : jobStruct.poArray = this;
808 16 : jobStruct.pbGlobalStatus = &bGlobalStatus;
809 16 : jobStruct.pnRemainingThreads = &nRemainingThreads;
810 16 : jobStruct.panReqBlocksIndices = &anReqBlocksIndices;
811 16 : jobStruct.nFirstIdx = static_cast<size_t>(i * nReqBlocks / nThreads);
812 16 : jobStruct.nLastIdxNotIncluded = std::min(
813 16 : static_cast<size_t>((i + 1) * nReqBlocks / nThreads), nReqBlocks);
814 16 : asJobStructs.emplace_back(std::move(jobStruct));
815 : }
816 :
817 16 : const auto JobFunc = [](void *pThreadData)
818 : {
819 16 : const JobStruct *jobStruct =
820 : static_cast<const JobStruct *>(pThreadData);
821 :
822 16 : const auto poArray = jobStruct->poArray;
823 16 : const size_t l_nDims = poArray->GetDimensionCount();
824 16 : ZarrByteVectorQuickResize abyRawBlockData;
825 16 : ZarrByteVectorQuickResize abyDecodedBlockData;
826 16 : ZarrByteVectorQuickResize abyTmpRawBlockData;
827 : const CPLCompressor *psDecompressor =
828 16 : CPLGetDecompressor(poArray->m_osDecompressorId.c_str());
829 :
830 10688 : for (size_t iReq = jobStruct->nFirstIdx;
831 10688 : iReq < jobStruct->nLastIdxNotIncluded; ++iReq)
832 : {
833 : // Check if we must early exit
834 : {
835 10672 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
836 10672 : if (!(*jobStruct->pbGlobalStatus))
837 0 : return;
838 : }
839 :
840 : const uint64_t *blockIndices =
841 10672 : jobStruct->panReqBlocksIndices->data() + iReq * l_nDims;
842 :
843 10672 : if (!poArray->AllocateWorkingBuffers(
844 : abyRawBlockData, abyTmpRawBlockData, abyDecodedBlockData))
845 : {
846 0 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
847 0 : *jobStruct->pbGlobalStatus = false;
848 0 : break;
849 : }
850 :
851 10672 : bool bIsEmpty = false;
852 10672 : bool success = poArray->LoadBlockData(
853 : blockIndices,
854 : true, // use mutex
855 : psDecompressor, abyRawBlockData, abyTmpRawBlockData,
856 : abyDecodedBlockData, bIsEmpty);
857 :
858 10672 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
859 10672 : if (!success)
860 : {
861 0 : *jobStruct->pbGlobalStatus = false;
862 0 : break;
863 : }
864 :
865 21344 : CachedBlock cachedBlock;
866 10672 : if (!bIsEmpty)
867 : {
868 10670 : if (!abyDecodedBlockData.empty())
869 0 : std::swap(cachedBlock.abyDecoded, abyDecodedBlockData);
870 : else
871 10670 : std::swap(cachedBlock.abyDecoded, abyRawBlockData);
872 : }
873 : const std::vector<uint64_t> cacheKey{blockIndices,
874 21344 : blockIndices + l_nDims};
875 10672 : poArray->m_oChunkCache[cacheKey] = std::move(cachedBlock);
876 : }
877 :
878 16 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
879 16 : (*jobStruct->pnRemainingThreads)--;
880 : };
881 :
882 : // Start jobs
883 20 : for (int i = 0; i < nThreads; i++)
884 : {
885 16 : if (!wtp->SubmitJob(JobFunc, &asJobStructs[i]))
886 : {
887 0 : std::lock_guard<std::mutex> oLock(m_oMutex);
888 0 : bGlobalStatus = false;
889 0 : nRemainingThreads = i;
890 0 : break;
891 : }
892 : }
893 :
894 : // Wait for all jobs to be finished
895 : while (true)
896 : {
897 : {
898 16 : std::lock_guard<std::mutex> oLock(m_oMutex);
899 16 : if (nRemainingThreads == 0)
900 4 : break;
901 : }
902 12 : wtp->WaitEvent();
903 12 : }
904 :
905 4 : return bGlobalStatus;
906 : }
907 :
908 : /************************************************************************/
909 : /* ZarrV2Array::FlushDirtyBlock() */
910 : /************************************************************************/
911 :
912 20073 : bool ZarrV2Array::FlushDirtyBlock() const
913 : {
914 20073 : if (!m_bDirtyBlock)
915 8002 : return true;
916 12071 : m_bDirtyBlock = false;
917 :
918 24142 : std::string osFilename = BuildChunkFilename(m_anCachedBlockIndices.data());
919 :
920 : const size_t nSourceSize =
921 12071 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
922 12071 : const auto &abyBlock = m_abyDecodedBlockData.empty()
923 : ? m_abyRawBlockData
924 12071 : : m_abyDecodedBlockData;
925 :
926 12071 : if (IsEmptyBlock(abyBlock))
927 : {
928 690 : m_bCachedBlockEmpty = true;
929 :
930 : VSIStatBufL sStat;
931 690 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
932 : {
933 108 : CPLDebugOnly(ZARR_DEBUG_KEY,
934 : "Deleting tile %s that has now empty content",
935 : osFilename.c_str());
936 108 : return VSIUnlink(osFilename.c_str()) == 0;
937 : }
938 582 : return true;
939 : }
940 :
941 11381 : if (!m_abyDecodedBlockData.empty())
942 : {
943 20 : const size_t nDTSize = m_oType.GetSize();
944 20 : const size_t nValues = m_abyDecodedBlockData.size() / nDTSize;
945 20 : GByte *pDst = &m_abyRawBlockData[0];
946 20 : const GByte *pSrc = m_abyDecodedBlockData.data();
947 140 : for (size_t i = 0; i < nValues;
948 120 : i++, pDst += nSourceSize, pSrc += nDTSize)
949 : {
950 120 : EncodeElt(m_aoDtypeElts, pSrc, pDst);
951 : }
952 : }
953 :
954 11381 : if (m_bFortranOrder && !m_aoDims.empty())
955 : {
956 20 : BlockTranspose(m_abyRawBlockData, m_abyTmpRawBlockData, false);
957 20 : std::swap(m_abyRawBlockData, m_abyTmpRawBlockData);
958 : }
959 :
960 11381 : size_t nRawDataSize = m_abyRawBlockData.size();
961 11384 : for (const auto &oFilter : m_oFiltersArray)
962 : {
963 8 : const auto osFilterId = oFilter["id"].ToString();
964 4 : if (osFilterId == "quantize" || osFilterId == "fixedscaleoffset")
965 : {
966 1 : CPLError(CE_Failure, CPLE_NotSupported,
967 : "%s filter not supported for writing", osFilterId.c_str());
968 1 : return false;
969 : }
970 : const auto psFilterCompressor =
971 3 : EQUAL(osFilterId.c_str(), "shuffle")
972 3 : ? ZarrGetShuffleCompressor()
973 2 : : CPLGetCompressor(osFilterId.c_str());
974 3 : CPLAssert(psFilterCompressor);
975 :
976 3 : CPLStringList aosOptions;
977 9 : for (const auto &obj : oFilter.GetChildren())
978 : {
979 12 : aosOptions.SetNameValue(obj.GetName().c_str(),
980 18 : obj.ToString().c_str());
981 : }
982 3 : void *out_buffer = &m_abyTmpRawBlockData[0];
983 3 : size_t nOutSize = m_abyTmpRawBlockData.size();
984 3 : if (!psFilterCompressor->pfnFunc(
985 3 : m_abyRawBlockData.data(), nRawDataSize, &out_buffer, &nOutSize,
986 3 : aosOptions.List(), psFilterCompressor->user_data))
987 : {
988 0 : CPLError(CE_Failure, CPLE_AppDefined,
989 : "Filter %s for tile %s failed", osFilterId.c_str(),
990 : osFilename.c_str());
991 0 : return false;
992 : }
993 :
994 3 : nRawDataSize = nOutSize;
995 3 : std::swap(m_abyRawBlockData, m_abyTmpRawBlockData);
996 : }
997 :
998 11380 : if (m_osDimSeparator == "/")
999 : {
1000 20 : std::string osDir = CPLGetDirnameSafe(osFilename.c_str());
1001 : VSIStatBufL sStat;
1002 20 : if (VSIStatL(osDir.c_str(), &sStat) != 0)
1003 : {
1004 20 : if (VSIMkdirRecursive(osDir.c_str(), 0755) != 0)
1005 : {
1006 0 : CPLError(CE_Failure, CPLE_AppDefined,
1007 : "Cannot create directory %s", osDir.c_str());
1008 0 : return false;
1009 : }
1010 : }
1011 : }
1012 :
1013 11380 : if (m_psCompressor == nullptr && m_psDecompressor != nullptr)
1014 : {
1015 : // Case of imagecodecs_tiff
1016 :
1017 1 : CPLError(CE_Failure, CPLE_NotSupported,
1018 : "Only decompression supported for '%s' compression method",
1019 : m_osDecompressorId.c_str());
1020 1 : return false;
1021 : }
1022 :
1023 11379 : VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
1024 11379 : if (fp == nullptr)
1025 : {
1026 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot create tile %s",
1027 : osFilename.c_str());
1028 0 : return false;
1029 : }
1030 :
1031 11379 : bool bRet = true;
1032 11379 : if (m_psCompressor == nullptr)
1033 : {
1034 5929 : if (VSIFWriteL(m_abyRawBlockData.data(), 1, nRawDataSize, fp) !=
1035 : nRawDataSize)
1036 : {
1037 2 : CPLError(CE_Failure, CPLE_AppDefined,
1038 : "Could not write tile %s correctly", osFilename.c_str());
1039 2 : bRet = false;
1040 : }
1041 : }
1042 : else
1043 : {
1044 10900 : std::vector<GByte> abyCompressedData;
1045 : try
1046 : {
1047 5450 : constexpr size_t MIN_BUF_SIZE = 64; // somewhat arbitrary
1048 5450 : abyCompressedData.resize(static_cast<size_t>(
1049 5450 : MIN_BUF_SIZE + nRawDataSize + nRawDataSize / 3));
1050 : }
1051 0 : catch (const std::exception &)
1052 : {
1053 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
1054 : "Cannot allocate memory for tile %s", osFilename.c_str());
1055 0 : bRet = false;
1056 : }
1057 :
1058 5450 : if (bRet)
1059 : {
1060 5450 : void *out_buffer = &abyCompressedData[0];
1061 5450 : size_t out_size = abyCompressedData.size();
1062 10900 : CPLStringList aosOptions;
1063 5450 : const auto &compressorConfig = m_oCompressorJSon;
1064 16350 : for (const auto &obj : compressorConfig.GetChildren())
1065 : {
1066 21800 : aosOptions.SetNameValue(obj.GetName().c_str(),
1067 32700 : obj.ToString().c_str());
1068 : }
1069 5450 : if (EQUAL(m_psCompressor->pszId, "blosc") &&
1070 0 : m_oType.GetClass() == GEDTC_NUMERIC)
1071 : {
1072 : aosOptions.SetNameValue(
1073 : "TYPESIZE",
1074 : CPLSPrintf("%d", GDALGetDataTypeSizeBytes(
1075 : GDALGetNonComplexDataType(
1076 0 : m_oType.GetNumericDataType()))));
1077 : }
1078 :
1079 5450 : if (!m_psCompressor->pfnFunc(
1080 5450 : m_abyRawBlockData.data(), nRawDataSize, &out_buffer,
1081 5450 : &out_size, aosOptions.List(), m_psCompressor->user_data))
1082 : {
1083 0 : CPLError(CE_Failure, CPLE_AppDefined,
1084 : "Compression of tile %s failed", osFilename.c_str());
1085 0 : bRet = false;
1086 : }
1087 5450 : abyCompressedData.resize(out_size);
1088 : }
1089 :
1090 10900 : if (bRet &&
1091 5450 : VSIFWriteL(abyCompressedData.data(), 1, abyCompressedData.size(),
1092 5450 : fp) != abyCompressedData.size())
1093 : {
1094 0 : CPLError(CE_Failure, CPLE_AppDefined,
1095 : "Could not write tile %s correctly", osFilename.c_str());
1096 0 : bRet = false;
1097 : }
1098 : }
1099 11379 : VSIFCloseL(fp);
1100 :
1101 11379 : return bRet;
1102 : }
1103 :
1104 : /************************************************************************/
1105 : /* BuildChunkFilename() */
1106 : /************************************************************************/
1107 :
1108 27643 : std::string ZarrV2Array::BuildChunkFilename(const uint64_t *blockIndices) const
1109 : {
1110 27643 : std::string osFilename;
1111 27643 : if (m_aoDims.empty())
1112 : {
1113 5 : osFilename = "0";
1114 : }
1115 : else
1116 : {
1117 83429 : for (size_t i = 0; i < m_aoDims.size(); ++i)
1118 : {
1119 55791 : if (!osFilename.empty())
1120 28153 : osFilename += m_osDimSeparator;
1121 55791 : osFilename += std::to_string(blockIndices[i]);
1122 : }
1123 : }
1124 :
1125 55286 : return CPLFormFilenameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
1126 82929 : osFilename.c_str(), nullptr);
1127 : }
1128 :
1129 : /************************************************************************/
1130 : /* GetDataDirectory() */
1131 : /************************************************************************/
1132 :
1133 2 : std::string ZarrV2Array::GetDataDirectory() const
1134 : {
1135 2 : return CPLGetDirnameSafe(m_osFilename.c_str());
1136 : }
1137 :
1138 : /************************************************************************/
1139 : /* GetChunkIndicesFromFilename() */
1140 : /************************************************************************/
1141 :
1142 : CPLStringList
1143 5 : ZarrV2Array::GetChunkIndicesFromFilename(const char *pszFilename) const
1144 : {
1145 : return CPLStringList(
1146 5 : CSLTokenizeString2(pszFilename, m_osDimSeparator.c_str(), 0));
1147 : }
1148 :
1149 : /************************************************************************/
1150 : /* ParseDtype() */
1151 : /************************************************************************/
1152 :
1153 26 : static size_t GetAlignment(const CPLJSONObject &obj)
1154 : {
1155 26 : if (obj.GetType() == CPLJSONObject::Type::String)
1156 : {
1157 69 : const auto str = obj.ToString();
1158 23 : if (str.size() < 3)
1159 0 : return 1;
1160 23 : const char chType = str[1];
1161 23 : const int nBytes = atoi(str.c_str() + 2);
1162 23 : if (chType == 'S')
1163 2 : return sizeof(char *);
1164 21 : if (chType == 'c' && nBytes == 8)
1165 0 : return sizeof(float);
1166 21 : if (chType == 'c' && nBytes == 16)
1167 0 : return sizeof(double);
1168 21 : return nBytes;
1169 : }
1170 3 : else if (obj.GetType() == CPLJSONObject::Type::Array)
1171 : {
1172 6 : const auto oArray = obj.ToArray();
1173 3 : size_t nAlignment = 1;
1174 9 : for (const auto &oElt : oArray)
1175 : {
1176 6 : const auto oEltArray = oElt.ToArray();
1177 12 : if (!oEltArray.IsValid() || oEltArray.Size() != 2 ||
1178 12 : oEltArray[0].GetType() != CPLJSONObject::Type::String)
1179 : {
1180 0 : return 1;
1181 : }
1182 6 : nAlignment = std::max(nAlignment, GetAlignment(oEltArray[1]));
1183 6 : if (nAlignment == sizeof(void *))
1184 0 : break;
1185 : }
1186 3 : return nAlignment;
1187 : }
1188 0 : return 1;
1189 : }
1190 :
1191 727 : static GDALExtendedDataType ParseDtype(const CPLJSONObject &obj,
1192 : std::vector<DtypeElt> &elts)
1193 : {
1194 29 : const auto AlignOffsetOn = [](size_t offset, size_t alignment)
1195 29 : { return offset + (alignment - (offset % alignment)) % alignment; };
1196 :
1197 : do
1198 : {
1199 727 : if (obj.GetType() == CPLJSONObject::Type::String)
1200 : {
1201 1432 : const auto str = obj.ToString();
1202 716 : char chEndianness = 0;
1203 : char chType;
1204 : int nBytes;
1205 716 : DtypeElt elt;
1206 716 : if (str.size() < 3)
1207 3 : break;
1208 713 : chEndianness = str[0];
1209 713 : chType = str[1];
1210 713 : nBytes = atoi(str.c_str() + 2);
1211 713 : if (nBytes <= 0 || nBytes >= 1000)
1212 : break;
1213 :
1214 711 : elt.needByteSwapping = false;
1215 711 : if ((nBytes > 1 && chType != 'S') || chType == 'U')
1216 : {
1217 397 : if (chEndianness == '<')
1218 353 : elt.needByteSwapping = (CPL_IS_LSB == 0);
1219 44 : else if (chEndianness == '>')
1220 44 : elt.needByteSwapping = (CPL_IS_LSB != 0);
1221 : }
1222 :
1223 : GDALDataType eDT;
1224 711 : if (!elts.empty())
1225 : {
1226 11 : elt.nativeOffset =
1227 11 : elts.back().nativeOffset + elts.back().nativeSize;
1228 : }
1229 711 : elt.nativeSize = nBytes;
1230 711 : if (chType == 'b' && nBytes == 1) // boolean
1231 : {
1232 67 : elt.nativeType = DtypeElt::NativeType::BOOLEAN;
1233 67 : eDT = GDT_UInt8;
1234 : }
1235 644 : else if (chType == 'u' && nBytes == 1)
1236 : {
1237 226 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1238 226 : eDT = GDT_UInt8;
1239 : }
1240 418 : else if (chType == 'i' && nBytes == 1)
1241 : {
1242 12 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1243 12 : eDT = GDT_Int8;
1244 : }
1245 406 : else if (chType == 'i' && nBytes == 2)
1246 : {
1247 20 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1248 20 : eDT = GDT_Int16;
1249 : }
1250 386 : else if (chType == 'i' && nBytes == 4)
1251 : {
1252 28 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1253 28 : eDT = GDT_Int32;
1254 : }
1255 358 : else if (chType == 'i' && nBytes == 8)
1256 : {
1257 15 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1258 15 : eDT = GDT_Int64;
1259 : }
1260 343 : else if (chType == 'u' && nBytes == 2)
1261 : {
1262 43 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1263 43 : eDT = GDT_UInt16;
1264 : }
1265 300 : else if (chType == 'u' && nBytes == 4)
1266 : {
1267 18 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1268 18 : eDT = GDT_UInt32;
1269 : }
1270 282 : else if (chType == 'u' && nBytes == 8)
1271 : {
1272 14 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1273 14 : eDT = GDT_UInt64;
1274 : }
1275 268 : else if (chType == 'f' && nBytes == 2)
1276 : {
1277 : // elt.nativeType = DtypeElt::NativeType::IEEEFP;
1278 : // elt.gdalTypeIsApproxOfNative = true;
1279 : // eDT = GDT_Float32;
1280 5 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
1281 5 : eDT = GDT_Float16;
1282 : }
1283 263 : else if (chType == 'f' && nBytes == 4)
1284 : {
1285 44 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
1286 44 : eDT = GDT_Float32;
1287 : }
1288 219 : else if (chType == 'f' && nBytes == 8)
1289 : {
1290 180 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
1291 180 : eDT = GDT_Float64;
1292 : }
1293 39 : else if (chType == 'c' && nBytes == 8)
1294 : {
1295 11 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
1296 11 : eDT = GDT_CFloat32;
1297 : }
1298 28 : else if (chType == 'c' && nBytes == 16)
1299 : {
1300 12 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
1301 12 : eDT = GDT_CFloat64;
1302 : }
1303 16 : else if (chType == 'S')
1304 : {
1305 9 : elt.nativeType = DtypeElt::NativeType::STRING_ASCII;
1306 9 : elt.gdalType = GDALExtendedDataType::CreateString(nBytes);
1307 9 : elt.gdalSize = elt.gdalType.GetSize();
1308 9 : elts.emplace_back(elt);
1309 9 : return GDALExtendedDataType::CreateString(nBytes);
1310 : }
1311 7 : else if (chType == 'U')
1312 : {
1313 6 : elt.nativeType = DtypeElt::NativeType::STRING_UNICODE;
1314 : // the dtype declaration is number of UCS4 characters. Store it
1315 : // as bytes
1316 6 : elt.nativeSize *= 4;
1317 : // We can really map UCS4 size to UTF-8
1318 6 : elt.gdalType = GDALExtendedDataType::CreateString();
1319 6 : elt.gdalSize = elt.gdalType.GetSize();
1320 6 : elts.emplace_back(elt);
1321 6 : return GDALExtendedDataType::CreateString();
1322 : }
1323 : else
1324 1 : break;
1325 695 : elt.gdalType = GDALExtendedDataType::Create(eDT);
1326 695 : elt.gdalSize = elt.gdalType.GetSize();
1327 695 : elts.emplace_back(elt);
1328 695 : return GDALExtendedDataType::Create(eDT);
1329 : }
1330 11 : else if (obj.GetType() == CPLJSONObject::Type::Array)
1331 : {
1332 9 : bool error = false;
1333 9 : const auto oArray = obj.ToArray();
1334 9 : std::vector<std::unique_ptr<GDALEDTComponent>> comps;
1335 9 : size_t offset = 0;
1336 9 : size_t alignmentMax = 1;
1337 29 : for (const auto &oElt : oArray)
1338 : {
1339 20 : const auto oEltArray = oElt.ToArray();
1340 40 : if (!oEltArray.IsValid() || oEltArray.Size() != 2 ||
1341 40 : oEltArray[0].GetType() != CPLJSONObject::Type::String)
1342 : {
1343 0 : error = true;
1344 0 : break;
1345 : }
1346 20 : GDALExtendedDataType subDT = ParseDtype(oEltArray[1], elts);
1347 35 : if (subDT.GetClass() == GEDTC_NUMERIC &&
1348 15 : subDT.GetNumericDataType() == GDT_Unknown)
1349 : {
1350 0 : error = true;
1351 0 : break;
1352 : }
1353 :
1354 40 : const std::string osName = oEltArray[0].ToString();
1355 : // Add padding for alignment
1356 20 : const size_t alignmentSub = GetAlignment(oEltArray[1]);
1357 20 : assert(alignmentSub);
1358 20 : alignmentMax = std::max(alignmentMax, alignmentSub);
1359 20 : offset = AlignOffsetOn(offset, alignmentSub);
1360 40 : comps.emplace_back(std::unique_ptr<GDALEDTComponent>(
1361 40 : new GDALEDTComponent(osName, offset, subDT)));
1362 20 : offset += subDT.GetSize();
1363 : }
1364 9 : if (error)
1365 0 : break;
1366 9 : size_t nTotalSize = offset;
1367 9 : nTotalSize = AlignOffsetOn(nTotalSize, alignmentMax);
1368 18 : return GDALExtendedDataType::Create(obj.ToString(), nTotalSize,
1369 18 : std::move(comps));
1370 : }
1371 : } while (false);
1372 8 : CPLError(CE_Failure, CPLE_AppDefined,
1373 : "Invalid or unsupported format for dtype: %s",
1374 16 : obj.ToString().c_str());
1375 8 : return GDALExtendedDataType::Create(GDT_Unknown);
1376 : }
1377 :
1378 719 : static void SetGDALOffset(const GDALExtendedDataType &dt,
1379 : const size_t nBaseOffset, std::vector<DtypeElt> &elts,
1380 : size_t &iCurElt)
1381 : {
1382 719 : if (dt.GetClass() == GEDTC_COMPOUND)
1383 : {
1384 9 : const auto &comps = dt.GetComponents();
1385 29 : for (const auto &comp : comps)
1386 : {
1387 20 : const size_t nBaseOffsetSub = nBaseOffset + comp->GetOffset();
1388 20 : SetGDALOffset(comp->GetType(), nBaseOffsetSub, elts, iCurElt);
1389 : }
1390 : }
1391 : else
1392 : {
1393 710 : elts[iCurElt].gdalOffset = nBaseOffset;
1394 710 : iCurElt++;
1395 : }
1396 719 : }
1397 :
1398 : /************************************************************************/
1399 : /* ZarrV2Group::LoadArray() */
1400 : /************************************************************************/
1401 :
1402 : std::shared_ptr<ZarrArray>
1403 725 : ZarrV2Group::LoadArray(const std::string &osArrayName,
1404 : const std::string &osZarrayFilename,
1405 : const CPLJSONObject &oRoot, bool bLoadedFromZMetadata,
1406 : const CPLJSONObject &oAttributesIn) const
1407 : {
1408 : // Add osZarrayFilename to m_poSharedResource during the scope
1409 : // of this function call.
1410 725 : ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
1411 1450 : osZarrayFilename);
1412 725 : if (!filenameAdder.ok())
1413 2 : return nullptr;
1414 :
1415 2169 : const auto osFormat = oRoot["zarr_format"].ToString();
1416 723 : if (osFormat != "2")
1417 : {
1418 3 : CPLError(CE_Failure, CPLE_NotSupported,
1419 : "Invalid value for zarr_format: %s", osFormat.c_str());
1420 3 : return nullptr;
1421 : }
1422 :
1423 720 : bool bFortranOrder = false;
1424 720 : const char *orderKey = "order";
1425 2160 : const auto osOrder = oRoot[orderKey].ToString();
1426 720 : if (osOrder == "C")
1427 : {
1428 : // ok
1429 : }
1430 34 : else if (osOrder == "F")
1431 : {
1432 31 : bFortranOrder = true;
1433 : }
1434 : else
1435 : {
1436 3 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid value for %s",
1437 : orderKey);
1438 3 : return nullptr;
1439 : }
1440 :
1441 2151 : const auto oShape = oRoot["shape"].ToArray();
1442 717 : if (!oShape.IsValid())
1443 : {
1444 3 : CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
1445 3 : return nullptr;
1446 : }
1447 :
1448 714 : const char *chunksKey = "chunks";
1449 2142 : const auto oChunks = oRoot[chunksKey].ToArray();
1450 714 : if (!oChunks.IsValid())
1451 : {
1452 3 : CPLError(CE_Failure, CPLE_AppDefined, "%s missing or not an array",
1453 : chunksKey);
1454 3 : return nullptr;
1455 : }
1456 :
1457 711 : if (oShape.Size() != oChunks.Size())
1458 : {
1459 2 : CPLError(CE_Failure, CPLE_AppDefined,
1460 : "shape and chunks arrays are of different size");
1461 2 : return nullptr;
1462 : }
1463 :
1464 1418 : CPLJSONObject oAttributes(oAttributesIn);
1465 709 : if (!bLoadedFromZMetadata)
1466 : {
1467 804 : CPLJSONDocument oDoc;
1468 : const std::string osZattrsFilename(CPLFormFilenameSafe(
1469 402 : CPLGetDirnameSafe(osZarrayFilename.c_str()).c_str(), ".zattrs",
1470 804 : nullptr));
1471 804 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
1472 402 : if (oDoc.Load(osZattrsFilename))
1473 : {
1474 153 : oAttributes = oDoc.GetRoot();
1475 : }
1476 : }
1477 :
1478 : // Deep-clone of oAttributes
1479 : {
1480 709 : CPLJSONDocument oTmpDoc;
1481 709 : oTmpDoc.SetRoot(oAttributes);
1482 709 : CPL_IGNORE_RET_VAL(oTmpDoc.LoadMemory(oTmpDoc.SaveAsString()));
1483 709 : oAttributes = oTmpDoc.GetRoot();
1484 : }
1485 :
1486 1418 : std::vector<std::shared_ptr<GDALDimension>> aoDims;
1487 1808 : for (int i = 0; i < oShape.Size(); ++i)
1488 : {
1489 1100 : const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
1490 1100 : if (nSize == 0)
1491 : {
1492 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
1493 1 : return nullptr;
1494 : }
1495 1099 : aoDims.emplace_back(std::make_shared<ZarrDimension>(
1496 1099 : m_poSharedResource,
1497 2198 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1498 2198 : std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
1499 1099 : nSize));
1500 : }
1501 :
1502 : // XArray extension
1503 2124 : const auto arrayDimensionsObj = oAttributes["_ARRAY_DIMENSIONS"];
1504 :
1505 : const auto FindDimension =
1506 527 : [this, &aoDims, bLoadedFromZMetadata, &osArrayName,
1507 : &oAttributes](const std::string &osDimName,
1508 5971 : std::shared_ptr<GDALDimension> &poDim, int i)
1509 : {
1510 527 : auto oIter = m_oMapDimensions.find(osDimName);
1511 527 : if (oIter != m_oMapDimensions.end())
1512 : {
1513 266 : if (m_bDimSizeInUpdate ||
1514 132 : oIter->second->GetSize() == poDim->GetSize())
1515 : {
1516 134 : poDim = oIter->second;
1517 134 : return true;
1518 : }
1519 : else
1520 : {
1521 0 : CPLError(CE_Warning, CPLE_AppDefined,
1522 : "Size of _ARRAY_DIMENSIONS[%d] different "
1523 : "from the one of shape",
1524 : i);
1525 0 : return false;
1526 : }
1527 : }
1528 :
1529 : // Try to load the indexing variable.
1530 :
1531 : // If loading from zmetadata, we should have normally
1532 : // already loaded the dimension variables, unless they
1533 : // are in a upper level.
1534 576 : if (bLoadedFromZMetadata && osArrayName != osDimName &&
1535 576 : m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
1536 : {
1537 183 : auto poParent = m_poParent.lock();
1538 199 : while (poParent != nullptr)
1539 : {
1540 22 : oIter = poParent->m_oMapDimensions.find(osDimName);
1541 28 : if (oIter != poParent->m_oMapDimensions.end() &&
1542 6 : oIter->second->GetSize() == poDim->GetSize())
1543 : {
1544 6 : poDim = oIter->second;
1545 6 : return true;
1546 : }
1547 16 : poParent = poParent->m_poParent.lock();
1548 : }
1549 : }
1550 :
1551 : // Not loading from zmetadata, and not in m_oMapMDArrays,
1552 : // then stat() the indexing variable.
1553 284 : else if (!bLoadedFromZMetadata && osArrayName != osDimName &&
1554 284 : m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
1555 : {
1556 74 : std::string osDirName = m_osDirectoryName;
1557 : while (true)
1558 : {
1559 162 : if (CPLHasPathTraversal(osDimName.c_str()))
1560 : {
1561 0 : CPLError(CE_Failure, CPLE_AppDefined,
1562 : "Path traversal detected in %s",
1563 : osDimName.c_str());
1564 0 : return false;
1565 : }
1566 : const std::string osArrayFilenameDim = CPLFormFilenameSafe(
1567 162 : CPLFormFilenameSafe(osDirName.c_str(), osDimName.c_str(),
1568 : nullptr)
1569 : .c_str(),
1570 162 : ".zarray", nullptr);
1571 : VSIStatBufL sStat;
1572 162 : if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
1573 : {
1574 100 : CPLJSONDocument oDoc;
1575 50 : if (oDoc.Load(osArrayFilenameDim))
1576 : {
1577 50 : LoadArray(osDimName, osArrayFilenameDim, oDoc.GetRoot(),
1578 100 : false, CPLJSONObject());
1579 : }
1580 : }
1581 : else
1582 : {
1583 112 : if ((cpl::starts_with(osDirName, JSON_REF_FS_PREFIX) ||
1584 115 : cpl::starts_with(osDirName, PARQUET_REF_FS_PREFIX)) &&
1585 3 : osDirName.back() == '}')
1586 : {
1587 3 : break;
1588 : }
1589 :
1590 : // Recurse to upper level for datasets such as
1591 : // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
1592 : std::string osDirNameNew =
1593 109 : CPLGetPathSafe(osDirName.c_str());
1594 109 : if (!osDirNameNew.empty() && osDirNameNew != osDirName)
1595 : {
1596 88 : osDirName = std::move(osDirNameNew);
1597 88 : continue;
1598 : }
1599 : }
1600 71 : break;
1601 88 : }
1602 : }
1603 :
1604 387 : oIter = m_oMapDimensions.find(osDimName);
1605 403 : if (oIter != m_oMapDimensions.end() &&
1606 16 : oIter->second->GetSize() == poDim->GetSize())
1607 : {
1608 16 : poDim = oIter->second;
1609 16 : return true;
1610 : }
1611 :
1612 742 : std::string osType;
1613 742 : std::string osDirection;
1614 371 : if (aoDims.size() == 1 && osArrayName == osDimName)
1615 : {
1616 136 : ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
1617 : osDirection);
1618 : }
1619 :
1620 : auto poDimLocal = std::make_shared<ZarrDimension>(
1621 371 : m_poSharedResource,
1622 742 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1623 742 : GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
1624 371 : poDimLocal->SetXArrayDimension();
1625 371 : m_oMapDimensions[osDimName] = poDimLocal;
1626 371 : poDim = poDimLocal;
1627 371 : return true;
1628 708 : };
1629 :
1630 708 : if (arrayDimensionsObj.GetType() == CPLJSONObject::Type::Array)
1631 : {
1632 710 : const auto arrayDims = arrayDimensionsObj.ToArray();
1633 355 : if (arrayDims.Size() == oShape.Size())
1634 : {
1635 355 : bool ok = true;
1636 882 : for (int i = 0; i < oShape.Size(); ++i)
1637 : {
1638 527 : if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
1639 : {
1640 1054 : const auto osDimName = arrayDims[i].ToString();
1641 527 : ok &= FindDimension(osDimName, aoDims[i], i);
1642 : }
1643 : }
1644 355 : if (ok)
1645 : {
1646 355 : oAttributes.Delete("_ARRAY_DIMENSIONS");
1647 : }
1648 : }
1649 : else
1650 : {
1651 0 : CPLError(
1652 : CE_Warning, CPLE_AppDefined,
1653 : "Size of _ARRAY_DIMENSIONS different from the one of shape");
1654 : }
1655 : }
1656 :
1657 : // _NCZARR_ARRAY extension
1658 2124 : const auto nczarrArrayDimrefs = oRoot["_NCZARR_ARRAY"]["dimrefs"].ToArray();
1659 708 : if (nczarrArrayDimrefs.IsValid())
1660 : {
1661 42 : const auto arrayDims = nczarrArrayDimrefs.ToArray();
1662 21 : if (arrayDims.Size() == oShape.Size())
1663 : {
1664 : auto poRG =
1665 42 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
1666 21 : CPLAssert(poRG != nullptr);
1667 : while (true)
1668 : {
1669 48 : auto poNewRG = poRG->m_poParent.lock();
1670 48 : if (poNewRG == nullptr)
1671 21 : break;
1672 27 : poRG = std::move(poNewRG);
1673 27 : }
1674 :
1675 49 : for (int i = 0; i < oShape.Size(); ++i)
1676 : {
1677 28 : if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
1678 : {
1679 84 : const auto osDimFullpath = arrayDims[i].ToString();
1680 : const std::string osArrayFullname =
1681 56 : (GetFullName() != "/" ? GetFullName() : std::string()) +
1682 56 : '/' + osArrayName;
1683 48 : if (aoDims.size() == 1 &&
1684 20 : (osDimFullpath == osArrayFullname ||
1685 34 : osDimFullpath == "/" + osArrayFullname))
1686 : {
1687 : // If this is an indexing variable, then fetch the
1688 : // dimension type and direction, and patch the dimension
1689 28 : std::string osType;
1690 28 : std::string osDirection;
1691 14 : ZarrArray::GetDimensionTypeDirection(
1692 : oAttributes, osType, osDirection);
1693 :
1694 : auto poDimLocal = std::make_shared<ZarrDimension>(
1695 14 : m_poSharedResource,
1696 28 : std::dynamic_pointer_cast<ZarrGroupBase>(
1697 14 : m_pSelf.lock()),
1698 14 : GetFullName(), osArrayName, osType, osDirection,
1699 28 : aoDims[i]->GetSize());
1700 14 : aoDims[i] = poDimLocal;
1701 :
1702 14 : m_oMapDimensions[osArrayName] = std::move(poDimLocal);
1703 : }
1704 14 : else if (auto poDim =
1705 28 : poRG->OpenDimensionFromFullname(osDimFullpath))
1706 : {
1707 13 : if (poDim->GetSize() != aoDims[i]->GetSize())
1708 : {
1709 1 : CPLError(CE_Failure, CPLE_AppDefined,
1710 : "Inconsistency in size between NCZarr "
1711 : "dimension %s and regular dimension",
1712 : osDimFullpath.c_str());
1713 : }
1714 : else
1715 : {
1716 12 : aoDims[i] = std::move(poDim);
1717 : }
1718 : }
1719 : else
1720 : {
1721 1 : CPLError(CE_Failure, CPLE_AppDefined,
1722 : "Cannot find NCZarr dimension %s",
1723 : osDimFullpath.c_str());
1724 : }
1725 : }
1726 : }
1727 : }
1728 : else
1729 : {
1730 0 : CPLError(CE_Warning, CPLE_AppDefined,
1731 : "Size of _NCZARR_ARRAY.dimrefs different from the one of "
1732 : "shape");
1733 : }
1734 : }
1735 :
1736 708 : constexpr const char *dtypeKey = "dtype";
1737 2124 : auto oDtype = oRoot[dtypeKey];
1738 708 : if (!oDtype.IsValid())
1739 : {
1740 1 : CPLError(CE_Failure, CPLE_NotSupported, "%s missing", dtypeKey);
1741 1 : return nullptr;
1742 : }
1743 1414 : std::vector<DtypeElt> aoDtypeElts;
1744 1414 : const auto oType = ParseDtype(oDtype, aoDtypeElts);
1745 1395 : if (oType.GetClass() == GEDTC_NUMERIC &&
1746 688 : oType.GetNumericDataType() == GDT_Unknown)
1747 8 : return nullptr;
1748 699 : size_t iCurElt = 0;
1749 699 : SetGDALOffset(oType, 0, aoDtypeElts, iCurElt);
1750 :
1751 1398 : std::vector<GUInt64> anBlockSize;
1752 699 : if (!ZarrArray::ParseChunkSize(oChunks, oType, anBlockSize))
1753 2 : return nullptr;
1754 :
1755 2091 : std::string osDimSeparator = oRoot["dimension_separator"].ToString();
1756 697 : if (osDimSeparator.empty())
1757 686 : osDimSeparator = ".";
1758 :
1759 1394 : std::vector<GByte> abyNoData;
1760 :
1761 : struct NoDataFreer
1762 : {
1763 : std::vector<GByte> &m_abyNodata;
1764 : const GDALExtendedDataType &m_oType;
1765 :
1766 697 : NoDataFreer(std::vector<GByte> &abyNoDataIn,
1767 : const GDALExtendedDataType &oTypeIn)
1768 697 : : m_abyNodata(abyNoDataIn), m_oType(oTypeIn)
1769 : {
1770 697 : }
1771 :
1772 697 : ~NoDataFreer()
1773 697 : {
1774 697 : if (!m_abyNodata.empty())
1775 132 : m_oType.FreeDynamicMemory(&m_abyNodata[0]);
1776 697 : }
1777 : };
1778 :
1779 1394 : NoDataFreer NoDataFreer(abyNoData, oType);
1780 :
1781 2091 : auto oFillValue = oRoot["fill_value"];
1782 697 : auto eFillValueType = oFillValue.GetType();
1783 :
1784 : // Normally arrays are not supported, but that's what NCZarr 4.8.0 outputs
1785 698 : if (eFillValueType == CPLJSONObject::Type::Array &&
1786 698 : oFillValue.ToArray().Size() == 1)
1787 : {
1788 0 : oFillValue = oFillValue.ToArray()[0];
1789 0 : eFillValueType = oFillValue.GetType();
1790 : }
1791 :
1792 697 : if (!oFillValue.IsValid())
1793 : {
1794 : // fill_value is normally required but some implementations
1795 : // are lacking it: https://github.com/Unidata/netcdf-c/issues/2059
1796 1 : CPLError(CE_Warning, CPLE_AppDefined, "fill_value missing");
1797 : }
1798 696 : else if (eFillValueType == CPLJSONObject::Type::Null)
1799 : {
1800 : // Nothing to do
1801 : }
1802 141 : else if (eFillValueType == CPLJSONObject::Type::String)
1803 : {
1804 100 : const auto osFillValue = oFillValue.ToString();
1805 87 : if (oType.GetClass() == GEDTC_NUMERIC &&
1806 37 : CPLGetValueType(osFillValue.c_str()) != CPL_VALUE_STRING)
1807 : {
1808 8 : abyNoData.resize(oType.GetSize());
1809 : // Be tolerant with numeric values serialized as strings.
1810 8 : if (oType.GetNumericDataType() == GDT_Int64)
1811 : {
1812 : const int64_t nVal = static_cast<int64_t>(
1813 2 : std::strtoll(osFillValue.c_str(), nullptr, 10));
1814 2 : GDALCopyWords(&nVal, GDT_Int64, 0, &abyNoData[0],
1815 : oType.GetNumericDataType(), 0, 1);
1816 : }
1817 6 : else if (oType.GetNumericDataType() == GDT_UInt64)
1818 : {
1819 : const uint64_t nVal = static_cast<uint64_t>(
1820 2 : std::strtoull(osFillValue.c_str(), nullptr, 10));
1821 2 : GDALCopyWords(&nVal, GDT_UInt64, 0, &abyNoData[0],
1822 : oType.GetNumericDataType(), 0, 1);
1823 : }
1824 : else
1825 : {
1826 4 : const double dfNoDataValue = CPLAtof(osFillValue.c_str());
1827 4 : GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
1828 : oType.GetNumericDataType(), 0, 1);
1829 : }
1830 : }
1831 42 : else if (oType.GetClass() == GEDTC_NUMERIC)
1832 : {
1833 : double dfNoDataValue;
1834 29 : if (osFillValue == "NaN")
1835 : {
1836 10 : dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
1837 : }
1838 19 : else if (osFillValue == "Infinity")
1839 : {
1840 9 : dfNoDataValue = std::numeric_limits<double>::infinity();
1841 : }
1842 10 : else if (osFillValue == "-Infinity")
1843 : {
1844 9 : dfNoDataValue = -std::numeric_limits<double>::infinity();
1845 : }
1846 : else
1847 : {
1848 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1849 2 : return nullptr;
1850 : }
1851 28 : if (oType.GetNumericDataType() == GDT_Float16)
1852 : {
1853 : const GFloat16 hfNoDataValue =
1854 0 : static_cast<GFloat16>(dfNoDataValue);
1855 0 : abyNoData.resize(sizeof(hfNoDataValue));
1856 0 : memcpy(&abyNoData[0], &hfNoDataValue, sizeof(hfNoDataValue));
1857 : }
1858 28 : if (oType.GetNumericDataType() == GDT_Float32)
1859 : {
1860 12 : const float fNoDataValue = static_cast<float>(dfNoDataValue);
1861 12 : abyNoData.resize(sizeof(fNoDataValue));
1862 12 : memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
1863 : }
1864 16 : else if (oType.GetNumericDataType() == GDT_Float64)
1865 : {
1866 15 : abyNoData.resize(sizeof(dfNoDataValue));
1867 15 : memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
1868 : }
1869 : else
1870 : {
1871 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1872 1 : return nullptr;
1873 : }
1874 : }
1875 13 : else if (oType.GetClass() == GEDTC_STRING)
1876 : {
1877 : // zarr.open('unicode_be.zarr', mode = 'w', shape=(1,), dtype =
1878 : // '>U1', compressor = None) oddly generates "fill_value": "0"
1879 8 : if (osFillValue != "0")
1880 : {
1881 3 : std::vector<GByte> abyNativeFillValue(osFillValue.size() + 1);
1882 3 : memcpy(&abyNativeFillValue[0], osFillValue.data(),
1883 : osFillValue.size());
1884 3 : int nBytes = CPLBase64DecodeInPlace(&abyNativeFillValue[0]);
1885 3 : abyNativeFillValue.resize(nBytes + 1);
1886 3 : abyNativeFillValue[nBytes] = 0;
1887 3 : abyNoData.resize(oType.GetSize());
1888 3 : char *pDstStr = CPLStrdup(
1889 3 : reinterpret_cast<const char *>(&abyNativeFillValue[0]));
1890 3 : char **pDstPtr = reinterpret_cast<char **>(&abyNoData[0]);
1891 3 : memcpy(pDstPtr, &pDstStr, sizeof(pDstStr));
1892 : }
1893 : }
1894 : else
1895 : {
1896 5 : std::vector<GByte> abyNativeFillValue(osFillValue.size() + 1);
1897 5 : memcpy(&abyNativeFillValue[0], osFillValue.data(),
1898 : osFillValue.size());
1899 5 : int nBytes = CPLBase64DecodeInPlace(&abyNativeFillValue[0]);
1900 5 : abyNativeFillValue.resize(nBytes);
1901 5 : if (abyNativeFillValue.size() !=
1902 5 : aoDtypeElts.back().nativeOffset + aoDtypeElts.back().nativeSize)
1903 : {
1904 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1905 0 : return nullptr;
1906 : }
1907 5 : abyNoData.resize(oType.GetSize());
1908 5 : ZarrArray::DecodeSourceElt(aoDtypeElts, abyNativeFillValue.data(),
1909 5 : &abyNoData[0]);
1910 : }
1911 : }
1912 91 : else if (eFillValueType == CPLJSONObject::Type::Boolean ||
1913 21 : eFillValueType == CPLJSONObject::Type::Integer ||
1914 13 : eFillValueType == CPLJSONObject::Type::Long ||
1915 : eFillValueType == CPLJSONObject::Type::Double)
1916 : {
1917 90 : if (oType.GetClass() == GEDTC_NUMERIC)
1918 : {
1919 89 : const double dfNoDataValue = oFillValue.ToDouble();
1920 89 : if (oType.GetNumericDataType() == GDT_Int64)
1921 : {
1922 : const int64_t nNoDataValue =
1923 2 : static_cast<int64_t>(oFillValue.ToLong());
1924 2 : abyNoData.resize(oType.GetSize());
1925 2 : GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1926 : oType.GetNumericDataType(), 0, 1);
1927 : }
1928 90 : else if (oType.GetNumericDataType() == GDT_UInt64 &&
1929 : /* we can't really deal with nodata value between */
1930 : /* int64::max and uint64::max due to json-c limitations */
1931 3 : dfNoDataValue >= 0)
1932 : {
1933 : const int64_t nNoDataValue =
1934 3 : static_cast<int64_t>(oFillValue.ToLong());
1935 3 : abyNoData.resize(oType.GetSize());
1936 3 : GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1937 : oType.GetNumericDataType(), 0, 1);
1938 : }
1939 : else
1940 : {
1941 84 : abyNoData.resize(oType.GetSize());
1942 84 : GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
1943 : oType.GetNumericDataType(), 0, 1);
1944 : }
1945 : }
1946 : else
1947 : {
1948 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1949 1 : return nullptr;
1950 89 : }
1951 : }
1952 : else
1953 : {
1954 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1955 1 : return nullptr;
1956 : }
1957 :
1958 693 : const CPLCompressor *psCompressor = nullptr;
1959 693 : const CPLCompressor *psDecompressor = nullptr;
1960 2079 : const auto oCompressor = oRoot["compressor"];
1961 1386 : std::string osDecompressorId("NONE");
1962 :
1963 693 : if (!oCompressor.IsValid())
1964 : {
1965 1 : CPLError(CE_Failure, CPLE_AppDefined, "compressor missing");
1966 1 : return nullptr;
1967 : }
1968 692 : if (oCompressor.GetType() == CPLJSONObject::Type::Null)
1969 : {
1970 : // nothing to do
1971 : }
1972 38 : else if (oCompressor.GetType() == CPLJSONObject::Type::Object)
1973 : {
1974 37 : osDecompressorId = oCompressor["id"].ToString();
1975 37 : if (osDecompressorId.empty())
1976 : {
1977 1 : CPLError(CE_Failure, CPLE_AppDefined, "Missing compressor id");
1978 1 : return nullptr;
1979 : }
1980 36 : if (osDecompressorId == "imagecodecs_tiff")
1981 : {
1982 5 : psDecompressor = ZarrGetTIFFDecompressor();
1983 : }
1984 : else
1985 : {
1986 31 : psCompressor = CPLGetCompressor(osDecompressorId.c_str());
1987 31 : psDecompressor = CPLGetDecompressor(osDecompressorId.c_str());
1988 31 : if (psCompressor == nullptr || psDecompressor == nullptr)
1989 : {
1990 1 : CPLError(CE_Failure, CPLE_AppDefined,
1991 : "Decompressor %s not handled",
1992 : osDecompressorId.c_str());
1993 1 : return nullptr;
1994 : }
1995 : }
1996 : }
1997 : else
1998 : {
1999 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid compressor");
2000 1 : return nullptr;
2001 : }
2002 :
2003 1378 : CPLJSONArray oFiltersArray;
2004 2067 : const auto oFilters = oRoot["filters"];
2005 689 : if (!oFilters.IsValid())
2006 : {
2007 1 : CPLError(CE_Failure, CPLE_AppDefined, "filters missing");
2008 1 : return nullptr;
2009 : }
2010 688 : if (oFilters.GetType() == CPLJSONObject::Type::Null)
2011 : {
2012 : }
2013 47 : else if (oFilters.GetType() == CPLJSONObject::Type::Array)
2014 : {
2015 45 : oFiltersArray = oFilters.ToArray();
2016 59 : for (const auto &oFilter : oFiltersArray)
2017 : {
2018 30 : const auto osFilterId = oFilter["id"].ToString();
2019 15 : if (osFilterId.empty())
2020 : {
2021 1 : CPLError(CE_Failure, CPLE_AppDefined, "Missing filter id");
2022 1 : return nullptr;
2023 : }
2024 14 : if (!EQUAL(osFilterId.c_str(), "shuffle") &&
2025 23 : !EQUAL(osFilterId.c_str(), "quantize") &&
2026 9 : !EQUAL(osFilterId.c_str(), "fixedscaleoffset"))
2027 : {
2028 : const auto psFilterCompressor =
2029 4 : CPLGetCompressor(osFilterId.c_str());
2030 : const auto psFilterDecompressor =
2031 4 : CPLGetDecompressor(osFilterId.c_str());
2032 4 : if (psFilterCompressor == nullptr ||
2033 : psFilterDecompressor == nullptr)
2034 : {
2035 0 : CPLError(CE_Failure, CPLE_AppDefined,
2036 : "Filter %s not handled", osFilterId.c_str());
2037 0 : return nullptr;
2038 : }
2039 : }
2040 : }
2041 : }
2042 : else
2043 : {
2044 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid filters");
2045 2 : return nullptr;
2046 : }
2047 :
2048 : auto poArray =
2049 0 : ZarrV2Array::Create(m_poSharedResource, Self(), osArrayName, aoDims,
2050 1370 : oType, aoDtypeElts, anBlockSize, bFortranOrder);
2051 685 : if (!poArray)
2052 1 : return nullptr;
2053 684 : poArray->SetCompressorJson(oCompressor);
2054 684 : poArray->SetUpdatable(m_bUpdatable); // must be set before SetAttributes()
2055 684 : poArray->SetFilename(osZarrayFilename);
2056 684 : poArray->SetDimSeparator(osDimSeparator);
2057 684 : poArray->SetCompressorDecompressor(osDecompressorId, psCompressor,
2058 : psDecompressor);
2059 684 : poArray->SetFilters(oFiltersArray);
2060 684 : if (!abyNoData.empty())
2061 : {
2062 132 : poArray->RegisterNoDataValue(abyNoData.data());
2063 : }
2064 :
2065 2052 : const auto gridMapping = oAttributes["grid_mapping"];
2066 684 : if (gridMapping.GetType() == CPLJSONObject::Type::String)
2067 : {
2068 2 : const std::string gridMappingName = gridMapping.ToString();
2069 1 : if (m_oMapMDArrays.find(gridMappingName) == m_oMapMDArrays.end())
2070 : {
2071 1 : if (CPLHasPathTraversal(gridMappingName.c_str()))
2072 : {
2073 0 : CPLError(CE_Failure, CPLE_AppDefined,
2074 : "Path traversal detected in %s",
2075 : gridMappingName.c_str());
2076 0 : return nullptr;
2077 : }
2078 : const std::string osArrayFilenameDim = CPLFormFilenameSafe(
2079 1 : CPLFormFilenameSafe(m_osDirectoryName.c_str(),
2080 : gridMappingName.c_str(), nullptr)
2081 : .c_str(),
2082 2 : ".zarray", nullptr);
2083 : VSIStatBufL sStat;
2084 1 : if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
2085 : {
2086 2 : CPLJSONDocument oDoc;
2087 1 : if (oDoc.Load(osArrayFilenameDim))
2088 : {
2089 1 : LoadArray(gridMappingName, osArrayFilenameDim,
2090 2 : oDoc.GetRoot(), false, CPLJSONObject());
2091 : }
2092 : }
2093 : }
2094 : }
2095 :
2096 684 : poArray->SetAttributes(Self(), oAttributes);
2097 684 : poArray->SetDtype(oDtype);
2098 684 : RegisterArray(poArray);
2099 :
2100 : // If this is an indexing variable, attach it to the dimension.
2101 684 : if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
2102 : {
2103 150 : auto oIter = m_oMapDimensions.find(poArray->GetName());
2104 150 : if (oIter != m_oMapDimensions.end())
2105 : {
2106 150 : oIter->second->SetIndexingVariable(poArray);
2107 : }
2108 : }
2109 :
2110 684 : if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
2111 : "CACHE_TILE_PRESENCE", "NO")))
2112 : {
2113 2 : poArray->BlockCachePresence();
2114 : }
2115 :
2116 684 : return poArray;
2117 : }
2118 :
2119 : /************************************************************************/
2120 : /* ZarrV2Array::SetCompressorJson() */
2121 : /************************************************************************/
2122 :
2123 701 : void ZarrV2Array::SetCompressorJson(const CPLJSONObject &oCompressor)
2124 : {
2125 701 : m_oCompressorJSon = oCompressor;
2126 701 : if (oCompressor.GetType() != CPLJSONObject::Type::Null)
2127 : m_aosStructuralInfo.SetNameValue("COMPRESSOR",
2128 52 : oCompressor.ToString().c_str());
2129 701 : }
2130 :
2131 : /************************************************************************/
2132 : /* ZarrV2Array::SetFilters() */
2133 : /************************************************************************/
2134 :
2135 996 : void ZarrV2Array::SetFilters(const CPLJSONArray &oFiltersArray)
2136 : {
2137 996 : m_oFiltersArray = oFiltersArray;
2138 996 : if (oFiltersArray.Size() > 0)
2139 : m_aosStructuralInfo.SetNameValue("FILTERS",
2140 15 : oFiltersArray.ToString().c_str());
2141 996 : }
2142 :
2143 : /************************************************************************/
2144 : /* ZarrV2Array::GetRawBlockInfoInfo() */
2145 : /************************************************************************/
2146 :
2147 6 : CPLStringList ZarrV2Array::GetRawBlockInfoInfo() const
2148 : {
2149 6 : CPLStringList aosInfo(m_aosStructuralInfo);
2150 12 : if (!m_aoDtypeElts.empty() && m_aoDtypeElts[0].nativeSize > 1 &&
2151 15 : m_aoDtypeElts[0].nativeType != DtypeElt::NativeType::STRING_ASCII &&
2152 3 : m_aoDtypeElts[0].nativeType != DtypeElt::NativeType::STRING_UNICODE)
2153 : {
2154 3 : if (m_aoDtypeElts[0].needByteSwapping ^ CPL_IS_LSB)
2155 2 : aosInfo.SetNameValue("ENDIANNESS", "LITTLE");
2156 : else
2157 1 : aosInfo.SetNameValue("ENDIANNESS", "BIG");
2158 : }
2159 6 : if (m_bFortranOrder)
2160 : {
2161 1 : const int nDims = static_cast<int>(m_aoDims.size());
2162 1 : if (nDims > 1)
2163 : {
2164 2 : std::string osOrder("[");
2165 3 : for (int i = 0; i < nDims; ++i)
2166 : {
2167 2 : if (i > 0)
2168 1 : osOrder += ',';
2169 2 : osOrder += std::to_string(nDims - 1 - i);
2170 : }
2171 1 : osOrder += ']';
2172 1 : aosInfo.SetNameValue("TRANSPOSE_ORDER", osOrder.c_str());
2173 : }
2174 : }
2175 6 : return aosInfo;
2176 : }
|