• 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

92.38
/frmts/zarr/zarr_v2_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 "netcdf_cf_constants.h"  // for CF_UNITS, etc
19

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

27
/************************************************************************/
28
/*                       ZarrV2Array::ZarrV2Array()                     */
29
/************************************************************************/
30

31
ZarrV2Array::ZarrV2Array(
956✔
32
    const std::shared_ptr<ZarrSharedResource> &poSharedResource,
33
    const std::string &osParentName, const std::string &osName,
34
    const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
35
    const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
36
    const std::vector<GUInt64> &anBlockSize, bool bFortranOrder)
956✔
37
    : GDALAbstractMDArray(osParentName, osName),
38
      ZarrArray(poSharedResource, osParentName, osName, aoDims, oType,
39
                aoDtypeElts, anBlockSize),
40
      m_bFortranOrder(bFortranOrder)
956✔
41
{
42
    m_oCompressorJSon.Deinit();
956✔
43
}
956✔
44

45
/************************************************************************/
46
/*                         ZarrV2Array::Create()                        */
47
/************************************************************************/
48

49
std::shared_ptr<ZarrV2Array>
50
ZarrV2Array::Create(const std::shared_ptr<ZarrSharedResource> &poSharedResource,
956✔
51
                    const std::string &osParentName, const std::string &osName,
52
                    const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
53
                    const GDALExtendedDataType &oType,
54
                    const std::vector<DtypeElt> &aoDtypeElts,
55
                    const std::vector<GUInt64> &anBlockSize, bool bFortranOrder)
56
{
57
    auto arr = std::shared_ptr<ZarrV2Array>(
58
        new ZarrV2Array(poSharedResource, osParentName, osName, aoDims, oType,
59
                        aoDtypeElts, anBlockSize, bFortranOrder));
1,912✔
60
    if (arr->m_nTotalTileCount == 0)
956✔
61
        return nullptr;
1✔
62
    arr->SetSelf(arr);
955✔
63

64
    return arr;
955✔
65
}
66

67
/************************************************************************/
68
/*                             ~ZarrV2Array()                           */
69
/************************************************************************/
70

71
ZarrV2Array::~ZarrV2Array()
1,912✔
72
{
73
    ZarrV2Array::Flush();
956✔
74
}
1,912✔
75

76
/************************************************************************/
77
/*                                Flush()                               */
78
/************************************************************************/
79

80
void ZarrV2Array::Flush()
2,213✔
81
{
82
    if (!m_bValid)
2,213✔
83
        return;
9✔
84

85
    ZarrV2Array::FlushDirtyTile();
2,204✔
86

87
    if (m_bDefinitionModified)
2,204✔
88
    {
89
        Serialize();
326✔
90
        m_bDefinitionModified = false;
326✔
91
    }
92

93
    CPLJSONArray j_ARRAY_DIMENSIONS;
4,408✔
94
    bool bDimensionsModified = false;
2,204✔
95
    if (!m_aoDims.empty())
2,204✔
96
    {
97
        for (const auto &poDim : m_aoDims)
4,367✔
98
        {
99
            const auto poZarrDim =
100
                dynamic_cast<const ZarrDimension *>(poDim.get());
2,851✔
101
            if (poZarrDim && poZarrDim->IsXArrayDimension())
2,851✔
102
            {
103
                if (poZarrDim->IsModified())
2,326✔
104
                    bDimensionsModified = true;
8✔
105
                j_ARRAY_DIMENSIONS.Add(poDim->GetName());
2,326✔
106
            }
107
            else
108
            {
109
                j_ARRAY_DIMENSIONS = CPLJSONArray();
525✔
110
                break;
525✔
111
            }
112
        }
113
    }
114

115
    if (m_oAttrGroup.IsModified() || bDimensionsModified ||
4,355✔
116
        (m_bNew && j_ARRAY_DIMENSIONS.Size() != 0) || m_bUnitModified ||
2,143✔
117
        m_bOffsetModified || m_bScaleModified || m_bSRSModified)
4,355✔
118
    {
119
        m_bNew = false;
373✔
120

121
        auto oAttrs = SerializeSpecialAttributes();
746✔
122

123
        if (j_ARRAY_DIMENSIONS.Size() != 0)
373✔
124
        {
125
            oAttrs.Delete("_ARRAY_DIMENSIONS");
357✔
126
            oAttrs.Add("_ARRAY_DIMENSIONS", j_ARRAY_DIMENSIONS);
357✔
127
        }
128

129
        CPLJSONDocument oDoc;
746✔
130
        oDoc.SetRoot(oAttrs);
373✔
131
        const std::string osAttrFilename =
132
            CPLFormFilenameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
373✔
133
                                ".zattrs", nullptr);
746✔
134
        oDoc.Save(osAttrFilename);
373✔
135
        m_poSharedResource->SetZMetadataItem(osAttrFilename, oAttrs);
373✔
136
    }
137
}
138

139
/************************************************************************/
140
/*           StripUselessItemsFromCompressorConfiguration()             */
141
/************************************************************************/
142

143
static void StripUselessItemsFromCompressorConfiguration(CPLJSONObject &o)
28✔
144
{
145
    if (o.GetType() == CPLJSONObject::Type::Object)
28✔
146
    {
147
        o.Delete("num_threads");  // Blosc
18✔
148
        o.Delete("typesize");     // Blosc
18✔
149
        o.Delete("header");       // LZ4
18✔
150
    }
151
}
28✔
152

153
/************************************************************************/
154
/*                    ZarrV2Array::Serialize()                          */
155
/************************************************************************/
156

157
void ZarrV2Array::Serialize()
326✔
158
{
159
    CPLJSONDocument oDoc;
652✔
160
    CPLJSONObject oRoot = oDoc.GetRoot();
652✔
161

162
    CPLJSONArray oChunks;
652✔
163
    for (const auto nBlockSize : m_anBlockSize)
797✔
164
    {
165
        oChunks.Add(static_cast<GInt64>(nBlockSize));
471✔
166
    }
167
    oRoot.Add("chunks", oChunks);
326✔
168

169
    if (m_oCompressorJSon.IsValid())
326✔
170
    {
171
        oRoot.Add("compressor", m_oCompressorJSon);
28✔
172
        CPLJSONObject compressor = oRoot["compressor"];
84✔
173
        StripUselessItemsFromCompressorConfiguration(compressor);
28✔
174
    }
175
    else
176
    {
177
        oRoot.AddNull("compressor");
298✔
178
    }
179

180
    if (m_dtype.GetType() == CPLJSONObject::Type::Object)
326✔
181
        oRoot.Add("dtype", m_dtype["dummy"]);
313✔
182
    else
183
        oRoot.Add("dtype", m_dtype);
13✔
184

185
    if (m_pabyNoData == nullptr)
326✔
186
    {
187
        oRoot.AddNull("fill_value");
316✔
188
    }
189
    else
190
    {
191
        switch (m_oType.GetClass())
10✔
192
        {
193
            case GEDTC_NUMERIC:
8✔
194
            {
195
                SerializeNumericNoData(oRoot);
8✔
196
                break;
8✔
197
            }
198

199
            case GEDTC_STRING:
1✔
200
            {
201
                char *pszStr;
202
                char **ppszStr = reinterpret_cast<char **>(m_pabyNoData);
1✔
203
                memcpy(&pszStr, ppszStr, sizeof(pszStr));
1✔
204
                if (pszStr)
1✔
205
                {
206
                    const size_t nNativeSize =
207
                        m_aoDtypeElts.back().nativeOffset +
1✔
208
                        m_aoDtypeElts.back().nativeSize;
1✔
209
                    char *base64 = CPLBase64Encode(
3✔
210
                        static_cast<int>(std::min(nNativeSize, strlen(pszStr))),
1✔
211
                        reinterpret_cast<const GByte *>(pszStr));
212
                    oRoot.Add("fill_value", base64);
1✔
213
                    CPLFree(base64);
1✔
214
                }
215
                else
216
                {
217
                    oRoot.AddNull("fill_value");
×
218
                }
219
                break;
1✔
220
            }
221

222
            case GEDTC_COMPOUND:
1✔
223
            {
224
                const size_t nNativeSize = m_aoDtypeElts.back().nativeOffset +
1✔
225
                                           m_aoDtypeElts.back().nativeSize;
1✔
226
                std::vector<GByte> nativeNoData(nNativeSize);
2✔
227
                EncodeElt(m_aoDtypeElts, m_pabyNoData, &nativeNoData[0]);
1✔
228
                char *base64 = CPLBase64Encode(static_cast<int>(nNativeSize),
1✔
229
                                               nativeNoData.data());
1✔
230
                oRoot.Add("fill_value", base64);
1✔
231
                CPLFree(base64);
1✔
232
            }
233
        }
234
    }
235

236
    if (m_oFiltersArray.Size() == 0)
326✔
237
        oRoot.AddNull("filters");
325✔
238
    else
239
        oRoot.Add("filters", m_oFiltersArray);
1✔
240

241
    oRoot.Add("order", m_bFortranOrder ? "F" : "C");
326✔
242

243
    CPLJSONArray oShape;
652✔
244
    for (const auto &poDim : m_aoDims)
797✔
245
    {
246
        oShape.Add(static_cast<GInt64>(poDim->GetSize()));
471✔
247
    }
248
    oRoot.Add("shape", oShape);
326✔
249

250
    oRoot.Add("zarr_format", 2);
326✔
251

252
    if (m_osDimSeparator != ".")
326✔
253
    {
254
        oRoot.Add("dimension_separator", m_osDimSeparator);
10✔
255
    }
256

257
    oDoc.Save(m_osFilename);
326✔
258

259
    m_poSharedResource->SetZMetadataItem(m_osFilename, oRoot);
326✔
260
}
326✔
261

262
/************************************************************************/
263
/*                  ZarrV2Array::NeedDecodedBuffer()                    */
264
/************************************************************************/
265

266
bool ZarrV2Array::NeedDecodedBuffer() const
11,690✔
267
{
268
    const size_t nSourceSize =
269
        m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
11,690✔
270
    if (m_oType.GetClass() == GEDTC_COMPOUND &&
11,695✔
271
        nSourceSize != m_oType.GetSize())
6✔
272
    {
273
        return true;
4✔
274
    }
275
    else if (m_oType.GetClass() != GEDTC_STRING)
11,684✔
276
    {
277
        for (const auto &elt : m_aoDtypeElts)
23,231✔
278
        {
279
            if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative ||
11,664✔
280
                elt.nativeType == DtypeElt::NativeType::STRING_ASCII ||
11,583✔
281
                elt.nativeType == DtypeElt::NativeType::STRING_UNICODE)
11,582✔
282
            {
283
                return true;
95✔
284
            }
285
        }
286
    }
287
    return false;
11,602✔
288
}
289

290
/************************************************************************/
291
/*               ZarrV2Array::AllocateWorkingBuffers()                  */
292
/************************************************************************/
293

