Coverage Report

Created: 2025-06-13 06:29

/src/gdal/gcore/gdalmultidim_subsetdimension.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 * Name:     gdalmultidim_subsetdimension.cpp
3
 * Project:  GDAL Core
4
 * Purpose:  GDALGroup::SubsetDimensionFromSelection() implementation
5
 * Author:   Even Rouault <even.rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2023, Even Rouault <even.rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdal_priv.h"
14
#include "gdal_pam.h"
15
16
#include <algorithm>
17
18
/************************************************************************/
19
/*                           GetParentName()                            */
20
/************************************************************************/
21
22
static std::string GetParentName(const std::string &osPath)
23
0
{
24
0
    if (osPath == "/" || osPath.rfind('/') == 0)
25
0
        return "/";
26
0
    return osPath.substr(0, osPath.rfind('/'));
27
0
}
28
29
/************************************************************************/
30
/*                   GDALSubsetGroupSharedResources                     */
31
/************************************************************************/
32
33
struct GDALSubsetGroupSharedResources
34
{
35
    std::shared_ptr<GDALGroup> m_poRootGroup{};  // may be nullptr
36
    std::string m_osDimFullName{};
37
    std::vector<int> m_anMapNewDimToOldDim{};
38
    std::string m_osSelection{};
39
    std::shared_ptr<GDALDimension> m_poNewDim{};
40
    std::shared_ptr<GDALMDArray> m_poNewIndexingVar{};
41
};
42
43
/************************************************************************/
44
/*                          CreateContext()                             */
45
/************************************************************************/
46
47
static std::string
48
CreateContext(const std::string &osParentContext,
49
              const std::shared_ptr<GDALSubsetGroupSharedResources> &poShared)
