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