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