• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 16038479760

03 Jul 2025 12:12AM UTC coverage: 71.106% (-0.004%) from 71.11%
16038479760

Pull #12692

github

web-flow
Merge efeee3602 into b5d2a80d4
Pull Request #12692: C/C++/Python band algebra: add gdal.abs(), sqrt(), log(), log10() and pow()

80 of 87 new or added lines in 3 files covered. (91.95%)

8848 existing lines in 54 files now uncovered.

574863 of 808463 relevant lines covered (71.11%)

255001.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.52
/frmts/zarr/zarr_v3_array.cpp
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  Zarr driver
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2021, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12

13
#include "cpl_float.h"
14
#include "cpl_vsi_virtual.h"
15
#include "gdal_thread_pool.h"
16
#include "zarr.h"
17

18
#include <algorithm>
19
#include <cassert>
20
#include <cmath>
21
#include <cstdlib>
22
#include <limits>
23
#include <map>
24
#include <set>
25

26
/************************************************************************/
27
/*                       ZarrV3Array::ZarrV3Array()                     */
28
/************************************************************************/
29

30
ZarrV3Array::ZarrV3Array(
283✔
31
    const std::shared_ptr<ZarrSharedResource> &poSharedResource,
32
    const std::string &osParentName, const std::string &osName,
33
    const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
34
    const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
35
    const std::vector<GUInt64> &anBlockSize)
283✔
36
    : GDALAbstractMDArray(osParentName, osName),
37
      ZarrArray(poSharedResource, osParentName, osName, aoDims, oType,
38
                aoDtypeElts, anBlockSize)
283✔
39
{
40
}
283✔
41

42
/************************************************************************/
43
/*                         ZarrV3Array::Create()                        */
44
/************************************************************************/
45

46
std::shared_ptr<ZarrV3Array>
47
ZarrV3Array::Create(const std::shared_ptr<ZarrSharedResource> &poSharedResource,
283✔
48
                    const std::string &osParentName, const std::string &osName,
49
                    const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
50
                    const GDALExtendedDataType &oType,
51
                    const std::vector<DtypeElt> &aoDtypeElts,
52
                    const std::vector<GUInt64> &anBlockSize)
53
{
54
    auto arr = std::shared_ptr<ZarrV3Array>(
55
        new ZarrV3Array(poSharedResource, osParentName, osName, aoDims, oType,
56
                        aoDtypeElts, anBlockSize));
566✔
57
    if (arr->m_nTotalTileCount == 0)
283✔
58
        return nullptr;
1✔
59
    arr->SetSelf(arr);
282✔
60

61
    return arr;
282✔
62
}
63

64
/************************************************************************/
65
/*                             ~ZarrV3Array()                           */
66
/************************************************************************/
67

68
ZarrV3Array::~ZarrV3Array()
566✔
69
{
70
    ZarrV3Array::Flush();
283✔
71
}
566✔
72

73
/************************************************************************/
74
/*                                Flush()                               */
75
/************************************************************************/
76

77
void ZarrV3Array::Flush()
685✔
78
{
79
    if (!m_bValid)
685✔
80
        return;
3✔
81

82
    ZarrV3Array::FlushDirtyTile();
682✔
83

84
    if (!m_aoDims.empty())
682✔
85
    {
86
        for (const auto &poDim : m_aoDims)
1,410✔
87
        {
88
            const auto poZarrDim =
89
                dynamic_cast<const ZarrDimension *>(poDim.get());
867✔
90
            if (poZarrDim && poZarrDim->IsXArrayDimension())
867✔
91
            {
92
                if (poZarrDim->IsModified())
798✔
93
                    m_bDefinitionModified = true;
4✔
94
            }
95
            else
96
            {
97
                break;
69✔
98
            }
99
        }
100
    }
101

102
    CPLJSONObject oAttrs;
1,364✔
103
    if (m_oAttrGroup.IsModified() || m_bUnitModified || m_bOffsetModified ||
1,326✔
104
        m_bScaleModified || m_bSRSModified)
1,326✔
105
    {
106
        m_bNew = false;
38✔
107

108
        oAttrs = SerializeSpecialAttributes();
38✔
109

110
        m_bDefinitionModified = true;
38✔
111
    }
112

113
    if (m_bDefinitionModified)
682✔
114
    {
115
        Serialize(oAttrs);
171✔
116
        m_bDefinitionModified = false;
171✔
117
    }
118
}
119

120
/************************************************************************/
121
/*                    ZarrV3Array::Serialize()                          */
122
/************************************************************************/
123

124
void ZarrV3Array::Serialize(const CPLJSONObject &oAttrs)
171✔
125
{
126
    CPLJSONDocument oDoc;
342✔
127
    CPLJSONObject oRoot = oDoc.GetRoot();
342✔
128

129
    oRoot.Add("zarr_format", 3);
171✔
130
    oRoot.Add("node_type", "array");
171✔
131

132
    CPLJSONArray oShape;
342✔
133
    for (const auto &poDim : m_aoDims)
380✔
134
    {
135
        oShape.Add(static_cast<GInt64>(poDim->GetSize()));
209✔
136
    }
137
    oRoot.Add("shape", oShape);
171✔
138

139
    oRoot.Add("data_type", m_dtype.ToString());
171✔
140

141
    {
142
        CPLJSONObject oChunkGrid;
342✔
143
        oRoot.Add("chunk_grid", oChunkGrid);
171✔
144
        oChunkGrid.Add("name", "regular");
171✔
145
        CPLJSONObject oConfiguration;
342✔
146
        oChunkGrid.Add("configuration", oConfiguration);
171✔
147
        CPLJSONArray oChunks;
171✔
148
        for (const auto nBlockSize : m_anBlockSize)
380✔
149
        {
150
            oChunks.Add(static_cast<GInt64>(nBlockSize));
209✔
151
        }
152
        oConfiguration.Add("chunk_shape", oChunks);
171✔
153
    }
154

155
    {
156
        CPLJSONObject oChunkKeyEncoding;
342✔
157
        oRoot.Add("chunk_key_encoding", oChunkKeyEncoding);
171✔
158
        oChunkKeyEncoding.Add("name", m_bV2ChunkKeyEncoding ? "v2" : "default");
171✔
159
        CPLJSONObject oConfiguration;
171✔
160
        oChunkKeyEncoding.Add("configuration", oConfiguration);
171✔
161
        oConfiguration.Add("separator", m_osDimSeparator);
171✔
162
    }
163

164
    if (m_pabyNoData == nullptr)
171✔
165
    {
166
        if (m_oType.GetNumericDataType() == GDT_Float16 ||
293✔
167
            m_oType.GetNumericDataType() == GDT_Float32 ||
293✔
168
            m_oType.GetNumericDataType() == GDT_Float64)
139✔
169
        {
170
            oRoot.Add("fill_value", "NaN");
19✔
171
        }
172
        else
173
        {
174
            oRoot.AddNull("fill_value");
128✔
175
        }
176
    }
177
    else
178
    {
179
        if (m_oType.GetNumericDataType() == GDT_CFloat16 ||
48✔
180
            m_oType.GetNumericDataType() == GDT_CFloat32 ||
48✔
181
            m_oType.GetNumericDataType() == GDT_CFloat64)
16✔
182
        {
183
            double adfNoDataValue[2];
184
            GDALCopyWords(m_pabyNoData, m_oType.GetNumericDataType(), 0,
16✔
185
                          adfNoDataValue, GDT_CFloat64, 0, 1);
186
            CPLJSONArray oArray;
16✔
187
            for (int i = 0; i < 2; ++i)
48✔
188
            {
189
                if (std::isnan(adfNoDataValue[i]))
32✔
190
                    oArray.Add("NaN");
6✔
191
                else if (adfNoDataValue[i] ==
26✔
192
                         std::numeric_limits<double>::infinity())
26✔
193
                    oArray.Add("Infinity");
4✔
194
                else if (adfNoDataValue[i] ==
44✔
195
                         -std::numeric_limits<double>::infinity())
22✔
196
                    oArray.Add("-Infinity");
4✔
197
                else
198
                    oArray.Add(adfNoDataValue[i]);
18✔
199
            }
200
            oRoot.Add("fill_value", oArray);
16✔
201
        }
202
        else
203
        {
204
            SerializeNumericNoData(oRoot);
8✔
205
        }
206
    }
207

208
    if (m_poCodecs)
171✔
209
    {
210
        oRoot.Add("codecs", m_poCodecs->GetJSon());
155✔
211
    }
212

213
    oRoot.Add("attributes", oAttrs);
171✔
214

215
    // Set dimension_names
216
    if (!m_aoDims.empty())
171✔
217
    {
218
        CPLJSONArray oDimensions;
294✔
219
        for (const auto &poDim : m_aoDims)
340✔
220
        {
221
            const auto poZarrDim =
222
                dynamic_cast<const ZarrDimension *>(poDim.get());
209✔
223
            if (poZarrDim && poZarrDim->IsXArrayDimension())
209✔
224
            {
225
                oDimensions.Add(poDim->GetName());
193✔
226
            }
227
            else
228
            {
229
                oDimensions = CPLJSONArray();
16✔
230
                break;
16✔
231
            }
232
        }
233
        if (oDimensions.Size() > 0)
147✔
234
        {
235
            oRoot.Add("dimension_names", oDimensions);
131✔
236
        }
237
    }
238

239
    // TODO: codecs
240

241
    oDoc.Save(m_osFilename);
171✔
242
}
171✔
243

