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_vsi_virtual.h"
14 : #include "gdal_thread_pool.h"
15 : #include "zarr.h"
16 :
17 : #include <algorithm>
18 : #include <cassert>
19 : #include <cmath>
20 : #include <cstdlib>
21 : #include <limits>
22 : #include <map>
23 : #include <set>
24 :
25 : /************************************************************************/
26 : /* ZarrV3Array::ZarrV3Array() */
27 : /************************************************************************/
28 :
29 270 : ZarrV3Array::ZarrV3Array(
30 : const std::shared_ptr<ZarrSharedResource> &poSharedResource,
31 : const std::string &osParentName, const std::string &osName,
32 : const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
33 : const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
34 270 : const std::vector<GUInt64> &anBlockSize)
35 : : GDALAbstractMDArray(osParentName, osName),
36 : ZarrArray(poSharedResource, osParentName, osName, aoDims, oType,
37 270 : aoDtypeElts, anBlockSize)
38 : {
39 270 : }
40 :
41 : /************************************************************************/
42 : /* ZarrV3Array::Create() */
43 : /************************************************************************/
44 :
45 : std::shared_ptr<ZarrV3Array>
46 270 : ZarrV3Array::Create(const std::shared_ptr<ZarrSharedResource> &poSharedResource,
47 : const std::string &osParentName, const std::string &osName,
48 : const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
49 : const GDALExtendedDataType &oType,
50 : const std::vector<DtypeElt> &aoDtypeElts,
51 : const std::vector<GUInt64> &anBlockSize)
52 : {
53 : auto arr = std::shared_ptr<ZarrV3Array>(
54 : new ZarrV3Array(poSharedResource, osParentName, osName, aoDims, oType,
55 540 : aoDtypeElts, anBlockSize));
56 270 : if (arr->m_nTotalTileCount == 0)
57 1 : return nullptr;
58 269 : arr->SetSelf(arr);
59 :
60 269 : return arr;
61 : }
62 :
63 : /************************************************************************/
64 : /* ~ZarrV3Array() */
65 : /************************************************************************/
66 :
67 540 : ZarrV3Array::~ZarrV3Array()
68 : {
69 270 : ZarrV3Array::Flush();
70 540 : }
71 :
72 : /************************************************************************/
73 : /* Flush() */
74 : /************************************************************************/
75 :
76 656 : void ZarrV3Array::Flush()
77 : {
78 656 : if (!m_bValid)
79 3 : return;
80 :
81 653 : ZarrV3Array::FlushDirtyTile();
82 :
83 653 : if (!m_aoDims.empty())
84 : {
85 1357 : for (const auto &poDim : m_aoDims)
86 : {
87 : const auto poZarrDim =
88 831 : dynamic_cast<const ZarrDimension *>(poDim.get());
89 831 : if (poZarrDim && poZarrDim->IsXArrayDimension())
90 : {
91 774 : if (poZarrDim->IsModified())
92 4 : m_bDefinitionModified = true;
93 : }
94 : else
95 : {
96 57 : break;
97 : }
98 : }
99 : }
100 :
101 1306 : CPLJSONObject oAttrs;
102 1268 : if (m_oAttrGroup.IsModified() || m_bUnitModified || m_bOffsetModified ||
103 1268 : m_bScaleModified || m_bSRSModified)
104 : {
105 38 : m_bNew = false;
106 :
107 38 : oAttrs = SerializeSpecialAttributes();
108 :
109 38 : m_bDefinitionModified = true;
110 : }
111 :
112 653 : if (m_bDefinitionModified)
113 : {
114 168 : Serialize(oAttrs);
115 168 : m_bDefinitionModified = false;
116 : }
117 : }
118 :
119 : /************************************************************************/
120 : /* ZarrV3Array::Serialize() */
121 : /************************************************************************/
122 :
123 168 : void ZarrV3Array::Serialize(const CPLJSONObject &oAttrs)
124 : {
125 336 : CPLJSONDocument oDoc;
126 336 : CPLJSONObject oRoot = oDoc.GetRoot();
127 :
128 168 : oRoot.Add("zarr_format", 3);
129 168 : oRoot.Add("node_type", "array");
130 :
131 336 : CPLJSONArray oShape;
132 373 : for (const auto &poDim : m_aoDims)
133 : {
134 205 : oShape.Add(static_cast<GInt64>(poDim->GetSize()));
135 : }
136 168 : oRoot.Add("shape", oShape);
137 :
138 168 : oRoot.Add("data_type", m_dtype.ToString());
139 :
140 : {
141 336 : CPLJSONObject oChunkGrid;
142 168 : oRoot.Add("chunk_grid", oChunkGrid);
143 168 : oChunkGrid.Add("name", "regular");
144 336 : CPLJSONObject oConfiguration;
145 168 : oChunkGrid.Add("configuration", oConfiguration);
146 168 : CPLJSONArray oChunks;
147 373 : for (const auto nBlockSize : m_anBlockSize)
148 : {
149 205 : oChunks.Add(static_cast<GInt64>(nBlockSize));
150 : }
151 168 : oConfiguration.Add("chunk_shape", oChunks);
152 : }
153 :
154 : {
155 336 : CPLJSONObject oChunkKeyEncoding;
156 168 : oRoot.Add("chunk_key_encoding", oChunkKeyEncoding);
157 168 : oChunkKeyEncoding.Add("name", m_bV2ChunkKeyEncoding ? "v2" : "default");
158 168 : CPLJSONObject oConfiguration;
159 168 : oChunkKeyEncoding.Add("configuration", oConfiguration);
160 168 : oConfiguration.Add("separator", m_osDimSeparator);
161 : }
162 :
163 168 : if (m_pabyNoData == nullptr)
164 : {
165 281 : if (m_oType.GetNumericDataType() == GDT_Float32 ||
166 137 : m_oType.GetNumericDataType() == GDT_Float64)
167 : {
168 18 : oRoot.Add("fill_value", "NaN");
169 : }
170 : else
171 : {
172 126 : oRoot.AddNull("fill_value");
173 : }
174 : }
175 : else
176 : {
177 40 : if (m_oType.GetNumericDataType() == GDT_CFloat32 ||
178 16 : m_oType.GetNumericDataType() == GDT_CFloat64)
179 : {
180 : double adfNoDataValue[2];
181 16 : GDALCopyWords(m_pabyNoData, m_oType.GetNumericDataType(), 0,
182 : adfNoDataValue, GDT_CFloat64, 0, 1);
183 16 : CPLJSONArray oArray;
184 48 : for (int i = 0; i < 2; ++i)
185 : {
186 32 : if (std::isnan(adfNoDataValue[i]))
187 6 : oArray.Add("NaN");
188 26 : else if (adfNoDataValue[i] ==
189 26 : std::numeric_limits<double>::infinity())
190 4 : oArray.Add("Infinity");
191 44 : else if (adfNoDataValue[i] ==
192 22 : -std::numeric_limits<double>::infinity())
193 4 : oArray.Add("-Infinity");
194 : else
195 18 : oArray.Add(adfNoDataValue[i]);
196 : }
197 16 : oRoot.Add("fill_value", oArray);
198 : }
199 : else
200 : {
201 8 : SerializeNumericNoData(oRoot);
202 : }
203 : }
204 :
205 168 : if (m_poCodecs)
206 : {
207 66 : oRoot.Add("codecs", m_poCodecs->GetJSon());
208 : }
209 :
210 168 : oRoot.Add("attributes", oAttrs);
211 :
212 : // Set dimension_names
213 168 : if (!m_aoDims.empty())
214 : {
215 288 : CPLJSONArray oDimensions;
216 333 : for (const auto &poDim : m_aoDims)
217 : {
218 : const auto poZarrDim =
219 205 : dynamic_cast<const ZarrDimension *>(poDim.get());
220 205 : if (poZarrDim && poZarrDim->IsXArrayDimension())
221 : {
222 189 : oDimensions.Add(poDim->GetName());
223 : }
224 : else
225 : {
226 16 : oDimensions = CPLJSONArray();
227 16 : break;
228 : }
229 : }
230 144 : if (oDimensions.Size() > 0)
231 : {
232 128 : oRoot.Add("dimension_names", oDimensions);
233 : }
234 : }
235 :
236 : // TODO: codecs
237 :
238 168 : oDoc.Save(m_osFilename);
239 168 : }
240 :
241 : /************************************************************************/
242 : /* ZarrV3Array::NeedDecodedBuffer() */
243 : /************************************************************************/
244 :
245 10961 : bool ZarrV3Array::NeedDecodedBuffer() const
246 : {
247 21910 : for (const auto &elt : m_aoDtypeElts)
248 : {
249 10957 : if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative)
250 : {
251 8 : return true;
252 : }
253 : }
254 10952 : return false;
255 : }
256 :
257 : /************************************************************************/
258 : /* ZarrV3Array::AllocateWorkingBuffers() */
259 : /************************************************************************/
260 :
261 153 : bool ZarrV3Array::AllocateWorkingBuffers() const
262 : {
263 153 : if (m_bAllocateWorkingBuffersDone)
264 5 : return m_bWorkingBuffersOK;
265 :
266 148 : m_bAllocateWorkingBuffersDone = true;
267 :
268 148 : size_t nSizeNeeded = m_nTileSize;
269 148 : if (NeedDecodedBuffer())
270 : {
271 0 : size_t nDecodedBufferSize = m_oType.GetSize();
272 0 : for (const auto &nBlockSize : m_anBlockSize)
273 : {
274 0 : if (nDecodedBufferSize > std::numeric_limits<size_t>::max() /
275 0 : static_cast<size_t>(nBlockSize))
276 : {
277 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
278 0 : return false;
279 : }
280 0 : nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
281 : }
282 0 : if (nSizeNeeded >
283 0 : std::numeric_limits<size_t>::max() - nDecodedBufferSize)
284 : {
285 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
286 0 : return false;
287 : }
288 0 : nSizeNeeded += nDecodedBufferSize;
289 : }
290 :
291 : // Reserve a buffer for tile content
292 148 : if (nSizeNeeded > 1024 * 1024 * 1024 &&
293 0 : !CPLTestBool(CPLGetConfigOption("ZARR_ALLOW_BIG_TILE_SIZE", "NO")))
294 : {
295 0 : CPLError(CE_Failure, CPLE_AppDefined,
296 : "Zarr tile allocation would require " CPL_FRMT_GUIB " bytes. "
297 : "By default the driver limits to 1 GB. To allow that memory "
298 : "allocation, set the ZARR_ALLOW_BIG_TILE_SIZE configuration "
299 : "option to YES.",
300 : static_cast<GUIntBig>(nSizeNeeded));
301 0 : return false;
302 : }
303 :
304 148 : m_bWorkingBuffersOK =
305 148 : AllocateWorkingBuffers(m_abyRawTileData, m_abyDecodedTileData);
306 148 : return m_bWorkingBuffersOK;
307 : }
308 :
309 10819 : bool ZarrV3Array::AllocateWorkingBuffers(
310 : ZarrByteVectorQuickResize &abyRawTileData,
311 : ZarrByteVectorQuickResize &abyDecodedTileData) const
312 : {
313 : // This method should NOT modify any ZarrArray member, as it is going to
314 : // be called concurrently from several threads.
315 :
316 : // Set those #define to avoid accidental use of some global variables
317 : #define m_abyRawTileData cannot_use_here
318 : #define m_abyDecodedTileData cannot_use_here
319 :
320 : try
321 : {
322 10819 : abyRawTileData.resize(m_nTileSize);
323 : }
324 0 : catch (const std::bad_alloc &e)
325 : {
326 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
327 0 : return false;
328 : }
329 :
330 10810 : if (NeedDecodedBuffer())
331 : {
332 0 : size_t nDecodedBufferSize = m_oType.GetSize();
333 0 : for (const auto &nBlockSize : m_anBlockSize)
334 : {
335 0 : nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
336 : }
337 : try
338 : {
339 0 : abyDecodedTileData.resize(nDecodedBufferSize);
340 : }
341 0 : catch (const std::bad_alloc &e)
342 : {
343 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
344 0 : return false;
345 : }
346 : }
347 :
348 10802 : return true;
349 : #undef m_abyRawTileData
350 : #undef m_abyDecodedTileData
351 : }
352 :
353 : /************************************************************************/
354 : /* ZarrV3Array::LoadTileData() */
355 : /************************************************************************/
356 :
357 416 : bool ZarrV3Array::LoadTileData(const uint64_t *tileIndices,
358 : bool &bMissingTileOut) const
359 : {
360 416 : return LoadTileData(tileIndices,
361 : false, // use mutex
362 416 : m_poCodecs.get(), m_abyRawTileData,
363 832 : m_abyDecodedTileData, bMissingTileOut);
364 : }
365 :
366 11065 : bool ZarrV3Array::LoadTileData(const uint64_t *tileIndices, bool bUseMutex,
367 : ZarrV3CodecSequence *poCodecs,
368 : ZarrByteVectorQuickResize &abyRawTileData,
369 : ZarrByteVectorQuickResize &abyDecodedTileData,
370 : bool &bMissingTileOut) const
371 : {
372 : // This method should NOT modify any ZarrArray member, as it is going to
373 : // be called concurrently from several threads.
374 :
375 : // Set those #define to avoid accidental use of some global variables
376 : #define m_abyRawTileData cannot_use_here
377 : #define m_abyDecodedTileData cannot_use_here
378 : #define m_poCodecs cannot_use_here
379 :
380 11065 : bMissingTileOut = false;
381 :
382 21730 : std::string osFilename = BuildTileFilename(tileIndices);
383 :
384 : // For network file systems, get the streaming version of the filename,
385 : // as we don't need arbitrary seeking in the file
386 11045 : osFilename = VSIFileManager::GetHandler(osFilename.c_str())
387 11080 : ->GetStreamingFilename(osFilename);
388 :
389 : // First if we have a tile presence cache, check tile presence from it
390 11043 : if (bUseMutex)
391 10582 : m_oMutex.lock();
392 21938 : auto poTilePresenceArray = OpenTilePresenceCache(false);
393 11088 : if (poTilePresenceArray)
394 : {
395 18 : std::vector<GUInt64> anTileIdx(m_aoDims.size());
396 18 : const std::vector<size_t> anCount(m_aoDims.size(), 1);
397 18 : const std::vector<GInt64> anArrayStep(m_aoDims.size(), 0);
398 18 : const std::vector<GPtrDiff_t> anBufferStride(m_aoDims.size(), 0);
399 18 : const auto eByteDT = GDALExtendedDataType::Create(GDT_Byte);
400 54 : for (size_t i = 0; i < m_aoDims.size(); ++i)
401 : {
402 36 : anTileIdx[i] = static_cast<GUInt64>(tileIndices[i]);
403 : }
404 18 : GByte byValue = 0;
405 18 : if (poTilePresenceArray->Read(anTileIdx.data(), anCount.data(),
406 : anArrayStep.data(), anBufferStride.data(),
407 36 : eByteDT, &byValue) &&
408 18 : byValue == 0)
409 : {
410 13 : if (bUseMutex)
411 0 : m_oMutex.unlock();
412 13 : CPLDebugOnly(ZARR_DEBUG_KEY, "Tile %s missing (=nodata)",
413 : osFilename.c_str());
414 13 : bMissingTileOut = true;
415 13 : return true;
416 : }
417 : }
418 11075 : if (bUseMutex)
419 10672 : m_oMutex.unlock();
420 :
421 11073 : VSILFILE *fp = nullptr;
422 : // This is the number of files returned in a S3 directory listing operation
423 11073 : constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
424 11073 : const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
425 : nullptr};
426 22144 : if ((m_osDimSeparator == "/" && !m_anBlockSize.empty() &&
427 33180 : m_anBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
428 11043 : (m_osDimSeparator != "/" &&
429 2 : m_nTotalTileCount > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING))
430 : {
431 : // Avoid issuing ReadDir() when a lot of files are expected
432 : CPLConfigOptionSetter optionSetter("GDAL_DISABLE_READDIR_ON_OPEN",
433 0 : "YES", true);
434 0 : fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
435 : }
436 : else
437 : {
438 11052 : fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
439 : }
440 11053 : if (fp == nullptr)
441 : {
442 : // Missing files are OK and indicate nodata_value
443 299 : CPLDebugOnly(ZARR_DEBUG_KEY, "Tile %s missing (=nodata)",
444 : osFilename.c_str());
445 299 : bMissingTileOut = true;
446 299 : return true;
447 : }
448 :
449 10754 : bMissingTileOut = false;
450 :
451 10754 : CPLAssert(abyRawTileData.capacity() >= m_nTileSize);
452 : // should not fail
453 10718 : abyRawTileData.resize(m_nTileSize);
454 :
455 10687 : bool bRet = true;
456 10687 : size_t nRawDataSize = abyRawTileData.size();
457 10658 : if (poCodecs == nullptr)
458 : {
459 5296 : nRawDataSize = VSIFReadL(&abyRawTileData[0], 1, nRawDataSize, fp);
460 : }
461 : else
462 : {
463 5362 : VSIFSeekL(fp, 0, SEEK_END);
464 5354 : const auto nSize = VSIFTellL(fp);
465 5269 : VSIFSeekL(fp, 0, SEEK_SET);
466 5202 : if (nSize > static_cast<vsi_l_offset>(std::numeric_limits<int>::max()))
467 : {
468 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large tile %s",
469 : osFilename.c_str());
470 0 : bRet = false;
471 : }
472 : else
473 : {
474 : try
475 : {
476 5199 : abyRawTileData.resize(static_cast<size_t>(nSize));
477 : }
478 0 : catch (const std::exception &)
479 : {
480 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
481 : "Cannot allocate memory for tile %s",
482 : osFilename.c_str());
483 0 : bRet = false;
484 : }
485 :
486 10509 : if (bRet && (abyRawTileData.empty() ||
487 5302 : VSIFReadL(&abyRawTileData[0], 1, abyRawTileData.size(),
488 5238 : fp) != abyRawTileData.size()))
489 : {
490 0 : CPLError(CE_Failure, CPLE_AppDefined,
491 : "Could not read tile %s correctly",
492 : osFilename.c_str());
493 0 : bRet = false;
494 : }
495 : else
496 : {
497 5213 : if (!poCodecs->Decode(abyRawTileData))
498 : {
499 0 : CPLError(CE_Failure, CPLE_AppDefined,
500 : "Decompression of tile %s failed",
501 : osFilename.c_str());
502 0 : bRet = false;
503 : }
504 : }
505 : }
506 : }
507 10662 : VSIFCloseL(fp);
508 10574 : if (!bRet)
509 0 : return false;
510 :
511 10574 : if (nRawDataSize != abyRawTileData.size())
512 : {
513 0 : CPLError(CE_Failure, CPLE_AppDefined,
514 : "Decompressed tile %s has not expected size. "
515 : "Got %u instead of %u",
516 : osFilename.c_str(),
517 0 : static_cast<unsigned>(abyRawTileData.size()),
518 : static_cast<unsigned>(nRawDataSize));
519 0 : return false;
520 : }
521 :
522 10419 : if (!abyDecodedTileData.empty())
523 : {
524 : const size_t nSourceSize =
525 0 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
526 0 : const auto nDTSize = m_oType.GetSize();
527 0 : const size_t nValues = abyDecodedTileData.size() / nDTSize;
528 0 : CPLAssert(nValues == m_nTileSize / nSourceSize);
529 0 : const GByte *pSrc = abyRawTileData.data();
530 0 : GByte *pDst = &abyDecodedTileData[0];
531 243 : for (size_t i = 0; i < nValues;
532 0 : i++, pSrc += nSourceSize, pDst += nDTSize)
533 : {
534 0 : DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
535 : }
536 : }
537 :
538 10493 : return true;
539 :
540 : #undef m_abyRawTileData
541 : #undef m_abyDecodedTileData
542 : #undef m_poCodecs
543 : }
544 :
545 : /************************************************************************/
546 : /* ZarrV3Array::IAdviseRead() */
547 : /************************************************************************/
548 :
549 6 : bool ZarrV3Array::IAdviseRead(const GUInt64 *arrayStartIdx, const size_t *count,
550 : CSLConstList papszOptions) const
551 : {
552 12 : std::vector<uint64_t> anIndicesCur;
553 6 : int nThreadsMax = 0;
554 12 : std::vector<uint64_t> anReqTilesIndices;
555 6 : size_t nReqTiles = 0;
556 6 : if (!IAdviseReadCommon(arrayStartIdx, count, papszOptions, anIndicesCur,
557 : nThreadsMax, anReqTilesIndices, nReqTiles))
558 : {
559 2 : return false;
560 : }
561 4 : if (nThreadsMax <= 1)
562 : {
563 0 : return true;
564 : }
565 :
566 : const int nThreads =
567 4 : static_cast<int>(std::min(static_cast<size_t>(nThreadsMax), nReqTiles));
568 :
569 4 : CPLWorkerThreadPool *wtp = GDALGetGlobalThreadPool(nThreadsMax);
570 4 : if (wtp == nullptr)
571 0 : return false;
572 :
573 : struct JobStruct
574 : {
575 : JobStruct() = default;
576 :
577 : JobStruct(const JobStruct &) = delete;
578 : JobStruct &operator=(const JobStruct &) = delete;
579 :
580 : JobStruct(JobStruct &&) = default;
581 : JobStruct &operator=(JobStruct &&) = default;
582 :
583 : const ZarrV3Array *poArray = nullptr;
584 : bool *pbGlobalStatus = nullptr;
585 : int *pnRemainingThreads = nullptr;
586 : const std::vector<uint64_t> *panReqTilesIndices = nullptr;
587 : size_t nFirstIdx = 0;
588 : size_t nLastIdxNotIncluded = 0;
589 : };
590 :
591 4 : std::vector<JobStruct> asJobStructs;
592 :
593 4 : bool bGlobalStatus = true;
594 4 : int nRemainingThreads = nThreads;
595 : // Check for very highly overflow in below loop
596 4 : assert(static_cast<size_t>(nThreads) <
597 : std::numeric_limits<size_t>::max() / nReqTiles);
598 :
599 : // Setup jobs
600 20 : for (int i = 0; i < nThreads; i++)
601 : {
602 16 : JobStruct jobStruct;
603 16 : jobStruct.poArray = this;
604 16 : jobStruct.pbGlobalStatus = &bGlobalStatus;
605 16 : jobStruct.pnRemainingThreads = &nRemainingThreads;
606 16 : jobStruct.panReqTilesIndices = &anReqTilesIndices;
607 16 : jobStruct.nFirstIdx = static_cast<size_t>(i * nReqTiles / nThreads);
608 16 : jobStruct.nLastIdxNotIncluded = std::min(
609 16 : static_cast<size_t>((i + 1) * nReqTiles / nThreads), nReqTiles);
610 16 : asJobStructs.emplace_back(std::move(jobStruct));
611 : }
612 :
613 16 : const auto JobFunc = [](void *pThreadData)
614 : {
615 16 : const JobStruct *jobStruct =
616 : static_cast<const JobStruct *>(pThreadData);
617 :
618 16 : const auto poArray = jobStruct->poArray;
619 16 : const auto &aoDims = poArray->GetDimensions();
620 16 : const size_t l_nDims = poArray->GetDimensionCount();
621 16 : ZarrByteVectorQuickResize abyRawTileData;
622 16 : ZarrByteVectorQuickResize abyDecodedTileData;
623 0 : std::unique_ptr<ZarrV3CodecSequence> poCodecs;
624 16 : if (poArray->m_poCodecs)
625 : {
626 8 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
627 8 : poCodecs = poArray->m_poCodecs->Clone();
628 : }
629 :
630 10688 : for (size_t iReq = jobStruct->nFirstIdx;
631 10688 : iReq < jobStruct->nLastIdxNotIncluded; ++iReq)
632 : {
633 : // Check if we must early exit
634 : {
635 10672 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
636 10672 : if (!(*jobStruct->pbGlobalStatus))
637 0 : return;
638 : }
639 :
640 : const uint64_t *tileIndices =
641 10670 : jobStruct->panReqTilesIndices->data() + iReq * l_nDims;
642 :
643 10672 : uint64_t nTileIdx = 0;
644 32016 : for (size_t j = 0; j < l_nDims; ++j)
645 : {
646 21344 : if (j > 0)
647 10672 : nTileIdx *= aoDims[j - 1]->GetSize();
648 21344 : nTileIdx += tileIndices[j];
649 : }
650 :
651 10672 : if (!poArray->AllocateWorkingBuffers(abyRawTileData,
652 : abyDecodedTileData))
653 : {
654 0 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
655 0 : *jobStruct->pbGlobalStatus = false;
656 0 : break;
657 : }
658 :
659 10659 : bool bIsEmpty = false;
660 10659 : bool success = poArray->LoadTileData(tileIndices,
661 : true, // use mutex
662 : poCodecs.get(), abyRawTileData,
663 : abyDecodedTileData, bIsEmpty);
664 :
665 10562 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
666 10672 : if (!success)
667 : {
668 0 : *jobStruct->pbGlobalStatus = false;
669 0 : break;
670 : }
671 :
672 21344 : CachedTile cachedTile;
673 10672 : if (!bIsEmpty)
674 : {
675 10670 : if (!abyDecodedTileData.empty())
676 0 : std::swap(cachedTile.abyDecoded, abyDecodedTileData);
677 : else
678 10670 : std::swap(cachedTile.abyDecoded, abyRawTileData);
679 : }
680 10672 : poArray->m_oMapTileIndexToCachedTile[nTileIdx] =
681 21344 : std::move(cachedTile);
682 : }
683 :
684 16 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
685 16 : (*jobStruct->pnRemainingThreads)--;
686 : };
687 :
688 : // Start jobs
689 20 : for (int i = 0; i < nThreads; i++)
690 : {
691 16 : if (!wtp->SubmitJob(JobFunc, &asJobStructs[i]))
692 : {
693 0 : std::lock_guard<std::mutex> oLock(m_oMutex);
694 0 : bGlobalStatus = false;
695 0 : nRemainingThreads = i;
696 0 : break;
697 : }
698 : }
699 :
700 : // Wait for all jobs to be finished
701 : while (true)
702 : {
703 : {
704 15 : std::lock_guard<std::mutex> oLock(m_oMutex);
705 15 : if (nRemainingThreads == 0)
706 4 : break;
707 : }
708 11 : wtp->WaitEvent();
709 11 : }
710 :
711 4 : return bGlobalStatus;
712 : }
713 :
714 : /************************************************************************/
715 : /* ZarrV3Array::FlushDirtyTile() */
716 : /************************************************************************/
717 :
718 11516 : bool ZarrV3Array::FlushDirtyTile() const
719 : {
720 11516 : if (!m_bDirtyTile)
721 772 : return true;
722 10744 : m_bDirtyTile = false;
723 :
724 21488 : std::string osFilename = BuildTileFilename(m_anCachedTiledIndices.data());
725 :
726 : const size_t nSourceSize =
727 10744 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
728 : const auto &abyTile =
729 10744 : m_abyDecodedTileData.empty() ? m_abyRawTileData : m_abyDecodedTileData;
730 :
731 10744 : if (IsEmptyTile(abyTile))
732 : {
733 2 : m_bCachedTiledEmpty = true;
734 :
735 : VSIStatBufL sStat;
736 2 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
737 : {
738 0 : CPLDebugOnly(ZARR_DEBUG_KEY,
739 : "Deleting tile %s that has now empty content",
740 : osFilename.c_str());
741 0 : return VSIUnlink(osFilename.c_str()) == 0;
742 : }
743 2 : return true;
744 : }
745 :
746 10742 : if (!m_abyDecodedTileData.empty())
747 : {
748 0 : const size_t nDTSize = m_oType.GetSize();
749 0 : const size_t nValues = m_abyDecodedTileData.size() / nDTSize;
750 0 : GByte *pDst = &m_abyRawTileData[0];
751 0 : const GByte *pSrc = m_abyDecodedTileData.data();
752 0 : for (size_t i = 0; i < nValues;
753 0 : i++, pDst += nSourceSize, pSrc += nDTSize)
754 : {
755 0 : EncodeElt(m_aoDtypeElts, pSrc, pDst);
756 : }
757 : }
758 :
759 10742 : const size_t nSizeBefore = m_abyRawTileData.size();
760 10742 : if (m_poCodecs)
761 : {
762 5405 : if (!m_poCodecs->Encode(m_abyRawTileData))
763 : {
764 0 : m_abyRawTileData.resize(nSizeBefore);
765 0 : return false;
766 : }
767 : }
768 :
769 10742 : if (m_osDimSeparator == "/")
770 : {
771 10742 : std::string osDir = CPLGetDirname(osFilename.c_str());
772 : VSIStatBufL sStat;
773 10742 : if (VSIStatL(osDir.c_str(), &sStat) != 0)
774 : {
775 204 : if (VSIMkdirRecursive(osDir.c_str(), 0755) != 0)
776 : {
777 0 : CPLError(CE_Failure, CPLE_AppDefined,
778 : "Cannot create directory %s", osDir.c_str());
779 0 : m_abyRawTileData.resize(nSizeBefore);
780 0 : return false;
781 : }
782 : }
783 : }
784 :
785 10742 : VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
786 10742 : if (fp == nullptr)
787 : {
788 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot create tile %s",
789 : osFilename.c_str());
790 0 : m_abyRawTileData.resize(nSizeBefore);
791 0 : return false;
792 : }
793 :
794 10742 : bool bRet = true;
795 10742 : const size_t nRawDataSize = m_abyRawTileData.size();
796 10742 : if (VSIFWriteL(m_abyRawTileData.data(), 1, nRawDataSize, fp) !=
797 : nRawDataSize)
798 : {
799 0 : CPLError(CE_Failure, CPLE_AppDefined,
800 : "Could not write tile %s correctly", osFilename.c_str());
801 0 : bRet = false;
802 : }
803 10742 : VSIFCloseL(fp);
804 :
805 10742 : m_abyRawTileData.resize(nSizeBefore);
806 :
807 10742 : return bRet;
808 : }
809 :
810 : /************************************************************************/
811 : /* BuildTileFilename() */
812 : /************************************************************************/
813 :
814 21817 : std::string ZarrV3Array::BuildTileFilename(const uint64_t *tileIndices) const
815 : {
816 21817 : if (m_aoDims.empty())
817 : {
818 : return CPLFormFilename(CPLGetDirname(m_osFilename.c_str()),
819 0 : m_bV2ChunkKeyEncoding ? "0" : "c", nullptr);
820 : }
821 : else
822 : {
823 43595 : std::string osFilename(CPLGetDirname(m_osFilename.c_str()));
824 21799 : osFilename += '/';
825 21823 : if (!m_bV2ChunkKeyEncoding)
826 : {
827 21809 : osFilename += 'c';
828 : }
829 65333 : for (size_t i = 0; i < m_aoDims.size(); ++i)
830 : {
831 43493 : if (i > 0 || !m_bV2ChunkKeyEncoding)
832 43504 : osFilename += m_osDimSeparator;
833 43475 : osFilename += std::to_string(tileIndices[i]);
834 : }
835 21848 : return osFilename;
836 : }
837 : }
838 :
839 : /************************************************************************/
840 : /* GetDataDirectory() */
841 : /************************************************************************/
842 :
843 2 : std::string ZarrV3Array::GetDataDirectory() const
844 : {
845 2 : return std::string(CPLGetDirname(m_osFilename.c_str()));
846 : }
847 :
848 : /************************************************************************/
849 : /* GetTileIndicesFromFilename() */
850 : /************************************************************************/
851 :
852 : CPLStringList
853 4 : ZarrV3Array::GetTileIndicesFromFilename(const char *pszFilename) const
854 : {
855 4 : if (!m_bV2ChunkKeyEncoding)
856 : {
857 4 : if (pszFilename[0] != 'c')
858 2 : return CPLStringList();
859 2 : if (m_osDimSeparator == "/")
860 : {
861 2 : if (pszFilename[1] != '/' && pszFilename[1] != '\\')
862 0 : return CPLStringList();
863 : }
864 0 : else if (pszFilename[1] != m_osDimSeparator[0])
865 : {
866 0 : return CPLStringList();
867 : }
868 : }
869 : return CPLStringList(
870 2 : CSLTokenizeString2(pszFilename + (m_bV2ChunkKeyEncoding ? 0 : 2),
871 4 : m_osDimSeparator.c_str(), 0));
872 : }
873 :
874 : /************************************************************************/
875 : /* ParseDtypeV3() */
876 : /************************************************************************/
877 :
878 170 : static GDALExtendedDataType ParseDtypeV3(const CPLJSONObject &obj,
879 : std::vector<DtypeElt> &elts)
880 : {
881 : do
882 : {
883 170 : if (obj.GetType() == CPLJSONObject::Type::String)
884 : {
885 340 : const auto str = obj.ToString();
886 170 : DtypeElt elt;
887 170 : GDALDataType eDT = GDT_Unknown;
888 :
889 170 : if (str == "bool") // boolean
890 : {
891 0 : elt.nativeType = DtypeElt::NativeType::BOOLEAN;
892 0 : eDT = GDT_Byte;
893 : }
894 170 : else if (str == "int8")
895 : {
896 6 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
897 6 : eDT = GDT_Int8;
898 : }
899 164 : else if (str == "uint8")
900 : {
901 65 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
902 65 : eDT = GDT_Byte;
903 : }
904 99 : else if (str == "int16")
905 : {
906 10 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
907 10 : eDT = GDT_Int16;
908 : }
909 89 : else if (str == "uint16")
910 : {
911 7 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
912 7 : eDT = GDT_UInt16;
913 : }
914 82 : else if (str == "int32")
915 : {
916 7 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
917 7 : eDT = GDT_Int32;
918 : }
919 75 : else if (str == "uint32")
920 : {
921 7 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
922 7 : eDT = GDT_UInt32;
923 : }
924 68 : else if (str == "int64")
925 : {
926 7 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
927 7 : eDT = GDT_Int64;
928 : }
929 61 : else if (str == "uint64")
930 : {
931 6 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
932 6 : eDT = GDT_UInt64;
933 : }
934 55 : else if (str == "float16")
935 : {
936 0 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
937 0 : elt.nativeSize = 2;
938 0 : elt.gdalTypeIsApproxOfNative = true;
939 0 : eDT = GDT_Float32;
940 : }
941 55 : else if (str == "float32")
942 : {
943 9 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
944 9 : eDT = GDT_Float32;
945 : }
946 46 : else if (str == "float64")
947 : {
948 15 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
949 15 : eDT = GDT_Float64;
950 : }
951 31 : else if (str == "complex64")
952 : {
953 15 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
954 15 : eDT = GDT_CFloat32;
955 : }
956 16 : else if (str == "complex128")
957 : {
958 15 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
959 15 : eDT = GDT_CFloat64;
960 : }
961 : else
962 1 : break;
963 :
964 169 : elt.gdalType = GDALExtendedDataType::Create(eDT);
965 169 : elt.gdalSize = elt.gdalType.GetSize();
966 169 : if (!elt.gdalTypeIsApproxOfNative)
967 169 : elt.nativeSize = elt.gdalSize;
968 :
969 169 : if (elt.nativeSize > 1)
970 : {
971 98 : elt.needByteSwapping = (CPL_IS_LSB == 0);
972 : }
973 :
974 169 : elts.emplace_back(elt);
975 169 : return GDALExtendedDataType::Create(eDT);
976 : }
977 : } while (false);
978 1 : CPLError(CE_Failure, CPLE_AppDefined,
979 : "Invalid or unsupported format for data_type: %s",
980 2 : obj.ToString().c_str());
981 1 : return GDALExtendedDataType::Create(GDT_Unknown);
982 : }
983 :
984 : /************************************************************************/
985 : /* ParseNoDataStringAsDouble() */
986 : /************************************************************************/
987 :
988 37 : static double ParseNoDataStringAsDouble(const std::string &osVal, bool &bOK)
989 : {
990 37 : double dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
991 37 : if (osVal == "NaN")
992 : {
993 : // initialized above
994 : }
995 15 : else if (osVal == "Infinity" || osVal == "+Infinity")
996 : {
997 5 : dfNoDataValue = std::numeric_limits<double>::infinity();
998 : }
999 10 : else if (osVal == "-Infinity")
1000 : {
1001 5 : dfNoDataValue = -std::numeric_limits<double>::infinity();
1002 : }
1003 : else
1004 : {
1005 5 : bOK = false;
1006 : }
1007 37 : return dfNoDataValue;
1008 : }
1009 :
1010 : /************************************************************************/
1011 : /* ParseNoDataComponent() */
1012 : /************************************************************************/
1013 :
1014 : template <typename T, typename Tint>
1015 40 : static T ParseNoDataComponent(const CPLJSONObject &oObj, bool &bOK)
1016 : {
1017 40 : if (oObj.GetType() == CPLJSONObject::Type::Integer ||
1018 62 : oObj.GetType() == CPLJSONObject::Type::Long ||
1019 22 : oObj.GetType() == CPLJSONObject::Type::Double)
1020 : {
1021 22 : return static_cast<T>(oObj.ToDouble());
1022 : }
1023 18 : else if (oObj.GetType() == CPLJSONObject::Type::String)
1024 : {
1025 54 : const auto osVal = oObj.ToString();
1026 18 : if (STARTS_WITH(osVal.c_str(), "0x"))
1027 : {
1028 2 : if (osVal.size() > 2 + 2 * sizeof(T))
1029 : {
1030 0 : bOK = false;
1031 0 : return 0;
1032 : }
1033 2 : Tint nVal = static_cast<Tint>(
1034 2 : std::strtoull(osVal.c_str() + 2, nullptr, 16));
1035 : T fVal;
1036 : static_assert(sizeof(nVal) == sizeof(fVal),
1037 : "sizeof(nVal) == sizeof(dfVal)");
1038 2 : memcpy(&fVal, &nVal, sizeof(nVal));
1039 2 : return fVal;
1040 : }
1041 : else
1042 : {
1043 16 : return static_cast<T>(ParseNoDataStringAsDouble(osVal, bOK));
1044 : }
1045 : }
1046 : else
1047 : {
1048 0 : bOK = false;
1049 0 : return 0;
1050 : }
1051 : }
1052 :
1053 : /************************************************************************/
1054 : /* ZarrV3Group::LoadArray() */
1055 : /************************************************************************/
1056 :
1057 : std::shared_ptr<ZarrArray>
1058 183 : ZarrV3Group::LoadArray(const std::string &osArrayName,
1059 : const std::string &osZarrayFilename,
1060 : const CPLJSONObject &oRoot) const
1061 : {
1062 : // Add osZarrayFilename to m_poSharedResource during the scope
1063 : // of this function call.
1064 183 : ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
1065 366 : osZarrayFilename);
1066 183 : if (!filenameAdder.ok())
1067 0 : return nullptr;
1068 :
1069 : // Warn about unknown members (the spec suggests to error out, but let be
1070 : // a bit more lenient)
1071 1766 : for (const auto &oNode : oRoot.GetChildren())
1072 : {
1073 3166 : const auto osName = oNode.GetName();
1074 4200 : if (osName != "zarr_format" && osName != "node_type" &&
1075 3105 : osName != "shape" && osName != "chunk_grid" &&
1076 2013 : osName != "data_type" && osName != "chunk_key_encoding" &&
1077 795 : osName != "fill_value" &&
1078 : // Below are optional
1079 627 : osName != "dimension_names" && osName != "codecs" &&
1080 3111 : osName != "storage_transformers" && osName != "attributes")
1081 : {
1082 4 : CPLError(CE_Warning, CPLE_AppDefined,
1083 : "%s array definition contains a unknown member (%s). "
1084 : "Interpretation of the array might be wrong.",
1085 : osZarrayFilename.c_str(), osName.c_str());
1086 : }
1087 : }
1088 :
1089 549 : const auto oStorageTransformers = oRoot["storage_transformers"].ToArray();
1090 183 : if (oStorageTransformers.Size() > 0)
1091 : {
1092 1 : CPLError(CE_Failure, CPLE_AppDefined,
1093 : "storage_transformers are not supported.");
1094 1 : return nullptr;
1095 : }
1096 :
1097 546 : const auto oShape = oRoot["shape"].ToArray();
1098 182 : if (!oShape.IsValid())
1099 : {
1100 2 : CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
1101 2 : return nullptr;
1102 : }
1103 :
1104 : // Parse chunk_grid
1105 540 : const auto oChunkGrid = oRoot["chunk_grid"];
1106 180 : if (oChunkGrid.GetType() != CPLJSONObject::Type::Object)
1107 : {
1108 1 : CPLError(CE_Failure, CPLE_AppDefined,
1109 : "chunk_grid missing or not an object");
1110 1 : return nullptr;
1111 : }
1112 :
1113 537 : const auto oChunkGridName = oChunkGrid["name"];
1114 179 : if (oChunkGridName.ToString() != "regular")
1115 : {
1116 1 : CPLError(CE_Failure, CPLE_AppDefined,
1117 : "Only chunk_grid.name = regular supported");
1118 1 : return nullptr;
1119 : }
1120 :
1121 534 : const auto oChunks = oChunkGrid["configuration"]["chunk_shape"].ToArray();
1122 178 : if (!oChunks.IsValid())
1123 : {
1124 1 : CPLError(
1125 : CE_Failure, CPLE_AppDefined,
1126 : "chunk_grid.configuration.chunk_shape missing or not an array");
1127 1 : return nullptr;
1128 : }
1129 :
1130 177 : if (oShape.Size() != oChunks.Size())
1131 : {
1132 1 : CPLError(CE_Failure, CPLE_AppDefined,
1133 : "shape and chunks arrays are of different size");
1134 1 : return nullptr;
1135 : }
1136 :
1137 : // Parse chunk_key_encoding
1138 528 : const auto oChunkKeyEncoding = oRoot["chunk_key_encoding"];
1139 176 : if (oChunkKeyEncoding.GetType() != CPLJSONObject::Type::Object)
1140 : {
1141 1 : CPLError(CE_Failure, CPLE_AppDefined,
1142 : "chunk_key_encoding missing or not an object");
1143 1 : return nullptr;
1144 : }
1145 :
1146 350 : std::string osDimSeparator;
1147 175 : bool bV2ChunkKeyEncoding = false;
1148 525 : const auto oChunkKeyEncodingName = oChunkKeyEncoding["name"];
1149 175 : if (oChunkKeyEncodingName.ToString() == "default")
1150 : {
1151 171 : osDimSeparator = "/";
1152 : }
1153 4 : else if (oChunkKeyEncodingName.ToString() == "v2")
1154 : {
1155 3 : osDimSeparator = ".";
1156 3 : bV2ChunkKeyEncoding = true;
1157 : }
1158 : else
1159 : {
1160 1 : CPLError(CE_Failure, CPLE_AppDefined,
1161 : "Unsupported chunk_key_encoding.name");
1162 1 : return nullptr;
1163 : }
1164 :
1165 : {
1166 348 : auto oConfiguration = oChunkKeyEncoding["configuration"];
1167 174 : if (oConfiguration.GetType() == CPLJSONObject::Type::Object)
1168 : {
1169 256 : auto oSeparator = oConfiguration["separator"];
1170 128 : if (oSeparator.IsValid())
1171 : {
1172 128 : osDimSeparator = oSeparator.ToString();
1173 128 : if (osDimSeparator != "/" && osDimSeparator != ".")
1174 : {
1175 1 : CPLError(CE_Failure, CPLE_AppDefined,
1176 : "Separator can only be '/' or '.'");
1177 1 : return nullptr;
1178 : }
1179 : }
1180 : }
1181 : }
1182 :
1183 519 : CPLJSONObject oAttributes = oRoot["attributes"];
1184 :
1185 : // Deep-clone of oAttributes
1186 173 : if (oAttributes.IsValid())
1187 : {
1188 123 : oAttributes = oAttributes.Clone();
1189 : }
1190 :
1191 346 : std::vector<std::shared_ptr<GDALDimension>> aoDims;
1192 395 : for (int i = 0; i < oShape.Size(); ++i)
1193 : {
1194 222 : const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
1195 222 : if (nSize == 0)
1196 : {
1197 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
1198 0 : return nullptr;
1199 : }
1200 222 : aoDims.emplace_back(std::make_shared<ZarrDimension>(
1201 222 : m_poSharedResource,
1202 444 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1203 444 : std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
1204 222 : nSize));
1205 : }
1206 :
1207 : // Deal with dimension_names
1208 519 : const auto dimensionNames = oRoot["dimension_names"];
1209 :
1210 166 : const auto FindDimension = [this, &aoDims, &osArrayName, &oAttributes](
1211 : const std::string &osDimName,
1212 2212 : std::shared_ptr<GDALDimension> &poDim, int i)
1213 : {
1214 166 : auto oIter = m_oMapDimensions.find(osDimName);
1215 166 : if (oIter != m_oMapDimensions.end())
1216 : {
1217 0 : if (m_bDimSizeInUpdate ||
1218 0 : oIter->second->GetSize() == poDim->GetSize())
1219 : {
1220 0 : poDim = oIter->second;
1221 0 : return true;
1222 : }
1223 : else
1224 : {
1225 0 : CPLError(CE_Warning, CPLE_AppDefined,
1226 : "Size of _ARRAY_DIMENSIONS[%d] different "
1227 : "from the one of shape",
1228 : i);
1229 0 : return false;
1230 : }
1231 : }
1232 :
1233 : // Try to load the indexing variable.
1234 : // Not in m_oMapMDArrays,
1235 : // then stat() the indexing variable.
1236 330 : else if (osArrayName != osDimName &&
1237 330 : m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
1238 : {
1239 328 : std::string osDirName = m_osDirectoryName;
1240 : while (true)
1241 : {
1242 : const std::string osArrayFilenameDim =
1243 : CPLFormFilename(CPLFormFilename(osDirName.c_str(),
1244 : osDimName.c_str(), nullptr),
1245 483 : "zarr.json", nullptr);
1246 : VSIStatBufL sStat;
1247 483 : if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
1248 : {
1249 4 : CPLJSONDocument oDoc;
1250 2 : if (oDoc.Load(osArrayFilenameDim))
1251 : {
1252 2 : LoadArray(osDimName, osArrayFilenameDim,
1253 4 : oDoc.GetRoot());
1254 : }
1255 : }
1256 : else
1257 : {
1258 : // Recurse to upper level for datasets such as
1259 : // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
1260 : const std::string osDirNameNew =
1261 481 : CPLGetPath(osDirName.c_str());
1262 481 : if (!osDirNameNew.empty() && osDirNameNew != osDirName)
1263 : {
1264 319 : osDirName = osDirNameNew;
1265 319 : continue;
1266 : }
1267 : }
1268 164 : break;
1269 319 : }
1270 : }
1271 :
1272 166 : oIter = m_oMapDimensions.find(osDimName);
1273 : // cppcheck-suppress knownConditionTrueFalse
1274 168 : if (oIter != m_oMapDimensions.end() &&
1275 2 : oIter->second->GetSize() == poDim->GetSize())
1276 : {
1277 2 : poDim = oIter->second;
1278 2 : return true;
1279 : }
1280 :
1281 328 : std::string osType;
1282 328 : std::string osDirection;
1283 164 : if (aoDims.size() == 1 && osArrayName == osDimName)
1284 : {
1285 2 : ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
1286 : osDirection);
1287 : }
1288 :
1289 : auto poDimLocal = std::make_shared<ZarrDimension>(
1290 164 : m_poSharedResource,
1291 328 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1292 328 : GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
1293 164 : poDimLocal->SetXArrayDimension();
1294 164 : m_oMapDimensions[osDimName] = poDimLocal;
1295 164 : poDim = poDimLocal;
1296 164 : return true;
1297 173 : };
1298 :
1299 173 : if (dimensionNames.GetType() == CPLJSONObject::Type::Array)
1300 : {
1301 112 : const auto arrayDims = dimensionNames.ToArray();
1302 112 : if (arrayDims.Size() == oShape.Size())
1303 : {
1304 277 : for (int i = 0; i < oShape.Size(); ++i)
1305 : {
1306 166 : if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
1307 : {
1308 498 : const auto osDimName = arrayDims[i].ToString();
1309 166 : FindDimension(osDimName, aoDims[i], i);
1310 : }
1311 : }
1312 : }
1313 : else
1314 : {
1315 1 : CPLError(
1316 : CE_Failure, CPLE_AppDefined,
1317 : "Size of dimension_names[] different from the one of shape");
1318 1 : return nullptr;
1319 : }
1320 : }
1321 61 : else if (dimensionNames.IsValid())
1322 : {
1323 1 : CPLError(CE_Failure, CPLE_AppDefined,
1324 : "dimension_names should be an array");
1325 1 : return nullptr;
1326 : }
1327 :
1328 513 : auto oDtype = oRoot["data_type"];
1329 171 : if (!oDtype.IsValid())
1330 : {
1331 1 : CPLError(CE_Failure, CPLE_NotSupported, "data_type missing");
1332 1 : return nullptr;
1333 : }
1334 170 : if (oDtype["fallback"].IsValid())
1335 1 : oDtype = oDtype["fallback"];
1336 340 : std::vector<DtypeElt> aoDtypeElts;
1337 340 : const auto oType = ParseDtypeV3(oDtype, aoDtypeElts);
1338 340 : if (oType.GetClass() == GEDTC_NUMERIC &&
1339 170 : oType.GetNumericDataType() == GDT_Unknown)
1340 1 : return nullptr;
1341 :
1342 338 : std::vector<GUInt64> anBlockSize;
1343 169 : if (!ZarrArray::ParseChunkSize(oChunks, oType, anBlockSize))
1344 1 : return nullptr;
1345 :
1346 336 : std::vector<GByte> abyNoData;
1347 :
1348 504 : auto oFillValue = oRoot["fill_value"];
1349 168 : auto eFillValueType = oFillValue.GetType();
1350 :
1351 168 : if (!oFillValue.IsValid())
1352 : {
1353 0 : CPLError(CE_Warning, CPLE_AppDefined, "Missing fill_value is invalid");
1354 : }
1355 168 : else if (eFillValueType == CPLJSONObject::Type::Null)
1356 : {
1357 97 : CPLError(CE_Warning, CPLE_AppDefined, "fill_value = null is invalid");
1358 : }
1359 71 : else if (GDALDataTypeIsComplex(oType.GetNumericDataType()) &&
1360 : eFillValueType != CPLJSONObject::Type::Array)
1361 : {
1362 4 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1363 4 : return nullptr;
1364 : }
1365 67 : else if (eFillValueType == CPLJSONObject::Type::String)
1366 : {
1367 54 : const auto osFillValue = oFillValue.ToString();
1368 27 : if (STARTS_WITH(osFillValue.c_str(), "0x"))
1369 : {
1370 3 : if (osFillValue.size() > 2 + 2 * oType.GetSize())
1371 : {
1372 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1373 1 : return nullptr;
1374 : }
1375 : uint64_t nVal = static_cast<uint64_t>(
1376 3 : std::strtoull(osFillValue.c_str() + 2, nullptr, 16));
1377 3 : if (oType.GetSize() == 4)
1378 : {
1379 1 : abyNoData.resize(oType.GetSize());
1380 1 : uint32_t nTmp = static_cast<uint32_t>(nVal);
1381 1 : memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
1382 : }
1383 2 : else if (oType.GetSize() == 8)
1384 : {
1385 1 : abyNoData.resize(oType.GetSize());
1386 1 : memcpy(&abyNoData[0], &nVal, sizeof(nVal));
1387 : }
1388 : else
1389 : {
1390 1 : CPLError(CE_Failure, CPLE_AppDefined,
1391 : "Hexadecimal representation of fill_value no "
1392 : "supported for this data type");
1393 1 : return nullptr;
1394 : }
1395 : }
1396 24 : else if (STARTS_WITH(osFillValue.c_str(), "0b"))
1397 : {
1398 3 : if (osFillValue.size() > 2 + 8 * oType.GetSize())
1399 : {
1400 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1401 1 : return nullptr;
1402 : }
1403 : uint64_t nVal = static_cast<uint64_t>(
1404 3 : std::strtoull(osFillValue.c_str() + 2, nullptr, 2));
1405 3 : if (oType.GetSize() == 4)
1406 : {
1407 1 : abyNoData.resize(oType.GetSize());
1408 1 : uint32_t nTmp = static_cast<uint32_t>(nVal);
1409 1 : memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
1410 : }
1411 2 : else if (oType.GetSize() == 8)
1412 : {
1413 1 : abyNoData.resize(oType.GetSize());
1414 1 : memcpy(&abyNoData[0], &nVal, sizeof(nVal));
1415 : }
1416 : else
1417 : {
1418 1 : CPLError(CE_Failure, CPLE_AppDefined,
1419 : "Binary representation of fill_value no supported for "
1420 : "this data type");
1421 1 : return nullptr;
1422 : }
1423 : }
1424 : else
1425 : {
1426 21 : bool bOK = true;
1427 21 : double dfNoDataValue = ParseNoDataStringAsDouble(osFillValue, bOK);
1428 21 : if (!bOK)
1429 : {
1430 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1431 2 : return nullptr;
1432 : }
1433 20 : else if (oType.GetNumericDataType() == GDT_Float32)
1434 : {
1435 7 : const float fNoDataValue = static_cast<float>(dfNoDataValue);
1436 7 : abyNoData.resize(sizeof(fNoDataValue));
1437 7 : memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
1438 : }
1439 13 : else if (oType.GetNumericDataType() == GDT_Float64)
1440 : {
1441 12 : abyNoData.resize(sizeof(dfNoDataValue));
1442 12 : memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
1443 : }
1444 : else
1445 : {
1446 1 : CPLError(CE_Failure, CPLE_AppDefined,
1447 : "Invalid fill_value for this data type");
1448 1 : return nullptr;
1449 : }
1450 : }
1451 : }
1452 40 : else if (eFillValueType == CPLJSONObject::Type::Boolean ||
1453 25 : eFillValueType == CPLJSONObject::Type::Integer ||
1454 25 : eFillValueType == CPLJSONObject::Type::Long ||
1455 : eFillValueType == CPLJSONObject::Type::Double)
1456 : {
1457 16 : const double dfNoDataValue = oFillValue.ToDouble();
1458 16 : if (oType.GetNumericDataType() == GDT_Int64)
1459 : {
1460 : const int64_t nNoDataValue =
1461 1 : static_cast<int64_t>(oFillValue.ToLong());
1462 1 : abyNoData.resize(oType.GetSize());
1463 1 : GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1464 : oType.GetNumericDataType(), 0, 1);
1465 : }
1466 15 : else if (oType.GetNumericDataType() == GDT_UInt64 &&
1467 : /* we can't really deal with nodata value between */
1468 : /* int64::max and uint64::max due to json-c limitations */
1469 0 : dfNoDataValue >= 0)
1470 : {
1471 : const int64_t nNoDataValue =
1472 0 : static_cast<int64_t>(oFillValue.ToLong());
1473 0 : abyNoData.resize(oType.GetSize());
1474 0 : GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1475 : oType.GetNumericDataType(), 0, 1);
1476 : }
1477 : else
1478 : {
1479 15 : abyNoData.resize(oType.GetSize());
1480 15 : GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
1481 : oType.GetNumericDataType(), 0, 1);
1482 16 : }
1483 : }
1484 24 : else if (eFillValueType == CPLJSONObject::Type::Array)
1485 : {
1486 24 : const auto oFillValueArray = oFillValue.ToArray();
1487 44 : if (oFillValueArray.Size() == 2 &&
1488 20 : GDALDataTypeIsComplex(oType.GetNumericDataType()))
1489 : {
1490 20 : if (oType.GetNumericDataType() == GDT_CFloat64)
1491 : {
1492 10 : bool bOK = true;
1493 : const double adfNoDataValue[2] = {
1494 10 : ParseNoDataComponent<double, uint64_t>(oFillValueArray[0],
1495 : bOK),
1496 10 : ParseNoDataComponent<double, uint64_t>(oFillValueArray[1],
1497 : bOK),
1498 20 : };
1499 10 : if (!bOK)
1500 : {
1501 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1502 2 : return nullptr;
1503 : }
1504 8 : abyNoData.resize(oType.GetSize());
1505 8 : CPLAssert(sizeof(adfNoDataValue) == oType.GetSize());
1506 8 : memcpy(abyNoData.data(), adfNoDataValue,
1507 : sizeof(adfNoDataValue));
1508 : }
1509 : else
1510 : {
1511 10 : CPLAssert(oType.GetNumericDataType() == GDT_CFloat32);
1512 10 : bool bOK = true;
1513 : const float afNoDataValue[2] = {
1514 10 : ParseNoDataComponent<float, uint32_t>(oFillValueArray[0],
1515 : bOK),
1516 10 : ParseNoDataComponent<float, uint32_t>(oFillValueArray[1],
1517 : bOK),
1518 20 : };
1519 10 : if (!bOK)
1520 : {
1521 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1522 2 : return nullptr;
1523 : }
1524 8 : abyNoData.resize(oType.GetSize());
1525 8 : CPLAssert(sizeof(afNoDataValue) == oType.GetSize());
1526 8 : memcpy(abyNoData.data(), afNoDataValue, sizeof(afNoDataValue));
1527 : }
1528 : }
1529 : else
1530 : {
1531 4 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1532 4 : return nullptr;
1533 : }
1534 : }
1535 : else
1536 : {
1537 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1538 0 : return nullptr;
1539 : }
1540 :
1541 456 : const auto oCodecs = oRoot["codecs"].ToArray();
1542 152 : std::unique_ptr<ZarrV3CodecSequence> poCodecs;
1543 152 : if (oCodecs.Size() > 0)
1544 : {
1545 : // Byte swapping will be done by the codec chain
1546 65 : aoDtypeElts.back().needByteSwapping = false;
1547 :
1548 65 : ZarrArrayMetadata oInputArrayMetadata;
1549 151 : for (auto &nSize : anBlockSize)
1550 86 : oInputArrayMetadata.anBlockSizes.push_back(
1551 86 : static_cast<size_t>(nSize));
1552 65 : oInputArrayMetadata.oElt = aoDtypeElts.back();
1553 65 : poCodecs = std::make_unique<ZarrV3CodecSequence>(oInputArrayMetadata);
1554 65 : if (!poCodecs->InitFromJson(oCodecs))
1555 0 : return nullptr;
1556 : }
1557 :
1558 : auto poArray =
1559 152 : ZarrV3Array::Create(m_poSharedResource, GetFullName(), osArrayName,
1560 304 : aoDims, oType, aoDtypeElts, anBlockSize);
1561 152 : if (!poArray)
1562 1 : return nullptr;
1563 151 : poArray->SetUpdatable(m_bUpdatable); // must be set before SetAttributes()
1564 151 : poArray->SetFilename(osZarrayFilename);
1565 151 : poArray->SetIsV2ChunkKeyEncoding(bV2ChunkKeyEncoding);
1566 151 : poArray->SetDimSeparator(osDimSeparator);
1567 151 : if (!abyNoData.empty())
1568 : {
1569 54 : poArray->RegisterNoDataValue(abyNoData.data());
1570 : }
1571 151 : poArray->ParseSpecialAttributes(m_pSelf.lock(), oAttributes);
1572 151 : poArray->SetAttributes(oAttributes);
1573 151 : poArray->SetDtype(oDtype);
1574 151 : if (poCodecs)
1575 65 : poArray->SetCodecs(std::move(poCodecs));
1576 151 : RegisterArray(poArray);
1577 :
1578 : // If this is an indexing variable, attach it to the dimension.
1579 151 : if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
1580 : {
1581 2 : auto oIter = m_oMapDimensions.find(poArray->GetName());
1582 2 : if (oIter != m_oMapDimensions.end())
1583 : {
1584 2 : oIter->second->SetIndexingVariable(poArray);
1585 : }
1586 : }
1587 :
1588 151 : if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
1589 : "CACHE_TILE_PRESENCE", "NO")))
1590 : {
1591 2 : poArray->CacheTilePresence();
1592 : }
1593 :
1594 151 : return poArray;
1595 : }
|