294
bool ZarrV2Array::AllocateWorkingBuffers() const
1,946✔
295
{
296
    if (m_bAllocateWorkingBuffersDone)
1,946✔
297
        return m_bWorkingBuffersOK;
1,435✔
298

299
    m_bAllocateWorkingBuffersDone = true;
511✔
300

301
    size_t nSizeNeeded = m_nTileSize;
511✔
302
    if (m_bFortranOrder || m_oFiltersArray.Size() != 0)
511✔
303
    {
304
        if (nSizeNeeded > std::numeric_limits<size_t>::max() / 2)
46✔
305
        {
306
            CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
1✔
307
            return false;
1✔
308
        }
309
        nSizeNeeded *= 2;
45✔
310
    }
311
    if (NeedDecodedBuffer())
510✔
312
    {
313
        size_t nDecodedBufferSize = m_oType.GetSize();
43✔
314
        for (const auto &nBlockSize : m_anBlockSize)
126✔
315
        {
316
            if (nDecodedBufferSize > std::numeric_limits<size_t>::max() /
83✔
317
                                         static_cast<size_t>(nBlockSize))
83✔
318
            {
319
                CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
×
320
                return false;
×
321
            }
322
            nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
83✔
323
        }
324
        if (nSizeNeeded >
43✔
325
            std::numeric_limits<size_t>::max() - nDecodedBufferSize)
43✔
326
        {
327
            CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
×
328
            return false;
×
329
        }
330
        nSizeNeeded += nDecodedBufferSize;
43✔
331
    }
332

333
    // Reserve a buffer for tile content
334
    if (nSizeNeeded > 1024 * 1024 * 1024 &&
512✔
335
        !CPLTestBool(CPLGetConfigOption("ZARR_ALLOW_BIG_TILE_SIZE", "NO")))
2✔
336
    {
337
        CPLError(CE_Failure, CPLE_AppDefined,
2✔
338
                 "Zarr tile allocation would require " CPL_FRMT_GUIB " bytes. "
339
                 "By default the driver limits to 1 GB. To allow that memory "
340
                 "allocation, set the ZARR_ALLOW_BIG_TILE_SIZE configuration "
341
                 "option to YES.",
342
                 static_cast<GUIntBig>(nSizeNeeded));
343
        return false;
2✔
344
    }
345

346
    m_bWorkingBuffersOK = AllocateWorkingBuffers(
1,016✔
347
        m_abyRawTileData, m_abyTmpRawTileData, m_abyDecodedTileData);
508✔
348
    return m_bWorkingBuffersOK;
508✔
349
}
350

351
bool ZarrV2Array::AllocateWorkingBuffers(
11,180✔
352
    ZarrByteVectorQuickResize &abyRawTileData,
353
    ZarrByteVectorQuickResize &abyTmpRawTileData,
354
    ZarrByteVectorQuickResize &abyDecodedTileData) const
355
{
356
    // This method should NOT modify any ZarrArray member, as it is going to
357
    // be called concurrently from several threads.
358

359
    // Set those #define to avoid accidental use of some global variables
360
#define m_abyTmpRawTileData cannot_use_here
361
#define m_abyRawTileData cannot_use_here
362
#define m_abyDecodedTileData cannot_use_here
363

364
    try
365
    {
366
        abyRawTileData.resize(m_nTileSize);
11,180✔
367
        if (m_bFortranOrder || m_oFiltersArray.Size() != 0)
11,180✔
368
            abyTmpRawTileData.resize(m_nTileSize);
45✔
369
    }
370
    catch (const std::bad_alloc &e)
×
371
    {
372
        CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
×
373
        return false;
×
374
    }
375

376
    if (NeedDecodedBuffer())
11,180✔
377
    {
378
        size_t nDecodedBufferSize = m_oType.GetSize();
43✔
379
        for (const auto &nBlockSize : m_anBlockSize)
126✔
380
        {
381
            nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
83✔
382
        }
383
        try
384
        {
385
            abyDecodedTileData.resize(nDecodedBufferSize);
43✔
386
        }
387
        catch (const std::bad_alloc &e)
×
388
        {
389
            CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
×
390
            return false;
×
391
        }
392
    }
393

394
    return true;
11,178✔
395
#undef m_abyTmpRawTileData
396
#undef m_abyRawTileData
397
#undef m_abyDecodedTileData
398
}
399

400
/************************************************************************/
401
/*                      ZarrV2Array::LoadTileData()                     */
402
/************************************************************************/
403

404
bool ZarrV2Array::LoadTileData(const uint64_t *tileIndices,
4,880✔
405
                               bool &bMissingTileOut) const
406
{
407
    return LoadTileData(tileIndices,
9,760✔
408
                        false,  // use mutex
409
                        m_psDecompressor, m_abyRawTileData, m_abyTmpRawTileData,
4,880✔
410
                        m_abyDecodedTileData, bMissingTileOut);
4,880✔
411
}
412

413
bool ZarrV2Array::LoadTileData(const uint64_t *tileIndices, bool bUseMutex,
15,551✔
414
                               const CPLCompressor *psDecompressor,
415
                               ZarrByteVectorQuickResize &abyRawTileData,
416
                               ZarrByteVectorQuickResize &abyTmpRawTileData,
417
                               ZarrByteVectorQuickResize &abyDecodedTileData,
418
                               bool &bMissingTileOut) const
419
{
420
    // This method should NOT modify any ZarrArray member, as it is going to
421
    // be called concurrently from several threads.
422

423
    // Set those #define to avoid accidental use of some global variables
424
#define m_abyTmpRawTileData cannot_use_here
425
#define m_abyRawTileData cannot_use_here
426
#define m_abyDecodedTileData cannot_use_here
427
#define m_psDecompressor cannot_use_here
428

429
    bMissingTileOut = false;
15,551✔
430

431
    std::string osFilename = BuildTileFilename(tileIndices);
31,100✔
432

433
    // For network file systems, get the streaming version of the filename,
434
    // as we don't need arbitrary seeking in the file
435
    osFilename = VSIFileManager::GetHandler(osFilename.c_str())
15,552✔
436
                     ->GetStreamingFilename(osFilename);
15,552✔
437

438
    // First if we have a tile presence cache, check tile presence from it
439
    if (bUseMutex)
15,551✔
440
        m_oMutex.lock();
10,672✔
441
    auto poTilePresenceArray = OpenTilePresenceCache(false);
31,096✔
442
    if (poTilePresenceArray)
15,552✔
443
    {
444
        std::vector<GUInt64> anTileIdx(m_aoDims.size());
18✔
445
        const std::vector<size_t> anCount(m_aoDims.size(), 1);
18✔
446
        const std::vector<GInt64> anArrayStep(m_aoDims.size(), 0);
18✔
447
        const std::vector<GPtrDiff_t> anBufferStride(m_aoDims.size(), 0);
18✔
448
        const auto eByteDT = GDALExtendedDataType::Create(GDT_Byte);
18✔
449
        for (size_t i = 0; i < m_aoDims.size(); ++i)
54✔
450
        {
451
            anTileIdx[i] = static_cast<GUInt64>(tileIndices[i]);
36✔
452
        }
453
        GByte byValue = 0;
18✔
454
        if (poTilePresenceArray->Read(anTileIdx.data(), anCount.data(),
18✔
455
                                      anArrayStep.data(), anBufferStride.data(),
456
                                      eByteDT, &byValue) &&
36✔
457
            byValue == 0)
18✔
458
        {
459
            if (bUseMutex)
13✔
460
                m_oMutex.unlock();
×
461
            CPLDebugOnly(ZARR_DEBUG_KEY, "Tile %s missing (=nodata)",
13✔
462
                         osFilename.c_str());
463
            bMissingTileOut = true;
13✔
464
            return true;
13✔
465
        }
466
    }
467
    if (bUseMutex)
15,539✔
468
        m_oMutex.unlock();
10,672✔
469

470
    VSILFILE *fp = nullptr;
15,539✔
471
    // This is the number of files returned in a S3 directory listing operation
472
    constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
15,539✔
473
    const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
15,539✔
474
                                           nullptr};
475
    const auto nErrorBefore = CPLGetErrorCounter();
15,539✔
476
    if ((m_osDimSeparator == "/" && !m_anBlockSize.empty() &&
15,558✔
477
         m_anBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
31,098✔
478
        (m_osDimSeparator != "/" &&
15,539✔
479
         m_nTotalTileCount > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING))
15,519✔
480
    {
481
        // Avoid issuing ReadDir() when a lot of files are expected
482
        CPLConfigOptionSetter optionSetter("GDAL_DISABLE_READDIR_ON_OPEN",
483
                                           "YES", true);
10,982✔
484
        fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
10,980✔
485
    }
486
    else
487
    {
488
        fp = VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions);
4,557✔
489
    }
490
    if (fp == nullptr)
15,537✔
491
    {
492
        if (nErrorBefore != CPLGetErrorCounter())
2,714✔
493
        {
494
            return false;
3✔
495
        }
496
        else
497
        {
498
            // Missing files are OK and indicate nodata_value
499
            CPLDebugOnly(ZARR_DEBUG_KEY, "Tile %s missing (=nodata)",
2,711✔
500
                         osFilename.c_str());
501
            bMissingTileOut = true;
2,711✔
502
            return true;
2,711✔
503
        }
504
    }
505

506
    bMissingTileOut = false;
12,823✔
507
    bool bRet = true;
12,823✔
508
    size_t nRawDataSize = abyRawTileData.size();
12,823✔
509
    if (psDecompressor == nullptr)
12,821✔
510
    {
511
        nRawDataSize = VSIFReadL(&abyRawTileData[0], 1, nRawDataSize, fp);
7,281✔
512
    }
513
    else
514
    {
515
        VSIFSeekL(fp, 0, SEEK_END);
5,540✔
516
        const auto nSize = VSIFTellL(fp);
5,541✔
517
        VSIFSeekL(fp, 0, SEEK_SET);
5,541✔
518
        if (nSize > static_cast<vsi_l_offset>(std::numeric_limits<int>::max()))
5,540✔
519
        {
520
            CPLError(CE_Failure, CPLE_AppDefined, "Too large tile %s",
×
521
                     osFilename.c_str());
522
            bRet = false;
×
523
        }
524
        else
525
        {
526
            ZarrByteVectorQuickResize abyCompressedData;
11,084✔
527
            try
528
            {
529
                abyCompressedData.resize(static_cast<size_t>(nSize));
5,540✔
530
            }
531
            catch (const std::exception &)
×
532
            {
533
                CPLError(CE_Failure, CPLE_OutOfMemory,
×
534
                         "Cannot allocate memory for tile %s",
535
                         osFilename.c_str());
536
                bRet = false;
×
537
            }
538

539
            if (bRet &&
16,624✔
540
                (abyCompressedData.empty() ||
11,083✔
541
                 VSIFReadL(&abyCompressedData[0], 1, abyCompressedData.size(),
5,542✔
542
                           fp) != abyCompressedData.size()))
5,541✔
543
            {
544
                CPLError(CE_Failure, CPLE_AppDefined,
×
545
                         "Could not read tile %s correctly",
546
                         osFilename.c_str());
547
                bRet = false;
×
548
            }
549
            else
550
            {
551
                void *out_buffer = &abyRawTileData[0];
5,540✔
552
                if (!psDecompressor->pfnFunc(
5,541✔
553
                        abyCompressedData.data(), abyCompressedData.size(),
5,540✔
554
                        &out_buffer, &nRawDataSize, nullptr,
555
                        psDecompressor->user_data))
5,540✔
556
                {
557
                    CPLError(CE_Failure, CPLE_AppDefined,
3✔
558
                             "Decompression of tile %s failed",
559
                             osFilename.c_str());
560
                    bRet = false;
3✔
561
                }
562
            }
563
        }
564
    }
