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