Coverage Report

Created: 2026-02-14 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/zarr/zarr_v3_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_enumerate.h"
14
#include "cpl_float.h"
15
#include "cpl_vsi_virtual.h"
16
#include "cpl_worker_thread_pool.h"
17
#include "gdal_thread_pool.h"
18
#include "zarr.h"
19
#include "zarr_v3_codec.h"
20
21
#include <algorithm>
22
#include <cassert>
23
#include <cinttypes>
24
#include <cmath>
25
#include <cstdlib>
26
#include <limits>
27
#include <map>
28
#include <set>
29
30
/************************************************************************/
31
/*                      ZarrV3Array::ZarrV3Array()                      */
32
/************************************************************************/
33
34
ZarrV3Array::ZarrV3Array(
35
    const std::shared_ptr<ZarrSharedResource> &poSharedResource,
36
    const std::shared_ptr<ZarrGroupBase> &poParent, const std::string &osName,
37
    const std::vector<std::shared_ptr<GDALDimension>> &aoDims,
38
    const GDALExtendedDataType &oType, const std::vector<DtypeElt> &aoDtypeElts,
39
    const std::vector<GUInt64> &anOuterBlockSize,
40
    const std::vector<GUInt64> &anInnerBlockSize)
41
0
    : GDALAbstractMDArray(poParent->GetFullName(), osName),
42
0
      ZarrArray(poSharedResource, poParent, osName, aoDims, oType, aoDtypeElts,
43
0
                anOuterBlockSize, anInnerBlockSize)
44
0
{
45
0
}
Unexecuted instantiation: ZarrV3Array::ZarrV3Array(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&, std::__1::vector<unsigned long long, std::__1::allocator<unsigned long long> > const&)
Unexecuted instantiation: ZarrV3Array::ZarrV3Array(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&, std::__1::vector<unsigned long long, std::__1::allocator<unsigned long long> > const&)
46
47
/************************************************************************/
48
/*                        ZarrV3Array::Create()                         */
49
/************************************************************************/
50
51
std::shared_ptr<ZarrV3Array> ZarrV3Array::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> &anOuterBlockSize,
57
    const std::vector<GUInt64> &anInnerBlockSize)
58
0
{
59
0
    auto arr = std::shared_ptr<ZarrV3Array>(
60
0
        new ZarrV3Array(poSharedResource, poParent, osName, aoDims, oType,
61
0
                        aoDtypeElts, anOuterBlockSize, anInnerBlockSize));
62
0
    if (arr->m_nTotalInnerChunkCount == 0)
63
0
        return nullptr;
64
0
    arr->SetSelf(arr);
65
66
0
    return arr;
67
0
}
68
69
/************************************************************************/
70
/*                            ~ZarrV3Array()                            */
71
/************************************************************************/
72
73
ZarrV3Array::~ZarrV3Array()
74
0
{
75
0
    ZarrV3Array::Flush();
76
0
}
77
78
/************************************************************************/
79
/*                               Flush()                                */
80
/************************************************************************/
81
82
bool ZarrV3Array::Flush()
83
0
{
84
0
    if (!m_bValid)
85
0
        return true;
86
87
0
    bool ret = ZarrV3Array::FlushDirtyBlock();
88
89
0
    if (!m_aoDims.empty())
90
0
    {
91
0
        for (const auto &poDim : m_aoDims)
92
0
        {
93
0
            const auto poZarrDim =
94
0
                dynamic_cast<const ZarrDimension *>(poDim.get());
95
0
            if (poZarrDim && poZarrDim->IsXArrayDimension())
96
0
            {
97
0
                if (poZarrDim->IsModified())
98
0
                    m_bDefinitionModified = true;
99
0
            }
100
0
            else
101
0
            {
102
0
                break;
103
0
            }
104
0
        }
105
0
    }
106
107
0
    CPLJSONObject oAttrs;
108
0
    if (m_oAttrGroup.IsModified() || m_bUnitModified || m_bOffsetModified ||
109
0
        m_bScaleModified || m_bSRSModified)
110
0
    {
111
0
        m_bNew = false;
112
113
0
        oAttrs = SerializeSpecialAttributes();
114
115
0
        m_bDefinitionModified = true;
116
0
    }
117
118
0
    if (m_bDefinitionModified)
119
0
    {
120
0
        if (!Serialize(oAttrs))
121
0
            ret = false;
122
0
        m_bDefinitionModified = false;
123
0
    }
124
125
0
    return ret;
126
0
}
127
128
/************************************************************************/
129
/*                       ZarrV3Array::Serialize()                       */
130
/************************************************************************/
131
132
bool ZarrV3Array::Serialize(const CPLJSONObject &oAttrs)
133
0
{
134
0
    CPLJSONDocument oDoc;
135
0
    CPLJSONObject oRoot = oDoc.GetRoot();
136
137
0
    oRoot.Add("zarr_format", 3);
138
0
    oRoot.Add("node_type", "array");
139
140
0
    CPLJSONArray oShape;
141
0
    for (const auto &poDim : m_aoDims)
142
0
    {
143
0
        oShape.Add(static_cast<GInt64>(poDim->GetSize()));
144
0
    }
145
0
    oRoot.Add("shape", oShape);
146
147
0
    oRoot.Add("data_type", m_dtype.ToString());
148
149
0
    {
150
0
        CPLJSONObject oChunkGrid;
151
0
        oRoot.Add("chunk_grid", oChunkGrid);
152
0
        oChunkGrid.Add("name", "regular");
153
0
        CPLJSONObject oConfiguration;
154
0
        oChunkGrid.Add("configuration", oConfiguration);
155
0
        CPLJSONArray oChunks;
156
0
        for (const auto nBlockSize : m_anOuterBlockSize)
157
0
        {
158
0
            oChunks.Add(static_cast<GInt64>(nBlockSize));
159
0
        }
160
0
        oConfiguration.Add("chunk_shape", oChunks);
161
0
    }
162
163
0
    {
164
0
        CPLJSONObject oChunkKeyEncoding;
165
0
        oRoot.Add("chunk_key_encoding", oChunkKeyEncoding);
166
0
        oChunkKeyEncoding.Add("name", m_bV2ChunkKeyEncoding ? "v2" : "default");
167
0
        CPLJSONObject oConfiguration;
168
0
        oChunkKeyEncoding.Add("configuration", oConfiguration);
169
0
        oConfiguration.Add("separator", m_osDimSeparator);
170
0
    }
171
172
0
    if (m_pabyNoData == nullptr)
173
0
    {
174
0
        if (m_oType.GetNumericDataType() == GDT_Float16 ||
175
0
            m_oType.GetNumericDataType() == GDT_Float32 ||
176
0
            m_oType.GetNumericDataType() == GDT_Float64)
177
0
        {
178
0
            oRoot.Add("fill_value", "NaN");
179
0
        }
180
0
        else
181
0
        {
182
0
            oRoot.AddNull("fill_value");
183
0
        }
184
0
    }
185
0
    else
186
0
    {
187
0
        if (m_oType.GetNumericDataType() == GDT_CFloat16 ||
188
0
            m_oType.GetNumericDataType() == GDT_CFloat32 ||
189
0
            m_oType.GetNumericDataType() == GDT_CFloat64)
190
0
        {
191
0
            double adfNoDataValue[2];
192
0
            GDALCopyWords(m_pabyNoData, m_oType.GetNumericDataType(), 0,
193
0
                          adfNoDataValue, GDT_CFloat64, 0, 1);
194
0
            CPLJSONArray oArray;
195
0
            for (int i = 0; i < 2; ++i)
196
0
            {
197
0
                if (std::isnan(adfNoDataValue[i]))
198
0
                    oArray.Add("NaN");
199
0
                else if (adfNoDataValue[i] ==
200
0
                         std::numeric_limits<double>::infinity())
201
0
                    oArray.Add("Infinity");
202
0
                else if (adfNoDataValue[i] ==
203
0
                         -std::numeric_limits<double>::infinity())
204
0
                    oArray.Add("-Infinity");
205
0
                else
206
0
                    oArray.Add(adfNoDataValue[i]);
207
0
            }
208
0
            oRoot.Add("fill_value", oArray);
209
0
        }
210
0
        else
211
0
        {
212
0
            SerializeNumericNoData(oRoot);
213
0
        }
214
0
    }
215
216
0
    if (m_poCodecs)
217
0
    {
218
0
        oRoot.Add("codecs", m_poCodecs->GetJSon());
219
0
    }
220
221
0
    oRoot.Add("attributes", oAttrs);
222
223
    // Set dimension_names
224
0
    if (!m_aoDims.empty())
225
0
    {
226
0
        CPLJSONArray oDimensions;
227
0
        for (const auto &poDim : m_aoDims)
228
0
        {
229
0
            const auto poZarrDim =
230
0
                dynamic_cast<const ZarrDimension *>(poDim.get());
231
0
            if (poZarrDim && poZarrDim->IsXArrayDimension())
232
0
            {
233
0
                oDimensions.Add(poDim->GetName());
234
0
            }
235
0
            else
236
0
            {
237
0
                oDimensions = CPLJSONArray();
238
0
                break;
239
0
            }
240
0
        }
241
0
        if (oDimensions.Size() > 0)
242
0
        {
243
0
            oRoot.Add("dimension_names", oDimensions);
244
0
        }
245
0
    }
246
247
    // TODO: codecs
248
249
0
    const bool bRet = oDoc.Save(m_osFilename);
250
0
    if (bRet)
251
0
        m_poSharedResource->SetZMetadataItem(m_osFilename, oRoot);
252
0
    return bRet;
253
0
}
254
255
/************************************************************************/
256
/*                   ZarrV3Array::NeedDecodedBuffer()                   */
257
/************************************************************************/
258
259
bool ZarrV3Array::NeedDecodedBuffer() const
260
0
{
261
0
    for (const auto &elt : m_aoDtypeElts)
262
0
    {
263
0
        if (elt.needByteSwapping || elt.gdalTypeIsApproxOfNative)
264
0
        {
265
0
            return true;
266
0
        }
267
0
    }
268
0
    return false;
269
0
}
270
271
/************************************************************************/
272
/*                ZarrV3Array::AllocateWorkingBuffers()                 */
273
/************************************************************************/
274
275
bool ZarrV3Array::AllocateWorkingBuffers() const
276
0
{
277
0
    if (m_bAllocateWorkingBuffersDone)
278
0
        return m_bWorkingBuffersOK;
279
280
0
    m_bAllocateWorkingBuffersDone = true;
281
282
0
    size_t nSizeNeeded = m_nInnerBlockSizeBytes;
283
0
    if (NeedDecodedBuffer())
284
0
    {
285
0
        size_t nDecodedBufferSize = m_oType.GetSize();
286
0
        for (const auto &nBlockSize : m_anInnerBlockSize)
287
0
        {
288
0
            if (nDecodedBufferSize > std::numeric_limits<size_t>::max() /
289
0
                                         static_cast<size_t>(nBlockSize))
290
0
            {
291
0
                CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
292
0
                return false;
293
0
            }
294
0
            nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
295
0
        }
296
0
        if (nSizeNeeded >
297
0
            std::numeric_limits<size_t>::max() - nDecodedBufferSize)
298
0
        {
299
0
            CPLError(CE_Failure, CPLE_AppDefined, "Too large chunk size");
300
0
            return false;
301
0
        }
302
0
        nSizeNeeded += nDecodedBufferSize;
303
0
    }
304
305
    // Reserve a buffer for tile content
306
0
    if (nSizeNeeded > 1024 * 1024 * 1024 &&
307
0
        !CPLTestBool(CPLGetConfigOption("ZARR_ALLOW_BIG_TILE_SIZE", "NO")))
308
0
    {
309
0
        CPLError(CE_Failure, CPLE_AppDefined,
310
0
                 "Zarr tile allocation would require " CPL_FRMT_GUIB " bytes. "
311
0
                 "By default the driver limits to 1 GB. To allow that memory "
312
0
                 "allocation, set the ZARR_ALLOW_BIG_TILE_SIZE configuration "
313
0
                 "option to YES.",
314
0
                 static_cast<GUIntBig>(nSizeNeeded));
315
0
        return false;
316
0
    }
317
318
0
    m_bWorkingBuffersOK =
319
0
        AllocateWorkingBuffers(m_abyRawBlockData, m_abyDecodedBlockData);
320
0
    return m_bWorkingBuffersOK;
321
0
}
322
323
bool ZarrV3Array::AllocateWorkingBuffers(
324
    ZarrByteVectorQuickResize &abyRawBlockData,
325
    ZarrByteVectorQuickResize &abyDecodedBlockData) const
