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