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 <algorithm>
34 : #include <cassert>
35 : #include <cstdlib>
36 : #include <limits>
37 : #include <map>
38 : #include <set>
39 :
40 : /************************************************************************/
41 : /* ZarrV3Array::ZarrV3Array() */
42 : /************************************************************************/
43 :
44 270 : ZarrV3Array::ZarrV3Array(
45 : const std::shared_ptr<ZarrSharedResource> &poSharedResource,
46 : const std::string &osParentName, const std::string &osName,
47 : const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
48 : const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
49 270 : const std::vector<GUInt64> &anBlockSize)
50 : : GDALAbstractMDArray(osParentName, osName),
51 : ZarrArray(poSharedResource, osParentName, osName, aoDims, oType,
52 270 : aoDtypeElts, anBlockSize)
53 : {
54 270 : }
55 :
56 : /************************************************************************/
57 : /* ZarrV3Array::Create() */
58 : /************************************************************************/
59 :
60 : std::shared_ptr<ZarrV3Array>
61 270 : ZarrV3Array::Create(const std::shared_ptr<ZarrSharedResource> &poSharedResource,
62 : const std::string &osParentName, const std::string &osName,
63 : const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
64 : const GDALExtendedDataType &oType,
65 : const std::vector<DtypeElt> &aoDtypeElts,
66 : const std::vector<GUInt64> &anBlockSize)
67 : {
68 : auto arr = std::shared_ptr<ZarrV3Array>(
69 : new ZarrV3Array(poSharedResource, osParentName, osName, aoDims, oType,
70 540 : aoDtypeElts, anBlockSize));
71 270 : if (arr->m_nTotalTileCount == 0)
72 1 : return nullptr;
73 269 : arr->SetSelf(arr);
74 :
75 269 : return arr;
76 : }
77 :
78 : /************************************************************************/
79 : /* ~ZarrV3Array() */
80 : /************************************************************************/
81 :
82 540 : ZarrV3Array::~ZarrV3Array()
83 : {
84 270 : ZarrV3Array::Flush();
85 540 : }
86 :
87 : /************************************************************************/
88 : /* Flush() */
89 : /************************************************************************/
90 :
91 656 : void ZarrV3Array::Flush()
92 : {
93 656 : if (!m_bValid)
94 3 : return;
95 :
96 653 : ZarrV3Array::FlushDirtyTile();
97 :
98 653 : if (!m_aoDims.empty())
99 : {
100 1357 : for (const auto &poDim : m_aoDims)
101 : {
102 : const auto poZarrDim =
103 831 : dynamic_cast<const ZarrDimension *>(poDim.get());
104 831 : if (poZarrDim && poZarrDim->IsXArrayDimension())
105 : {
106 774 : if (poZarrDim->IsModified())
107 4 : m_bDefinitionModified = true;
108 : }
109 : else
110 : {
111 57 : break;
112 : }
113 : }
114 : }
115 :
116 1306 : CPLJSONObject oAttrs;
117 1268 : if (m_oAttrGroup.IsModified() || m_bUnitModified || m_bOffsetModified ||
118 1268 : m_bScaleModified || m_bSRSModified)
119 : {
120 38 : m_bNew = false;
121 :
122 38 : oAttrs = SerializeSpecialAttributes();
123 :
124 38 : m_bDefinitionModified = true;
125 : }
126 :
127 653 : if (m_bDefinitionModified)
128 : {
129 168 : Serialize(oAttrs);
130 168 : m_bDefinitionModified = false;
131 : }
132 : }
133 :
134 : /************************************************************************/
135 : /* ZarrV3Array::Serialize() */
136 : /************************************************************************/
137 :
138 168 : void ZarrV3Array::Serialize(const CPLJSONObject &oAttrs)
139 : {
140 336 : CPLJSONDocument oDoc;
141 336 : CPLJSONObject oRoot = oDoc.GetRoot();
142 :
143 168 : oRoot.Add("zarr_format", 3);
144 168 : oRoot.Add("node_type", "array");
145 :
146 336 : CPLJSONArray oShape;
147 373 : for (const auto &poDim : m_aoDims)
148 : {
149 205 : oShape.Add(static_cast<GInt64>(poDim->GetSize()));
150 : }
151 168 : oRoot.Add("shape", oShape);
152 :
153 168 : oRoot.Add("data_type", m_dtype.ToString());
154 :
155 : {
156 336 : CPLJSONObject oChunkGrid;
157 168 : oRoot.Add("chunk_grid", oChunkGrid);
158 168 : oChunkGrid.Add("name", "regular");
159 336 : CPLJSONObject oConfiguration;
160 168 : oChunkGrid.Add("configuration", oConfiguration);
161 168 : CPLJSONArray oChunks;
162 373 : for (const auto nBlockSize : m_anBlockSize)
163 : {
164 205 : oChunks.Add(static_cast<GInt64>(nBlockSize));
165 : }
166 168 : oConfiguration.Add("chunk_shape", oChunks);
167 : }
168 :
169 : {
170 336 : CPLJSONObject oChunkKeyEncoding;
171 168 : oRoot.Add("chunk_key_encoding", oChunkKeyEncoding);
172 168 : oChunkKeyEncoding.Add("name", m_bV2ChunkKeyEncoding ? "v2" : "default");
173 168 : CPLJSONObject oConfiguration;
174 168 : oChunkKeyEncoding.Add("configuration", oConfiguration);
175 168 : oConfiguration.Add("separator", m_osDimSeparator);
176 : }
177 :
178 168 : if (m_pabyNoData == nullptr)
179 : {
180 281 : if (m_oType.GetNumericDataType() == GDT_Float32 ||
181 137 : m_oType.GetNumericDataType() == GDT_Float64)
182 : {
183 18 : oRoot.Add("fill_value", "NaN");
184 : }
185 : else
186 : {
187 126 : oRoot.AddNull("fill_value");
188 : }
189 : }
190 : else
191 : {
192 40 : if (m_oType.GetNumericDataType() == GDT_CFloat32 ||
193 16 : m_oType.GetNumericDataType() == GDT_CFloat64)
194 : {
195 : double adfNoDataValue[2];
196 16 : GDALCopyWords(m_pabyNoData, m_oType.GetNumericDataType(), 0,
197 : adfNoDataValue, GDT_CFloat64, 0, 1);
198 16 : CPLJSONArray oArray;
199 48 : for (int i = 0; i < 2; ++i)
200 : {
201 32 : if (std::isnan(adfNoDataValue[i]))
202 6 : oArray.Add("NaN");
203 26 : else if (adfNoDataValue[i] ==
204 26 : std::numeric_limits<double>::infinity())
205 4 : oArray.Add("Infinity");
206 44 : else if (adfNoDataValue[i] ==
207 22 : -std::numeric_limits<double>::infinity())
208 4 : oArray.Add("-Infinity");
209 : else
210 18 : oArray.Add(adfNoDataValue[i]);
211 : }
212 16 : oRoot.Add("fill_value", oArray);
213 : }
214 : else
215 : {
216 8 : SerializeNumericNoData(oRoot);
217 : }
218 : }
219 :
220 168 : if (m_poCodecs)
221 : {
222 66 : oRoot.Add("codecs", m_poCodecs->GetJSon());
223 : }
224 :
225 168 : oRoot.Add("attributes", oAttrs);
226 :
227 : // Set dimension_names
228 168 : if (!m_aoDims.empty())
229 : {
230 288 : CPLJSONArray oDimensions;
231 333 : for (const auto &poDim : m_aoDims)
232 : {
233 : const auto poZarrDim =
234 205 : dynamic_cast<const ZarrDimension *>(poDim.get());
235 205 : if (poZarrDim && poZarrDim->IsXArrayDimension())
236 : {
237 189 : oDimensions.Add(poDim->GetName());
238 : }
239 : else
240 : {
241 16 : oDimensions = CPLJSONArray();
242 16 : break;
243 : }
244 : }
245 144 : if (oDimensions.Size() > 0)
246 : {
247 128 : oRoot.Add("dimension_names", oDimensions);
248 : }
249 : }
250 :
251 : // TODO: codecs
252 :
253 168 : oDoc.Save(m_osFilename);
254 168 : }
255 :
256 : /************************************************************************/
257 : /* ZarrV3Array::NeedDecodedBuffer() */
258 : /************************************************************************/
259 :
260 10952 : bool ZarrV3Array::NeedDecodedBuffer() const
261 : {
262 21881 : for (const auto &elt : m_aoDtypeElts)
263 : {
264 10921 : if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative)
265 : {
266 0 : return true;
267 : }
268 : }
269 10934 : return false;
270 : }
271 :
272 : /************************************************************************/
273 : /* ZarrV3Array::AllocateWorkingBuffers() */
274 : /************************************************************************/
275 :
276 153 : bool ZarrV3Array::AllocateWorkingBuffers() const
277 : {
278 153 : if (m_bAllocateWorkingBuffersDone)
279 5 : return m_bWorkingBuffersOK;
280 :
281 148 : m_bAllocateWorkingBuffersDone = true;
282 :
283 148 : size_t nSizeNeeded = m_nTileSize;
284 148 : if (NeedDecodedBuffer())
285 : {
286 0 : size_t nDecodedBufferSize = m_oType.GetSize();
287 0 : for (const auto &nBlockSize : m_anBlockSize)
288 : {
289 0 : if (nDecodedBufferSize > std::numeric_limits<size_t>::max() /
290 0 : static_cast<size_t>(nBlockSize))
291 : {
292 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
293 0 : return false;
294 : }
295 0 : nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
296 : }
297 0 : if (nSizeNeeded >
298 0 : std::numeric_limits<size_t>::max() - nDecodedBufferSize)
299 : {
300 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
301 0 : return false;
302 : }
303 0 : nSizeNeeded += nDecodedBufferSize;
304 : }
305 :
306 : // Reserve a buffer for tile content
307 148 : if (nSizeNeeded > 1024 * 1024 * 1024 &&
308 0 : !CPLTestBool(CPLGetConfigOption("ZARR_ALLOW_BIG_TILE_SIZE", "NO")))
309 : {
310 0 : CPLError(CE_Failure, CPLE_AppDefined,
311 : "Zarr tile allocation would require " CPL_FRMT_GUIB " bytes. "
312 : "By default the driver limits to 1 GB. To allow that memory "
313 : "allocation, set the ZARR_ALLOW_BIG_TILE_SIZE configuration "
314 : "option to YES.",
315 : static_cast<GUIntBig>(nSizeNeeded));
316 0 : return false;
317 : }
318 :
319 148 : m_bWorkingBuffersOK =
320 148 : AllocateWorkingBuffers(m_abyRawTileData, m_abyDecodedTileData);
321 148 : return m_bWorkingBuffersOK;
322 : }
323 :
324 10811 : bool ZarrV3Array::AllocateWorkingBuffers(
325 : ZarrByteVectorQuickResize &abyRawTileData,
326 : ZarrByteVectorQuickResize &abyDecodedTileData) const
327 : {
328 : // This method should NOT modify any ZarrArray member, as it is going to
329 : // be called concurrently from several threads.
330 :
331 : // Set those #define to avoid accidental use of some global variables
332 : #define m_abyRawTileData cannot_use_here
333 : #define m_abyDecodedTileData cannot_use_here
334 :
335 : try
336 : {
337 10811 : abyRawTileData.resize(m_nTileSize);
338 : }
339 0 : catch (const std::bad_alloc &e)
340 : {
341 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
342 0 : return false;
343 : }
344 :
345 10788 : if (NeedDecodedBuffer())
346 : {
347 0 : size_t nDecodedBufferSize = m_oType.GetSize();
348 0 : for (const auto &nBlockSize : m_anBlockSize)
349 : {
350 0 : nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
351 : }
352 : try
353 : {
354 0 : abyDecodedTileData.resize(nDecodedBufferSize);
355 : }
356 0 : catch (const std::bad_alloc &e)
357 : {
358 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
359 0 : return false;
360 : }
361 : }
362 :
363 10801 : return true;
364 : #undef m_abyRawTileData
365 : #undef m_abyDecodedTileData
366 : }
367 :
368 : /************************************************************************/
369 : /* ZarrV3Array::LoadTileData() */
370 : /************************************************************************/
371 :
372 416 : bool ZarrV3Array::LoadTileData(const uint64_t *tileIndices,
373 : bool &bMissingTileOut) const
374 : {
375 416 : return LoadTileData(tileIndices,
376 : false, // use mutex
377 416 : m_poCodecs.get(), m_abyRawTileData,
378 832 : m_abyDecodedTileData, bMissingTileOut);
379 : }
380 :
381 11070 : bool ZarrV3Array::LoadTileData(const uint64_t *tileIndices, bool bUseMutex,
382 : ZarrV3CodecSequence *poCodecs,
383 : ZarrByteVectorQuickResize &abyRawTileData,
384 : ZarrByteVectorQuickResize &abyDecodedTileData,
385 : bool &bMissingTileOut) const
386 : {
387 : // This method should NOT modify any ZarrArray member, as it is going to
388 : // be called concurrently from several threads.
389 :
390 : // Set those #define to avoid accidental use of some global variables
391 : #define m_abyRawTileData cannot_use_here
392 : #define m_abyDecodedTileData cannot_use_here
393 : #define m_poCodecs cannot_use_here
394 :
395 11070 : bMissingTileOut = false;
396 :
397 21568 : std::string osFilename = BuildTileFilename(tileIndices);
398 :
399 : // For network file systems, get the streaming version of the filename,
400 : // as we don't need arbitrary seeking in the file
401 11055 : osFilename = VSIFileManager::GetHandler(osFilename.c_str())
402 11076 : ->GetStreamingFilename(osFilename);
403 :
404 : // First if we have a tile presence cache, check tile presence from it
405 11056 : if (bUseMutex)
406 10629 : m_oMutex.lock();
407 21709 : auto poTilePresenceArray = OpenTilePresenceCache(false);
408 11088 : if (poTilePresenceArray)
409 : {
410 18 : std::vector<GUInt64> anTileIdx(m_aoDims.size());
411 18 : const std::vector<size_t> anCount(m_aoDims.size(), 1);
412 18 : const std::vector<GInt64> anArrayStep(m_aoDims.size(), 0);
413 18 : const std::vector<GPtrDiff_t> anBufferStride(m_aoDims.size(), 0);
414 18 : const auto eByteDT = GDALExtendedDataType::Create(GDT_Byte);
415 54 : for (size_t i = 0; i < m_aoDims.size(); ++i)
416 : {
417 36 : anTileIdx[i] = static_cast<GUInt64>(tileIndices[i]);
418 : }
419 18 : GByte byValue = 0;
420 18 : if (poTilePresenceArray->Read(anTileIdx.data(), anCount.data(),
421 : anArrayStep.data(), anBufferStride.data(),
422 36 : eByteDT, &byValue) &&
423 18 : byValue == 0)
424 : {
425 13 : if (bUseMutex)
426 0 : m_oMutex.unlock();
427 13 : CPLDebugOnly(ZARR_DEBUG_KEY, "Tile %s missing (=nodata)",
428 : osFilename.c_str());
429 13 : bMissingTileOut = true;
430 13 : return true;
431 : }
432 : }
433 11075 : if (bUseMutex)
434 10672 : m_oMutex.unlock();
435 :
436 11074 : VSILFILE *fp = nullptr;
437 : // This is the number of files returned in a S3 directory listing operation
438 11074 : constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
439 11074 : const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
440 : nullptr};
441 22145 : if ((m_osDimSeparator == "/" && !m_anBlockSize.empty() &&
442 33151 : m_anBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
443 11038 : (m_osDimSeparator != "/" &&
444 2 : m_nTotalTileCount > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING))
445 : {
446 : // Avoid issuing ReadDir() when a lot of files are expected
447 : CPLConfigOptionSetter optionSetter("GDAL_DISABLE_READDIR_ON_OPEN",
448 0 : "YES", true);
449 0 : fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
450 : }
451 : else
452 : {
453 11006 : fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
454 : }
455 11054 : if (fp == nullptr)
456 : {
457 : // Missing files are OK and indicate nodata_value
458 299 : CPLDebugOnly(ZARR_DEBUG_KEY, "Tile %s missing (=nodata)",
459 : osFilename.c_str());
460 299 : bMissingTileOut = true;
461 299 : return true;
462 : }
463 :
464 10755 : bMissingTileOut = false;
465 :
466 10755 : CPLAssert(abyRawTileData.capacity() >= m_nTileSize);
467 : // should not fail
468 10672 : abyRawTileData.resize(m_nTileSize);
469 :
470 10610 : bool bRet = true;
471 10610 : size_t nRawDataSize = abyRawTileData.size();
472 10591 : if (poCodecs == nullptr)
473 : {
474 5269 : nRawDataSize = VSIFReadL(&abyRawTileData[0], 1, nRawDataSize, fp);
475 : }
476 : else
477 : {
478 5322 : VSIFSeekL(fp, 0, SEEK_END);
479 5342 : const auto nSize = VSIFTellL(fp);
480 5070 : VSIFSeekL(fp, 0, SEEK_SET);
481 5084 : if (nSize > static_cast<vsi_l_offset>(std::numeric_limits<int>::max()))
482 : {
483 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too large tile %s",
484 : osFilename.c_str());
485 0 : bRet = false;
486 : }
487 : else
488 : {
489 : try
490 : {
491 5026 : abyRawTileData.resize(static_cast<size_t>(nSize));
492 : }
493 0 : catch (const std::exception &)
494 : {
495 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
496 : "Cannot allocate memory for tile %s",
497 : osFilename.c_str());
498 0 : bRet = false;
499 : }
500 :
501 10187 : if (bRet && (abyRawTileData.empty() ||
502 5179 : VSIFReadL(&abyRawTileData[0], 1, abyRawTileData.size(),
503 5019 : fp) != abyRawTileData.size()))
504 : {
505 0 : CPLError(CE_Failure, CPLE_AppDefined,
506 : "Could not read tile %s correctly",
507 : osFilename.c_str());
508 0 : bRet = false;
509 : }
510 : else
511 : {
512 5007 : if (!poCodecs->Decode(abyRawTileData))
513 : {
514 0 : CPLError(CE_Failure, CPLE_AppDefined,
515 : "Decompression of tile %s failed",
516 : osFilename.c_str());
517 0 : bRet = false;
518 : }
519 : }
520 : }
521 : }
522 10579 : VSIFCloseL(fp);
523 10523 : if (!bRet)
524 0 : return false;
525 :
526 10523 : if (nRawDataSize != abyRawTileData.size())
527 : {
528 0 : CPLError(CE_Failure, CPLE_AppDefined,
529 : "Decompressed tile %s has not expected size. "
530 : "Got %u instead of %u",
531 : osFilename.c_str(),
532 0 : static_cast<unsigned>(abyRawTileData.size()),
533 : static_cast<unsigned>(nRawDataSize));
534 0 : return false;
535 : }
536 :
537 10199 : if (!abyDecodedTileData.empty())
538 : {
539 : const size_t nSourceSize =
540 0 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
541 0 : const auto nDTSize = m_oType.GetSize();
542 0 : const size_t nValues = abyDecodedTileData.size() / nDTSize;
543 0 : CPLAssert(nValues == m_nTileSize / nSourceSize);
544 0 : const GByte *pSrc = abyRawTileData.data();
545 0 : GByte *pDst = &abyDecodedTileData[0];
546 171 : for (size_t i = 0; i < nValues;
547 0 : i++, pSrc += nSourceSize, pDst += nDTSize)
548 : {
549 0 : DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
550 : }
551 : }
552 :
553 10298 : return true;
554 :
555 : #undef m_abyRawTileData
556 : #undef m_abyDecodedTileData
557 : #undef m_poCodecs
558 : }
559 :
560 : /************************************************************************/
561 : /* ZarrV3Array::IAdviseRead() */
562 : /************************************************************************/
563 :
564 6 : bool ZarrV3Array::IAdviseRead(const GUInt64 *arrayStartIdx, const size_t *count,
565 : CSLConstList papszOptions) const
566 : {
567 12 : std::vector<uint64_t> anIndicesCur;
568 6 : int nThreadsMax = 0;
569 12 : std::vector<uint64_t> anReqTilesIndices;
570 6 : size_t nReqTiles = 0;
571 6 : if (!IAdviseReadCommon(arrayStartIdx, count, papszOptions, anIndicesCur,
572 : nThreadsMax, anReqTilesIndices, nReqTiles))
573 : {
574 2 : return false;
575 : }
576 4 : if (nThreadsMax <= 1)
577 : {
578 0 : return true;
579 : }
580 :
581 : const int nThreads =
582 4 : static_cast<int>(std::min(static_cast<size_t>(nThreadsMax), nReqTiles));
583 :
584 4 : CPLWorkerThreadPool *wtp = GDALGetGlobalThreadPool(nThreadsMax);
585 4 : if (wtp == nullptr)
586 0 : return false;
587 :
588 : struct JobStruct
589 : {
590 : JobStruct() = default;
591 :
592 : JobStruct(const JobStruct &) = delete;
593 : JobStruct &operator=(const JobStruct &) = delete;
594 :
595 : JobStruct(JobStruct &&) = default;
596 : JobStruct &operator=(JobStruct &&) = default;
597 :
598 : const ZarrV3Array *poArray = nullptr;
599 : bool *pbGlobalStatus = nullptr;
600 : int *pnRemainingThreads = nullptr;
601 : const std::vector<uint64_t> *panReqTilesIndices = nullptr;
602 : size_t nFirstIdx = 0;
603 : size_t nLastIdxNotIncluded = 0;
604 : };
605 :
606 4 : std::vector<JobStruct> asJobStructs;
607 :
608 4 : bool bGlobalStatus = true;
609 4 : int nRemainingThreads = nThreads;
610 : // Check for very highly overflow in below loop
611 4 : assert(static_cast<size_t>(nThreads) <
612 : std::numeric_limits<size_t>::max() / nReqTiles);
613 :
614 : // Setup jobs
615 20 : for (int i = 0; i < nThreads; i++)
616 : {
617 16 : JobStruct jobStruct;
618 16 : jobStruct.poArray = this;
619 16 : jobStruct.pbGlobalStatus = &bGlobalStatus;
620 16 : jobStruct.pnRemainingThreads = &nRemainingThreads;
621 16 : jobStruct.panReqTilesIndices = &anReqTilesIndices;
622 16 : jobStruct.nFirstIdx = static_cast<size_t>(i * nReqTiles / nThreads);
623 16 : jobStruct.nLastIdxNotIncluded = std::min(
624 16 : static_cast<size_t>((i + 1) * nReqTiles / nThreads), nReqTiles);
625 16 : asJobStructs.emplace_back(std::move(jobStruct));
626 : }
627 :
628 16 : const auto JobFunc = [](void *pThreadData)
629 : {
630 16 : const JobStruct *jobStruct =
631 : static_cast<const JobStruct *>(pThreadData);
632 :
633 16 : const auto poArray = jobStruct->poArray;
634 16 : const auto &aoDims = poArray->GetDimensions();
635 16 : const size_t l_nDims = poArray->GetDimensionCount();
636 16 : ZarrByteVectorQuickResize abyRawTileData;
637 15 : ZarrByteVectorQuickResize abyDecodedTileData;
638 0 : std::unique_ptr<ZarrV3CodecSequence> poCodecs;
639 16 : if (poArray->m_poCodecs)
640 : {
641 8 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
642 8 : poCodecs = poArray->m_poCodecs->Clone();
643 : }
644 :
645 10688 : for (size_t iReq = jobStruct->nFirstIdx;
646 10688 : iReq < jobStruct->nLastIdxNotIncluded; ++iReq)
647 : {
648 : // Check if we must early exit
649 : {
650 10671 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
651 10672 : if (!(*jobStruct->pbGlobalStatus))
652 0 : return;
653 : }
654 :
655 : const uint64_t *tileIndices =
656 10672 : jobStruct->panReqTilesIndices->data() + iReq * l_nDims;
657 :
658 10665 : uint64_t nTileIdx = 0;
659 31990 : for (size_t j = 0; j < l_nDims; ++j)
660 : {
661 21329 : if (j > 0)
662 10668 : nTileIdx *= aoDims[j - 1]->GetSize();
663 21325 : nTileIdx += tileIndices[j];
664 : }
665 :
666 10661 : if (!poArray->AllocateWorkingBuffers(abyRawTileData,
667 : abyDecodedTileData))
668 : {
669 0 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
670 0 : *jobStruct->pbGlobalStatus = false;
671 0 : break;
672 : }
673 :
674 10635 : bool bIsEmpty = false;
675 10635 : bool success = poArray->LoadTileData(tileIndices,
676 : true, // use mutex
677 : poCodecs.get(), abyRawTileData,
678 : abyDecodedTileData, bIsEmpty);
679 :
680 10445 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
681 10672 : if (!success)
682 : {
683 0 : *jobStruct->pbGlobalStatus = false;
684 0 : break;
685 : }
686 :
687 21344 : CachedTile cachedTile;
688 10672 : if (!bIsEmpty)
689 : {
690 10670 : if (!abyDecodedTileData.empty())
691 0 : std::swap(cachedTile.abyDecoded, abyDecodedTileData);
692 : else
693 10670 : std::swap(cachedTile.abyDecoded, abyRawTileData);
694 : }
695 10672 : poArray->m_oMapTileIndexToCachedTile[nTileIdx] =
696 21344 : std::move(cachedTile);
697 : }
698 :
699 17 : std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
700 16 : (*jobStruct->pnRemainingThreads)--;
701 : };
702 :
703 : // Start jobs
704 20 : for (int i = 0; i < nThreads; i++)
705 : {
706 16 : if (!wtp->SubmitJob(JobFunc, &asJobStructs[i]))
707 : {
708 0 : std::lock_guard<std::mutex> oLock(m_oMutex);
709 0 : bGlobalStatus = false;
710 0 : nRemainingThreads = i;
711 0 : break;
712 : }
713 : }
714 :
715 : // Wait for all jobs to be finished
716 : while (true)
717 : {
718 : {
719 17 : std::lock_guard<std::mutex> oLock(m_oMutex);
720 17 : if (nRemainingThreads == 0)
721 4 : break;
722 : }
723 13 : wtp->WaitEvent();
724 13 : }
725 :
726 4 : return bGlobalStatus;
727 : }
728 :
729 : /************************************************************************/
730 : /* ZarrV3Array::FlushDirtyTile() */
731 : /************************************************************************/
732 :
733 11516 : bool ZarrV3Array::FlushDirtyTile() const
734 : {
735 11516 : if (!m_bDirtyTile)
736 772 : return true;
737 10744 : m_bDirtyTile = false;
738 :
739 21488 : std::string osFilename = BuildTileFilename(m_anCachedTiledIndices.data());
740 :
741 : const size_t nSourceSize =
742 10744 : m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
743 : const auto &abyTile =
744 10744 : m_abyDecodedTileData.empty() ? m_abyRawTileData : m_abyDecodedTileData;
745 :
746 10744 : if (IsEmptyTile(abyTile))
747 : {
748 2 : m_bCachedTiledEmpty = true;
749 :
750 : VSIStatBufL sStat;
751 2 : if (VSIStatL(osFilename.c_str(), &sStat) == 0)
752 : {
753 0 : CPLDebugOnly(ZARR_DEBUG_KEY,
754 : "Deleting tile %s that has now empty content",
755 : osFilename.c_str());
756 0 : return VSIUnlink(osFilename.c_str()) == 0;
757 : }
758 2 : return true;
759 : }
760 :
761 10742 : if (!m_abyDecodedTileData.empty())
762 : {
763 0 : const size_t nDTSize = m_oType.GetSize();
764 0 : const size_t nValues = m_abyDecodedTileData.size() / nDTSize;
765 0 : GByte *pDst = &m_abyRawTileData[0];
766 0 : const GByte *pSrc = m_abyDecodedTileData.data();
767 0 : for (size_t i = 0; i < nValues;
768 0 : i++, pDst += nSourceSize, pSrc += nDTSize)
769 : {
770 0 : EncodeElt(m_aoDtypeElts, pSrc, pDst);
771 : }
772 : }
773 :
774 10742 : const size_t nSizeBefore = m_abyRawTileData.size();
775 10742 : if (m_poCodecs)
776 : {
777 5405 : if (!m_poCodecs->Encode(m_abyRawTileData))
778 : {
779 0 : m_abyRawTileData.resize(nSizeBefore);
780 0 : return false;
781 : }
782 : }
783 :
784 10742 : if (m_osDimSeparator == "/")
785 : {
786 10742 : std::string osDir = CPLGetDirname(osFilename.c_str());
787 : VSIStatBufL sStat;
788 10742 : if (VSIStatL(osDir.c_str(), &sStat) != 0)
789 : {
790 204 : if (VSIMkdirRecursive(osDir.c_str(), 0755) != 0)
791 : {
792 0 : CPLError(CE_Failure, CPLE_AppDefined,
793 : "Cannot create directory %s", osDir.c_str());
794 0 : m_abyRawTileData.resize(nSizeBefore);
795 0 : return false;
796 : }
797 : }
798 : }
799 :
800 10742 : VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
801 10742 : if (fp == nullptr)
802 : {
803 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot create tile %s",
804 : osFilename.c_str());
805 0 : m_abyRawTileData.resize(nSizeBefore);
806 0 : return false;
807 : }
808 :
809 10742 : bool bRet = true;
810 10742 : const size_t nRawDataSize = m_abyRawTileData.size();
811 10742 : if (VSIFWriteL(m_abyRawTileData.data(), 1, nRawDataSize, fp) !=
812 : nRawDataSize)
813 : {
814 0 : CPLError(CE_Failure, CPLE_AppDefined,
815 : "Could not write tile %s correctly", osFilename.c_str());
816 0 : bRet = false;
817 : }
818 10742 : VSIFCloseL(fp);
819 :
820 10742 : m_abyRawTileData.resize(nSizeBefore);
821 :
822 10742 : return bRet;
823 : }
824 :
825 : /************************************************************************/
826 : /* BuildTileFilename() */
827 : /************************************************************************/
828 :
829 21821 : std::string ZarrV3Array::BuildTileFilename(const uint64_t *tileIndices) const
830 : {
831 21821 : if (m_aoDims.empty())
832 : {
833 : return CPLFormFilename(CPLGetDirname(m_osFilename.c_str()),
834 0 : m_bV2ChunkKeyEncoding ? "0" : "c", nullptr);
835 : }
836 : else
837 : {
838 43564 : std::string osFilename(CPLGetDirname(m_osFilename.c_str()));
839 21808 : osFilename += '/';
840 21812 : if (!m_bV2ChunkKeyEncoding)
841 : {
842 21793 : osFilename += 'c';
843 : }
844 65302 : for (size_t i = 0; i < m_aoDims.size(); ++i)
845 : {
846 43459 : if (i > 0 || !m_bV2ChunkKeyEncoding)
847 43477 : osFilename += m_osDimSeparator;
848 43431 : osFilename += std::to_string(tileIndices[i]);
849 : }
850 21840 : return osFilename;
851 : }
852 : }
853 :
854 : /************************************************************************/
855 : /* GetDataDirectory() */
856 : /************************************************************************/
857 :
858 2 : std::string ZarrV3Array::GetDataDirectory() const
859 : {
860 2 : return std::string(CPLGetDirname(m_osFilename.c_str()));
861 : }
862 :
863 : /************************************************************************/
864 : /* GetTileIndicesFromFilename() */
865 : /************************************************************************/
866 :
867 : CPLStringList
868 4 : ZarrV3Array::GetTileIndicesFromFilename(const char *pszFilename) const
869 : {
870 4 : if (!m_bV2ChunkKeyEncoding)
871 : {
872 4 : if (pszFilename[0] != 'c')
873 2 : return CPLStringList();
874 2 : if (m_osDimSeparator == "/")
875 : {
876 2 : if (pszFilename[1] != '/' && pszFilename[1] != '\\')
877 0 : return CPLStringList();
878 : }
879 0 : else if (pszFilename[1] != m_osDimSeparator[0])
880 : {
881 0 : return CPLStringList();
882 : }
883 : }
884 : return CPLStringList(
885 2 : CSLTokenizeString2(pszFilename + (m_bV2ChunkKeyEncoding ? 0 : 2),
886 4 : m_osDimSeparator.c_str(), 0));
887 : }
888 :
889 : /************************************************************************/
890 : /* ParseDtypeV3() */
891 : /************************************************************************/
892 :
893 170 : static GDALExtendedDataType ParseDtypeV3(const CPLJSONObject &obj,
894 : std::vector<DtypeElt> &elts)
895 : {
896 : do
897 : {
898 170 : if (obj.GetType() == CPLJSONObject::Type::String)
899 : {
900 340 : const auto str = obj.ToString();
901 170 : DtypeElt elt;
902 170 : GDALDataType eDT = GDT_Unknown;
903 :
904 170 : if (str == "bool") // boolean
905 : {
906 0 : elt.nativeType = DtypeElt::NativeType::BOOLEAN;
907 0 : eDT = GDT_Byte;
908 : }
909 170 : else if (str == "int8")
910 : {
911 6 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
912 6 : eDT = GDT_Int8;
913 : }
914 164 : else if (str == "uint8")
915 : {
916 65 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
917 65 : eDT = GDT_Byte;
918 : }
919 99 : else if (str == "int16")
920 : {
921 10 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
922 10 : eDT = GDT_Int16;
923 : }
924 89 : else if (str == "uint16")
925 : {
926 7 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
927 7 : eDT = GDT_UInt16;
928 : }
929 82 : else if (str == "int32")
930 : {
931 7 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
932 7 : eDT = GDT_Int32;
933 : }
934 75 : else if (str == "uint32")
935 : {
936 7 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
937 7 : eDT = GDT_UInt32;
938 : }
939 68 : else if (str == "int64")
940 : {
941 7 : elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
942 7 : eDT = GDT_Int64;
943 : }
944 61 : else if (str == "uint64")
945 : {
946 6 : elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
947 6 : eDT = GDT_UInt64;
948 : }
949 55 : else if (str == "float16")
950 : {
951 0 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
952 0 : elt.nativeSize = 2;
953 0 : elt.gdalTypeIsApproxOfNative = true;
954 0 : eDT = GDT_Float32;
955 : }
956 55 : else if (str == "float32")
957 : {
958 9 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
959 9 : eDT = GDT_Float32;
960 : }
961 46 : else if (str == "float64")
962 : {
963 15 : elt.nativeType = DtypeElt::NativeType::IEEEFP;
964 15 : eDT = GDT_Float64;
965 : }
966 31 : else if (str == "complex64")
967 : {
968 15 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
969 15 : eDT = GDT_CFloat32;
970 : }
971 16 : else if (str == "complex128")
972 : {
973 15 : elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
974 15 : eDT = GDT_CFloat64;
975 : }
976 : else
977 1 : break;
978 :
979 169 : elt.gdalType = GDALExtendedDataType::Create(eDT);
980 169 : elt.gdalSize = elt.gdalType.GetSize();
981 169 : if (!elt.gdalTypeIsApproxOfNative)
982 169 : elt.nativeSize = elt.gdalSize;
983 :
984 169 : if (elt.nativeSize > 1)
985 : {
986 98 : elt.needByteSwapping = (CPL_IS_LSB == 0);
987 : }
988 :
989 169 : elts.emplace_back(elt);
990 169 : return GDALExtendedDataType::Create(eDT);
991 : }
992 : } while (false);
993 1 : CPLError(CE_Failure, CPLE_AppDefined,
994 : "Invalid or unsupported format for data_type: %s",
995 2 : obj.ToString().c_str());
996 1 : return GDALExtendedDataType::Create(GDT_Unknown);
997 : }
998 :
999 : /************************************************************************/
1000 : /* ParseNoDataStringAsDouble() */
1001 : /************************************************************************/
1002 :
1003 37 : static double ParseNoDataStringAsDouble(const std::string &osVal, bool &bOK)
1004 : {
1005 37 : double dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
1006 37 : if (osVal == "NaN")
1007 : {
1008 : // initialized above
1009 : }
1010 15 : else if (osVal == "Infinity" || osVal == "+Infinity")
1011 : {
1012 5 : dfNoDataValue = std::numeric_limits<double>::infinity();
1013 : }
1014 10 : else if (osVal == "-Infinity")
1015 : {
1016 5 : dfNoDataValue = -std::numeric_limits<double>::infinity();
1017 : }
1018 : else
1019 : {
1020 5 : bOK = false;
1021 : }
1022 37 : return dfNoDataValue;
1023 : }
1024 :
1025 : /************************************************************************/
1026 : /* ParseNoDataComponent() */
1027 : /************************************************************************/
1028 :
1029 : template <typename T, typename Tint>
1030 40 : static T ParseNoDataComponent(const CPLJSONObject &oObj, bool &bOK)
1031 : {
1032 40 : if (oObj.GetType() == CPLJSONObject::Type::Integer ||
1033 62 : oObj.GetType() == CPLJSONObject::Type::Long ||
1034 22 : oObj.GetType() == CPLJSONObject::Type::Double)
1035 : {
1036 22 : return static_cast<T>(oObj.ToDouble());
1037 : }
1038 18 : else if (oObj.GetType() == CPLJSONObject::Type::String)
1039 : {
1040 54 : const auto osVal = oObj.ToString();
1041 18 : if (STARTS_WITH(osVal.c_str(), "0x"))
1042 : {
1043 2 : if (osVal.size() > 2 + 2 * sizeof(T))
1044 : {
1045 0 : bOK = false;
1046 0 : return 0;
1047 : }
1048 2 : Tint nVal = static_cast<Tint>(
1049 2 : std::strtoull(osVal.c_str() + 2, nullptr, 16));
1050 : T fVal;
1051 : static_assert(sizeof(nVal) == sizeof(fVal),
1052 : "sizeof(nVal) == sizeof(dfVal)");
1053 2 : memcpy(&fVal, &nVal, sizeof(nVal));
1054 2 : return fVal;
1055 : }
1056 : else
1057 : {
1058 16 : return static_cast<T>(ParseNoDataStringAsDouble(osVal, bOK));
1059 : }
1060 : }
1061 : else
1062 : {
1063 0 : bOK = false;
1064 0 : return 0;
1065 : }
1066 : }
1067 :
1068 : /************************************************************************/
1069 : /* ZarrV3Group::LoadArray() */
1070 : /************************************************************************/
1071 :
1072 : std::shared_ptr<ZarrArray>
1073 183 : ZarrV3Group::LoadArray(const std::string &osArrayName,
1074 : const std::string &osZarrayFilename,
1075 : const CPLJSONObject &oRoot) const
1076 : {
1077 : // Add osZarrayFilename to m_poSharedResource during the scope
1078 : // of this function call.
1079 183 : ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
1080 366 : osZarrayFilename);
1081 183 : if (!filenameAdder.ok())
1082 0 : return nullptr;
1083 :
1084 : // Warn about unknown members (the spec suggests to error out, but let be
1085 : // a bit more lenient)
1086 1766 : for (const auto &oNode : oRoot.GetChildren())
1087 : {
1088 3166 : const auto osName = oNode.GetName();
1089 4200 : if (osName != "zarr_format" && osName != "node_type" &&
1090 3105 : osName != "shape" && osName != "chunk_grid" &&
1091 2013 : osName != "data_type" && osName != "chunk_key_encoding" &&
1092 795 : osName != "fill_value" &&
1093 : // Below are optional
1094 627 : osName != "dimension_names" && osName != "codecs" &&
1095 3111 : osName != "storage_transformers" && osName != "attributes")
1096 : {
1097 4 : CPLError(CE_Warning, CPLE_AppDefined,
1098 : "%s array definition contains a unknown member (%s). "
1099 : "Interpretation of the array might be wrong.",
1100 : osZarrayFilename.c_str(), osName.c_str());
1101 : }
1102 : }
1103 :
1104 549 : const auto oStorageTransformers = oRoot["storage_transformers"].ToArray();
1105 183 : if (oStorageTransformers.Size() > 0)
1106 : {
1107 1 : CPLError(CE_Failure, CPLE_AppDefined,
1108 : "storage_transformers are not supported.");
1109 1 : return nullptr;
1110 : }
1111 :
1112 546 : const auto oShape = oRoot["shape"].ToArray();
1113 182 : if (!oShape.IsValid())
1114 : {
1115 2 : CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
1116 2 : return nullptr;
1117 : }
1118 :
1119 : // Parse chunk_grid
1120 540 : const auto oChunkGrid = oRoot["chunk_grid"];
1121 180 : if (oChunkGrid.GetType() != CPLJSONObject::Type::Object)
1122 : {
1123 1 : CPLError(CE_Failure, CPLE_AppDefined,
1124 : "chunk_grid missing or not an object");
1125 1 : return nullptr;
1126 : }
1127 :
1128 537 : const auto oChunkGridName = oChunkGrid["name"];
1129 179 : if (oChunkGridName.ToString() != "regular")
1130 : {
1131 1 : CPLError(CE_Failure, CPLE_AppDefined,
1132 : "Only chunk_grid.name = regular supported");
1133 1 : return nullptr;
1134 : }
1135 :
1136 534 : const auto oChunks = oChunkGrid["configuration"]["chunk_shape"].ToArray();
1137 178 : if (!oChunks.IsValid())
1138 : {
1139 1 : CPLError(
1140 : CE_Failure, CPLE_AppDefined,
1141 : "chunk_grid.configuration.chunk_shape missing or not an array");
1142 1 : return nullptr;
1143 : }
1144 :
1145 177 : if (oShape.Size() != oChunks.Size())
1146 : {
1147 1 : CPLError(CE_Failure, CPLE_AppDefined,
1148 : "shape and chunks arrays are of different size");
1149 1 : return nullptr;
1150 : }
1151 :
1152 : // Parse chunk_key_encoding
1153 528 : const auto oChunkKeyEncoding = oRoot["chunk_key_encoding"];
1154 176 : if (oChunkKeyEncoding.GetType() != CPLJSONObject::Type::Object)
1155 : {
1156 1 : CPLError(CE_Failure, CPLE_AppDefined,
1157 : "chunk_key_encoding missing or not an object");
1158 1 : return nullptr;
1159 : }
1160 :
1161 350 : std::string osDimSeparator;
1162 175 : bool bV2ChunkKeyEncoding = false;
1163 525 : const auto oChunkKeyEncodingName = oChunkKeyEncoding["name"];
1164 175 : if (oChunkKeyEncodingName.ToString() == "default")
1165 : {
1166 171 : osDimSeparator = "/";
1167 : }
1168 4 : else if (oChunkKeyEncodingName.ToString() == "v2")
1169 : {
1170 3 : osDimSeparator = ".";
1171 3 : bV2ChunkKeyEncoding = true;
1172 : }
1173 : else
1174 : {
1175 1 : CPLError(CE_Failure, CPLE_AppDefined,
1176 : "Unsupported chunk_key_encoding.name");
1177 1 : return nullptr;
1178 : }
1179 :
1180 : {
1181 348 : auto oConfiguration = oChunkKeyEncoding["configuration"];
1182 174 : if (oConfiguration.GetType() == CPLJSONObject::Type::Object)
1183 : {
1184 256 : auto oSeparator = oConfiguration["separator"];
1185 128 : if (oSeparator.IsValid())
1186 : {
1187 128 : osDimSeparator = oSeparator.ToString();
1188 128 : if (osDimSeparator != "/" && osDimSeparator != ".")
1189 : {
1190 1 : CPLError(CE_Failure, CPLE_AppDefined,
1191 : "Separator can only be '/' or '.'");
1192 1 : return nullptr;
1193 : }
1194 : }
1195 : }
1196 : }
1197 :
1198 519 : CPLJSONObject oAttributes = oRoot["attributes"];
1199 :
1200 : // Deep-clone of oAttributes
1201 173 : if (oAttributes.IsValid())
1202 : {
1203 123 : oAttributes = oAttributes.Clone();
1204 : }
1205 :
1206 346 : std::vector<std::shared_ptr<GDALDimension>> aoDims;
1207 395 : for (int i = 0; i < oShape.Size(); ++i)
1208 : {
1209 222 : const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
1210 222 : if (nSize == 0)
1211 : {
1212 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
1213 0 : return nullptr;
1214 : }
1215 222 : aoDims.emplace_back(std::make_shared<ZarrDimension>(
1216 222 : m_poSharedResource,
1217 444 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1218 444 : std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
1219 222 : nSize));
1220 : }
1221 :
1222 : // Deal with dimension_names
1223 519 : const auto dimensionNames = oRoot["dimension_names"];
1224 :
1225 166 : const auto FindDimension = [this, &aoDims, &osArrayName, &oAttributes](
1226 : const std::string &osDimName,
1227 2206 : std::shared_ptr<GDALDimension> &poDim, int i)
1228 : {
1229 166 : auto oIter = m_oMapDimensions.find(osDimName);
1230 166 : if (oIter != m_oMapDimensions.end())
1231 : {
1232 2 : if (m_bDimSizeInUpdate ||
1233 1 : oIter->second->GetSize() == poDim->GetSize())
1234 : {
1235 1 : poDim = oIter->second;
1236 1 : return true;
1237 : }
1238 : else
1239 : {
1240 0 : CPLError(CE_Warning, CPLE_AppDefined,
1241 : "Size of _ARRAY_DIMENSIONS[%d] different "
1242 : "from the one of shape",
1243 : i);
1244 0 : return false;
1245 : }
1246 : }
1247 :
1248 : // Try to load the indexing variable.
1249 : // Not in m_oMapMDArrays,
1250 : // then stat() the indexing variable.
1251 328 : else if (osArrayName != osDimName &&
1252 328 : m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
1253 : {
1254 326 : std::string osDirName = m_osDirectoryName;
1255 : while (true)
1256 : {
1257 : const std::string osArrayFilenameDim =
1258 : CPLFormFilename(CPLFormFilename(osDirName.c_str(),
1259 : osDimName.c_str(), nullptr),
1260 482 : "zarr.json", nullptr);
1261 : VSIStatBufL sStat;
1262 482 : if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
1263 : {
1264 2 : CPLJSONDocument oDoc;
1265 1 : if (oDoc.Load(osArrayFilenameDim))
1266 : {
1267 1 : LoadArray(osDimName, osArrayFilenameDim,
1268 2 : oDoc.GetRoot());
1269 : }
1270 : }
1271 : else
1272 : {
1273 : // Recurse to upper level for datasets such as
1274 : // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
1275 : const std::string osDirNameNew =
1276 481 : CPLGetPath(osDirName.c_str());
1277 481 : if (!osDirNameNew.empty() && osDirNameNew != osDirName)
1278 : {
1279 319 : osDirName = osDirNameNew;
1280 319 : continue;
1281 : }
1282 : }
1283 163 : break;
1284 319 : }
1285 : }
1286 :
1287 165 : oIter = m_oMapDimensions.find(osDimName);
1288 : // cppcheck-suppress knownConditionTrueFalse
1289 166 : if (oIter != m_oMapDimensions.end() &&
1290 1 : oIter->second->GetSize() == poDim->GetSize())
1291 : {
1292 1 : poDim = oIter->second;
1293 1 : return true;
1294 : }
1295 :
1296 328 : std::string osType;
1297 328 : std::string osDirection;
1298 164 : if (aoDims.size() == 1 && osArrayName == osDimName)
1299 : {
1300 2 : ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
1301 : osDirection);
1302 : }
1303 :
1304 : auto poDimLocal = std::make_shared<ZarrDimension>(
1305 164 : m_poSharedResource,
1306 328 : std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1307 328 : GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
1308 164 : poDimLocal->SetXArrayDimension();
1309 164 : m_oMapDimensions[osDimName] = poDimLocal;
1310 164 : poDim = poDimLocal;
1311 164 : return true;
1312 173 : };
1313 :
1314 173 : if (dimensionNames.GetType() == CPLJSONObject::Type::Array)
1315 : {
1316 112 : const auto arrayDims = dimensionNames.ToArray();
1317 112 : if (arrayDims.Size() == oShape.Size())
1318 : {
1319 277 : for (int i = 0; i < oShape.Size(); ++i)
1320 : {
1321 166 : if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
1322 : {
1323 498 : const auto osDimName = arrayDims[i].ToString();
1324 166 : FindDimension(osDimName, aoDims[i], i);
1325 : }
1326 : }
1327 : }
1328 : else
1329 : {
1330 1 : CPLError(
1331 : CE_Failure, CPLE_AppDefined,
1332 : "Size of dimension_names[] different from the one of shape");
1333 1 : return nullptr;
1334 : }
1335 : }
1336 61 : else if (dimensionNames.IsValid())
1337 : {
1338 1 : CPLError(CE_Failure, CPLE_AppDefined,
1339 : "dimension_names should be an array");
1340 1 : return nullptr;
1341 : }
1342 :
1343 513 : auto oDtype = oRoot["data_type"];
1344 171 : if (!oDtype.IsValid())
1345 : {
1346 1 : CPLError(CE_Failure, CPLE_NotSupported, "data_type missing");
1347 1 : return nullptr;
1348 : }
1349 170 : if (oDtype["fallback"].IsValid())
1350 1 : oDtype = oDtype["fallback"];
1351 340 : std::vector<DtypeElt> aoDtypeElts;
1352 340 : const auto oType = ParseDtypeV3(oDtype, aoDtypeElts);
1353 340 : if (oType.GetClass() == GEDTC_NUMERIC &&
1354 170 : oType.GetNumericDataType() == GDT_Unknown)
1355 1 : return nullptr;
1356 :
1357 338 : std::vector<GUInt64> anBlockSize;
1358 169 : if (!ZarrArray::ParseChunkSize(oChunks, oType, anBlockSize))
1359 1 : return nullptr;
1360 :
1361 336 : std::vector<GByte> abyNoData;
1362 :
1363 504 : auto oFillValue = oRoot["fill_value"];
1364 168 : auto eFillValueType = oFillValue.GetType();
1365 :
1366 168 : if (!oFillValue.IsValid())
1367 : {
1368 0 : CPLError(CE_Warning, CPLE_AppDefined, "Missing fill_value is invalid");
1369 : }
1370 168 : else if (eFillValueType == CPLJSONObject::Type::Null)
1371 : {
1372 97 : CPLError(CE_Warning, CPLE_AppDefined, "fill_value = null is invalid");
1373 : }
1374 71 : else if (GDALDataTypeIsComplex(oType.GetNumericDataType()) &&
1375 : eFillValueType != CPLJSONObject::Type::Array)
1376 : {
1377 4 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1378 4 : return nullptr;
1379 : }
1380 67 : else if (eFillValueType == CPLJSONObject::Type::String)
1381 : {
1382 54 : const auto osFillValue = oFillValue.ToString();
1383 27 : if (STARTS_WITH(osFillValue.c_str(), "0x"))
1384 : {
1385 3 : if (osFillValue.size() > 2 + 2 * oType.GetSize())
1386 : {
1387 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1388 1 : return nullptr;
1389 : }
1390 : uint64_t nVal = static_cast<uint64_t>(
1391 3 : std::strtoull(osFillValue.c_str() + 2, nullptr, 16));
1392 3 : if (oType.GetSize() == 4)
1393 : {
1394 1 : abyNoData.resize(oType.GetSize());
1395 1 : uint32_t nTmp = static_cast<uint32_t>(nVal);
1396 1 : memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
1397 : }
1398 2 : else if (oType.GetSize() == 8)
1399 : {
1400 1 : abyNoData.resize(oType.GetSize());
1401 1 : memcpy(&abyNoData[0], &nVal, sizeof(nVal));
1402 : }
1403 : else
1404 : {
1405 1 : CPLError(CE_Failure, CPLE_AppDefined,
1406 : "Hexadecimal representation of fill_value no "
1407 : "supported for this data type");
1408 1 : return nullptr;
1409 : }
1410 : }
1411 24 : else if (STARTS_WITH(osFillValue.c_str(), "0b"))
1412 : {
1413 3 : if (osFillValue.size() > 2 + 8 * oType.GetSize())
1414 : {
1415 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1416 1 : return nullptr;
1417 : }
1418 : uint64_t nVal = static_cast<uint64_t>(
1419 3 : std::strtoull(osFillValue.c_str() + 2, nullptr, 2));
1420 3 : if (oType.GetSize() == 4)
1421 : {
1422 1 : abyNoData.resize(oType.GetSize());
1423 1 : uint32_t nTmp = static_cast<uint32_t>(nVal);
1424 1 : memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
1425 : }
1426 2 : else if (oType.GetSize() == 8)
1427 : {
1428 1 : abyNoData.resize(oType.GetSize());
1429 1 : memcpy(&abyNoData[0], &nVal, sizeof(nVal));
1430 : }
1431 : else
1432 : {
1433 1 : CPLError(CE_Failure, CPLE_AppDefined,
1434 : "Binary representation of fill_value no supported for "
1435 : "this data type");
1436 1 : return nullptr;
1437 : }
1438 : }
1439 : else
1440 : {
1441 21 : bool bOK = true;
1442 21 : double dfNoDataValue = ParseNoDataStringAsDouble(osFillValue, bOK);
1443 21 : if (!bOK)
1444 : {
1445 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1446 2 : return nullptr;
1447 : }
1448 20 : else if (oType.GetNumericDataType() == GDT_Float32)
1449 : {
1450 7 : const float fNoDataValue = static_cast<float>(dfNoDataValue);
1451 7 : abyNoData.resize(sizeof(fNoDataValue));
1452 7 : memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
1453 : }
1454 13 : else if (oType.GetNumericDataType() == GDT_Float64)
1455 : {
1456 12 : abyNoData.resize(sizeof(dfNoDataValue));
1457 12 : memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
1458 : }
1459 : else
1460 : {
1461 1 : CPLError(CE_Failure, CPLE_AppDefined,
1462 : "Invalid fill_value for this data type");
1463 1 : return nullptr;
1464 : }
1465 : }
1466 : }
1467 40 : else if (eFillValueType == CPLJSONObject::Type::Boolean ||
1468 25 : eFillValueType == CPLJSONObject::Type::Integer ||
1469 25 : eFillValueType == CPLJSONObject::Type::Long ||
1470 : eFillValueType == CPLJSONObject::Type::Double)
1471 : {
1472 16 : const double dfNoDataValue = oFillValue.ToDouble();
1473 16 : if (oType.GetNumericDataType() == GDT_Int64)
1474 : {
1475 : const int64_t nNoDataValue =
1476 1 : static_cast<int64_t>(oFillValue.ToLong());
1477 1 : abyNoData.resize(oType.GetSize());
1478 1 : GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1479 : oType.GetNumericDataType(), 0, 1);
1480 : }
1481 15 : else if (oType.GetNumericDataType() == GDT_UInt64 &&
1482 : /* we can't really deal with nodata value between */
1483 : /* int64::max and uint64::max due to json-c limitations */
1484 0 : dfNoDataValue >= 0)
1485 : {
1486 : const int64_t nNoDataValue =
1487 0 : static_cast<int64_t>(oFillValue.ToLong());
1488 0 : abyNoData.resize(oType.GetSize());
1489 0 : GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1490 : oType.GetNumericDataType(), 0, 1);
1491 : }
1492 : else
1493 : {
1494 15 : abyNoData.resize(oType.GetSize());
1495 15 : GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
1496 : oType.GetNumericDataType(), 0, 1);
1497 16 : }
1498 : }
1499 24 : else if (eFillValueType == CPLJSONObject::Type::Array)
1500 : {
1501 24 : const auto oFillValueArray = oFillValue.ToArray();
1502 44 : if (oFillValueArray.Size() == 2 &&
1503 20 : GDALDataTypeIsComplex(oType.GetNumericDataType()))
1504 : {
1505 20 : if (oType.GetNumericDataType() == GDT_CFloat64)
1506 : {
1507 10 : bool bOK = true;
1508 : const double adfNoDataValue[2] = {
1509 10 : ParseNoDataComponent<double, uint64_t>(oFillValueArray[0],
1510 : bOK),
1511 10 : ParseNoDataComponent<double, uint64_t>(oFillValueArray[1],
1512 : bOK),
1513 20 : };
1514 10 : if (!bOK)
1515 : {
1516 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1517 2 : return nullptr;
1518 : }
1519 8 : abyNoData.resize(oType.GetSize());
1520 8 : CPLAssert(sizeof(adfNoDataValue) == oType.GetSize());
1521 8 : memcpy(abyNoData.data(), adfNoDataValue,
1522 : sizeof(adfNoDataValue));
1523 : }
1524 : else
1525 : {
1526 10 : CPLAssert(oType.GetNumericDataType() == GDT_CFloat32);
1527 10 : bool bOK = true;
1528 : const float afNoDataValue[2] = {
1529 10 : ParseNoDataComponent<float, uint32_t>(oFillValueArray[0],
1530 : bOK),
1531 10 : ParseNoDataComponent<float, uint32_t>(oFillValueArray[1],
1532 : bOK),
1533 20 : };
1534 10 : if (!bOK)
1535 : {
1536 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1537 2 : return nullptr;
1538 : }
1539 8 : abyNoData.resize(oType.GetSize());
1540 8 : CPLAssert(sizeof(afNoDataValue) == oType.GetSize());
1541 8 : memcpy(abyNoData.data(), afNoDataValue, sizeof(afNoDataValue));
1542 : }
1543 : }
1544 : else
1545 : {
1546 4 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1547 4 : return nullptr;
1548 : }
1549 : }
1550 : else
1551 : {
1552 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1553 0 : return nullptr;
1554 : }
1555 :
1556 456 : const auto oCodecs = oRoot["codecs"].ToArray();
1557 152 : std::unique_ptr<ZarrV3CodecSequence> poCodecs;
1558 152 : if (oCodecs.Size() > 0)
1559 : {
1560 : // Byte swapping will be done by the codec chain
1561 65 : aoDtypeElts.back().needByteSwapping = false;
1562 :
1563 65 : ZarrArrayMetadata oInputArrayMetadata;
1564 151 : for (auto &nSize : anBlockSize)
1565 86 : oInputArrayMetadata.anBlockSizes.push_back(
1566 86 : static_cast<size_t>(nSize));
1567 65 : oInputArrayMetadata.oElt = aoDtypeElts.back();
1568 65 : poCodecs = std::make_unique<ZarrV3CodecSequence>(oInputArrayMetadata);
1569 65 : if (!poCodecs->InitFromJson(oCodecs))
1570 0 : return nullptr;
1571 : }
1572 :
1573 : auto poArray =
1574 152 : ZarrV3Array::Create(m_poSharedResource, GetFullName(), osArrayName,
1575 304 : aoDims, oType, aoDtypeElts, anBlockSize);
1576 152 : if (!poArray)
1577 1 : return nullptr;
1578 151 : poArray->SetUpdatable(m_bUpdatable); // must be set before SetAttributes()
1579 151 : poArray->SetFilename(osZarrayFilename);
1580 151 : poArray->SetIsV2ChunkKeyEncoding(bV2ChunkKeyEncoding);
1581 151 : poArray->SetDimSeparator(osDimSeparator);
1582 151 : if (!abyNoData.empty())
1583 : {
1584 54 : poArray->RegisterNoDataValue(abyNoData.data());
1585 : }
1586 151 : poArray->ParseSpecialAttributes(m_pSelf.lock(), oAttributes);
1587 151 : poArray->SetAttributes(oAttributes);
1588 151 : poArray->SetDtype(oDtype);
1589 151 : if (poCodecs)
1590 65 : poArray->SetCodecs(std::move(poCodecs));
1591 151 : RegisterArray(poArray);
1592 :
1593 : // If this is an indexing variable, attach it to the dimension.
1594 151 : if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
1595 : {
1596 2 : auto oIter = m_oMapDimensions.find(poArray->GetName());
1597 2 : if (oIter != m_oMapDimensions.end())
1598 : {
1599 2 : oIter->second->SetIndexingVariable(poArray);
1600 : }
1601 : }
1602 :
1603 151 : if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
1604 : "CACHE_TILE_PRESENCE", "NO")))
1605 : {
1606 2 : poArray->CacheTilePresence();
1607 : }
1608 :
1609 151 : return poArray;
1610 : }
|