Coverage Report

Created: 2026-04-10 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_raster_overview_refresh.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "raster overview refresh" subcommand
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_raster_overview_refresh.h"
14
15
#include "cpl_string.h"
16
#include "gdal_priv.h"
17
#include "vrtdataset.h"
18
#include "vrt_priv.h"
19
20
#include <algorithm>
21
#include <limits>
22
23
//! @cond Doxygen_Suppress
24
25
#ifndef _
26
0
#define _(x) (x)
27
#endif
28
29
/************************************************************************/
30
/*                 GDALRasterOverviewAlgorithmRefresh()                 */
31
/************************************************************************/
32
33
GDALRasterOverviewAlgorithmRefresh::GDALRasterOverviewAlgorithmRefresh()
34
0
    : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
35
0
{
36
0
    AddProgressArg();
37
0
    AddOpenOptionsArg(&m_openOptions);
38
0
    AddInputDatasetArg(&m_dataset, GDAL_OF_RASTER | GDAL_OF_UPDATE,
39
0
                       /* positionalAndRequired = */ true,
40
0
                       _("Dataset (to be updated in-place, unless --external)"))
41
0
        .AddAlias("dataset");
42
0
    AddArg("external", 0, _("Refresh external overviews"), &m_readOnly)
43
0
        .AddHiddenAlias("ro")
44
0
        .AddHiddenAlias(GDAL_ARG_NAME_READ_ONLY);
45
46
0
    AddArg("resampling", 'r', _("Resampling method"), &m_resampling)
47
0
        .SetChoices("nearest", "average", "cubic", "cubicspline", "lanczos",
48
0
                    "bilinear", "gauss", "average_magphase", "rms", "mode")
49
0
        .SetHiddenChoices("near", "none");
50
51
0
    AddArg("levels", 0, _("Levels / decimation factors"), &m_levels)
52
0
        .SetMinValueIncluded(2);
53
54
0
    AddBBOXArg(&m_refreshBbox, _("Bounding box to refresh"))
55
0
        .SetMutualExclusionGroup("refresh");
56
0
    AddArg("like", 0, _("Use extent of dataset(s)"), &m_like)
57
0
        .SetMutualExclusionGroup("refresh");
58
0
    AddArg("use-source-timestamp", 0,
59
0
           _("Use timestamp of VRT or GTI sources as refresh criterion"),
60
0
           &m_refreshFromSourceTimestamp)
61
0
        .SetMutualExclusionGroup("refresh");
62
0
}
63
64
/************************************************************************/
65
/*                           PartialRefresh()                           */
66
/************************************************************************/
67
68
static bool PartialRefresh(GDALDataset *poDS,
69
                           const std::vector<int> &anOvrIndices,
70
                           const char *pszResampling, int nXOff, int nYOff,
71
                           int nXSize, int nYSize, GDALProgressFunc pfnProgress,
72
                           void *pProgressArg)
73
0
{
74
0
    int nOvCount = 0;
75
0
    const int nBandCount = poDS->GetRasterCount();
76
0
    for (int i = 0; i < nBandCount; ++i)
77
0
    {
78
0
        auto poSrcBand = poDS->GetRasterBand(i + 1);
79
0
        if (i == 0)
80
0
            nOvCount = poSrcBand->GetOverviewCount();
81
0
        else if (nOvCount != poSrcBand->GetOverviewCount())
82
0
        {
83
0
            CPLError(CE_Failure, CPLE_AppDefined,
84
0
                     "Not same number of overviews on all bands");
85
0
            return false;
86
0
        }
87
0
    }
88
89
0
    std::vector<GDALRasterBand *> apoSrcBands;
90
0
    std::vector<std::vector<GDALRasterBand *>> aapoOverviewBands;
91
0
    for (int i = 0; i < nBandCount; ++i)
92
0
    {
93
0
        auto poSrcBand = poDS->GetRasterBand(i + 1);
94
0
        apoSrcBands.push_back(poSrcBand);
95
0
        std::vector<GDALRasterBand *> apoOverviewBands;
96
0
        for (int nOvrIdx : anOvrIndices)
97
0
        {
98
0
            apoOverviewBands.push_back(poSrcBand->GetOverview(nOvrIdx));
99
0
        }
100
0
        aapoOverviewBands.push_back(std::move(apoOverviewBands));
101
0
    }
102
103
0
    CPLStringList aosOptions;
104
0
    aosOptions.SetNameValue("XOFF", CPLSPrintf("%d", nXOff));
105
0
    aosOptions.SetNameValue("YOFF", CPLSPrintf("%d", nYOff));
106
0
    aosOptions.SetNameValue("XSIZE", CPLSPrintf("%d", nXSize));
107
0
    aosOptions.SetNameValue("YSIZE", CPLSPrintf("%d", nYSize));
108
0
    return GDALRegenerateOverviewsMultiBand(
109
0
               apoSrcBands, aapoOverviewBands, pszResampling, pfnProgress,
110
0
               pProgressArg, aosOptions.List()) == CE_None;
111
0
}
112
113
/************************************************************************/
114
/*                 PartialRefreshFromSourceTimestamp()                  */
115
/************************************************************************/
116
117
static bool
118
PartialRefreshFromSourceTimestamp(GDALDataset *poDS, const char *pszResampling,
119
                                  const std::vector<int> &anOvrIndices,
120
                                  GDALProgressFunc pfnProgress,
121
                                  void *pProgressArg)
