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