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