326
0
{
327
    // This method should NOT modify any ZarrArray member, as it is going to
328
    // be called concurrently from several threads.
329
330
    // Set those #define to avoid accidental use of some global variables
331
0
#define m_abyRawBlockData cannot_use_here
332
0
#define m_abyDecodedBlockData cannot_use_here
333
334
0
    const size_t nSizeNeeded = m_nInnerBlockSizeBytes;
335
0
    try
336
0
    {
337
0
        abyRawBlockData.resize(nSizeNeeded);
338
0
    }
339
0
    catch (const std::bad_alloc &e)
340
0
    {
341
0
        CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
342
0
        return false;
343
0
    }
344
345
0
    if (NeedDecodedBuffer())
346
0
    {
347
0
        size_t nDecodedBufferSize = m_oType.GetSize();
348
0
        for (const auto &nBlockSize : m_anInnerBlockSize)
349
0
        {
350
0
            nDecodedBufferSize *= static_cast<size_t>(nBlockSize);
351
0
        }
352
0
        try
353
0
        {
354
0
            abyDecodedBlockData.resize(nDecodedBufferSize);
355
0
        }
356
0
        catch (const std::bad_alloc &e)
357
0
        {
358
0
            CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
359
0
            return false;
360
0
        }
361
0
    }
362
363
0
    return true;
364
0
#undef m_abyRawBlockData
365
0
#undef m_abyDecodedBlockData
366
0
}
367
368
/************************************************************************/
369
/*                     ZarrV3Array::LoadBlockData()                     */
370
/************************************************************************/
371
372
bool ZarrV3Array::LoadBlockData(const uint64_t *blockIndices,
373
                                bool &bMissingBlockOut) const
374
0
{
375
0
    return LoadBlockData(blockIndices,
376
0
                         false,  // use mutex
377
0
                         m_poCodecs.get(), m_abyRawBlockData,
378
0
                         m_abyDecodedBlockData, bMissingBlockOut);
379
0
}
380
381
bool ZarrV3Array::LoadBlockData(const uint64_t *blockIndices, bool bUseMutex,
382
                                ZarrV3CodecSequence *poCodecs,
383
                                ZarrByteVectorQuickResize &abyRawBlockData,
384
                                ZarrByteVectorQuickResize &abyDecodedBlockData,
385
                                bool &bMissingBlockOut) const
386
0
{
387
    // This method should NOT modify any ZarrArray member, as it is going to
388
    // be called concurrently from several threads.
389
390
    // Set those #define to avoid accidental use of some global variables
391
0
#define m_abyRawBlockData cannot_use_here
392
0
#define m_abyDecodedBlockData cannot_use_here
393
0
#define m_poCodecs cannot_use_here
394
395
0
    bMissingBlockOut = false;
396
397
0
    std::string osFilename;
398
0
    if (poCodecs && poCodecs->SupportsPartialDecoding())
399
0
    {
400
0
        std::vector<uint64_t> outerChunkIndices;
401
0
        for (size_t i = 0; i < GetDimensionCount(); ++i)
402
0
        {
403
            // Note: m_anOuterBlockSize[i]/m_anInnerBlockSize[i] is an integer
404
0
            outerChunkIndices.push_back(blockIndices[i] *
405
0
                                        m_anInnerBlockSize[i] /
406
0
                                        m_anOuterBlockSize[i]);
407
0
        }
408
409
0
        osFilename = BuildChunkFilename(outerChunkIndices.data());
410
0
    }
411
0
    else
412
0
    {
413
0
        osFilename = BuildChunkFilename(blockIndices);
414
0
    }
415
416
    // For network file systems, get the streaming version of the filename,
417
    // as we don't need arbitrary seeking in the file
418
    // ... unless we do partial decoding, in which case range requests within
419
    // a shard are much more efficient
420
0
    if (!(poCodecs && poCodecs->SupportsPartialDecoding()))
421
0
    {
422
0
        osFilename = VSIFileManager::GetHandler(osFilename.c_str())
423
0
                         ->GetStreamingFilename(osFilename);
424
0
    }
425
426
    // First if we have a tile presence cache, check tile presence from it
427
0
    bool bEarlyRet;
428
0
    if (bUseMutex)
429
0
    {
430
0
        std::lock_guard<std::mutex> oLock(m_oMutex);
431
0
        bEarlyRet = IsBlockMissingFromCacheInfo(osFilename, blockIndices);
432
0
    }
433
0
    else
434
0
    {
435
0
        bEarlyRet = IsBlockMissingFromCacheInfo(osFilename, blockIndices);
436
0
    }
437
0
    if (bEarlyRet)
438
0
    {
439
0
        bMissingBlockOut = true;
440
0
        return true;
441
0
    }
442
0
    VSIVirtualHandleUniquePtr fp;
443
    // This is the number of files returned in a S3 directory listing operation
444
0
    constexpr uint64_t MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING = 1000;
445
0
    const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
446
0
                                           nullptr};
447
0
    const auto nErrorBefore = CPLGetErrorCounter();
448
0
    if ((m_osDimSeparator == "/" && !m_anOuterBlockSize.empty() &&
449
0
         m_anOuterBlockSize.back() > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING) ||
450
0
        (m_osDimSeparator != "/" &&
451
0
         m_nTotalInnerChunkCount > MAX_TILES_ALLOWED_FOR_DIRECTORY_LISTING))
452
0
    {
453
        // Avoid issuing ReadDir() when a lot of files are expected
454
0
        CPLConfigOptionSetter optionSetter("GDAL_DISABLE_READDIR_ON_OPEN",
455
0
                                           "YES", true);
456
0
        fp.reset(VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions));
457
0
    }
458
0
    else
459
0
    {
460
0
        fp.reset(VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions));
461
0
    }
462
0
    if (fp == nullptr)
463
0
    {
464
0
        if (nErrorBefore != CPLGetErrorCounter())
465
0
        {
466
0
            return false;
467
0
        }
468
0
        else
469
0
        {
470
            // Missing files are OK and indicate nodata_value
471
0
            CPLDebugOnly(ZARR_DEBUG_KEY, "Block %s missing (=nodata)",
472
0
                         osFilename.c_str());
473
0
            bMissingBlockOut = true;
474
0
            return true;
475
0
        }
476
0
    }
477
478
0
    bMissingBlockOut = false;
479
480
0
    if (poCodecs && poCodecs->SupportsPartialDecoding())
481
0
    {
482
0
        std::vector<size_t> anStartIdx;
483
0
        std::vector<size_t> anCount;
484
0
        for (size_t i = 0; i < GetDimensionCount(); ++i)
485
0
        {
486
0
            anStartIdx.push_back(
487
0
                static_cast<size_t>((blockIndices[i] * m_anInnerBlockSize[i]) %
488
0
                                    m_anOuterBlockSize[i]));
489
0
            anCount.push_back(static_cast<size_t>(m_anInnerBlockSize[i]));
490
0
        }
491
0
        if (!poCodecs->DecodePartial(fp.get(), abyRawBlockData, anStartIdx,
492
0
                                     anCount))
493
0
            return false;
494
0
    }
495
0
    else
496
0
    {
497
0
        CPLAssert(abyRawBlockData.capacity() >= m_nInnerBlockSizeBytes);
498
        // should not fail
499
0
        abyRawBlockData.resize(m_nInnerBlockSizeBytes);
500
501
0
        bool bRet = true;
502
0
        size_t nRawDataSize = abyRawBlockData.size();
503
0
        if (poCodecs == nullptr)
504
0
        {
505
0
            nRawDataSize = fp->Read(&abyRawBlockData[0], 1, nRawDataSize);
506
0
        }
507
0
        else
508
0
        {
509
0
            fp->Seek(0, SEEK_END);
510
0
            const auto nSize = fp->Tell();
511
0
            fp->Seek(0, SEEK_SET);
512
0
            if (nSize >
513
0
                static_cast<vsi_l_offset>(std::numeric_limits<int>::max()))
514
0
            {
515
0
                CPLError(CE_Failure, CPLE_AppDefined, "Too large tile %s",
516
0
                         osFilename.c_str());
517
0
                bRet = false;
518
0
            }
519
0
            else
520
0
            {
521
0
                try
522
0
                {
523
0
                    abyRawBlockData.resize(static_cast<size_t>(nSize));
524
0
                }
525
0
                catch (const std::exception &)
526
0
                {
527
0
                    CPLError(CE_Failure, CPLE_OutOfMemory,
528
0
                             "Cannot allocate memory for tile %s",
529
0
                             osFilename.c_str());
530
0
                    bRet = false;
531
0
                }
532
533
0
                if (bRet &&
534
0
                    (abyRawBlockData.empty() ||
535
0
                     fp->Read(&abyRawBlockData[0], 1, abyRawBlockData.size()) !=
536
0
                         abyRawBlockData.size()))
537
0
                {
538
0
                    CPLError(CE_Failure, CPLE_AppDefined,
539
0
                             "Could not read tile %s correctly",
540
0
                             osFilename.c_str());
541
0
                    bRet = false;
542
0
                }
543
0
                else
544
0
                {
545
0
                    if (!poCodecs->Decode(abyRawBlockData))
546
0
                    {
547
0
                        CPLError(CE_Failure, CPLE_AppDefined,
548
0
                                 "Decompression of tile %s failed",
549
0
                                 osFilename.c_str());
550
0
                        bRet = false;
551
0
                    }
552
0
                }
553
0
            }
554
0
        }
555
0
        if (!bRet)
556
0
            return false;
557
558
0
        if (nRawDataSize != abyRawBlockData.size())
559
0
        {
560
0
            CPLError(CE_Failure, CPLE_AppDefined,
561
0
                     "Decompressed tile %s has not expected size. "
562
0
                     "Got %u instead of %u",
563
0
                     osFilename.c_str(),
564
0
                     static_cast<unsigned>(abyRawBlockData.size()),
565
0
                     static_cast<unsigned>(nRawDataSize));
566
0
            return false;
567
0
        }
568
0
    }
569
570
0
    if (!abyDecodedBlockData.empty())
571
0
    {
572
0
        const size_t nSourceSize =
573
0
            m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
574
0
        const auto nDTSize = m_oType.GetSize();
575
0
        const size_t nValues = abyDecodedBlockData.size() / nDTSize;
576
0
        CPLAssert(nValues == m_nInnerBlockSizeBytes / nSourceSize);
577
0
        const GByte *pSrc = abyRawBlockData.data();
578
0
        GByte *pDst = &abyDecodedBlockData[0];
579
0
        for (size_t i = 0; i < nValues;
580
0
             i++, pSrc += nSourceSize, pDst += nDTSize)
581
0
        {
582
0
            DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
583
0
        }
584
0
    }
585
586
0
    return true;
587
588
0
#undef m_abyRawBlockData
589
0
#undef m_abyDecodedBlockData
590
0
#undef m_poCodecs
591
0
}
592
593
/************************************************************************/
594
/*                         ZarrV3Array::IRead()                         */
595
/************************************************************************/
596
597
bool ZarrV3Array::IRead(const GUInt64 *arrayStartIdx, const size_t *count,
598
                        const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
599
                        const GDALExtendedDataType &bufferDataType,
600
                        void *pDstBuffer) const
601
0
{
602
    // For sharded arrays, pre-populate the block cache via ReadMultiRange()
603
    // so that the base-class block-by-block loop hits memory, not HTTP.
604
0
    if (m_poCodecs && m_poCodecs->SupportsPartialDecoding())
605
0
    {
606
0
        PreloadShardedBlocks(arrayStartIdx, count);
607
0
    }
608
0
    return ZarrArray::IRead(arrayStartIdx, count, arrayStep, bufferStride,
609
0
                            bufferDataType, pDstBuffer);
610
0
}
611
612
/************************************************************************/
613
/*                 ZarrV3Array::PreloadShardedBlocks()                  */
614
/************************************************************************/
615
616
void ZarrV3Array::PreloadShardedBlocks(const GUInt64 *arrayStartIdx,
617
                                       const size_t *count) const