244
/************************************************************************/
245
/*                  ZarrV3Array::NeedDecodedBuffer()                    */
246
/************************************************************************/
247

248
bool ZarrV3Array::NeedDecodedBuffer() const
10,984✔
249
{
250
    for (const auto &elt : m_aoDtypeElts)
21,964✔
251
    {
252
        if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative)
10,978✔
253
        {
UNCOV
254
            return true;
×
255
        }
256
    }
257
    return false;
10,981✔
258
}
259

260
/************************************************************************/
261
/*               ZarrV3Array::AllocateWorkingBuffers()                  */
262
/************************************************************************/
263

264
bool ZarrV3Array::AllocateWorkingBuffers() const
162✔
265
{
266
    if (m_bAllocateWorkingBuffersDone)
162✔
267
        return m_bWorkingBuffersOK;
5✔
268

269
    m_bAllocateWorkingBuffersDone = true;
157✔
270

271
    size_t nSizeNeeded = m_nTileSize;
157✔
272
    if (NeedDecodedBuffer())
157✔
273
    {
274
        size_t nDecodedBufferSize = m_oType.GetSize();
×
275
        for (const auto &nBlockSize : m_anBlockSize)
×
276
        {
277
            if (nDecodedBufferSize > std::numeric_limits<size_t>::max() /
×
278
                                         static_cast<size_t>(nBlockSize))
×
279
            {
280
                CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
×
281
                return false;
×
282
            }
283
            nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
×
284
        }
285
        if (nSizeNeeded >
×
286
            std::numeric_limits<size_t>::max() - nDecodedBufferSize)
×
287
        {
288
            CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
×
289
            return false;
×
290
        }
291
        nSizeNeeded += nDecodedBufferSize;
×
292
    }
293

294
    // Reserve a buffer for tile content
295
    if (nSizeNeeded > 1024 * 1024 * 1024 &&
157✔
296
        !CPLTestBool(CPLGetConfigOption("ZARR_ALLOW_BIG_TILE_SIZE", "NO")))
×
297
    {
298
        CPLError(CE_Failure, CPLE_AppDefined,
×
299
                 "Zarr tile allocation would require " CPL_FRMT_GUIB " bytes. "
300
                 "By default the driver limits to 1 GB. To allow that memory "
301
                 "allocation, set the ZARR_ALLOW_BIG_TILE_SIZE configuration "
302
                 "option to YES.",
303
                 static_cast<GUIntBig>(nSizeNeeded));
304
        return false;
×
305
    }
306

307
    m_bWorkingBuffersOK =
157✔
308
        AllocateWorkingBuffers(m_abyRawTileData, m_abyDecodedTileData);
157✔
309
    return m_bWorkingBuffersOK;
157✔
310
}
311

312
bool ZarrV3Array::AllocateWorkingBuffers(
10,827✔
313
    ZarrByteVectorQuickResize &abyRawTileData,
314
    ZarrByteVectorQuickResize &abyDecodedTileData) const
315
{
316
    // This method should NOT modify any ZarrArray member, as it is going to
317
    // be called concurrently from several threads.
318

319
    // Set those #define to avoid accidental use of some global variables
320
#define m_abyRawTileData cannot_use_here
321
#define m_abyDecodedTileData cannot_use_here
322

323
    try
324
    {
325
        abyRawTileData.resize(m_nTileSize);
10,827✔
326
    }
327
    catch (const std::bad_alloc &e)
×
328
    {
329
        CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
×
330
        return false;
×
331
    }
332

333
    if (NeedDecodedBuffer())
10,829✔
334
    {
335
        size_t nDecodedBufferSize = m_oType.GetSize();
×
336
        for (const auto &nBlockSize : m_anBlockSize)
×
337
        {
338
            nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
×
339
        }
340
        try
341
        {
342
            abyDecodedTileData.resize(nDecodedBufferSize);
×
343
        }
344
        catch (const std::bad_alloc &e)
×
345
        {
346
            CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
×
347
            return false;
×
348
        }
349
    }
350

351
    return true;
10,827✔
352
#undef m_abyRawTileData
353
#undef m_abyDecodedTileData
354
}
355

356
/************************************************************************/
357
/*                      ZarrV3Array::LoadTileData()                     */
358
/************************************************************************/
359

360
bool ZarrV3Array::LoadTileData(const uint64_t *tileIndices,
423✔
361
                               bool &bMissingTileOut) const
362
{
363
    return LoadTileData(tileIndices,
423✔
364
                        false,  // use mutex
365
                        m_poCodecs.get(), m_abyRawTileData,
423✔
366
                        m_abyDecodedTileData, bMissingTileOut);
846✔
367
}
368

369
bool ZarrV3Array::LoadTileData(const uint64_t *tileIndices, bool bUseMutex,
11,093✔
370
                               ZarrV3CodecSequence *poCodecs,
371
                               ZarrByteVectorQuickResize &abyRawTileData,
372
                               ZarrByteVectorQuickResize &abyDecodedTileData,
373
                               bool &bMissingTileOut) const
374
{
375
    // This method should NOT modify any ZarrArray member, as it is going to
376
    // be called concurrently from several threads.
377

378
    // Set those #define to avoid accidental use of some global variables
379
#define m_abyRawTileData cannot_use_here
380
#define m_abyDecodedTileData cannot_use_here
381
#define m_poCodecs cannot_use_here
382

383
    bMissingTileOut = false;
11,093✔
384

385
    std::string osFilename = BuildTileFilename(tileIndices);
21,849✔
386

387
    // For network file systems, get the streaming version of the filename,
388
    // as we don't need arbitrary seeking in the file
389
    osFilename = VSIFileManager::GetHandler(osFilename.c_str())
11,070✔
390
                     ->GetStreamingFilename(osFilename);
11,090✔
391

392
    // First if we have a tile presence cache, check tile presence from it
393
    if (bUseMutex)
11,083✔
394
        m_oMutex.lock();
10,649✔
395
    auto poTilePresenceArray = OpenTilePresenceCache(false);
21,936✔
396
    if (poTilePresenceArray)
11,095✔
397
    {
398
        std::vector<GUInt64> anTileIdx(m_aoDims.size());
18✔
399
        const std::vector<size_t> anCount(m_aoDims.size(), 1);
18✔
400
        const std::vector<GInt64> anArrayStep(m_aoDims.size(), 0);
18✔
401
        const std::vector<GPtrDiff_t> anBufferStride(m_aoDims.size(), 0);
18✔
402
        const auto eByteDT = GDALExtendedDataType::Create(GDT_Byte);
18✔
403
        for (size_t i = 0; i < m_aoDims.size(); ++i)
54✔
404
        {
405
            anTileIdx[i] = static_cast<GUInt64>(tileIndices[i]);
36✔
406
        }
407
        GByte byValue = 0;
18✔
408
        if (poTilePresenceArray->Read(anTileIdx.data(), anCount.data(),
18✔
409
                                      anArrayStep.data(), anBufferStride.data(),
410
                                      eByteDT, &byValue) &&
36✔
411
            byValue == 0)
18✔
412
        {
413
            if (bUseMutex)
13✔
414
                m_oMutex.unlock();
×
415
            CPLDebugOnly(ZARR_DEBUG_KEY, "Tile %s missing (=nodata)",
13✔
416
                         osFilename.c_str());
417
            bMissingTileOut = true;
13✔
418
            return true;
13✔
419
        }
420
    }
421
    if (bUseMutex)
11,082✔
422
        m_oMutex.unlock();
10,672✔
423

424
    VSILFILE *fp = nullptr;
11,082✔
425
    // This is the number of files returned in a S3 directory listing operation
426
    constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
11,082✔
427
    const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
11,082✔
428
                                           nullptr};
