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