Coverage Report

Created: 2026-02-14 06:52

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