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