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