429
    const auto nErrorBefore = CPLGetErrorCounter();
11,082✔
430
    if ((m_osDimSeparator == "/" && !m_anBlockSize.empty() &&
22,147✔
431
         m_anBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
33,186✔
432
        (m_osDimSeparator != "/" &&
11,073✔
433
         m_nTotalTileCount > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING))
4✔
434
    {
435
        // Avoid issuing ReadDir() when a lot of files are expected
436
        CPLConfigOptionSetter optionSetter("GDAL_DISABLE_READDIR_ON_OPEN",
437
                                           "YES", true);
×
438
        fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
×
439
    }
440
    else
441
    {
442
        fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
11,055✔
443
    }
444
    if (fp == nullptr)
11,074✔
445
    {
446
        if (nErrorBefore != CPLGetErrorCounter())
299✔
447
        {
448
            return false;
×
449
        }
450
        else
451
        {
452
            // Missing files are OK and indicate nodata_value
453
            CPLDebugOnly(ZARR_DEBUG_KEY, "Tile %s missing (=nodata)",
299✔
454
                         osFilename.c_str());
455
            bMissingTileOut = true;
299✔
456
            return true;
299✔
457
        }
458
    }
459

460
    bMissingTileOut = false;
10,775✔
461

462
    CPLAssert(abyRawTileData.capacity() >= m_nTileSize);
10,775✔
463
    // should not fail
464
    abyRawTileData.resize(m_nTileSize);
10,738✔
465

466
    bool bRet = true;
10,734✔
467
    size_t nRawDataSize = abyRawTileData.size();
10,734✔
468
    if (poCodecs == nullptr)
10,727✔
469
    {
470
        nRawDataSize = VSIFReadL(&abyRawTileData[0], 1, nRawDataSize, fp);
6✔
471
    }
472
    else
473
    {
474
        VSIFSeekL(fp, 0, SEEK_END);
10,721✔
475
        const auto nSize = VSIFTellL(fp);
10,591✔
476
        VSIFSeekL(fp, 0, SEEK_SET);
10,619✔
477
        if (nSize > static_cast<vsi_l_offset>(std::numeric_limits<int>::max()))
10,614✔
478
        {
479
            CPLError(CE_Failure, CPLE_AppDefined, "Too large tile %s",
×
480
                     osFilename.c_str());
481
            bRet = false;
×
482
        }
483
        else
484
        {
485
            try
486
            {
487
                abyRawTileData.resize(static_cast<size_t>(nSize));
10,664✔
488
            }
489
            catch (const std::exception &)
×
490
            {
491
                CPLError(CE_Failure, CPLE_OutOfMemory,
×
492
                         "Cannot allocate memory for tile %s",
493
                         osFilename.c_str());
494
                bRet = false;
×
495
            }
496

497
            if (bRet && (abyRawTileData.empty() ||
21,162✔
498
                         VSIFReadL(&abyRawTileData[0], 1, abyRawTileData.size(),
10,640✔
499
                                   fp) != abyRawTileData.size()))
10,569✔
500
            {
501
                CPLError(CE_Failure, CPLE_AppDefined,
×
502
                         "Could not read tile %s correctly",
503
                         osFilename.c_str());
504
                bRet = false;
×
505
            }
506
            else
507
            {
508
                if (!poCodecs->Decode(abyRawTileData))
10,531✔
509
                {
510
                    CPLError(CE_Failure, CPLE_AppDefined,
×
511
                             "Decompression of tile %s failed",
512
                             osFilename.c_str());
513
                    bRet = false;
×
514
                }
515
            }
516
        }
517
    }
518
    VSIFCloseL(fp);
10,671✔
519
    if (!bRet)
10,667✔
520
        return false;
×
521

522
    if (nRawDataSize != abyRawTileData.size())
10,667✔
523
    {
524
        CPLError(CE_Failure, CPLE_AppDefined,
×
525
                 "Decompressed tile %s has not expected size. "
526
                 "Got %u instead of %u",
527
                 osFilename.c_str(),
528
                 static_cast<unsigned>(abyRawTileData.size()),
×
529
                 static_cast<unsigned>(nRawDataSize));
530
        return false;
×
531
    }
532

533
    if (!abyDecodedTileData.empty())
10,614✔
534
    {
535
        const size_t nSourceSize =
536
            m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
×
537
        const auto nDTSize = m_oType.GetSize();
×
538
        const size_t nValues = abyDecodedTileData.size() / nDTSize;
×
539
        CPLAssert(nValues == m_nTileSize / nSourceSize);
×
540
        const GByte *pSrc = abyRawTileData.data();
×
541
        GByte *pDst = &abyDecodedTileData[0];
×
542
        for (size_t i = 0; i < nValues;
58✔
543
             i++, pSrc += nSourceSize, pDst += nDTSize)
×
544
        {
545
            DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
×
546
        }
547
    }
548

549
    return true;
10,518✔
550

551
#undef m_abyRawTileData
552
#undef m_abyDecodedTileData
553
#undef m_poCodecs
554
}
555

556
/************************************************************************/
557
/*                      ZarrV3Array::IAdviseRead()                      */
558
/************************************************************************/
559

560
bool ZarrV3Array::IAdviseRead(const GUInt64 *arrayStartIdx, const size_t *count,
6✔
561
                              CSLConstList papszOptions) const
