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 "netcdf_cf_constants.h" // for CF_UNITS, etc
19 :
20 : #include <algorithm>
21 : #include <cassert>
22 : #include <cstdlib>
23 : #include <limits>
24 : #include <map>
25 : #include <set>
26 :
27 : /************************************************************************/
28 : /* ZarrV2Array::ZarrV2Array() */
29 : /************************************************************************/
30 :
31 963 : ZarrV2Array::ZarrV2Array(
32 : const std::shared_ptr<ZarrSharedResource> &poSharedResource,
33 : const std::string &osParentName, const std::string &osName,
34 : const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
35 : const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
36 963 : const std::vector<GUInt64> &anBlockSize, bool bFortranOrder)
37 : : GDALAbstractMDArray(osParentName, osName),
38 : ZarrArray(poSharedResource, osParentName, osName, aoDims, oType,
39 : aoDtypeElts, anBlockSize),
40 963 : m_bFortranOrder(bFortranOrder)
41 : {
42 963 : m_oCompressorJSon.Deinit();
43 963 : }
44 :
45 : /************************************************************************/
46 : /* ZarrV2Array::Create() */
47 : /************************************************************************/
48 :
49 : std::shared_ptr<ZarrV2Array>
50 963 : ZarrV2Array::Create(const std::shared_ptr<ZarrSharedResource> &poSharedResource,
51 : const std::string &osParentName, const std::string &osName,
52 : const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
53 : const GDALExtendedDataType &oType,
54 : const std::vector<DtypeElt> &aoDtypeElts,
55 : const std::vector<GUInt64> &anBlockSize, bool bFortranOrder)
56 : {
57 : auto arr = std::shared_ptr<ZarrV2Array>(
58 : new ZarrV2Array(poSharedResource, osParentName, osName, aoDims, oType,
59 1926 : aoDtypeElts, anBlockSize, bFortranOrder));
60 963 : if (arr->m_nTotalTileCount == 0)
61 1 : return nullptr;
62 962 : arr->SetSelf(arr);
63 :
64 962 : return arr;
65 : }
66 :
67 : /************************************************************************/
68 : /* ~ZarrV2Array() */
69 : /************************************************************************/
70 :
71 1926 : ZarrV2Array::~ZarrV2Array()
72 : {
73 963 : ZarrV2Array::Flush();
74 1926 : }
75 :
76 : /************************************************************************/
77 : /* Flush() */
78 : /************************************************************************/
79 :
80 4207 : bool ZarrV2Array::Flush()
81 : {
82 4207 : if (!m_bValid)
83 12 : return true;
84 :
85 4195 : bool ret = ZarrV2Array::FlushDirtyTile();
86 :
87 4195 : if (m_bDefinitionModified)
88 : {
89 322 : if (!Serialize())
90 3 : ret = false;
91 322 : m_bDefinitionModified = false;
92 : }
93 :
94 4195 : CPLJSONArray j_ARRAY_DIMENSIONS;
95 4195 : bool bDimensionsModified = false;
96 4195 : if (!m_aoDims.empty())
97 : {
98 8047 : for (const auto &poDim : m_aoDims)
99 : {
100 : const auto poZarrDim =
101 5262 : dynamic_cast<const ZarrDimension *>(poDim.get());
102 5262 : if (poZarrDim && poZarrDim->IsXArrayDimension())
103 : {
104 4189 : if (poZarrDim->IsModified())
105 16 : bDimensionsModified = true;
106 4189 : j_ARRAY_DIMENSIONS.Add(poDim->GetName());
107 : }
108 : else
109 : {
110 1073 : j_ARRAY_DIMENSIONS = CPLJSONArray();
111 1073 : break;
112 : }
113 : }
114 : }
115 :
116 8331 : if (m_oAttrGroup.IsModified() || bDimensionsModified ||
117 4120 : (m_bNew && j_ARRAY_DIMENSIONS.Size() != 0) || m_bUnitModified ||
118 8331 : m_bOffsetModified || m_bScaleModified || m_bSRSModified)
119 : {
120 348 : m_bNew = false;
121 :
122 696 : auto oAttrs = SerializeSpecialAttributes();
123 :
124 348 : if (j_ARRAY_DIMENSIONS.Size() != 0)
125 : {
126 332 : oAttrs.Delete("_ARRAY_DIMENSIONS");
127 332 : oAttrs.Add("_ARRAY_DIMENSIONS", j_ARRAY_DIMENSIONS);
128 : }
129 :
130 696 : CPLJSONDocument oDoc;
131 348 : oDoc.SetRoot(oAttrs);
132 : const std::string osAttrFilename =
133 348 : CPLFormFilenameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
134 696 : ".zattrs", nullptr);
135 348 : if (!oDoc.Save(osAttrFilename))
136 7 : ret = false;
137 348 : m_poSharedResource->SetZMetadataItem(osAttrFilename, oAttrs);
138 : }
139 :
140 4195 : return ret;
141 : }
142 :
143 : /************************************************************************/
144 : /* StripUselessItemsFromCompressorConfiguration() */
145 : /************************************************************************/
146 :
147 28 : static void StripUselessItemsFromCompressorConfiguration(CPLJSONObject &o)
148 : {
149 28 : if (o.GetType() == CPLJSONObject::Type::Object)
150 : {
151 18 : o.Delete("num_threads"); // Blosc
152 18 : o.Delete("typesize"); // Blosc
153 18 : o.Delete("header"); // LZ4
154 : }
155 28 : }
156 :
157 : /************************************************************************/
158 : /* ZarrV2Array::Serialize() */
159 : /************************************************************************/
160 :
161 322 : bool ZarrV2Array::Serialize()
162 : {
163 644 : CPLJSONDocument oDoc;
164 644 : CPLJSONObject oRoot = oDoc.GetRoot();
165 :
166 644 : CPLJSONArray oChunks;
167 782 : for (const auto nBlockSize : m_anBlockSize)
168 : {
169 460 : oChunks.Add(static_cast<GInt64>(nBlockSize));
170 : }
171 322 : oRoot.Add("chunks", oChunks);
172 :
173 322 : if (m_oCompressorJSon.IsValid())
174 : {
175 28 : oRoot.Add("compressor", m_oCompressorJSon);
176 84 : CPLJSONObject compressor = oRoot["compressor"];
177 28 : StripUselessItemsFromCompressorConfiguration(compressor);
178 : }
179 : else
180 : {
181 294 : oRoot.AddNull("compressor");
182 : }
183 :
184 322 : if (m_dtype.GetType() == CPLJSONObject::Type::Object)
185 310 : oRoot.Add("dtype", m_dtype["dummy"]);
186 : else
187 12 : oRoot.Add("dtype", m_dtype);
188 :
189 322 : if (m_pabyNoData == nullptr)
190 : {
191 312 : oRoot.AddNull("fill_value");
192 : }
193 : else
194 : {
195 10 : switch (m_oType.GetClass())
196 : {
197 8 : case GEDTC_NUMERIC:
198 : {
199 8 : SerializeNumericNoData(oRoot);
200 8 : break;
201 : }
202 :
203 1 : case GEDTC_STRING:
204 : {
205 : char *pszStr;
206 1 : char **ppszStr = reinterpret_cast<char **>(m_pabyNoData);
207 1 : memcpy(&pszStr, ppszStr, sizeof(pszStr));
208 1 : if (pszStr)
209 : {
210 : const size_t nNativeSize =
211 1 : m_aoDtypeElts.back().nativeOffset +
212 1 : m_aoDtypeElts.back().nativeSize;
213 3 : char *base64 = CPLBase64Encode(
214 1 : static_cast<int>(std::min(nNativeSize, strlen(pszStr))),
215 : reinterpret_cast<const GByte *>(pszStr));
216 1 : oRoot.Add("fill_value", base64);
217 1 : CPLFree(base64);
218 : }
219 : else
220 : {
221 0 : oRoot.AddNull("fill_value");
222 : }
223 1 : break;
224 : }
225 :
226 1 : case GEDTC_COMPOUND:
227 : {
228 1 : const size_t nNativeSize = m_aoDtypeElts.back().nativeOffset +
229 1 : m_aoDtypeElts.back().nativeSize;
230 2 : std::vector<GByte> nativeNoData(nNativeSize);
231 1 : EncodeElt(m_aoDtypeElts, m_pabyNoData, &nativeNoData[0]);
232 1 : char *base64 = CPLBase64Encode(static_cast<int>(nNativeSize),
233 1 : nativeNoData.data());
234 1 : oRoot.Add("fill_value", base64);
235 1 : CPLFree(base64);
236 : }
237 : }
238 : }
239 :
240 322 : if (m_oFiltersArray.Size() == 0)
241 321 : oRoot.AddNull("filters");
242 : else
243 1 : oRoot.Add("filters", m_oFiltersArray);
244 :
245 322 : oRoot.Add("order", m_bFortranOrder ? "F" : "C");
246 :
247 322 : CPLJSONArray oShape;
248 782 : for (const auto &poDim : m_aoDims)
249 : {
250 460 : oShape.Add(static_cast<GInt64>(poDim->GetSize()));
251 : }
252 322 : oRoot.Add("shape", oShape);
253 :
254 322 : oRoot.Add("zarr_format", 2);
255 :
256 322 : if (m_osDimSeparator != ".")
257 : {
258 10 : oRoot.Add("dimension_separator", m_osDimSeparator);
259 : }
260 :
261 322 : bool ret = oDoc.Save(m_osFilename);
262 :
263 322 : m_poSharedResource->SetZMetadataItem(m_osFilename, oRoot);
264 :
265 644 : return ret;
266 : }
267 :
268 : /************************************************************************/
269 : /* ZarrV2Array::NeedDecodedBuffer() */
270 : /************************************************************************/
271 :
272 11698 : bool ZarrV2Array::NeedDecodedBuffer() const
273 : {
274 : const size_t nSourceSize =
275 11698 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
276 11693 : if (m_oType.GetClass() == GEDTC_COMPOUND &&
277 6 : nSourceSize != m_oType.GetSize())
278 : {
279 4 : return true;
280 : }
281 11677 : else if (m_oType.GetClass() != GEDTC_STRING)
282 : {
283 23236 : for (const auto &elt : m_aoDtypeElts)
284 : {
285 11666 : if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative ||
286 11580 : elt.nativeType == DtypeElt::NativeType::STRING_ASCII ||
287 11578 : elt.nativeType == DtypeElt::NativeType::STRING_UNICODE)
288 : {
289 87 : return true;
290 : }
291 : }
292 : }
293 11594 : return false;
294 : }
295 :
296 : /************************************************************************/
297 : /* ZarrV2Array::AllocateWorkingBuffers() */
298 : /************************************************************************/
299 :
300 1951 : bool ZarrV2Array::AllocateWorkingBuffers() const
301 : {
302 1951 : if (m_bAllocateWorkingBuffersDone)
303 1435 : return m_bWorkingBuffersOK;
304 :
305 516 : m_bAllocateWorkingBuffersDone = true;
306 :
307 516 : size_t nSizeNeeded = m_nTileSize;
308 516 : if (m_bFortranOrder || m_oFiltersArray.Size() != 0)
309 : {
310 46 : if (nSizeNeeded > std::numeric_limits<size_t>::max() / 2)
311 : {
312 1 : CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
313 1 : return false;
314 : }
315 45 : nSizeNeeded *= 2;
316 : }
317 515 : if (NeedDecodedBuffer())
318 : {
319 43 : size_t nDecodedBufferSize = m_oType.GetSize();
320 126 : for (const auto &nBlockSize : m_anBlockSize)
321 : {
322 83 : if (nDecodedBufferSize > std::numeric_limits<size_t>::max() /
323 83 : static_cast<size_t>(nBlockSize))
324 : {
325 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
326 0 : return false;
327 : }
328 83 : nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
329 : }
330 43 : if (nSizeNeeded >
331 43 : std::numeric_limits<size_t>::max() - nDecodedBufferSize)
332 : {
333 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
334 0 : return false;
335 : }
336 43 : nSizeNeeded += nDecodedBufferSize;
337 : }
338 :
339 : // Reserve a buffer for tile content
340 517 : if (nSizeNeeded > 1024 * 1024 * 1024 &&
341 2 : !CPLTestBool(CPLGetConfigOption("ZARR_ALLOW_BIG_TILE_SIZE", "NO")))
342 : {
343 2 : CPLError(CE_Failure, CPLE_AppDefined,
344 : "Zarr tile allocation would require " CPL_FRMT_GUIB " bytes. "
345 : "By default the driver limits to 1 GB. To allow that memory "
346 : "allocation, set the ZARR_ALLOW_BIG_TILE_SIZE configuration "
347 : "option to YES.",
348 : static_cast<GUIntBig>(nSizeNeeded));
349 2 : return false;
350 : }
351 :
352 1026 : m_bWorkingBuffersOK = AllocateWorkingBuffers(
353 513 : m_abyRawTileData, m_abyTmpRawTileData, m_abyDecodedTileData);
354 513 : return m_bWorkingBuffersOK;
355 : }
356 :
357 11185 : bool ZarrV2Array::AllocateWorkingBuffers(
358 : ZarrByteVectorQuickResize &abyRawTileData,
359 : ZarrByteVectorQuickResize &abyTmpRawTileData,
360 : ZarrByteVectorQuickResize &abyDecodedTileData) const
361 : {
362 : // This method should NOT modify any ZarrArray member, as it is going to
363 : // be called concurrently from several threads.
364 :
365 : // Set those #define to avoid accidental use of some global variables
366 : #define m_abyTmpRawTileData cannot_use_here
367 : #define m_abyRawTileData cannot_use_here
368 : #define m_abyDecodedTileData cannot_use_here
369 :
370 : try
371 : {
372 11185 : abyRawTileData.resize(m_nTileSize);
373 11175 : if (m_bFortranOrder || m_oFiltersArray.Size() != 0)
374 45 : abyTmpRawTileData.resize(m_nTileSize);
375 : }
376 0 : catch (const std::bad_alloc &e)
377 : {
378 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
379 0 : return false;
380 : }
381 :
382 11166 : if (NeedDecodedBuffer())
383 : {
384 43 : size_t nDecodedBufferSize = m_oType.GetSize();
385 126 : for (const auto &nBlockSize : m_anBlockSize)
386 : {
387 83 : nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
388 : }
389 : try
390 : {
391 43 : abyDecodedTileData.resize(nDecodedBufferSize);
392 : }
393 0 : catch (const std::bad_alloc &e)
394 : {
395 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
396 0 : return false;
397 : }
398 : }
399 :
400 11178 : return true;
401 : #undef m_abyTmpRawTileData
402 : #undef m_abyRawTileData
403 : #undef m_abyDecodedTileData
404 : }
405 :
406 : /************************************************************************/
407 : /* ZarrV2Array::LoadTileData() */
408 : /************************************************************************/
409 :
410 4882 : bool ZarrV2Array::LoadTileData(const uint64_t *tileIndices,
411 : bool &bMissingTileOut) const
412 : {
413 9764 : return LoadTileData(tileIndices,
414 : false, // use mutex
415 4882 : m_psDecompressor, m_abyRawTileData, m_abyTmpRawTileData,
416 4882 : m_abyDecodedTileData, bMissingTileOut);
417 : }
418 :
419 15546 : bool ZarrV2Array::LoadTileData(const uint64_t *tileIndices, bool bUseMutex,
420 : const CPLCompressor *psDecompressor,
421 : ZarrByteVectorQuickResize &abyRawTileData,
422 : ZarrByteVectorQuickResize &abyTmpRawTileData,
423 : ZarrByteVectorQuickResize &abyDecodedTileData,
424 : bool &bMissingTileOut) const
425 : {
426 : // This method should NOT modify any ZarrArray member, as it is going to
427 : // be called concurrently from several threads.
428 :
429 : // Set those #define to avoid accidental use of some global variables
430 : #define m_abyTmpRawTileData cannot_use_here
431 : #define m_abyRawTileData cannot_use_here
432 : #define m_abyDecodedTileData cannot_use_here
433 : #define m_psDecompressor cannot_use_here
434 :
435 15546 : bMissingTileOut = false;
436 :
437 30658 : std::string osFilename = BuildTileFilename(tileIndices);
438 :
439 : // For network file systems, get the streaming version of the filename,
440 : // as we don't need arbitrary seeking in the file
441 15531 : osFilename = VSIFileManager::GetHandler(osFilename.c_str())
442 15553 : ->GetStreamingFilename(osFilename);
443 :
444 : // First if we have a tile presence cache, check tile presence from it
445 15537 : if (bUseMutex)
446 10651 : m_oMutex.lock();
447 30572 : auto poTilePresenceArray = OpenTilePresenceCache(false);
448 15554 : if (poTilePresenceArray)
449 : {
450 18 : std::vector<GUInt64> anTileIdx(m_aoDims.size());
451 18 : const std::vector<size_t> anCount(m_aoDims.size(), 1);
452 18 : const std::vector<GInt64> anArrayStep(m_aoDims.size(), 0);
453 18 : const std::vector<GPtrDiff_t> anBufferStride(m_aoDims.size(), 0);
454 18 : const auto eByteDT = GDALExtendedDataType::Create(GDT_Byte);
455 54 : for (size_t i = 0; i < m_aoDims.size(); ++i)
456 : {
457 36 : anTileIdx[i] = static_cast<GUInt64>(tileIndices[i]);
458 : }
459 18 : GByte byValue = 0;
460 18 : if (poTilePresenceArray->Read(anTileIdx.data(), anCount.data(),
461 : anArrayStep.data(), anBufferStride.data(),
462 36 : eByteDT, &byValue) &&
463 18 : byValue == 0)
464 : {
465 13 : if (bUseMutex)
466 0 : m_oMutex.unlock();
467 13 : CPLDebugOnly(ZARR_DEBUG_KEY, "Tile %s missing (=nodata)",
468 : osFilename.c_str());
469 13 : bMissingTileOut = true;
470 13 : return true;
471 : }
472 : }
473 15541 : if (bUseMutex)
474 10672 : m_oMutex.unlock();
475 :
476 15541 : VSILFILE *fp = nullptr;
477 : // This is the number of files returned in a S3 directory listing operation
478 15541 : constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
479 15541 : const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
480 : nullptr};
481 15541 : const auto nErrorBefore = CPLGetErrorCounter();
482 15559 : if ((m_osDimSeparator == "/" && !m_anBlockSize.empty() &&
483 31086 : m_anBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
484 15534 : (m_osDimSeparator != "/" &&
485 15508 : m_nTotalTileCount > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING))
486 : {
487 : // Avoid issuing ReadDir() when a lot of files are expected
488 : CPLConfigOptionSetter optionSetter("GDAL_DISABLE_READDIR_ON_OPEN",
489 10971 : "YES", true);
490 10893 : fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
491 : }
492 : else
493 : {
494 4561 : fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
495 : }
496 15388 : if (fp == nullptr)
497 : {
498 2714 : if (nErrorBefore != CPLGetErrorCounter())
499 : {
500 3 : return false;
501 : }
502 : else
503 : {
504 : // Missing files are OK and indicate nodata_value
505 2711 : CPLDebugOnly(ZARR_DEBUG_KEY, "Tile %s missing (=nodata)",
506 : osFilename.c_str());
507 2711 : bMissingTileOut = true;
508 2711 : return true;
509 : }
510 : }
511 :
512 12674 : bMissingTileOut = false;
513 12674 : bool bRet = true;
514 12674 : size_t nRawDataSize = abyRawTileData.size();
515 12629 : if (psDecompressor == nullptr)
516 : {
517 7197 : nRawDataSize = VSIFReadL(&abyRawTileData[0], 1, nRawDataSize, fp);
518 : }
519 : else
520 : {
521 5432 : VSIFSeekL(fp, 0, SEEK_END);
522 5352 : const auto nSize = VSIFTellL(fp);
523 5282 : VSIFSeekL(fp, 0, SEEK_SET);
524 5316 : if (nSize > static_cast<vsi_l_offset>(std::numeric_limits<int>::max()))
525 : {
526 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large tile %s",
527 : osFilename.c_str());
528 0 : bRet = false;
529 : }
530 : else
531 : {
532 10746 : ZarrByteVectorQuickResize abyCompressedData;
533 : try
534 : {
535 5254 : abyCompressedData.resize(static_cast<size_t>(nSize));
536 : }
537 0 : catch (const std::exception &)
538 : {
539 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
540 : "Cannot allocate memory for tile %s",
541 : osFilename.c_str());
542 0 : bRet = false;
543 : }
544 :
545 15800 : if (bRet &&
546 10553 : (abyCompressedData.empty() ||
547 5259 : VSIFReadL(&abyCompressedData[0], 1, abyCompressedData.size(),
548 5276 : fp) != abyCompressedData.size()))
549 : {
550 0 : CPLError(CE_Failure, CPLE_AppDefined,
551 : "Could not read tile %s correctly",
552 : osFilename.c_str());
553 0 : bRet = false;
554 : }
555 : else
556 : {
557 5170 : void *out_buffer = &abyRawTileData[0];
558 5171 : if (!psDecompressor->pfnFunc(
559 5245 : abyCompressedData.data(), abyCompressedData.size(),
560 : &out_buffer, &nRawDataSize, nullptr,
561 5284 : psDecompressor->user_data))
562 : {
563 3 : CPLError(CE_Failure, CPLE_AppDefined,
564 : "Decompression of tile %s failed",
565 : osFilename.c_str());
566 3 : bRet = false;
567 : }
568 : }
569 : }
570 : }
571 12506 : VSIFCloseL(fp);
572 12660 : if (!bRet)
573 3 : return false;
574 :
575 12850 : for (int i = m_oFiltersArray.Size(); i > 0;)
576 : {
577 12 : --i;
578 12 : const auto &oFilter = m_oFiltersArray[i];
579 24 : const auto osFilterId = oFilter["id"].ToString();
580 : const auto psFilterDecompressor =
581 22 : EQUAL(osFilterId.c_str(), "shuffle") ? ZarrGetShuffleDecompressor()
582 10 : : EQUAL(osFilterId.c_str(), "quantize")
583 18 : ? ZarrGetQuantizeDecompressor()
584 8 : : EQUAL(osFilterId.c_str(), "fixedscaleoffset")
585 8 : ? ZarrGetFixedScaleOffsetDecompressor()
586 4 : : CPLGetDecompressor(osFilterId.c_str());
587 12 : CPLAssert(psFilterDecompressor);
588 :
589 12 : CPLStringList aosOptions;
590 52 : for (const auto &obj : oFilter.GetChildren())
591 : {
592 80 : aosOptions.SetNameValue(obj.GetName().c_str(),
593 120 : obj.ToString().c_str());
594 : }
595 12 : void *out_buffer = &abyTmpRawTileData[0];
596 12 : size_t nOutSize = abyTmpRawTileData.size();
597 12 : if (!psFilterDecompressor->pfnFunc(
598 12 : abyRawTileData.data(), nRawDataSize, &out_buffer, &nOutSize,
599 12 : aosOptions.List(), psFilterDecompressor->user_data))
600 : {
601 0 : CPLError(CE_Failure, CPLE_AppDefined,
602 : "Filter %s for tile %s failed", osFilterId.c_str(),
603 : osFilename.c_str());
604 0 : return false;
605 : }
606 :
607 12 : nRawDataSize = nOutSize;
608 12 : std::swap(abyRawTileData, abyTmpRawTileData);
609 : }
610 12402 : if (nRawDataSize != abyRawTileData.size())
611 : {
612 106 : CPLError(CE_Failure, CPLE_AppDefined,
613 : "Decompressed tile %s has not expected size after filters",
614 : osFilename.c_str());
615 0 : return false;
616 : }
617 :
618 12282 : if (m_bFortranOrder && !m_aoDims.empty())
619 : {
620 46 : BlockTranspose(abyRawTileData, abyTmpRawTileData, true);
621 46 : std::swap(abyRawTileData, abyTmpRawTileData);
622 : }
623 :
624 12282 : if (!abyDecodedTileData.empty())
625 : {
626 : const size_t nSourceSize =
627 326 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
628 326 : const auto nDTSize = m_oType.GetSize();
629 326 : const size_t nValues = abyDecodedTileData.size() / nDTSize;
630 326 : const GByte *pSrc = abyRawTileData.data();
631 326 : GByte *pDst = &abyDecodedTileData[0];
632 2424 : for (size_t i = 0; i < nValues;
633 1931 : i++, pSrc += nSourceSize, pDst += nDTSize)
634 : {
635 1931 : DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
636 : }
637 : }
638 :
639 12465 : return true;
640 :
641 : #undef m_abyTmpRawTileData
642 : #undef m_abyRawTileData
643 : #undef m_abyDecodedTileData
644 : #undef m_psDecompressor
645 : }
646 :
647 : /************************************************************************/
648 : /* ZarrV2Array::IAdviseRead() */
649 : /************************************************************************/
650 :
651 6 : bool ZarrV2Array::IAdviseRead(const GUInt64 *arrayStartIdx, const size_t *count,
652 : CSLConstList papszOptions) const
653 : {
654 12 : std::vector<uint64_t> anIndicesCur;
655 6 : int nThreadsMax = 0;
656 12 : std::vector<uint64_t> anReqTilesIndices;
657 6 : size_t nReqTiles = 0;
658 6 : if (!IAdviseReadCommon(arrayStartIdx, count, papszOptions, anIndicesCur,
659 : nThreadsMax, anReqTilesIndices, nReqTiles))
660 : {
661 2 : return false;
662 : }
663 4 : if (nThreadsMax <= 1)
664 : {
665 0 : return true;
666 : }
667 :
668 : const int nThreads =
669 4 : static_cast<int>(std::min(static_cast<size_t>(nThreadsMax), nReqTiles));
670 :
671 4 : CPLWorkerThreadPool *wtp = GDALGetGlobalThreadPool(nThreadsMax);
672 4 : if (wtp == nullptr)
673 0 : return false;
674 :
675 : struct JobStruct
676 : {
677 : JobStruct() = default;
678 :
679 : JobStruct(const JobStruct &) = delete;
680 : JobStruct &operator=(const JobStruct &) = delete;
681 :
682 : JobStruct(JobStruct &&) = default;
683 : JobStruct &operator=(JobStruct &&) = default;
684 :
685 : const ZarrV2Array *poArray = nullptr;
686 : bool *pbGlobalStatus = nullptr;
687 : int *pnRemainingThreads = nullptr;
688 : const std::vector<uint64_t> *panReqTilesIndices = nullptr;
689 : size_t nFirstIdx = 0;
690 : size_t nLastIdxNotIncluded = 0;
691 : };
692 :
693 4 : std::vector<JobStruct> asJobStructs;
694 :
695 4 : bool bGlobalStatus = true;
696 4 : int nRemainingThreads = nThreads;
697 : // Check for very highly overflow in below loop
698 4 : assert(static_cast<size_t>(nThreads) <
699 : std::numeric_limits<size_t>::max() / nReqTiles);
700 :
701 : // Setup jobs
702 20 : for (int i = 0; i < nThreads; i++)
703 : {
704 16 : JobStruct jobStruct;
705 16 : jobStruct.poArray = this;
706 16 : jobStruct.pbGlobalStatus = &bGlobalStatus;
707 16 : jobStruct.pnRemainingThreads = &nRemainingThreads;
708 16 : jobStruct.panReqTilesIndices = &anReqTilesIndices;
709 16 : jobStruct.nFirstIdx = static_cast<size_t>(i * nReqTiles / nThreads);
710 16 : jobStruct.nLastIdxNotIncluded = std::min(
711 16 : static_cast<size_t>((i + 1) * nReqTiles / nThreads), nReqTiles);
712 16 : asJobStructs.emplace_back(std::move(jobStruct));
713 : }
714 :
715 16 : const auto JobFunc = [](void *pThreadData)
716 : {
717 16 : const JobStruct *jobStruct =
718 : static_cast<const JobStruct *>(pThreadData);
719 :
720 16 : const auto poArray = jobStruct->poArray;
721 16 : const auto &aoDims = poArray->GetDimensions();
722 16 : const size_t l_nDims = poArray->GetDimensionCount();
723 16 : ZarrByteVectorQuickResize abyRawTileData;
724 16 : ZarrByteVectorQuickResize abyDecodedTileData;
725 16 : ZarrByteVectorQuickResize abyTmpRawTileData;
726 : const CPLCompressor *psDecompressor =
727 16 : CPLGetDecompressor(poArray->m_osDecompressorId.c_str());
728 :
729 10688 : for (size_t iReq = jobStruct->nFirstIdx;
730 10688 : iReq < jobStruct->nLastIdxNotIncluded; ++iReq)
731 : {
732 : // Check if we must early exit
733 : {
734 10672 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
735 10672 : if (!(*jobStruct->pbGlobalStatus))
736 0 : return;
737 : }
738 :
739 : const uint64_t *tileIndices =
740 10672 : jobStruct->panReqTilesIndices->data() + iReq * l_nDims;
741 :
742 10672 : uint64_t nTileIdx = 0;
743 32015 : for (size_t j = 0; j < l_nDims; ++j)
744 : {
745 21344 : if (j > 0)
746 10672 : nTileIdx *= aoDims[j - 1]->GetSize();
747 21343 : nTileIdx += tileIndices[j];
748 : }
749 :
750 10671 : if (!poArray->AllocateWorkingBuffers(
751 : abyRawTileData, abyTmpRawTileData, abyDecodedTileData))
752 : {
753 0 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
754 0 : *jobStruct->pbGlobalStatus = false;
755 0 : break;
756 : }
757 :
758 10668 : bool bIsEmpty = false;
759 10668 : bool success = poArray->LoadTileData(tileIndices,
760 : true, // use mutex
761 : psDecompressor, abyRawTileData,
762 : abyTmpRawTileData,
763 : abyDecodedTileData, bIsEmpty);
764 :
765 10439 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
766 10672 : if (!success)
767 : {
768 0 : *jobStruct->pbGlobalStatus = false;
769 0 : break;
770 : }
771 :
772 21344 : CachedTile cachedTile;
773 10672 : if (!bIsEmpty)
774 : {
775 10670 : if (!abyDecodedTileData.empty())
776 0 : std::swap(cachedTile.abyDecoded, abyDecodedTileData);
777 : else
778 10670 : std::swap(cachedTile.abyDecoded, abyRawTileData);
779 : }
780 10672 : poArray->m_oMapTileIndexToCachedTile[nTileIdx] =
781 21344 : std::move(cachedTile);
782 : }
783 :
784 16 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
785 16 : (*jobStruct->pnRemainingThreads)--;
786 : };
787 :
788 : // Start jobs
789 20 : for (int i = 0; i < nThreads; i++)
790 : {
791 16 : if (!wtp->SubmitJob(JobFunc, &asJobStructs[i]))
792 : {
793 0 : std::lock_guard<std::mutex> oLock(m_oMutex);
794 0 : bGlobalStatus = false;
795 0 : nRemainingThreads = i;
796 0 : break;
797 : }
798 : }
799 :
800 : // Wait for all jobs to be finished
801 : while (true)
802 : {
803 : {
804 16 : std::lock_guard<std::mutex> oLock(m_oMutex);
805 16 : if (nRemainingThreads == 0)
806 4 : break;
807 : }
808 12 : wtp->WaitEvent();
809 12 : }
810 :
811 4 : return bGlobalStatus;
812 : }
813 :
814 : /************************************************************************/
815 : /* ZarrV2Array::FlushDirtyTile() */
816 : /************************************************************************/
817 :
818 19916 : bool ZarrV2Array::FlushDirtyTile() const
819 : {
820 19916 : if (!m_bDirtyTile)
821 7846 : return true;
822 12070 : m_bDirtyTile = false;
823 :
824 24140 : std::string osFilename = BuildTileFilename(m_anCachedTiledIndices.data());
825 :
826 : const size_t nSourceSize =
827 12070 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
828 : const auto &abyTile =
829 12070 : m_abyDecodedTileData.empty() ? m_abyRawTileData : m_abyDecodedTileData;
830 :
831 12070 : if (IsEmptyTile(abyTile))
832 : {
833 690 : m_bCachedTiledEmpty = true;
834 :
835 : VSIStatBufL sStat;
836 690 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
837 : {
838 108 : CPLDebugOnly(ZARR_DEBUG_KEY,
839 : "Deleting tile %s that has now empty content",
840 : osFilename.c_str());
841 108 : return VSIUnlink(osFilename.c_str()) == 0;
842 : }
843 582 : return true;
844 : }
845 :
846 11380 : if (!m_abyDecodedTileData.empty())
847 : {
848 20 : const size_t nDTSize = m_oType.GetSize();
849 20 : const size_t nValues = m_abyDecodedTileData.size() / nDTSize;
850 20 : GByte *pDst = &m_abyRawTileData[0];
851 20 : const GByte *pSrc = m_abyDecodedTileData.data();
852 140 : for (size_t i = 0; i < nValues;
853 120 : i++, pDst += nSourceSize, pSrc += nDTSize)
854 : {
855 120 : EncodeElt(m_aoDtypeElts, pSrc, pDst);
856 : }
857 : }
858 :
859 11380 : if (m_bFortranOrder && !m_aoDims.empty())
860 : {
861 20 : BlockTranspose(m_abyRawTileData, m_abyTmpRawTileData, false);
862 20 : std::swap(m_abyRawTileData, m_abyTmpRawTileData);
863 : }
864 :
865 11380 : size_t nRawDataSize = m_abyRawTileData.size();
866 11383 : for (const auto &oFilter : m_oFiltersArray)
867 : {
868 8 : const auto osFilterId = oFilter["id"].ToString();
869 4 : if (osFilterId == "quantize" || osFilterId == "fixedscaleoffset")
870 : {
871 1 : CPLError(CE_Failure, CPLE_NotSupported,
872 : "%s filter not supported for writing", osFilterId.c_str());
873 1 : return false;
874 : }
875 : const auto psFilterCompressor =
876 3 : EQUAL(osFilterId.c_str(), "shuffle")
877 3 : ? ZarrGetShuffleCompressor()
878 2 : : CPLGetCompressor(osFilterId.c_str());
879 3 : CPLAssert(psFilterCompressor);
880 :
881 3 : CPLStringList aosOptions;
882 9 : for (const auto &obj : oFilter.GetChildren())
883 : {
884 12 : aosOptions.SetNameValue(obj.GetName().c_str(),
885 18 : obj.ToString().c_str());
886 : }
887 3 : void *out_buffer = &m_abyTmpRawTileData[0];
888 3 : size_t nOutSize = m_abyTmpRawTileData.size();
889 3 : if (!psFilterCompressor->pfnFunc(
890 3 : m_abyRawTileData.data(), nRawDataSize, &out_buffer, &nOutSize,
891 3 : aosOptions.List(), psFilterCompressor->user_data))
892 : {
893 0 : CPLError(CE_Failure, CPLE_AppDefined,
894 : "Filter %s for tile %s failed", osFilterId.c_str(),
895 : osFilename.c_str());
896 0 : return false;
897 : }
898 :
899 3 : nRawDataSize = nOutSize;
900 3 : std::swap(m_abyRawTileData, m_abyTmpRawTileData);
901 : }
902 :
903 11379 : if (m_osDimSeparator == "/")
904 : {
905 20 : std::string osDir = CPLGetDirnameSafe(osFilename.c_str());
906 : VSIStatBufL sStat;
907 20 : if (VSIStatL(osDir.c_str(), &sStat) != 0)
908 : {
909 20 : if (VSIMkdirRecursive(osDir.c_str(), 0755) != 0)
910 : {
911 0 : CPLError(CE_Failure, CPLE_AppDefined,
912 : "Cannot create directory %s", osDir.c_str());
913 0 : return false;
914 : }
915 : }
916 : }
917 :
918 11379 : if (m_psCompressor == nullptr && m_psDecompressor != nullptr)
919 : {
920 : // Case of imagecodecs_tiff
921 :
922 1 : CPLError(CE_Failure, CPLE_NotSupported,
923 : "Only decompression supported for '%s' compression method",
924 : m_osDecompressorId.c_str());
925 1 : return false;
926 : }
927 :
928 11378 : VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
929 11378 : if (fp == nullptr)
930 : {
931 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot create tile %s",
932 : osFilename.c_str());
933 0 : return false;
934 : }
935 :
936 11378 : bool bRet = true;
937 11378 : if (m_psCompressor == nullptr)
938 : {
939 5928 : if (VSIFWriteL(m_abyRawTileData.data(), 1, nRawDataSize, fp) !=
940 : nRawDataSize)
941 : {
942 2 : CPLError(CE_Failure, CPLE_AppDefined,
943 : "Could not write tile %s correctly", osFilename.c_str());
944 2 : bRet = false;
945 : }
946 : }
947 : else
948 : {
949 10900 : std::vector<GByte> abyCompressedData;
950 : try
951 : {
952 5450 : constexpr size_t MIN_BUF_SIZE = 64; // somewhat arbitrary
953 5450 : abyCompressedData.resize(static_cast<size_t>(
954 5450 : MIN_BUF_SIZE + nRawDataSize + nRawDataSize / 3));
955 : }
956 0 : catch (const std::exception &)
957 : {
958 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
959 : "Cannot allocate memory for tile %s", osFilename.c_str());
960 0 : bRet = false;
961 : }
962 :
963 5450 : if (bRet)
964 : {
965 5450 : void *out_buffer = &abyCompressedData[0];
966 5450 : size_t out_size = abyCompressedData.size();
967 10900 : CPLStringList aosOptions;
968 5450 : const auto &compressorConfig = m_oCompressorJSon;
969 16350 : for (const auto &obj : compressorConfig.GetChildren())
970 : {
971 21800 : aosOptions.SetNameValue(obj.GetName().c_str(),
972 32700 : obj.ToString().c_str());
973 : }
974 5450 : if (EQUAL(m_psCompressor->pszId, "blosc") &&
975 0 : m_oType.GetClass() == GEDTC_NUMERIC)
976 : {
977 : aosOptions.SetNameValue(
978 : "TYPESIZE",
979 : CPLSPrintf("%d", GDALGetDataTypeSizeBytes(
980 : GDALGetNonComplexDataType(
981 0 : m_oType.GetNumericDataType()))));
982 : }
983 :
984 5450 : if (!m_psCompressor->pfnFunc(
985 5450 : m_abyRawTileData.data(), nRawDataSize, &out_buffer,
986 5450 : &out_size, aosOptions.List(), m_psCompressor->user_data))
987 : {
988 0 : CPLError(CE_Failure, CPLE_AppDefined,
989 : "Compression of tile %s failed", osFilename.c_str());
990 0 : bRet = false;
991 : }
992 5450 : abyCompressedData.resize(out_size);
993 : }
994 :
995 10900 : if (bRet &&
996 5450 : VSIFWriteL(abyCompressedData.data(), 1, abyCompressedData.size(),
997 5450 : fp) != abyCompressedData.size())
998 : {
999 0 : CPLError(CE_Failure, CPLE_AppDefined,
1000 : "Could not write tile %s correctly", osFilename.c_str());
1001 0 : bRet = false;
1002 : }
1003 : }
1004 11378 : VSIFCloseL(fp);
1005 :
1006 11378 : return bRet;
1007 : }
1008 :
1009 : /************************************************************************/
1010 : /* BuildTileFilename() */
1011 : /************************************************************************/
1012 :
1013 27622 : std::string ZarrV2Array::BuildTileFilename(const uint64_t *tileIndices) const
1014 : {
1015 27622 : std::string osFilename;
1016 27609 : if (m_aoDims.empty())
1017 : {
1018 4 : osFilename = "0";
1019 : }
1020 : else
1021 : {
1022 83337 : for (size_t i = 0; i < m_aoDims.size(); ++i)
1023 : {
1024 55697 : if (!osFilename.empty())
1025 28106 : osFilename += m_osDimSeparator;
1026 55734 : osFilename += std::to_string(tileIndices[i]);
1027 : }
1028 : }
1029 :
1030 55204 : return CPLFormFilenameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
1031 82800 : osFilename.c_str(), nullptr);
1032 : }
1033 :
1034 : /************************************************************************/
1035 : /* GetDataDirectory() */
1036 : /************************************************************************/
1037 :
1038 2 : std::string ZarrV2Array::GetDataDirectory() const
1039 : {
1040 2 : return CPLGetDirnameSafe(m_osFilename.c_str());
1041 : }
1042 :
1043 : /************************************************************************/
1044 : /* GetTileIndicesFromFilename() */
1045 : /************************************************************************/
1046 :
1047 : CPLStringList
1048 5 : ZarrV2Array::GetTileIndicesFromFilename(const char *pszFilename) const
1049 : {
1050 : return CPLStringList(
1051 5 : CSLTokenizeString2(pszFilename, m_osDimSeparator.c_str(), 0));
1052 : }
1053 :
1054 : /************************************************************************/
1055 : /* ParseDtype() */
1056 : /************************************************************************/
1057 :
1058 26 : static size_t GetAlignment(const CPLJSONObject &obj)
1059 : {
1060 26 : if (obj.GetType() == CPLJSONObject::Type::String)
1061 : {
1062 69 : const auto str = obj.ToString();
1063 23 : if (str.size() < 3)
1064 0 : return 1;
1065 23 : const char chType = str[1];
1066 23 : const int nBytes = atoi(str.c_str() + 2);
1067 23 : if (chType == 'S')
1068 2 : return sizeof(char *);
1069 21 : if (chType == 'c' && nBytes == 8)
1070 0 : return sizeof(float);
1071 21 : if (chType == 'c' && nBytes == 16)
1072 0 : return sizeof(double);
1073 21 : return nBytes;
1074 : }
1075 3 : else if (obj.GetType() == CPLJSONObject::Type::Array)
1076 : {
1077 6 : const auto oArray = obj.ToArray();
1078 3 : size_t nAlignment = 1;
1079 9 : for (const auto &oElt : oArray)
1080 : {
1081 6 : const auto oEltArray = oElt.ToArray();
1082 12 : if (!oEltArray.IsValid() || oEltArray.Size() != 2 ||
1083 12 : oEltArray[0].GetType() != CPLJSONObject::Type::String)
1084 : {
1085 0 : return 1;
1086 : }
1087 6 : nAlignment = std::max(nAlignment, GetAlignment(oEltArray[1]));
1088 6 : if (nAlignment == sizeof(void *))
1089 0 : break;
1090 : }
1091 3 : return nAlignment;
1092 : }
1093 0 : return 1;
1094 : }
1095 :
1096 695 : static GDALExtendedDataType ParseDtype(const CPLJSONObject &obj,
1097 : std::vector<DtypeElt> &elts)
1098 : {
1099 29 : const auto AlignOffsetOn = [](size_t offset, size_t alignment)
1100 29 : { return offset + (alignment - (offset % alignment)) % alignment; };
1101 :
1102 : do
1103 : {
1104 695 : if (obj.GetType() == CPLJSONObject::Type::String)
1105 : {
1106 1368 : const auto str = obj.ToString();
1107 684 : char chEndianness = 0;
1108 : char chType;
1109 : int nBytes;
1110 684 : DtypeElt elt;
1111 684 : if (str.size() < 3)
1112 3 : break;
1113 681 : chEndianness = str[0];
1114 681 : chType = str[1];
1115 681 : nBytes = atoi(str.c_str() + 2);
1116 681 : if (nBytes <= 0 || nBytes >= 1000)
1117 : break;
1118 :
1119 679 : elt.needByteSwapping = false;
1120 679 : if ((nBytes > 1 && chType != 'S') || chType == 'U')
1121 : {
1122 373 : if (chEndianness == '<')
1123 330 : elt.needByteSwapping = (CPL_IS_LSB == 0);
1124 43 : else if (chEndianness == '>')
1125 43 : elt.needByteSwapping = (CPL_IS_LSB != 0);
1126 : }
1127 :
1128 : GDALDataType eDT;
1129 679 : if (!elts.empty())
1130 : {
1131 11 : elt.nativeOffset =
1132 11 : elts.back().nativeOffset + elts.back().nativeSize;
1133 : }
1134 679 : elt.nativeSize = nBytes;
1135 679 : if (chType == 'b' && nBytes == 1) // boolean
1136 : {
1137 66 : elt.nativeType = DtypeElt::NativeType::BOOLEAN;
1138 66 : eDT = GDT_Byte;
1139 : }
1140 613 : else if (chType == 'u' && nBytes == 1)
1141 : {
1142 219 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1143 219 : eDT = GDT_Byte;
1144 : }
1145 394 : else if (chType == 'i' && nBytes == 1)
1146 : {
1147 12 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1148 12 : eDT = GDT_Int8;
1149 : }
1150 382 : else if (chType == 'i' && nBytes == 2)
1151 : {
1152 20 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1153 20 : eDT = GDT_Int16;
1154 : }
1155 362 : else if (chType == 'i' && nBytes == 4)
1156 : {
1157 26 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1158 26 : eDT = GDT_Int32;
1159 : }
1160 336 : else if (chType == 'i' && nBytes == 8)
1161 : {
1162 15 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1163 15 : eDT = GDT_Int64;
1164 : }
1165 321 : else if (chType == 'u' && nBytes == 2)
1166 : {
1167 29 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1168 29 : eDT = GDT_UInt16;
1169 : }
1170 292 : else if (chType == 'u' && nBytes == 4)
1171 : {
1172 18 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1173 18 : eDT = GDT_UInt32;
1174 : }
1175 274 : else if (chType == 'u' && nBytes == 8)
1176 : {
1177 14 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1178 14 : eDT = GDT_UInt64;
1179 : }
1180 260 : else if (chType == 'f' && nBytes == 2)
1181 : {
1182 : // elt.nativeType = DtypeElt::NativeType::IEEEFP;
1183 : // elt.gdalTypeIsApproxOfNative = true;
1184 : // eDT = GDT_Float32;
1185 3 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
1186 3 : eDT = GDT_Float16;
1187 : }
1188 257 : else if (chType == 'f' && nBytes == 4)
1189 : {
1190 44 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
1191 44 : eDT = GDT_Float32;
1192 : }
1193 213 : else if (chType == 'f' && nBytes == 8)
1194 : {
1195 175 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
1196 175 : eDT = GDT_Float64;
1197 : }
1198 38 : else if (chType == 'c' && nBytes == 8)
1199 : {
1200 11 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
1201 11 : eDT = GDT_CFloat32;
1202 : }
1203 27 : else if (chType == 'c' && nBytes == 16)
1204 : {
1205 12 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
1206 12 : eDT = GDT_CFloat64;
1207 : }
1208 15 : else if (chType == 'S')
1209 : {
1210 9 : elt.nativeType = DtypeElt::NativeType::STRING_ASCII;
1211 9 : elt.gdalType = GDALExtendedDataType::CreateString(nBytes);
1212 9 : elt.gdalSize = elt.gdalType.GetSize();
1213 9 : elts.emplace_back(elt);
1214 9 : return GDALExtendedDataType::CreateString(nBytes);
1215 : }
1216 6 : else if (chType == 'U')
1217 : {
1218 5 : elt.nativeType = DtypeElt::NativeType::STRING_UNICODE;
1219 : // the dtype declaration is number of UCS4 characters. Store it
1220 : // as bytes
1221 5 : elt.nativeSize *= 4;
1222 : // We can really map UCS4 size to UTF-8
1223 5 : elt.gdalType = GDALExtendedDataType::CreateString();
1224 5 : elt.gdalSize = elt.gdalType.GetSize();
1225 5 : elts.emplace_back(elt);
1226 5 : return GDALExtendedDataType::CreateString();
1227 : }
1228 : else
1229 1 : break;
1230 664 : elt.gdalType = GDALExtendedDataType::Create(eDT);
1231 664 : elt.gdalSize = elt.gdalType.GetSize();
1232 664 : elts.emplace_back(elt);
1233 664 : return GDALExtendedDataType::Create(eDT);
1234 : }
1235 11 : else if (obj.GetType() == CPLJSONObject::Type::Array)
1236 : {
1237 9 : bool error = false;
1238 9 : const auto oArray = obj.ToArray();
1239 9 : std::vector<std::unique_ptr<GDALEDTComponent>> comps;
1240 9 : size_t offset = 0;
1241 9 : size_t alignmentMax = 1;
1242 29 : for (const auto &oElt : oArray)
1243 : {
1244 20 : const auto oEltArray = oElt.ToArray();
1245 40 : if (!oEltArray.IsValid() || oEltArray.Size() != 2 ||
1246 40 : oEltArray[0].GetType() != CPLJSONObject::Type::String)
1247 : {
1248 0 : error = true;
1249 0 : break;
1250 : }
1251 20 : GDALExtendedDataType subDT = ParseDtype(oEltArray[1], elts);
1252 35 : if (subDT.GetClass() == GEDTC_NUMERIC &&
1253 15 : subDT.GetNumericDataType() == GDT_Unknown)
1254 : {
1255 0 : error = true;
1256 0 : break;
1257 : }
1258 :
1259 40 : const std::string osName = oEltArray[0].ToString();
1260 : // Add padding for alignment
1261 20 : const size_t alignmentSub = GetAlignment(oEltArray[1]);
1262 20 : assert(alignmentSub);
1263 20 : alignmentMax = std::max(alignmentMax, alignmentSub);
1264 20 : offset = AlignOffsetOn(offset, alignmentSub);
1265 40 : comps.emplace_back(std::unique_ptr<GDALEDTComponent>(
1266 40 : new GDALEDTComponent(osName, offset, subDT)));
1267 20 : offset += subDT.GetSize();
1268 : }
1269 9 : if (error)
1270 0 : break;
1271 9 : size_t nTotalSize = offset;
1272 9 : nTotalSize = AlignOffsetOn(nTotalSize, alignmentMax);
1273 18 : return GDALExtendedDataType::Create(obj.ToString(), nTotalSize,
1274 18 : std::move(comps));
1275 : }
1276 : } while (false);
1277 8 : CPLError(CE_Failure, CPLE_AppDefined,
1278 : "Invalid or unsupported format for dtype: %s",
1279 16 : obj.ToString().c_str());
1280 8 : return GDALExtendedDataType::Create(GDT_Unknown);
1281 : }
1282 :
1283 687 : static void SetGDALOffset(const GDALExtendedDataType &dt,
1284 : const size_t nBaseOffset, std::vector<DtypeElt> &elts,
1285 : size_t &iCurElt)
1286 : {
1287 687 : if (dt.GetClass() == GEDTC_COMPOUND)
1288 : {
1289 9 : const auto &comps = dt.GetComponents();
1290 29 : for (const auto &comp : comps)
1291 : {
1292 20 : const size_t nBaseOffsetSub = nBaseOffset + comp->GetOffset();
1293 20 : SetGDALOffset(comp->GetType(), nBaseOffsetSub, elts, iCurElt);
1294 : }
1295 : }
1296 : else
1297 : {
1298 678 : elts[iCurElt].gdalOffset = nBaseOffset;
1299 678 : iCurElt++;
1300 : }
1301 687 : }
1302 :
1303 : /************************************************************************/
1304 : /* ZarrV2Group::LoadArray() */
1305 : /************************************************************************/
1306 :
1307 : std::shared_ptr<ZarrArray>
1308 693 : ZarrV2Group::LoadArray(const std::string &osArrayName,
1309 : const std::string &osZarrayFilename,
1310 : const CPLJSONObject &oRoot, bool bLoadedFromZMetadata,
1311 : const CPLJSONObject &oAttributesIn) const
1312 : {
1313 : // Add osZarrayFilename to m_poSharedResource during the scope
1314 : // of this function call.
1315 693 : ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
1316 1386 : osZarrayFilename);
1317 693 : if (!filenameAdder.ok())
1318 2 : return nullptr;
1319 :
1320 2073 : const auto osFormat = oRoot["zarr_format"].ToString();
1321 691 : if (osFormat != "2")
1322 : {
1323 3 : CPLError(CE_Failure, CPLE_NotSupported,
1324 : "Invalid value for zarr_format: %s", osFormat.c_str());
1325 3 : return nullptr;
1326 : }
1327 :
1328 688 : bool bFortranOrder = false;
1329 688 : const char *orderKey = "order";
1330 2064 : const auto osOrder = oRoot[orderKey].ToString();
1331 688 : if (osOrder == "C")
1332 : {
1333 : // ok
1334 : }
1335 33 : else if (osOrder == "F")
1336 : {
1337 30 : bFortranOrder = true;
1338 : }
1339 : else
1340 : {
1341 3 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid value for %s",
1342 : orderKey);
1343 3 : return nullptr;
1344 : }
1345 :
1346 2055 : const auto oShape = oRoot["shape"].ToArray();
1347 685 : if (!oShape.IsValid())
1348 : {
1349 3 : CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
1350 3 : return nullptr;
1351 : }
1352 :
1353 682 : const char *chunksKey = "chunks";
1354 2046 : const auto oChunks = oRoot[chunksKey].ToArray();
1355 682 : if (!oChunks.IsValid())
1356 : {
1357 3 : CPLError(CE_Failure, CPLE_AppDefined, "%s missing or not an array",
1358 : chunksKey);
1359 3 : return nullptr;
1360 : }
1361 :
1362 679 : if (oShape.Size() != oChunks.Size())
1363 : {
1364 2 : CPLError(CE_Failure, CPLE_AppDefined,
1365 : "shape and chunks arrays are of different size");
1366 2 : return nullptr;
1367 : }
1368 :
1369 1354 : CPLJSONObject oAttributes(oAttributesIn);
1370 677 : if (!bLoadedFromZMetadata)
1371 : {
1372 782 : CPLJSONDocument oDoc;
1373 : const std::string osZattrsFilename(CPLFormFilenameSafe(
1374 391 : CPLGetDirnameSafe(osZarrayFilename.c_str()).c_str(), ".zattrs",
1375 782 : nullptr));
1376 782 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
1377 391 : if (oDoc.Load(osZattrsFilename))
1378 : {
1379 148 : oAttributes = oDoc.GetRoot();
1380 : }
1381 : }
1382 :
1383 : // Deep-clone of oAttributes
1384 : {
1385 677 : CPLJSONDocument oTmpDoc;
1386 677 : oTmpDoc.SetRoot(oAttributes);
1387 677 : CPL_IGNORE_RET_VAL(oTmpDoc.LoadMemory(oTmpDoc.SaveAsString()));
1388 677 : oAttributes = oTmpDoc.GetRoot();
1389 : }
1390 :
1391 1354 : std::vector<std::shared_ptr<GDALDimension>> aoDims;
1392 1728 : for (int i = 0; i < oShape.Size(); ++i)
1393 : {
1394 1052 : const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
1395 1052 : if (nSize == 0)
1396 : {
1397 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
1398 1 : return nullptr;
1399 : }
1400 1051 : aoDims.emplace_back(std::make_shared<ZarrDimension>(
1401 1051 : m_poSharedResource,
1402 2102 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1403 2102 : std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
1404 1051 : nSize));
1405 : }
1406 :
1407 : // XArray extension
1408 2028 : const auto arrayDimensionsObj = oAttributes["_ARRAY_DIMENSIONS"];
1409 :
1410 : const auto FindDimension =
1411 515 : [this, &aoDims, bLoadedFromZMetadata, &osArrayName,
1412 : &oAttributes](const std::string &osDimName,
1413 5864 : std::shared_ptr<GDALDimension> &poDim, int i)
1414 : {
1415 515 : auto oIter = m_oMapDimensions.find(osDimName);
1416 515 : if (oIter != m_oMapDimensions.end())
1417 : {
1418 252 : if (m_bDimSizeInUpdate ||
1419 125 : oIter->second->GetSize() == poDim->GetSize())
1420 : {
1421 127 : poDim = oIter->second;
1422 127 : return true;
1423 : }
1424 : else
1425 : {
1426 0 : CPLError(CE_Warning, CPLE_AppDefined,
1427 : "Size of _ARRAY_DIMENSIONS[%d] different "
1428 : "from the one of shape",
1429 : i);
1430 0 : return false;
1431 : }
1432 : }
1433 :
1434 : // Try to load the indexing variable.
1435 :
1436 : // If loading from zmetadata, we should have normally
1437 : // already loaded the dimension variables, unless they
1438 : // are in a upper level.
1439 570 : if (bLoadedFromZMetadata && osArrayName != osDimName &&
1440 570 : m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
1441 : {
1442 182 : auto poParent = m_poParent.lock();
1443 198 : while (poParent != nullptr)
1444 : {
1445 22 : oIter = poParent->m_oMapDimensions.find(osDimName);
1446 28 : if (oIter != poParent->m_oMapDimensions.end() &&
1447 6 : oIter->second->GetSize() == poDim->GetSize())
1448 : {
1449 6 : poDim = oIter->second;
1450 6 : return true;
1451 : }
1452 16 : poParent = poParent->m_poParent.lock();
1453 : }
1454 : }
1455 :
1456 : // Not loading from zmetadata, and not in m_oMapMDArrays,
1457 : // then stat() the indexing variable.
1458 280 : else if (!bLoadedFromZMetadata && osArrayName != osDimName &&
1459 280 : m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
1460 : {
1461 148 : std::string osDirName = m_osDirectoryName;
1462 : while (true)
1463 : {
1464 : const std::string osArrayFilenameDim = CPLFormFilenameSafe(
1465 162 : CPLFormFilenameSafe(osDirName.c_str(), osDimName.c_str(),
1466 : nullptr)
1467 : .c_str(),
1468 162 : ".zarray", nullptr);
1469 : VSIStatBufL sStat;
1470 162 : if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
1471 : {
1472 106 : CPLJSONDocument oDoc;
1473 53 : if (oDoc.Load(osArrayFilenameDim))
1474 : {
1475 53 : LoadArray(osDimName, osArrayFilenameDim, oDoc.GetRoot(),
1476 106 : false, CPLJSONObject());
1477 : }
1478 : }
1479 : else
1480 : {
1481 : // Recurse to upper level for datasets such as
1482 : // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
1483 : std::string osDirNameNew =
1484 109 : CPLGetPathSafe(osDirName.c_str());
1485 109 : if (!osDirNameNew.empty() && osDirNameNew != osDirName)
1486 : {
1487 88 : osDirName = std::move(osDirNameNew);
1488 88 : continue;
1489 : }
1490 : }
1491 74 : break;
1492 88 : }
1493 : }
1494 :
1495 382 : oIter = m_oMapDimensions.find(osDimName);
1496 401 : if (oIter != m_oMapDimensions.end() &&
1497 19 : oIter->second->GetSize() == poDim->GetSize())
1498 : {
1499 19 : poDim = oIter->second;
1500 19 : return true;
1501 : }
1502 :
1503 726 : std::string osType;
1504 726 : std::string osDirection;
1505 363 : if (aoDims.size() == 1 && osArrayName == osDimName)
1506 : {
1507 132 : ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
1508 : osDirection);
1509 : }
1510 :
1511 : auto poDimLocal = std::make_shared<ZarrDimension>(
1512 363 : m_poSharedResource,
1513 726 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1514 726 : GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
1515 363 : poDimLocal->SetXArrayDimension();
1516 363 : m_oMapDimensions[osDimName] = poDimLocal;
1517 363 : poDim = poDimLocal;
1518 363 : return true;
1519 676 : };
1520 :
1521 676 : if (arrayDimensionsObj.GetType() == CPLJSONObject::Type::Array)
1522 : {
1523 690 : const auto arrayDims = arrayDimensionsObj.ToArray();
1524 345 : if (arrayDims.Size() == oShape.Size())
1525 : {
1526 345 : bool ok = true;
1527 860 : for (int i = 0; i < oShape.Size(); ++i)
1528 : {
1529 515 : if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
1530 : {
1531 1030 : const auto osDimName = arrayDims[i].ToString();
1532 515 : ok &= FindDimension(osDimName, aoDims[i], i);
1533 : }
1534 : }
1535 345 : if (ok)
1536 : {
1537 345 : oAttributes.Delete("_ARRAY_DIMENSIONS");
1538 : }
1539 : }
1540 : else
1541 : {
1542 0 : CPLError(
1543 : CE_Warning, CPLE_AppDefined,
1544 : "Size of _ARRAY_DIMENSIONS different from the one of shape");
1545 : }
1546 : }
1547 :
1548 : // _NCZARR_ARRAY extension
1549 2028 : const auto nczarrArrayDimrefs = oRoot["_NCZARR_ARRAY"]["dimrefs"].ToArray();
1550 676 : if (nczarrArrayDimrefs.IsValid())
1551 : {
1552 42 : const auto arrayDims = nczarrArrayDimrefs.ToArray();
1553 21 : if (arrayDims.Size() == oShape.Size())
1554 : {
1555 : auto poRG =
1556 42 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
1557 21 : CPLAssert(poRG != nullptr);
1558 : while (true)
1559 : {
1560 48 : auto poNewRG = poRG->m_poParent.lock();
1561 48 : if (poNewRG == nullptr)
1562 21 : break;
1563 27 : poRG = std::move(poNewRG);
1564 27 : }
1565 :
1566 49 : for (int i = 0; i < oShape.Size(); ++i)
1567 : {
1568 28 : if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
1569 : {
1570 84 : const auto osDimFullpath = arrayDims[i].ToString();
1571 : const std::string osArrayFullname =
1572 56 : (GetFullName() != "/" ? GetFullName() : std::string()) +
1573 56 : '/' + osArrayName;
1574 48 : if (aoDims.size() == 1 &&
1575 20 : (osDimFullpath == osArrayFullname ||
1576 34 : osDimFullpath == "/" + osArrayFullname))
1577 : {
1578 : // If this is an indexing variable, then fetch the
1579 : // dimension type and direction, and patch the dimension
1580 28 : std::string osType;
1581 28 : std::string osDirection;
1582 14 : ZarrArray::GetDimensionTypeDirection(
1583 : oAttributes, osType, osDirection);
1584 :
1585 : auto poDimLocal = std::make_shared<ZarrDimension>(
1586 14 : m_poSharedResource,
1587 28 : std::dynamic_pointer_cast<ZarrGroupBase>(
1588 14 : m_pSelf.lock()),
1589 14 : GetFullName(), osArrayName, osType, osDirection,
1590 28 : aoDims[i]->GetSize());
1591 14 : aoDims[i] = poDimLocal;
1592 :
1593 14 : m_oMapDimensions[osArrayName] = std::move(poDimLocal);
1594 : }
1595 14 : else if (auto poDim =
1596 28 : poRG->OpenDimensionFromFullname(osDimFullpath))
1597 : {
1598 13 : if (poDim->GetSize() != aoDims[i]->GetSize())
1599 : {
1600 1 : CPLError(CE_Failure, CPLE_AppDefined,
1601 : "Inconsistency in size between NCZarr "
1602 : "dimension %s and regular dimension",
1603 : osDimFullpath.c_str());
1604 : }
1605 : else
1606 : {
1607 12 : aoDims[i] = std::move(poDim);
1608 : }
1609 : }
1610 : else
1611 : {
1612 1 : CPLError(CE_Failure, CPLE_AppDefined,
1613 : "Cannot find NCZarr dimension %s",
1614 : osDimFullpath.c_str());
1615 : }
1616 : }
1617 : }
1618 : }
1619 : else
1620 : {
1621 0 : CPLError(CE_Warning, CPLE_AppDefined,
1622 : "Size of _NCZARR_ARRAY.dimrefs different from the one of "
1623 : "shape");
1624 : }
1625 : }
1626 :
1627 676 : constexpr const char *dtypeKey = "dtype";
1628 2028 : auto oDtype = oRoot[dtypeKey];
1629 676 : if (!oDtype.IsValid())
1630 : {
1631 1 : CPLError(CE_Failure, CPLE_NotSupported, "%s missing", dtypeKey);
1632 1 : return nullptr;
1633 : }
1634 1350 : std::vector<DtypeElt> aoDtypeElts;
1635 1350 : const auto oType = ParseDtype(oDtype, aoDtypeElts);
1636 1332 : if (oType.GetClass() == GEDTC_NUMERIC &&
1637 657 : oType.GetNumericDataType() == GDT_Unknown)
1638 8 : return nullptr;
1639 667 : size_t iCurElt = 0;
1640 667 : SetGDALOffset(oType, 0, aoDtypeElts, iCurElt);
1641 :
1642 1334 : std::vector<GUInt64> anBlockSize;
1643 667 : if (!ZarrArray::ParseChunkSize(oChunks, oType, anBlockSize))
1644 2 : return nullptr;
1645 :
1646 1995 : std::string osDimSeparator = oRoot["dimension_separator"].ToString();
1647 665 : if (osDimSeparator.empty())
1648 655 : osDimSeparator = ".";
1649 :
1650 1330 : std::vector<GByte> abyNoData;
1651 :
1652 : struct NoDataFreer
1653 : {
1654 : std::vector<GByte> &m_abyNodata;
1655 : const GDALExtendedDataType &m_oType;
1656 :
1657 665 : NoDataFreer(std::vector<GByte> &abyNoDataIn,
1658 : const GDALExtendedDataType &oTypeIn)
1659 665 : : m_abyNodata(abyNoDataIn), m_oType(oTypeIn)
1660 : {
1661 665 : }
1662 :
1663 665 : ~NoDataFreer()
1664 665 : {
1665 665 : if (!m_abyNodata.empty())
1666 112 : m_oType.FreeDynamicMemory(&m_abyNodata[0]);
1667 665 : }
1668 : };
1669 :
1670 1330 : NoDataFreer NoDataFreer(abyNoData, oType);
1671 :
1672 1995 : auto oFillValue = oRoot["fill_value"];
1673 665 : auto eFillValueType = oFillValue.GetType();
1674 :
1675 : // Normally arrays are not supported, but that's what NCZarr 4.8.0 outputs
1676 666 : if (eFillValueType == CPLJSONObject::Type::Array &&
1677 666 : oFillValue.ToArray().Size() == 1)
1678 : {
1679 0 : oFillValue = oFillValue.ToArray()[0];
1680 0 : eFillValueType = oFillValue.GetType();
1681 : }
1682 :
1683 665 : if (!oFillValue.IsValid())
1684 : {
1685 : // fill_value is normally required but some implementations
1686 : // are lacking it: https://github.com/Unidata/netcdf-c/issues/2059
1687 1 : CPLError(CE_Warning, CPLE_AppDefined, "fill_value missing");
1688 : }
1689 664 : else if (eFillValueType == CPLJSONObject::Type::Null)
1690 : {
1691 : // Nothing to do
1692 : }
1693 120 : else if (eFillValueType == CPLJSONObject::Type::String)
1694 : {
1695 98 : const auto osFillValue = oFillValue.ToString();
1696 86 : if (oType.GetClass() == GEDTC_NUMERIC &&
1697 37 : CPLGetValueType(osFillValue.c_str()) != CPL_VALUE_STRING)
1698 : {
1699 8 : abyNoData.resize(oType.GetSize());
1700 : // Be tolerant with numeric values serialized as strings.
1701 8 : if (oType.GetNumericDataType() == GDT_Int64)
1702 : {
1703 : const int64_t nVal = static_cast<int64_t>(
1704 2 : std::strtoll(osFillValue.c_str(), nullptr, 10));
1705 2 : GDALCopyWords(&nVal, GDT_Int64, 0, &abyNoData[0],
1706 : oType.GetNumericDataType(), 0, 1);
1707 : }
1708 6 : else if (oType.GetNumericDataType() == GDT_UInt64)
1709 : {
1710 : const uint64_t nVal = static_cast<uint64_t>(
1711 2 : std::strtoull(osFillValue.c_str(), nullptr, 10));
1712 2 : GDALCopyWords(&nVal, GDT_UInt64, 0, &abyNoData[0],
1713 : oType.GetNumericDataType(), 0, 1);
1714 : }
1715 : else
1716 : {
1717 4 : const double dfNoDataValue = CPLAtof(osFillValue.c_str());
1718 4 : GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
1719 : oType.GetNumericDataType(), 0, 1);
1720 : }
1721 : }
1722 41 : else if (oType.GetClass() == GEDTC_NUMERIC)
1723 : {
1724 : double dfNoDataValue;
1725 29 : if (osFillValue == "NaN")
1726 : {
1727 10 : dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
1728 : }
1729 19 : else if (osFillValue == "Infinity")
1730 : {
1731 9 : dfNoDataValue = std::numeric_limits<double>::infinity();
1732 : }
1733 10 : else if (osFillValue == "-Infinity")
1734 : {
1735 9 : dfNoDataValue = -std::numeric_limits<double>::infinity();
1736 : }
1737 : else
1738 : {
1739 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1740 2 : return nullptr;
1741 : }
1742 28 : if (oType.GetNumericDataType() == GDT_Float16)
1743 : {
1744 : const GFloat16 hfNoDataValue =
1745 0 : static_cast<GFloat16>(dfNoDataValue);
1746 0 : abyNoData.resize(sizeof(hfNoDataValue));
1747 0 : memcpy(&abyNoData[0], &hfNoDataValue, sizeof(hfNoDataValue));
1748 : }
1749 28 : if (oType.GetNumericDataType() == GDT_Float32)
1750 : {
1751 12 : const float fNoDataValue = static_cast<float>(dfNoDataValue);
1752 12 : abyNoData.resize(sizeof(fNoDataValue));
1753 12 : memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
1754 : }
1755 16 : else if (oType.GetNumericDataType() == GDT_Float64)
1756 : {
1757 15 : abyNoData.resize(sizeof(dfNoDataValue));
1758 15 : memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
1759 : }
1760 : else
1761 : {
1762 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1763 1 : return nullptr;
1764 : }
1765 : }
1766 12 : else if (oType.GetClass() == GEDTC_STRING)
1767 : {
1768 : // zarr.open('unicode_be.zarr', mode = 'w', shape=(1,), dtype =
1769 : // '>U1', compressor = None) oddly generates "fill_value": "0"
1770 7 : if (osFillValue != "0")
1771 : {
1772 3 : std::vector<GByte> abyNativeFillValue(osFillValue.size() + 1);
1773 3 : memcpy(&abyNativeFillValue[0], osFillValue.data(),
1774 : osFillValue.size());
1775 3 : int nBytes = CPLBase64DecodeInPlace(&abyNativeFillValue[0]);
1776 3 : abyNativeFillValue.resize(nBytes + 1);
1777 3 : abyNativeFillValue[nBytes] = 0;
1778 3 : abyNoData.resize(oType.GetSize());
1779 3 : char *pDstStr = CPLStrdup(
1780 3 : reinterpret_cast<const char *>(&abyNativeFillValue[0]));
1781 3 : char **pDstPtr = reinterpret_cast<char **>(&abyNoData[0]);
1782 3 : memcpy(pDstPtr, &pDstStr, sizeof(pDstStr));
1783 : }
1784 : }
1785 : else
1786 : {
1787 5 : std::vector<GByte> abyNativeFillValue(osFillValue.size() + 1);
1788 5 : memcpy(&abyNativeFillValue[0], osFillValue.data(),
1789 : osFillValue.size());
1790 5 : int nBytes = CPLBase64DecodeInPlace(&abyNativeFillValue[0]);
1791 5 : abyNativeFillValue.resize(nBytes);
1792 5 : if (abyNativeFillValue.size() !=
1793 5 : aoDtypeElts.back().nativeOffset + aoDtypeElts.back().nativeSize)
1794 : {
1795 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1796 0 : return nullptr;
1797 : }
1798 5 : abyNoData.resize(oType.GetSize());
1799 5 : ZarrArray::DecodeSourceElt(aoDtypeElts, abyNativeFillValue.data(),
1800 5 : &abyNoData[0]);
1801 : }
1802 : }
1803 71 : else if (eFillValueType == CPLJSONObject::Type::Boolean ||
1804 20 : eFillValueType == CPLJSONObject::Type::Integer ||
1805 12 : eFillValueType == CPLJSONObject::Type::Long ||
1806 : eFillValueType == CPLJSONObject::Type::Double)
1807 : {
1808 70 : if (oType.GetClass() == GEDTC_NUMERIC)
1809 : {
1810 69 : const double dfNoDataValue = oFillValue.ToDouble();
1811 69 : if (oType.GetNumericDataType() == GDT_Int64)
1812 : {
1813 : const int64_t nNoDataValue =
1814 2 : static_cast<int64_t>(oFillValue.ToLong());
1815 2 : abyNoData.resize(oType.GetSize());
1816 2 : GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1817 : oType.GetNumericDataType(), 0, 1);
1818 : }
1819 70 : else if (oType.GetNumericDataType() == GDT_UInt64 &&
1820 : /* we can't really deal with nodata value between */
1821 : /* int64::max and uint64::max due to json-c limitations */
1822 3 : dfNoDataValue >= 0)
1823 : {
1824 : const int64_t nNoDataValue =
1825 3 : static_cast<int64_t>(oFillValue.ToLong());
1826 3 : abyNoData.resize(oType.GetSize());
1827 3 : GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1828 : oType.GetNumericDataType(), 0, 1);
1829 : }
1830 : else
1831 : {
1832 64 : abyNoData.resize(oType.GetSize());
1833 64 : GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
1834 : oType.GetNumericDataType(), 0, 1);
1835 : }
1836 : }
1837 : else
1838 : {
1839 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1840 1 : return nullptr;
1841 69 : }
1842 : }
1843 : else
1844 : {
1845 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1846 1 : return nullptr;
1847 : }
1848 :
1849 661 : const CPLCompressor *psCompressor = nullptr;
1850 661 : const CPLCompressor *psDecompressor = nullptr;
1851 1983 : const auto oCompressor = oRoot["compressor"];
1852 1322 : std::string osDecompressorId("NONE");
1853 :
1854 661 : if (!oCompressor.IsValid())
1855 : {
1856 1 : CPLError(CE_Failure, CPLE_AppDefined, "compressor missing");
1857 1 : return nullptr;
1858 : }
1859 660 : if (oCompressor.GetType() == CPLJSONObject::Type::Null)
1860 : {
1861 : // nothing to do
1862 : }
1863 37 : else if (oCompressor.GetType() == CPLJSONObject::Type::Object)
1864 : {
1865 36 : osDecompressorId = oCompressor["id"].ToString();
1866 36 : if (osDecompressorId.empty())
1867 : {
1868 1 : CPLError(CE_Failure, CPLE_AppDefined, "Missing compressor id");
1869 1 : return nullptr;
1870 : }
1871 35 : if (osDecompressorId == "imagecodecs_tiff")
1872 : {
1873 5 : psDecompressor = ZarrGetTIFFDecompressor();
1874 : }
1875 : else
1876 : {
1877 30 : psCompressor = CPLGetCompressor(osDecompressorId.c_str());
1878 30 : psDecompressor = CPLGetDecompressor(osDecompressorId.c_str());
1879 30 : if (psCompressor == nullptr || psDecompressor == nullptr)
1880 : {
1881 1 : CPLError(CE_Failure, CPLE_AppDefined,
1882 : "Decompressor %s not handled",
1883 : osDecompressorId.c_str());
1884 1 : return nullptr;
1885 : }
1886 : }
1887 : }
1888 : else
1889 : {
1890 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid compressor");
1891 1 : return nullptr;
1892 : }
1893 :
1894 1314 : CPLJSONArray oFiltersArray;
1895 1971 : const auto oFilters = oRoot["filters"];
1896 657 : if (!oFilters.IsValid())
1897 : {
1898 1 : CPLError(CE_Failure, CPLE_AppDefined, "filters missing");
1899 1 : return nullptr;
1900 : }
1901 656 : if (oFilters.GetType() == CPLJSONObject::Type::Null)
1902 : {
1903 : }
1904 43 : else if (oFilters.GetType() == CPLJSONObject::Type::Array)
1905 : {
1906 41 : oFiltersArray = oFilters.ToArray();
1907 54 : for (const auto &oFilter : oFiltersArray)
1908 : {
1909 28 : const auto osFilterId = oFilter["id"].ToString();
1910 14 : if (osFilterId.empty())
1911 : {
1912 1 : CPLError(CE_Failure, CPLE_AppDefined, "Missing filter id");
1913 1 : return nullptr;
1914 : }
1915 13 : if (!EQUAL(osFilterId.c_str(), "shuffle") &&
1916 21 : !EQUAL(osFilterId.c_str(), "quantize") &&
1917 8 : !EQUAL(osFilterId.c_str(), "fixedscaleoffset"))
1918 : {
1919 : const auto psFilterCompressor =
1920 4 : CPLGetCompressor(osFilterId.c_str());
1921 : const auto psFilterDecompressor =
1922 4 : CPLGetDecompressor(osFilterId.c_str());
1923 4 : if (psFilterCompressor == nullptr ||
1924 : psFilterDecompressor == nullptr)
1925 : {
1926 0 : CPLError(CE_Failure, CPLE_AppDefined,
1927 : "Filter %s not handled", osFilterId.c_str());
1928 0 : return nullptr;
1929 : }
1930 : }
1931 : }
1932 : }
1933 : else
1934 : {
1935 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid filters");
1936 2 : return nullptr;
1937 : }
1938 :
1939 653 : auto poArray = ZarrV2Array::Create(m_poSharedResource, GetFullName(),
1940 : osArrayName, aoDims, oType, aoDtypeElts,
1941 1306 : anBlockSize, bFortranOrder);
1942 653 : if (!poArray)
1943 1 : return nullptr;
1944 652 : poArray->SetCompressorJson(oCompressor);
1945 652 : poArray->SetUpdatable(m_bUpdatable); // must be set before SetAttributes()
1946 652 : poArray->SetFilename(osZarrayFilename);
1947 652 : poArray->SetDimSeparator(osDimSeparator);
1948 652 : poArray->SetCompressorDecompressor(osDecompressorId, psCompressor,
1949 : psDecompressor);
1950 652 : poArray->SetFilters(oFiltersArray);
1951 652 : if (!abyNoData.empty())
1952 : {
1953 112 : poArray->RegisterNoDataValue(abyNoData.data());
1954 : }
1955 :
1956 1956 : const auto gridMapping = oAttributes["grid_mapping"];
1957 652 : if (gridMapping.GetType() == CPLJSONObject::Type::String)
1958 : {
1959 3 : const std::string gridMappingName = gridMapping.ToString();
1960 1 : if (m_oMapMDArrays.find(gridMappingName) == m_oMapMDArrays.end())
1961 : {
1962 : const std::string osArrayFilenameDim = CPLFormFilenameSafe(
1963 1 : CPLFormFilenameSafe(m_osDirectoryName.c_str(),
1964 : gridMappingName.c_str(), nullptr)
1965 : .c_str(),
1966 2 : ".zarray", nullptr);
1967 : VSIStatBufL sStat;
1968 1 : if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
1969 : {
1970 2 : CPLJSONDocument oDoc;
1971 1 : if (oDoc.Load(osArrayFilenameDim))
1972 : {
1973 1 : LoadArray(gridMappingName, osArrayFilenameDim,
1974 2 : oDoc.GetRoot(), false, CPLJSONObject());
1975 : }
1976 : }
1977 : }
1978 : }
1979 :
1980 652 : poArray->ParseSpecialAttributes(m_pSelf.lock(), oAttributes);
1981 652 : poArray->SetAttributes(oAttributes);
1982 652 : poArray->SetDtype(oDtype);
1983 652 : RegisterArray(poArray);
1984 :
1985 : // If this is an indexing variable, attach it to the dimension.
1986 652 : if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
1987 : {
1988 146 : auto oIter = m_oMapDimensions.find(poArray->GetName());
1989 146 : if (oIter != m_oMapDimensions.end())
1990 : {
1991 146 : oIter->second->SetIndexingVariable(poArray);
1992 : }
1993 : }
1994 :
1995 652 : if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
1996 : "CACHE_TILE_PRESENCE", "NO")))
1997 : {
1998 2 : poArray->CacheTilePresence();
1999 : }
2000 :
2001 652 : return poArray;
2002 : }
2003 :
2004 : /************************************************************************/
2005 : /* ZarrV2Group::SetCompressorJson() */
2006 : /************************************************************************/
2007 :
2008 669 : void ZarrV2Array::SetCompressorJson(const CPLJSONObject &oCompressor)
2009 : {
2010 669 : m_oCompressorJSon = oCompressor;
2011 669 : if (oCompressor.GetType() != CPLJSONObject::Type::Null)
2012 : m_aosStructuralInfo.SetNameValue("COMPRESSOR",
2013 51 : oCompressor.ToString().c_str());
2014 669 : }
2015 :
2016 : /************************************************************************/
2017 : /* ZarrV2Group::SetFilters() */
2018 : /************************************************************************/
2019 :
2020 962 : void ZarrV2Array::SetFilters(const CPLJSONArray &oFiltersArray)
2021 : {
2022 962 : m_oFiltersArray = oFiltersArray;
2023 962 : if (oFiltersArray.Size() > 0)
2024 : m_aosStructuralInfo.SetNameValue("FILTERS",
2025 14 : oFiltersArray.ToString().c_str());
2026 962 : }
|