50
0
{
51
0
    std::string osRet(osParentContext);
52
0
    if (!osRet.empty())
53
0
        osRet += ". ";
54
0
    osRet += "Selection ";
55
0
    osRet += poShared->m_osSelection;
56
0
    return osRet;
57
0
}
58
59
/************************************************************************/
60
/*                           GDALSubsetGroup                            */
61
/************************************************************************/
62
63
class GDALSubsetGroup final : public GDALGroup
64
{
65
    std::shared_ptr<GDALGroup> m_poParent{};
66
    std::shared_ptr<GDALSubsetGroupSharedResources> m_poShared{};
67
68
    GDALSubsetGroup(
69
        const std::shared_ptr<GDALGroup> &poParent,
70
        const std::shared_ptr<GDALSubsetGroupSharedResources> &poShared)
71
0
        : GDALGroup(GetParentName(poParent->GetFullName()), poParent->GetName(),
72
0
                    CreateContext(poParent->GetContext(), poShared)),
73
0
          m_poParent(std::move(poParent)), m_poShared(std::move(poShared))
74
0
    {
75
0
    }
76
77
  public:
78
    static std::shared_ptr<GDALGroup>
79
    Create(const std::shared_ptr<GDALGroup> &poParent,
80
           const std::shared_ptr<GDALSubsetGroupSharedResources> &poShared)
81
0
    {
82
0
        auto poGroup = std::shared_ptr<GDALSubsetGroup>(
83
0
            new GDALSubsetGroup(poParent, poShared));
84
0
        poGroup->SetSelf(poGroup);
85
0
        return poGroup;
86
0
    }
87
88
    std::vector<std::string>
89
    GetMDArrayNames(CSLConstList papszOptions = nullptr) const override
90
0
    {
91
0
        return m_poParent->GetMDArrayNames(papszOptions);
92
0
    }
93
94
    std::shared_ptr<GDALMDArray>
95
    OpenMDArray(const std::string &osName,
96
                CSLConstList papszOptions = nullptr) const override;
97
98
    std::vector<std::string>
99
    GetGroupNames(CSLConstList papszOptions = nullptr) const override
100
0
    {
101
0
        return m_poParent->GetGroupNames(papszOptions);
102
0
    }
103
104
    std::shared_ptr<GDALGroup>
105
    OpenGroup(const std::string &osName,
106
              CSLConstList papszOptions = nullptr) const override;
107
108
    std::vector<std::shared_ptr<GDALDimension>>
109
    GetDimensions(CSLConstList papszOptions = nullptr) const override;
110
111
    std::shared_ptr<GDALAttribute>
112
    GetAttribute(const std::string &osName) const override
113
0
    {
114
0
        return m_poParent->GetAttribute(osName);
115
0
    }
116
117
    std::vector<std::shared_ptr<GDALAttribute>>
118
    GetAttributes(CSLConstList papszOptions = nullptr) const override
119
0
    {
120
0
        return m_poParent->GetAttributes(papszOptions);
121
0
    }
122
};
123
124
/************************************************************************/
125
/*                           GDALSubsetArray                            */
126
/************************************************************************/
127
128
class GDALSubsetArray final : public GDALPamMDArray
129
{
130
  private:
131
    std::shared_ptr<GDALMDArray> m_poParent{};
132
    std::shared_ptr<GDALSubsetGroupSharedResources> m_poShared{};
133
    std::vector<std::shared_ptr<GDALDimension>> m_apoDims{};
134
    std::vector<bool> m_abPatchedDim{};
135
    bool m_bPatchedDimIsFirst = false;
136
137
  protected:
138
    GDALSubsetArray(
139
        const std::shared_ptr<GDALMDArray> &poParent,
140
        const std::shared_ptr<GDALSubsetGroupSharedResources> &poShared,
141
        const std::string &osContext)
142
0
        : GDALAbstractMDArray(GetParentName(poParent->GetFullName()),
143
0
                              poParent->GetName()),
144
0
          GDALPamMDArray(GetParentName(poParent->GetFullName()),
145
0
                         poParent->GetName(), GDALPamMultiDim::GetPAM(poParent),
146
0
                         osContext),
147
0
          m_poParent(std::move(poParent)), m_poShared(std::move(poShared))
148
0
    {
149
0
        m_apoDims = m_poParent->GetDimensions();
150
0
        for (size_t i = 0; i < m_apoDims.size(); ++i)
151
0
        {
152
0
            auto &poDim = m_apoDims[i];
153
0
            if (poDim->GetFullName() == m_poShared->m_osDimFullName)
154
0
            {
155
0
                m_bPatchedDimIsFirst = (i == 0);
156
0
                poDim = m_poShared->m_poNewDim;
157
0
                m_abPatchedDim.push_back(true);
158
0
            }
159
0
            else
160
0
            {
161
0
                m_abPatchedDim.push_back(false);
162
0
            }
163
0
        }
164
0
    }
165
166
    bool IRead(const GUInt64 *arrayStartIdx, const size_t *count,
167
               const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
168
               const GDALExtendedDataType &bufferDataType,
169
               void *pDstBuffer) const override;
170
171
  public:
172
    static std::shared_ptr<GDALSubsetArray>
173
    Create(const std::shared_ptr<GDALMDArray> &poParent,
174
           const std::shared_ptr<GDALSubsetGroupSharedResources> &poShared,
175
           const std::string &osContext)
176
0
    {
177
0
        auto newAr(std::shared_ptr<GDALSubsetArray>(
178
0
            new GDALSubsetArray(poParent, poShared, osContext)));
179
0
        newAr->SetSelf(newAr);
180
0
        return newAr;
181
0
    }
182
183
    bool IsWritable() const override
184
0
    {
185
0
        return false;
186
0
    }
187
188
    const std::string &GetFilename() const override
189
0
    {
190
0
        return m_poParent->GetFilename();
191
0
    }
192
193
    const std::vector<std::shared_ptr<GDALDimension>> &
194
    GetDimensions() const override
195
0
    {
196
0
        return m_apoDims;
197
0
    }
198
199
    const GDALExtendedDataType &GetDataType() const override
200
0
    {
201
0
        return m_poParent->GetDataType();
202
0
    }
203
204
    const std::string &GetUnit() const override
205
0
    {
206
0
        return m_poParent->GetUnit();
207
0
    }
208
209
    std::shared_ptr<OGRSpatialReference> GetSpatialRef() const override
210
0
    {
211
0
        return m_poParent->GetSpatialRef();
212
0
    }
213
214
    const void *GetRawNoDataValue() const override
215
0
    {
216
0
        return m_poParent->GetRawNoDataValue();
217
0
    }
218
219
    std::vector<GUInt64> GetBlockSize() const override
220
0
    {
221
0
        std::vector<GUInt64> ret(m_poParent->GetBlockSize());
222
0
        for (size_t i = 0; i < m_apoDims.size(); ++i)
223
0
        {
224
0
            if (m_abPatchedDim[i])
225
0
                ret[1] = 1;
226
0
        }
227
0
        return ret;
228
0
    }
229
230
    std::shared_ptr<GDALAttribute>
231
    GetAttribute(const std::string &osName) const override
232
0
    {
233
0
        return m_poParent->GetAttribute(osName);
234
0
    }
235
236
    std::vector<std::shared_ptr<GDALAttribute>>
237
    GetAttributes(CSLConstList papszOptions = nullptr) const override
238
0
    {
239
0
        return m_poParent->GetAttributes(papszOptions);
240
0
    }
241
242
    std::shared_ptr<GDALGroup> GetRootGroup() const override
243
0
    {
244
0
        if (m_poShared->m_poRootGroup)
245
0
        {
246
0
            return GDALSubsetGroup::Create(m_poShared->m_poRootGroup,
247
0
                                           m_poShared);
248
0
        }
249
0
        return nullptr;
250
0
    }
251
};
252
253
/************************************************************************/
254
/*                            OpenMDArray()                             */
255
/************************************************************************/
256
257
std::shared_ptr<GDALMDArray>
258
GDALSubsetGroup::OpenMDArray(const std::string &osName,
259
                             CSLConstList papszOptions) const
