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