618
0
{
619
0
    const size_t nDims = m_aoDims.size();
620
0
    if (nDims == 0)
621
0
        return;
622
623
    // Calculate needed block index range
624
0
    std::vector<uint64_t> anBlockMin(nDims), anBlockMax(nDims);
625
0
    size_t nTotalBlocks = 1;
626
0
    for (size_t i = 0; i < nDims; ++i)
627
0
    {
628
0
        anBlockMin[i] = arrayStartIdx[i] / m_anInnerBlockSize[i];
629
0
        anBlockMax[i] =
630
0
            (arrayStartIdx[i] + count[i] - 1) / m_anInnerBlockSize[i];
631
0
        nTotalBlocks *= static_cast<size_t>(anBlockMax[i] - anBlockMin[i] + 1);
632
0
    }
633
634
0
    if (nTotalBlocks <= 1)
635
0
        return;  // single block — no batching benefit
636
637
0
    CPLDebugOnly("ZARR", "PreloadShardedBlocks: %" PRIu64 " blocks to batch",
638
0
                 static_cast<uint64_t>(nTotalBlocks));
639
640
    // Enumerate all needed blocks, grouped by shard filename
641
0
    struct BlockInfo
642
0
    {
643
0
        std::vector<uint64_t> anBlockIndices{};
644
0
        std::vector<size_t> anStartIdx{};
645
0
        std::vector<size_t> anCount{};
646
0
    };
647
648
0
    std::map<std::string, std::vector<BlockInfo>> oShardToBlocks;
649
650
    // Iterate over all needed block indices
651
0
    std::vector<uint64_t> anCur(nDims);
652
0
    size_t dimIdx = 0;
653
0
lbl_next:
654
0
    if (dimIdx == nDims)
655
0
    {
656
        // Skip blocks already in cache
657
0
        const std::vector<uint64_t> cacheKey(anCur.begin(), anCur.end());
658
0
        if (m_oChunkCache.find(cacheKey) == m_oChunkCache.end())
659
0
        {
660
            // Compute shard filename and inner chunk start/count
661
0
            std::vector<uint64_t> outerIdx(nDims);
662
0
            BlockInfo info;
663
0
            info.anBlockIndices = anCur;
664
0
            info.anStartIdx.resize(nDims);
665
0
            info.anCount.resize(nDims);
666
0
            for (size_t i = 0; i < nDims; ++i)
667
0
            {
668
0
                outerIdx[i] =
669
0
                    anCur[i] * m_anInnerBlockSize[i] / m_anOuterBlockSize[i];
670
0
                info.anStartIdx[i] = static_cast<size_t>(
671
0
                    (anCur[i] * m_anInnerBlockSize[i]) % m_anOuterBlockSize[i]);
672
0
                info.anCount[i] = static_cast<size_t>(m_anInnerBlockSize[i]);
673
0
            }
674
675
0
            std::string osFilename = BuildChunkFilename(outerIdx.data());
676
0
            oShardToBlocks[osFilename].push_back(std::move(info));
677
0
        }
678
0
    }
679
0
    else
680
0
    {
681
0
        anCur[dimIdx] = anBlockMin[dimIdx];
682
0
        while (true)
683
0
        {
684
0
            dimIdx++;
685
0
            goto lbl_next;
686
0
        lbl_return:
687
0
            dimIdx--;
688
0
            if (anCur[dimIdx] == anBlockMax[dimIdx])
689
0
                break;
690
0
            ++anCur[dimIdx];
691
0
        }
692
0
    }
693
0
    if (dimIdx > 0)
694
0
        goto lbl_return;
695
696
    // For each shard with >1 uncached block, batch-read
697
0
    const char *const apszOpenOptions[] = {"IGNORE_FILENAME_RESTRICTIONS=YES",
698
0
                                           nullptr};
699
700
0
    for (auto &[osFilename, aBlocks] : oShardToBlocks)
701
0
    {
702
0
        if (aBlocks.size() <= 1)
703
0
            continue;
704
705
0
        VSIVirtualHandleUniquePtr fp(
706
0
            VSIFOpenEx2L(osFilename.c_str(), "rb", 0, apszOpenOptions));
707
0
        if (!fp)
708
0
            continue;
709
710
        // Build request list
711
0
        std::vector<std::pair<std::vector<size_t>, std::vector<size_t>>>
712
0
            anRequests;
713
0
        anRequests.reserve(aBlocks.size());
714
0
        for (const auto &info : aBlocks)
715
0
        {
716
0
            anRequests.push_back({info.anStartIdx, info.anCount});
717
0
        }
718
719
0
        std::vector<ZarrByteVectorQuickResize> aResults;
720
0
        if (!m_poCodecs->BatchDecodePartial(fp.get(), anRequests, aResults))
721
0
            continue;
722
723
        // Store results in block cache
724
0
        const bool bNeedDecode = NeedDecodedBuffer();
725
0
        for (size_t i = 0; i < aBlocks.size(); ++i)
726
0
        {
727
0
            if (aResults[i].empty())
728
0
            {
729
0
                CachedBlock cachedBlock;
730
0
                m_oChunkCache[aBlocks[i].anBlockIndices] =
731
0
                    std::move(cachedBlock);
732
0
                continue;
733
0
            }
734
735
0
            CachedBlock cachedBlock;
736
0
            if (bNeedDecode)
737
0
            {
738
0
                const size_t nSourceSize = m_aoDtypeElts.back().nativeOffset +
739
0
                                           m_aoDtypeElts.back().nativeSize;
740
0
                const auto nGDALDTSize = m_oType.GetSize();
741
0
                const size_t nValues = aResults[i].size() / nSourceSize;
742
0
                ZarrByteVectorQuickResize abyDecoded;
743
0
                abyDecoded.resize(nValues * nGDALDTSize);
744
0
                const GByte *pSrc = aResults[i].data();
745
0
                GByte *pDst = abyDecoded.data();
746
0
                for (size_t v = 0; v < nValues;
747
0
                     v++, pSrc += nSourceSize, pDst += nGDALDTSize)
748
0
                {
749
0
                    DecodeSourceElt(m_aoDtypeElts, pSrc, pDst);
750
0
                }
751
0
                std::swap(cachedBlock.abyDecoded, abyDecoded);
752
0
            }
753
0
            else
754
0
            {
755
0
                std::swap(cachedBlock.abyDecoded, aResults[i]);
756
0
            }
757
0
            m_oChunkCache[aBlocks[i].anBlockIndices] = std::move(cachedBlock);
758
0
        }
759
0
    }
760
0
}
761
762
/************************************************************************/
763
/*                      ZarrV3Array::IAdviseRead()                      */
764
/************************************************************************/
765
766
bool ZarrV3Array::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 ZarrV3Array *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
        std::unique_ptr<ZarrV3CodecSequence> poCodecs;
840
0
        if (poArray->m_poCodecs)
841
0
        {
842
0
            std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
843
0
            poCodecs = poArray->m_poCodecs->Clone();
844
0
        }
845
846
0
        for (size_t iReq = jobStruct->nFirstIdx;
847
0
             iReq < jobStruct->nLastIdxNotIncluded; ++iReq)
848
0
        {
849
            // Check if we must early exit
850
0
            {
851
0
                std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
852
0
                if (!(*jobStruct->pbGlobalStatus))
853
0
                    return;
854
0
            }
855
856
0
            const uint64_t *blockIndices =
857
0
                jobStruct->panReqBlocksIndices->data() + iReq * l_nDims;
858
859
0
            if (!poArray->AllocateWorkingBuffers(abyRawBlockData,
860
0
                                                 abyDecodedBlockData))
861
0
            {
862
0
                std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
863
0
                *jobStruct->pbGlobalStatus = false;
864
0
                break;
865
0
            }
866
867
0
            bool bIsEmpty = false;
868
0
            bool success = poArray->LoadBlockData(
869
0
                blockIndices,
870
0
                true,  // use mutex
871
0
                poCodecs.get(), abyRawBlockData, abyDecodedBlockData, bIsEmpty);
872
873
0
            std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
874
0
            if (!success)
875
0
            {
876
0
                *jobStruct->pbGlobalStatus = false;
877
0
                break;
878
0
            }
879
880
0
            CachedBlock cachedBlock;
881
0
            if (!bIsEmpty)
882
0
            {
883
0
                if (!abyDecodedBlockData.empty())
884
0
                    std::swap(cachedBlock.abyDecoded, abyDecodedBlockData);
885
0
                else
886
0
                    std::swap(cachedBlock.abyDecoded, abyRawBlockData);
887
0
            }
888
0
            const std::vector<uint64_t> cacheKey{blockIndices,
889
0
                                                 blockIndices + l_nDims};
890
0
            poArray->m_oChunkCache[cacheKey] = std::move(cachedBlock);
891
0
        }
892
893
0
        std::lock_guard<std::mutex> oLock(poArray->m_oMutex);
894
0
        (*jobStruct->pnRemainingThreads)--;
895
0
    };
896
897
    // Start jobs
898
0
    for (int i = 0; i < nThreads; i++)
899
0
    {
900
0
        if (!wtp->SubmitJob(JobFunc, &asJobStructs[i]))
901
0
        {
902
0
            std::lock_guard<std::mutex> oLock(m_oMutex);
903
0
            bGlobalStatus = false;
904
0
            nRemainingThreads = i;
905
0
            break;
906
0
        }
907
0
    }
908
909
    // Wait for all jobs to be finished
910
0
    while (true)
911
0
    {
912
0
        {
913
0
            std::lock_guard<std::mutex> oLock(m_oMutex);
914
0
            if (nRemainingThreads == 0)
915
0
                break;
916
0
        }
917
0
        wtp->WaitEvent();
918
0
    }
919
920
0
    return bGlobalStatus;
921
0
}
922
923
/************************************************************************/
924
/*                    ZarrV3Array::FlushDirtyBlock()                    */
925
/************************************************************************/
926
927
bool ZarrV3Array::FlushDirtyBlock() const
928
0
{
929
0
    if (!m_bDirtyBlock)
930
0
        return true;
931
0
    m_bDirtyBlock = false;
932
933
0
    std::string osFilename = BuildChunkFilename(m_anCachedBlockIndices.data());
934
935
0
    const size_t nSourceSize =
936
0
        m_aoDtypeElts.back().nativeOffset + m_aoDtypeElts.back().nativeSize;
937
0
    const auto &abyBlock = m_abyDecodedBlockData.empty()
938
0
                               ? m_abyRawBlockData
939
0
                               : m_abyDecodedBlockData;
940
941
0
    if (IsEmptyBlock(abyBlock))
942
0
    {
943
0
        m_bCachedBlockEmpty = true;
944
945
0
        VSIStatBufL sStat;
946
0
        if (VSIStatL(osFilename.c_str(), &sStat) == 0)
947
0
        {
948
0
            CPLDebugOnly(ZARR_DEBUG_KEY,
949
0
                         "Deleting tile %s that has now empty content",
950
0
                         osFilename.c_str());
951
0
            return VSIUnlink(osFilename.c_str()) == 0;
952
0
        }
953
0
        return true;
954
0
    }
955
956
0
    if (!m_abyDecodedBlockData.empty())
957
0
    {
958
0
        const size_t nDTSize = m_oType.GetSize();
959
0
        const size_t nValues = m_abyDecodedBlockData.size() / nDTSize;
960
0
        GByte *pDst = &m_abyRawBlockData[0];
961
0
        const GByte *pSrc = m_abyDecodedBlockData.data();
962
0
        for (size_t i = 0; i < nValues;
963
0
             i++, pDst += nSourceSize, pSrc += nDTSize)
964
0
        {
965
0
            EncodeElt(m_aoDtypeElts, pSrc, pDst);
966
0
        }
967
0
    }
968
969
0
    const size_t nSizeBefore = m_abyRawBlockData.size();
970
0
    if (m_poCodecs)
971
0
    {
972
0
        if (!m_poCodecs->Encode(m_abyRawBlockData))
973
0
        {
974
0
            m_abyRawBlockData.resize(nSizeBefore);
975
0
            return false;
976
0
        }
977
0
    }
978
979
0
    if (m_osDimSeparator == "/")
980
0
    {
981
0
        std::string osDir = CPLGetDirnameSafe(osFilename.c_str());
982
0
        VSIStatBufL sStat;
983
0
        if (VSIStatL(osDir.c_str(), &sStat) != 0)
984
0
        {
985
0
            if (VSIMkdirRecursive(osDir.c_str(), 0755) != 0)
986
0
            {
987
0
                CPLError(CE_Failure, CPLE_AppDefined,
988
0
                         "Cannot create directory %s", osDir.c_str());
989
0
                m_abyRawBlockData.resize(nSizeBefore);
990
0
                return false;
991
0
            }
992
0
        }
993
0
    }
994
995
0
    VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
996
0
    if (fp == nullptr)
997
0
    {
998
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot create tile %s",
999
0
                 osFilename.c_str());
1000
0
        m_abyRawBlockData.resize(nSizeBefore);
1001
0
        return false;
1002
0
    }
1003
1004
0
    bool bRet = true;
1005
0
    const size_t nRawDataSize = m_abyRawBlockData.size();
1006
0
    if (VSIFWriteL(m_abyRawBlockData.data(), 1, nRawDataSize, fp) !=
1007
0
        nRawDataSize)
1008
0
    {
1009
0
        CPLError(CE_Failure, CPLE_AppDefined,
1010
0
                 "Could not write tile %s correctly", osFilename.c_str());
1011
0
        bRet = false;
1012
0
    }
1013
0
    VSIFCloseL(fp);
1014
1015
0
    m_abyRawBlockData.resize(nSizeBefore);