260
0
{
261
0
    auto poArray = m_poParent->OpenMDArray(osName, papszOptions);
262
0
    if (poArray)
263
0
    {
264
0
        for (const auto &poDim : poArray->GetDimensions())
265
0
        {
266
0
            if (poDim->GetFullName() == m_poShared->m_osDimFullName)
267
0
            {
268
0
                return GDALSubsetArray::Create(poArray, m_poShared,
269
0
                                               GetContext());
270
0
            }
271
0
        }
272
0
    }
273
0
    return poArray;
274
0
}
275
276
/************************************************************************/
277
/*                             OpenGroup()                              */
278
/************************************************************************/
279
280
std::shared_ptr<GDALGroup>
281
GDALSubsetGroup::OpenGroup(const std::string &osName,
282
                           CSLConstList papszOptions) const
283
0
{
284
0
    auto poSubGroup = m_poParent->OpenGroup(osName, papszOptions);
285
0
    if (poSubGroup)
286
0
    {
287
0
        poSubGroup = GDALSubsetGroup::Create(poSubGroup, m_poShared);
288
0
    }
289
0
    return poSubGroup;
290
0
}
291
292
/************************************************************************/
293
/*                             GetDimensions()                          */
294
/************************************************************************/
295
296
std::vector<std::shared_ptr<GDALDimension>>
297
GDALSubsetGroup::GetDimensions(CSLConstList papszOptions) const
298
0
{
299
0
    auto apoDims = m_poParent->GetDimensions(papszOptions);
300
0
    for (auto &poDim : apoDims)
301
0
    {
302
0
        if (poDim->GetFullName() == m_poShared->m_osDimFullName)
303
0
        {
304
0
            poDim = m_poShared->m_poNewDim;
305
0
        }
306
0
    }
307
0
    return apoDims;
308
0
}
309
310
/************************************************************************/
311
/*                             IRead()                                  */
312
/************************************************************************/
313
314
bool GDALSubsetArray::IRead(const GUInt64 *arrayStartIdx, const size_t *count,
315
                            const GInt64 *arrayStep,
316
                            const GPtrDiff_t *bufferStride,
317
                            const GDALExtendedDataType &bufferDataType,
318
                            void *pDstBuffer) const