565
    VSIFCloseL(fp);
12,822✔
566
    if (!bRet)
12,823✔
567
        return false;
3✔
568

569
    for (int i = m_oFiltersArray.Size(); i > 0;)
12,833✔
570
    {
571
        --i;
12✔
572
        const auto &oFilter = m_oFiltersArray[i];
12✔
573
        const auto osFilterId = oFilter["id"].ToString();
24✔
574
        const auto psFilterDecompressor =
575
            EQUAL(osFilterId.c_str(), "shuffle") ? ZarrGetShuffleDecompressor()
22✔
576
            : EQUAL(osFilterId.c_str(), "quantize")
10✔
577
                ? ZarrGetQuantizeDecompressor()
18✔
578
            : EQUAL(osFilterId.c_str(), "fixedscaleoffset")
8✔
579
                ? ZarrGetFixedScaleOffsetDecompressor()
8✔
580
                : CPLGetDecompressor(osFilterId.c_str());
4✔
581
        CPLAssert(psFilterDecompressor);
12✔
582

583
        CPLStringList aosOptions;
12✔
584
        for (const auto &obj : oFilter.GetChildren())
52✔
585
        {
586
            aosOptions.SetNameValue(obj.GetName().c_str(),
80✔
587
                                    obj.ToString().c_str());
120✔
588
        }
589
        void *out_buffer = &abyTmpRawTileData[0];
12✔
590
        size_t nOutSize = abyTmpRawTileData.size();
12✔
591
        if (!psFilterDecompressor->pfnFunc(
12✔
592
                abyRawTileData.data(), nRawDataSize, &out_buffer, &nOutSize,
12✔
593
                aosOptions.List(), psFilterDecompressor->user_data))
12✔
594
        {
595
            CPLError(CE_Failure, CPLE_AppDefined,
×
596
                     "Filter %s for tile %s failed", osFilterId.c_str(),
597
                     osFilename.c_str());
598
            return false;
×
599
        }
600

601
        nRawDataSize = nOutSize;
12✔
602
        std::swap(abyRawTileData, abyTmpRawTileData);
12✔
603
    }
604
    if (nRawDataSize != abyRawTileData.size())
12,819✔
605
    {
UNCOV
606
        CPLError(CE_Failure, CPLE_AppDefined,
×
607
                 "Decompressed tile %s has not expected size after filters",
608
                 osFilename.c_str());
609
        return false;
×
610
    }
611

612
    if (m_bFortranOrder && !m_aoDims.empty())
12,817✔
613
    {
614
        BlockTranspose(abyRawTileData, abyTmpRawTileData, true);
46✔
615
        std::swap(abyRawTileData, abyTmpRawTileData);
46✔
616
    }
617

618
    if (!abyDecodedTileData.empty())
12,817✔
619
    {
620
        const size_t nSourceSize =
621
            m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
326✔
622
        const auto nDTSize = m_oType.GetSize();
326✔
623
        const size_t nValues = abyDecodedTileData.size() / nDTSize;
326✔
624
        const GByte *pSrc = abyRawTileData.data();
326✔
625
        GByte *pDst = &abyDecodedTileData[0];
326✔
626
        for (size_t i = 0; i < nValues;
2,259✔
627
             i++, pSrc += nSourceSize, pDst += nDTSize)
1,931✔
628
        {
629
            DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
1,931✔
630
        }
631
    }
632

633
    return true;
12,816✔
634

635
#undef m_abyTmpRawTileData
636
#undef m_abyRawTileData
637
#undef m_abyDecodedTileData
638
#undef m_psDecompressor
639
}
640

641
/************************************************************************/
642
/*                      ZarrV2Array::IAdviseRead()                      */
643
/************************************************************************/
644

645
bool ZarrV2Array::IAdviseRead(const GUInt64 *arrayStartIdx, const size_t *count,
6✔
646
                              CSLConstList papszOptions) const
647
{
648
    std::vector<uint64_t> anIndicesCur;
12✔
649
    int nThreadsMax = 0;
6✔
650
    std::vector<uint64_t> anReqTilesIndices;
12✔
651
    size_t nReqTiles = 0;
6✔
652
    if (!IAdviseReadCommon(arrayStartIdx, count, papszOptions, anIndicesCur,
6✔
653
                           nThreadsMax, anReqTilesIndices, nReqTiles))
654
    {
655
        return false;
2✔
656
    }
657
    if (nThreadsMax <= 1)
4✔
658
    {
659
        return true;
×
660
    }
661

662
    const int nThreads =
663
        static_cast<int>(std::min(static_cast<size_t>(nThreadsMax), nReqTiles));
4✔
664

665
    CPLWorkerThreadPool *wtp = GDALGetGlobalThreadPool(nThreadsMax);
4✔
666
    if (wtp == nullptr)
4✔
667
        return false;
×
668

669
    struct JobStruct
670
    {
671
        JobStruct() = default;
672

673
        JobStruct(const JobStruct &) = delete;
674
        JobStruct &operator=(const JobStruct &) = delete;
675

676
        JobStruct(JobStruct &&) = default;
677
        JobStruct &operator=(JobStruct &&) = default;
678

679
        const ZarrV2Array *poArray = nullptr;
680
        bool *pbGlobalStatus = nullptr;
681
        int *pnRemainingThreads = nullptr;
682
        const std::vector<uint64_t> *panReqTilesIndices = nullptr;
683
        size_t nFirstIdx = 0;
684
        size_t nLastIdxNotIncluded = 0;
685
    };
686

687
    std::vector<JobStruct> asJobStructs;
4✔
688

689
    bool bGlobalStatus = true;
4✔
690
    int nRemainingThreads = nThreads;
4✔
691
    // Check for very highly overflow in below loop
692
    assert(static_cast<size_t>(nThreads) <
4✔
693
           std::numeric_limits<size_t>::max() / nReqTiles);
694

695
    // Setup jobs
696
    for (int i = 0; i < nThreads; i++)
20✔
697
    {
698
        JobStruct jobStruct;
16✔
699
        jobStruct.poArray = this;
16✔
700
        jobStruct.pbGlobalStatus = &bGlobalStatus;
16✔
701
        jobStruct.pnRemainingThreads = &nRemainingThreads;
16✔
702
        jobStruct.panReqTilesIndices = &anReqTilesIndices;
16✔
703
        jobStruct.nFirstIdx = static_cast<size_t>(i * nReqTiles / nThreads);
16✔
704
        jobStruct.nLastIdxNotIncluded = std::min(
16✔
705
            static_cast<size_t>((i + 1) * nReqTiles / nThreads), nReqTiles);
16✔
706
        asJobStructs.emplace_back(std::move(jobStruct));
16✔
707
    }
708

709
    const auto JobFunc = [](void *pThreadData)
16✔
710
    {
711
        const JobStruct *jobStruct =
16✔
712
            static_cast<const JobStruct *>(pThreadData);
713

714
        const auto poArray = jobStruct->poArray;
16✔
715
        const auto &aoDims = poArray->GetDimensions();
16✔
716
        const size_t l_nDims = poArray->GetDimensionCount();
16✔
717
        ZarrByteVectorQuickResize abyRawTileData;
16✔
718
        ZarrByteVectorQuickResize abyDecodedTileData;
16✔
719
        ZarrByteVectorQuickResize abyTmpRawTileData;
16✔
720
        const CPLCompressor *psDecompressor =
721
            CPLGetDecompressor(poArray->m_osDecompressorId.c_str());
16✔
722

723
        for (size_t iReq = jobStruct->nFirstIdx;
10,688✔
724
             iReq < jobStruct->nLastIdxNotIncluded; ++iReq)
10,688✔
725
        {
726
            // Check if we must early exit
727
            {
728
                std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
10,672✔
729
                if (!(*jobStruct->pbGlobalStatus))
10,672✔
730
                    return;
×
731
            }
732

733
            const uint64_t *tileIndices =
734
                jobStruct->panReqTilesIndices->data() + iReq * l_nDims;
10,672✔
735

736
            uint64_t nTileIdx = 0;
10,672✔
737
            for (size_t j = 0; j < l_nDims; ++j)
32,016✔
738
            {
739
                if (j > 0)
21,344✔
740
                    nTileIdx *= aoDims[j - 1]->GetSize();
10,672✔
741
                nTileIdx += tileIndices[j];
21,344✔
742
            }
743

744
            if (!poArray->AllocateWorkingBuffers(
10,672✔
745
                    abyRawTileData, abyTmpRawTileData, abyDecodedTileData))
746
            {
747
                std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
×
748
                *jobStruct->pbGlobalStatus = false;
×
749
                break;
×
750
            }
751

752
            bool bIsEmpty = false;
10,667✔
753
            bool success = poArray->LoadTileData(tileIndices,
10,667✔
754
                                                 true,  // use mutex
755
                                                 psDecompressor, abyRawTileData,
756
                                                 abyTmpRawTileData,
757
                                                 abyDecodedTileData, bIsEmpty);
758

759
            std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
10,669✔
760
            if (!success)
10,672✔
761
            {
762
                *jobStruct->pbGlobalStatus = false;
×
763
                break;
×
764
            }
765

766
            CachedTile cachedTile;
21,344✔
767
            if (!bIsEmpty)
10,672✔
768
            {
769
                if (!abyDecodedTileData.empty())
10,670✔
770
                    std::swap(cachedTile.abyDecoded, abyDecodedTileData);
×
771
                else
772
                    std::swap(cachedTile.abyDecoded, abyRawTileData);
10,670✔
773
            }
774
            poArray->m_oMapTileIndexToCachedTile[nTileIdx] =
10,672✔
775
                std::move(cachedTile);
21,344✔
776
        }
777

778
        std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
16✔
779
        (*jobStruct->pnRemainingThreads)--;
16✔
780
    };
781

782
    // Start jobs
783
    for (int i = 0; i < nThreads; i++)
20✔
784
    {
785
        if (!wtp->SubmitJob(JobFunc, &asJobStructs[i]))
16✔
786
        {
787
            std::lock_guard<std::mutex> oLock(m_oMutex);
×
788
            bGlobalStatus = false;
×
789
            nRemainingThreads = i;
×
790
            break;
×
791
        }
792
    }
793

794
    // Wait for all jobs to be finished
795
    while (true)
796
    {
797
        {
798
            std::lock_guard<std::mutex> oLock(m_oMutex);
17✔
799
            if (nRemainingThreads == 0)
17✔
800
                break;
4✔
801
        }
802
        wtp->WaitEvent();
13✔
803
    }
13✔
804

805
    return bGlobalStatus;
4✔
806
}
807

