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