319
0
{
320
0
    const auto nDims = m_apoDims.size();
321
0
    std::vector<GUInt64> newArrayStartIdx(nDims);
322
    // the +1 in nDims + 1 is to make happy -Werror=null-dereference when
323
    // doing newCount[0] = 1 and newArrayStep[0] = 1
324
0
    std::vector<size_t> newCount(nDims + 1, 1);
325
0
    std::vector<GInt64> newArrayStep(nDims + 1, 1);
326
0
    const size_t nBufferDTSize = bufferDataType.GetSize();
327
328
0
    if (m_bPatchedDimIsFirst)
329
0
    {
330
        // Optimized case when the only patched dimension is the first one.
331
0
        std::copy_n(arrayStartIdx, nDims, newArrayStartIdx.data());
332
0
        std::copy_n(count, nDims, newCount.data());
333
0
        std::copy_n(arrayStep, nDims, newArrayStep.data());
334
0
        GUInt64 arrayIdx = arrayStartIdx[0];
335
0
        GByte *pabyDstBuffer = static_cast<GByte *>(pDstBuffer);
336
0
#if defined(__GNUC__)
337
0
#pragma GCC diagnostic push
338
0
#pragma GCC diagnostic ignored "-Wnull-dereference"
339
0
#endif
340
0
        newCount[0] = 1;
341
0
        newArrayStep[0] = 1;
342
0
#if defined(__GNUC__)
343
0
#pragma GCC diagnostic pop
344
0
#endif
345
0
        for (size_t i = 0; i < count[0]; ++i)
346
0
        {
347
0
            if (i > 0)
348
0
            {
349
0
                if (arrayStep[0] > 0)
350
0
                    arrayIdx += arrayStep[0];
351
0
                else
352
0
                    arrayIdx -= static_cast<GUInt64>(-arrayStep[0]);
353
0
                pabyDstBuffer += bufferStride[0] * nBufferDTSize;
354
0
            }
355
0
            newArrayStartIdx[0] =
356
0
                m_poShared->m_anMapNewDimToOldDim[static_cast<int>(arrayIdx)];
357
0
            if (!m_poParent->Read(newArrayStartIdx.data(), newCount.data(),
358
0
                                  newArrayStep.data(), bufferStride,
359
0
                                  bufferDataType, pabyDstBuffer))
360
0
            {
361
0
                return false;
362
0
            }
363
0
        }
364
0
        return true;
365
0
    }
366
367
    // Slow/unoptimized case
368
0
    std::vector<size_t> anStackIter(nDims);
369
0
    std::vector<GUInt64> anStackArrayIdx(nDims);
370
0
    std::vector<GByte *> pabyDstBufferStack(nDims + 1);
371
0
#if defined(__GNUC__)
372
0
#pragma GCC diagnostic push
373
0
#pragma GCC diagnostic ignored "-Wnull-dereference"
374
0
#endif
375
0
    pabyDstBufferStack[0] = static_cast<GByte *>(pDstBuffer);
376
0
#if defined(__GNUC__)
377
0
#pragma GCC diagnostic pop
378
0
#endif
379
0
    size_t iDim = 0;
380
0
lbl_next_depth:
381
0
    if (iDim == nDims)
382
0
    {
383
0
        if (!m_poParent->Read(newArrayStartIdx.data(), newCount.data(),
384
0
                              newArrayStep.data(), bufferStride, bufferDataType,
385
0
                              pabyDstBufferStack[iDim]))
386
0
        {
387
0
            return false;
388
0
        }
389
0
    }
390
0
    else
391
0
    {
392
0
        anStackIter[iDim] = 0;
393
0
        anStackArrayIdx[iDim] = arrayStartIdx[iDim];
394
0
        while (true)
395
0
        {
396
0
            if (m_abPatchedDim[iDim])
397
0
            {
398
0
                newArrayStartIdx[iDim] =
399
0
                    m_poShared->m_anMapNewDimToOldDim[static_cast<int>(
400
0
                        anStackArrayIdx[iDim])];
401
0
            }
402
0
            else
403
0
            {
404
0
                newArrayStartIdx[iDim] = anStackArrayIdx[iDim];
405
0
            }
406
0
            ++iDim;
407
0
            pabyDstBufferStack[iDim] = pabyDstBufferStack[iDim - 1];
408
0
            goto lbl_next_depth;
409
0
        lbl_return_to_caller_in_loop:
410
0
            --iDim;
411
0
            ++anStackIter[iDim];
412
0
            if (anStackIter[iDim] == count[iDim])
413
0
                break;
414
0
            if (arrayStep[iDim] > 0)
415
0
                anStackArrayIdx[iDim] += arrayStep[iDim];
416
0
            else
417
0
                anStackArrayIdx[iDim] -= -arrayStep[iDim];
418
0
            pabyDstBufferStack[iDim] += bufferStride[iDim] * nBufferDTSize;
419
0
        }
420
0
    }
421
0
    if (iDim > 0)
422
0
        goto lbl_return_to_caller_in_loop;
423
424
0
    return true;
425
0
}
426
427
/************************************************************************/
428
/*                   SubsetDimensionFromSelection()                     */
429
/************************************************************************/
430
431
/** Return a virtual group whose one dimension has been subset according to a
432
 * selection.
433
 *
434
 * The selection criterion is currently restricted to the form
435
 * "/path/to/array=numeric_value" (no spaces around equal)
436
 *
437
 * This is similar to XArray indexing by name and label on a XArray Dataset
438
 * using the sel() method.
439
 * Cf https://docs.xarray.dev/en/latest/user-guide/indexing.html#quick-overview
440
 *
441
 * For example on a EMIT L2A product
442
 * (https://github.com/nasa/EMIT-Data-Resources/blob/main/python/tutorials/Exploring_EMIT_L2A_Reflectance.ipynb),
443
 * this can be used to keep only valid bands with
444
 * SubsetDimensionFromSelection("/sensor_band_parameters/good_wavelengths=1")
445
 *
446
 * This is the same as the C function GDALGroupSubsetDimensionFromSelection().
447
 *
448
 * @param osSelection Selection criterion.
449
 * @return a virtual group, or nullptr in case of error
450
 * @since 3.8
451
 */