808
/************************************************************************/
809
/*                    ZarrV2Array::FlushDirtyTile()                     */
810
/************************************************************************/
811

812
bool ZarrV2Array::FlushDirtyTile() const
17,920✔
813
{
814
    if (!m_bDirtyTile)
17,920✔
815
        return true;
5,853✔
816
    m_bDirtyTile = false;
12,067✔
817

818
    std::string osFilename = BuildTileFilename(m_anCachedTiledIndices.data());
24,134✔
819

820
    const size_t nSourceSize =
821
        m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
12,067✔
822
    const auto &abyTile =
823
        m_abyDecodedTileData.empty() ? m_abyRawTileData : m_abyDecodedTileData;
12,067✔
824

825
    if (IsEmptyTile(abyTile))
12,067✔
826
    {
827
        m_bCachedTiledEmpty = true;
690✔
828

829
        VSIStatBufL sStat;
830
        if (VSIStatL(osFilename.c_str(), &sStat) == 0)
690✔
831
        {
832
            CPLDebugOnly(ZARR_DEBUG_KEY,
108✔
833
                         "Deleting tile %s that has now empty content",
834
                         osFilename.c_str());
835
            return VSIUnlink(osFilename.c_str()) == 0;
108✔
836
        }
837
        return true;
582✔
838
    }
839

840
    if (!m_abyDecodedTileData.empty())
11,377✔
841
    {
842
        const size_t nDTSize = m_oType.GetSize();
20✔
843
        const size_t nValues = m_abyDecodedTileData.size() / nDTSize;
20✔
844
        GByte *pDst = &m_abyRawTileData[0];
20✔
845
        const GByte *pSrc = m_abyDecodedTileData.data();
20✔
846
        for (size_t i = 0; i < nValues;
140✔
847
             i++, pDst += nSourceSize, pSrc += nDTSize)
120✔
848
        {
849
            EncodeElt(m_aoDtypeElts, pSrc, pDst);
120✔
850
        }
851
    }
852

853
    if (m_bFortranOrder && !m_aoDims.empty())
11,377✔
854
    {
855
        BlockTranspose(m_abyRawTileData, m_abyTmpRawTileData, false);
20✔
856
        std::swap(m_abyRawTileData, m_abyTmpRawTileData);
20✔
857
    }
858

859
    size_t nRawDataSize = m_abyRawTileData.size();
11,377✔
860
    for (const auto &oFilter : m_oFiltersArray)
11,380✔
861
    {
862
        const auto osFilterId = oFilter["id"].ToString();
8✔
863
        if (osFilterId == "quantize" || osFilterId == "fixedscaleoffset")
4✔
864
        {
865
            CPLError(CE_Failure, CPLE_NotSupported,
1✔
866
                     "%s filter not supported for writing", osFilterId.c_str());
867
            return false;
1✔
868
        }
869
        const auto psFilterCompressor =
870
            EQUAL(osFilterId.c_str(), "shuffle")
3✔
871
                ? ZarrGetShuffleCompressor()
3✔
872
                : CPLGetCompressor(osFilterId.c_str());
2✔
873
        CPLAssert(psFilterCompressor);
3✔
874

875
        CPLStringList aosOptions;
3✔
876
        for (const auto &obj : oFilter.GetChildren())
9✔
877
        {
878
            aosOptions.SetNameValue(obj.GetName().c_str(),
12✔
879
                                    obj.ToString().c_str());
18✔
880
        }
881
        void *out_buffer = &m_abyTmpRawTileData[0];
3✔
882
        size_t nOutSize = m_abyTmpRawTileData.size();
3✔
883
        if (!psFilterCompressor->pfnFunc(
3✔
884
                m_abyRawTileData.data(), nRawDataSize, &out_buffer, &nOutSize,
3✔
885
                aosOptions.List(), psFilterCompressor->user_data))
3✔
886
        {
887
            CPLError(CE_Failure, CPLE_AppDefined,
×
888
                     "Filter %s for tile %s failed", osFilterId.c_str(),
889
                     osFilename.c_str());
890
            return false;
×
891
        }
892

893
        nRawDataSize = nOutSize;
3✔
894
        std::swap(m_abyRawTileData, m_abyTmpRawTileData);
3✔
895
    }
896

897
    if (m_osDimSeparator == "/")
11,376✔
898
    {
899
        std::string osDir = CPLGetDirnameSafe(osFilename.c_str());
20✔
900
        VSIStatBufL sStat;
901
        if (VSIStatL(osDir.c_str(), &sStat) != 0)
20✔
902
        {
903
            if (VSIMkdirRecursive(osDir.c_str(), 0755) != 0)
20✔
904
            {
905
                CPLError(CE_Failure, CPLE_AppDefined,
×
906
                         "Cannot create directory %s", osDir.c_str());
907
                return false;
×
908
            }
909
        }
910
    }
911

912
    if (m_psCompressor == nullptr && m_psDecompressor != nullptr)
11,376✔
913
    {
914
        // Case of imagecodecs_tiff
915

916
        CPLError(CE_Failure, CPLE_NotSupported,
1✔
917
                 "Only decompression supported for '%s' compression method",
918
                 m_osDecompressorId.c_str());
919
        return false;
1✔
920
    }
921

922
    VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
11,375✔
923
    if (fp == nullptr)
11,375✔
924
    {
925
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot create tile %s",
×
926
                 osFilename.c_str());
927
        return false;
×
928
    }
929

930
    bool bRet = true;
11,375✔
931
    if (m_psCompressor == nullptr)
11,375✔
932
    {
933
        if (VSIFWriteL(m_abyRawTileData.data(), 1, nRawDataSize, fp) !=
5,925✔
934
            nRawDataSize)
935
        {
936
            CPLError(CE_Failure, CPLE_AppDefined,
2✔
937
                     "Could not write tile %s correctly", osFilename.c_str());
938
            bRet = false;
2✔
939
        }
940
    }
941
    else
942
    {
943
        std::vector<GByte> abyCompressedData;
10,900✔
944
        try
945
        {
946
            constexpr size_t MIN_BUF_SIZE = 64;  // somewhat arbitrary
5,450✔
947
            abyCompressedData.resize(static_cast<size_t>(
5,450✔
948
                MIN_BUF_SIZE + nRawDataSize + nRawDataSize / 3));
5,450✔
949
        }
950
        catch (const std::exception &)
×
951
        {
952
            CPLError(CE_Failure, CPLE_OutOfMemory,
×
953
                     "Cannot allocate memory for tile %s", osFilename.c_str());
954
            bRet = false;
×
955
        }
956

957
        if (bRet)
5,450✔
958
        {
959
            void *out_buffer = &abyCompressedData[0];
5,450✔
960
            size_t out_size = abyCompressedData.size();
5,450✔
961
            CPLStringList aosOptions;
10,900✔
962
            const auto &compressorConfig = m_oCompressorJSon;
5,450✔
963
            for (const auto &obj : compressorConfig.GetChildren())
16,350✔
964
            {
965
                aosOptions.SetNameValue(obj.GetName().c_str(),
21,800✔
966
                                        obj.ToString().c_str());
32,700✔
967
            }
968
            if (EQUAL(m_psCompressor->pszId, "blosc") &&
5,450✔
969
                m_oType.GetClass() == GEDTC_NUMERIC)
×
970
            {
971
                aosOptions.SetNameValue(
972
                    "TYPESIZE",
973
                    CPLSPrintf("%d", GDALGetDataTypeSizeBytes(
974
                                         GDALGetNonComplexDataType(
975
                                             m_oType.GetNumericDataType()))));
×
976
            }
977

978
            if (!m_psCompressor->pfnFunc(
5,450✔
979
                    m_abyRawTileData.data(), nRawDataSize, &out_buffer,
5,450✔
980
                    &out_size, aosOptions.List(), m_psCompressor->user_data))
5,450✔
981
            {
982
                CPLError(CE_Failure, CPLE_AppDefined,
×
983
                         "Compression of tile %s failed", osFilename.c_str());
984
                bRet = false;
×
985
            }
986
            abyCompressedData.resize(out_size);
5,450✔
987
        }
988

989
        if (bRet &&
10,900✔
990
            VSIFWriteL(abyCompressedData.data(), 1, abyCompressedData.size(),
5,450✔
991
                       fp) != abyCompressedData.size())
5,450✔
992
        {
993
            CPLError(CE_Failure, CPLE_AppDefined,
×
994
                     "Could not write tile %s correctly", osFilename.c_str());
995
            bRet = false;
×
996
        }
997
    }
998
    VSIFCloseL(fp);
11,375✔
999

1000
    return bRet;
11,375✔
1001
}
1002

1003
/************************************************************************/
1004
/*                          BuildTileFilename()                         */
1005
/************************************************************************/
1006

1007
std::string ZarrV2Array::BuildTileFilename(const uint64_t *tileIndices) const
27,618✔
1008
{
1009
    std::string osFilename;
27,618✔
1010
    if (m_aoDims.empty())
27,610✔
1011
    {
1012
        osFilename = "0";
4✔
1013
    }
1014
    else
1015
    {
1016
        for (size_t i = 0; i < m_aoDims.size(); ++i)
83,375✔
1017
        {
1018
            if (!osFilename.empty())
55,759✔
1019
                osFilename += m_osDimSeparator;
28,146✔
1020
            osFilename += std::to_string(tileIndices[i]);
55,761✔
1021
        }
1022
    }
1023

1024
    return CPLFormFilenameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
55,238✔
1025
                               osFilename.c_str(), nullptr);
82,854✔
1026
}
1027

1028
/************************************************************************/
1029
/*                          GetDataDirectory()                          */
1030
/************************************************************************/
1031

1032
std::string ZarrV2Array::GetDataDirectory() const
2✔
1033
{
1034
    return CPLGetDirnameSafe(m_osFilename.c_str());
2✔
1035
}
1036

1037
/************************************************************************/
1038
/*                        GetTileIndicesFromFilename()                  */
1039
/************************************************************************/
1040

1041
CPLStringList
1042
ZarrV2Array::GetTileIndicesFromFilename(const char *pszFilename) const
5✔
1043
{
1044
    return CPLStringList(
1045
        CSLTokenizeString2(pszFilename, m_osDimSeparator.c_str(), 0));
5✔
1046
}
1047

1048
/************************************************************************/
1049
/*                             ParseDtype()                             */
1050
/************************************************************************/
1051