1016
1017
0
    return bRet;
1018
0
}
1019
1020
/************************************************************************/
1021
/*                        ZarrV3Array::IWrite()                         */
1022
/************************************************************************/
1023
1024
bool ZarrV3Array::IWrite(const GUInt64 *arrayStartIdx, const size_t *count,
1025
                         const GInt64 *arrayStep,
1026
                         const GPtrDiff_t *bufferStride,
1027
                         const GDALExtendedDataType &bufferDataType,
1028
                         const void *pSrcBuffer)
1029
0
{
1030
0
    if (m_poCodecs && m_poCodecs->SupportsPartialDecoding())
1031
0
    {
1032
0
        CPLError(CE_Failure, CPLE_NotSupported,
1033
0
                 "Writing to sharded dataset is not supported");
1034
0
        return false;
1035
0
    }
1036
0
    return ZarrArray::IWrite(arrayStartIdx, count, arrayStep, bufferStride,
1037
0
                             bufferDataType, pSrcBuffer);
1038
0
}
1039
1040
/************************************************************************/
1041
/*                         BuildChunkFilename()                         */
1042
/************************************************************************/
1043
1044
std::string ZarrV3Array::BuildChunkFilename(const uint64_t *blockIndices) const
1045
0
{
1046
0
    if (m_aoDims.empty())
1047
0
    {
1048
0
        return CPLFormFilenameSafe(
1049
0
            CPLGetDirnameSafe(m_osFilename.c_str()).c_str(),
1050
0
            m_bV2ChunkKeyEncoding ? "0" : "c", nullptr);
1051
0
    }
1052
0
    else
1053
0
    {
1054
0
        std::string osFilename(CPLGetDirnameSafe(m_osFilename.c_str()));
1055
0
        osFilename += '/';
1056
0
        if (!m_bV2ChunkKeyEncoding)
1057
0
        {
1058
0
            osFilename += 'c';
1059
0
        }
1060
0
        for (size_t i = 0; i < m_aoDims.size(); ++i)
1061
0
        {
1062
0
            if (i > 0 || !m_bV2ChunkKeyEncoding)
1063
0
                osFilename += m_osDimSeparator;
1064
0
            osFilename += std::to_string(blockIndices[i]);
1065
0
        }
1066
0
        return osFilename;
1067
0
    }
1068
0
}
1069
1070
/************************************************************************/
1071
/*                          GetDataDirectory()                          */
1072
/************************************************************************/
1073
1074
std::string ZarrV3Array::GetDataDirectory() const
1075
0
{
1076
0
    return std::string(CPLGetDirnameSafe(m_osFilename.c_str()));
1077
0
}
1078
1079
/************************************************************************/
1080
/*                    GetChunkIndicesFromFilename()                     */
1081
/************************************************************************/
1082
1083
CPLStringList
1084
ZarrV3Array::GetChunkIndicesFromFilename(const char *pszFilename) const
1085
0
{
1086
0
    if (!m_bV2ChunkKeyEncoding)
1087
0
    {
1088
0
        if (pszFilename[0] != 'c')
1089
0
            return CPLStringList();
1090
0
        if (m_osDimSeparator == "/")
1091
0
        {
1092
0
            if (pszFilename[1] != '/' && pszFilename[1] != '\\')
1093
0
                return CPLStringList();
1094
0
        }
1095
0
        else if (pszFilename[1] != m_osDimSeparator[0])
1096
0
        {
1097
0
            return CPLStringList();
1098
0
        }
1099
0
    }
1100
0
    return CPLStringList(
1101
0
        CSLTokenizeString2(pszFilename + (m_bV2ChunkKeyEncoding ? 0 : 2),
1102
0
                           m_osDimSeparator.c_str(), 0));
1103
0
}
1104
1105
/************************************************************************/
1106
/*                            ParseDtypeV3()                            */
1107
/************************************************************************/
1108
1109
static GDALExtendedDataType ParseDtypeV3(const CPLJSONObject &obj,
1110
                                         std::vector<DtypeElt> &elts)
1111
0
{
1112
0
    do
1113
0
    {
1114
0
        if (obj.GetType() == CPLJSONObject::Type::String)
1115
0
        {
1116
0
            const auto str = obj.ToString();
1117
0
            DtypeElt elt;
1118
0
            GDALDataType eDT = GDT_Unknown;
1119
1120
0
            if (str == "bool")  // boolean
1121
0
            {
1122
0
                elt.nativeType = DtypeElt::NativeType::BOOLEAN;
1123
0
                eDT = GDT_UInt8;
1124
0
            }
1125
0
            else if (str == "int8")
1126
0
            {
1127
0
                elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1128
0
                eDT = GDT_Int8;
1129
0
            }
1130
0
            else if (str == "uint8")
1131
0
            {
1132
0
                elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1133
0
                eDT = GDT_UInt8;
1134
0
            }
1135
0
            else if (str == "int16")
1136
0
            {
1137
0
                elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1138
0
                eDT = GDT_Int16;
1139
0
            }
1140
0
            else if (str == "uint16")
1141
0
            {
1142
0
                elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1143
0
                eDT = GDT_UInt16;
1144
0
            }
1145
0
            else if (str == "int32")
1146
0
            {
1147
0
                elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1148
0
                eDT = GDT_Int32;
1149
0
            }
1150
0
            else if (str == "uint32")
1151
0
            {
1152
0
                elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1153
0
                eDT = GDT_UInt32;
1154
0
            }
1155
0
            else if (str == "int64")
1156
0
            {
1157
0
                elt.nativeType = DtypeElt::NativeType::SIGNED_INT;
1158
0
                eDT = GDT_Int64;
1159
0
            }
1160
0
            else if (str == "uint64")
1161
0
            {
1162
0
                elt.nativeType = DtypeElt::NativeType::UNSIGNED_INT;
1163
0
                eDT = GDT_UInt64;
1164
0
            }
1165
0
            else if (str == "float16")
1166
0
            {
1167
0
                elt.nativeType = DtypeElt::NativeType::IEEEFP;
1168
0
                eDT = GDT_Float16;
1169
0
            }
1170
0
            else if (str == "float32")
1171
0
            {
1172
0
                elt.nativeType = DtypeElt::NativeType::IEEEFP;
1173
0
                eDT = GDT_Float32;
1174
0
            }
1175
0
            else if (str == "float64")
1176
0
            {
1177
0
                elt.nativeType = DtypeElt::NativeType::IEEEFP;
1178
0
                eDT = GDT_Float64;
1179
0
            }
1180
0
            else if (str == "complex64")
1181
0
            {
1182
0
                elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
1183
0
                eDT = GDT_CFloat32;
1184
0
            }
1185
0
            else if (str == "complex128")
1186
0
            {
1187
0
                elt.nativeType = DtypeElt::NativeType::COMPLEX_IEEEFP;
1188
0
                eDT = GDT_CFloat64;
1189
0
            }
1190
0
            else
1191
0
                break;
1192
1193
0
            elt.gdalType = GDALExtendedDataType::Create(eDT);
1194
0
            elt.gdalSize = elt.gdalType.GetSize();
1195
0
            if (!elt.gdalTypeIsApproxOfNative)
1196
0
                elt.nativeSize = elt.gdalSize;
1197
1198
0
            if (elt.nativeSize > 1)
1199
0
            {
1200
0
                elt.needByteSwapping = (CPL_IS_LSB == 0);
1201
0
            }
1202
1203
0
            elts.emplace_back(elt);
1204
0
            return GDALExtendedDataType::Create(eDT);
1205
0
        }
1206
0
    } while (false);
1207
0
    CPLError(CE_Failure, CPLE_AppDefined,
1208
0
             "Invalid or unsupported format for data_type: %s",
1209
0
             obj.ToString().c_str());
1210
0
    return GDALExtendedDataType::Create(GDT_Unknown);
1211
0
}
1212
1213
/************************************************************************/
1214
/*                     ParseNoDataStringAsDouble()                      */
1215
/************************************************************************/
1216
1217
static double ParseNoDataStringAsDouble(const std::string &osVal, bool &bOK)
1218
0
{
1219
0
    double dfNoDataValue = std::numeric_limits<double>::quiet_NaN();
1220
0
    if (osVal == "NaN")
1221
0
    {
1222
        // initialized above
1223
0
    }
1224
0
    else if (osVal == "Infinity" || osVal == "+Infinity")
1225
0
    {
1226
0
        dfNoDataValue = std::numeric_limits<double>::infinity();
1227
0
    }
1228
0
    else if (osVal == "-Infinity")
1229
0
    {
1230
0
        dfNoDataValue = -std::numeric_limits<double>::infinity();
1231
0
    }
1232
0
    else
1233
0
    {
1234
0
        bOK = false;
1235
0
    }
1236
0
    return dfNoDataValue;
1237
0
}
1238
1239
/************************************************************************/
1240
/*                        ParseNoDataComponent()                        */
1241
/************************************************************************/
1242
1243
template <typename T, typename Tint>
1244
static T ParseNoDataComponent(const CPLJSONObject &oObj, bool &bOK)
1245
0
{
1246
0
    if (oObj.GetType() == CPLJSONObject::Type::Integer ||
1247
0
        oObj.GetType() == CPLJSONObject::Type::Long ||
1248
0
        oObj.GetType() == CPLJSONObject::Type::Double)
1249
0
    {
1250
0
        return static_cast<T>(oObj.ToDouble());
1251
0
    }
1252
0
    else if (oObj.GetType() == CPLJSONObject::Type::String)
1253
0
    {
1254
0
        const auto osVal = oObj.ToString();
1255
0
        if (STARTS_WITH(osVal.c_str(), "0x"))
1256
0
        {
1257
0
            if (osVal.size() > 2 + 2 * sizeof(T))
1258
0
            {
1259
0
                bOK = false;
1260
0
                return 0;
1261
0
            }
1262
0
            Tint nVal = static_cast<Tint>(
1263
0
                std::strtoull(osVal.c_str() + 2, nullptr, 16));
1264
0
            T fVal;
1265
0
            static_assert(sizeof(nVal) == sizeof(fVal),
1266
0
                          "sizeof(nVal) == sizeof(dfVal)");
1267
0
            memcpy(&fVal, &nVal, sizeof(nVal));
1268
0
            return fVal;
1269
0
        }
1270
0
        else
1271
0
        {
1272
0
            return static_cast<T>(ParseNoDataStringAsDouble(osVal, bOK));
1273
0
        }
1274
0
    }
1275
0
    else
1276
0
    {
1277
0
        bOK = false;
1278
0
        return 0;
1279
0
    }
1280
0
}
Unexecuted instantiation: zarr_v3_array.cpp:double ParseNoDataComponent<double, unsigned long>(CPLJSONObject const&, bool&)
Unexecuted instantiation: zarr_v3_array.cpp:float ParseNoDataComponent<float, unsigned int>(CPLJSONObject const&, bool&)
1281
1282
/************************************************************************/
1283
/*                       ZarrV3Group::LoadArray()                       */
1284
/************************************************************************/
1285
1286
std::shared_ptr<ZarrArray>
1287
ZarrV3Group::LoadArray(const std::string &osArrayName,
1288
                       const std::string &osZarrayFilename,
1289
                       const CPLJSONObject &oRoot) const