452
std::shared_ptr<GDALGroup>
453
GDALGroup::SubsetDimensionFromSelection(const std::string &osSelection) const
454
0
{
455
0
    auto self = std::dynamic_pointer_cast<GDALGroup>(m_pSelf.lock());
456
0
    if (!self)
457
0
    {
458
0
        CPLError(CE_Failure, CPLE_AppDefined,
459
0
                 "Driver implementation issue: m_pSelf not set !");
460
0
        return nullptr;
461
0
    }
462
463
0
    const auto nEqualPos = osSelection.find('=');
464
0
    if (nEqualPos == std::string::npos)
465
0
    {
466
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for selection");
467
0
        return nullptr;
468
0
    }
469
0
    const auto osArrayName = osSelection.substr(0, nEqualPos);
470
0
    const auto osValue = osSelection.substr(nEqualPos + 1);
471
0
    if (CPLGetValueType(osValue.c_str()) != CPL_VALUE_INTEGER &&
472
0
        CPLGetValueType(osValue.c_str()) != CPL_VALUE_REAL)
473
0
    {
474
0
        CPLError(CE_Failure, CPLE_AppDefined,
475
0
                 "Non-numeric value in selection criterion");
476
0
        return nullptr;
477
0
    }
478
0
    auto poArray = OpenMDArrayFromFullname(osArrayName);
479
0
    if (!poArray)
480
0
    {
481
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find array %s",
482
0
                 osArrayName.c_str());
483
0
        return nullptr;
484
0
    }
485
0
    if (poArray->GetDimensionCount() != 1)
486
0
    {
487
0
        CPLError(CE_Failure, CPLE_AppDefined,
488
0
                 "Array %s is not single dimensional", osArrayName.c_str());
489
0
        return nullptr;
490
0
    }
491
0
    if (poArray->GetDataType().GetClass() != GEDTC_NUMERIC)
492
0
    {
493
0
        CPLError(CE_Failure, CPLE_AppDefined, "Array %s is not of numeric type",
494
0
                 osArrayName.c_str());
495
0
        return nullptr;
496
0
    }
497
498
0
    const auto nElts = poArray->GetTotalElementsCount();
499
0
    if (nElts > 10 * 1024 * 1024)