562
{
563
    std::vector<uint64_t> anIndicesCur;
12✔
564
    int nThreadsMax = 0;
6✔
565
    std::vector<uint64_t> anReqTilesIndices;
12✔
566
    size_t nReqTiles = 0;
6✔
567
    if (!IAdviseReadCommon(arrayStartIdx, count, papszOptions, anIndicesCur,
6✔
568
                           nThreadsMax, anReqTilesIndices, nReqTiles))
569
    {
570
        return false;
2✔
571
    }
572
    if (nThreadsMax <= 1)
4✔
573
    {
574
        return true;
×
575
    }
576

577
    const int nThreads =
578
        static_cast<int>(std::min(static_cast<size_t>(nThreadsMax), nReqTiles));
4✔
579

580
    CPLWorkerThreadPool *wtp = GDALGetGlobalThreadPool(nThreadsMax);
4✔
581
    if (wtp == nullptr)
4✔
582
        return false;
×
583

584
    struct JobStruct
585
    {
586
        JobStruct() = default;
587

588
        JobStruct(const JobStruct &) = delete;
589
        JobStruct &operator=(const JobStruct &) = delete;
590

591
        JobStruct(JobStruct &&) = default;
592
        JobStruct &operator=(JobStruct &&) = default;
593

594
        const ZarrV3Array *poArray = nullptr;
595
        bool *pbGlobalStatus = nullptr;
596
        int *pnRemainingThreads = nullptr;
597
        const std::vector<uint64_t> *panReqTilesIndices = nullptr;
598
        size_t nFirstIdx = 0;
599
        size_t nLastIdxNotIncluded = 0;
600
    };
601

602
    std::vector<JobStruct> asJobStructs;
4✔
603

604
    bool bGlobalStatus = true;
4✔
605
    int nRemainingThreads = nThreads;
4✔
606
    // Check for very highly overflow in below loop
607
    assert(static_cast<size_t>(nThreads) <
4✔
608
           std::numeric_limits<size_t>::max() / nReqTiles);
609

610
    // Setup jobs
611
    for (int i = 0; i < nThreads; i++)
20✔
612
    {
613
        JobStruct jobStruct;
16✔
614
        jobStruct.poArray = this;
16✔
615
        jobStruct.pbGlobalStatus = &bGlobalStatus;
16✔
616
        jobStruct.pnRemainingThreads = &nRemainingThreads;
16✔
617
        jobStruct.panReqTilesIndices = &anReqTilesIndices;
16✔
618
        jobStruct.nFirstIdx = static_cast<size_t>(i * nReqTiles / nThreads);
16✔
619
        jobStruct.nLastIdxNotIncluded = std::min(
16✔
620
            static_cast<size_t>((i + 1) * nReqTiles / nThreads), nReqTiles);
16✔
621
        asJobStructs.emplace_back(std::move(jobStruct));
16✔
622
    }
623

624
    const auto JobFunc = [](void *pThreadData)
16✔
625
    {
626
        const JobStruct *jobStruct =
16✔
627
            static_cast<const JobStruct *>(pThreadData);
628

629
        const auto poArray = jobStruct->poArray;
16✔
630
        const auto &aoDims = poArray->GetDimensions();
16✔
631
        const size_t l_nDims = poArray->GetDimensionCount();
16✔
632
        ZarrByteVectorQuickResize abyRawTileData;
16✔
633
        ZarrByteVectorQuickResize abyDecodedTileData;
14✔
634
        std::unique_ptr<ZarrV3CodecSequence> poCodecs;
×
635
        if (poArray->m_poCodecs)
14✔
636
        {
637
            std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
14✔
638
            poCodecs = poArray->m_poCodecs->Clone();
16✔
639
        }
640

641
        for (size_t iReq = jobStruct->nFirstIdx;
10,688✔
642
             iReq < jobStruct->nLastIdxNotIncluded; ++iReq)
10,688✔
643
        {
644
            // Check if we must early exit
645
            {
646
                std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
10,672✔
647
                if (!(*jobStruct->pbGlobalStatus))
10,672✔
648
                    return;
×
649
            }
650

651
            const uint64_t *tileIndices =
652
                jobStruct->panReqTilesIndices->data() + iReq * l_nDims;
10,672✔
653

654
            uint64_t nTileIdx = 0;
10,669✔
655
            for (size_t j = 0; j < l_nDims; ++j)
32,007✔
656
            {
657
                if (j > 0)
21,341✔
658
                    nTileIdx *= aoDims[j - 1]->GetSize();
10,672✔
659
                nTileIdx += tileIndices[j];
21,338✔
660
            }
661

662
            if (!poArray->AllocateWorkingBuffers(abyRawTileData,
10,666✔
663
                                                 abyDecodedTileData))
664
            {
665
                std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
×
666
                *jobStruct->pbGlobalStatus = false;
×
667
                break;
×
668
            }
669

670
            bool bIsEmpty = false;
10,667✔
671
            bool success = poArray->LoadTileData(tileIndices,
10,667✔
672
                                                 true,  // use mutex
673
                                                 poCodecs.get(), abyRawTileData,
674
                                                 abyDecodedTileData, bIsEmpty);
675

676
            std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
10,462✔
677
            if (!success)
10,672✔
678
            {
679
                *jobStruct->pbGlobalStatus = false;
×
680
                break;
×
681
            }
682

683
            CachedTile cachedTile;
21,344✔
684
            if (!bIsEmpty)
10,672✔
685
            {
686
                if (!abyDecodedTileData.empty())
10,670✔
687
                    std::swap(cachedTile.abyDecoded, abyDecodedTileData);
×
688
                else
689
                    std::swap(cachedTile.abyDecoded, abyRawTileData);
10,670✔
690
            }
691
            poArray->m_oMapTileIndexToCachedTile[nTileIdx] =
10,672✔
692
                std::move(cachedTile);
21,344✔
693
        }
694

695
        std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
16✔
696
        (*jobStruct->pnRemainingThreads)--;
16✔
697
    };
698

699
    // Start jobs
700
    for (int i = 0; i < nThreads; i++)
20✔
701
    {
702
        if (!wtp->SubmitJob(JobFunc, &asJobStructs[i]))
16✔
703
        {
704
            std::lock_guard<std::mutex> oLock(m_oMutex);
×
705
            bGlobalStatus = false;
×
706
            nRemainingThreads = i;
×
707
            break;
×
708
        }
709
    }
710

711
    // Wait for all jobs to be finished
712
    while (true)
713
    {
714
        {
715
            std::lock_guard<std::mutex> oLock(m_oMutex);
18✔
716
            if (nRemainingThreads == 0)
18✔
717
                break;
4✔
718
        }
719
        wtp->WaitEvent();
14✔
720
    }
14✔
721

722
    return bGlobalStatus;
4✔
723
}
724

725
/************************************************************************/
726
/*                    ZarrV3Array::FlushDirtyTile()                     */
727
/************************************************************************/
728

729
bool ZarrV3Array::FlushDirtyTile() const
11,554✔
730
{
731
    if (!m_bDirtyTile)
11,554✔
732
        return true;
808✔
733
    m_bDirtyTile = false;
10,746✔
734

735
    std::string osFilename = BuildTileFilename(m_anCachedTiledIndices.data());
21,492✔
736

737
    const size_t nSourceSize =
738
        m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
10,746✔
739
    const auto &abyTile =
740
        m_abyDecodedTileData.empty() ? m_abyRawTileData : m_abyDecodedTileData;
10,746✔
741

742
    if (IsEmptyTile(abyTile))
10,746✔
743
    {
744
        m_bCachedTiledEmpty = true;
2✔
745

746
        VSIStatBufL sStat;
747
        if (VSIStatL(osFilename.c_str(), &sStat) == 0)
2✔
748
        {
749
            CPLDebugOnly(ZARR_DEBUG_KEY,
×
750
                         "Deleting tile %s that has now empty content",
751
                         osFilename.c_str());
752
            return VSIUnlink(osFilename.c_str()) == 0;
×
753
        }
754
        return true;
2✔
755
    }
756

757
    if (!m_abyDecodedTileData.empty())
10,744✔
758
    {
759
        const size_t nDTSize = m_oType.GetSize();
×
760
        const size_t nValues = m_abyDecodedTileData.size() / nDTSize;
×
761
        GByte *pDst = &m_abyRawTileData[0];
×
762
        const GByte *pSrc = m_abyDecodedTileData.data();
×
763
        for (size_t i = 0; i < nValues;
×
764
             i++, pDst += nSourceSize, pSrc += nDTSize)
×
765
        {
766
            EncodeElt(m_aoDtypeElts, pSrc, pDst);
×
767
        }
768
    }
769

770
    const size_t nSizeBefore = m_abyRawTileData.size();
10,744✔
771
    if (m_poCodecs)
10,744✔
772
    {
773
        if (!m_poCodecs->Encode(m_abyRawTileData))
10,744✔
774
        {
775
            m_abyRawTileData.resize(nSizeBefore);
×
776
            return false;
×
777
        }
778
    }
779

780
    if (m_osDimSeparator == "/")
10,744✔
781
    {
782
        std::string osDir = CPLGetDirnameSafe(osFilename.c_str());
10,744✔
783
        VSIStatBufL sStat;
784
        if (VSIStatL(osDir.c_str(), &sStat) != 0)
10,744✔
785
        {
786
            if (VSIMkdirRecursive(osDir.c_str(), 0755) != 0)
206✔
787
            {
788
                CPLError(CE_Failure, CPLE_AppDefined,
×
789
                         "Cannot create directory %s", osDir.c_str());
790
                m_abyRawTileData.resize(nSizeBefore);
×
791
                return false;
×
792
            }
793
        }
794
    }
795

796
    VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
10,744✔
797
    if (fp == nullptr)
10,744✔
798
    {
799
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot create tile %s",
×
800
                 osFilename.c_str());
801
        m_abyRawTileData.resize(nSizeBefore);
×
802
        return false;
×
803
    }
804

805
    bool bRet = true;
10,744✔
806
    const size_t nRawDataSize = m_abyRawTileData.size();
10,744✔
807
    if (VSIFWriteL(m_abyRawTileData.data(), 1, nRawDataSize, fp) !=
10,744✔
808
        nRawDataSize)
809
    {
810
        CPLError(CE_Failure, CPLE_AppDefined,
×
811
                 "Could not write tile %s correctly", osFilename.c_str());
812
        bRet = false;
×
813
    }
814
    VSIFCloseL(fp);
10,744✔
815

816
    m_abyRawTileData.resize(nSizeBefore);
10,744✔
817

818
    return bRet;
10,744✔
819
}
820

821
/************************************************************************/
822
/*                          BuildTileFilename()                         */
823
/************************************************************************/
824