1290
0
{
1291
    // Add osZarrayFilename to m_poSharedResource during the scope
1292
    // of this function call.
1293
0
    ZarrSharedResource::SetFilenameAdder filenameAdder(m_poSharedResource,
1294
0
                                                       osZarrayFilename);
1295
0
    if (!filenameAdder.ok())
1296
0
        return nullptr;
1297
1298
    // Warn about unknown members (the spec suggests to error out, but let be
1299
    // a bit more lenient)
1300
0
    for (const auto &oNode : oRoot.GetChildren())
1301
0
    {
1302
0
        const auto osName = oNode.GetName();
1303
0
        if (osName != "zarr_format" && osName != "node_type" &&
1304
0
            osName != "shape" && osName != "chunk_grid" &&
1305
0
            osName != "data_type" && osName != "chunk_key_encoding" &&
1306
0
            osName != "fill_value" &&
1307
            // Below are optional
1308
0
            osName != "dimension_names" && osName != "codecs" &&
1309
0
            osName != "storage_transformers" && osName != "attributes")
1310
0
        {
1311
0
            CPLError(CE_Warning, CPLE_AppDefined,
1312
0
                     "%s array definition contains a unknown member (%s). "
1313
0
                     "Interpretation of the array might be wrong.",
1314
0
                     osZarrayFilename.c_str(), osName.c_str());
1315
0
        }
1316
0
    }
1317
1318
0
    const auto oStorageTransformers = oRoot["storage_transformers"].ToArray();
1319
0
    if (oStorageTransformers.Size() > 0)
1320
0
    {
1321
0
        CPLError(CE_Failure, CPLE_AppDefined,
1322
0
                 "storage_transformers are not supported.");
1323
0
        return nullptr;
1324
0
    }
1325
1326
0
    const auto oShape = oRoot["shape"].ToArray();
1327
0
    if (!oShape.IsValid())
1328
0
    {
1329
0
        CPLError(CE_Failure, CPLE_AppDefined, "shape missing or not an array");
1330
0
        return nullptr;
1331
0
    }
1332
1333
    // Parse chunk_grid
1334
0
    const auto oChunkGrid = oRoot["chunk_grid"];
1335
0
    if (oChunkGrid.GetType() != CPLJSONObject::Type::Object)
1336
0
    {
1337
0
        CPLError(CE_Failure, CPLE_AppDefined,
1338
0
                 "chunk_grid missing or not an object");
1339
0
        return nullptr;
1340
0
    }
1341
1342
0
    const auto oChunkGridName = oChunkGrid["name"];
1343
0
    if (oChunkGridName.ToString() != "regular")
1344
0
    {
1345
0
        CPLError(CE_Failure, CPLE_AppDefined,
1346
0
                 "Only chunk_grid.name = regular supported");
1347
0
        return nullptr;
1348
0
    }
1349
1350
0
    const auto oChunks = oChunkGrid["configuration"]["chunk_shape"].ToArray();
1351
0
    if (!oChunks.IsValid())
1352
0
    {
1353
0
        CPLError(
1354
0
            CE_Failure, CPLE_AppDefined,
1355
0
            "chunk_grid.configuration.chunk_shape missing or not an array");
1356
0
        return nullptr;
1357
0
    }
1358
1359
0
    if (oShape.Size() != oChunks.Size())
1360
0
    {
1361
0
        CPLError(CE_Failure, CPLE_AppDefined,
1362
0
                 "shape and chunks arrays are of different size");
1363
0
        return nullptr;
1364
0
    }
1365
1366
    // Parse chunk_key_encoding
1367
0
    const auto oChunkKeyEncoding = oRoot["chunk_key_encoding"];
1368
0
    if (oChunkKeyEncoding.GetType() != CPLJSONObject::Type::Object)
1369
0
    {
1370
0
        CPLError(CE_Failure, CPLE_AppDefined,
1371
0
                 "chunk_key_encoding missing or not an object");
1372
0
        return nullptr;
1373
0
    }
1374
1375
0
    std::string osDimSeparator;
1376
0
    bool bV2ChunkKeyEncoding = false;
1377
0
    const auto oChunkKeyEncodingName = oChunkKeyEncoding["name"];
1378
0
    if (oChunkKeyEncodingName.ToString() == "default")
1379
0
    {
1380
0
        osDimSeparator = "/";
1381
0
    }
1382
0
    else if (oChunkKeyEncodingName.ToString() == "v2")
1383
0
    {
1384
0
        osDimSeparator = ".";
1385
0
        bV2ChunkKeyEncoding = true;
1386
0
    }
1387
0
    else
1388
0
    {
1389
0
        CPLError(CE_Failure, CPLE_AppDefined,
1390
0
                 "Unsupported chunk_key_encoding.name");
1391
0
        return nullptr;
1392
0
    }
1393
1394
0
    {
1395
0
        auto oConfiguration = oChunkKeyEncoding["configuration"];
1396
0
        if (oConfiguration.GetType() == CPLJSONObject::Type::Object)
1397
0
        {
1398
0
            auto oSeparator = oConfiguration["separator"];
1399
0
            if (oSeparator.IsValid())
1400
0
            {
1401
0
                osDimSeparator = oSeparator.ToString();
1402
0
                if (osDimSeparator != "/" && osDimSeparator != ".")
1403
0
                {
1404
0
                    CPLError(CE_Failure, CPLE_AppDefined,
1405
0
                             "Separator can only be '/' or '.'");
1406
0
                    return nullptr;
1407
0
                }
1408
0
            }
1409
0
        }
1410
0
    }
1411
1412
0
    CPLJSONObject oAttributes = oRoot["attributes"];
1413
1414
    // Deep-clone of oAttributes
1415
0
    if (oAttributes.IsValid())
1416
0
    {
1417
0
        oAttributes = oAttributes.Clone();
1418
0
    }
1419
1420
0
    std::vector<std::shared_ptr<GDALDimension>> aoDims;
1421
0
    for (int i = 0; i < oShape.Size(); ++i)
1422
0
    {
1423
0
        const auto nSize = static_cast<GUInt64>(oShape[i].ToLong());
1424
0
        if (nSize == 0)
1425
0
        {
1426
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid content for shape");
1427
0
            return nullptr;
1428
0
        }
1429
0
        aoDims.emplace_back(std::make_shared<ZarrDimension>(
1430
0
            m_poSharedResource,
1431
0
            std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1432
0
            std::string(), CPLSPrintf("dim%d", i), std::string(), std::string(),
1433
0
            nSize));
1434
0
    }
1435
1436
    // Deal with dimension_names
1437
0
    const auto dimensionNames = oRoot["dimension_names"];
1438
1439
0
    const auto FindDimension = [this, &aoDims, &osArrayName, &oAttributes](
1440
0
                                   const std::string &osDimName,
1441
0
                                   std::shared_ptr<GDALDimension> &poDim, int i)
1442
0
    {
1443
0
        auto oIter = m_oMapDimensions.find(osDimName);
1444
0
        if (oIter != m_oMapDimensions.end())
1445
0
        {
1446
0
            if (m_bDimSizeInUpdate ||
1447
0
                oIter->second->GetSize() == poDim->GetSize())
1448
0
            {
1449
0
                poDim = oIter->second;
1450
0
                return true;
1451
0
            }
1452
0
            else
1453
0
            {
1454
0
                CPLError(CE_Warning, CPLE_AppDefined,
1455
0
                         "Size of _ARRAY_DIMENSIONS[%d] different "
1456
0
                         "from the one of shape",
1457
0
                         i);
1458
0
                return false;
1459
0
            }
1460
0
        }
1461
1462
        // Try to load the indexing variable.
1463
        // Not in m_oMapMDArrays,
1464
        // then stat() the indexing variable.
1465
0
        else if (osArrayName != osDimName &&
1466
0
                 m_oMapMDArrays.find(osDimName) == m_oMapMDArrays.end())
1467
0
        {
1468
0
            std::string osDirName = m_osDirectoryName;
1469
0
            while (true)
1470
0
            {
1471
0
                const std::string osArrayFilenameDim = CPLFormFilenameSafe(
1472
0
                    CPLFormFilenameSafe(osDirName.c_str(), osDimName.c_str(),
1473
0
                                        nullptr)
1474
0
                        .c_str(),
1475
0
                    "zarr.json", nullptr);
1476
0
                VSIStatBufL sStat;
1477
0
                if (VSIStatL(osArrayFilenameDim.c_str(), &sStat) == 0)
1478
0
                {
1479
0
                    CPLJSONDocument oDoc;
1480
0
                    if (oDoc.Load(osArrayFilenameDim))
1481
0
                    {
1482
0
                        LoadArray(osDimName, osArrayFilenameDim,
1483
0
                                  oDoc.GetRoot());
1484
0
                    }
1485
0
                }
1486
0
                else
1487
0
                {
1488
                    // Recurse to upper level for datasets such as
1489
                    // /vsis3/hrrrzarr/sfc/20210809/20210809_00z_anl.zarr/0.1_sigma_level/HAIL_max_fcst/0.1_sigma_level/HAIL_max_fcst
1490
0
                    std::string osDirNameNew =
1491
0
                        CPLGetPathSafe(osDirName.c_str());
1492
0
                    if (!osDirNameNew.empty() && osDirNameNew != osDirName)
1493
0
                    {
1494
0
                        osDirName = std::move(osDirNameNew);
1495
0
                        continue;
1496
0
                    }
1497
0
                }
1498
0
                break;
1499
0
            }
1500
0
        }
1501
1502
0
        oIter = m_oMapDimensions.find(osDimName);
1503
        // cppcheck-suppress knownConditionTrueFalse
1504
0
        if (oIter != m_oMapDimensions.end() &&
1505
0
            oIter->second->GetSize() == poDim->GetSize())
1506
0
        {
1507
0
            poDim = oIter->second;
1508
0
            return true;
1509
0
        }
1510
1511
0
        std::string osType;
1512
0
        std::string osDirection;
1513
0
        if (aoDims.size() == 1 && osArrayName == osDimName)
1514
0
        {
1515
0
            ZarrArray::GetDimensionTypeDirection(oAttributes, osType,
1516
0
                                                 osDirection);
1517
0
        }
1518
1519
0
        auto poDimLocal = std::make_shared<ZarrDimension>(
1520
0
            m_poSharedResource,
1521
0
            std::dynamic_pointer_cast<ZarrGroupBase>(m_pSelf.lock()),
1522
0
            GetFullName(), osDimName, osType, osDirection, poDim->GetSize());
1523
0
        poDimLocal->SetXArrayDimension();
1524
0
        m_oMapDimensions[osDimName] = poDimLocal;
1525
0
        poDim = poDimLocal;
1526
0
        return true;
1527
0
    };
1528
1529
0
    if (dimensionNames.GetType() == CPLJSONObject::Type::Array)
1530
0
    {
1531
0
        const auto arrayDims = dimensionNames.ToArray();
1532
0
        if (arrayDims.Size() == oShape.Size())
1533
0
        {
1534
0
            for (int i = 0; i < oShape.Size(); ++i)
1535
0
            {
1536
0
                if (arrayDims[i].GetType() == CPLJSONObject::Type::String)
1537
0
                {
1538
0
                    const auto osDimName = arrayDims[i].ToString();
1539
0
                    FindDimension(osDimName, aoDims[i], i);
1540
0
                }
1541
0
            }
1542
0
        }
1543
0
        else
1544
0
        {
1545
0
            CPLError(
1546
0
                CE_Failure, CPLE_AppDefined,
1547
0
                "Size of dimension_names[] different from the one of shape");
1548
0
            return nullptr;
1549
0
        }
1550
0
    }
1551
0
    else if (dimensionNames.IsValid())
1552
0
    {
1553
0
        CPLError(CE_Failure, CPLE_AppDefined,
1554
0
                 "dimension_names should be an array");
1555
0
        return nullptr;
1556
0
    }
1557
1558
0
    auto oDtype = oRoot["data_type"];
1559
0
    if (!oDtype.IsValid())
1560
0
    {
1561
0
        CPLError(CE_Failure, CPLE_NotSupported, "data_type missing");
1562
0
        return nullptr;
1563
0
    }
1564
0
    if (oDtype["fallback"].IsValid())
1565
0
        oDtype = oDtype["fallback"];
1566
0
    std::vector<DtypeElt> aoDtypeElts;
1567
0
    const auto oType = ParseDtypeV3(oDtype, aoDtypeElts);
1568
0
    if (oType.GetClass() == GEDTC_NUMERIC &&
1569
0
        oType.GetNumericDataType() == GDT_Unknown)
1570
0
        return nullptr;
1571
1572
0
    std::vector<GUInt64> anOuterBlockSize;
1573
0
    if (!ZarrArray::ParseChunkSize(oChunks, oType, anOuterBlockSize))
1574
0
        return nullptr;
1575
1576
0
    std::vector<GByte> abyNoData;
1577
1578
0
    auto oFillValue = oRoot["fill_value"];
1579
0
    auto eFillValueType = oFillValue.GetType();
1580
1581
0
    if (!oFillValue.IsValid())
1582
0
    {
1583
0
        CPLError(CE_Warning, CPLE_AppDefined, "Missing fill_value is invalid");
1584
0
    }
1585
0
    else if (eFillValueType == CPLJSONObject::Type::Null)
1586
0
    {
1587
0
        CPLError(CE_Warning, CPLE_AppDefined, "fill_value = null is invalid");
1588
0
    }
1589
0
    else if (GDALDataTypeIsComplex(oType.GetNumericDataType()) &&
1590
0
             eFillValueType != CPLJSONObject::Type::Array)
1591
0
    {
1592
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1593
0
        return nullptr;
1594
0
    }
1595
0
    else if (eFillValueType == CPLJSONObject::Type::String)
1596
0
    {
1597
0
        const auto osFillValue = oFillValue.ToString();
1598
0
        if (STARTS_WITH(osFillValue.c_str(), "0x"))
1599
0
        {
1600
0
            if (osFillValue.size() > 2 + 2 * oType.GetSize())
1601
0
            {
1602
0
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1603
0
                return nullptr;
1604
0
            }
1605
0
            uint64_t nVal = static_cast<uint64_t>(
1606
0
                std::strtoull(osFillValue.c_str() + 2, nullptr, 16));
1607
0
            if (oType.GetSize() == 4)
1608
0
            {
1609
0
                abyNoData.resize(oType.GetSize());
1610
0
                uint32_t nTmp = static_cast<uint32_t>(nVal);
1611
0
                memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
1612
0
            }
1613
0
            else if (oType.GetSize() == 8)
1614
0
            {
1615
0
                abyNoData.resize(oType.GetSize());
1616
0
                memcpy(&abyNoData[0], &nVal, sizeof(nVal));
1617
0
            }
1618
0
            else
1619
0
            {
1620
0
                CPLError(CE_Failure, CPLE_AppDefined,
1621
0
                         "Hexadecimal representation of fill_value no "
1622
0
                         "supported for this data type");
1623
0
                return nullptr;
1624
0
            }
1625
0
        }
1626
0
        else if (STARTS_WITH(osFillValue.c_str(), "0b"))
1627
0
        {
1628
0
            if (osFillValue.size() > 2 + 8 * oType.GetSize())
1629
0
            {
1630
0
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1631
0
                return nullptr;
1632
0
            }
1633
0
            uint64_t nVal = static_cast<uint64_t>(
1634
0
                std::strtoull(osFillValue.c_str() + 2, nullptr, 2));
1635
0
            if (oType.GetSize() == 4)
1636
0
            {
1637
0
                abyNoData.resize(oType.GetSize());
1638
0
                uint32_t nTmp = static_cast<uint32_t>(nVal);
1639
0
                memcpy(&abyNoData[0], &nTmp, sizeof(nTmp));
1640
0
            }
1641
0
            else if (oType.GetSize() == 8)
1642
0
            {
1643
0
                abyNoData.resize(oType.GetSize());
1644
0
                memcpy(&abyNoData[0], &nVal, sizeof(nVal));
1645
0
            }
1646
0
            else
1647
0
            {
1648
0
                CPLError(CE_Failure, CPLE_AppDefined,
1649
0
                         "Binary representation of fill_value no supported for "
1650
0
                         "this data type");
1651
0
                return nullptr;
1652
0
            }
1653
0
        }
1654
0
        else
1655
0
        {
1656
0
            bool bOK = true;
1657
0
            double dfNoDataValue = ParseNoDataStringAsDouble(osFillValue, bOK);
1658
0
            if (!bOK)
1659
0
            {
1660
0
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1661
0
                return nullptr;
1662
0
            }
1663
0
            else if (oType.GetNumericDataType() == GDT_Float16)
1664
0
            {
1665
0
                const GFloat16 hfNoDataValue =
1666
0
                    static_cast<GFloat16>(dfNoDataValue);
1667
0
                abyNoData.resize(sizeof(hfNoDataValue));
1668
0
                memcpy(&abyNoData[0], &hfNoDataValue, sizeof(hfNoDataValue));
1669
0
            }
1670
0
            else if (oType.GetNumericDataType() == GDT_Float32)
1671
0
            {
1672
0
                const float fNoDataValue = static_cast<float>(dfNoDataValue);
1673
0
                abyNoData.resize(sizeof(fNoDataValue));
1674
0
                memcpy(&abyNoData[0], &fNoDataValue, sizeof(fNoDataValue));
1675
0
            }
1676
0
            else if (oType.GetNumericDataType() == GDT_Float64)
1677
0
            {
1678
0
                abyNoData.resize(sizeof(dfNoDataValue));
1679
0
                memcpy(&abyNoData[0], &dfNoDataValue, sizeof(dfNoDataValue));
1680
0
            }
1681
0
            else
1682
0
            {
1683
0
                CPLError(CE_Failure, CPLE_AppDefined,
1684
0
                         "Invalid fill_value for this data type");
1685
0
                return nullptr;
1686
0
            }
1687
0
        }
1688
0
    }
1689
0
    else if (eFillValueType == CPLJSONObject::Type::Boolean ||
1690
0
             eFillValueType == CPLJSONObject::Type::Integer ||
1691
0
             eFillValueType == CPLJSONObject::Type::Long ||
1692
0
             eFillValueType == CPLJSONObject::Type::Double)
1693
0
    {
1694
0
        const double dfNoDataValue = oFillValue.ToDouble();
1695
0
        if (oType.GetNumericDataType() == GDT_Int64)
1696
0
        {
1697
0
            const int64_t nNoDataValue =
1698
0
                static_cast<int64_t>(oFillValue.ToLong());
1699
0
            abyNoData.resize(oType.GetSize());
1700
0
            GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1701
0
                          oType.GetNumericDataType(), 0, 1);
1702
0
        }
1703
0
        else if (oType.GetNumericDataType() == GDT_UInt64 &&
1704
                 /* we can't really deal with nodata value between */
1705
                 /* int64::max and uint64::max due to json-c limitations */
1706
0
                 dfNoDataValue >= 0)
1707
0
        {
1708
0
            const int64_t nNoDataValue =
1709
0
                static_cast<int64_t>(oFillValue.ToLong());
1710
0
            abyNoData.resize(oType.GetSize());
1711
0
            GDALCopyWords(&nNoDataValue, GDT_Int64, 0, &abyNoData[0],
1712
0
                          oType.GetNumericDataType(), 0, 1);
1713
0
        }
1714
0
        else
1715
0
        {
1716
0
            abyNoData.resize(oType.GetSize());
1717
0
            GDALCopyWords(&dfNoDataValue, GDT_Float64, 0, &abyNoData[0],
1718
0
                          oType.GetNumericDataType(), 0, 1);
1719
0
        }
1720
0
    }