1052
static size_t GetAlignment(const CPLJSONObject &obj)
26✔
1053
{
1054
    if (obj.GetType() == CPLJSONObject::Type::String)
26✔
1055
    {
1056
        const auto str = obj.ToString();
69✔
1057
        if (str.size() < 3)
23✔
1058
            return 1;
×
1059
        const char chType = str[1];
23✔
1060
        const int nBytes = atoi(str.c_str() + 2);
23✔
1061
        if (chType == 'S')
23✔
1062
            return sizeof(char *);
2✔
1063
        if (chType == 'c' && nBytes == 8)
21✔
1064
            return sizeof(float);
×
1065
        if (chType == 'c' && nBytes == 16)
21✔
1066
            return sizeof(double);
×
1067
        return nBytes;
21✔
1068
    }
1069
    else if (obj.GetType() == CPLJSONObject::Type::Array)
3✔
1070
    {
1071
        const auto oArray = obj.ToArray();
6✔
1072
        size_t nAlignment = 1;
3✔
1073
        for (const auto &oElt : oArray)
9✔
1074
        {
1075
            const auto oEltArray = oElt.ToArray();
6✔
1076
            if (!oEltArray.IsValid() || oEltArray.Size() != 2 ||
12✔
1077
                oEltArray[0].GetType() != CPLJSONObject::Type::String)
12✔
1078
            {
1079
                return 1;
×
1080
            }
1081
            nAlignment = std::max(nAlignment, GetAlignment(oEltArray[1]));
6✔
1082
            if (nAlignment == sizeof(void *))
6✔
1083
                break;
×
1084
        }
1085
        return nAlignment;
3✔
1086
    }
1087
    return 1;
×
1088
}
1089

1090
static GDALExtendedDataType ParseDtype(const CPLJSONObject &obj,
692✔
1091
                                       std::vector<DtypeElt> &elts)
1092
{
1093
    const auto AlignOffsetOn = [](size_t offset, size_t alignment)
29✔
1094
    { return offset + (alignment - (offset % alignment)) % alignment; };
29✔
1095

1096
    do
1097
    {
1098
        if (obj.GetType() == CPLJSONObject::Type::String)
692✔
1099
        {
1100
            const auto str = obj.ToString();
1,362✔
1101
            char chEndianness = 0;
681✔
1102
            char chType;
1103
            int nBytes;
1104
            DtypeElt elt;
681✔
1105
            if (str.size() < 3)
681✔
1106
                break;
3✔
1107
            chEndianness = str[0];
678✔
1108
            chType = str[1];
678✔
1109
            nBytes = atoi(str.c_str() + 2);
678✔
1110
            if (nBytes <= 0 || nBytes >= 1000)
678✔
1111
                break;
1112

1113
            elt.needByteSwapping = false;
676✔
1114
            if ((nBytes > 1 && chType != 'S') || chType == 'U')
676✔
1115
            {
1116
                if (chEndianness == '<')
371✔
1117
                    elt.needByteSwapping = (CPL_IS_LSB == 0);
328✔
1118
                else if (chEndianness == '>')
43✔
1119
                    elt.needByteSwapping = (CPL_IS_LSB != 0);
43✔
1120
            }
1121

1122
            GDALDataType eDT;
1123
            if (!elts.empty())
676✔
1124
            {
1125
                elt.nativeOffset =
11✔
1126
                    elts.back().nativeOffset + elts.back().nativeSize;
11✔
1127
            }
1128
            elt.nativeSize = nBytes;
676✔
1129
            if (chType == 'b' && nBytes == 1)  // boolean
676✔
1130
            {
1131
                elt.nativeType = DtypeElt::NativeType::BOOLEAN;
66✔
1132
                eDT = GDT_Byte;
66✔
1133
            }
1134
            else if (chType == 'u' && nBytes == 1)
610✔
1135
            {
1136
                elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
218✔
1137
                eDT = GDT_Byte;
218✔
1138
            }
1139
            else if (chType == 'i' && nBytes == 1)
392✔
1140
            {
1141
                elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
12✔
1142
                eDT = GDT_Int8;
12✔
1143
            }
1144
            else if (chType == 'i' && nBytes == 2)
380✔
1145
            {
1146
                elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
20✔
1147
                eDT = GDT_Int16;
20✔
1148
            }
1149
            else if (chType == 'i' && nBytes == 4)
360✔
1150
            {
1151
                elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
26✔
1152
                eDT = GDT_Int32;
26✔
1153
            }
1154
            else if (chType == 'i' && nBytes == 8)
334✔
1155
            {
1156
                elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
15✔
1157
                eDT = GDT_Int64;
15✔
1158
            }
1159
            else if (chType == 'u' && nBytes == 2)
319✔
1160
            {
1161
                elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
29✔
1162
                eDT = GDT_UInt16;
29✔
1163
            }
1164
            else if (chType == 'u' && nBytes == 4)
290✔
1165
            {
1166
                elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
18✔
1167
                eDT = GDT_UInt32;
18✔
1168
            }
1169
            else if (chType == 'u' && nBytes == 8)
272✔
1170
            {
1171
                elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
14✔
1172
                eDT = GDT_UInt64;
14✔
1173
            }
1174
            else if (chType == 'f' && nBytes == 2)
258✔
1175
            {
1176
                // elt.nativeType = DtypeElt::NativeType::IEEEFP;
1177
                // elt.gdalTypeIsApproxOfNative = true;
1178
                // eDT = GDT_Float32;
1179
                elt.nativeType = DtypeElt::NativeType::IEEEFP;
3✔
1180
                eDT = GDT_Float16;
3✔
1181
            }
1182
            else if (chType == 'f' && nBytes == 4)
255✔
1183
            {
1184
                elt.nativeType = DtypeElt::NativeType::IEEEFP;
44✔
1185
                eDT = GDT_Float32;
44✔
1186
            }
1187
            else if (chType == 'f' && nBytes == 8)
211✔
1188
            {
1189
                elt.nativeType = DtypeElt::NativeType::IEEEFP;
173✔
1190
                eDT = GDT_Float64;
173✔
1191
            }
1192
            else if (chType == 'c' && nBytes == 8)
38✔
1193
            {
1194
                elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
11✔
1195
                eDT = GDT_CFloat32;
11✔
1196
            }
1197
            else if (chType == 'c' && nBytes == 16)
27✔
1198
            {
1199
                elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
12✔
1200
                eDT = GDT_CFloat64;
12✔
1201
            }
1202
            else if (chType == 'S')
15✔
1203
            {
1204
                elt.nativeType = DtypeElt::NativeType::STRING_ASCII;
9✔
1205
                elt.gdalType = GDALExtendedDataType::CreateString(nBytes);
9✔
1206
                elt.gdalSize = elt.gdalType.GetSize();
9✔
1207
                elts.emplace_back(elt);
9✔
1208
                return GDALExtendedDataType::CreateString(nBytes);
9✔
1209
            }
1210
            else if (chType == 'U')
6✔
1211
            {
1212
                elt.nativeType = DtypeElt::NativeType::STRING_UNICODE;
5✔
1213
                // the dtype declaration is number of UCS4 characters. Store it
1214
                // as bytes
1215
                elt.nativeSize *= 4;
5✔
1216
                // We can really map UCS4 size to UTF-8
1217
                elt.gdalType = GDALExtendedDataType::CreateString();
5✔
1218
                elt.gdalSize = elt.gdalType.GetSize();
5✔
1219
                elts.emplace_back(elt);
5✔
1220
                return GDALExtendedDataType::CreateString();
5✔
1221
            }
1222
            else
1223
                break;
1✔
1224
            elt.gdalType = GDALExtendedDataType::Create(eDT);
661✔
1225
            elt.gdalSize = elt.gdalType.GetSize();
661✔
1226
            elts.emplace_back(elt);
661✔
1227
            return GDALExtendedDataType::Create(eDT);
661✔
1228
        }
1229
        else if (obj.GetType() == CPLJSONObject::Type::Array)
11✔
1230
        {
1231
            bool error = false;
9✔
1232
            const auto oArray = obj.ToArray();
9✔
1233
            std::vector<std::unique_ptr<GDALEDTComponent>> comps;
9✔
1234
            size_t offset = 0;
9✔
1235
            size_t alignmentMax = 1;
9✔
1236
            for (const auto &oElt : oArray)
29✔
1237
            {
1238
                const auto oEltArray = oElt.ToArray();
20✔
1239
                if (!oEltArray.IsValid() || oEltArray.Size() != 2 ||
40✔
1240
                    oEltArray[0].GetType() != CPLJSONObject::Type::String)
40✔
1241
                {
1242
                    error = true;
×
1243
                    break;
×
1244
                }
1245
                GDALExtendedDataType subDT = ParseDtype(oEltArray[1], elts);
20✔
1246
                if (subDT.GetClass() == GEDTC_NUMERIC &&
35✔
1247
                    subDT.GetNumericDataType() == GDT_Unknown)
15✔
1248
                {
1249
                    error = true;
×
1250
                    break;
×
1251
                }
1252

1253
                const std::string osName = oEltArray[0].ToString();
40✔
1254
                // Add padding for alignment
1255
                const size_t alignmentSub = GetAlignment(oEltArray[1]);
20✔
1256
                assert(alignmentSub);
20✔
1257
                alignmentMax = std::max(alignmentMax, alignmentSub);
20✔
1258
                offset = AlignOffsetOn(offset, alignmentSub);
20✔
1259
                comps.emplace_back(std::unique_ptr<GDALEDTComponent>(
40✔
1260
                    new GDALEDTComponent(osName, offset, subDT)));
40✔
1261
                offset += subDT.GetSize();
20✔
1262
            }
1263
            if (error)
9✔
1264
                break;
×
1265
            size_t nTotalSize = offset;
9✔
1266
            nTotalSize = AlignOffsetOn(nTotalSize, alignmentMax);
9✔
1267
            return GDALExtendedDataType::Create(obj.ToString(), nTotalSize,
18✔
1268
                                                std::move(comps));
18✔
1269
        }
1270
    } while (false);
1271
    CPLError(CE_Failure, CPLE_AppDefined,
8✔
1272
             "Invalid or unsupported format for dtype: %s",
1273
             obj.ToString().c_str());
16✔
1274
    return GDALExtendedDataType::Create(GDT_Unknown);
8✔
1275
}
1276

1277
static void SetGDALOffset(const GDALExtendedDataType &dt,
684✔
1278
                          const size_t nBaseOffset, std::vector<DtypeElt> &elts,
1279
                          size_t &iCurElt)
1280
{
1281
    if (dt.GetClass() == GEDTC_COMPOUND)
684✔
1282
    {
1283
        const auto &comps = dt.GetComponents();
9✔
1284
        for (const auto &comp : comps)
29✔
1285
        {
1286
            const size_t nBaseOffsetSub = nBaseOffset + comp->GetOffset();
20✔
1287
            SetGDALOffset(comp->GetType(), nBaseOffsetSub, elts, iCurElt);
20✔
1288
        }
1289
    }
1290
    else
1291
    {
1292
        elts[iCurElt].gdalOffset = nBaseOffset;
675✔
1293
        iCurElt++;
675✔
1294
    }
1295
}
684✔
1296