825
std::string ZarrV3Array::BuildTileFilename(const uint64_t *tileIndices) const
21,836✔
826
{
827
    if (m_aoDims.empty())
21,836✔
828
    {
829
        return CPLFormFilenameSafe(
830
            CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
×
831
            m_bV2ChunkKeyEncoding ? "0" : "c", nullptr);
×
832
    }
833
    else
834
    {
835
        std::string osFilename(CPLGetDirnameSafe(m_osFilename.c_str()));
43,667✔
836
        osFilename += '/';
21,835✔
837
        if (!m_bV2ChunkKeyEncoding)
21,825✔
838
        {
839
            osFilename += 'c';
21,819✔
840
        }
841
        for (size_t i = 0; i < m_aoDims.size(); ++i)
65,411✔
842
        {
843
            if (i > 0 || !m_bV2ChunkKeyEncoding)
43,560✔
844
                osFilename += m_osDimSeparator;
43,561✔
845
            osFilename += std::to_string(tileIndices[i]);
43,548✔
846
        }
847
        return osFilename;
21,849✔
848
    }
849
}
850

851
/************************************************************************/
852
/*                          GetDataDirectory()                          */
853
/************************************************************************/
854

855
std::string ZarrV3Array::GetDataDirectory() const
2✔
856
{
857
    return std::string(CPLGetDirnameSafe(m_osFilename.c_str()));
2✔
858
}
859

860
/************************************************************************/
861
/*                        GetTileIndicesFromFilename()                  */
862
/************************************************************************/
863

864
CPLStringList
865
ZarrV3Array::GetTileIndicesFromFilename(const char *pszFilename) const
4✔
866
{
867
    if (!m_bV2ChunkKeyEncoding)
4✔
868
    {
869
        if (pszFilename[0] != 'c')
4✔
870
            return CPLStringList();
2✔
871
        if (m_osDimSeparator == "/")
2✔
872
        {
873
            if (pszFilename[1] != '/' && pszFilename[1] != '\\')
2✔
874
                return CPLStringList();
×
875
        }
876
        else if (pszFilename[1] != m_osDimSeparator[0])
×
877
        {
878
            return CPLStringList();
×
879
        }
880
    }
881
    return CPLStringList(
882
        CSLTokenizeString2(pszFilename + (m_bV2ChunkKeyEncoding ? 0 : 2),
2✔
883
                           m_osDimSeparator.c_str(), 0));
4✔
884
}
885

886
/************************************************************************/
887
/*                           ParseDtypeV3()                             */
888
/************************************************************************/
889

890
static GDALExtendedDataType ParseDtypeV3(const CPLJSONObject &obj,
180✔
891
                                         std::vector<DtypeElt> &elts)
892
{
893
    do
894
    {
895
        if (obj.GetType() == CPLJSONObject::Type::String)
180✔
896
        {
897
            const auto str = obj.ToString();
360✔
898
            DtypeElt elt;
180✔
899
            GDALDataType eDT = GDT_Unknown;
180✔
900

901
            if (str == "bool")  // boolean
180✔
902
            {
903
                elt.nativeType = DtypeElt::NativeType::BOOLEAN;
×
904
                eDT = GDT_Byte;
×
905
            }
906
            else if (str == "int8")
180✔
907
            {
908
                elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
6✔
909
                eDT = GDT_Int8;
6✔
910
            }
911
            else if (str == "uint8")
174✔
912
            {
913
                elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
74✔
914
                eDT = GDT_Byte;
74✔
915
            }
916
            else if (str == "int16")
100✔
917
            {
918
                elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
10✔
919
                eDT = GDT_Int16;
10✔
920
            }
921
            else if (str == "uint16")
90✔
922
            {
923
                elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
7✔
924
                eDT = GDT_UInt16;
7✔
925
            }
926
            else if (str == "int32")
83✔
927
            {
928
                elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
7✔
929
                eDT = GDT_Int32;
7✔
930
            }
931
            else if (str == "uint32")
76✔
932
            {
933
                elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
7✔
934
                eDT = GDT_UInt32;
7✔
935
            }
936
            else if (str == "int64")
69✔
937
            {
938
                elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
7✔
939
                eDT = GDT_Int64;
7✔
940
            }
941
            else if (str == "uint64")
62✔
942
            {
943
                elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
6✔
944
                eDT = GDT_UInt64;
6✔
945
            }
946
            else if (str == "float16")
56✔
947
            {
948
                // elt.nativeType = DtypeElt::NativeType::IEEEFP;
949
                // elt.nativeSize = 2;
950
                // elt.gdalTypeIsApproxOfNative = true;
951
                // eDT = GDT_Float32;
952
                elt.nativeType = DtypeElt::NativeType::IEEEFP;
1✔
953
                elt.nativeSize = 2;
1✔
954
                eDT = GDT_Float16;
1✔
955
            }
956
            else if (str == "float32")
55✔
957
            {
958
                elt.nativeType = DtypeElt::NativeType::IEEEFP;
9✔
959
                eDT = GDT_Float32;
9✔
960
            }
961
            else if (str == "float64")
46✔
962
            {
963
                elt.nativeType = DtypeElt::NativeType::IEEEFP;
15✔
964
                eDT = GDT_Float64;
15✔
965
            }
966
            else if (str == "complex64")
31✔
967
            {
968
                elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
15✔
969
                eDT = GDT_CFloat32;
15✔
970
            }
971
            else if (str == "complex128")
16✔
972
            {
973
                elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
15✔
974
                eDT = GDT_CFloat64;
15✔
975
            }
976
            else
977
                break;
1✔
978

979
            elt.gdalType = GDALExtendedDataType::Create(eDT);
179✔
980
            elt.gdalSize = elt.gdalType.GetSize();
179✔
981
            if (!elt.gdalTypeIsApproxOfNative)
179✔
982
                elt.nativeSize = elt.gdalSize;
179✔
983

984
            if (elt.nativeSize > 1)
179✔
985
            {
986
                elt.needByteSwapping = (CPL_IS_LSB == 0);
99✔
987
            }
988

989
            elts.emplace_back(elt);
179✔
990
            return GDALExtendedDataType::Create(eDT);
179✔
991
        }
992
    } while (false);
993
    CPLError(CE_Failure, CPLE_AppDefined,
1✔
994
             "Invalid or unsupported format for data_type: %s",
995
             obj.ToString().c_str());
2✔
996
    return GDALExtendedDataType::Create(GDT_Unknown);
1✔
997
}
998

999
/************************************************************************/
1000
/*                    ParseNoDataStringAsDouble()                       */
1001
/************************************************************************/
1002

1003
static double ParseNoDataStringAsDouble(const std::string &osVal, bool &bOK)
38✔
1004
{
1005
    double dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
38✔
1006
    if (osVal == "NaN")
38✔
1007
    {
1008
        // initialized above
1009
    }
1010
    else if (osVal == "Infinity" || osVal == "+Infinity")
15✔
1011
    {
1012
        dfNoDataValue = std::numeric_limits<double>::infinity();
5✔
1013
    }
1014
    else if (osVal == "-Infinity")
10✔
1015
    {
1016
        dfNoDataValue = -std::numeric_limits<double>::infinity();
5✔
1017
    }
1018
    else
1019
    {
1020
        bOK = false;
5✔
1021
    }
1022
    return dfNoDataValue;
38✔
1023
}
1024

1025
/************************************************************************/
1026
/*                     ParseNoDataComponent()                           */
1027
/************************************************************************/
1028

