Coverage Report

Created: 2026-09-26 08:22

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