1297
/************************************************************************/
1298
/*                     ZarrV2Group::LoadArray()                         */
1299
/************************************************************************/
1300

1301
std::shared_ptr<ZarrArray>
1302
ZarrV2Group::LoadArray(const std::string &osArrayName,
690✔
1303
                       const std::string &osZarrayFilename,
1304
                       const CPLJSONObject &oRoot, bool bLoadedFromZMetadata,
1305
                       const CPLJSONObject &oAttributesIn) const
1306
{
1307
    // Add osZarrayFilename to m_poSharedResource during the scope
1308
    // of this function call.
1309
    ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
690✔
1310
                                                       osZarrayFilename);
1,380✔
1311
    if (!filenameAdder.ok())
690✔
1312
        return nullptr;
2✔
1313

1314
    const auto osFormat = oRoot["zarr_format"].ToString();
2,064✔
1315
    if (osFormat != "2")
688✔
1316
    {
1317
        CPLError(CE_Failure, CPLE_NotSupported,
3✔
1318
                 "Invalid value for zarr_format: %s", osFormat.c_str());
1319
        return nullptr;
3✔
1320
    }
1321

1322
    bool bFortranOrder = false;
685✔
1323
    const char *orderKey = "order";
685✔
1324
    const auto osOrder = oRoot[orderKey].ToString();
2,055✔
1325
    if (osOrder == "C")
685✔
1326
    {
1327
        // ok
1328
    }
1329
    else if (osOrder == "F")
33✔
1330
    {
1331
        bFortranOrder = true;
30✔
1332
    }
1333
    else
1334
    {
1335
        CPLError(CE_Failure, CPLE_NotSupported, "Invalid value for %s",
3✔
1336
                 orderKey);
1337
        return nullptr;
3✔
1338
    }
1339

1340
    const auto oShape = oRoot["shape"].ToArray();
2,046✔
1341
    if (!oShape.IsValid())
682✔
1342
    {
1343
        CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
3✔
1344
        return nullptr;
3✔
1345
    }
1346

1347
    const char *chunksKey = "chunks";
679✔
1348
    const auto oChunks = oRoot[chunksKey].ToArray();
2,037✔
1349
    if (!oChunks.IsValid())
679✔
1350
    {
1351
        CPLError(CE_Failure, CPLE_AppDefined, "%s missing or not an array",
3✔
1352
                 chunksKey);
1353
        return nullptr;
3✔
1354
    }
1355

1356
    if (oShape.Size() != oChunks.Size())
676✔
1357
    {
1358
        CPLError(CE_Failure, CPLE_AppDefined,
2✔
1359
                 "shape and chunks arrays are of different size");
1360
        return nullptr;
2✔
1361
    }
1362

1363
    CPLJSONObject oAttributes(oAttributesIn);
1,348✔
1364
    if (!bLoadedFromZMetadata)
674✔
1365
    {
1366
        CPLJSONDocument oDoc;
782✔
1367
        const std::string osZattrsFilename(CPLFormFilenameSafe(
1368
            CPLGetDirnameSafe(osZarrayFilename.c_str()).c_str(), ".zattrs",
391✔
1369
            nullptr));
782✔
1370
        CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
782✔
1371
        if (oDoc.Load(osZattrsFilename))
391✔
1372
        {
1373
            oAttributes = oDoc.GetRoot();
148✔
1374
        }
1375
    }
1376

1377
    // Deep-clone of oAttributes
1378
    {
1379
        CPLJSONDocument oTmpDoc;
674✔
1380
        oTmpDoc.SetRoot(oAttributes);
674✔
1381
        CPL_IGNORE_RET_VAL(oTmpDoc.LoadMemory(oTmpDoc.SaveAsString()));
674✔
1382
        oAttributes = oTmpDoc.GetRoot();
674✔
1383
    }
1384

1385
    std::vector<std::shared_ptr<GDALDimension>> aoDims;
1,348✔
1386
    for (int i = 0; i < oShape.Size(); ++i)
1,721✔
1387
    {
1388
        const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
1,048✔
1389
        if (nSize == 0)
1,048✔
1390
        {
1391
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
1✔
1392
            return nullptr;
1✔
1393
        }
1394
        aoDims.emplace_back(std::make_shared<ZarrDimension>(
1,047✔
1395
            m_poSharedResource,
1,047✔
1396
            std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
2,094✔
1397
            std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
2,094✔
1398
            nSize));
1,047✔
1399
    }
1400

1401
    // XArray extension
1402
    const auto arrayDimensionsObj = oAttributes["_ARRAY_DIMENSIONS"];
2,019✔
1403

1404
    const auto FindDimension =
1405
        [this, &aoDims, bLoadedFromZMetadata, &osArrayName,
511✔
1406
         &oAttributes](const std::string &osDimName,
1407
                       std::shared_ptr<GDALDimension> &poDim, int i)