1029
template <typename T, typename Tint>
1030
static T ParseNoDataComponent(const CPLJSONObject &oObj, bool &bOK)
40✔
1031
{
1032
    if (oObj.GetType() == CPLJSONObject::Type::Integer ||
40✔
1033
        oObj.GetType() == CPLJSONObject::Type::Long ||
62✔
1034
        oObj.GetType() == CPLJSONObject::Type::Double)
22✔
1035
    {
1036
        return static_cast<T>(oObj.ToDouble());
22✔
1037
    }
1038
    else if (oObj.GetType() == CPLJSONObject::Type::String)
18✔
1039
    {
1040
        const auto osVal = oObj.ToString();
54✔
1041
        if (STARTS_WITH(osVal.c_str(), "0x"))
18✔
1042
        {
1043
            if (osVal.size() > 2 + 2 * sizeof(T))
2✔
1044
            {
1045
                bOK = false;
×
1046
                return 0;
×
1047
            }
1048
            Tint nVal = static_cast<Tint>(
2✔
1049
                std::strtoull(osVal.c_str() + 2, nullptr, 16));
2✔
1050
            T fVal;
1051
            static_assert(sizeof(nVal) == sizeof(fVal),
1052
                          "sizeof(nVal) == sizeof(dfVal)");
1053
            memcpy(&fVal, &nVal, sizeof(nVal));
2✔
1054
            return fVal;
2✔
1055
        }
1056
        else
1057
        {
1058
            return static_cast<T>(ParseNoDataStringAsDouble(osVal, bOK));
16✔
1059
        }
1060
    }
1061
    else
1062
    {
1063
        bOK = false;
×
1064
        return 0;
×
1065
    }
1066
}
1067

1068
/************************************************************************/
1069
/*                     ZarrV3Group::LoadArray()                         */
1070
/************************************************************************/
1071

1072
std::shared_ptr<ZarrArray>
1073
ZarrV3Group::LoadArray(const std::string &osArrayName,
193✔
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
    ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
193✔
1080
                                                       osZarrayFilename);
386✔
1081
    if (!filenameAdder.ok())
193✔
1082
        return nullptr;
×
1083

1084
    // Warn about unknown members (the spec suggests to error out, but let be
1085
    // a bit more lenient)
1086
    for (const auto &oNode : oRoot.GetChildren())