122
0
{
123
0
    VSIStatBufL sStatOvr;
124
0
    std::string osOvr(std::string(poDS->GetDescription()) + ".ovr");
125
0
    if (VSIStatL(osOvr.c_str(), &sStatOvr) != 0)
126
0
    {
127
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s", osOvr.c_str());
128
0
        return false;
129
0
    }
130
0
    if (sStatOvr.st_mtime == 0)
131
0
    {
132
0
        CPLError(CE_Failure, CPLE_AppDefined,
133
0
                 "Cannot get modification time of %s", osOvr.c_str());
134
0
        return false;
135
0
    }
136
137
0
    std::vector<GTISourceDesc> regions;
138
139
    // init slightly above zero to please Coverity Scan
140
0
    double dfTotalPixels = std::numeric_limits<double>::min();
141
142
0
    if (dynamic_cast<VRTDataset *>(poDS))
143
0
    {
144
0
        auto poVRTBand =
145
0
            dynamic_cast<VRTSourcedRasterBand *>(poDS->GetRasterBand(1));
146
0
        if (!poVRTBand)
147
0
        {
148
0
            CPLError(CE_Failure, CPLE_AppDefined,
149
0
                     "Band is not a VRTSourcedRasterBand");
150
0
            return false;
151
0
        }
152
153
0
        for (auto &poSource : poVRTBand->m_papoSources)
154
0
        {
155
0
            auto poSimpleSource =
156
0
                dynamic_cast<VRTSimpleSource *>(poSource.get());
157
0
            if (poSimpleSource)
158
0
            {
159
0
                VSIStatBufL sStatSource;
160
0
                if (VSIStatL(poSimpleSource->GetSourceDatasetName().c_str(),
161
0
                             &sStatSource) == 0)
162
0
                {
163
0
                    if (sStatSource.st_mtime > sStatOvr.st_mtime)
164
0
                    {
165
0
                        double dfXOff, dfYOff, dfXSize, dfYSize;
166
0
                        poSimpleSource->GetDstWindow(dfXOff, dfYOff, dfXSize,
167
0
                                                     dfYSize);
168
0
                        constexpr double EPS = 1e-8;
169
0
                        int nXOff = static_cast<int>(dfXOff + EPS);
170
0
                        int nYOff = static_cast<int>(dfYOff + EPS);
171
0
                        int nXSize = static_cast<int>(dfXSize + 0.5);
172
0
                        int nYSize = static_cast<int>(dfYSize + 0.5);
173
0
                        if (!(nXOff > poDS->GetRasterXSize() ||
174
0
                              nYOff > poDS->GetRasterYSize() || nXSize <= 0 ||
175
0
                              nYSize <= 0))
176
0
                        {
177
0
                            if (nXOff < 0)
178
0
                            {
179
0
                                nXSize += nXOff;
180
0
                                nXOff = 0;
181
0
                            }
182
0
                            if (nXOff > poDS->GetRasterXSize() - nXSize)
183
0
                            {
184
0
                                nXSize = poDS->GetRasterXSize() - nXOff;
185
0
                            }
186
0
                            if (nYOff < 0)
187
0
                            {
188
0
                                nYSize += nYOff;
189
0
                                nYOff = 0;
190
0
                            }
191
0
                            if (nYOff > poDS->GetRasterYSize() - nYSize)
192
0
                            {
193
0
                                nYSize = poDS->GetRasterYSize() - nYOff;
194
0
                            }
195
196
0
                            dfTotalPixels +=
197
0
                                static_cast<double>(nXSize) * nYSize;
198
0
                            GTISourceDesc region;
199
0
                            region.osFilename =
200
0
                                poSimpleSource->GetSourceDatasetName();
201
0
                            region.nDstXOff = nXOff;
202
0
                            region.nDstYOff = nYOff;
203
0
                            region.nDstXSize = nXSize;
204
0
                            region.nDstYSize = nYSize;
205
0
                            regions.push_back(std::move(region));
206
0
                        }
207
0
                    }
208
0
                }
209
0
            }
210
0
        }
211
0
    }
212
0
#ifdef GTI_DRIVER_DISABLED_OR_PLUGIN
213
0
    else if (poDS->GetDriver() &&
214
0
             EQUAL(poDS->GetDriver()->GetDescription(), "GTI"))
215
0
    {
216
0
        CPLError(CE_Failure, CPLE_NotSupported,
217
0
                 "--use-source-timestamp only works on a GTI "
218
0
                 "dataset if the GTI driver is not built as a plugin, "
219
0
                 "but in core library");
220
0
        return false;
221
0
    }
222
#else
223
    else if (auto poGTIDS = GDALDatasetCastToGTIDataset(poDS))
224
    {
225
        regions = GTIGetSourcesMoreRecentThan(poGTIDS, sStatOvr.st_mtime);
226
        for (const auto &region : regions)
227
        {
228
            dfTotalPixels +=
229
                static_cast<double>(region.nDstXSize) * region.nDstYSize;
230
        }
231
    }
232
#endif
233
0
    else
234
0
    {
235
0
        CPLError(CE_Failure, CPLE_AppDefined,
236
0
                 "--use-source-timestamp only works on a VRT or GTI "
237
0
                 "dataset");
238
0
        return false;
239
0
    }
240
241
0
    bool bRet = true;
242
0
    if (!regions.empty())
243
0
    {
244
0
        double dfCurPixels = 0;
245
0
        for (const auto &region : regions)
246
0
        {
247
0
            if (bRet)
248
0
            {
249
0
                CPLDebug("GDAL", "Refresh from source %s",
250
0
                         region.osFilename.c_str());
251
0
                double dfNextCurPixels =
252
0
                    dfCurPixels +
253
0
                    static_cast<double>(region.nDstXSize) * region.nDstYSize;
254
0
                void *pScaledProgress = GDALCreateScaledProgress(
255
0
                    dfCurPixels / dfTotalPixels,
256
0
                    dfNextCurPixels / dfTotalPixels, pfnProgress, pProgressArg);
257
0
                bRet = PartialRefresh(
258
0
                    poDS, anOvrIndices, pszResampling, region.nDstXOff,
259
0
                    region.nDstYOff, region.nDstXSize, region.nDstYSize,
260
0
                    pScaledProgress ? GDALScaledProgress : nullptr,
261
0
                    pScaledProgress);
262
0
                GDALDestroyScaledProgress(pScaledProgress);
263
0
                dfCurPixels = dfNextCurPixels;
264
0
            }
265
0
        }
266
0
    }
267
0
    else
268
0
    {
269
0
        CPLDebug("GDAL", "No source is more recent than the overviews");
270
0
    }
271
272
0
    return bRet;
273
0
}
274
275
/************************************************************************/
276
/*                   PartialRefreshFromSourceExtent()                   */
277
/************************************************************************/
278
279
static bool PartialRefreshFromSourceExtent(
280
    GDALDataset *poDS, const std::vector<std::string> &sources,
281
    const char *pszResampling, const std::vector<int> &anOvrIndices,
282
    GDALProgressFunc pfnProgress, void *pProgressArg)
283
0
{
284
0
    GDALGeoTransform gt;
285
0
    if (poDS->GetGeoTransform(gt) != CE_None)
286
0
    {
287
0
        CPLError(CE_Failure, CPLE_AppDefined, "Dataset has no geotransform");
288
0
        return false;
289
0
    }
290
0
    GDALGeoTransform invGT;
291
0
    if (!gt.GetInverse(invGT))
292
0
    {
293
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform");
294
0
        return false;
295
0
    }
296
297
0
    struct Region
298
0
    {
299
0
        std::string osFileName{};
300
0
        int nXOff = 0;
301
0
        int nYOff = 0;
302
0
        int nXSize = 0;
303
0
        int nYSize = 0;
304
0
    };
305
306
0
    std::vector<Region> regions;
307
308
    // init slightly above zero to please Coverity Scan
309
0
    double dfTotalPixels = std::numeric_limits<double>::min();
310
0
    for (const std::string &filename : sources)
311
0
    {
312
0
        auto poSrcDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
313
0
            filename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
314
0
        if (!poSrcDS)
315
0
            return false;
316
317
0
        GDALGeoTransform srcGT;
318
0
        if (poSrcDS->GetGeoTransform(srcGT) != CE_None)
319
0
        {
320
0
            CPLError(CE_Failure, CPLE_AppDefined,
321
0
                     "Source dataset has no geotransform");
322
0
            return false;
323
0
        }
324
325
0
        const double dfULX = srcGT[0];
326
0
        const double dfULY = srcGT[3];
327
0
        const double dfLRX = srcGT[0] + poSrcDS->GetRasterXSize() * srcGT[1] +
328
0
                             poSrcDS->GetRasterYSize() * srcGT[2];
329
0
        const double dfLRY = srcGT[3] + poSrcDS->GetRasterXSize() * srcGT[4] +
330
0
                             poSrcDS->GetRasterYSize() * srcGT[5];
331
0
        const double dfX1 = invGT[0] + invGT[1] * dfULX + invGT[2] * dfULY;
332
0
        const double dfY1 = invGT[3] + invGT[4] * dfULX + invGT[5] * dfULY;
333
0
        const double dfX2 = invGT[0] + invGT[1] * dfLRX + invGT[2] * dfLRY;
334
0
        const double dfY2 = invGT[3] + invGT[4] * dfLRX + invGT[5] * dfLRY;
335
0
        constexpr double EPS = 1e-8;
336
0
        const int nXOff =
337
0
            static_cast<int>(std::max(0.0, std::min(dfX1, dfX2)) + EPS);
338
0
        const int nYOff =
339
0
            static_cast<int>(std::max(0.0, std::min(dfY1, dfY2)) + EPS);
340
0
        const int nXSize =
341
0
            static_cast<int>(
342
0
                std::ceil(std::min(static_cast<double>(poDS->GetRasterXSize()),
343
0
                                   std::max(dfX1, dfX2)) -
344
0
                          EPS)) -
345
0
            nXOff;
346
0
        const int nYSize =
347
0
            static_cast<int>(
348
0
                std::ceil(std::min(static_cast<double>(poDS->GetRasterYSize()),
349
0
                                   std::max(dfY1, dfY2)) -
350
0
                          EPS)) -
351
0
            nYOff;
352
353
0
        dfTotalPixels += static_cast<double>(nXSize) * nYSize;
354
0
        Region region;
355
0
        region.osFileName = filename;
356
0
        region.nXOff = nXOff;
357
0
        region.nYOff = nYOff;
358
0
        region.nXSize = nXSize;
359
0
        region.nYSize = nYSize;
360
0
        regions.push_back(std::move(region));
361
0
    }
362
363
0
    bool bRet = true;
364
0
    double dfCurPixels = 0;
365
0
    for (const auto &region : regions)
366
0
    {
367
0
        if (bRet)
368
0
        {
369
0
            CPLDebug("GDAL", "Refresh from source %s",
370
0
                     region.osFileName.c_str());
371
0
            double dfNextCurPixels =
372
0
                dfCurPixels +
373
0
                static_cast<double>(region.nXSize) * region.nYSize;
374
            // coverity[divide_by_zero]
375
0
            void *pScaledProgress = GDALCreateScaledProgress(
376
0
                dfCurPixels / dfTotalPixels, dfNextCurPixels / dfTotalPixels,
377
0
                pfnProgress, pProgressArg);
378
0
            bRet =
379
0
                PartialRefresh(poDS, anOvrIndices, pszResampling, region.nXOff,
380
0
                               region.nYOff, region.nXSize, region.nYSize,
381
0
                               pScaledProgress ? GDALScaledProgress : nullptr,
382
0
                               pScaledProgress);
383
0
            GDALDestroyScaledProgress(pScaledProgress);
384
0
            dfCurPixels = dfNextCurPixels;
385
0
        }
386
0
    }
387
388
0
    return bRet;
389
0
}
390
391
/************************************************************************/
392
/*                       PartialRefreshFromBBOX()                       */
393
/************************************************************************/
394
395
static bool PartialRefreshFromBBOX(GDALDataset *poDS,
396
                                   const std::vector<double> &bbox,
397
                                   const char *pszResampling,
398
                                   const std::vector<int> &anOvrIndices,
399
                                   GDALProgressFunc pfnProgress,
400
                                   void *pProgressArg)