5,822✔
1408
    {
1409
        auto oIter = m_oMapDimensions.find(osDimName);
511✔
1410
        if (oIter != m_oMapDimensions.end())
511✔
1411
        {
1412
            if (m_bDimSizeInUpdate ||
248✔
1413
                oIter->second->GetSize() == poDim->GetSize())
123✔
1414
            {
1415
                poDim = oIter->second;
125✔
1416
                return true;
125✔
1417
            }
1418
            else
1419
            {
1420
                CPLError(CE_Warning, CPLE_AppDefined,
×
1421
                         "Size of _ARRAY_DIMENSIONS[%d] different "
1422
                         "from the one of shape",
1423
                         i);
1424
                return false;
×
1425
            }
1426
        }
1427

1428
        // Try to load the indexing variable.
1429

1430
        // If loading from zmetadata, we should have normally
1431
        // already loaded the dimension variables, unless they
1432
        // are in a upper level.
1433
        if (bLoadedFromZMetadata && osArrayName != osDimName &&
568✔
1434
            m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
568✔
1435
        {
1436
            auto poParent = m_poParent.lock();
182✔
1437
            while (poParent != nullptr)
196✔
1438
            {
1439
                oIter = poParent->m_oMapDimensions.find(osDimName);
21✔
1440
                if (oIter != poParent->m_oMapDimensions.end() &&
28✔
1441
                    oIter->second->GetSize() == poDim->GetSize())
7✔
1442
                {
1443
                    poDim = oIter->second;
7✔
1444
                    return true;
7✔
1445
                }
1446
                poParent = poParent->m_poParent.lock();
14✔
1447
            }
1448
        }
1449

1450
        // Not loading from zmetadata, and not in m_oMapMDArrays,
1451
        // then stat() the indexing variable.
1452
        else if (!bLoadedFromZMetadata && osArrayName != osDimName &&
278✔
1453
                 m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
278✔
1454
        {
1455
            std::string osDirName = m_osDirectoryName;
148✔
1456
            while (true)
1457
            {
1458
                const std::string osArrayFilenameDim = CPLFormFilenameSafe(
1459
                    CPLFormFilenameSafe(osDirName.c_str(), osDimName.c_str(),
160✔
1460
                                        nullptr)
1461
                        .c_str(),
1462
                    ".zarray", nullptr);
160✔
1463
                VSIStatBufL sStat;
1464
                if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
160✔
1465
                {
1466
                    CPLJSONDocument oDoc;
106✔
1467
                    if (oDoc.Load(osArrayFilenameDim))
53✔
1468
                    {
1469
                        LoadArray(osDimName, osArrayFilenameDim, oDoc.GetRoot(),
53✔
1470
                                  false, CPLJSONObject());
106✔
1471
                    }
1472
                }
1473
                else
1474
                {
1475
                    // Recurse to upper level for datasets such as
1476
                    // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
1477
                    std::string osDirNameNew =
1478
                        CPLGetPathSafe(osDirName.c_str());
107✔
1479
                    if (!osDirNameNew.empty() && osDirNameNew != osDirName)
107✔
1480
                    {
1481
                        osDirName = std::move(osDirNameNew);
86✔
1482
                        continue;
86✔
1483
                    }
1484
                }
1485
                break;
74✔
1486
            }
86✔
1487
        }
1488

1489
        oIter = m_oMapDimensions.find(osDimName);
379✔
1490
        if (oIter != m_oMapDimensions.end() &&
398✔
1491
            oIter->second->GetSize() == poDim->GetSize())
19✔
1492
        {
1493
            poDim = oIter->second;
19✔
1494
            return true;
19✔
1495
        }
1496

1497
        std::string osType;
720✔
1498
        std::string osDirection;
720✔
1499
        if (aoDims.size() == 1 && osArrayName == osDimName)
360✔
1500
        {
1501
            ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
130✔
1502
                                                 osDirection);
1503
        }
1504

1505
        auto poDimLocal = std::make_shared<ZarrDimension>(
1506
            m_poSharedResource,
360✔
1507
            std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
720✔
1508
            GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
720✔
1509
        poDimLocal->SetXArrayDimension();
360✔
1510
        m_oMapDimensions[osDimName] = poDimLocal;
360✔
1511
        poDim = poDimLocal;
360✔
1512
        return true;
360✔
1513
    };
673✔
1514

1515
    if (arrayDimensionsObj.GetType() == CPLJSONObject::Type::Array)
673✔
1516
    {
1517
        const auto arrayDims = arrayDimensionsObj.ToArray();
684✔
1518
        if (arrayDims.Size() == oShape.Size())
342✔
1519
        {
1520
            bool ok = true;
342✔
1521
            for (int i = 0; i < oShape.Size(); ++i)
853✔
1522
            {
1523
                if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
511✔
1524
                {
1525
                    const auto osDimName = arrayDims[i].ToString();
1,022✔
1526
                    ok &= FindDimension(osDimName, aoDims[i], i);
511✔
1527
                }
1528
            }
1529
            if (ok)
342✔
1530
            {
1531
                oAttributes.Delete("_ARRAY_DIMENSIONS");
342✔
1532
            }
1533
        }
1534
        else
1535
        {
1536
            CPLError(
×
1537
                CE_Warning, CPLE_AppDefined,
1538
                "Size of _ARRAY_DIMENSIONS different from the one of shape");
1539
        }
1540
    }
1541

1542
    // _NCZARR_ARRAY extension
1543
    const auto nczarrArrayDimrefs = oRoot["_NCZARR_ARRAY"]["dimrefs"].ToArray();
2,019✔
1544
    if (nczarrArrayDimrefs.IsValid())
673✔
1545
    {
1546
        const auto arrayDims = nczarrArrayDimrefs.ToArray();
42✔
1547
        if (arrayDims.Size() == oShape.Size())
21✔
1548
        {
1549
            auto poRG =
1550
                std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock());
42✔
1551
            CPLAssert(poRG != nullptr);
21✔
1552
            while (true)
1553
            {
1554
                auto poNewRG = poRG->m_poParent.lock();
48✔
1555
                if (poNewRG == nullptr)
48✔
1556
                    break;
21✔
1557
                poRG = std::move(poNewRG);
27✔
1558
            }
27✔
1559

1560
            for (int i = 0; i < oShape.Size(); ++i)
49✔
1561
            {
1562
                if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
28✔
1563
                {
1564
                    const auto osDimFullpath = arrayDims[i].ToString();
84✔
1565
                    const std::string osArrayFullname =
1566
                        (GetFullName() != "/" ? GetFullName() : std::string()) +
56✔
1567
                        '/' + osArrayName;
56✔
1568
                    if (aoDims.size() == 1 &&
48✔
1569
                        (osDimFullpath == osArrayFullname ||
20✔
1570
                         osDimFullpath == "/" + osArrayFullname))
34✔
1571
                    {
1572
                        // If this is an indexing variable, then fetch the
1573
                        // dimension type and direction, and patch the dimension
1574
                        std::string osType;
28✔
1575
                        std::string osDirection;
28✔
1576
                        ZarrArray::GetDimensionTypeDirection(
14✔
1577
                            oAttributes, osType, osDirection);
1578

1579
                        auto poDimLocal = std::make_shared<ZarrDimension>(
1580
                            m_poSharedResource,
14✔
1581
                            std::dynamic_pointer_cast<ZarrGroupBase>(
28✔
1582
                                m_pSelf.lock()),
14✔
1583
                            GetFullName(), osArrayName, osType, osDirection,
14✔
1584
                            aoDims[i]->GetSize());
28✔
1585
                        aoDims[i] = poDimLocal;
14✔
1586

1587
                        m_oMapDimensions[osArrayName] = std::move(poDimLocal);
14✔
1588
                    }
1589
                    else if (auto poDim =
14✔
1590
                                 poRG->OpenDimensionFromFullname(osDimFullpath))
28✔
1591
                    {
1592
                        if (poDim->GetSize() != aoDims[i]->GetSize())
13✔
1593
                        {
1594
                            CPLError(CE_Failure, CPLE_AppDefined,
1✔
1595
                                     "Inconsistency in size between NCZarr "
1596
                                     "dimension %s and regular dimension",
1597
                                     osDimFullpath.c_str());
1598
                        }
1599
                        else
1600
                        {
1601
                            aoDims[i] = std::move(poDim);
12✔
1602
                        }
1603
                    }
1604
                    else
1605
                    {
1606
                        CPLError(CE_Failure, CPLE_AppDefined,
1✔
1607
                                 "Cannot find NCZarr dimension %s",
1608
                                 osDimFullpath.c_str());
1609
                    }
1610
                }
1611
            }
1612
        }
1613
        else
1614
        {
1615
            CPLError(CE_Warning, CPLE_AppDefined,
×
1616
                     "Size of _NCZARR_ARRAY.dimrefs different from the one of "
1617
                     "shape");
1618
        }
1619
    }
1620

1621
    constexpr const char *dtypeKey = "dtype";
673✔
1622
    auto oDtype = oRoot[dtypeKey];
2,019✔
1623
    if (!oDtype.IsValid())
673✔
1624
    {
1625
        CPLError(CE_Failure, CPLE_NotSupported, "%s missing", dtypeKey);
1✔
1626
        return nullptr;
1✔
1627
    }
1628
    std::vector<DtypeElt> aoDtypeElts;
1,344✔
1629
    const auto oType = ParseDtype(oDtype, aoDtypeElts);
1,344✔
1630
    if (oType.GetClass() == GEDTC_NUMERIC &&
1,326✔
1631
        oType.GetNumericDataType() == GDT_Unknown)
654✔
1632
        return nullptr;
8✔
1633
    size_t iCurElt = 0;
664✔
1634
    SetGDALOffset(oType, 0, aoDtypeElts, iCurElt);
664✔
1635

1636
    std::vector<GUInt64> anBlockSize;
1,328✔
1637
    if (!ZarrArray::ParseChunkSize(oChunks, oType, anBlockSize))
664✔
1638
        return nullptr;
2✔
1639

1640
    std::string osDimSeparator = oRoot["dimension_separator"].ToString();
1,986✔
1641
    if (osDimSeparator.empty())
662✔
1642
        osDimSeparator = ".";
652✔
1643

1644
    std::vector<GByte> abyNoData;
1,324✔
1645

1646
    struct NoDataFreer
1647
    {
1648
        std::vector<GByte> &m_abyNodata;
1649
        const GDALExtendedDataType &m_oType;
1650

1651
        NoDataFreer(std::vector<GByte> &abyNoDataIn,
662✔
1652
                    const GDALExtendedDataType &oTypeIn)
1653
            : m_abyNodata(abyNoDataIn), m_oType(oTypeIn)
662✔
1654
        {
1655
        }
662✔
1656

1657
        ~NoDataFreer()
662✔
1658
        {
662✔
1659
            if (!m_abyNodata.empty())
662✔
1660
                m_oType.FreeDynamicMemory(&m_abyNodata[0]);
112✔
1661
        }
662✔
1662
    };
1663

1664
    NoDataFreer NoDataFreer(abyNoData, oType);
1,324✔
1665

1666
    auto oFillValue = oRoot["fill_value"];
1,986✔
1667
    auto eFillValueType = oFillValue.GetType();
662✔
1668

1669
    // Normally arrays are not supported, but that's what NCZarr 4.8.0 outputs
1670
    if (eFillValueType == CPLJSONObject::Type::Array &&
663✔
1671
        oFillValue.ToArray().Size() == 1)
663✔
1672
    {
1673
        oFillValue = oFillValue.ToArray()[0];
×
1674
        eFillValueType = oFillValue.GetType();
×
1675
    }
1676

1677
    if (!oFillValue.IsValid())
662✔
1678
    {
1679
        // fill_value is normally required but some implementations
1680
        // are lacking it: https://github.com/Unidata/netcdf-c/issues/2059
1681
        CPLError(CE_Warning, CPLE_AppDefined, "fill_value missing");
1✔
1682
    }
1683
    else if (eFillValueType == CPLJSONObject::Type::Null)
661✔
1684
    {
1685
        // Nothing to do
1686
    }
1687
    else if (eFillValueType == CPLJSONObject::Type::String)
120✔
1688
    {
1689
        const auto osFillValue = oFillValue.ToString();
98✔
1690
        if (oType.GetClass() == GEDTC_NUMERIC &&
86✔
1691
            CPLGetValueType(osFillValue.c_str()) != CPL_VALUE_STRING)
37✔
1692
        {
1693
            abyNoData.resize(oType.GetSize());
8✔
1694
            // Be tolerant with numeric values serialized as strings.
1695
            if (oType.GetNumericDataType() == GDT_Int64)
8✔
1696
            {
1697
                const int64_t nVal = static_cast<int64_t>(
1698
                    std::strtoll(osFillValue.c_str(), nullptr, 10));
2✔
1699
                GDALCopyWords(&nVal, GDT_Int64, 0, &abyNoData[0],
2✔
1700
                              oType.GetNumericDataType(), 0, 1);
1701
            }
1702
            else if (oType.GetNumericDataType() == GDT_UInt64)
6✔
1703
            {
1704
                const uint64_t nVal = static_cast<uint64_t>(
1705
                    std::strtoull(osFillValue.c_str(), nullptr, 10));
2✔
1706
                GDALCopyWords(&nVal, GDT_UInt64, 0, &abyNoData[0],
2✔
1707
                              oType.GetNumericDataType(), 0, 1);
1708
            }
1709
            else
1710
            {
1711
                const double dfNoDataValue = CPLAtof(osFillValue.c_str());
4✔
1712
                GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
4✔
1713
                              oType.GetNumericDataType(), 0, 1);
1714
            }
1715
        }
1716
        else if (oType.GetClass() == GEDTC_NUMERIC)
41✔
1717
        {
1718
            double dfNoDataValue;
1719
            if (osFillValue == "NaN")
29✔
1720
            {
1721
                dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
10✔
1722
            }
1723
            else if (osFillValue == "Infinity")
19✔
1724
            {
1725
                dfNoDataValue = std::numeric_limits<double>::infinity();
9✔
1726
            }
1727
            else if (osFillValue == "-Infinity")
10✔
1728
            {
1729
                dfNoDataValue = -std::numeric_limits<double>::infinity();
9✔
1730
            }
1731
            else
1732
            {
1733
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1✔
1734
                return nullptr;
2✔
1735
            }
1736
            if (oType.GetNumericDataType() == GDT_Float16)
28✔
1737
            {
1738
                const GFloat16 hfNoDataValue =
1739
                    static_cast<GFloat16>(dfNoDataValue);
×
1740
                abyNoData.resize(sizeof(hfNoDataValue));
×
1741
                memcpy(&abyNoData[0], &hfNoDataValue, sizeof(hfNoDataValue));
×
1742
            }
1743
            if (oType.GetNumericDataType() == GDT_Float32)
28✔
1744
            {
1745
                const float fNoDataValue = static_cast<float>(dfNoDataValue);
12✔
1746
                abyNoData.resize(sizeof(fNoDataValue));
12✔
1747
                memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
12✔
1748
            }
1749
            else if (oType.GetNumericDataType() == GDT_Float64)
16✔
1750
            {
1751
                abyNoData.resize(sizeof(dfNoDataValue));
15✔
1752
                memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
15✔
1753
            }
1754
            else
1755
            {
1756
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1✔
1757
                return nullptr;
1✔
1758
            }
1759
        }
1760
        else if (oType.GetClass() == GEDTC_STRING)
12✔
1761
        {
1762
            // zarr.open('unicode_be.zarr', mode = 'w', shape=(1,), dtype =
1763
            // '>U1', compressor = None) oddly generates "fill_value": "0"
1764
            if (osFillValue != "0")
7✔
1765
            {
1766
                std::vector<GByte> abyNativeFillValue(osFillValue.size() + 1);
3✔
1767
                memcpy(&abyNativeFillValue[0], osFillValue.data(),
3✔
1768
                       osFillValue.size());
1769
                int nBytes = CPLBase64DecodeInPlace(&abyNativeFillValue[0]);
3✔
1770
                abyNativeFillValue.resize(nBytes + 1);
3✔
1771
                abyNativeFillValue[nBytes] = 0;
3✔
1772
                abyNoData.resize(oType.GetSize());
3✔
1773
                char *pDstStr = CPLStrdup(
3✔
1774
                    reinterpret_cast<const char *>(&abyNativeFillValue[0]));
3✔
1775
                char **pDstPtr = reinterpret_cast<char **>(&abyNoData[0]);
3✔
1776
                memcpy(pDstPtr, &pDstStr, sizeof(pDstStr));
3✔
1777
            }
1778
        }
1779
        else
1780
        {
1781
            std::vector<GByte> abyNativeFillValue(osFillValue.size() + 1);
5✔
1782
            memcpy(&abyNativeFillValue[0], osFillValue.data(),
5✔
1783
                   osFillValue.size());
1784
            int nBytes = CPLBase64DecodeInPlace(&abyNativeFillValue[0]);
5✔
1785
            abyNativeFillValue.resize(nBytes);
5✔
1786
            if (abyNativeFillValue.size() !=
5✔
1787
                aoDtypeElts.back().nativeOffset + aoDtypeElts.back().nativeSize)
5✔
1788
            {
1789
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
×
1790
                return nullptr;
×
1791
            }
1792
            abyNoData.resize(oType.GetSize());
5✔
1793
            ZarrArray::DecodeSourceElt(aoDtypeElts, abyNativeFillValue.data(),
5✔
1794
                                       &abyNoData[0]);
5✔
1795
        }
1796
    }