1,921✔
1087
    {
1088
        const auto osName = oNode.GetName();
3,456✔
1089
        if (osName != "zarr_format" && osName != "node_type" &&
4,605✔
1090
            osName != "shape" && osName != "chunk_grid" &&
3,450✔
1091
            osName != "data_type" && osName != "chunk_key_encoding" &&
2,298✔
1092
            osName != "fill_value" &&
955✔
1093
            // Below are optional
1094
            osName != "dimension_names" && osName != "codecs" &&
777✔
1095
            osName != "storage_transformers" && osName != "attributes")
3,395✔
1096
        {
1097
            CPLError(CE_Warning, CPLE_AppDefined,
4✔
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
    const auto oStorageTransformers = oRoot["storage_transformers"].ToArray();
579✔
1105
    if (oStorageTransformers.Size() > 0)
193✔
1106
    {
1107
        CPLError(CE_Failure, CPLE_AppDefined,
1✔
1108
                 "storage_transformers are not supported.");
1109
        return nullptr;
1✔
1110
    }
1111

1112
    const auto oShape = oRoot["shape"].ToArray();
576✔
1113
    if (!oShape.IsValid())
192✔
1114
    {
1115
        CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
2✔
1116
        return nullptr;
2✔
1117
    }
1118

1119
    // Parse chunk_grid
1120
    const auto oChunkGrid = oRoot["chunk_grid"];
570✔
1121
    if (oChunkGrid.GetType() != CPLJSONObject::Type::Object)
190✔
1122
    {
1123
        CPLError(CE_Failure, CPLE_AppDefined,
1✔
1124
                 "chunk_grid missing or not an object");
1125
        return nullptr;
1✔
1126
    }
1127

1128
    const auto oChunkGridName = oChunkGrid["name"];
567✔
1129
    if (oChunkGridName.ToString() != "regular")
189✔
1130
    {
1131
        CPLError(CE_Failure, CPLE_AppDefined,
1✔
1132
                 "Only chunk_grid.name = regular supported");
1133
        return nullptr;
1✔
1134
    }
1135

1136
    const auto oChunks = oChunkGrid["configuration"]["chunk_shape"].ToArray();
564✔
1137
    if (!oChunks.IsValid())
188✔
1138
    {
1139
        CPLError(
1✔
1140
            CE_Failure, CPLE_AppDefined,
1141
            "chunk_grid.configuration.chunk_shape missing or not an array");
1142
        return nullptr;
1✔
1143
    }
1144

1145
    if (oShape.Size() != oChunks.Size())
187✔
1146
    {
1147
        CPLError(CE_Failure, CPLE_AppDefined,
1✔
1148
                 "shape and chunks arrays are of different size");
1149
        return nullptr;
1✔
1150
    }
1151

1152
    // Parse chunk_key_encoding
1153
    const auto oChunkKeyEncoding = oRoot["chunk_key_encoding"];
558✔
1154
    if (oChunkKeyEncoding.GetType() != CPLJSONObject::Type::Object)
186✔
1155
    {
1156
        CPLError(CE_Failure, CPLE_AppDefined,
1✔
1157
                 "chunk_key_encoding missing or not an object");
1158
        return nullptr;
1✔
1159
    }
1160

1161
    std::string osDimSeparator;
370✔
1162
    bool bV2ChunkKeyEncoding = false;
185✔
1163
    const auto oChunkKeyEncodingName = oChunkKeyEncoding["name"];
555✔
1164
    if (oChunkKeyEncodingName.ToString() == "default")
185✔
1165
    {
1166
        osDimSeparator = "/";
178✔
1167
    }
1168
    else if (oChunkKeyEncodingName.ToString() == "v2")
7✔
1169
    {
1170
        osDimSeparator = ".";
6✔
1171
        bV2ChunkKeyEncoding = true;
6✔
1172
    }
1173
    else
1174
    {
1175
        CPLError(CE_Failure, CPLE_AppDefined,
1✔
1176
                 "Unsupported chunk_key_encoding.name");
1177
        return nullptr;
1✔
1178
    }
1179

1180
    {
1181
        auto oConfiguration = oChunkKeyEncoding["configuration"];
368✔
1182
        if (oConfiguration.GetType() == CPLJSONObject::Type::Object)
184✔
1183
        {
1184
            auto oSeparator = oConfiguration["separator"];
270✔
1185
            if (oSeparator.IsValid())
135✔
1186
            {
1187
                osDimSeparator = oSeparator.ToString();
135✔
1188
                if (osDimSeparator != "/" && osDimSeparator != ".")
135✔
1189
                {
1190
                    CPLError(CE_Failure, CPLE_AppDefined,
1✔
1191
                             "Separator can only be '/' or '.'");
1192
                    return nullptr;
1✔
1193
                }
1194
            }
1195
        }
1196
    }
1197

1198
    CPLJSONObject oAttributes = oRoot["attributes"];
549✔
1199

1200
    // Deep-clone of oAttributes
1201
    if (oAttributes.IsValid())
183✔
1202
    {
1203
        oAttributes = oAttributes.Clone();
127✔
1204
    }
1205

1206
    std::vector<std::shared_ptr<GDALDimension>> aoDims;
366✔
1207
    for (int i = 0; i < oShape.Size(); ++i)
420✔
1208
    {
1209
        const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
237✔
1210
        if (nSize == 0)
237✔
1211
        {
1212
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
×
1213
            return nullptr;
×
1214
        }
1215
        aoDims.emplace_back(std::make_shared<ZarrDimension>(
237✔
1216
            m_poSharedResource,
237✔
1217
            std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
474✔
1218
            std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
474✔
1219
            nSize));
237✔
1220
    }
1221

1222
    // Deal with dimension_names
1223
    const auto dimensionNames = oRoot["dimension_names"];
549✔
1224

1225
    const auto FindDimension = [this, &aoDims, &osArrayName, &oAttributes](
172✔
1226
                                   const std::string &osDimName,
1227
                                   std::shared_ptr<GDALDimension> &poDim, int i)
2,280✔
1228
    {
1229
        auto oIter = m_oMapDimensions.find(osDimName);
172✔
1230
        if (oIter != m_oMapDimensions.end())
172✔
1231
        {
1232
            if (m_bDimSizeInUpdate ||
4✔
1233
                oIter->second->GetSize() == poDim->GetSize())
2✔
1234
            {
1235
                poDim = oIter->second;
2✔
1236
                return true;
2✔
1237
            }
1238
            else
1239
            {
1240
                CPLError(CE_Warning, CPLE_AppDefined,
×
1241
                         "Size of _ARRAY_DIMENSIONS[%d] different "
1242
                         "from the one of shape",
1243
                         i);
1244
                return false;
×
1245
            }
1246
        }
1247

1248
        // Try to load the indexing variable.
1249
        // Not in m_oMapMDArrays,
1250
        // then stat() the indexing variable.
1251
        else if (osArrayName != osDimName &&
338✔
1252
                 m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
338✔
1253
        {
1254
            std::string osDirName = m_osDirectoryName;
336✔
1255
            while (true)
1256
            {
1257
                const std::string osArrayFilenameDim = CPLFormFilenameSafe(
1258
                    CPLFormFilenameSafe(osDirName.c_str(), osDimName.c_str(),
709✔
1259
                                        nullptr)
1260
                        .c_str(),
1261
                    "zarr.json", nullptr);
709✔
1262
                VSIStatBufL sStat;
1263
                if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
709✔
1264
                {
1265
                    CPLJSONDocument oDoc;
×
1266
                    if (oDoc.Load(osArrayFilenameDim))
×
1267
                    {
1268
                        LoadArray(osDimName, osArrayFilenameDim,
×
1269
                                  oDoc.GetRoot());
×
1270
                    }
1271
                }
1272
                else
1273
                {
1274
                    // Recurse to upper level for datasets such as
1275
                    // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
1276
                    std::string osDirNameNew =
1277
                        CPLGetPathSafe(osDirName.c_str());
709✔
1278
                    if (!osDirNameNew.empty() && osDirNameNew != osDirName)
709✔
1279
                    {
1280
                        osDirName = std::move(osDirNameNew);
541✔
1281
                        continue;
541✔
1282
                    }
1283
                }
1284
                break;
168✔
1285
            }
541✔
1286
        }
1287

1288
        oIter = m_oMapDimensions.find(osDimName);
170✔
1289
        // cppcheck-suppress knownConditionTrueFalse
1290
        if (oIter != m_oMapDimensions.end() &&
170✔
1291
            oIter->second->GetSize() == poDim->GetSize())
×
1292
        {
1293
            poDim = oIter->second;
×
1294
            return true;
×
1295
        }
1296

1297
        std::string osType;
340✔
1298
        std::string osDirection;
340✔
1299
        if (aoDims.size() == 1 && osArrayName == osDimName)
170✔
1300
        {
1301
            ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
2✔
1302
                                                 osDirection);
1303
        }
1304

1305
        auto poDimLocal = std::make_shared<ZarrDimension>(
1306
            m_poSharedResource,
170✔
1307
            std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
340✔
1308
            GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
340✔
1309
        poDimLocal->SetXArrayDimension();
170✔
1310
        m_oMapDimensions[osDimName] = poDimLocal;
170✔
1311
        poDim = poDimLocal;
170✔
1312
        return true;
170✔
1313
    };
183✔
1314

1315
    if (dimensionNames.GetType() == CPLJSONObject::Type::Array)
183✔
1316
    {
1317
        const auto arrayDims = dimensionNames.ToArray();
116✔
1318
        if (arrayDims.Size() == oShape.Size())
116✔
1319
        {
1320
            for (int i = 0; i < oShape.Size(); ++i)
287✔
1321
            {
1322
                if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
172✔
1323
                {
1324
                    const auto osDimName = arrayDims[i].ToString();
516✔
1325
                    FindDimension(osDimName, aoDims[i], i);
172✔
1326
                }
1327
            }
1328
        }
1329
        else
1330
        {
1331
            CPLError(
1✔
1332
                CE_Failure, CPLE_AppDefined,
1333
                "Size of dimension_names[] different from the one of shape");
1334
            return nullptr;
1✔
1335
        }
1336
    }
1337
    else if (dimensionNames.IsValid())
67✔
1338
    {
1339
        CPLError(CE_Failure, CPLE_AppDefined,
1✔
1340
                 "dimension_names should be an array");
1341
        return nullptr;
1✔
1342
    }
1343

1344
    auto oDtype = oRoot["data_type"];
543✔
1345
    if (!oDtype.IsValid())
181✔
1346
    {
1347
        CPLError(CE_Failure, CPLE_NotSupported, "data_type missing");
1✔
1348
        return nullptr;
1✔
1349
    }
1350
    if (oDtype["fallback"].IsValid())
180✔
1351
        oDtype = oDtype["fallback"];
1✔
1352
    std::vector<DtypeElt> aoDtypeElts;
360✔
1353
    const auto oType = ParseDtypeV3(oDtype, aoDtypeElts);
360✔
1354
    if (oType.GetClass() == GEDTC_NUMERIC &&
360✔
1355
        oType.GetNumericDataType() == GDT_Unknown)
180✔
1356
        return nullptr;
1✔
1357

1358
    std::vector<GUInt64> anBlockSize;
358✔
1359
    if (!ZarrArray::ParseChunkSize(oChunks, oType, anBlockSize))
179✔
1360
        return nullptr;
1✔
1361

1362
    std::vector<GByte> abyNoData;
356✔
1363

1364
    auto oFillValue = oRoot["fill_value"];
534✔
1365
    auto eFillValueType = oFillValue.GetType();
178✔
1366

1367
    if (!oFillValue.IsValid())
178✔
1368
    {
1369
        CPLError(CE_Warning, CPLE_AppDefined, "Missing fill_value is invalid");
×
1370
    }
1371
    else if (eFillValueType == CPLJSONObject::Type::Null)
178✔
1372
    {
1373
        CPLError(CE_Warning, CPLE_AppDefined, "fill_value = null is invalid");
99✔
1374
    }
1375
    else if (GDALDataTypeIsComplex(oType.GetNumericDataType()) &&
79✔
1376
             eFillValueType != CPLJSONObject::Type::Array)
1377
    {
1378
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
4✔
1379
        return nullptr;
4✔
1380
    }
1381
    else if (eFillValueType == CPLJSONObject::Type::String)
75✔
1382
    {
1383
        const auto osFillValue = oFillValue.ToString();
56✔
1384
        if (STARTS_WITH(osFillValue.c_str(), "0x"))
28✔
1385
        {
1386
            if (osFillValue.size() > 2 + 2 * oType.GetSize())
3✔
1387
            {
1388
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
×
1389
                return nullptr;
1✔
1390
            }
1391
            uint64_t nVal = static_cast<uint64_t>(
1392
                std::strtoull(osFillValue.c_str() + 2, nullptr, 16));
3✔
1393
            if (oType.GetSize() == 4)
3✔
1394
            {
1395
                abyNoData.resize(oType.GetSize());
1✔
1396
                uint32_t nTmp = static_cast<uint32_t>(nVal);
1✔
1397
                memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
1✔
1398
            }
1399
            else if (oType.GetSize() == 8)
2✔
1400
            {
1401
                abyNoData.resize(oType.GetSize());
1✔
1402
                memcpy(&abyNoData[0], &nVal, sizeof(nVal));
1✔
1403
            }
1404
            else
1405
            {
1406
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
1407
                         "Hexadecimal representation of fill_value no "
1408
                         "supported for this data type");
1409
                return nullptr;
1✔
1410
            }
1411
        }
1412
        else if (STARTS_WITH(osFillValue.c_str(), "0b"))
25✔
1413
        {
1414
            if (osFillValue.size() > 2 + 8 * oType.GetSize())
3✔
1415
            {
1416
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
×
1417
                return nullptr;
1✔
1418
            }
1419
            uint64_t nVal = static_cast<uint64_t>(
1420
                std::strtoull(osFillValue.c_str() + 2, nullptr, 2));
3✔
1421
            if (oType.GetSize() == 4)
3✔
1422
            {
1423
                abyNoData.resize(oType.GetSize());
1✔
1424
                uint32_t nTmp = static_cast<uint32_t>(nVal);
1✔
1425
                memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
1✔
1426
            }
1427
            else if (oType.GetSize() == 8)
2✔
1428
            {
1429
                abyNoData.resize(oType.GetSize());
1✔
1430
                memcpy(&abyNoData[0], &nVal, sizeof(nVal));
1✔
1431
            }
1432
            else
1433
            {
1434
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
1435
                         "Binary representation of fill_value no supported for "
1436
                         "this data type");
1437
                return nullptr;
1✔
1438
            }
1439
        }
1440
        else
1441
        {
1442
            bool bOK = true;
22✔
1443
            double dfNoDataValue = ParseNoDataStringAsDouble(osFillValue, bOK);
22✔
1444
            if (!bOK)
22✔
1445
            {
1446
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1✔
1447
                return nullptr;
2✔
1448
            }
1449
            else if (oType.GetNumericDataType() == GDT_Float16)
21✔
1450
            {
1451
                const GFloat16 hfNoDataValue =
1452
                    static_cast<GFloat16>(dfNoDataValue);
1✔
1453
                abyNoData.resize(sizeof(hfNoDataValue));
1✔
1454
                memcpy(&abyNoData[0], &hfNoDataValue, sizeof(hfNoDataValue));
1✔
1455
            }
1456
            else if (oType.GetNumericDataType() == GDT_Float32)
20✔
1457
            {
1458
                const float fNoDataValue = static_cast<float>(dfNoDataValue);
7✔
1459
                abyNoData.resize(sizeof(fNoDataValue));
7✔
1460
                memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
7✔
1461
            }
1462
            else if (oType.GetNumericDataType() == GDT_Float64)
13✔
1463
            {
1464
                abyNoData.resize(sizeof(dfNoDataValue));
12✔
1465
                memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
12✔
1466
            }
1467
            else
1468
            {
1469
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
1470
                         "Invalid fill_value for this data type");
1471
                return nullptr;
1✔
1472
            }
1473
        }
1474
    }
1475
    else if (eFillValueType == CPLJSONObject::Type::Boolean ||
47✔
1476
             eFillValueType == CPLJSONObject::Type::Integer ||
25✔
1477
             eFillValueType == CPLJSONObject::Type::Long ||
25✔
1478
             eFillValueType == CPLJSONObject::Type::Double)
1479
    {
1480
        const double dfNoDataValue = oFillValue.ToDouble();
23✔
1481
        if (oType.GetNumericDataType() == GDT_Int64)
23✔
1482
        {
1483
            const int64_t nNoDataValue =
1484
                static_cast<int64_t>(oFillValue.ToLong());
1✔
1485
            abyNoData.resize(oType.GetSize());
1✔
1486
            GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1✔
1487
                          oType.GetNumericDataType(), 0, 1);
1488
        }
1489
        else if (oType.GetNumericDataType() == GDT_UInt64 &&
22✔
1490
                 /* we can't really deal with nodata value between */
1491
                 /* int64::max and uint64::max due to json-c limitations */
1492
                 dfNoDataValue >= 0)
×
1493
        {
1494
            const int64_t nNoDataValue =
1495
                static_cast<int64_t>(oFillValue.ToLong());
×
1496
            abyNoData.resize(oType.GetSize());
×
1497
            GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
×
1498
                          oType.GetNumericDataType(), 0, 1);
1499
        }