1721
0
    else if (eFillValueType == CPLJSONObject::Type::Array)
1722
0
    {
1723
0
        const auto oFillValueArray = oFillValue.ToArray();
1724
0
        if (oFillValueArray.Size() == 2 &&
1725
0
            GDALDataTypeIsComplex(oType.GetNumericDataType()))
1726
0
        {
1727
0
            if (oType.GetNumericDataType() == GDT_CFloat64)
1728
0
            {
1729
0
                bool bOK = true;
1730
0
                const double adfNoDataValue[2] = {
1731
0
                    ParseNoDataComponent<double, uint64_t>(oFillValueArray[0],
1732
0
                                                           bOK),
1733
0
                    ParseNoDataComponent<double, uint64_t>(oFillValueArray[1],
1734
0
                                                           bOK),
1735
0
                };
1736
0
                if (!bOK)
1737
0
                {
1738
0
                    CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1739
0
                    return nullptr;
1740
0
                }
1741
0
                abyNoData.resize(oType.GetSize());
1742
0
                CPLAssert(sizeof(adfNoDataValue) == oType.GetSize());
1743
0
                memcpy(abyNoData.data(), adfNoDataValue,
1744
0
                       sizeof(adfNoDataValue));
1745
0
            }
1746
0
            else
1747
0
            {
1748
0
                CPLAssert(oType.GetNumericDataType() == GDT_CFloat32);
1749
0
                bool bOK = true;
1750
0
                const float afNoDataValue[2] = {
1751
0
                    ParseNoDataComponent<float, uint32_t>(oFillValueArray[0],
1752
0
                                                          bOK),
1753
0
                    ParseNoDataComponent<float, uint32_t>(oFillValueArray[1],
1754
0
                                                          bOK),
1755
0
                };
1756
0
                if (!bOK)
1757
0
                {
1758
0
                    CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1759
0
                    return nullptr;
1760
0
                }
1761
0
                abyNoData.resize(oType.GetSize());
1762
0
                CPLAssert(sizeof(afNoDataValue) == oType.GetSize());
1763
0
                memcpy(abyNoData.data(), afNoDataValue, sizeof(afNoDataValue));
1764
0
            }
1765
0
        }
1766
0
        else
1767
0
        {
1768
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1769
0
            return nullptr;
1770
0
        }
1771
0
    }
1772
0
    else
1773
0
    {
1774
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid fill_value");
1775
0
        return nullptr;
1776
0
    }
1777
1778
0
    const auto oCodecs = oRoot["codecs"].ToArray();
1779
0
    std::unique_ptr<ZarrV3CodecSequence> poCodecs;
1780
0
    std::vector<GUInt64> anInnerBlockSize = anOuterBlockSize;
1781
0
    if (oCodecs.Size() > 0)
1782
0
    {
1783
0
        poCodecs = ZarrV3Array::SetupCodecs(oCodecs, anOuterBlockSize,
1784
0
                                            anInnerBlockSize,
1785
0
                                            aoDtypeElts.back(), abyNoData);
1786
0
        if (!poCodecs)
1787
0
        {
1788
0
            return nullptr;
1789
0
        }
1790
0
    }
1791
1792
0
    auto poArray = ZarrV3Array::Create(m_poSharedResource, Self(), osArrayName,
1793
0
                                       aoDims, oType, aoDtypeElts,
1794
0
                                       anOuterBlockSize, anInnerBlockSize);
1795
0
    if (!poArray)
1796
0
        return nullptr;
1797
0
    poArray->SetUpdatable(m_bUpdatable);  // must be set before SetAttributes()
1798
0
    poArray->SetFilename(osZarrayFilename);
1799
0
    poArray->SetIsV2ChunkKeyEncoding(bV2ChunkKeyEncoding);
1800
0
    poArray->SetDimSeparator(osDimSeparator);
1801
0
    if (!abyNoData.empty())
1802
0
    {
1803
0
        poArray->RegisterNoDataValue(abyNoData.data());
1804
0
    }
1805
0
    poArray->SetAttributes(Self(), oAttributes);
1806
0
    poArray->SetDtype(oDtype);
1807
0
    if (oCodecs.Size() > 0 &&
1808
0
        oCodecs[oCodecs.Size() - 1].GetString("name") != "bytes")
1809
0
    {
1810
0
        poArray->SetStructuralInfo(
1811
0
            "COMPRESSOR", oCodecs[oCodecs.Size() - 1].ToString().c_str());
1812
0
    }
1813
0
    if (poCodecs)
1814
0
        poArray->SetCodecs(oCodecs, std::move(poCodecs));
1815
0
    RegisterArray(poArray);
1816
1817
    // If this is an indexing variable, attach it to the dimension.
1818
0
    if (aoDims.size() == 1 && aoDims[0]->GetName() == poArray->GetName())
1819
0
    {
1820
0
        auto oIter = m_oMapDimensions.find(poArray->GetName());
1821
0
        if (oIter != m_oMapDimensions.end())
1822
0
        {
1823
0
            oIter->second->SetIndexingVariable(poArray);
1824
0
        }
1825
0
    }
1826
1827
0
    if (CPLTestBool(m_poSharedResource->GetOpenOptions().FetchNameValueDef(
1828
0
            "CACHE_TILE_PRESENCE", "NO")))
1829
0
    {
1830
0
        poArray->BlockCachePresence();
1831
0
    }
1832
1833
0
    return poArray;