1797
    else if (eFillValueType == CPLJSONObject::Type::Boolean ||
71✔
1798
             eFillValueType == CPLJSONObject::Type::Integer ||
20✔
1799
             eFillValueType == CPLJSONObject::Type::Long ||
12✔
1800
             eFillValueType == CPLJSONObject::Type::Double)
1801
    {
1802
        if (oType.GetClass() == GEDTC_NUMERIC)
70✔
1803
        {
1804
            const double dfNoDataValue = oFillValue.ToDouble();
69✔
1805
            if (oType.GetNumericDataType() == GDT_Int64)
69✔
1806
            {
1807
                const int64_t nNoDataValue =
1808
                    static_cast<int64_t>(oFillValue.ToLong());
2✔
1809
                abyNoData.resize(oType.GetSize());
2✔
1810
                GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
2✔
1811
                              oType.GetNumericDataType(), 0, 1);
1812
            }
1813
            else if (oType.GetNumericDataType() == GDT_UInt64 &&
70✔
1814
                     /* we can't really deal with nodata value between */
1815
                     /* int64::max and uint64::max due to json-c limitations */
1816
                     dfNoDataValue >= 0)
3✔
1817
            {
1818
                const int64_t nNoDataValue =
1819
                    static_cast<int64_t>(oFillValue.ToLong());
3✔
1820
                abyNoData.resize(oType.GetSize());
3✔
1821
                GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
3✔
1822
                              oType.GetNumericDataType(), 0, 1);
1823
            }
1824
            else
1825
            {
1826
                abyNoData.resize(oType.GetSize());
64✔
1827
                GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
64✔
1828
                              oType.GetNumericDataType(), 0, 1);
1829
            }
1830
        }
1831
        else
1832
        {
1833
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1✔
1834
            return nullptr;
1✔
1835
        }
69✔
1836
    }
1837
    else
1838
    {
1839
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1✔
1840
        return nullptr;
1✔
1841
    }
1842

1843
    const CPLCompressor *psCompressor = nullptr;
658✔
1844
    const CPLCompressor *psDecompressor = nullptr;
658✔
1845
    const auto oCompressor = oRoot["compressor"];
1,974✔
1846
    std::string osDecompressorId("NONE");
1,316✔
1847

1848
    if (!oCompressor.IsValid())
658✔
1849
    {
1850
        CPLError(CE_Failure, CPLE_AppDefined, "compressor missing");
1✔
1851
        return nullptr;
1✔
1852
    }
1853
    if (oCompressor.GetType() == CPLJSONObject::Type::Null)
657✔
1854
    {
1855
        // nothing to do
1856
    }
1857
    else if (oCompressor.GetType() == CPLJSONObject::Type::Object)
37✔
1858
    {
1859
        osDecompressorId = oCompressor["id"].ToString();
36✔
1860
        if (osDecompressorId.empty())
36✔
1861
        {
1862
            CPLError(CE_Failure, CPLE_AppDefined, "Missing compressor id");
1✔
1863
            return nullptr;
1✔
1864
        }
1865
        if (osDecompressorId == "imagecodecs_tiff")
35✔
1866
        {
1867
            psDecompressor = ZarrGetTIFFDecompressor();
5✔
1868
        }
1869
        else
1870
        {
1871
            psCompressor = CPLGetCompressor(osDecompressorId.c_str());
30✔
1872
            psDecompressor = CPLGetDecompressor(osDecompressorId.c_str());
30✔
1873
            if (psCompressor == nullptr || psDecompressor == nullptr)
30✔
1874
            {
1875
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
1876
                         "Decompressor %s not handled",
1877
                         osDecompressorId.c_str());
1878
                return nullptr;
1✔
1879
            }
1880
        }
1881
    }
1882
    else
1883
    {
1884
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid compressor");
1✔
1885
        return nullptr;
1✔
1886
    }
1887

1888
    CPLJSONArray oFiltersArray;
1,308✔
1889
    const auto oFilters = oRoot["filters"];
1,962✔
1890
    if (!oFilters.IsValid())
654✔
1891
    {
1892
        CPLError(CE_Failure, CPLE_AppDefined, "filters missing");
1✔
1893
        return nullptr;
1✔
1894
    }
1895
    if (oFilters.GetType() == CPLJSONObject::Type::Null)
653✔
1896
    {
1897
    }
1898
    else if (oFilters.GetType() == CPLJSONObject::Type::Array)
43✔
1899
    {
1900
        oFiltersArray = oFilters.ToArray();
41✔
1901
        for (const auto &oFilter : oFiltersArray)
54✔
1902
        {
1903
            const auto osFilterId = oFilter["id"].ToString();
28✔
1904
            if (osFilterId.empty())
14✔
1905
            {
1906
                CPLError(CE_Failure, CPLE_AppDefined, "Missing filter id");
1✔
1907
                return nullptr;
1✔
1908
            }
1909
            if (!EQUAL(osFilterId.c_str(), "shuffle") &&
13✔
1910
                !EQUAL(osFilterId.c_str(), "quantize") &&
21✔
1911
                !EQUAL(osFilterId.c_str(), "fixedscaleoffset"))
8✔
1912
            {
1913
                const auto psFilterCompressor =
1914
                    CPLGetCompressor(osFilterId.c_str());
4✔
1915
                const auto psFilterDecompressor =
1916
                    CPLGetDecompressor(osFilterId.c_str());
4✔
1917
                if (psFilterCompressor == nullptr ||
4✔
1918
                    psFilterDecompressor == nullptr)
1919
                {
1920
                    CPLError(CE_Failure, CPLE_AppDefined,
×
1921
                             "Filter %s not handled", osFilterId.c_str());
1922
                    return nullptr;
×
1923
                }
1924
            }
1925
        }
1926
    }
1927
    else
1928
    {
1929
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid filters");
2✔
1930
        return nullptr;
2✔
1931
    }
1932

1933
    auto poArray = ZarrV2Array::Create(m_poSharedResource, GetFullName(),
650✔
1934
                                       osArrayName, aoDims, oType, aoDtypeElts,
1935
                                       anBlockSize, bFortranOrder);
1,300✔
1936
    if (!poArray)
650✔
1937
        return nullptr;
1✔
1938
    poArray->SetCompressorJson(oCompressor);
649✔
1939
    poArray->SetUpdatable(m_bUpdatable);  // must be set before SetAttributes()
649✔
1940
    poArray->SetFilename(osZarrayFilename);
649✔
1941
    poArray->SetDimSeparator(osDimSeparator);
649✔
1942
    poArray->SetCompressorDecompressor(osDecompressorId, psCompressor,
649✔
1943
                                       psDecompressor);
1944
    poArray->SetFilters(oFiltersArray);
649✔
1945
    if (!abyNoData.empty())
649✔
1946
    {
1947
        poArray->RegisterNoDataValue(abyNoData.data());
112✔
1948
    }
1949

1950
    const auto gridMapping = oAttributes["grid_mapping"];
1,947✔
1951
    if (gridMapping.GetType() == CPLJSONObject::Type::String)
649✔
1952
    {
1953
        const std::string gridMappingName = gridMapping.ToString();
3✔
1954
        if (m_oMapMDArrays.find(gridMappingName) == m_oMapMDArrays.end())
1✔
1955
        {
1956
            const std::string osArrayFilenameDim = CPLFormFilenameSafe(
1957
                CPLFormFilenameSafe(m_osDirectoryName.c_str(),
1✔
1958
                                    gridMappingName.c_str(), nullptr)
1959
                    .c_str(),
1960
                ".zarray", nullptr);
2✔
1961
            VSIStatBufL sStat;
1962
            if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
1✔
1963
            {
1964
                CPLJSONDocument oDoc;
2✔
1965
                if (oDoc.Load(osArrayFilenameDim))
1✔
1966
                {
1967
                    LoadArray(gridMappingName, osArrayFilenameDim,
1✔
1968
                              oDoc.GetRoot(), false, CPLJSONObject());
2✔
1969
                }
1970
            }
1971
        }
1972
    }
1973

1974
    poArray->ParseSpecialAttributes(m_pSelf.lock(), oAttributes);
649✔
1975
    poArray->SetAttributes(oAttributes);
649✔
1976
    poArray->SetDtype(oDtype);
649✔
1977
    RegisterArray(poArray);
649✔
1978

1979
    // If this is an indexing variable, attach it to the dimension.
1980
    if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
649✔
1981
    {
1982
        auto oIter = m_oMapDimensions.find(poArray->GetName());
144✔
1983
        if (oIter != m_oMapDimensions.end())
144✔
1984
        {
1985
            oIter->second->SetIndexingVariable(poArray);
144✔
1986
        }
1987
    }
1988

1989
    if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
649✔
1990
            "CACHE_TILE_PRESENCE", "NO")))
1991
    {
1992
        poArray->CacheTilePresence();
2✔
1993
    }
1994

1995
    return poArray;
649✔
1996
}
1997

1998
/************************************************************************/
1999
/*                    ZarrV2Group::SetCompressorJson()                  */
2000
/************************************************************************/
2001

2002
void ZarrV2Array::SetCompressorJson(const CPLJSONObject &oCompressor)
666✔
2003
{
2004
    m_oCompressorJSon = oCompressor;
666✔
2005
    if (oCompressor.GetType() != CPLJSONObject::Type::Null)
666✔
2006
        m_aosStructuralInfo.SetNameValue("COMPRESSOR",
2007
                                         oCompressor.ToString().c_str());
51✔
2008
}
666✔
2009

2010
/************************************************************************/
2011
/*                     ZarrV2Group::SetFilters()                        */
2012
/************************************************************************/
2013

2014
void ZarrV2Array::SetFilters(const CPLJSONArray &oFiltersArray)
955✔
2015
{
2016
    m_oFiltersArray = oFiltersArray;
955✔
2017
    if (oFiltersArray.Size() > 0)
955✔
2018
        m_aosStructuralInfo.SetNameValue("FILTERS",
2019
                                         oFiltersArray.ToString().c_str());
14✔
2020
}
955✔
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