1500
        else
1501
        {
1502
            abyNoData.resize(oType.GetSize());
22✔
1503
            GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
22✔
1504
                          oType.GetNumericDataType(), 0, 1);
1505
        }
23✔
1506
    }
1507
    else if (eFillValueType == CPLJSONObject::Type::Array)
24✔
1508
    {
1509
        const auto oFillValueArray = oFillValue.ToArray();
24✔
1510
        if (oFillValueArray.Size() == 2 &&
44✔
1511
            GDALDataTypeIsComplex(oType.GetNumericDataType()))
20✔
1512
        {
1513
            if (oType.GetNumericDataType() == GDT_CFloat64)
20✔
1514
            {
1515
                bool bOK = true;
10✔
1516
                const double adfNoDataValue[2] = {
1517
                    ParseNoDataComponent<double, uint64_t>(oFillValueArray[0],
10✔
1518
                                                           bOK),
1519
                    ParseNoDataComponent<double, uint64_t>(oFillValueArray[1],
10✔
1520
                                                           bOK),
1521
                };
20✔
1522
                if (!bOK)
10✔
1523
                {
1524
                    CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
2✔
1525
                    return nullptr;
2✔
1526
                }
1527
                abyNoData.resize(oType.GetSize());
8✔
1528
                CPLAssert(sizeof(adfNoDataValue) == oType.GetSize());
8✔
1529
                memcpy(abyNoData.data(), adfNoDataValue,
8✔
1530
                       sizeof(adfNoDataValue));
1531
            }
1532
            else
1533
            {
1534
                CPLAssert(oType.GetNumericDataType() == GDT_CFloat32);
10✔
1535
                bool bOK = true;
10✔
1536
                const float afNoDataValue[2] = {
1537
                    ParseNoDataComponent<float, uint32_t>(oFillValueArray[0],
10✔
1538
                                                          bOK),
1539
                    ParseNoDataComponent<float, uint32_t>(oFillValueArray[1],
10✔
1540
                                                          bOK),
1541
                };
20✔
1542
                if (!bOK)
10✔
1543
                {
1544
                    CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
2✔
1545
                    return nullptr;
2✔
1546
                }
1547
                abyNoData.resize(oType.GetSize());
8✔
1548
                CPLAssert(sizeof(afNoDataValue) == oType.GetSize());
8✔
1549
                memcpy(abyNoData.data(), afNoDataValue, sizeof(afNoDataValue));
8✔
1550
            }
1551
        }
1552
        else
1553
        {
1554
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
4✔
1555
            return nullptr;
4✔
1556
        }
1557
    }
1558
    else
1559
    {
1560
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
×
1561
        return nullptr;
×
1562
    }
1563

1564
    const auto oCodecs = oRoot["codecs"].ToArray();
486✔
1565
    std::unique_ptr<ZarrV3CodecSequence> poCodecs;
162✔
1566
    if (oCodecs.Size() > 0)
162✔
1567
    {
1568
        // Byte swapping will be done by the codec chain
1569
        aoDtypeElts.back().needByteSwapping = false;
132✔
1570

1571
        ZarrArrayMetadata oInputArrayMetadata;
132✔
1572
        for (auto &nSize : anBlockSize)
313✔
1573
            oInputArrayMetadata.anBlockSizes.push_back(
181✔
1574
                static_cast<size_t>(nSize));
181✔
1575
        oInputArrayMetadata.oElt = aoDtypeElts.back();
132✔
1576
        poCodecs = std::make_unique<ZarrV3CodecSequence>(oInputArrayMetadata);
132✔
1577
        if (!poCodecs->InitFromJson(oCodecs))
132✔
1578
            return nullptr;
×
1579
    }
1580

1581
    auto poArray =
1582
        ZarrV3Array::Create(m_poSharedResource, GetFullName(), osArrayName,
162✔
1583
                            aoDims, oType, aoDtypeElts, anBlockSize);
324✔
1584
    if (!poArray)
162✔
1585
        return nullptr;
1✔
1586
    poArray->SetUpdatable(m_bUpdatable);  // must be set before SetAttributes()
161✔
1587
    poArray->SetFilename(osZarrayFilename);
161✔
1588
    poArray->SetIsV2ChunkKeyEncoding(bV2ChunkKeyEncoding);
161✔
1589
    poArray->SetDimSeparator(osDimSeparator);
161✔
1590
    if (!abyNoData.empty())
161✔
1591
    {
1592
        poArray->RegisterNoDataValue(abyNoData.data());
62✔
1593
    }
1594
    poArray->ParseSpecialAttributes(m_pSelf.lock(), oAttributes);
161✔
1595
    poArray->SetAttributes(oAttributes);
161✔
1596
    poArray->SetDtype(oDtype);
161✔
1597
    if (oCodecs.Size() > 0 &&
293✔
1598
        oCodecs[oCodecs.Size() - 1].GetString("name") != "bytes")
293✔
1599
    {
1600
        poArray->SetStructuralInfo(
56✔
1601
            "COMPRESSOR", oCodecs[oCodecs.Size() - 1].ToString().c_str());
56✔
1602
    }
1603
    if (poCodecs)
161✔
1604
        poArray->SetCodecs(std::move(poCodecs));
132✔
1605
    RegisterArray(poArray);
161✔
1606

1607
    // If this is an indexing variable, attach it to the dimension.
1608
    if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
161✔
1609
    {
1610
        auto oIter = m_oMapDimensions.find(poArray->GetName());
2✔
1611
        if (oIter != m_oMapDimensions.end())
2✔
1612
        {
1613
            oIter->second->SetIndexingVariable(poArray);
2✔
1614
        }
1615
    }
1616

1617
    if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
161✔
1618
            "CACHE_TILE_PRESENCE", "NO")))
1619
    {
1620
        poArray->CacheTilePresence();
2✔
1621
    }
1622

1623
    return poArray;
161✔
1624
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc