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