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 10956 : bool ZarrV3Array::NeedDecodedBuffer() const
246 : {
247 21904 : for (const auto &elt : m_aoDtypeElts)
248 : {
249 10935 : if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative)
250 : {
251 0 : return true;
252 : }
253 : }
254 10950 : 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 10811 : 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 10809 : 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 11076 : 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 11076 : bMissingTileOut = false;
381 :
382 21733 : 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 11046 : osFilename = VSIFileManager::GetHandler(osFilename.c_str())
387 11073 : ->GetStreamingFilename(osFilename);
388 :
389 : // First if we have a tile presence cache, check tile presence from it
390 11045 : if (bUseMutex)
391 10578 : m_oMutex.lock();
392 21894 : 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 11075 : VSILFILE *fp = nullptr;
422 : // This is the number of files returned in a S3 directory listing operation
423 11075 : constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
424 11075 : const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
425 : nullptr};
426 22147 : if ((m_osDimSeparator == "/" && !m_anBlockSize.empty() &&
427 33169 : m_anBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
428 11068 : (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 11017 : fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
439 : }
440 11041 : 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 10742 : bMissingTileOut = false;
450 :
451 10742 : CPLAssert(abyRawTileData.capacity() >= m_nTileSize);
452 : // should not fail
453 10669 : abyRawTileData.resize(m_nTileSize);
454 :
455 10613 : bool bRet = true;
456 10613 : size_t nRawDataSize = abyRawTileData.size();
457 10641 : if (poCodecs == nullptr)
458 : {
459 5297 : nRawDataSize = VSIFReadL(&abyRawTileData[0], 1, nRawDataSize, fp);
460 : }
461 : else
462 : {
463 5344 : VSIFSeekL(fp, 0, SEEK_END);
464 5327 : const auto nSize = VSIFTellL(fp);
465 5071 : VSIFSeekL(fp, 0, SEEK_SET);
466 5099 : 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 5088 : 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 10188 : if (bRet && (abyRawTileData.empty() ||
487 5137 : VSIFReadL(&abyRawTileData[0], 1, abyRawTileData.size(),
488 5040 : 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 5035 : 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 10558 : VSIFCloseL(fp);
508 10663 : if (!bRet)
509 0 : return false;
510 :
511 10663 : 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 10255 : 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 57 : 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 10443 : 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 10672 : jobStruct->panReqTilesIndices->data() + iReq * l_nDims;
642 :
643 10661 : uint64_t nTileIdx = 0;
644 31996 : for (size_t j = 0; j < l_nDims; ++j)
645 : {
646 21330 : if (j > 0)
647 10668 : nTileIdx *= aoDims[j - 1]->GetSize();
648 21335 : nTileIdx += tileIndices[j];
649 : }
650 :
651 10666 : 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 10653 : bool bIsEmpty = false;
660 10653 : bool success = poArray->LoadTileData(tileIndices,
661 : true, // use mutex
662 : poCodecs.get(), abyRawTileData,
663 : abyDecodedTileData, bIsEmpty);
664 :
665 10493 : 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 16 : std::lock_guard<std::mutex> oLock(m_oMutex);
705 16 : if (nRemainingThreads == 0)
706 4 : break;
707 : }
708 12 : wtp->WaitEvent();
709 12 : }
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 = CPLGetDirnameSafe(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 21810 : std::string ZarrV3Array::BuildTileFilename(const uint64_t *tileIndices) const
815 : {
816 21810 : if (m_aoDims.empty())
817 : {
818 : return CPLFormFilenameSafe(
819 0 : CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
820 0 : m_bV2ChunkKeyEncoding ? "0" : "c", nullptr);
821 : }
822 : else
823 : {
824 43602 : std::string osFilename(CPLGetDirnameSafe(m_osFilename.c_str()));
825 21815 : osFilename += '/';
826 21792 : if (!m_bV2ChunkKeyEncoding)
827 : {
828 21790 : osFilename += 'c';
829 : }
830 65301 : for (size_t i = 0; i < m_aoDims.size(); ++i)
831 : {
832 43477 : if (i > 0 || !m_bV2ChunkKeyEncoding)
833 43470 : osFilename += m_osDimSeparator;
834 43450 : osFilename += std::to_string(tileIndices[i]);
835 : }
836 21817 : return osFilename;
837 : }
838 : }
839 :
840 : /************************************************************************/
841 : /* GetDataDirectory() */
842 : /************************************************************************/
843 :
844 2 : std::string ZarrV3Array::GetDataDirectory() const
845 : {
846 2 : return std::string(CPLGetDirnameSafe(m_osFilename.c_str()));
847 : }
848 :
849 : /************************************************************************/
850 : /* GetTileIndicesFromFilename() */
851 : /************************************************************************/
852 :
853 : CPLStringList
854 4 : ZarrV3Array::GetTileIndicesFromFilename(const char *pszFilename) const
855 : {
856 4 : if (!m_bV2ChunkKeyEncoding)
857 : {
858 4 : if (pszFilename[0] != 'c')
859 2 : return CPLStringList();
860 2 : if (m_osDimSeparator == "/")
861 : {
862 2 : if (pszFilename[1] != '/' && pszFilename[1] != '\\')
863 0 : return CPLStringList();
864 : }
865 0 : else if (pszFilename[1] != m_osDimSeparator[0])
866 : {
867 0 : return CPLStringList();
868 : }
869 : }
870 : return CPLStringList(
871 2 : CSLTokenizeString2(pszFilename + (m_bV2ChunkKeyEncoding ? 0 : 2),
872 4 : m_osDimSeparator.c_str(), 0));
873 : }
874 :
875 : /************************************************************************/
876 : /* ParseDtypeV3() */
877 : /************************************************************************/
878 :
879 170 : static GDALExtendedDataType ParseDtypeV3(const CPLJSONObject &obj,
880 : std::vector<DtypeElt> &elts)
881 : {
882 : do
883 : {
884 170 : if (obj.GetType() == CPLJSONObject::Type::String)
885 : {
886 340 : const auto str = obj.ToString();
887 170 : DtypeElt elt;
888 170 : GDALDataType eDT = GDT_Unknown;
889 :
890 170 : if (str == "bool") // boolean
891 : {
892 0 : elt.nativeType = DtypeElt::NativeType::BOOLEAN;
893 0 : eDT = GDT_Byte;
894 : }
895 170 : else if (str == "int8")
896 : {
897 6 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
898 6 : eDT = GDT_Int8;
899 : }
900 164 : else if (str == "uint8")
901 : {
902 65 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
903 65 : eDT = GDT_Byte;
904 : }
905 99 : else if (str == "int16")
906 : {
907 10 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
908 10 : eDT = GDT_Int16;
909 : }
910 89 : else if (str == "uint16")
911 : {
912 7 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
913 7 : eDT = GDT_UInt16;
914 : }
915 82 : else if (str == "int32")
916 : {
917 7 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
918 7 : eDT = GDT_Int32;
919 : }
920 75 : else if (str == "uint32")
921 : {
922 7 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
923 7 : eDT = GDT_UInt32;
924 : }
925 68 : else if (str == "int64")
926 : {
927 7 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
928 7 : eDT = GDT_Int64;
929 : }
930 61 : else if (str == "uint64")
931 : {
932 6 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
933 6 : eDT = GDT_UInt64;
934 : }
935 55 : else if (str == "float16")
936 : {
937 0 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
938 0 : elt.nativeSize = 2;
939 0 : elt.gdalTypeIsApproxOfNative = true;
940 0 : eDT = GDT_Float32;
941 : }
942 55 : else if (str == "float32")
943 : {
944 9 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
945 9 : eDT = GDT_Float32;
946 : }
947 46 : else if (str == "float64")
948 : {
949 15 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
950 15 : eDT = GDT_Float64;
951 : }
952 31 : else if (str == "complex64")
953 : {
954 15 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
955 15 : eDT = GDT_CFloat32;
956 : }
957 16 : else if (str == "complex128")
958 : {
959 15 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
960 15 : eDT = GDT_CFloat64;
961 : }
962 : else
963 1 : break;
964 :
965 169 : elt.gdalType = GDALExtendedDataType::Create(eDT);
966 169 : elt.gdalSize = elt.gdalType.GetSize();
967 169 : if (!elt.gdalTypeIsApproxOfNative)
968 169 : elt.nativeSize = elt.gdalSize;
969 :
970 169 : if (elt.nativeSize > 1)
971 : {
972 98 : elt.needByteSwapping = (CPL_IS_LSB == 0);
973 : }
974 :
975 169 : elts.emplace_back(elt);
976 169 : return GDALExtendedDataType::Create(eDT);
977 : }
978 : } while (false);
979 1 : CPLError(CE_Failure, CPLE_AppDefined,
980 : "Invalid or unsupported format for data_type: %s",
981 2 : obj.ToString().c_str());
982 1 : return GDALExtendedDataType::Create(GDT_Unknown);
983 : }
984 :
985 : /************************************************************************/
986 : /* ParseNoDataStringAsDouble() */
987 : /************************************************************************/
988 :
989 37 : static double ParseNoDataStringAsDouble(const std::string &osVal, bool &bOK)
990 : {
991 37 : double dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
992 37 : if (osVal == "NaN")
993 : {
994 : // initialized above
995 : }
996 15 : else if (osVal == "Infinity" || osVal == "+Infinity")
997 : {
998 5 : dfNoDataValue = std::numeric_limits<double>::infinity();
999 : }
1000 10 : else if (osVal == "-Infinity")
1001 : {
1002 5 : dfNoDataValue = -std::numeric_limits<double>::infinity();
1003 : }
1004 : else
1005 : {
1006 5 : bOK = false;
1007 : }
1008 37 : return dfNoDataValue;
1009 : }
1010 :
1011 : /************************************************************************/
1012 : /* ParseNoDataComponent() */
1013 : /************************************************************************/
1014 :
1015 : template <typename T, typename Tint>
1016 40 : static T ParseNoDataComponent(const CPLJSONObject &oObj, bool &bOK)
1017 : {
1018 40 : if (oObj.GetType() == CPLJSONObject::Type::Integer ||
1019 62 : oObj.GetType() == CPLJSONObject::Type::Long ||
1020 22 : oObj.GetType() == CPLJSONObject::Type::Double)
1021 : {
1022 22 : return static_cast<T>(oObj.ToDouble());
1023 : }
1024 18 : else if (oObj.GetType() == CPLJSONObject::Type::String)
1025 : {
1026 54 : const auto osVal = oObj.ToString();
1027 18 : if (STARTS_WITH(osVal.c_str(), "0x"))
1028 : {
1029 2 : if (osVal.size() > 2 + 2 * sizeof(T))
1030 : {
1031 0 : bOK = false;
1032 0 : return 0;
1033 : }
1034 2 : Tint nVal = static_cast<Tint>(
1035 2 : std::strtoull(osVal.c_str() + 2, nullptr, 16));
1036 : T fVal;
1037 : static_assert(sizeof(nVal) == sizeof(fVal),
1038 : "sizeof(nVal) == sizeof(dfVal)");
1039 2 : memcpy(&fVal, &nVal, sizeof(nVal));
1040 2 : return fVal;
1041 : }
1042 : else
1043 : {
1044 16 : return static_cast<T>(ParseNoDataStringAsDouble(osVal, bOK));
1045 : }
1046 : }
1047 : else
1048 : {
1049 0 : bOK = false;
1050 0 : return 0;
1051 : }
1052 : }
1053 :
1054 : /************************************************************************/
1055 : /* ZarrV3Group::LoadArray() */
1056 : /************************************************************************/
1057 :
1058 : std::shared_ptr<ZarrArray>
1059 183 : ZarrV3Group::LoadArray(const std::string &osArrayName,
1060 : const std::string &osZarrayFilename,
1061 : const CPLJSONObject &oRoot) const
1062 : {
1063 : // Add osZarrayFilename to m_poSharedResource during the scope
1064 : // of this function call.
1065 183 : ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
1066 366 : osZarrayFilename);
1067 183 : if (!filenameAdder.ok())
1068 0 : return nullptr;
1069 :
1070 : // Warn about unknown members (the spec suggests to error out, but let be
1071 : // a bit more lenient)
1072 1766 : for (const auto &oNode : oRoot.GetChildren())
1073 : {
1074 3166 : const auto osName = oNode.GetName();
1075 4200 : if (osName != "zarr_format" && osName != "node_type" &&
1076 3105 : osName != "shape" && osName != "chunk_grid" &&
1077 2013 : osName != "data_type" && osName != "chunk_key_encoding" &&
1078 795 : osName != "fill_value" &&
1079 : // Below are optional
1080 627 : osName != "dimension_names" && osName != "codecs" &&
1081 3111 : osName != "storage_transformers" && osName != "attributes")
1082 : {
1083 4 : CPLError(CE_Warning, CPLE_AppDefined,
1084 : "%s array definition contains a unknown member (%s). "
1085 : "Interpretation of the array might be wrong.",
1086 : osZarrayFilename.c_str(), osName.c_str());
1087 : }
1088 : }
1089 :
1090 549 : const auto oStorageTransformers = oRoot["storage_transformers"].ToArray();
1091 183 : if (oStorageTransformers.Size() > 0)
1092 : {
1093 1 : CPLError(CE_Failure, CPLE_AppDefined,
1094 : "storage_transformers are not supported.");
1095 1 : return nullptr;
1096 : }
1097 :
1098 546 : const auto oShape = oRoot["shape"].ToArray();
1099 182 : if (!oShape.IsValid())
1100 : {
1101 2 : CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
1102 2 : return nullptr;
1103 : }
1104 :
1105 : // Parse chunk_grid
1106 540 : const auto oChunkGrid = oRoot["chunk_grid"];
1107 180 : if (oChunkGrid.GetType() != CPLJSONObject::Type::Object)
1108 : {
1109 1 : CPLError(CE_Failure, CPLE_AppDefined,
1110 : "chunk_grid missing or not an object");
1111 1 : return nullptr;
1112 : }
1113 :
1114 537 : const auto oChunkGridName = oChunkGrid["name"];
1115 179 : if (oChunkGridName.ToString() != "regular")
1116 : {
1117 1 : CPLError(CE_Failure, CPLE_AppDefined,
1118 : "Only chunk_grid.name = regular supported");
1119 1 : return nullptr;
1120 : }
1121 :
1122 534 : const auto oChunks = oChunkGrid["configuration"]["chunk_shape"].ToArray();
1123 178 : if (!oChunks.IsValid())
1124 : {
1125 1 : CPLError(
1126 : CE_Failure, CPLE_AppDefined,
1127 : "chunk_grid.configuration.chunk_shape missing or not an array");
1128 1 : return nullptr;
1129 : }
1130 :
1131 177 : if (oShape.Size() != oChunks.Size())
1132 : {
1133 1 : CPLError(CE_Failure, CPLE_AppDefined,
1134 : "shape and chunks arrays are of different size");
1135 1 : return nullptr;
1136 : }
1137 :
1138 : // Parse chunk_key_encoding
1139 528 : const auto oChunkKeyEncoding = oRoot["chunk_key_encoding"];
1140 176 : if (oChunkKeyEncoding.GetType() != CPLJSONObject::Type::Object)
1141 : {
1142 1 : CPLError(CE_Failure, CPLE_AppDefined,
1143 : "chunk_key_encoding missing or not an object");
1144 1 : return nullptr;
1145 : }
1146 :
1147 350 : std::string osDimSeparator;
1148 175 : bool bV2ChunkKeyEncoding = false;
1149 525 : const auto oChunkKeyEncodingName = oChunkKeyEncoding["name"];
1150 175 : if (oChunkKeyEncodingName.ToString() == "default")
1151 : {
1152 171 : osDimSeparator = "/";
1153 : }
1154 4 : else if (oChunkKeyEncodingName.ToString() == "v2")
1155 : {
1156 3 : osDimSeparator = ".";
1157 3 : bV2ChunkKeyEncoding = true;
1158 : }
1159 : else
1160 : {
1161 1 : CPLError(CE_Failure, CPLE_AppDefined,
1162 : "Unsupported chunk_key_encoding.name");
1163 1 : return nullptr;
1164 : }
1165 :
1166 : {
1167 348 : auto oConfiguration = oChunkKeyEncoding["configuration"];
1168 174 : if (oConfiguration.GetType() == CPLJSONObject::Type::Object)
1169 : {
1170 256 : auto oSeparator = oConfiguration["separator"];
1171 128 : if (oSeparator.IsValid())
1172 : {
1173 128 : osDimSeparator = oSeparator.ToString();
1174 128 : if (osDimSeparator != "/" && osDimSeparator != ".")
1175 : {
1176 1 : CPLError(CE_Failure, CPLE_AppDefined,
1177 : "Separator can only be '/' or '.'");
1178 1 : return nullptr;
1179 : }
1180 : }
1181 : }
1182 : }
1183 :
1184 519 : CPLJSONObject oAttributes = oRoot["attributes"];
1185 :
1186 : // Deep-clone of oAttributes
1187 173 : if (oAttributes.IsValid())
1188 : {
1189 123 : oAttributes = oAttributes.Clone();
1190 : }
1191 :
1192 346 : std::vector<std::shared_ptr<GDALDimension>> aoDims;
1193 395 : for (int i = 0; i < oShape.Size(); ++i)
1194 : {
1195 222 : const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
1196 222 : if (nSize == 0)
1197 : {
1198 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
1199 0 : return nullptr;
1200 : }
1201 222 : aoDims.emplace_back(std::make_shared<ZarrDimension>(
1202 222 : m_poSharedResource,
1203 444 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1204 444 : std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
1205 222 : nSize));
1206 : }
1207 :
1208 : // Deal with dimension_names
1209 519 : const auto dimensionNames = oRoot["dimension_names"];
1210 :
1211 166 : const auto FindDimension = [this, &aoDims, &osArrayName, &oAttributes](
1212 : const std::string &osDimName,
1213 2212 : std::shared_ptr<GDALDimension> &poDim, int i)
1214 : {
1215 166 : auto oIter = m_oMapDimensions.find(osDimName);
1216 166 : if (oIter != m_oMapDimensions.end())
1217 : {
1218 0 : if (m_bDimSizeInUpdate ||
1219 0 : oIter->second->GetSize() == poDim->GetSize())
1220 : {
1221 0 : poDim = oIter->second;
1222 0 : return true;
1223 : }
1224 : else
1225 : {
1226 0 : CPLError(CE_Warning, CPLE_AppDefined,
1227 : "Size of _ARRAY_DIMENSIONS[%d] different "
1228 : "from the one of shape",
1229 : i);
1230 0 : return false;
1231 : }
1232 : }
1233 :
1234 : // Try to load the indexing variable.
1235 : // Not in m_oMapMDArrays,
1236 : // then stat() the indexing variable.
1237 330 : else if (osArrayName != osDimName &&
1238 330 : m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
1239 : {
1240 328 : std::string osDirName = m_osDirectoryName;
1241 : while (true)
1242 : {
1243 : const std::string osArrayFilenameDim = CPLFormFilenameSafe(
1244 483 : CPLFormFilenameSafe(osDirName.c_str(), osDimName.c_str(),
1245 : nullptr)
1246 : .c_str(),
1247 483 : "zarr.json", nullptr);
1248 : VSIStatBufL sStat;
1249 483 : if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
1250 : {
1251 4 : CPLJSONDocument oDoc;
1252 2 : if (oDoc.Load(osArrayFilenameDim))
1253 : {
1254 2 : LoadArray(osDimName, osArrayFilenameDim,
1255 4 : oDoc.GetRoot());
1256 : }
1257 : }
1258 : else
1259 : {
1260 : // Recurse to upper level for datasets such as
1261 : // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
1262 : const std::string osDirNameNew =
1263 481 : CPLGetPathSafe(osDirName.c_str());
1264 481 : if (!osDirNameNew.empty() && osDirNameNew != osDirName)
1265 : {
1266 319 : osDirName = osDirNameNew;
1267 319 : continue;
1268 : }
1269 : }
1270 164 : break;
1271 319 : }
1272 : }
1273 :
1274 166 : oIter = m_oMapDimensions.find(osDimName);
1275 : // cppcheck-suppress knownConditionTrueFalse
1276 168 : if (oIter != m_oMapDimensions.end() &&
1277 2 : oIter->second->GetSize() == poDim->GetSize())
1278 : {
1279 2 : poDim = oIter->second;
1280 2 : return true;
1281 : }
1282 :
1283 328 : std::string osType;
1284 328 : std::string osDirection;
1285 164 : if (aoDims.size() == 1 && osArrayName == osDimName)
1286 : {
1287 2 : ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
1288 : osDirection);
1289 : }
1290 :
1291 : auto poDimLocal = std::make_shared<ZarrDimension>(
1292 164 : m_poSharedResource,
1293 328 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1294 328 : GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
1295 164 : poDimLocal->SetXArrayDimension();
1296 164 : m_oMapDimensions[osDimName] = poDimLocal;
1297 164 : poDim = poDimLocal;
1298 164 : return true;
1299 173 : };
1300 :
1301 173 : if (dimensionNames.GetType() == CPLJSONObject::Type::Array)
1302 : {
1303 112 : const auto arrayDims = dimensionNames.ToArray();
1304 112 : if (arrayDims.Size() == oShape.Size())
1305 : {
1306 277 : for (int i = 0; i < oShape.Size(); ++i)
1307 : {
1308 166 : if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
1309 : {
1310 498 : const auto osDimName = arrayDims[i].ToString();
1311 166 : FindDimension(osDimName, aoDims[i], i);
1312 : }
1313 : }
1314 : }
1315 : else
1316 : {
1317 1 : CPLError(
1318 : CE_Failure, CPLE_AppDefined,
1319 : "Size of dimension_names[] different from the one of shape");
1320 1 : return nullptr;
1321 : }
1322 : }
1323 61 : else if (dimensionNames.IsValid())
1324 : {
1325 1 : CPLError(CE_Failure, CPLE_AppDefined,
1326 : "dimension_names should be an array");
1327 1 : return nullptr;
1328 : }
1329 :
1330 513 : auto oDtype = oRoot["data_type"];
1331 171 : if (!oDtype.IsValid())
1332 : {
1333 1 : CPLError(CE_Failure, CPLE_NotSupported, "data_type missing");
1334 1 : return nullptr;
1335 : }
1336 170 : if (oDtype["fallback"].IsValid())
1337 1 : oDtype = oDtype["fallback"];
1338 340 : std::vector<DtypeElt> aoDtypeElts;
1339 340 : const auto oType = ParseDtypeV3(oDtype, aoDtypeElts);
1340 340 : if (oType.GetClass() == GEDTC_NUMERIC &&
1341 170 : oType.GetNumericDataType() == GDT_Unknown)
1342 1 : return nullptr;
1343 :
1344 338 : std::vector<GUInt64> anBlockSize;
1345 169 : if (!ZarrArray::ParseChunkSize(oChunks, oType, anBlockSize))
1346 1 : return nullptr;
1347 :
1348 336 : std::vector<GByte> abyNoData;
1349 :
1350 504 : auto oFillValue = oRoot["fill_value"];
1351 168 : auto eFillValueType = oFillValue.GetType();
1352 :
1353 168 : if (!oFillValue.IsValid())
1354 : {
1355 0 : CPLError(CE_Warning, CPLE_AppDefined, "Missing fill_value is invalid");
1356 : }
1357 168 : else if (eFillValueType == CPLJSONObject::Type::Null)
1358 : {
1359 97 : CPLError(CE_Warning, CPLE_AppDefined, "fill_value = null is invalid");
1360 : }
1361 71 : else if (GDALDataTypeIsComplex(oType.GetNumericDataType()) &&
1362 : eFillValueType != CPLJSONObject::Type::Array)
1363 : {
1364 4 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1365 4 : return nullptr;
1366 : }
1367 67 : else if (eFillValueType == CPLJSONObject::Type::String)
1368 : {
1369 54 : const auto osFillValue = oFillValue.ToString();
1370 27 : if (STARTS_WITH(osFillValue.c_str(), "0x"))
1371 : {
1372 3 : if (osFillValue.size() > 2 + 2 * oType.GetSize())
1373 : {
1374 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1375 1 : return nullptr;
1376 : }
1377 : uint64_t nVal = static_cast<uint64_t>(
1378 3 : std::strtoull(osFillValue.c_str() + 2, nullptr, 16));
1379 3 : if (oType.GetSize() == 4)
1380 : {
1381 1 : abyNoData.resize(oType.GetSize());
1382 1 : uint32_t nTmp = static_cast<uint32_t>(nVal);
1383 1 : memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
1384 : }
1385 2 : else if (oType.GetSize() == 8)
1386 : {
1387 1 : abyNoData.resize(oType.GetSize());
1388 1 : memcpy(&abyNoData[0], &nVal, sizeof(nVal));
1389 : }
1390 : else
1391 : {
1392 1 : CPLError(CE_Failure, CPLE_AppDefined,
1393 : "Hexadecimal representation of fill_value no "
1394 : "supported for this data type");
1395 1 : return nullptr;
1396 : }
1397 : }
1398 24 : else if (STARTS_WITH(osFillValue.c_str(), "0b"))
1399 : {
1400 3 : if (osFillValue.size() > 2 + 8 * oType.GetSize())
1401 : {
1402 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1403 1 : return nullptr;
1404 : }
1405 : uint64_t nVal = static_cast<uint64_t>(
1406 3 : std::strtoull(osFillValue.c_str() + 2, nullptr, 2));
1407 3 : if (oType.GetSize() == 4)
1408 : {
1409 1 : abyNoData.resize(oType.GetSize());
1410 1 : uint32_t nTmp = static_cast<uint32_t>(nVal);
1411 1 : memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
1412 : }
1413 2 : else if (oType.GetSize() == 8)
1414 : {
1415 1 : abyNoData.resize(oType.GetSize());
1416 1 : memcpy(&abyNoData[0], &nVal, sizeof(nVal));
1417 : }
1418 : else
1419 : {
1420 1 : CPLError(CE_Failure, CPLE_AppDefined,
1421 : "Binary representation of fill_value no supported for "
1422 : "this data type");
1423 1 : return nullptr;
1424 : }
1425 : }
1426 : else
1427 : {
1428 21 : bool bOK = true;
1429 21 : double dfNoDataValue = ParseNoDataStringAsDouble(osFillValue, bOK);
1430 21 : if (!bOK)
1431 : {
1432 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1433 2 : return nullptr;
1434 : }
1435 20 : else if (oType.GetNumericDataType() == GDT_Float32)
1436 : {
1437 7 : const float fNoDataValue = static_cast<float>(dfNoDataValue);
1438 7 : abyNoData.resize(sizeof(fNoDataValue));
1439 7 : memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
1440 : }
1441 13 : else if (oType.GetNumericDataType() == GDT_Float64)
1442 : {
1443 12 : abyNoData.resize(sizeof(dfNoDataValue));
1444 12 : memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
1445 : }
1446 : else
1447 : {
1448 1 : CPLError(CE_Failure, CPLE_AppDefined,
1449 : "Invalid fill_value for this data type");
1450 1 : return nullptr;
1451 : }
1452 : }
1453 : }
1454 40 : else if (eFillValueType == CPLJSONObject::Type::Boolean ||
1455 25 : eFillValueType == CPLJSONObject::Type::Integer ||
1456 25 : eFillValueType == CPLJSONObject::Type::Long ||
1457 : eFillValueType == CPLJSONObject::Type::Double)
1458 : {
1459 16 : const double dfNoDataValue = oFillValue.ToDouble();
1460 16 : if (oType.GetNumericDataType() == GDT_Int64)
1461 : {
1462 : const int64_t nNoDataValue =
1463 1 : static_cast<int64_t>(oFillValue.ToLong());
1464 1 : abyNoData.resize(oType.GetSize());
1465 1 : GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1466 : oType.GetNumericDataType(), 0, 1);
1467 : }
1468 15 : else if (oType.GetNumericDataType() == GDT_UInt64 &&
1469 : /* we can't really deal with nodata value between */
1470 : /* int64::max and uint64::max due to json-c limitations */
1471 0 : dfNoDataValue >= 0)
1472 : {
1473 : const int64_t nNoDataValue =
1474 0 : static_cast<int64_t>(oFillValue.ToLong());
1475 0 : abyNoData.resize(oType.GetSize());
1476 0 : GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1477 : oType.GetNumericDataType(), 0, 1);
1478 : }
1479 : else
1480 : {
1481 15 : abyNoData.resize(oType.GetSize());
1482 15 : GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
1483 : oType.GetNumericDataType(), 0, 1);
1484 16 : }
1485 : }
1486 24 : else if (eFillValueType == CPLJSONObject::Type::Array)
1487 : {
1488 24 : const auto oFillValueArray = oFillValue.ToArray();
1489 44 : if (oFillValueArray.Size() == 2 &&
1490 20 : GDALDataTypeIsComplex(oType.GetNumericDataType()))
1491 : {
1492 20 : if (oType.GetNumericDataType() == GDT_CFloat64)
1493 : {
1494 10 : bool bOK = true;
1495 : const double adfNoDataValue[2] = {
1496 10 : ParseNoDataComponent<double, uint64_t>(oFillValueArray[0],
1497 : bOK),
1498 10 : ParseNoDataComponent<double, uint64_t>(oFillValueArray[1],
1499 : bOK),
1500 20 : };
1501 10 : if (!bOK)
1502 : {
1503 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1504 2 : return nullptr;
1505 : }
1506 8 : abyNoData.resize(oType.GetSize());
1507 8 : CPLAssert(sizeof(adfNoDataValue) == oType.GetSize());
1508 8 : memcpy(abyNoData.data(), adfNoDataValue,
1509 : sizeof(adfNoDataValue));
1510 : }
1511 : else
1512 : {
1513 10 : CPLAssert(oType.GetNumericDataType() == GDT_CFloat32);
1514 10 : bool bOK = true;
1515 : const float afNoDataValue[2] = {
1516 10 : ParseNoDataComponent<float, uint32_t>(oFillValueArray[0],
1517 : bOK),
1518 10 : ParseNoDataComponent<float, uint32_t>(oFillValueArray[1],
1519 : bOK),
1520 20 : };
1521 10 : if (!bOK)
1522 : {
1523 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1524 2 : return nullptr;
1525 : }
1526 8 : abyNoData.resize(oType.GetSize());
1527 8 : CPLAssert(sizeof(afNoDataValue) == oType.GetSize());
1528 8 : memcpy(abyNoData.data(), afNoDataValue, sizeof(afNoDataValue));
1529 : }
1530 : }
1531 : else
1532 : {
1533 4 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1534 4 : return nullptr;
1535 : }
1536 : }
1537 : else
1538 : {
1539 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1540 0 : return nullptr;
1541 : }
1542 :
1543 456 : const auto oCodecs = oRoot["codecs"].ToArray();
1544 152 : std::unique_ptr<ZarrV3CodecSequence> poCodecs;
1545 152 : if (oCodecs.Size() > 0)
1546 : {
1547 : // Byte swapping will be done by the codec chain
1548 65 : aoDtypeElts.back().needByteSwapping = false;
1549 :
1550 65 : ZarrArrayMetadata oInputArrayMetadata;
1551 151 : for (auto &nSize : anBlockSize)
1552 86 : oInputArrayMetadata.anBlockSizes.push_back(
1553 86 : static_cast<size_t>(nSize));
1554 65 : oInputArrayMetadata.oElt = aoDtypeElts.back();
1555 65 : poCodecs = std::make_unique<ZarrV3CodecSequence>(oInputArrayMetadata);
1556 65 : if (!poCodecs->InitFromJson(oCodecs))
1557 0 : return nullptr;
1558 : }
1559 :
1560 : auto poArray =
1561 152 : ZarrV3Array::Create(m_poSharedResource, GetFullName(), osArrayName,
1562 304 : aoDims, oType, aoDtypeElts, anBlockSize);
1563 152 : if (!poArray)
1564 1 : return nullptr;
1565 151 : poArray->SetUpdatable(m_bUpdatable); // must be set before SetAttributes()
1566 151 : poArray->SetFilename(osZarrayFilename);
1567 151 : poArray->SetIsV2ChunkKeyEncoding(bV2ChunkKeyEncoding);
1568 151 : poArray->SetDimSeparator(osDimSeparator);
1569 151 : if (!abyNoData.empty())
1570 : {
1571 54 : poArray->RegisterNoDataValue(abyNoData.data());
1572 : }
1573 151 : poArray->ParseSpecialAttributes(m_pSelf.lock(), oAttributes);
1574 151 : poArray->SetAttributes(oAttributes);
1575 151 : poArray->SetDtype(oDtype);
1576 151 : if (poCodecs)
1577 65 : poArray->SetCodecs(std::move(poCodecs));
1578 151 : RegisterArray(poArray);
1579 :
1580 : // If this is an indexing variable, attach it to the dimension.
1581 151 : if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
1582 : {
1583 2 : auto oIter = m_oMapDimensions.find(poArray->GetName());
1584 2 : if (oIter != m_oMapDimensions.end())
1585 : {
1586 2 : oIter->second->SetIndexingVariable(poArray);
1587 : }
1588 : }
1589 :
1590 151 : if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
1591 : "CACHE_TILE_PRESENCE", "NO")))
1592 : {
1593 2 : poArray->CacheTilePresence();
1594 : }
1595 :
1596 151 : return poArray;
1597 : }
|