1834
0
}
1835
1836
/************************************************************************/
1837
/*                  ZarrV3Array::GetRawBlockInfoInfo()                  */
1838
/************************************************************************/
1839
1840
CPLStringList ZarrV3Array::GetRawBlockInfoInfo() const
1841
0
{
1842
0
    CPLStringList aosInfo(m_aosStructuralInfo);
1843
0
    if (m_oType.GetSize() > 1)
1844
0
    {
1845
        // By default, assume that the ENDIANNESS is the native one.
1846
        // Otherwise there will be a ZarrV3CodecBytes instance.
1847
        if constexpr (CPL_IS_LSB)
1848
0
            aosInfo.SetNameValue("ENDIANNESS", "LITTLE");
1849
        else
1850
            aosInfo.SetNameValue("ENDIANNESS", "BIG");
1851
0
    }
1852
1853
0
    if (m_poCodecs)
1854
0
    {
1855
0
        bool bHasOtherCodec = false;
1856
0
        for (const auto &poCodec : m_poCodecs->GetCodecs())
1857
0
        {
1858
0
            if (poCodec->GetName() == ZarrV3CodecBytes::NAME &&
1859
0
                m_oType.GetSize() > 1)
1860
0
            {
1861
0
                auto poBytesCodec =
1862
0
                    dynamic_cast<const ZarrV3CodecBytes *>(poCodec.get());
1863
0
                if (poBytesCodec)
1864
0
                {
1865
0
                    if (poBytesCodec->IsLittle())
1866
0
                        aosInfo.SetNameValue("ENDIANNESS", "LITTLE");
1867
0
                    else
1868
0
                        aosInfo.SetNameValue("ENDIANNESS", "BIG");
1869
0
                }
1870
0
            }
1871
0
            else if (poCodec->GetName() == ZarrV3CodecTranspose::NAME &&
1872
0
                     m_aoDims.size() > 1)
1873
0
            {
1874
0
                auto poTransposeCodec =
1875
0
                    dynamic_cast<const ZarrV3CodecTranspose *>(poCodec.get());
1876
0
                if (poTransposeCodec && !poTransposeCodec->IsNoOp())
1877
0
                {
1878
0
                    const auto &anOrder = poTransposeCodec->GetOrder();
1879
0
                    const int nDims = static_cast<int>(anOrder.size());
1880
0
                    std::string osOrder("[");
1881
0
                    for (int i = 0; i < nDims; ++i)
1882
0
                    {
1883
0
                        if (i > 0)
1884
0
                            osOrder += ',';
1885
0
                        osOrder += std::to_string(anOrder[i]);
1886
0
                    }
1887
0
                    osOrder += ']';
1888
0
                    aosInfo.SetNameValue("TRANSPOSE_ORDER", osOrder.c_str());
1889
0
                }
1890
0
            }
1891
0
            else if (poCodec->GetName() != ZarrV3CodecGZip::NAME &&
1892
0
                     poCodec->GetName() != ZarrV3CodecBlosc::NAME &&
1893
0
                     poCodec->GetName() != ZarrV3CodecZstd::NAME)
1894
0
            {
1895
0
                bHasOtherCodec = true;
1896
0
            }
1897
0
        }
1898
0
        if (bHasOtherCodec)
1899
0
        {
1900
0
            aosInfo.SetNameValue("CODECS", m_oJSONCodecs.ToString().c_str());
1901
0
        }
1902
1903
0
        if (m_poCodecs->SupportsPartialDecoding())
1904
0
        {
1905
0
            aosInfo.SetNameValue("CHUNK_TYPE", "INNER");
1906
0
        }
1907
0
    }
1908
1909
0
    return aosInfo;
1910
0
}
1911
1912
/************************************************************************/
1913
/*                      ZarrV3Array::SetupCodecs()                      */
1914
/************************************************************************/
1915
1916
/* static */ std::unique_ptr<ZarrV3CodecSequence> ZarrV3Array::SetupCodecs(
1917
    const CPLJSONArray &oCodecs, const std::vector<GUInt64> &anOuterBlockSize,
1918
    std::vector<GUInt64> &anInnerBlockSize, DtypeElt &zarrDataType,
1919
    const std::vector<GByte> &abyNoData)
1920
1921
0
{
1922
    // Byte swapping will be done by the codec chain
1923
0
    zarrDataType.needByteSwapping = false;
1924
1925
0
    ZarrArrayMetadata oInputArrayMetadata;
1926
0
    if (!abyNoData.empty() && zarrDataType.gdalTypeIsApproxOfNative)
1927
0
    {
1928
        // This cannot happen today with the data types we support, but
1929
        // might in the future. In which case we'll have to translate the
1930
        // nodata value from its GDAL representation to the native one
1931
        // (since that's what zarr_v3_codec_sharding::FillWithNoData()
1932
        // expects
1933
0
        CPLError(CE_Warning, CPLE_AppDefined,
1934
0
                 "Zarr driver issue: gdalTypeIsApproxOfNative is not taken "
1935
0
                 "into account by codecs. Nodata will be assumed to be zero by "
1936
0
                 "sharding codec");
1937
0
    }
1938
0
    else
1939
0
    {
1940
0
        oInputArrayMetadata.abyNoData = abyNoData;
1941
0
    }
1942
0
    for (auto &nSize : anOuterBlockSize)
1943
0
    {
1944
0
        oInputArrayMetadata.anBlockSizes.push_back(static_cast<size_t>(nSize));
1945
0
    }
1946
0
    oInputArrayMetadata.oElt = zarrDataType;
1947
0
    auto poCodecs = std::make_unique<ZarrV3CodecSequence>(oInputArrayMetadata);
1948
0
    ZarrArrayMetadata oOutputArrayMetadata;
1949
0
    if (!poCodecs->InitFromJson(oCodecs, oOutputArrayMetadata))
1950
0
    {
1951
0
        return nullptr;
1952
0
    }
1953
0
    std::vector<size_t> anOuterBlockSizeSizet;
1954
0
    for (auto nVal : oOutputArrayMetadata.anBlockSizes)
1955
0
    {
1956
0
        anOuterBlockSizeSizet.push_back(static_cast<size_t>(nVal));
1957
0
    }
1958
0
    anInnerBlockSize.clear();
1959
0
    for (size_t nVal : poCodecs->GetInnerMostBlockSize(anOuterBlockSizeSizet))
1960
0
    {
1961
0
        anInnerBlockSize.push_back(nVal);
1962
0
    }
1963
0
    return poCodecs;
1964
0
}
1965
1966
/************************************************************************/
1967
/*                       ZarrV3Array::SetCodecs()                       */
1968
/************************************************************************/
1969
1970
void ZarrV3Array::SetCodecs(const CPLJSONArray &oJSONCodecs,
1971
                            std::unique_ptr<ZarrV3CodecSequence> &&poCodecs)