401
0
{
402
0
    const double dfULX = bbox[0];
403
0
    const double dfLRY = bbox[1];
404
0
    const double dfLRX = bbox[2];
405
0
    const double dfULY = bbox[3];
406
407
0
    GDALGeoTransform gt;
408
0
    if (poDS->GetGeoTransform(gt) != CE_None)
409
0
    {
410
0
        CPLError(CE_Failure, CPLE_AppDefined, "Dataset has no geotransform");
411
0
        return false;
412
0
    }
413
0
    GDALGeoTransform invGT;
414
0
    if (!gt.GetInverse(invGT))
415
0
    {
416
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform");
417
0
        return false;
418
0
    }
419
0
    const double dfX1 = invGT[0] + invGT[1] * dfULX + invGT[2] * dfULY;
420
0
    const double dfY1 = invGT[3] + invGT[4] * dfULX + invGT[5] * dfULY;
421
0
    const double dfX2 = invGT[0] + invGT[1] * dfLRX + invGT[2] * dfLRY;
422
0
    const double dfY2 = invGT[3] + invGT[4] * dfLRX + invGT[5] * dfLRY;
423
0
    constexpr double EPS = 1e-8;
424
0
    const int nXOff =
425
0
        static_cast<int>(std::max(0.0, std::min(dfX1, dfX2)) + EPS);
426
0
    const int nYOff =
427
0
        static_cast<int>(std::max(0.0, std::min(dfY1, dfY2)) + EPS);
428
0
    const int nXSize = static_cast<int>(std::ceil(
429
0
                           std::min(static_cast<double>(poDS->GetRasterXSize()),
430
0
                                    std::max(dfX1, dfX2)) -
431
0
                           EPS)) -
432
0
                       nXOff;
433
0
    const int nYSize = static_cast<int>(std::ceil(
434
0
                           std::min(static_cast<double>(poDS->GetRasterYSize()),
435
0
                                    std::max(dfY1, dfY2)) -
436
0
                           EPS)) -
437
0
                       nYOff;
438
0
    return PartialRefresh(poDS, anOvrIndices, pszResampling, nXOff, nYOff,
439
0
                          nXSize, nYSize, pfnProgress, pProgressArg);
440
0
}
441
442
/************************************************************************/
443
/*            GDALRasterOverviewAlgorithmRefresh::RunImpl()             */
444
/************************************************************************/
445
446
bool GDALRasterOverviewAlgorithmRefresh::RunImpl(GDALProgressFunc pfnProgress,
447
                                                 void *pProgressData)
