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