1972
0
{
1973
0
    m_oJSONCodecs = oJSONCodecs;
1974
0
    m_poCodecs = std::move(poCodecs);
1975
0
}
1976
1977
/************************************************************************/
1978
/*                     ZarrV3Array::LoadOverviews()                     */
1979
/************************************************************************/
1980
1981
void ZarrV3Array::LoadOverviews() const
1982
0
{
1983
0
    if (m_bOverviewsLoaded)
1984
0
        return;
1985
0
    m_bOverviewsLoaded = true;
1986
1987
    // Cf https://github.com/zarr-conventions/multiscales
1988
    // and https://github.com/zarr-conventions/spatial
1989
1990
0
    const auto poRG = GetRootGroup();
1991
0
    if (!poRG)
1992
0
    {
1993
0
        CPLError(CE_Warning, CPLE_AppDefined,
1994
0
                 "LoadOverviews(): cannot access root group");
1995
0
        return;
1996
0
    }
1997
1998
0
    auto poGroup = GetParentGroup();
1999
0
    if (!poGroup)
2000
0
    {
2001
0
        CPLDebugOnly(ZARR_DEBUG_KEY,
2002
0
                     "LoadOverviews(): cannot access parent group");
2003
0
        return;
2004
0
    }
2005
2006
    // Look for "zarr_conventions" and "multiscales" attributes in our
2007
    // immediate parent, or in our grandparent if not found.
2008
0
    auto poAttrZarrConventions = poGroup->GetAttribute("zarr_conventions");
2009
0
    auto poAttrMultiscales = poGroup->GetAttribute("multiscales");
2010
0
    if (!poAttrZarrConventions || !poAttrMultiscales)
2011
0
    {
2012
0
        poGroup = poGroup->GetParentGroup();
2013
0
        if (poGroup)
2014
0
        {
2015
0
            poAttrZarrConventions = poGroup->GetAttribute("zarr_conventions");
2016
0
            poAttrMultiscales = poGroup->GetAttribute("multiscales");
2017
0
        }
2018
0
        if (!poAttrZarrConventions || !poAttrMultiscales)
2019
0
        {
2020
0
            return;
2021
0
        }
2022
0
    }
2023
2024
0
    const char *pszZarrConventions = poAttrZarrConventions->ReadAsString();
2025
0
    const char *pszMultiscales = poAttrMultiscales->ReadAsString();
2026
0
    if (!pszZarrConventions || !pszMultiscales)
2027
0
        return;
2028
2029
0
    CPLJSONDocument oDoc;
2030
0
    if (!oDoc.LoadMemory(pszZarrConventions))
2031
0
        return;
2032
0
    const auto oZarrConventions = oDoc.GetRoot();
2033
2034
0
    if (!oDoc.LoadMemory(pszMultiscales))
2035
0
        return;
2036
0
    const auto oMultiscales = oDoc.GetRoot();
2037
2038
0
    if (!oZarrConventions.IsValid() ||
2039
0
        oZarrConventions.GetType() != CPLJSONObject::Type::Array ||
2040
0
        !oMultiscales.IsValid() ||
2041
0
        oMultiscales.GetType() != CPLJSONObject::Type::Object)
2042
0
    {
2043
0
        return;
2044
0
    }
2045
2046
0
    const auto oZarrConventionsArray = oZarrConventions.ToArray();
2047
0
    const auto hasMultiscalesUUIDLambda = [](const CPLJSONObject &obj)
2048
0
    {
2049
0
        constexpr const char *MULTISCALES_UUID =
2050
0
            "d35379db-88df-4056-af3a-620245f8e347";
2051
0
        return obj.GetString("uuid") == MULTISCALES_UUID;
2052
0
    };
2053
0
    const bool bFoundMultiScalesUUID =
2054
0
        std::find_if(oZarrConventionsArray.begin(), oZarrConventionsArray.end(),
2055
0
                     hasMultiscalesUUIDLambda) != oZarrConventionsArray.end();
2056
0
    if (!bFoundMultiScalesUUID)
2057
0
        return;
2058
2059
0
    const auto hasSpatialUUIDLambda = [](const CPLJSONObject &obj)
2060
0
    {
2061
0
        constexpr const char *SPATIAL_UUID =
2062
0
            "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4";
2063
0
        return obj.GetString("uuid") == SPATIAL_UUID;
2064
0
    };
2065
0
    const bool bFoundSpatialUUID =
2066
0
        std::find_if(oZarrConventionsArray.begin(), oZarrConventionsArray.end(),
2067
0
                     hasSpatialUUIDLambda) != oZarrConventionsArray.end();
2068
2069
0
    const auto oLayout = oMultiscales["layout"];
2070
0
    if (!oLayout.IsValid() && oLayout.GetType() != CPLJSONObject::Type::Array)
2071
0
    {
2072
0
        CPLError(CE_Warning, CPLE_AppDefined,
2073
0
                 "layout not found in multiscales");
2074
0
        return;
2075
0
    }
2076
2077
    // is pixel-is-area ?
2078
0
    auto poSpatialRegistration = poGroup->GetAttribute("spatial:registration");
2079
0
    const char *pszSpatialRegistration =
2080
0
        poSpatialRegistration ? poSpatialRegistration->ReadAsString() : nullptr;
2081
0
    const bool bHasExplicitPixelSpatialRegistration =
2082
0
        bFoundSpatialUUID && pszSpatialRegistration &&
2083
0
        strcmp(pszSpatialRegistration, "pixel") == 0;
2084
2085
0
    std::vector<std::string> aosSpatialDimensions;
2086
0
    std::set<std::string> oSetSpatialDimensionNames;
2087
0
    auto poSpatialDimensions = poGroup->GetAttribute("spatial:dimensions");
2088
0
    if (bFoundSpatialUUID && poSpatialDimensions)
2089
0
    {
2090
0
        aosSpatialDimensions = poSpatialDimensions->ReadAsStringArray();
2091
0
        for (const auto &osDimName : aosSpatialDimensions)
2092
0
        {
2093
0
            oSetSpatialDimensionNames.insert(osDimName);
2094
0
        }
2095
0
    }
2096
2097
    // Multiscales convention: asset/derived_from paths are relative to
2098
    // the group holding the convention metadata, not the store root.
2099
0
    const std::string osGroupPrefix = poGroup->GetFullName().back() == '/'
2100
0
                                          ? poGroup->GetFullName()
2101
0
                                          : poGroup->GetFullName() + '/';
2102
0
    const auto resolveAssetPath =
2103
0
        [&osGroupPrefix](const std::string &osRelative) -> std::string
2104
0
    { return osGroupPrefix + osRelative; };
2105
2106
0
    for (const auto &oLayoutItem : oLayout.ToArray())
2107
0
    {
2108
0
        const std::string osAsset = oLayoutItem.GetString("asset");
2109
0
        if (osAsset.empty())
2110
0
        {
2111
0
            CPLError(CE_Warning, CPLE_AppDefined,
2112
0
                     "multiscales.layout[].asset not found");
2113
0
            continue;
2114
0
        }
2115
2116
        // Resolve "asset" to a MDArray
2117
0
        std::shared_ptr<GDALGroup> poAssetGroup;
2118
0
        {
2119
0
            CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
2120
0
            poAssetGroup =
2121
0
                poRG->OpenGroupFromFullname(resolveAssetPath(osAsset));
2122
0
        }
2123
0
        std::shared_ptr<GDALMDArray> poAssetArray;
2124
0
        if (poAssetGroup)
2125
0
        {
2126
0
            poAssetArray = poAssetGroup->OpenMDArray(GetName());
2127
0
        }
2128
0
        else if (osAsset.find('/') == std::string::npos)
2129
0
        {
2130
0
            poAssetArray = poGroup->OpenMDArray(osAsset);
2131
0
            if (!poAssetArray)
2132
0
            {
2133
0
                CPLError(CE_Warning, CPLE_AppDefined,
2134
0
                         "multiscales.layout[].asset=%s ignored, because it is "
2135
0
                         "not a valid group or array name",
2136
0
                         osAsset.c_str());
2137
0
                continue;
2138
0
            }
2139
0
        }
2140
0
        else
2141
0
        {
2142
0
            poAssetArray =
2143
0
                poRG->OpenMDArrayFromFullname(resolveAssetPath(osAsset));
2144
0
            if (poAssetArray && poAssetArray->GetName() != GetName())
2145
0
            {
2146
0
                continue;
2147
0
            }
2148
0
        }
2149
0
        if (!poAssetArray)
2150
0
        {
2151
0
            continue;
2152
0
        }
2153
0
        if (poAssetArray->GetDimensionCount() != GetDimensionCount())
2154
0
        {
2155
0
            CPLError(
2156
0
                CE_Warning, CPLE_AppDefined,
2157
0
                "multiscales.layout[].asset=%s (%s) ignored, because it  has "
2158
0
                "not the same dimension count as %s (%s)",
2159
0
                osAsset.c_str(), poAssetArray->GetFullName().c_str(),
2160
0
                GetName().c_str(), GetFullName().c_str());
2161
0
            continue;
2162
0
        }
2163
0
        if (poAssetArray->GetDataType() != GetDataType())
2164
0
        {
2165
0
            CPLError(
2166
0
                CE_Warning, CPLE_AppDefined,
2167
0
                "multiscales.layout[].asset=%s (%s) ignored, because it has "
2168
0
                "not the same data type as %s (%s)",
2169
0
                osAsset.c_str(), poAssetArray->GetFullName().c_str(),
2170
0
                GetName().c_str(), GetFullName().c_str());
2171
0
            continue;
2172
0
        }
2173
2174
0
        bool bAssetIsDownsampledOfThis = false;
2175
0
        for (size_t iDim = 0; iDim < GetDimensionCount(); ++iDim)
2176
0
        {
2177
0
            if (poAssetArray->GetDimensions()[iDim]->GetSize() <
2178
0
                GetDimensions()[iDim]->GetSize())
2179
0
            {
2180
0
                bAssetIsDownsampledOfThis = true;
2181
0
                break;
2182
0
            }
2183
0
        }
2184
0
        if (!bAssetIsDownsampledOfThis)
2185
0
        {
2186
            // not an error
2187
0
            continue;
2188
0
        }
2189
2190
        // Inspect dimensions of the asset
2191
0
        std::map<std::string, size_t> oMapAssetDimNameToIdx;
2192
0
        const auto &apoAssetDims = poAssetArray->GetDimensions();
2193
0
        size_t nCountSpatialDimsFoundInAsset = 0;
2194
0
        for (const auto &[idx, poDim] : cpl::enumerate(apoAssetDims))
2195
0
        {
2196
0
            oMapAssetDimNameToIdx[poDim->GetName()] = idx;
2197
0
            if (cpl::contains(oSetSpatialDimensionNames, poDim->GetName()))
2198
0
                ++nCountSpatialDimsFoundInAsset;
2199
0
        }
2200
0
        const bool bAssetHasAllSpatialDims =
2201
0
            (nCountSpatialDimsFoundInAsset == aosSpatialDimensions.size());
2202
2203
        // Consistency checks on "derived_from" and "transform"
2204
0
        const auto oDerivedFrom = oLayoutItem["derived_from"];
2205
0
        const auto oTransform = oLayoutItem["transform"];
2206
0
        if (oDerivedFrom.IsValid() && oTransform.IsValid() &&
2207
0
            oDerivedFrom.GetType() == CPLJSONObject::Type::String &&
2208
0
            oTransform.GetType() == CPLJSONObject::Type::Object)
2209
0
        {
2210
0
            const std::string osDerivedFrom = oDerivedFrom.ToString();
2211
            // Resolve "derived_from" to a MDArray
2212
0
            std::shared_ptr<GDALGroup> poDerivedFromGroup;
2213
0
            {
2214
0
                CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
2215
0
                poDerivedFromGroup = poRG->OpenGroupFromFullname(
2216
0
                    resolveAssetPath(osDerivedFrom));
2217
0
            }
2218
0
            std::shared_ptr<GDALMDArray> poDerivedFromArray;
2219
0
            if (poDerivedFromGroup)
2220
0
            {
2221
0
                poDerivedFromArray = poDerivedFromGroup->OpenMDArray(GetName());
2222
0
            }
2223
0
            else if (osDerivedFrom.find('/') == std::string::npos)
2224
0
            {
2225
0
                poDerivedFromArray = poGroup->OpenMDArray(osDerivedFrom);
2226
0
                if (!poDerivedFromArray)
2227
0
                {
2228
0
                    CPLError(CE_Warning, CPLE_AppDefined,
2229
0
                             "multiscales.layout[].asset=%s refers to "
2230
0
                             "derived_from=%s which does not exist",
2231
0
                             osAsset.c_str(), osDerivedFrom.c_str());
2232
0
                    poDerivedFromArray.reset();
2233
0
                }
2234
0
            }
2235
0
            else
2236
0
            {
2237
0
                poDerivedFromArray = poRG->OpenMDArrayFromFullname(
2238
0
                    resolveAssetPath(osDerivedFrom));
2239
0
            }
2240
0
            if (poDerivedFromArray && bAssetHasAllSpatialDims)
2241
0
            {
2242
0
                if (poDerivedFromArray->GetDimensionCount() !=
2243
0
                    GetDimensionCount())
2244
0
                {
2245
0
                    CPLError(CE_Warning, CPLE_AppDefined,
2246
0
                             "multiscales.layout[].asset=%s refers to "
2247
0
                             "derived_from=%s that does not have the expected "
2248
0
                             "number of dimensions. Ignoring that asset",
2249
0
                             osAsset.c_str(), osDerivedFrom.c_str());
2250
0
                    continue;
2251
0
                }
2252
2253
0
                const auto oScale = oTransform["scale"];
2254
0
                if (oScale.GetType() == CPLJSONObject::Type::Array &&
2255
0
                    bHasExplicitPixelSpatialRegistration)
2256
0
                {
2257
0
                    const auto oScaleArray = oScale.ToArray();
2258
0
                    if (oScaleArray.size() != GetDimensionCount())
2259
0
                    {
2260
2261
0
                        CPLError(CE_Warning, CPLE_AppDefined,
2262
0
                                 "multiscales.layout[].asset=%s has a "
2263
0
                                 "transform.scale array with an unexpected "
2264
0
                                 "number of values. Ignoring the asset",
2265
0
                                 osAsset.c_str());
2266
0
                        continue;
2267
0
                    }
2268
2269
0
                    for (size_t iDim = 0; iDim < GetDimensionCount(); ++iDim)
2270
0
                    {
2271
0
                        const double dfScale = oScaleArray[iDim].ToDouble();
2272
0
                        const double dfExpectedScale =
2273
0
                            static_cast<double>(
2274
0
                                poDerivedFromArray->GetDimensions()[iDim]
2275
0
                                    ->GetSize()) /
2276
0
                            static_cast<double>(
2277
0
                                poAssetArray->GetDimensions()[iDim]->GetSize());
2278
0
                        constexpr double EPSILON = 1e-3;
2279
0
                        if (std::fabs(dfScale - dfExpectedScale) >
2280
0
                            EPSILON * dfExpectedScale)
2281
0
                        {
2282
0
                            CPLError(CE_Warning, CPLE_AppDefined,
2283
0
                                     "multiscales.layout[].asset=%s has a "
2284
0
                                     "transform.scale[%d]=%f value whereas %f "
2285
0
                                     "was expected. "
2286
0
                                     "Assuming that later value as the scale.",
2287
0
                                     osAsset.c_str(), static_cast<int>(iDim),
2288
0
                                     dfScale, dfExpectedScale);
2289
0
                        }
2290
0
                    }
2291
0
                }
2292
2293
0
                const auto oTranslation = oTransform["translation"];
2294
0
                if (oTranslation.GetType() == CPLJSONObject::Type::Array &&
2295
0
                    bHasExplicitPixelSpatialRegistration)
2296
0
                {
2297
0
                    const auto oTranslationArray = oTranslation.ToArray();
2298
0
                    if (oTranslationArray.size() != GetDimensionCount())
2299
0
                    {
2300
0
                        CPLError(CE_Warning, CPLE_AppDefined,
2301
0
                                 "multiscales.layout[].asset=%s has a "
2302
0
                                 "transform.translation array with an "
2303
0
                                 "unexpected number of values. "
2304
0
                                 "Ignoring the asset",
2305
0
                                 osAsset.c_str());
2306
0
                        continue;
2307
0
                    }
2308
2309
0
                    for (size_t iDim = 0; iDim < GetDimensionCount(); ++iDim)
2310
0
                    {
2311
0
                        const double dfOffset =
2312
0
                            oTranslationArray[iDim].ToDouble();
2313
0
                        if (dfOffset != 0)
2314
0
                        {
2315
0
                            CPLError(CE_Warning, CPLE_AppDefined,
2316
0
                                     "multiscales.layout[].asset=%s has a "
2317
0
                                     "transform.translation[%d]=%f value. "
2318
0
                                     "Ignoring that offset.",
2319
0
                                     osAsset.c_str(), static_cast<int>(iDim),
2320
0
                                     dfOffset);
2321
0
                        }
2322
0
                    }
2323
0
                }
2324
0
            }
2325
0
        }
2326
2327
0
        if (bFoundSpatialUUID && bAssetHasAllSpatialDims)
2328
0
        {
2329
0
            const auto oSpatialShape = oLayoutItem["spatial:shape"];
2330
0
            if (oSpatialShape.IsValid())
2331
0
            {
2332
0
                if (oSpatialShape.GetType() != CPLJSONObject::Type::Array)
2333
0
                {
2334
0
                    CPLError(
2335
0
                        CE_Warning, CPLE_AppDefined,
2336
0
                        "multiscales.layout[].asset=%s ignored, because its "
2337
0
                        "spatial:shape property is not an array",
2338
0
                        osAsset.c_str());
2339
0
                    continue;
2340
0
                }
2341
0
                const auto oSpatialShapeArray = oSpatialShape.ToArray();
2342
0
                if (oSpatialShapeArray.size() != aosSpatialDimensions.size())
2343
0
                {
2344
0
                    CPLError(
2345
0
                        CE_Warning, CPLE_AppDefined,
2346
0
                        "multiscales.layout[].asset=%s ignored, because its "
2347
0
                        "spatial:shape property has not the expected number "
2348
0
                        "of values",
2349
0
                        osAsset.c_str());
2350
0
                    continue;
2351
0
                }
2352
2353
0
                bool bSkip = false;
2354
0
                for (const auto &[idx, oShapeVal] :
2355
0
                     cpl::enumerate(oSpatialShapeArray))
2356
0
                {
2357
0
                    const auto oIter =
2358
0
                        oMapAssetDimNameToIdx.find(aosSpatialDimensions[idx]);
2359
0
                    if (oIter != oMapAssetDimNameToIdx.end())
2360
0
                    {
2361
0
                        const auto poDim = apoAssetDims[oIter->second];
2362
0
                        if (poDim->GetSize() !=
2363
0
                            static_cast<uint64_t>(oShapeVal.ToLong()))
2364
0
                        {
2365
0
                            bSkip = true;
2366
0
                            CPLError(CE_Warning, CPLE_AppDefined,
2367
0
                                     "multiscales.layout[].asset=%s ignored, "
2368
0
                                     "because its "
2369
0
                                     "spatial:shape[%d] value is %" PRIu64
2370
0
                                     " whereas %" PRIu64 " was expected.",
2371
0
                                     osAsset.c_str(), static_cast<int>(idx),
2372
0
                                     static_cast<uint64_t>(oShapeVal.ToLong()),
2373
0
                                     static_cast<uint64_t>(poDim->GetSize()));
2374
0
                        }
2375
0
                    }
2376
0
                }
2377
0
                if (bSkip)
2378
0
                    continue;
2379
0
            }
2380
0
        }
2381
2382
0
        m_apoOverviews.push_back(std::move(poAssetArray));
2383
0
    }
2384
0
}
2385
2386
/************************************************************************/
2387
/*                   ZarrV3Array::GetOverviewCount()                    */
2388
/************************************************************************/
2389
2390
int ZarrV3Array::GetOverviewCount() const
2391
0
{
2392
0
    LoadOverviews();
2393
0
    return static_cast<int>(m_apoOverviews.size());
2394
0
}
2395
2396
/************************************************************************/
2397
/*                      ZarrV3Array::GetOverview()                      */
2398
/************************************************************************/
2399
2400
std::shared_ptr<GDALMDArray> ZarrV3Array::GetOverview(int idx) const
2401
0
{
2402
0
    if (idx < 0 || idx >= GetOverviewCount())
2403
0
        return nullptr;
2404
0
    return m_apoOverviews[idx];
2405
0
}