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