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