500
0
    {
501
0
        CPLError(CE_Failure, CPLE_AppDefined, "Too many values in %s",
502
0
                 osArrayName.c_str());
503
0
        return nullptr;
504
0
    }
505
0
    std::vector<double> values;
506
0
    try
507
0
    {
508
0
        values.resize(static_cast<size_t>(nElts));
509
0
    }
510
0
    catch (const std::bad_alloc &e)
511
0
    {
512
0
        CPLError(CE_Failure, CPLE_AppDefined, "Out of memory: %s", e.what());
513
0
        return nullptr;
514
0
    }
515
0
    const GUInt64 startIdx[1] = {0};
516
0
    const size_t count[1] = {values.size()};
517
0
    if (!poArray->Read(startIdx, count, nullptr, nullptr,
518
0
                       GDALExtendedDataType::Create(GDT_Float64), &values[0],
519
0
                       values.data(), values.size() * sizeof(values[0])))
520
0
    {
521
0
        return nullptr;
522
0
    }
523
0
    const double dfSelectionValue = CPLAtof(osValue.c_str());
524
0
    std::vector<int> anMapNewDimToOldDim;
525
0
    for (int i = 0; i < static_cast<int>(nElts); ++i)
526
0
    {
527
0
        if (values[i] == dfSelectionValue)
528
0
            anMapNewDimToOldDim.push_back(i);
529
0
    }
530
0
    if (anMapNewDimToOldDim.empty())
531
0
    {
532
0
        CPLError(CE_Failure, CPLE_AppDefined, "No value in %s matching %f",
533
0
                 osArrayName.c_str(), dfSelectionValue);
534
0
        return nullptr;
535
0
    }
536
0
    if (anMapNewDimToOldDim.size() == nElts)
537
0
    {
538
0
        return self;
539
0
    }
540
541
0
    auto poDim = poArray->GetDimensions()[0];
542
0
    auto poShared = std::make_shared<GDALSubsetGroupSharedResources>();
543
0
    if (GetFullName() == "/")
544
0
        poShared->m_poRootGroup = self;
545
0
    poShared->m_osSelection = osSelection;
546
0
    poShared->m_osDimFullName = poArray->GetDimensions()[0]->GetFullName();
547
0
    poShared->m_anMapNewDimToOldDim = std::move(anMapNewDimToOldDim);
548
549
    // Create a modified dimension of reduced size
550
0
    auto poNewDim = std::make_shared<GDALDimensionWeakIndexingVar>(
551
0
        GetParentName(poDim->GetFullName()), poDim->GetName(), poDim->GetType(),
552
0
        poDim->GetDirection(), poShared->m_anMapNewDimToOldDim.size());
553
0
    poShared->m_poNewDim = poNewDim;
554
555
0
    auto poIndexingVar = poDim->GetIndexingVariable();
556
0
    if (poIndexingVar)
557
0
    {
558
        // poNewIndexingVar must be created with a different GDALSubsetGroupSharedResources
559
        // instance than poShared, to avoid cross reference, that would result in
560
        // objects not being freed !
561
0
        auto poSpecificShared =
562
0
            std::make_shared<GDALSubsetGroupSharedResources>();
563
0
        poSpecificShared->m_poRootGroup = poShared->m_poRootGroup;
564
0
        poSpecificShared->m_osSelection = osSelection;
565
0
        poSpecificShared->m_osDimFullName =
566
0
            poArray->GetDimensions()[0]->GetFullName();
567
0
        poSpecificShared->m_anMapNewDimToOldDim =
568
0
            poShared->m_anMapNewDimToOldDim;
569
0
        poSpecificShared->m_poNewDim = poNewDim;
570
0
        auto poNewIndexingVar =
571
0
            GDALSubsetArray::Create(poIndexingVar, poSpecificShared,
572
0
                                    CreateContext(GetContext(), poShared));
573
0
        poNewDim->SetIndexingVariable(poNewIndexingVar);
574
0
        poShared->m_poNewIndexingVar = poNewIndexingVar;
575
0
    }
576
577
0
    return GDALSubsetGroup::Create(self, poShared);
578
0
}