448
0
{
449
0
    auto poDS = m_dataset.GetDatasetRef();
450
0
    CPLAssert(poDS);
451
0
    if (poDS->GetRasterCount() == 0)
452
0
    {
453
0
        ReportError(CE_Failure, CPLE_AppDefined, "Dataset has no raster band");
454
0
        return false;
455
0
    }
456
457
0
    auto poBand = poDS->GetRasterBand(1);
458
0
    const int nOvCount = poBand->GetOverviewCount();
459
460
0
    std::vector<int> levels = m_levels;
461
462
    // If no levels are specified, reuse the potentially existing ones.
463
0
    if (levels.empty())
464
0
    {
465
0
        for (int iOvr = 0; iOvr < nOvCount; ++iOvr)
466
0
        {
467
0
            auto poOverview = poBand->GetOverview(iOvr);
468
0
            if (poOverview)
469
0
            {
470
0
                const int nOvFactor = GDALComputeOvFactor(
471
0
                    poOverview->GetXSize(), poBand->GetXSize(),
472
0
                    poOverview->GetYSize(), poBand->GetYSize());
473
0
                levels.push_back(nOvFactor);
474
0
            }
475
0
        }
476
0
    }
477
0
    if (levels.empty())
478
0
    {
479
0
        ReportError(CE_Failure, CPLE_AppDefined, "No overviews to refresh");
480
0
        return false;
481
0
    }
482
483
0
    std::vector<int> anOvrIndices;
484
0
    for (int nLevel : levels)
485
0
    {
486
0
        int nIdx = -1;
487
0
        for (int iOvr = 0; iOvr < nOvCount; iOvr++)
488
0
        {
489
0
            auto poOverview = poBand->GetOverview(iOvr);
490
0
            if (poOverview)
491
0
            {
492
0
                const int nOvFactor = GDALComputeOvFactor(
493
0
                    poOverview->GetXSize(), poBand->GetXSize(),
494
0
                    poOverview->GetYSize(), poBand->GetYSize());
495
0
                if (nOvFactor == nLevel ||
496
0
                    nOvFactor == GDALOvLevelAdjust2(nLevel, poBand->GetXSize(),
497
0
                                                    poBand->GetYSize()))
498
0
                {
499
0
                    nIdx = iOvr;
500
0
                    break;
501
0
                }
502
0
            }
503
0
        }
504
0
        if (nIdx < 0)
505
0
        {
506
0
            CPLError(CE_Failure, CPLE_AppDefined,
507
0
                     "Cannot find overview level with subsampling factor of %d",
508
0
                     nLevel);
509
0
            return false;
510
0
        }
511
0
        CPLDebug("GDAL", "Refreshing overview idx %d", nIdx);
512
0
        anOvrIndices.push_back(nIdx);
513
0
    }
514
515
0
    std::string resampling = m_resampling;
516
0
    if (resampling.empty())
517
0
    {
518
0
        const char *pszResampling =
519
0
            poBand->GetOverview(0)->GetMetadataItem("RESAMPLING");
520
0
        if (pszResampling)
521
0
        {
522
0
            resampling = pszResampling;
523
0
            CPLDebug("GDAL",
524
0
                     "Reusing resampling method %s from existing "
525
0
                     "overview",
526
0
                     pszResampling);
527
0
        }
528
0
    }
529
0
    if (resampling.empty())
530
0
        resampling = "nearest";
531
532
0
    if (m_refreshFromSourceTimestamp)
533
0
    {
534
0
        return PartialRefreshFromSourceTimestamp(
535
0
            poDS, resampling.c_str(), anOvrIndices, pfnProgress, pProgressData);
536
0
    }
537
0
    else if (!m_refreshBbox.empty())
538
0
    {
539
0
        return PartialRefreshFromBBOX(poDS, m_refreshBbox, resampling.c_str(),
540
0
                                      anOvrIndices, pfnProgress, pProgressData);
541
0
    }
542
0
    else if (!m_like.empty())
543
0
    {
544
0
        return PartialRefreshFromSourceExtent(poDS, m_like, resampling.c_str(),
545
0
                                              anOvrIndices, pfnProgress,
546
0
                                              pProgressData);
547
0
    }
548
0
    else
549
0
    {
550
0
        return GDALBuildOverviews(
551
0
                   GDALDataset::ToHandle(poDS), resampling.c_str(),
552
0
                   static_cast<int>(levels.size()), levels.data(), 0, nullptr,
553
0
                   pfnProgress, pProgressData) == CE_None;
554
0
    }
555
0
}
556
557
//! @endcond