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 906 : 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 906 : const std::vector<GUInt64> &anBlockSize, bool bFortranOrder)
36 : : GDALAbstractMDArray(osParentName, osName),
37 : ZarrArray(poSharedResource, osParentName, osName, aoDims, oType,
38 : aoDtypeElts, anBlockSize),
39 906 : m_bFortranOrder(bFortranOrder)
40 : {
41 906 : m_oCompressorJSon.Deinit();
42 906 : }
43 :
44 : /************************************************************************/
45 : /* ZarrV2Array::Create() */
46 : /************************************************************************/
47 :
48 : std::shared_ptr<ZarrV2Array>
49 906 : 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 1812 : aoDtypeElts, anBlockSize, bFortranOrder));
59 906 : if (arr->m_nTotalTileCount == 0)
60 1 : return nullptr;
61 905 : arr->SetSelf(arr);
62 :
63 905 : return arr;
64 : }
65 :
66 : /************************************************************************/
67 : /* ~ZarrV2Array() */
68 : /************************************************************************/
69 :
70 1812 : ZarrV2Array::~ZarrV2Array()
71 : {
72 906 : ZarrV2Array::Flush();
73 1812 : }
74 :
75 : /************************************************************************/
76 : /* Flush() */
77 : /************************************************************************/
78 :
79 2112 : void ZarrV2Array::Flush()
80 : {
81 2112 : if (!m_bValid)
82 9 : return;
83 :
84 2103 : ZarrV2Array::FlushDirtyTile();
85 :
86 2103 : if (m_bDefinitionModified)
87 : {
88 325 : Serialize();
89 325 : m_bDefinitionModified = false;
90 : }
91 :
92 4206 : CPLJSONArray j_ARRAY_DIMENSIONS;
93 2103 : bool bDimensionsModified = false;
94 2103 : if (!m_aoDims.empty())
95 : {
96 4234 : for (const auto &poDim : m_aoDims)
97 : {
98 : const auto poZarrDim =
99 2745 : dynamic_cast<const ZarrDimension *>(poDim.get());
100 2745 : if (poZarrDim && poZarrDim->IsXArrayDimension())
101 : {
102 2288 : if (poZarrDim->IsModified())
103 8 : bDimensionsModified = true;
104 2288 : j_ARRAY_DIMENSIONS.Add(poDim->GetName());
105 : }
106 : else
107 : {
108 457 : j_ARRAY_DIMENSIONS = CPLJSONArray();
109 457 : break;
110 : }
111 : }
112 : }
113 :
114 4153 : if (m_oAttrGroup.IsModified() || bDimensionsModified ||
115 2042 : (m_bNew && j_ARRAY_DIMENSIONS.Size() != 0) || m_bUnitModified ||
116 4153 : m_bOffsetModified || m_bScaleModified || m_bSRSModified)
117 : {
118 372 : m_bNew = false;
119 :
120 744 : auto oAttrs = SerializeSpecialAttributes();
121 :
122 372 : if (j_ARRAY_DIMENSIONS.Size() != 0)
123 : {
124 356 : oAttrs.Delete("_ARRAY_DIMENSIONS");
125 356 : oAttrs.Add("_ARRAY_DIMENSIONS", j_ARRAY_DIMENSIONS);
126 : }
127 :
128 744 : CPLJSONDocument oDoc;
129 372 : oDoc.SetRoot(oAttrs);
130 : const std::string osAttrFilename =
131 372 : CPLFormFilenameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
132 744 : ".zattrs", nullptr);
133 372 : oDoc.Save(osAttrFilename);
134 372 : 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 325 : void ZarrV2Array::Serialize()
157 : {
158 650 : CPLJSONDocument oDoc;
159 650 : CPLJSONObject oRoot = oDoc.GetRoot();
160 :
161 650 : CPLJSONArray oChunks;
162 794 : for (const auto nBlockSize : m_anBlockSize)
163 : {
164 469 : oChunks.Add(static_cast<GInt64>(nBlockSize));
165 : }
166 325 : oRoot.Add("chunks", oChunks);
167 :
168 325 : 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 297 : oRoot.AddNull("compressor");
177 : }
178 :
179 325 : if (m_dtype.GetType() == CPLJSONObject::Type::Object)
180 312 : oRoot.Add("dtype", m_dtype["dummy"]);
181 : else
182 13 : oRoot.Add("dtype", m_dtype);
183 :
184 325 : if (m_pabyNoData == nullptr)
185 : {
186 315 : 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 325 : if (m_oFiltersArray.Size() == 0)
236 324 : oRoot.AddNull("filters");
237 : else
238 1 : oRoot.Add("filters", m_oFiltersArray);
239 :
240 325 : oRoot.Add("order", m_bFortranOrder ? "F" : "C");
241 :
242 650 : CPLJSONArray oShape;
243 794 : for (const auto &poDim : m_aoDims)
244 : {
245 469 : oShape.Add(static_cast<GInt64>(poDim->GetSize()));
246 : }
247 325 : oRoot.Add("shape", oShape);
248 :
249 325 : oRoot.Add("zarr_format", 2);
250 :
251 325 : if (m_osDimSeparator != ".")
252 : {
253 10 : oRoot.Add("dimension_separator", m_osDimSeparator);
254 : }
255 :
256 325 : oDoc.Save(m_osFilename);
257 :
258 325 : m_poSharedResource->SetZMetadataItem(m_osFilename, oRoot);
259 325 : }
260 :
261 : /************************************************************************/
262 : /* ZarrV2Array::NeedDecodedBuffer() */
263 : /************************************************************************/
264 :
265 11596 : bool ZarrV2Array::NeedDecodedBuffer() const
266 : {
267 : const size_t nSourceSize =
268 11596 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
269 11589 : if (m_oType.GetClass() == GEDTC_COMPOUND &&
270 6 : nSourceSize != m_oType.GetSize())
271 : {
272 4 : return true;
273 : }
274 11569 : else if (m_oType.GetClass() != GEDTC_STRING)
275 : {
276 23029 : for (const auto &elt : m_aoDtypeElts)
277 : {
278 11556 : if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative ||
279 11480 : elt.nativeType == DtypeElt::NativeType::STRING_ASCII ||
280 11480 : elt.nativeType == DtypeElt::NativeType::STRING_UNICODE)
281 : {
282 77 : return true;
283 : }
284 : }
285 : }
286 11500 : return false;
287 : }
288 :
289 : /************************************************************************/
290 : /* ZarrV2Array::AllocateWorkingBuffers() */
291 : /************************************************************************/
292 :
293 1903 : bool ZarrV2Array::AllocateWorkingBuffers() const
294 : {
295 1903 : if (m_bAllocateWorkingBuffersDone)
296 1434 : return m_bWorkingBuffersOK;
297 :
298 469 : m_bAllocateWorkingBuffersDone = true;
299 :
300 469 : size_t nSizeNeeded = m_nTileSize;
301 469 : 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 468 : if (NeedDecodedBuffer())
311 : {
312 43 : size_t nDecodedBufferSize = m_oType.GetSize();
313 126 : for (const auto &nBlockSize : m_anBlockSize)
314 : {
315 83 : if (nDecodedBufferSize > std::numeric_limits<size_t>::max() /
316 83 : static_cast<size_t>(nBlockSize))
317 : {
318 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
319 0 : return false;
320 : }
321 83 : nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
322 : }
323 43 : if (nSizeNeeded >
324 43 : 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 43 : nSizeNeeded += nDecodedBufferSize;
330 : }
331 :
332 : // Reserve a buffer for tile content
333 470 : 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 932 : m_bWorkingBuffersOK = AllocateWorkingBuffers(
346 466 : m_abyRawTileData, m_abyTmpRawTileData, m_abyDecodedTileData);
347 466 : return m_bWorkingBuffersOK;
348 : }
349 :
350 11137 : 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 11137 : abyRawTileData.resize(m_nTileSize);
366 11126 : 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 11112 : if (NeedDecodedBuffer())
376 : {
377 43 : size_t nDecodedBufferSize = m_oType.GetSize();
378 126 : for (const auto &nBlockSize : m_anBlockSize)
379 : {
380 83 : nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
381 : }
382 : try
383 : {
384 43 : 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 11124 : return true;
394 : #undef m_abyTmpRawTileData
395 : #undef m_abyRawTileData
396 : #undef m_abyDecodedTileData
397 : }
398 :
399 : /************************************************************************/
400 : /* ZarrV2Array::LoadTileData() */
401 : /************************************************************************/
402 :
403 4837 : bool ZarrV2Array::LoadTileData(const uint64_t *tileIndices,
404 : bool &bMissingTileOut) const
405 : {
406 9674 : return LoadTileData(tileIndices,
407 : false, // use mutex
408 4837 : m_psDecompressor, m_abyRawTileData, m_abyTmpRawTileData,
409 4837 : m_abyDecodedTileData, bMissingTileOut);
410 : }
411 :
412 15500 : 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 15500 : bMissingTileOut = false;
429 :
430 30260 : 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 15493 : osFilename = VSIFileManager::GetHandler(osFilename.c_str())
435 15507 : ->GetStreamingFilename(osFilename);
436 :
437 : // First if we have a tile presence cache, check tile presence from it
438 15491 : if (bUseMutex)
439 10653 : m_oMutex.lock();
440 30176 : auto poTilePresenceArray = OpenTilePresenceCache(false);
441 15509 : 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 15496 : if (bUseMutex)
467 10672 : m_oMutex.unlock();
468 :
469 15496 : VSILFILE *fp = nullptr;
470 : // This is the number of files returned in a S3 directory listing operation
471 15496 : constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
472 15496 : const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
473 : nullptr};
474 15516 : if ((m_osDimSeparator == "/" && !m_anBlockSize.empty() &&
475 31010 : m_anBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
476 15495 : (m_osDimSeparator != "/" &&
477 15476 : 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 10981 : "YES", true);
482 10893 : fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
483 : }
484 : else
485 : {
486 4514 : fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
487 : }
488 15349 : 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 12639 : bMissingTileOut = false;
498 12639 : bool bRet = true;
499 12639 : size_t nRawDataSize = abyRawTileData.size();
500 12504 : if (psDecompressor == nullptr)
501 : {
502 7083 : nRawDataSize = VSIFReadL(&abyRawTileData[0], 1, nRawDataSize, fp);
503 : }
504 : else
505 : {
506 5421 : VSIFSeekL(fp, 0, SEEK_END);
507 5412 : const auto nSize = VSIFTellL(fp);
508 5348 : VSIFSeekL(fp, 0, SEEK_SET);
509 5291 : 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 10672 : ZarrByteVectorQuickResize abyCompressedData;
518 : try
519 : {
520 5287 : 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 15701 : if (bRet &&
531 10500 : (abyCompressedData.empty() ||
532 5196 : VSIFReadL(&abyCompressedData[0], 1, abyCompressedData.size(),
533 5159 : 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 5126 : void *out_buffer = &abyRawTileData[0];
543 5183 : if (!psDecompressor->pfnFunc(
544 5224 : abyCompressedData.data(), abyCompressedData.size(),
545 : &out_buffer, &nRawDataSize, nullptr,
546 5241 : 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 12510 : VSIFCloseL(fp);
557 12499 : if (!bRet)
558 0 : return false;
559 :
560 12785 : 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 12353 : if (nRawDataSize != abyRawTileData.size())
591 : {
592 56 : 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 12225 : if (m_bFortranOrder && !m_aoDims.empty())
599 : {
600 46 : BlockTranspose(abyRawTileData, abyTmpRawTileData, true);
601 46 : std::swap(abyRawTileData, abyTmpRawTileData);
602 : }
603 :
604 12225 : if (!abyDecodedTileData.empty())
605 : {
606 : const size_t nSourceSize =
607 326 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
608 326 : const auto nDTSize = m_oType.GetSize();
609 326 : const size_t nValues = abyDecodedTileData.size() / nDTSize;
610 326 : const GByte *pSrc = abyRawTileData.data();
611 326 : GByte *pDst = &abyDecodedTileData[0];
612 2064 : for (size_t i = 0; i < nValues;
613 1931 : i++, pSrc += nSourceSize, pDst += nDTSize)
614 : {
615 1931 : DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
616 : }
617 : }
618 :
619 12225 : 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 10688 : for (size_t iReq = jobStruct->nFirstIdx;
710 10688 : iReq < jobStruct->nLastIdxNotIncluded; ++iReq)
711 : {
712 : // Check if we must early exit
713 : {
714 10672 : 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 10671 : uint64_t nTileIdx = 0;
723 32009 : for (size_t j = 0; j < l_nDims; ++j)
724 : {
725 21342 : if (j > 0)
726 10670 : nTileIdx *= aoDims[j - 1]->GetSize();
727 21338 : nTileIdx += tileIndices[j];
728 : }
729 :
730 10667 : 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 10652 : bool bIsEmpty = false;
739 10652 : bool success = poArray->LoadTileData(tileIndices,
740 : true, // use mutex
741 : psDecompressor, abyRawTileData,
742 : abyTmpRawTileData,
743 : abyDecodedTileData, bIsEmpty);
744 :
745 10155 : 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 16 : 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 15 : std::lock_guard<std::mutex> oLock(m_oMutex);
785 15 : if (nRemainingThreads == 0)
786 4 : break;
787 : }
788 11 : wtp->WaitEvent();
789 11 : }
790 :
791 4 : return bGlobalStatus;
792 : }
793 :
794 : /************************************************************************/
795 : /* ZarrV2Array::FlushDirtyTile() */
796 : /************************************************************************/
797 :
798 17774 : bool ZarrV2Array::FlushDirtyTile() const
799 : {
800 17774 : if (!m_bDirtyTile)
801 5710 : return true;
802 12064 : m_bDirtyTile = false;
803 :
804 24128 : std::string osFilename = BuildTileFilename(m_anCachedTiledIndices.data());
805 :
806 : const size_t nSourceSize =
807 12064 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
808 : const auto &abyTile =
809 12064 : m_abyDecodedTileData.empty() ? m_abyRawTileData : m_abyDecodedTileData;
810 :
811 12064 : 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 11374 : 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 11374 : 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 11374 : size_t nRawDataSize = m_abyRawTileData.size();
846 11376 : 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 11374 : 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 11374 : VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
890 11374 : 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 11374 : bool bRet = true;
898 11374 : if (m_psCompressor == nullptr)
899 : {
900 5924 : 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 11374 : VSIFCloseL(fp);
966 :
967 11374 : return bRet;
968 : }
969 :
970 : /************************************************************************/
971 : /* BuildTileFilename() */
972 : /************************************************************************/
973 :
974 27558 : std::string ZarrV2Array::BuildTileFilename(const uint64_t *tileIndices) const
975 : {
976 27558 : std::string osFilename;
977 27565 : if (m_aoDims.empty())
978 : {
979 1 : osFilename = "0";
980 : }
981 : else
982 : {
983 83198 : for (size_t i = 0; i < m_aoDims.size(); ++i)
984 : {
985 55617 : if (!osFilename.empty())
986 28086 : osFilename += m_osDimSeparator;
987 55662 : osFilename += std::to_string(tileIndices[i]);
988 : }
989 : }
990 :
991 55093 : return CPLFormFilenameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
992 82585 : 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 643 : 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 643 : if (obj.GetType() == CPLJSONObject::Type::String)
1066 : {
1067 1264 : const auto str = obj.ToString();
1068 632 : char chEndianness = 0;
1069 : char chType;
1070 : int nBytes;
1071 632 : DtypeElt elt;
1072 632 : if (str.size() < 3)
1073 3 : break;
1074 629 : chEndianness = str[0];
1075 629 : chType = str[1];
1076 629 : nBytes = atoi(str.c_str() + 2);
1077 629 : if (nBytes <= 0 || nBytes >= 1000)
1078 : break;
1079 :
1080 627 : elt.needByteSwapping = false;
1081 627 : if ((nBytes > 1 && chType != 'S') || chType == 'U')
1082 : {
1083 347 : if (chEndianness == '<')
1084 304 : 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 627 : if (!elts.empty())
1091 : {
1092 11 : elt.nativeOffset =
1093 11 : elts.back().nativeOffset + elts.back().nativeSize;
1094 : }
1095 627 : elt.nativeSize = nBytes;
1096 627 : if (chType == 'b' && nBytes == 1) // boolean
1097 : {
1098 66 : elt.nativeType = DtypeElt::NativeType::BOOLEAN;
1099 66 : eDT = GDT_Byte;
1100 : }
1101 561 : else if (chType == 'u' && nBytes == 1)
1102 : {
1103 193 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1104 193 : eDT = GDT_Byte;
1105 : }
1106 368 : else if (chType == 'i' && nBytes == 1)
1107 : {
1108 12 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1109 12 : eDT = GDT_Int8;
1110 : }
1111 356 : else if (chType == 'i' && nBytes == 2)
1112 : {
1113 20 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1114 20 : eDT = GDT_Int16;
1115 : }
1116 336 : else if (chType == 'i' && nBytes == 4)
1117 : {
1118 26 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1119 26 : eDT = GDT_Int32;
1120 : }
1121 310 : else if (chType == 'i' && nBytes == 8)
1122 : {
1123 15 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1124 15 : eDT = GDT_Int64;
1125 : }
1126 295 : else if (chType == 'u' && nBytes == 2)
1127 : {
1128 21 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1129 21 : eDT = GDT_UInt16;
1130 : }
1131 274 : else if (chType == 'u' && nBytes == 4)
1132 : {
1133 18 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1134 18 : eDT = GDT_UInt32;
1135 : }
1136 256 : else if (chType == 'u' && nBytes == 8)
1137 : {
1138 14 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1139 14 : eDT = GDT_UInt64;
1140 : }
1141 242 : else if (chType == 'f' && nBytes == 2)
1142 : {
1143 : // elt.nativeType = DtypeElt::NativeType::IEEEFP;
1144 : // elt.gdalTypeIsApproxOfNative = true;
1145 : // eDT = GDT_Float32;
1146 3 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
1147 3 : eDT = GDT_Float16;
1148 : }
1149 239 : else if (chType == 'f' && nBytes == 4)
1150 : {
1151 43 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
1152 43 : eDT = GDT_Float32;
1153 : }
1154 196 : else if (chType == 'f' && nBytes == 8)
1155 : {
1156 158 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
1157 158 : eDT = GDT_Float64;
1158 : }
1159 38 : else if (chType == 'c' && nBytes == 8)
1160 : {
1161 11 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
1162 11 : eDT = GDT_CFloat32;
1163 : }
1164 27 : else if (chType == 'c' && nBytes == 16)
1165 : {
1166 12 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
1167 12 : eDT = GDT_CFloat64;
1168 : }
1169 15 : else if (chType == 'S')
1170 : {
1171 9 : elt.nativeType = DtypeElt::NativeType::STRING_ASCII;
1172 9 : elt.gdalType = GDALExtendedDataType::CreateString(nBytes);
1173 9 : elt.gdalSize = elt.gdalType.GetSize();
1174 9 : elts.emplace_back(elt);
1175 9 : return GDALExtendedDataType::CreateString(nBytes);
1176 : }
1177 6 : else if (chType == 'U')
1178 : {
1179 5 : elt.nativeType = DtypeElt::NativeType::STRING_UNICODE;
1180 : // the dtype declaration is number of UCS4 characters. Store it
1181 : // as bytes
1182 5 : elt.nativeSize *= 4;
1183 : // We can really map UCS4 size to UTF-8
1184 5 : elt.gdalType = GDALExtendedDataType::CreateString();
1185 5 : elt.gdalSize = elt.gdalType.GetSize();
1186 5 : elts.emplace_back(elt);
1187 5 : return GDALExtendedDataType::CreateString();
1188 : }
1189 : else
1190 1 : break;
1191 612 : elt.gdalType = GDALExtendedDataType::Create(eDT);
1192 612 : elt.gdalSize = elt.gdalType.GetSize();
1193 612 : elts.emplace_back(elt);
1194 612 : return GDALExtendedDataType::Create(eDT);
1195 : }
1196 11 : else if (obj.GetType() == CPLJSONObject::Type::Array)
1197 : {
1198 9 : bool error = false;
1199 9 : const auto oArray = obj.ToArray();
1200 9 : std::vector<std::unique_ptr<GDALEDTComponent>> comps;
1201 9 : size_t offset = 0;
1202 9 : size_t alignmentMax = 1;
1203 29 : for (const auto &oElt : oArray)
1204 : {
1205 20 : const auto oEltArray = oElt.ToArray();
1206 40 : if (!oEltArray.IsValid() || oEltArray.Size() != 2 ||
1207 40 : oEltArray[0].GetType() != CPLJSONObject::Type::String)
1208 : {
1209 0 : error = true;
1210 0 : break;
1211 : }
1212 20 : GDALExtendedDataType subDT = ParseDtype(oEltArray[1], elts);
1213 35 : if (subDT.GetClass() == GEDTC_NUMERIC &&
1214 15 : subDT.GetNumericDataType() == GDT_Unknown)
1215 : {
1216 0 : error = true;
1217 0 : break;
1218 : }
1219 :
1220 40 : const std::string osName = oEltArray[0].ToString();
1221 : // Add padding for alignment
1222 20 : const size_t alignmentSub = GetAlignment(oEltArray[1]);
1223 20 : assert(alignmentSub);
1224 20 : alignmentMax = std::max(alignmentMax, alignmentSub);
1225 20 : offset = AlignOffsetOn(offset, alignmentSub);
1226 40 : comps.emplace_back(std::unique_ptr<GDALEDTComponent>(
1227 40 : new GDALEDTComponent(osName, offset, subDT)));
1228 20 : offset += subDT.GetSize();
1229 : }
1230 9 : if (error)
1231 0 : break;
1232 9 : size_t nTotalSize = offset;
1233 9 : nTotalSize = AlignOffsetOn(nTotalSize, alignmentMax);
1234 18 : return GDALExtendedDataType::Create(obj.ToString(), nTotalSize,
1235 18 : std::move(comps));
1236 : }
1237 : } while (false);
1238 8 : CPLError(CE_Failure, CPLE_AppDefined,
1239 : "Invalid or unsupported format for dtype: %s",
1240 16 : obj.ToString().c_str());
1241 8 : return GDALExtendedDataType::Create(GDT_Unknown);
1242 : }
1243 :
1244 635 : static void SetGDALOffset(const GDALExtendedDataType &dt,
1245 : const size_t nBaseOffset, std::vector<DtypeElt> &elts,
1246 : size_t &iCurElt)
1247 : {
1248 635 : if (dt.GetClass() == GEDTC_COMPOUND)
1249 : {
1250 9 : const auto &comps = dt.GetComponents();
1251 29 : for (const auto &comp : comps)
1252 : {
1253 20 : const size_t nBaseOffsetSub = nBaseOffset + comp->GetOffset();
1254 20 : SetGDALOffset(comp->GetType(), nBaseOffsetSub, elts, iCurElt);
1255 : }
1256 : }
1257 : else
1258 : {
1259 626 : elts[iCurElt].gdalOffset = nBaseOffset;
1260 626 : iCurElt++;
1261 : }
1262 635 : }
1263 :
1264 : /************************************************************************/
1265 : /* ZarrV2Group::LoadArray() */
1266 : /************************************************************************/
1267 :
1268 : std::shared_ptr<ZarrArray>
1269 641 : ZarrV2Group::LoadArray(const std::string &osArrayName,
1270 : const std::string &osZarrayFilename,
1271 : const CPLJSONObject &oRoot, bool bLoadedFromZMetadata,
1272 : const CPLJSONObject &oAttributesIn) const
1273 : {
1274 : // Add osZarrayFilename to m_poSharedResource during the scope
1275 : // of this function call.
1276 641 : ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
1277 1282 : osZarrayFilename);
1278 641 : if (!filenameAdder.ok())
1279 2 : return nullptr;
1280 :
1281 1917 : const auto osFormat = oRoot["zarr_format"].ToString();
1282 639 : if (osFormat != "2")
1283 : {
1284 3 : CPLError(CE_Failure, CPLE_NotSupported,
1285 : "Invalid value for zarr_format");
1286 3 : return nullptr;
1287 : }
1288 :
1289 636 : bool bFortranOrder = false;
1290 636 : const char *orderKey = "order";
1291 1908 : const auto osOrder = oRoot[orderKey].ToString();
1292 636 : if (osOrder == "C")
1293 : {
1294 : // ok
1295 : }
1296 33 : else if (osOrder == "F")
1297 : {
1298 30 : bFortranOrder = true;
1299 : }
1300 : else
1301 : {
1302 3 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid value for %s",
1303 : orderKey);
1304 3 : return nullptr;
1305 : }
1306 :
1307 1899 : const auto oShape = oRoot["shape"].ToArray();
1308 633 : if (!oShape.IsValid())
1309 : {
1310 3 : CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
1311 3 : return nullptr;
1312 : }
1313 :
1314 630 : const char *chunksKey = "chunks";
1315 1890 : const auto oChunks = oRoot[chunksKey].ToArray();
1316 630 : if (!oChunks.IsValid())
1317 : {
1318 3 : CPLError(CE_Failure, CPLE_AppDefined, "%s missing or not an array",
1319 : chunksKey);
1320 3 : return nullptr;
1321 : }
1322 :
1323 627 : if (oShape.Size() != oChunks.Size())
1324 : {
1325 2 : CPLError(CE_Failure, CPLE_AppDefined,
1326 : "shape and chunks arrays are of different size");
1327 2 : return nullptr;
1328 : }
1329 :
1330 1250 : CPLJSONObject oAttributes(oAttributesIn);
1331 625 : if (!bLoadedFromZMetadata)
1332 : {
1333 696 : CPLJSONDocument oDoc;
1334 : const std::string osZattrsFilename(CPLFormFilenameSafe(
1335 348 : CPLGetDirnameSafe(osZarrayFilename.c_str()).c_str(), ".zattrs",
1336 696 : nullptr));
1337 696 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
1338 348 : if (oDoc.Load(osZattrsFilename))
1339 : {
1340 114 : oAttributes = oDoc.GetRoot();
1341 : }
1342 : }
1343 :
1344 : // Deep-clone of oAttributes
1345 : {
1346 625 : CPLJSONDocument oTmpDoc;
1347 625 : oTmpDoc.SetRoot(oAttributes);
1348 625 : CPL_IGNORE_RET_VAL(oTmpDoc.LoadMemory(oTmpDoc.SaveAsString()));
1349 625 : oAttributes = oTmpDoc.GetRoot();
1350 : }
1351 :
1352 1250 : std::vector<std::shared_ptr<GDALDimension>> aoDims;
1353 1616 : for (int i = 0; i < oShape.Size(); ++i)
1354 : {
1355 992 : const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
1356 992 : if (nSize == 0)
1357 : {
1358 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
1359 1 : return nullptr;
1360 : }
1361 991 : aoDims.emplace_back(std::make_shared<ZarrDimension>(
1362 991 : m_poSharedResource,
1363 1982 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1364 1982 : std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
1365 991 : nSize));
1366 : }
1367 :
1368 : // XArray extension
1369 1872 : const auto arrayDimensionsObj = oAttributes["_ARRAY_DIMENSIONS"];
1370 :
1371 : const auto FindDimension =
1372 495 : [this, &aoDims, bLoadedFromZMetadata, &osArrayName,
1373 : &oAttributes](const std::string &osDimName,
1374 5662 : std::shared_ptr<GDALDimension> &poDim, int i)
1375 : {
1376 495 : auto oIter = m_oMapDimensions.find(osDimName);
1377 495 : if (oIter != m_oMapDimensions.end())
1378 : {
1379 238 : if (m_bDimSizeInUpdate ||
1380 118 : oIter->second->GetSize() == poDim->GetSize())
1381 : {
1382 120 : poDim = oIter->second;
1383 120 : return true;
1384 : }
1385 : else
1386 : {
1387 0 : CPLError(CE_Warning, CPLE_AppDefined,
1388 : "Size of _ARRAY_DIMENSIONS[%d] different "
1389 : "from the one of shape",
1390 : i);
1391 0 : return false;
1392 : }
1393 : }
1394 :
1395 : // Try to load the indexing variable.
1396 :
1397 : // If loading from zmetadata, we should have normally
1398 : // already loaded the dimension variables, unless they
1399 : // are in a upper level.
1400 555 : if (bLoadedFromZMetadata && osArrayName != osDimName &&
1401 555 : m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
1402 : {
1403 180 : auto poParent = m_poParent.lock();
1404 194 : while (poParent != nullptr)
1405 : {
1406 21 : oIter = poParent->m_oMapDimensions.find(osDimName);
1407 28 : if (oIter != poParent->m_oMapDimensions.end() &&
1408 7 : oIter->second->GetSize() == poDim->GetSize())
1409 : {
1410 7 : poDim = oIter->second;
1411 7 : return true;
1412 : }
1413 14 : poParent = poParent->m_poParent.lock();
1414 : }
1415 : }
1416 :
1417 : // Not loading from zmetadata, and not in m_oMapMDArrays,
1418 : // then stat() the indexing variable.
1419 266 : else if (!bLoadedFromZMetadata && osArrayName != osDimName &&
1420 266 : m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
1421 : {
1422 142 : std::string osDirName = m_osDirectoryName;
1423 : while (true)
1424 : {
1425 : const std::string osArrayFilenameDim = CPLFormFilenameSafe(
1426 136 : CPLFormFilenameSafe(osDirName.c_str(), osDimName.c_str(),
1427 : nullptr)
1428 : .c_str(),
1429 136 : ".zarray", nullptr);
1430 : VSIStatBufL sStat;
1431 136 : if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
1432 : {
1433 100 : CPLJSONDocument oDoc;
1434 50 : if (oDoc.Load(osArrayFilenameDim))
1435 : {
1436 50 : LoadArray(osDimName, osArrayFilenameDim, oDoc.GetRoot(),
1437 100 : false, CPLJSONObject());
1438 : }
1439 : }
1440 : else
1441 : {
1442 : // Recurse to upper level for datasets such as
1443 : // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
1444 : const std::string osDirNameNew =
1445 86 : CPLGetPathSafe(osDirName.c_str());
1446 86 : if (!osDirNameNew.empty() && osDirNameNew != osDirName)
1447 : {
1448 65 : osDirName = osDirNameNew;
1449 65 : continue;
1450 : }
1451 : }
1452 71 : break;
1453 65 : }
1454 : }
1455 :
1456 368 : oIter = m_oMapDimensions.find(osDimName);
1457 384 : if (oIter != m_oMapDimensions.end() &&
1458 16 : oIter->second->GetSize() == poDim->GetSize())
1459 : {
1460 16 : poDim = oIter->second;
1461 16 : return true;
1462 : }
1463 :
1464 704 : std::string osType;
1465 704 : std::string osDirection;
1466 352 : if (aoDims.size() == 1 && osArrayName == osDimName)
1467 : {
1468 124 : ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
1469 : osDirection);
1470 : }
1471 :
1472 : auto poDimLocal = std::make_shared<ZarrDimension>(
1473 352 : m_poSharedResource,
1474 704 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1475 704 : GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
1476 352 : poDimLocal->SetXArrayDimension();
1477 352 : m_oMapDimensions[osDimName] = poDimLocal;
1478 352 : poDim = poDimLocal;
1479 352 : return true;
1480 624 : };
1481 :
1482 624 : if (arrayDimensionsObj.GetType() == CPLJSONObject::Type::Array)
1483 : {
1484 660 : const auto arrayDims = arrayDimensionsObj.ToArray();
1485 330 : if (arrayDims.Size() == oShape.Size())
1486 : {
1487 330 : bool ok = true;
1488 825 : for (int i = 0; i < oShape.Size(); ++i)
1489 : {
1490 495 : if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
1491 : {
1492 990 : const auto osDimName = arrayDims[i].ToString();
1493 495 : ok &= FindDimension(osDimName, aoDims[i], i);
1494 : }
1495 : }
1496 330 : if (ok)
1497 : {
1498 330 : oAttributes.Delete("_ARRAY_DIMENSIONS");
1499 : }
1500 : }
1501 : else
1502 : {
1503 0 : CPLError(
1504 : CE_Warning, CPLE_AppDefined,
1505 : "Size of _ARRAY_DIMENSIONS different from the one of shape");
1506 : }
1507 : }
1508 :
1509 : // _NCZARR_ARRAY extension
1510 1872 : const auto nczarrArrayDimrefs = oRoot["_NCZARR_ARRAY"]["dimrefs"].ToArray();
1511 624 : if (nczarrArrayDimrefs.IsValid())
1512 : {
1513 42 : const auto arrayDims = nczarrArrayDimrefs.ToArray();
1514 21 : if (arrayDims.Size() == oShape.Size())
1515 : {
1516 : auto poRG =
1517 42 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
1518 21 : CPLAssert(poRG != nullptr);
1519 : while (true)
1520 : {
1521 48 : auto poNewRG = poRG->m_poParent.lock();
1522 48 : if (poNewRG == nullptr)
1523 21 : break;
1524 27 : poRG = std::move(poNewRG);
1525 27 : }
1526 :
1527 49 : for (int i = 0; i < oShape.Size(); ++i)
1528 : {
1529 28 : if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
1530 : {
1531 84 : const auto osDimFullpath = arrayDims[i].ToString();
1532 : const std::string osArrayFullname =
1533 56 : (GetFullName() != "/" ? GetFullName() : std::string()) +
1534 56 : '/' + osArrayName;
1535 48 : if (aoDims.size() == 1 &&
1536 20 : (osDimFullpath == osArrayFullname ||
1537 34 : osDimFullpath == "/" + osArrayFullname))
1538 : {
1539 : // If this is an indexing variable, then fetch the
1540 : // dimension type and direction, and patch the dimension
1541 28 : std::string osType;
1542 28 : std::string osDirection;
1543 14 : ZarrArray::GetDimensionTypeDirection(
1544 : oAttributes, osType, osDirection);
1545 :
1546 : auto poDimLocal = std::make_shared<ZarrDimension>(
1547 14 : m_poSharedResource,
1548 28 : std::dynamic_pointer_cast<ZarrGroupBase>(
1549 14 : m_pSelf.lock()),
1550 14 : GetFullName(), osArrayName, osType, osDirection,
1551 28 : aoDims[i]->GetSize());
1552 14 : aoDims[i] = poDimLocal;
1553 :
1554 14 : m_oMapDimensions[osArrayName] = std::move(poDimLocal);
1555 : }
1556 14 : else if (auto poDim =
1557 28 : poRG->OpenDimensionFromFullname(osDimFullpath))
1558 : {
1559 13 : if (poDim->GetSize() != aoDims[i]->GetSize())
1560 : {
1561 1 : CPLError(CE_Failure, CPLE_AppDefined,
1562 : "Inconsistency in size between NCZarr "
1563 : "dimension %s and regular dimension",
1564 : osDimFullpath.c_str());
1565 : }
1566 : else
1567 : {
1568 12 : aoDims[i] = std::move(poDim);
1569 : }
1570 : }
1571 : else
1572 : {
1573 1 : CPLError(CE_Failure, CPLE_AppDefined,
1574 : "Cannot find NCZarr dimension %s",
1575 : osDimFullpath.c_str());
1576 : }
1577 : }
1578 : }
1579 : }
1580 : else
1581 : {
1582 0 : CPLError(CE_Warning, CPLE_AppDefined,
1583 : "Size of _NCZARR_ARRAY.dimrefs different from the one of "
1584 : "shape");
1585 : }
1586 : }
1587 :
1588 624 : constexpr const char *dtypeKey = "dtype";
1589 1872 : auto oDtype = oRoot[dtypeKey];
1590 624 : if (!oDtype.IsValid())
1591 : {
1592 1 : CPLError(CE_Failure, CPLE_NotSupported, "%s missing", dtypeKey);
1593 1 : return nullptr;
1594 : }
1595 1246 : std::vector<DtypeElt> aoDtypeElts;
1596 1246 : const auto oType = ParseDtype(oDtype, aoDtypeElts);
1597 1228 : if (oType.GetClass() == GEDTC_NUMERIC &&
1598 605 : oType.GetNumericDataType() == GDT_Unknown)
1599 8 : return nullptr;
1600 615 : size_t iCurElt = 0;
1601 615 : SetGDALOffset(oType, 0, aoDtypeElts, iCurElt);
1602 :
1603 1230 : std::vector<GUInt64> anBlockSize;
1604 615 : if (!ZarrArray::ParseChunkSize(oChunks, oType, anBlockSize))
1605 2 : return nullptr;
1606 :
1607 1839 : std::string osDimSeparator = oRoot["dimension_separator"].ToString();
1608 613 : if (osDimSeparator.empty())
1609 603 : osDimSeparator = ".";
1610 :
1611 1226 : std::vector<GByte> abyNoData;
1612 :
1613 : struct NoDataFreer
1614 : {
1615 : std::vector<GByte> &m_abyNodata;
1616 : const GDALExtendedDataType &m_oType;
1617 :
1618 613 : NoDataFreer(std::vector<GByte> &abyNoDataIn,
1619 : const GDALExtendedDataType &oTypeIn)
1620 613 : : m_abyNodata(abyNoDataIn), m_oType(oTypeIn)
1621 : {
1622 613 : }
1623 :
1624 613 : ~NoDataFreer()
1625 613 : {
1626 613 : if (!m_abyNodata.empty())
1627 103 : m_oType.FreeDynamicMemory(&m_abyNodata[0]);
1628 613 : }
1629 : };
1630 :
1631 1226 : NoDataFreer NoDataFreer(abyNoData, oType);
1632 :
1633 1839 : auto oFillValue = oRoot["fill_value"];
1634 613 : auto eFillValueType = oFillValue.GetType();
1635 :
1636 : // Normally arrays are not supported, but that's what NCZarr 4.8.0 outputs
1637 614 : if (eFillValueType == CPLJSONObject::Type::Array &&
1638 614 : oFillValue.ToArray().Size() == 1)
1639 : {
1640 0 : oFillValue = oFillValue.ToArray()[0];
1641 0 : eFillValueType = oFillValue.GetType();
1642 : }
1643 :
1644 613 : if (!oFillValue.IsValid())
1645 : {
1646 : // fill_value is normally required but some implementations
1647 : // are lacking it: https://github.com/Unidata/netcdf-c/issues/2059
1648 1 : CPLError(CE_Warning, CPLE_AppDefined, "fill_value missing");
1649 : }
1650 612 : else if (eFillValueType == CPLJSONObject::Type::Null)
1651 : {
1652 : // Nothing to do
1653 : }
1654 111 : else if (eFillValueType == CPLJSONObject::Type::String)
1655 : {
1656 98 : const auto osFillValue = oFillValue.ToString();
1657 86 : if (oType.GetClass() == GEDTC_NUMERIC &&
1658 37 : CPLGetValueType(osFillValue.c_str()) != CPL_VALUE_STRING)
1659 : {
1660 8 : abyNoData.resize(oType.GetSize());
1661 : // Be tolerant with numeric values serialized as strings.
1662 8 : if (oType.GetNumericDataType() == GDT_Int64)
1663 : {
1664 : const int64_t nVal = static_cast<int64_t>(
1665 2 : std::strtoll(osFillValue.c_str(), nullptr, 10));
1666 2 : GDALCopyWords(&nVal, GDT_Int64, 0, &abyNoData[0],
1667 : oType.GetNumericDataType(), 0, 1);
1668 : }
1669 6 : else if (oType.GetNumericDataType() == GDT_UInt64)
1670 : {
1671 : const uint64_t nVal = static_cast<uint64_t>(
1672 2 : std::strtoull(osFillValue.c_str(), nullptr, 10));
1673 2 : GDALCopyWords(&nVal, GDT_UInt64, 0, &abyNoData[0],
1674 : oType.GetNumericDataType(), 0, 1);
1675 : }
1676 : else
1677 : {
1678 4 : const double dfNoDataValue = CPLAtof(osFillValue.c_str());
1679 4 : GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
1680 : oType.GetNumericDataType(), 0, 1);
1681 : }
1682 : }
1683 41 : else if (oType.GetClass() == GEDTC_NUMERIC)
1684 : {
1685 : double dfNoDataValue;
1686 29 : if (osFillValue == "NaN")
1687 : {
1688 10 : dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
1689 : }
1690 19 : else if (osFillValue == "Infinity")
1691 : {
1692 9 : dfNoDataValue = std::numeric_limits<double>::infinity();
1693 : }
1694 10 : else if (osFillValue == "-Infinity")
1695 : {
1696 9 : dfNoDataValue = -std::numeric_limits<double>::infinity();
1697 : }
1698 : else
1699 : {
1700 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1701 2 : return nullptr;
1702 : }
1703 28 : if (oType.GetNumericDataType() == GDT_Float16)
1704 : {
1705 : const GFloat16 hfNoDataValue =
1706 0 : static_cast<GFloat16>(dfNoDataValue);
1707 0 : abyNoData.resize(sizeof(hfNoDataValue));
1708 0 : memcpy(&abyNoData[0], &hfNoDataValue, sizeof(hfNoDataValue));
1709 : }
1710 28 : if (oType.GetNumericDataType() == GDT_Float32)
1711 : {
1712 12 : const float fNoDataValue = static_cast<float>(dfNoDataValue);
1713 12 : abyNoData.resize(sizeof(fNoDataValue));
1714 12 : memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
1715 : }
1716 16 : else if (oType.GetNumericDataType() == GDT_Float64)
1717 : {
1718 15 : abyNoData.resize(sizeof(dfNoDataValue));
1719 15 : memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
1720 : }
1721 : else
1722 : {
1723 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1724 1 : return nullptr;
1725 : }
1726 : }
1727 12 : else if (oType.GetClass() == GEDTC_STRING)
1728 : {
1729 : // zarr.open('unicode_be.zarr', mode = 'w', shape=(1,), dtype =
1730 : // '>U1', compressor = None) oddly generates "fill_value": "0"
1731 7 : if (osFillValue != "0")
1732 : {
1733 3 : std::vector<GByte> abyNativeFillValue(osFillValue.size() + 1);
1734 3 : memcpy(&abyNativeFillValue[0], osFillValue.data(),
1735 : osFillValue.size());
1736 3 : int nBytes = CPLBase64DecodeInPlace(&abyNativeFillValue[0]);
1737 3 : abyNativeFillValue.resize(nBytes + 1);
1738 3 : abyNativeFillValue[nBytes] = 0;
1739 3 : abyNoData.resize(oType.GetSize());
1740 3 : char *pDstStr = CPLStrdup(
1741 3 : reinterpret_cast<const char *>(&abyNativeFillValue[0]));
1742 3 : char **pDstPtr = reinterpret_cast<char **>(&abyNoData[0]);
1743 3 : memcpy(pDstPtr, &pDstStr, sizeof(pDstStr));
1744 : }
1745 : }
1746 : else
1747 : {
1748 5 : std::vector<GByte> abyNativeFillValue(osFillValue.size() + 1);
1749 5 : memcpy(&abyNativeFillValue[0], osFillValue.data(),
1750 : osFillValue.size());
1751 5 : int nBytes = CPLBase64DecodeInPlace(&abyNativeFillValue[0]);
1752 5 : abyNativeFillValue.resize(nBytes);
1753 5 : if (abyNativeFillValue.size() !=
1754 5 : aoDtypeElts.back().nativeOffset + aoDtypeElts.back().nativeSize)
1755 : {
1756 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1757 0 : return nullptr;
1758 : }
1759 5 : abyNoData.resize(oType.GetSize());
1760 5 : ZarrArray::DecodeSourceElt(aoDtypeElts, abyNativeFillValue.data(),
1761 5 : &abyNoData[0]);
1762 : }
1763 : }
1764 62 : else if (eFillValueType == CPLJSONObject::Type::Boolean ||
1765 14 : eFillValueType == CPLJSONObject::Type::Integer ||
1766 6 : eFillValueType == CPLJSONObject::Type::Long ||
1767 : eFillValueType == CPLJSONObject::Type::Double)
1768 : {
1769 61 : if (oType.GetClass() == GEDTC_NUMERIC)
1770 : {
1771 60 : const double dfNoDataValue = oFillValue.ToDouble();
1772 60 : if (oType.GetNumericDataType() == GDT_Int64)
1773 : {
1774 : const int64_t nNoDataValue =
1775 2 : static_cast<int64_t>(oFillValue.ToLong());
1776 2 : abyNoData.resize(oType.GetSize());
1777 2 : GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1778 : oType.GetNumericDataType(), 0, 1);
1779 : }
1780 61 : else if (oType.GetNumericDataType() == GDT_UInt64 &&
1781 : /* we can't really deal with nodata value between */
1782 : /* int64::max and uint64::max due to json-c limitations */
1783 3 : dfNoDataValue >= 0)
1784 : {
1785 : const int64_t nNoDataValue =
1786 3 : static_cast<int64_t>(oFillValue.ToLong());
1787 3 : abyNoData.resize(oType.GetSize());
1788 3 : GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1789 : oType.GetNumericDataType(), 0, 1);
1790 : }
1791 : else
1792 : {
1793 55 : abyNoData.resize(oType.GetSize());
1794 55 : GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
1795 : oType.GetNumericDataType(), 0, 1);
1796 : }
1797 : }
1798 : else
1799 : {
1800 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1801 1 : return nullptr;
1802 60 : }
1803 : }
1804 : else
1805 : {
1806 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1807 1 : return nullptr;
1808 : }
1809 :
1810 609 : const CPLCompressor *psCompressor = nullptr;
1811 609 : const CPLCompressor *psDecompressor = nullptr;
1812 1827 : const auto oCompressor = oRoot["compressor"];
1813 1218 : std::string osDecompressorId("NONE");
1814 :
1815 609 : if (!oCompressor.IsValid())
1816 : {
1817 1 : CPLError(CE_Failure, CPLE_AppDefined, "compressor missing");
1818 1 : return nullptr;
1819 : }
1820 608 : if (oCompressor.GetType() == CPLJSONObject::Type::Null)
1821 : {
1822 : // nothing to do
1823 : }
1824 32 : else if (oCompressor.GetType() == CPLJSONObject::Type::Object)
1825 : {
1826 31 : osDecompressorId = oCompressor["id"].ToString();
1827 31 : if (osDecompressorId.empty())
1828 : {
1829 1 : CPLError(CE_Failure, CPLE_AppDefined, "Missing compressor id");
1830 1 : return nullptr;
1831 : }
1832 30 : psCompressor = CPLGetCompressor(osDecompressorId.c_str());
1833 30 : psDecompressor = CPLGetDecompressor(osDecompressorId.c_str());
1834 30 : if (psCompressor == nullptr || psDecompressor == nullptr)
1835 : {
1836 1 : CPLError(CE_Failure, CPLE_AppDefined, "Decompressor %s not handled",
1837 : osDecompressorId.c_str());
1838 1 : return nullptr;
1839 : }
1840 : }
1841 : else
1842 : {
1843 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid compressor");
1844 1 : return nullptr;
1845 : }
1846 :
1847 1210 : CPLJSONArray oFiltersArray;
1848 1815 : const auto oFilters = oRoot["filters"];
1849 605 : if (!oFilters.IsValid())
1850 : {
1851 1 : CPLError(CE_Failure, CPLE_AppDefined, "filters missing");
1852 1 : return nullptr;
1853 : }
1854 604 : if (oFilters.GetType() == CPLJSONObject::Type::Null)
1855 : {
1856 : }
1857 9 : else if (oFilters.GetType() == CPLJSONObject::Type::Array)
1858 : {
1859 7 : oFiltersArray = oFilters.ToArray();
1860 11 : for (const auto &oFilter : oFiltersArray)
1861 : {
1862 10 : const auto osFilterId = oFilter["id"].ToString();
1863 5 : if (osFilterId.empty())
1864 : {
1865 1 : CPLError(CE_Failure, CPLE_AppDefined, "Missing filter id");
1866 1 : return nullptr;
1867 : }
1868 : const auto psFilterCompressor =
1869 4 : CPLGetCompressor(osFilterId.c_str());
1870 : const auto psFilterDecompressor =
1871 4 : CPLGetDecompressor(osFilterId.c_str());
1872 4 : if (psFilterCompressor == nullptr ||
1873 : psFilterDecompressor == nullptr)
1874 : {
1875 0 : CPLError(CE_Failure, CPLE_AppDefined, "Filter %s not handled",
1876 : osFilterId.c_str());
1877 0 : return nullptr;
1878 : }
1879 : }
1880 : }
1881 : else
1882 : {
1883 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid filters");
1884 2 : return nullptr;
1885 : }
1886 :
1887 601 : auto poArray = ZarrV2Array::Create(m_poSharedResource, GetFullName(),
1888 : osArrayName, aoDims, oType, aoDtypeElts,
1889 1202 : anBlockSize, bFortranOrder);
1890 601 : if (!poArray)
1891 1 : return nullptr;
1892 600 : poArray->SetCompressorJson(oCompressor);
1893 600 : poArray->SetUpdatable(m_bUpdatable); // must be set before SetAttributes()
1894 600 : poArray->SetFilename(osZarrayFilename);
1895 600 : poArray->SetDimSeparator(osDimSeparator);
1896 600 : poArray->SetCompressorDecompressor(osDecompressorId, psCompressor,
1897 : psDecompressor);
1898 600 : poArray->SetFilters(oFiltersArray);
1899 600 : if (!abyNoData.empty())
1900 : {
1901 103 : poArray->RegisterNoDataValue(abyNoData.data());
1902 : }
1903 :
1904 1800 : const auto gridMapping = oAttributes["grid_mapping"];
1905 600 : if (gridMapping.GetType() == CPLJSONObject::Type::String)
1906 : {
1907 3 : const std::string gridMappingName = gridMapping.ToString();
1908 1 : if (m_oMapMDArrays.find(gridMappingName) == m_oMapMDArrays.end())
1909 : {
1910 : const std::string osArrayFilenameDim = CPLFormFilenameSafe(
1911 1 : CPLFormFilenameSafe(m_osDirectoryName.c_str(),
1912 : gridMappingName.c_str(), nullptr)
1913 : .c_str(),
1914 2 : ".zarray", nullptr);
1915 : VSIStatBufL sStat;
1916 1 : if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
1917 : {
1918 2 : CPLJSONDocument oDoc;
1919 1 : if (oDoc.Load(osArrayFilenameDim))
1920 : {
1921 1 : LoadArray(gridMappingName, osArrayFilenameDim,
1922 2 : oDoc.GetRoot(), false, CPLJSONObject());
1923 : }
1924 : }
1925 : }
1926 : }
1927 :
1928 600 : poArray->ParseSpecialAttributes(m_pSelf.lock(), oAttributes);
1929 600 : poArray->SetAttributes(oAttributes);
1930 600 : poArray->SetDtype(oDtype);
1931 600 : RegisterArray(poArray);
1932 :
1933 : // If this is an indexing variable, attach it to the dimension.
1934 600 : if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
1935 : {
1936 138 : auto oIter = m_oMapDimensions.find(poArray->GetName());
1937 138 : if (oIter != m_oMapDimensions.end())
1938 : {
1939 138 : oIter->second->SetIndexingVariable(poArray);
1940 : }
1941 : }
1942 :
1943 600 : if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
1944 : "CACHE_TILE_PRESENCE", "NO")))
1945 : {
1946 2 : poArray->CacheTilePresence();
1947 : }
1948 :
1949 600 : return poArray;
1950 : }
|