Coverage Report

Created: 2026-07-25 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalbuildvrt_lib.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL Utilities
4
 * Purpose:  Command line application to build VRT datasets from raster products
5
 *           or content of SHP tile index
6
 * Author:   Even Rouault, <even dot rouault at spatialys dot com>
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2007-2016, Even Rouault <even dot rouault at spatialys dot com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "ogr_api.h"
15
#include "ogr_srs_api.h"
16
17
#include "cpl_port.h"
18
#include "gdal_utils.h"
19
#include "gdal_utils_priv.h"
20
#include "gdalargumentparser.h"
21
22
#include <cassert>
23
#include <cmath>
24
#include <cstdio>
25
#include <cstdlib>
26
#include <cstring>
27
28
#include <algorithm>
29
#include <memory>
30
#include <optional>
31
#include <set>
32
#include <string>
33
#include <vector>
34
35
#include "commonutils.h"
36
#include "cpl_conv.h"
37
#include "cpl_error.h"
38
#include "cpl_float.h"
39
#include "cpl_progress.h"
40
#include "cpl_string.h"
41
#include "cpl_vsi.h"
42
#include "cpl_vsi_virtual.h"
43
#include "gdal.h"
44
#include "gdal_vrt.h"
45
#include "gdal_priv.h"
46
#include "gdal_proxy.h"
47
#include "ogr_api.h"
48
#include "ogr_core.h"
49
#include "ogr_srs_api.h"
50
#include "ogr_spatialref.h"
51
#include "ogrsf_frmts.h"
52
#include "vrtdataset.h"
53
54
0
#define GEOTRSFRM_TOPLEFT_X 0
55
0
#define GEOTRSFRM_WE_RES 1
56
0
#define GEOTRSFRM_ROTATION_PARAM1 2
57
0
#define GEOTRSFRM_TOPLEFT_Y 3
58
0
#define GEOTRSFRM_ROTATION_PARAM2 4
59
0
#define GEOTRSFRM_NS_RES 5
60
61
namespace gdal::GDALBuildVRT
62
{
63
typedef enum
64
{
65
    LOWEST_RESOLUTION,
66
    HIGHEST_RESOLUTION,
67
    AVERAGE_RESOLUTION,
68
    SAME_RESOLUTION,
69
    USER_RESOLUTION,
70
    COMMON_RESOLUTION,
71
} ResolutionStrategy;
72
73
struct DatasetProperty
74
{
75
    int isFileOK = FALSE;
76
    int nRasterXSize = 0;
77
    int nRasterYSize = 0;
78
    GDALGeoTransform gt{};
79
    int nBlockXSize = 0;
80
    int nBlockYSize = 0;
81
    std::vector<GDALDataType> aeBandType{};
82
    std::vector<bool> abHasNoData{};
83
    std::vector<double> adfNoDataValues{};
84
    std::vector<bool> abHasOffset{};
85
    std::vector<double> adfOffset{};
86
    std::vector<bool> abHasScale{};
87
    std::vector<bool> abHasMaskBand{};
88
    std::vector<double> adfScale{};
89
    int bHasDatasetMask = 0;
90
    bool bLastBandIsAlpha = false;
91
    int nMaskBlockXSize = 0;
92
    int nMaskBlockYSize = 0;
93
    std::vector<int> anOverviewFactors{};
94
    std::vector<std::string> aosDescriptions{};
95
    std::map<int, std::map<std::string, std::string>> mapBandMetadata{};
96
};
97
98
struct BandProperty
99
{
100
    GDALColorInterp colorInterpretation = GCI_Undefined;
101
    GDALDataType dataType = GDT_Unknown;
102
    std::unique_ptr<GDALColorTable> colorTable{};
103
    bool bHasNoData = false;
104
    double noDataValue = 0;
105
    bool bHasOffset = false;
106
    double dfOffset = 0;
107
    bool bHasScale = false;
108
    double dfScale = 0;
109
    std::string osDescription = "";
110
    std::map<std::string, std::string> mapBandMetadata{};
111
};
112
}  // namespace gdal::GDALBuildVRT
113
114
using namespace gdal::GDALBuildVRT;
115
116
/************************************************************************/
117
/*                            GetSrcDstWin()                            */
118
/************************************************************************/
119
120
static int GetSrcDstWin(DatasetProperty *psDP, double we_res, double ns_res,
121
                        double minX, double minY, double maxX, double maxY,
122
                        int nTargetXSize, int nTargetYSize, double *pdfSrcXOff,
123
                        double *pdfSrcYOff, double *pdfSrcXSize,
124
                        double *pdfSrcYSize, double *pdfDstXOff,
125
                        double *pdfDstYOff, double *pdfDstXSize,
126
                        double *pdfDstYSize)
127
0
{
128
0
    if (we_res == 0 || ns_res == 0)
129
0
    {
130
        // should not happen. to please Coverity
131
0
        return FALSE;
132
0
    }
133
134
    /* Check that the destination bounding box intersects the source bounding
135
     * box */
136
0
    if (psDP->gt[GEOTRSFRM_TOPLEFT_X] +
137
0
            psDP->nRasterXSize * psDP->gt[GEOTRSFRM_WE_RES] <=
138
0
        minX)
139
0
        return FALSE;
140
0
    if (psDP->gt[GEOTRSFRM_TOPLEFT_X] >= maxX)
141
0
        return FALSE;
142
0
    if (psDP->gt[GEOTRSFRM_TOPLEFT_Y] +
143
0
            psDP->nRasterYSize * psDP->gt[GEOTRSFRM_NS_RES] >=
144
0
        maxY)
145
0
        return FALSE;
146
0
    if (psDP->gt[GEOTRSFRM_TOPLEFT_Y] <= minY)
147
0
        return FALSE;
148
149
0
    if (psDP->gt[GEOTRSFRM_TOPLEFT_X] < minX)
150
0
    {
151
0
        *pdfSrcXOff =
152
0
            (minX - psDP->gt[GEOTRSFRM_TOPLEFT_X]) / psDP->gt[GEOTRSFRM_WE_RES];
153
0
        *pdfDstXOff = 0.0;
154
0
    }
155
0
    else
156
0
    {
157
0
        *pdfSrcXOff = 0.0;
158
0
        *pdfDstXOff = ((psDP->gt[GEOTRSFRM_TOPLEFT_X] - minX) / we_res);
159
0
    }
160
0
    if (maxY < psDP->gt[GEOTRSFRM_TOPLEFT_Y])
161
0
    {
162
0
        *pdfSrcYOff = (psDP->gt[GEOTRSFRM_TOPLEFT_Y] - maxY) /
163
0
                      -psDP->gt[GEOTRSFRM_NS_RES];
164
0
        *pdfDstYOff = 0.0;
165
0
    }
166
0
    else
167
0
    {
168
0
        *pdfSrcYOff = 0.0;
169
0
        *pdfDstYOff = ((maxY - psDP->gt[GEOTRSFRM_TOPLEFT_Y]) / -ns_res);
170
0
    }
171
172
0
    *pdfSrcXSize = psDP->nRasterXSize;
173
0
    *pdfSrcYSize = psDP->nRasterYSize;
174
0
    if (*pdfSrcXOff > 0)
175
0
        *pdfSrcXSize -= *pdfSrcXOff;
176
0
    if (*pdfSrcYOff > 0)
177
0
        *pdfSrcYSize -= *pdfSrcYOff;
178
179
0
    const double dfSrcToDstXSize = psDP->gt[GEOTRSFRM_WE_RES] / we_res;
180
0
    *pdfDstXSize = *pdfSrcXSize * dfSrcToDstXSize;
181
0
    const double dfSrcToDstYSize = psDP->gt[GEOTRSFRM_NS_RES] / ns_res;
182
0
    *pdfDstYSize = *pdfSrcYSize * dfSrcToDstYSize;
183
184
0
    if (*pdfDstXOff + *pdfDstXSize > nTargetXSize)
185
0
    {
186
0
        *pdfDstXSize = nTargetXSize - *pdfDstXOff;
187
0
        *pdfSrcXSize = *pdfDstXSize / dfSrcToDstXSize;
188
0
    }
189
190
0
    if (*pdfDstYOff + *pdfDstYSize > nTargetYSize)
191
0
    {
192
0
        *pdfDstYSize = nTargetYSize - *pdfDstYOff;
193
0
        *pdfSrcYSize = *pdfDstYSize / dfSrcToDstYSize;
194
0
    }
195
196
0
    return *pdfSrcXSize > 0 && *pdfDstXSize > 0 && *pdfSrcYSize > 0 &&
197
0
           *pdfDstYSize > 0;
198
0
}
199
200
/************************************************************************/
201
/*                              VRTBuilder                              */
202
/************************************************************************/
203
204
class VRTBuilder
205
{
206
    /* Input parameters */
207
    bool bStrict = false;
208
    char *pszOutputFilename = nullptr;
209
    int nInputFiles = 0;
210
    char **ppszInputFilenames = nullptr;
211
    int nSrcDSCount = 0;
212
    GDALDatasetH *pahSrcDS = nullptr;
213
    int nTotalBands = 0;
214
    bool bLastBandIsAlpha = false;
215
    bool bExplicitBandList = false;
216
    int nMaxSelectedBandNo = 0;
217
    int nSelectedBands = 0;
218
    int *panSelectedBandList = nullptr;
219
    ResolutionStrategy resolutionStrategy = AVERAGE_RESOLUTION;
220
    int nCountValid = 0;
221
    double we_res = 0;
222
    double ns_res = 0;
223
    int bTargetAlignedPixels = 0;
224
    double minX = 0;
225
    double minY = 0;
226
    double maxX = 0;
227
    double maxY = 0;
228
    int bSeparate = 0;
229
    int bAllowProjectionDifference = 0;
230
    int bAddAlpha = 0;
231
    int bHideNoData = 0;
232
    int nSubdataset = 0;
233
    char *pszSrcNoData = nullptr;
234
    char *pszVRTNoData = nullptr;
235
    char *pszOutputSRS = nullptr;
236
    char *pszResampling = nullptr;
237
    char **papszOpenOptions = nullptr;
238
    bool bUseSrcMaskBand = true;
239
    bool bNoDataFromMask = false;
240
    double dfMaskValueThreshold = 0;
241
    const CPLStringList aosCreateOptions;
242
    std::string osPixelFunction{};
243
    const CPLStringList aosPixelFunctionArgs;
244
    const bool bWriteAbsolutePath;
245
246
    /* Internal variables */
247
    char *pszProjectionRef = nullptr;
248
    std::vector<BandProperty> asBandProperties{};
249
    int bFirst = TRUE;
250
    int bHasGeoTransform = 0;
251
    int nRasterXSize = 0;
252
    int nRasterYSize = 0;
253
    std::vector<DatasetProperty> asDatasetProperties{};
254
    int bUserExtent = 0;
255
    int bAllowSrcNoData = TRUE;
256
    double *padfSrcNoData = nullptr;
257
    int nSrcNoDataCount = 0;
258
    int bAllowVRTNoData = TRUE;
259
    double *padfVRTNoData = nullptr;
260
    int nVRTNoDataCount = 0;
261
    int bHasRunBuild = 0;
262
    int bHasDatasetMask = 0;
263
264
    std::string AnalyseRaster(GDALDatasetH hDS,
265
                              DatasetProperty *psDatasetProperties);
266
267
    void CreateVRTSeparate(VRTDataset *poVTDS);
268
    void CreateVRTNonSeparate(VRTDataset *poVRTDS);
269
270
    CPL_DISALLOW_COPY_ASSIGN(VRTBuilder)
271
272
  public:
273
    VRTBuilder(bool bStrictIn, const char *pszOutputFilename, int nInputFiles,
274
               const char *const *ppszInputFilenames, GDALDatasetH *pahSrcDSIn,
275
               const int *panSelectedBandListIn, int nBandCount,
276
               ResolutionStrategy resolutionStrategy, double we_res,
277
               double ns_res, int bTargetAlignedPixels, double minX,
278
               double minY, double maxX, double maxY, int bSeparate,
279
               int bAllowProjectionDifference, int bAddAlpha, int bHideNoData,
280
               int nSubdataset, const char *pszSrcNoData,
281
               const char *pszVRTNoData, bool bUseSrcMaskBand,
282
               bool bNoDataFromMask, double dfMaskValueThreshold,
283
               const char *pszOutputSRS, const char *pszResampling,
284
               const char *pszPixelFunctionName,
285
               const CPLStringList &aosPixelFunctionArgs,
286
               const char *const *papszOpenOptionsIn,
287
               const CPLStringList &aosCreateOptionsIn,
288
               bool bWriteAbsolutePathIn);
289
290
    ~VRTBuilder();
291
292
    std::unique_ptr<GDALDataset> Build(GDALProgressFunc pfnProgress,
293
                                       void *pProgressData);
294
295
    std::string m_osProgramName{};
296
};
297
298
/************************************************************************/
299
/*                             VRTBuilder()                             */
300
/************************************************************************/
301
302
VRTBuilder::VRTBuilder(
303
    bool bStrictIn, const char *pszOutputFilenameIn, int nInputFilesIn,
304
    const char *const *ppszInputFilenamesIn, GDALDatasetH *pahSrcDSIn,
305
    const int *panSelectedBandListIn, int nBandCount,
306
    ResolutionStrategy resolutionStrategyIn, double we_resIn, double ns_resIn,
307
    int bTargetAlignedPixelsIn, double minXIn, double minYIn, double maxXIn,
308
    double maxYIn, int bSeparateIn, int bAllowProjectionDifferenceIn,
309
    int bAddAlphaIn, int bHideNoDataIn, int nSubdatasetIn,
310
    const char *pszSrcNoDataIn, const char *pszVRTNoDataIn,
311
    bool bUseSrcMaskBandIn, bool bNoDataFromMaskIn,
312
    double dfMaskValueThresholdIn, const char *pszOutputSRSIn,
313
    const char *pszResamplingIn, const char *pszPixelFunctionIn,
314
    const CPLStringList &aosPixelFunctionArgsIn,
315
    const char *const *papszOpenOptionsIn,
316
    const CPLStringList &aosCreateOptionsIn, bool bWriteAbsolutePathIn)
317
0
    : bStrict(bStrictIn), aosCreateOptions(aosCreateOptionsIn),
318
0
      aosPixelFunctionArgs(aosPixelFunctionArgsIn),
319
0
      bWriteAbsolutePath(bWriteAbsolutePathIn)
320
0
{
321
0
    pszOutputFilename = CPLStrdup(pszOutputFilenameIn);
322
0
    nInputFiles = nInputFilesIn;
323
0
    papszOpenOptions = CSLDuplicate(const_cast<char **>(papszOpenOptionsIn));
324
325
0
    if (pszPixelFunctionIn != nullptr)
326
0
    {
327
0
        osPixelFunction = pszPixelFunctionIn;
328
0
    }
329
330
0
    if (ppszInputFilenamesIn)
331
0
    {
332
0
        ppszInputFilenames =
333
0
            static_cast<char **>(CPLMalloc(nInputFiles * sizeof(char *)));
334
0
        for (int i = 0; i < nInputFiles; i++)
335
0
        {
336
0
            ppszInputFilenames[i] = CPLStrdup(ppszInputFilenamesIn[i]);
337
0
        }
338
0
    }
339
0
    else if (pahSrcDSIn)
340
0
    {
341
0
        nSrcDSCount = nInputFiles;
342
0
        pahSrcDS = static_cast<GDALDatasetH *>(
343
0
            CPLMalloc(nInputFiles * sizeof(GDALDatasetH)));
344
0
        memcpy(pahSrcDS, pahSrcDSIn, nInputFiles * sizeof(GDALDatasetH));
345
0
        ppszInputFilenames =
346
0
            static_cast<char **>(CPLMalloc(nInputFiles * sizeof(char *)));
347
0
        for (int i = 0; i < nInputFiles; i++)
348
0
        {
349
0
            ppszInputFilenames[i] =
350
0
                CPLStrdup(GDALGetDescription(pahSrcDSIn[i]));
351
0
        }
352
0
    }
353
354
0
    bExplicitBandList = nBandCount != 0;
355
0
    nSelectedBands = nBandCount;
356
0
    if (nBandCount)
357
0
    {
358
0
        panSelectedBandList =
359
0
            static_cast<int *>(CPLMalloc(nSelectedBands * sizeof(int)));
360
0
        memcpy(panSelectedBandList, panSelectedBandListIn,
361
0
               nSelectedBands * sizeof(int));
362
0
    }
363
364
0
    resolutionStrategy = resolutionStrategyIn;
365
0
    we_res = we_resIn;
366
0
    ns_res = ns_resIn;
367
0
    bTargetAlignedPixels = bTargetAlignedPixelsIn;
368
0
    minX = minXIn;
369
0
    minY = minYIn;
370
0
    maxX = maxXIn;
371
0
    maxY = maxYIn;
372
0
    bSeparate = bSeparateIn;
373
0
    bAllowProjectionDifference = bAllowProjectionDifferenceIn;
374
0
    bAddAlpha = bAddAlphaIn;
375
0
    bHideNoData = bHideNoDataIn;
376
0
    nSubdataset = nSubdatasetIn;
377
0
    pszSrcNoData = (pszSrcNoDataIn) ? CPLStrdup(pszSrcNoDataIn) : nullptr;
378
0
    pszVRTNoData = (pszVRTNoDataIn) ? CPLStrdup(pszVRTNoDataIn) : nullptr;
379
0
    pszOutputSRS = (pszOutputSRSIn) ? CPLStrdup(pszOutputSRSIn) : nullptr;
380
0
    pszResampling = (pszResamplingIn) ? CPLStrdup(pszResamplingIn) : nullptr;
381
0
    bUseSrcMaskBand = bUseSrcMaskBandIn;
382
0
    bNoDataFromMask = bNoDataFromMaskIn;
383
0
    dfMaskValueThreshold = dfMaskValueThresholdIn;
384
0
}
385
386
/************************************************************************/
387
/*                            ~VRTBuilder()                             */
388
/************************************************************************/
389
390
VRTBuilder::~VRTBuilder()
391
0
{
392
0
    CPLFree(pszOutputFilename);
393
0
    CPLFree(pszSrcNoData);
394
0
    CPLFree(pszVRTNoData);
395
0
    CPLFree(panSelectedBandList);
396
397
0
    if (ppszInputFilenames)
398
0
    {
399
0
        for (int i = 0; i < nInputFiles; i++)
400
0
        {
401
0
            CPLFree(ppszInputFilenames[i]);
402
0
        }
403
0
    }
404
0
    CPLFree(ppszInputFilenames);
405
0
    CPLFree(pahSrcDS);
406
407
0
    CPLFree(pszProjectionRef);
408
0
    CPLFree(padfSrcNoData);
409
0
    CPLFree(padfVRTNoData);
410
0
    CPLFree(pszOutputSRS);
411
0
    CPLFree(pszResampling);
412
0
    CSLDestroy(papszOpenOptions);
413
0
}
414
415
/************************************************************************/
416
/*                            ProjAreEqual()                            */
417
/************************************************************************/
418
419
static int ProjAreEqual(const char *pszWKT1, const char *pszWKT2)
420
0
{
421
0
    if (EQUAL(pszWKT1, pszWKT2))
422
0
        return TRUE;
423
424
0
    OGRSpatialReferenceH hSRS1 = OSRNewSpatialReference(pszWKT1);
425
0
    OGRSpatialReferenceH hSRS2 = OSRNewSpatialReference(pszWKT2);
426
0
    int bRet = hSRS1 != nullptr && hSRS2 != nullptr && OSRIsSame(hSRS1, hSRS2);
427
0
    if (hSRS1)
428
0
        OSRDestroySpatialReference(hSRS1);
429
0
    if (hSRS2)
430
0
        OSRDestroySpatialReference(hSRS2);
431
0
    return bRet;
432
0
}
433
434
/************************************************************************/
435
/*                         GetProjectionName()                          */
436
/************************************************************************/
437
438
static CPLString GetProjectionName(const char *pszProjection)
439
0
{
440
0
    if (!pszProjection)
441
0
        return "(null)";
442
443
0
    OGRSpatialReference oSRS;
444
0
    oSRS.SetFromUserInput(pszProjection);
445
446
0
    const char *pszName = oSRS.GetName();
447
0
    return pszName ? pszName : "(null)";
448
0
}
449
450
/************************************************************************/
451
/*                         checkNoDataValues()                          */
452
/************************************************************************/
453
454
static void checkNoDataValues(const std::vector<BandProperty> &asProperties)
455
0
{
456
0
    for (const auto &oProps : asProperties)
457
0
    {
458
0
        if (oProps.bHasNoData && GDALDataTypeIsInteger(oProps.dataType) &&
459
0
            !GDALIsValueExactAs(oProps.noDataValue, oProps.dataType))
460
0
        {
461
0
            CPLError(CE_Warning, CPLE_NotSupported,
462
0
                     "Band data type of %s cannot represent the specified "
463
0
                     "NoData value of %g",
464
0
                     GDALGetDataTypeName(oProps.dataType), oProps.noDataValue);
465
0
        }
466
0
    }
467
0
}
468
469
/************************************************************************/
470
/*                           AnalyseRaster()                            */
471
/************************************************************************/
472
473
std::string VRTBuilder::AnalyseRaster(GDALDatasetH hDS,
474
                                      DatasetProperty *psDatasetProperties)
475
0
{
476
0
    GDALDataset *poDS = GDALDataset::FromHandle(hDS);
477
0
    const char *dsFileName = poDS->GetDescription();
478
0
    CSLConstList papszMetadata = poDS->GetMetadata(GDAL_MDD_SUBDATASETS);
479
0
    if (CSLCount(papszMetadata) > 0 && poDS->GetRasterCount() == 0)
480
0
    {
481
0
        ppszInputFilenames = static_cast<char **>(CPLRealloc(
482
0
            ppszInputFilenames,
483
0
            sizeof(char *) * (nInputFiles + CSLCount(papszMetadata))));
484
0
        if (nSubdataset < 0)
485
0
        {
486
0
            int count = 1;
487
0
            char subdatasetNameKey[80];
488
0
            snprintf(subdatasetNameKey, sizeof(subdatasetNameKey),
489
0
                     "SUBDATASET_%d_NAME", count);
490
0
            while (*papszMetadata != nullptr)
491
0
            {
492
0
                if (EQUALN(*papszMetadata, subdatasetNameKey,
493
0
                           strlen(subdatasetNameKey)))
494
0
                {
495
0
                    asDatasetProperties.resize(nInputFiles + 1);
496
0
                    ppszInputFilenames[nInputFiles] = CPLStrdup(
497
0
                        *papszMetadata + strlen(subdatasetNameKey) + 1);
498
0
                    nInputFiles++;
499
0
                    count++;
500
0
                    snprintf(subdatasetNameKey, sizeof(subdatasetNameKey),
501
0
                             "SUBDATASET_%d_NAME", count);
502
0
                }
503
0
                papszMetadata++;
504
0
            }
505
0
        }
506
0
        else
507
0
        {
508
0
            char subdatasetNameKey[80];
509
0
            const char *pszSubdatasetName;
510
511
0
            snprintf(subdatasetNameKey, sizeof(subdatasetNameKey),
512
0
                     "SUBDATASET_%d_NAME", nSubdataset);
513
0
            pszSubdatasetName =
514
0
                CSLFetchNameValue(papszMetadata, subdatasetNameKey);
515
0
            if (pszSubdatasetName)
516
0
            {
517
0
                asDatasetProperties.resize(nInputFiles + 1);
518
0
                ppszInputFilenames[nInputFiles] = CPLStrdup(pszSubdatasetName);
519
0
                nInputFiles++;
520
0
            }
521
0
        }
522
0
        return "SILENTLY_IGNORE";
523
0
    }
524
525
0
    const char *proj = poDS->GetProjectionRef();
526
0
    auto &gt = psDatasetProperties->gt;
527
0
    int bGotGeoTransform = poDS->GetGeoTransform(gt) == CE_None;
528
0
    if (bSeparate)
529
0
    {
530
0
        std::string osProgramName(m_osProgramName);
531
0
        if (osProgramName == "gdalbuildvrt")
532
0
            osProgramName += " -separate";
533
534
0
        if (bFirst)
535
0
        {
536
0
            bHasGeoTransform = bGotGeoTransform;
537
0
            if (!bHasGeoTransform)
538
0
            {
539
0
                if (bUserExtent)
540
0
                {
541
0
                    CPLError(CE_Warning, CPLE_NotSupported, "%s",
542
0
                             ("User extent ignored by " + osProgramName +
543
0
                              "with ungeoreferenced images.")
544
0
                                 .c_str());
545
0
                }
546
0
                if (resolutionStrategy == USER_RESOLUTION)
547
0
                {
548
0
                    CPLError(CE_Warning, CPLE_NotSupported, "%s",
549
0
                             ("User resolution ignored by " + osProgramName +
550
0
                              " with ungeoreferenced images.")
551
0
                                 .c_str());
552
0
                }
553
0
            }
554
0
        }
555
0
        else if (bHasGeoTransform != bGotGeoTransform)
556
0
        {
557
0
            return osProgramName + " cannot stack ungeoreferenced and "
558
0
                                   "georeferenced images.";
559
0
        }
560
0
        else if (!bHasGeoTransform && (nRasterXSize != poDS->GetRasterXSize() ||
561
0
                                       nRasterYSize != poDS->GetRasterYSize()))
562
0
        {
563
0
            return osProgramName + " cannot stack ungeoreferenced images "
564
0
                                   "that have not the same dimensions.";
565
0
        }
566
0
    }
567
0
    else
568
0
    {
569
0
        if (!bGotGeoTransform)
570
0
        {
571
0
            return m_osProgramName + " does not support ungeoreferenced image.";
572
0
        }
573
0
        bHasGeoTransform = TRUE;
574
0
    }
575
576
0
    if (bGotGeoTransform)
577
0
    {
578
0
        if (gt[GEOTRSFRM_ROTATION_PARAM1] != 0 ||
579
0
            gt[GEOTRSFRM_ROTATION_PARAM2] != 0)
580
0
        {
581
0
            return m_osProgramName +
582
0
                   " does not support rotated geo transforms.";
583
0
        }
584
0
        if (gt[GEOTRSFRM_NS_RES] >= 0)
585
0
        {
586
0
            return m_osProgramName +
587
0
                   " does not support positive NS resolution.";
588
0
        }
589
0
    }
590
591
0
    psDatasetProperties->nRasterXSize = poDS->GetRasterXSize();
592
0
    psDatasetProperties->nRasterYSize = poDS->GetRasterYSize();
593
0
    if (bFirst && bSeparate && !bGotGeoTransform)
594
0
    {
595
0
        nRasterXSize = poDS->GetRasterXSize();
596
0
        nRasterYSize = poDS->GetRasterYSize();
597
0
    }
598
599
0
    double ds_minX = gt[GEOTRSFRM_TOPLEFT_X];
600
0
    double ds_maxY = gt[GEOTRSFRM_TOPLEFT_Y];
601
0
    double ds_maxX = ds_minX + GDALGetRasterXSize(hDS) * gt[GEOTRSFRM_WE_RES];
602
0
    double ds_minY = ds_maxY + GDALGetRasterYSize(hDS) * gt[GEOTRSFRM_NS_RES];
603
604
0
    int _nBands = GDALGetRasterCount(hDS);
605
0
    if (_nBands == 0)
606
0
    {
607
0
        return "Dataset has no bands";
608
0
    }
609
0
    if (bNoDataFromMask &&
610
0
        poDS->GetRasterBand(_nBands)->GetColorInterpretation() == GCI_AlphaBand)
611
0
        _nBands--;
612
613
0
    GDALRasterBand *poFirstBand = poDS->GetRasterBand(1);
614
0
    poFirstBand->GetBlockSize(&psDatasetProperties->nBlockXSize,
615
0
                              &psDatasetProperties->nBlockYSize);
616
617
    /* For the -separate case */
618
0
    psDatasetProperties->aeBandType.resize(_nBands);
619
0
    psDatasetProperties->aosDescriptions.resize(_nBands);
620
621
0
    psDatasetProperties->adfNoDataValues.resize(_nBands);
622
0
    psDatasetProperties->abHasNoData.resize(_nBands);
623
624
0
    psDatasetProperties->adfOffset.resize(_nBands);
625
0
    psDatasetProperties->abHasOffset.resize(_nBands);
626
627
0
    psDatasetProperties->adfScale.resize(_nBands);
628
0
    psDatasetProperties->abHasScale.resize(_nBands);
629
630
0
    psDatasetProperties->abHasMaskBand.resize(_nBands);
631
632
0
    psDatasetProperties->bHasDatasetMask =
633
0
        poFirstBand->GetMaskFlags() == GMF_PER_DATASET;
634
0
    if (psDatasetProperties->bHasDatasetMask)
635
0
        bHasDatasetMask = TRUE;
636
0
    poFirstBand->GetMaskBand()->GetBlockSize(
637
0
        &psDatasetProperties->nMaskBlockXSize,
638
0
        &psDatasetProperties->nMaskBlockYSize);
639
640
0
    psDatasetProperties->bLastBandIsAlpha = false;
641
0
    if (poDS->GetRasterBand(_nBands)->GetColorInterpretation() == GCI_AlphaBand)
642
0
        psDatasetProperties->bLastBandIsAlpha = true;
643
644
    // Collect overview factors. We only handle power-of-two situations for now
645
0
    const int nOverviews = poFirstBand->GetOverviewCount();
646
0
    int nExpectedOvFactor = 2;
647
0
    for (int j = 0; j < nOverviews; j++)
648
0
    {
649
0
        GDALRasterBand *poOverview = poFirstBand->GetOverview(j);
650
0
        if (!poOverview)
651
0
            continue;
652
0
        if (poOverview->GetXSize() < 128 && poOverview->GetYSize() < 128)
653
0
        {
654
0
            break;
655
0
        }
656
657
0
        const int nOvFactor = GDALComputeOvFactor(
658
0
            poOverview->GetXSize(), poFirstBand->GetXSize(),
659
0
            poOverview->GetYSize(), poFirstBand->GetYSize());
660
661
0
        if (nOvFactor != nExpectedOvFactor)
662
0
            break;
663
664
0
        psDatasetProperties->anOverviewFactors.push_back(nOvFactor);
665
0
        nExpectedOvFactor *= 2;
666
0
    }
667
668
0
    for (int j = 0; j < _nBands; j++)
669
0
    {
670
0
        GDALRasterBand *poBand = poDS->GetRasterBand(j + 1);
671
672
0
        psDatasetProperties->aeBandType[j] = poBand->GetRasterDataType();
673
674
        // Only used by separate mode
675
0
        if (bSeparate)
676
0
        {
677
0
            psDatasetProperties->aosDescriptions[j] = poBand->GetDescription();
678
            // Add metadata items
679
0
            CSLConstList papszMD(poBand->GetMetadata());
680
0
            for (const auto &[pszKey, pszValue] :
681
0
                 cpl::IterateNameValue(papszMD))
682
0
            {
683
0
                psDatasetProperties->mapBandMetadata[j][pszKey] = pszValue;
684
0
            }
685
0
        }
686
687
0
        if (!bSeparate && nSrcNoDataCount > 0)
688
0
        {
689
0
            psDatasetProperties->abHasNoData[j] = true;
690
0
            if (j < nSrcNoDataCount)
691
0
                psDatasetProperties->adfNoDataValues[j] = padfSrcNoData[j];
692
0
            else
693
0
                psDatasetProperties->adfNoDataValues[j] =
694
0
                    padfSrcNoData[nSrcNoDataCount - 1];
695
0
        }
696
0
        else
697
0
        {
698
0
            int bHasNoData = false;
699
0
            psDatasetProperties->adfNoDataValues[j] =
700
0
                poBand->GetNoDataValue(&bHasNoData);
701
0
            psDatasetProperties->abHasNoData[j] = bHasNoData != 0;
702
0
        }
703
704
0
        int bHasOffset = false;
705
0
        psDatasetProperties->adfOffset[j] = poBand->GetOffset(&bHasOffset);
706
0
        psDatasetProperties->abHasOffset[j] =
707
0
            bHasOffset != 0 && psDatasetProperties->adfOffset[j] != 0.0;
708
709
0
        int bHasScale = false;
710
0
        psDatasetProperties->adfScale[j] = poBand->GetScale(&bHasScale);
711
0
        psDatasetProperties->abHasScale[j] =
712
0
            bHasScale != 0 && psDatasetProperties->adfScale[j] != 1.0;
713
714
0
        const int nMaskFlags = poBand->GetMaskFlags();
715
0
        psDatasetProperties->abHasMaskBand[j] =
716
0
            (nMaskFlags != GMF_ALL_VALID && nMaskFlags != GMF_NODATA) ||
717
0
            poBand->GetColorInterpretation() == GCI_AlphaBand;
718
0
    }
719
720
0
    if (bSeparate)
721
0
    {
722
0
        for (int j = 0; j < nSelectedBands; j++)
723
0
        {
724
0
            if (panSelectedBandList[j] > _nBands)
725
0
            {
726
0
                return CPLSPrintf("%s has %d bands, but %d is requested",
727
0
                                  dsFileName, _nBands, panSelectedBandList[j]);
728
0
            }
729
0
        }
730
0
    }
731
732
0
    if (bFirst)
733
0
    {
734
0
        nTotalBands = _nBands;
735
0
        if (bAddAlpha && psDatasetProperties->bLastBandIsAlpha)
736
0
        {
737
0
            bLastBandIsAlpha = true;
738
0
            nTotalBands--;
739
0
        }
740
741
0
        if (proj)
742
0
            pszProjectionRef = CPLStrdup(proj);
743
0
        if (!bUserExtent)
744
0
        {
745
0
            minX = ds_minX;
746
0
            minY = ds_minY;
747
0
            maxX = ds_maxX;
748
0
            maxY = ds_maxY;
749
0
        }
750
751
0
        if (!bSeparate)
752
0
        {
753
            // if not provided an explicit band list, take the one of the first
754
            // dataset
755
0
            if (nSelectedBands == 0)
756
0
            {
757
0
                nSelectedBands = nTotalBands;
758
0
                CPLFree(panSelectedBandList);
759
0
                panSelectedBandList =
760
0
                    static_cast<int *>(CPLMalloc(nSelectedBands * sizeof(int)));
761
0
                for (int j = 0; j < nSelectedBands; j++)
762
0
                {
763
0
                    panSelectedBandList[j] = j + 1;
764
0
                }
765
0
            }
766
0
            for (int j = 0; j < nSelectedBands; j++)
767
0
            {
768
0
                nMaxSelectedBandNo =
769
0
                    std::max(nMaxSelectedBandNo, panSelectedBandList[j]);
770
0
            }
771
772
0
            asBandProperties.resize(nSelectedBands);
773
0
            for (int j = 0; j < nSelectedBands; j++)
774
0
            {
775
0
                const int nSelBand = panSelectedBandList[j];
776
0
                if (nSelBand <= 0 || nSelBand > nTotalBands)
777
0
                {
778
0
                    return CPLSPrintf("Invalid band number: %d", nSelBand);
779
0
                }
780
0
                GDALRasterBand *poBand = poDS->GetRasterBand(nSelBand);
781
0
                asBandProperties[j].colorInterpretation =
782
0
                    poBand->GetColorInterpretation();
783
0
                asBandProperties[j].dataType = poBand->GetRasterDataType();
784
0
                asBandProperties[j].osDescription = poBand->GetDescription();
785
                // Add metadata items
786
0
                const CSLConstList aosMD(poBand->GetMetadata());
787
0
                for (const auto &[pszKey, pszValue] :
788
0
                     cpl::IterateNameValue(aosMD))
789
0
                {
790
0
                    asBandProperties[j].mapBandMetadata[pszKey] = pszValue;
791
0
                }
792
793
0
                if (asBandProperties[j].colorInterpretation == GCI_PaletteIndex)
794
0
                {
795
0
                    auto colorTable = poBand->GetColorTable();
796
0
                    if (colorTable)
797
0
                    {
798
0
                        asBandProperties[j].colorTable.reset(
799
0
                            colorTable->Clone());
800
0
                    }
801
0
                }
802
0
                else
803
0
                    asBandProperties[j].colorTable = nullptr;
804
805
0
                if (nVRTNoDataCount > 0)
806
0
                {
807
0
                    asBandProperties[j].bHasNoData = true;
808
0
                    if (j < nVRTNoDataCount)
809
0
                        asBandProperties[j].noDataValue = padfVRTNoData[j];
810
0
                    else
811
0
                        asBandProperties[j].noDataValue =
812
0
                            padfVRTNoData[nVRTNoDataCount - 1];
813
0
                }
814
0
                else
815
0
                {
816
0
                    int bHasNoData = false;
817
0
                    asBandProperties[j].noDataValue =
818
0
                        poBand->GetNoDataValue(&bHasNoData);
819
0
                    asBandProperties[j].bHasNoData = bHasNoData != 0;
820
0
                }
821
822
0
                int bHasOffset = false;
823
0
                asBandProperties[j].dfOffset = poBand->GetOffset(&bHasOffset);
824
0
                asBandProperties[j].bHasOffset =
825
0
                    bHasOffset != 0 && asBandProperties[j].dfOffset != 0.0;
826
827
0
                int bHasScale = false;
828
0
                asBandProperties[j].dfScale = poBand->GetScale(&bHasScale);
829
0
                asBandProperties[j].bHasScale =
830
0
                    bHasScale != 0 && asBandProperties[j].dfScale != 1.0;
831
0
            }
832
0
        }
833
0
    }
834
0
    else
835
0
    {
836
0
        if ((proj != nullptr && pszProjectionRef == nullptr) ||
837
0
            (proj == nullptr && pszProjectionRef != nullptr) ||
838
0
            (proj != nullptr && pszProjectionRef != nullptr &&
839
0
             ProjAreEqual(proj, pszProjectionRef) == FALSE))
840
0
        {
841
0
            if (!bAllowProjectionDifference)
842
0
            {
843
0
                CPLString osExpected = GetProjectionName(pszProjectionRef);
844
0
                CPLString osGot = GetProjectionName(proj);
845
0
                return m_osProgramName +
846
0
                       CPLSPrintf(" does not support heterogeneous "
847
0
                                  "projection: expected \"%s\", got \"%s\".",
848
0
                                  osExpected.c_str(), osGot.c_str());
849
0
            }
850
0
        }
851
0
        if (!bSeparate)
852
0
        {
853
0
            if (!bExplicitBandList && _nBands != nTotalBands)
854
0
            {
855
0
                if (bAddAlpha && _nBands == nTotalBands + 1 &&
856
0
                    psDatasetProperties->bLastBandIsAlpha)
857
0
                {
858
0
                    bLastBandIsAlpha = true;
859
0
                }
860
0
                else
861
0
                {
862
0
                    return m_osProgramName +
863
0
                           CPLSPrintf(" does not support heterogeneous band "
864
0
                                      "numbers: expected %d, got %d.",
865
0
                                      nTotalBands, _nBands);
866
0
                }
867
0
            }
868
0
            else if (bExplicitBandList && _nBands < nMaxSelectedBandNo)
869
0
            {
870
0
                return m_osProgramName +
871
0
                       CPLSPrintf(" does not support heterogeneous band "
872
0
                                  "numbers: expected at least %d, got %d.",
873
0
                                  nMaxSelectedBandNo, _nBands);
874
0
            }
875
876
0
            for (int j = 0; j < nSelectedBands; j++)
877
0
            {
878
0
                const int nSelBand = panSelectedBandList[j];
879
0
                CPLAssert(nSelBand >= 1 && nSelBand <= _nBands);
880
0
                GDALRasterBand *poBand = poDS->GetRasterBand(nSelBand);
881
                // In normal mode we only preserve description if identical across
882
0
                if (asBandProperties[j].osDescription !=
883
0
                    poBand->GetDescription())
884
0
                {
885
0
                    asBandProperties[j].osDescription = "";
886
0
                }
887
                // same for metadata
888
0
                const CPLStringList aosMD(poBand->GetMetadata());
889
0
                std::vector<std::string> keysToErase;
890
0
                for (const auto &[pszKey, pszValue] :
891
0
                     cpl::IterateNameValue(aosMD))
892
0
                {
893
0
                    const auto &existingValue =
894
0
                        asBandProperties[j].mapBandMetadata[pszKey];
895
0
                    if (existingValue.empty() ||
896
0
                        !EQUAL(existingValue.c_str(), pszValue))
897
0
                    {
898
0
                        keysToErase.push_back(pszKey);
899
0
                    }
900
0
                }
901
                // Also expand keysToErase to those that are not in the current band
902
0
                for (const auto &pair : asBandProperties[j].mapBandMetadata)
903
0
                {
904
0
                    if (aosMD.FetchNameValue(pair.first.c_str()) == nullptr)
905
0
                    {
906
0
                        keysToErase.push_back(pair.first);
907
0
                    }
908
0
                }
909
910
0
                for (const auto &key : keysToErase)
911
0
                {
912
0
                    asBandProperties[j].mapBandMetadata.erase(key);
913
0
                }
914
915
0
                if (asBandProperties[j].colorInterpretation !=
916
0
                    poBand->GetColorInterpretation())
917
0
                {
918
0
                    return m_osProgramName +
919
0
                           CPLSPrintf(
920
0
                               " does not support heterogeneous "
921
0
                               "band color interpretation: expected %s, got "
922
0
                               "%s.",
923
0
                               GDALGetColorInterpretationName(
924
0
                                   asBandProperties[j].colorInterpretation),
925
0
                               GDALGetColorInterpretationName(
926
0
                                   poBand->GetColorInterpretation()));
927
0
                }
928
0
                if (asBandProperties[j].dataType != poBand->GetRasterDataType())
929
0
                {
930
0
                    return m_osProgramName +
931
0
                           CPLSPrintf(" does not support heterogeneous "
932
0
                                      "band data type: expected %s, got %s.",
933
0
                                      GDALGetDataTypeName(
934
0
                                          asBandProperties[j].dataType),
935
0
                                      GDALGetDataTypeName(
936
0
                                          poBand->GetRasterDataType()));
937
0
                }
938
0
                if (asBandProperties[j].colorTable)
939
0
                {
940
0
                    const GDALColorTable *colorTable = poBand->GetColorTable();
941
0
                    int nRefColorEntryCount =
942
0
                        asBandProperties[j].colorTable->GetColorEntryCount();
943
0
                    if (colorTable == nullptr ||
944
0
                        (colorTable->GetColorEntryCount() !=
945
0
                             nRefColorEntryCount &&
946
                         // For NITF CADRG tiles that may have 256 or 257 colors,
947
                         // with the 257th color when present being transparent
948
0
                         !(std::min(colorTable->GetColorEntryCount(),
949
0
                                    nRefColorEntryCount) +
950
0
                               1 ==
951
0
                           std::max(colorTable->GetColorEntryCount(),
952
0
                                    nRefColorEntryCount))))
953
954
0
                    {
955
0
                        return m_osProgramName +
956
0
                               " does not support rasters with "
957
0
                               "different color tables (different number of "
958
0
                               "color table entries)";
959
0
                    }
960
961
                    /* Check that the palette are the same too */
962
                    /* We just warn and still process the file. It is not a
963
                     * technical no-go, but the user */
964
                    /* should check that the end result is OK for him. */
965
0
                    for (int i = 0;
966
0
                         i < std::min(nRefColorEntryCount,
967
0
                                      colorTable->GetColorEntryCount());
968
0
                         i++)
969
0
                    {
970
0
                        const GDALColorEntry *psEntry =
971
0
                            colorTable->GetColorEntry(i);
972
0
                        const GDALColorEntry *psEntryRef =
973
0
                            asBandProperties[j].colorTable->GetColorEntry(i);
974
0
                        if (psEntry->c1 != psEntryRef->c1 ||
975
0
                            psEntry->c2 != psEntryRef->c2 ||
976
0
                            psEntry->c3 != psEntryRef->c3 ||
977
0
                            psEntry->c4 != psEntryRef->c4)
978
0
                        {
979
0
                            static int bFirstWarningPCT = TRUE;
980
0
                            if (bFirstWarningPCT)
981
0
                                CPLError(
982
0
                                    CE_Warning, CPLE_NotSupported,
983
0
                                    "%s has different values than the first "
984
0
                                    "raster for some entries in the color "
985
0
                                    "table.\n"
986
0
                                    "The end result might produce weird "
987
0
                                    "colors.\n"
988
0
                                    "You're advised to pre-process your "
989
0
                                    "rasters with other tools, such as "
990
0
                                    "pct2rgb.py or gdal_translate -expand RGB\n"
991
0
                                    "to operate %s on RGB rasters "
992
0
                                    "instead",
993
0
                                    dsFileName, m_osProgramName.c_str());
994
0
                            else
995
0
                                CPLError(CE_Warning, CPLE_NotSupported,
996
0
                                         "%s has different values than the "
997
0
                                         "first raster for some entries in the "
998
0
                                         "color table.",
999
0
                                         dsFileName);
1000
0
                            bFirstWarningPCT = FALSE;
1001
0
                            break;
1002
0
                        }
1003
0
                    }
1004
1005
                    // For NITF CADRG tiles that may have 256 or 257 colors,
1006
                    // with the 257th color when present being transparent
1007
0
                    if (nRefColorEntryCount + 1 ==
1008
0
                            colorTable->GetColorEntryCount() &&
1009
0
                        colorTable->GetColorEntry(nRefColorEntryCount)->c1 ==
1010
0
                            0 &&
1011
0
                        colorTable->GetColorEntry(nRefColorEntryCount)->c2 ==
1012
0
                            0 &&
1013
0
                        colorTable->GetColorEntry(nRefColorEntryCount)->c3 ==
1014
0
                            0 &&
1015
0
                        colorTable->GetColorEntry(nRefColorEntryCount)->c4 == 0)
1016
0
                    {
1017
0
                        int bHasNoData = false;
1018
0
                        const double dfNoData =
1019
0
                            poBand->GetNoDataValue(&bHasNoData);
1020
0
                        if (bHasNoData && !asBandProperties[j].bHasNoData &&
1021
0
                            dfNoData == nRefColorEntryCount)
1022
0
                        {
1023
0
                            asBandProperties[j].bHasNoData = true;
1024
0
                            asBandProperties[j].noDataValue = dfNoData;
1025
0
                        }
1026
1027
0
                        asBandProperties[j].colorTable.reset(
1028
0
                            colorTable->Clone());
1029
0
                    }
1030
0
                }
1031
1032
0
                if (psDatasetProperties->abHasOffset[j] !=
1033
0
                        asBandProperties[j].bHasOffset ||
1034
0
                    (asBandProperties[j].bHasOffset &&
1035
0
                     psDatasetProperties->adfOffset[j] !=
1036
0
                         asBandProperties[j].dfOffset))
1037
0
                {
1038
0
                    return m_osProgramName +
1039
0
                           CPLSPrintf(
1040
0
                               " does not support heterogeneous "
1041
0
                               "band offset: expected (%d,%f), got (%d,%f).",
1042
0
                               static_cast<int>(asBandProperties[j].bHasOffset),
1043
0
                               asBandProperties[j].dfOffset,
1044
0
                               static_cast<int>(
1045
0
                                   psDatasetProperties->abHasOffset[j]),
1046
0
                               psDatasetProperties->adfOffset[j]);
1047
0
                }
1048
1049
0
                if (psDatasetProperties->abHasScale[j] !=
1050
0
                        asBandProperties[j].bHasScale ||
1051
0
                    (asBandProperties[j].bHasScale &&
1052
0
                     psDatasetProperties->adfScale[j] !=
1053
0
                         asBandProperties[j].dfScale))
1054
0
                {
1055
0
                    return m_osProgramName +
1056
0
                           CPLSPrintf(
1057
0
                               " does not support heterogeneous "
1058
0
                               "band scale: expected (%d,%f), got (%d,%f).",
1059
0
                               static_cast<int>(asBandProperties[j].bHasScale),
1060
0
                               asBandProperties[j].dfScale,
1061
0
                               static_cast<int>(
1062
0
                                   psDatasetProperties->abHasScale[j]),
1063
0
                               psDatasetProperties->adfScale[j]);
1064
0
                }
1065
0
            }
1066
0
        }
1067
0
        if (!bUserExtent)
1068
0
        {
1069
0
            if (ds_minX < minX)
1070
0
                minX = ds_minX;
1071
0
            if (ds_minY < minY)
1072
0
                minY = ds_minY;
1073
0
            if (ds_maxX > maxX)
1074
0
                maxX = ds_maxX;
1075
0
            if (ds_maxY > maxY)
1076
0
                maxY = ds_maxY;
1077
0
        }
1078
0
    }
1079
1080
0
    if (resolutionStrategy == AVERAGE_RESOLUTION)
1081
0
    {
1082
0
        ++nCountValid;
1083
0
        {
1084
0
            const double dfDelta = gt[GEOTRSFRM_WE_RES] - we_res;
1085
0
            we_res += dfDelta / nCountValid;
1086
0
        }
1087
0
        {
1088
0
            const double dfDelta = gt[GEOTRSFRM_NS_RES] - ns_res;
1089
0
            ns_res += dfDelta / nCountValid;
1090
0
        }
1091
0
    }
1092
0
    else if (resolutionStrategy == SAME_RESOLUTION)
1093
0
    {
1094
0
        if (bFirst)
1095
0
        {
1096
0
            we_res = gt[GEOTRSFRM_WE_RES];
1097
0
            ns_res = gt[GEOTRSFRM_NS_RES];
1098
0
        }
1099
0
        else if (we_res != gt[GEOTRSFRM_WE_RES] ||
1100
0
                 ns_res != gt[GEOTRSFRM_NS_RES])
1101
0
        {
1102
0
            return CPLSPrintf(
1103
0
                "Dataset %s has resolution %.17g x %.17g, whereas "
1104
0
                "previous sources have resolution %.17g x %.17g. To mosaic "
1105
0
                "these data sources, a different resolution strategy should be "
1106
0
                "specified.",
1107
0
                dsFileName, gt[GEOTRSFRM_WE_RES], gt[GEOTRSFRM_NS_RES], we_res,
1108
0
                ns_res);
1109
0
        }
1110
0
    }
1111
0
    else if (resolutionStrategy != USER_RESOLUTION)
1112
0
    {
1113
0
        if (bFirst)
1114
0
        {
1115
0
            we_res = gt[GEOTRSFRM_WE_RES];
1116
0
            ns_res = gt[GEOTRSFRM_NS_RES];
1117
0
        }
1118
0
        else if (resolutionStrategy == HIGHEST_RESOLUTION)
1119
0
        {
1120
0
            we_res = std::min(we_res, gt[GEOTRSFRM_WE_RES]);
1121
            // ns_res is negative, the highest resolution is the max value.
1122
0
            ns_res = std::max(ns_res, gt[GEOTRSFRM_NS_RES]);
1123
0
        }
1124
0
        else if (resolutionStrategy == COMMON_RESOLUTION)
1125
0
        {
1126
0
            we_res = CPLGreatestCommonDivisor(we_res, gt[GEOTRSFRM_WE_RES]);
1127
0
            if (we_res == 0)
1128
0
            {
1129
0
                return "Failed to get common resolution";
1130
0
            }
1131
0
            ns_res = CPLGreatestCommonDivisor(ns_res, gt[GEOTRSFRM_NS_RES]);
1132
0
            if (ns_res == 0)
1133
0
            {
1134
0
                return "Failed to get common resolution";
1135
0
            }
1136
0
        }
1137
0
        else
1138
0
        {
1139
0
            we_res = std::max(we_res, gt[GEOTRSFRM_WE_RES]);
1140
            // ns_res is negative, the lowest resolution is the min value.
1141
0
            ns_res = std::min(ns_res, gt[GEOTRSFRM_NS_RES]);
1142
0
        }
1143
0
    }
1144
1145
0
    checkNoDataValues(asBandProperties);
1146
1147
0
    return "";
1148
0
}
1149
1150
/************************************************************************/
1151
/*                         WriteAbsolutePath()                          */
1152
/************************************************************************/
1153
1154
static void WriteAbsolutePath(VRTSimpleSource *poSource, const char *dsFileName)
1155
0
{
1156
0
    if (dsFileName[0])
1157
0
    {
1158
0
        if (CPLIsFilenameRelative(dsFileName))
1159
0
        {
1160
0
            VSIStatBufL sStat;
1161
0
            if (VSIStatL(dsFileName, &sStat) == 0)
1162
0
            {
1163
0
                if (char *pszCurDir = CPLGetCurrentDir())
1164
0
                {
1165
0
                    poSource->SetSourceDatasetName(
1166
0
                        CPLFormFilenameSafe(pszCurDir, dsFileName, nullptr)
1167
0
                            .c_str(),
1168
0
                        false);
1169
0
                    CPLFree(pszCurDir);
1170
0
                }
1171
0
            }
1172
0
        }
1173
0
        else
1174
0
        {
1175
0
            poSource->SetSourceDatasetName(dsFileName, false);
1176
0
        }
1177
0
    }
1178
0
}
1179
1180
/************************************************************************/
1181
/*                       IsTransientSrcDataset()                        */
1182
/************************************************************************/
1183
1184
static bool IsTransientSrcDataset(const char *dsFileName, GDALDatasetH hDS)
1185
0
{
1186
0
    auto hDriver = GDALGetDatasetDriver(hDS);
1187
0
    return !hDriver || dsFileName[0] == '\0' ||  // could be a unnamed VRT file
1188
                                                 // Inner pipeline
1189
0
           (dsFileName[0] == '[' &&
1190
0
            dsFileName[strlen(dsFileName) - 1] == ']') ||
1191
0
           EQUAL(GDALGetDescription(hDriver), "MEM");
1192
0
}
1193
1194
/************************************************************************/
1195
/*                         CreateVRTSeparate()                          */
1196
/************************************************************************/
1197
1198
void VRTBuilder::CreateVRTSeparate(VRTDataset *poVRTDS)
1199
0
{
1200
0
    int iBand = 1;
1201
0
    for (int i = 0; ppszInputFilenames != nullptr && i < nInputFiles; i++)
1202
0
    {
1203
0
        DatasetProperty *psDatasetProperties = &asDatasetProperties[i];
1204
1205
0
        if (psDatasetProperties->isFileOK == FALSE)
1206
0
            continue;
1207
1208
0
        const char *dsFileName = ppszInputFilenames[i];
1209
1210
0
        double dfSrcXOff, dfSrcYOff, dfSrcXSize, dfSrcYSize, dfDstXOff,
1211
0
            dfDstYOff, dfDstXSize, dfDstYSize;
1212
0
        if (bHasGeoTransform)
1213
0
        {
1214
0
            if (!GetSrcDstWin(psDatasetProperties, we_res, ns_res, minX, minY,
1215
0
                              maxX, maxY, nRasterXSize, nRasterYSize,
1216
0
                              &dfSrcXOff, &dfSrcYOff, &dfSrcXSize, &dfSrcYSize,
1217
0
                              &dfDstXOff, &dfDstYOff, &dfDstXSize, &dfDstYSize))
1218
0
            {
1219
0
                CPLDebug("BuildVRT",
1220
0
                         "Skipping %s as not intersecting area of interest",
1221
0
                         dsFileName);
1222
0
                continue;
1223
0
            }
1224
0
        }
1225
0
        else
1226
0
        {
1227
0
            dfSrcXOff = dfSrcYOff = dfDstXOff = dfDstYOff = 0;
1228
0
            dfSrcXSize = dfDstXSize = nRasterXSize;
1229
0
            dfSrcYSize = dfDstYSize = nRasterYSize;
1230
0
        }
1231
1232
0
        GDALDatasetH hSourceDS;
1233
0
        bool bDropRef = false;
1234
0
        if (nSrcDSCount == nInputFiles &&
1235
0
            IsTransientSrcDataset(dsFileName, pahSrcDS[i]))
1236
0
        {
1237
0
            hSourceDS = pahSrcDS[i];
1238
0
        }
1239
0
        else
1240
0
        {
1241
0
            bDropRef = true;
1242
0
            GDALProxyPoolDatasetH hProxyDS = GDALProxyPoolDatasetCreate(
1243
0
                dsFileName, psDatasetProperties->nRasterXSize,
1244
0
                psDatasetProperties->nRasterYSize, GA_ReadOnly, TRUE,
1245
0
                pszProjectionRef, psDatasetProperties->gt.data());
1246
0
            hSourceDS = static_cast<GDALDatasetH>(hProxyDS);
1247
0
            cpl::down_cast<GDALProxyPoolDataset *>(
1248
0
                GDALDataset::FromHandle(hProxyDS))
1249
0
                ->SetOpenOptions(papszOpenOptions);
1250
1251
0
            for (int jBand = 0;
1252
0
                 jBand <
1253
0
                 static_cast<int>(psDatasetProperties->aeBandType.size());
1254
0
                 ++jBand)
1255
0
            {
1256
0
                GDALProxyPoolDatasetAddSrcBandDescription(
1257
0
                    hProxyDS, psDatasetProperties->aeBandType[jBand],
1258
0
                    psDatasetProperties->nBlockXSize,
1259
0
                    psDatasetProperties->nBlockYSize);
1260
0
            }
1261
0
        }
1262
1263
0
        const int nBandsToIter =
1264
0
            nSelectedBands > 0
1265
0
                ? nSelectedBands
1266
0
                : static_cast<int>(psDatasetProperties->aeBandType.size());
1267
0
        for (int iBandToIter = 0; iBandToIter < nBandsToIter; ++iBandToIter)
1268
0
        {
1269
            // 0-based
1270
0
            const int nSrcBandIdx = nSelectedBands > 0
1271
0
                                        ? panSelectedBandList[iBandToIter] - 1
1272
0
                                        : iBandToIter;
1273
0
            assert(nSrcBandIdx >= 0);
1274
0
            const auto eBandType = psDatasetProperties->aeBandType[nSrcBandIdx];
1275
0
            poVRTDS->AddBand(eBandType, nullptr);
1276
1277
0
            VRTSourcedRasterBand *poVRTBand =
1278
0
                static_cast<VRTSourcedRasterBand *>(
1279
0
                    poVRTDS->GetRasterBand(iBand));
1280
1281
0
            poVRTBand->SetDescription(
1282
0
                psDatasetProperties->aosDescriptions[nSrcBandIdx].c_str());
1283
0
            if (!psDatasetProperties->mapBandMetadata[nSrcBandIdx].empty())
1284
0
            {
1285
0
                for (const auto &[key, value] :
1286
0
                     psDatasetProperties->mapBandMetadata[nSrcBandIdx])
1287
0
                {
1288
0
                    poVRTBand->SetMetadataItem(key.c_str(), value.c_str());
1289
0
                }
1290
0
            }
1291
1292
0
            if (bHideNoData)
1293
0
                poVRTBand->SetMetadataItem("HideNoDataValue", "1", nullptr);
1294
1295
0
            if (bAllowVRTNoData)
1296
0
            {
1297
0
                std::optional<double> noData;
1298
0
                if (nVRTNoDataCount > 0)
1299
0
                {
1300
0
                    if (iBand - 1 < nVRTNoDataCount)
1301
0
                        noData = padfVRTNoData[iBand - 1];
1302
0
                    else
1303
0
                        noData = padfVRTNoData[nVRTNoDataCount - 1];
1304
0
                }
1305
0
                else if (psDatasetProperties->abHasNoData[nSrcBandIdx])
1306
0
                {
1307
0
                    noData = psDatasetProperties->adfNoDataValues[nSrcBandIdx];
1308
0
                }
1309
0
                if (noData.has_value())
1310
0
                {
1311
0
                    if (GDALDataTypeIsInteger(eBandType) &&
1312
0
                        !GDALIsValueExactAs(*noData, eBandType))
1313
0
                    {
1314
0
                        CPLError(CE_Warning, CPLE_NotSupported,
1315
0
                                 "Band data type of %s cannot represent the "
1316
0
                                 "specified "
1317
0
                                 "NoData value of %g",
1318
0
                                 GDALGetDataTypeName(eBandType), *noData);
1319
0
                    }
1320
0
                    poVRTBand->SetNoDataValue(*noData);
1321
0
                }
1322
0
            }
1323
1324
0
            VRTSimpleSource *poSimpleSource;
1325
0
            if (bAllowSrcNoData &&
1326
0
                (nSrcNoDataCount > 0 ||
1327
0
                 psDatasetProperties->abHasNoData[nSrcBandIdx]))
1328
0
            {
1329
0
                auto poComplexSource = new VRTComplexSource();
1330
0
                poSimpleSource = poComplexSource;
1331
0
                if (nSrcNoDataCount > 0)
1332
0
                {
1333
0
                    if (iBand - 1 < nSrcNoDataCount)
1334
0
                        poComplexSource->SetNoDataValue(
1335
0
                            padfSrcNoData[iBand - 1]);
1336
0
                    else
1337
0
                        poComplexSource->SetNoDataValue(
1338
0
                            padfSrcNoData[nSrcNoDataCount - 1]);
1339
0
                }
1340
0
                else /* if (psDatasetProperties->abHasNoData[nSrcBandIdx]) */
1341
0
                {
1342
0
                    poComplexSource->SetNoDataValue(
1343
0
                        psDatasetProperties->adfNoDataValues[nSrcBandIdx]);
1344
0
                }
1345
0
            }
1346
0
            else if (bUseSrcMaskBand &&
1347
0
                     psDatasetProperties->abHasMaskBand[nSrcBandIdx])
1348
0
            {
1349
0
                auto poSource = new VRTComplexSource();
1350
0
                poSource->SetUseMaskBand(true);
1351
0
                poSimpleSource = poSource;
1352
0
            }
1353
0
            else
1354
0
                poSimpleSource = new VRTSimpleSource();
1355
1356
0
            if (pszResampling)
1357
0
                poSimpleSource->SetResampling(pszResampling);
1358
0
            poVRTBand->ConfigureSource(
1359
0
                poSimpleSource,
1360
0
                static_cast<GDALRasterBand *>(
1361
0
                    GDALGetRasterBand(hSourceDS, nSrcBandIdx + 1)),
1362
0
                FALSE, dfSrcXOff, dfSrcYOff, dfSrcXSize, dfSrcYSize, dfDstXOff,
1363
0
                dfDstYOff, dfDstXSize, dfDstYSize);
1364
1365
0
            if (bWriteAbsolutePath)
1366
0
                WriteAbsolutePath(poSimpleSource, dsFileName);
1367
1368
0
            if (psDatasetProperties->abHasOffset[nSrcBandIdx])
1369
0
                poVRTBand->SetOffset(
1370
0
                    psDatasetProperties->adfOffset[nSrcBandIdx]);
1371
1372
0
            if (psDatasetProperties->abHasScale[nSrcBandIdx])
1373
0
                poVRTBand->SetScale(psDatasetProperties->adfScale[nSrcBandIdx]);
1374
1375
0
            poVRTBand->AddSource(poSimpleSource);
1376
1377
0
            iBand++;
1378
0
        }
1379
1380
0
        if (bDropRef)
1381
0
        {
1382
0
            GDALDereferenceDataset(hSourceDS);
1383
0
        }
1384
0
    }
1385
0
}
1386
1387
/************************************************************************/
1388
/*                        CreateVRTNonSeparate()                        */
1389
/************************************************************************/
1390
1391
void VRTBuilder::CreateVRTNonSeparate(VRTDataset *poVRTDS)
1392
0
{
1393
0
    CPLStringList aosOptions;
1394
1395
0
    if (!osPixelFunction.empty())
1396
0
    {
1397
0
        aosOptions.AddNameValue("subclass", "VRTDerivedRasterBand");
1398
0
        aosOptions.AddNameValue("PixelFunctionType", osPixelFunction.c_str());
1399
0
        aosOptions.AddNameValue("SkipNonContributingSources", "1");
1400
0
        CPLString osName;
1401
0
        for (const auto &[pszKey, pszValue] :
1402
0
             cpl::IterateNameValue(aosPixelFunctionArgs))
1403
0
        {
1404
0
            osName.Printf("_PIXELFN_ARG_%s", pszKey);
1405
0
            aosOptions.AddNameValue(osName.c_str(), pszValue);
1406
0
        }
1407
0
    }
1408
1409
0
    for (int j = 0; j < nSelectedBands; j++)
1410
0
    {
1411
0
        const char *pszSourceTransferType = "Float64";
1412
0
        if (osPixelFunction == "mean" || osPixelFunction == "min" ||
1413
0
            osPixelFunction == "max")
1414
0
        {
1415
0
            pszSourceTransferType =
1416
0
                GDALGetDataTypeName(asBandProperties[j].dataType);
1417
0
        }
1418
0
        aosOptions.AddNameValue("SourceTransferType", pszSourceTransferType);
1419
1420
0
        poVRTDS->AddBand(asBandProperties[j].dataType, aosOptions.List());
1421
0
        GDALRasterBand *poBand = poVRTDS->GetRasterBand(j + 1);
1422
0
        poBand->SetColorInterpretation(asBandProperties[j].colorInterpretation);
1423
0
        poBand->SetDescription(asBandProperties[j].osDescription.c_str());
1424
0
        if (!asBandProperties[j].mapBandMetadata.empty())
1425
0
        {
1426
0
            for (const auto &[key, value] : asBandProperties[j].mapBandMetadata)
1427
0
            {
1428
0
                poBand->SetMetadataItem(key.c_str(), value.c_str());
1429
0
            }
1430
0
        }
1431
0
        if (asBandProperties[j].colorInterpretation == GCI_PaletteIndex)
1432
0
        {
1433
0
            poBand->SetColorTable(asBandProperties[j].colorTable.get());
1434
0
        }
1435
0
        if (bAllowVRTNoData && asBandProperties[j].bHasNoData)
1436
0
            poBand->SetNoDataValue(asBandProperties[j].noDataValue);
1437
0
        if (bHideNoData)
1438
0
            poBand->SetMetadataItem("HideNoDataValue", "1");
1439
1440
0
        if (asBandProperties[j].bHasOffset)
1441
0
            poBand->SetOffset(asBandProperties[j].dfOffset);
1442
1443
0
        if (asBandProperties[j].bHasScale)
1444
0
            poBand->SetScale(asBandProperties[j].dfScale);
1445
0
    }
1446
1447
0
    VRTSourcedRasterBand *poMaskVRTBand = nullptr;
1448
0
    if (bAddAlpha)
1449
0
    {
1450
0
        poVRTDS->AddBand(GDT_UInt8);
1451
0
        GDALRasterBand *poBand = poVRTDS->GetRasterBand(nSelectedBands + 1);
1452
0
        poBand->SetColorInterpretation(GCI_AlphaBand);
1453
0
    }
1454
0
    else if (bHasDatasetMask)
1455
0
    {
1456
0
        poVRTDS->CreateMaskBand(GMF_PER_DATASET);
1457
0
        poMaskVRTBand = static_cast<VRTSourcedRasterBand *>(
1458
0
            poVRTDS->GetRasterBand(1)->GetMaskBand());
1459
0
    }
1460
1461
0
    bool bCanCollectOverviewFactors = true;
1462
0
    std::set<int> anOverviewFactorsSet;
1463
0
    std::vector<int> anIdxValidDatasets;
1464
1465
0
    for (int i = 0; ppszInputFilenames != nullptr && i < nInputFiles; i++)
1466
0
    {
1467
0
        DatasetProperty *psDatasetProperties = &asDatasetProperties[i];
1468
1469
0
        if (psDatasetProperties->isFileOK == FALSE)
1470
0
            continue;
1471
1472
0
        const char *dsFileName = ppszInputFilenames[i];
1473
1474
0
        double dfSrcXOff;
1475
0
        double dfSrcYOff;
1476
0
        double dfSrcXSize;
1477
0
        double dfSrcYSize;
1478
0
        double dfDstXOff;
1479
0
        double dfDstYOff;
1480
0
        double dfDstXSize;
1481
0
        double dfDstYSize;
1482
0
        if (!GetSrcDstWin(psDatasetProperties, we_res, ns_res, minX, minY, maxX,
1483
0
                          maxY, nRasterXSize, nRasterYSize, &dfSrcXOff,
1484
0
                          &dfSrcYOff, &dfSrcXSize, &dfSrcYSize, &dfDstXOff,
1485
0
                          &dfDstYOff, &dfDstXSize, &dfDstYSize))
1486
0
        {
1487
0
            CPLDebug("BuildVRT",
1488
0
                     "Skipping %s as not intersecting area of interest",
1489
0
                     dsFileName);
1490
0
            continue;
1491
0
        }
1492
1493
0
        anIdxValidDatasets.push_back(i);
1494
1495
0
        if (bCanCollectOverviewFactors)
1496
0
        {
1497
0
            if (std::abs(psDatasetProperties->gt.xscale - we_res) >
1498
0
                    1e-8 * std::abs(we_res) ||
1499
0
                std::abs(psDatasetProperties->gt.yscale - ns_res) >
1500
0
                    1e-8 * std::abs(ns_res))
1501
0
            {
1502
0
                bCanCollectOverviewFactors = false;
1503
0
                anOverviewFactorsSet.clear();
1504
0
            }
1505
0
        }
1506
0
        if (bCanCollectOverviewFactors)
1507
0
        {
1508
0
            for (int nOvFactor : psDatasetProperties->anOverviewFactors)
1509
0
                anOverviewFactorsSet.insert(nOvFactor);
1510
0
        }
1511
1512
0
        GDALDatasetH hSourceDS;
1513
0
        bool bDropRef = false;
1514
1515
0
        if (nSrcDSCount == nInputFiles &&
1516
0
            IsTransientSrcDataset(dsFileName, pahSrcDS[i]))
1517
0
        {
1518
0
            hSourceDS = pahSrcDS[i];
1519
0
        }
1520
0
        else
1521
0
        {
1522
0
            bDropRef = true;
1523
0
            GDALProxyPoolDatasetH hProxyDS = GDALProxyPoolDatasetCreate(
1524
0
                dsFileName, psDatasetProperties->nRasterXSize,
1525
0
                psDatasetProperties->nRasterYSize, GA_ReadOnly, TRUE,
1526
0
                pszProjectionRef, psDatasetProperties->gt.data());
1527
0
            cpl::down_cast<GDALProxyPoolDataset *>(
1528
0
                GDALDataset::FromHandle(hProxyDS))
1529
0
                ->SetOpenOptions(papszOpenOptions);
1530
1531
0
            for (int j = 0;
1532
0
                 j < nMaxSelectedBandNo +
1533
0
                         (bAddAlpha && psDatasetProperties->bLastBandIsAlpha
1534
0
                              ? 1
1535
0
                              : 0);
1536
0
                 j++)
1537
0
            {
1538
0
                GDALProxyPoolDatasetAddSrcBandDescription(
1539
0
                    hProxyDS,
1540
0
                    j < static_cast<int>(asBandProperties.size())
1541
0
                        ? asBandProperties[j].dataType
1542
0
                        : GDT_UInt8,
1543
0
                    psDatasetProperties->nBlockXSize,
1544
0
                    psDatasetProperties->nBlockYSize);
1545
0
            }
1546
0
            if (bHasDatasetMask && !bAddAlpha)
1547
0
            {
1548
0
                static_cast<GDALProxyPoolRasterBand *>(
1549
0
                    cpl::down_cast<GDALProxyPoolDataset *>(
1550
0
                        GDALDataset::FromHandle(hProxyDS))
1551
0
                        ->GetRasterBand(1))
1552
0
                    ->AddSrcMaskBandDescription(
1553
0
                        GDT_UInt8, psDatasetProperties->nMaskBlockXSize,
1554
0
                        psDatasetProperties->nMaskBlockYSize);
1555
0
            }
1556
1557
0
            hSourceDS = static_cast<GDALDatasetH>(hProxyDS);
1558
0
        }
1559
1560
0
        for (int j = 0;
1561
0
             j <
1562
0
             nSelectedBands +
1563
0
                 (bAddAlpha && psDatasetProperties->bLastBandIsAlpha ? 1 : 0);
1564
0
             j++)
1565
0
        {
1566
0
            VRTSourcedRasterBandH hVRTBand = static_cast<VRTSourcedRasterBandH>(
1567
0
                poVRTDS->GetRasterBand(j + 1));
1568
0
            const int nSelBand = j == nSelectedBands ? nSelectedBands + 1
1569
0
                                                     : panSelectedBandList[j];
1570
1571
            /* Place the raster band at the right position in the VRT */
1572
0
            VRTSourcedRasterBand *poVRTBand =
1573
0
                static_cast<VRTSourcedRasterBand *>(hVRTBand);
1574
1575
0
            VRTSimpleSource *poSimpleSource;
1576
0
            if (bNoDataFromMask)
1577
0
            {
1578
0
                auto poNoDataFromMaskSource = new VRTNoDataFromMaskSource();
1579
0
                poSimpleSource = poNoDataFromMaskSource;
1580
0
                poNoDataFromMaskSource->SetParameters(
1581
0
                    (nVRTNoDataCount > 0)
1582
0
                        ? ((j < nVRTNoDataCount)
1583
0
                               ? padfVRTNoData[j]
1584
0
                               : padfVRTNoData[nVRTNoDataCount - 1])
1585
0
                        : 0,
1586
0
                    dfMaskValueThreshold);
1587
0
            }
1588
0
            else if (bAllowSrcNoData &&
1589
0
                     psDatasetProperties->abHasNoData[nSelBand - 1])
1590
0
            {
1591
0
                auto poComplexSource = new VRTComplexSource();
1592
0
                poSimpleSource = poComplexSource;
1593
0
                poComplexSource->SetNoDataValue(
1594
0
                    psDatasetProperties->adfNoDataValues[nSelBand - 1]);
1595
0
            }
1596
0
            else if (bUseSrcMaskBand &&
1597
0
                     psDatasetProperties->abHasMaskBand[nSelBand - 1])
1598
0
            {
1599
0
                auto poSource = new VRTComplexSource();
1600
0
                poSource->SetUseMaskBand(true);
1601
0
                poSimpleSource = poSource;
1602
0
            }
1603
0
            else
1604
0
                poSimpleSource = new VRTSimpleSource();
1605
0
            if (pszResampling)
1606
0
                poSimpleSource->SetResampling(pszResampling);
1607
0
            auto poSrcBand = GDALRasterBand::FromHandle(
1608
0
                GDALGetRasterBand(hSourceDS, nSelBand));
1609
0
            poVRTBand->ConfigureSource(poSimpleSource, poSrcBand, FALSE,
1610
0
                                       dfSrcXOff, dfSrcYOff, dfSrcXSize,
1611
0
                                       dfSrcYSize, dfDstXOff, dfDstYOff,
1612
0
                                       dfDstXSize, dfDstYSize);
1613
1614
0
            if (bWriteAbsolutePath)
1615
0
                WriteAbsolutePath(poSimpleSource, dsFileName);
1616
1617
0
            poVRTBand->AddSource(poSimpleSource);
1618
0
        }
1619
1620
0
        if (bAddAlpha && !psDatasetProperties->bLastBandIsAlpha)
1621
0
        {
1622
0
            VRTSourcedRasterBand *poVRTBand =
1623
0
                static_cast<VRTSourcedRasterBand *>(
1624
0
                    poVRTDS->GetRasterBand(nSelectedBands + 1));
1625
0
            if (psDatasetProperties->bHasDatasetMask && bUseSrcMaskBand)
1626
0
            {
1627
0
                auto poComplexSource = new VRTComplexSource();
1628
0
                poComplexSource->SetUseMaskBand(true);
1629
0
                poVRTBand->ConfigureSource(
1630
0
                    poComplexSource,
1631
0
                    GDALRasterBand::FromHandle(GDALGetRasterBand(hSourceDS, 1)),
1632
0
                    TRUE, dfSrcXOff, dfSrcYOff, dfSrcXSize, dfSrcYSize,
1633
0
                    dfDstXOff, dfDstYOff, dfDstXSize, dfDstYSize);
1634
1635
0
                if (bWriteAbsolutePath)
1636
0
                    WriteAbsolutePath(poComplexSource, dsFileName);
1637
1638
0
                poVRTBand->AddSource(poComplexSource);
1639
0
            }
1640
0
            else
1641
0
            {
1642
                /* Little trick : we use an offset of 255 and a scaling of 0, so
1643
                 * that in areas covered */
1644
                /* by the source, the value of the alpha band will be 255, otherwise
1645
                 * it will be 0 */
1646
0
                poVRTBand->AddComplexSource(
1647
0
                    GDALRasterBand::FromHandle(GDALGetRasterBand(hSourceDS, 1)),
1648
0
                    dfSrcXOff, dfSrcYOff, dfSrcXSize, dfSrcYSize, dfDstXOff,
1649
0
                    dfDstYOff, dfDstXSize, dfDstYSize, 255, 0,
1650
0
                    VRT_NODATA_UNSET);
1651
0
            }
1652
0
        }
1653
0
        else if (bHasDatasetMask)
1654
0
        {
1655
0
            VRTSimpleSource *poSource;
1656
0
            if (bUseSrcMaskBand)
1657
0
            {
1658
0
                auto poComplexSource = new VRTComplexSource();
1659
0
                poComplexSource->SetUseMaskBand(true);
1660
0
                poSource = poComplexSource;
1661
0
            }
1662
0
            else
1663
0
            {
1664
0
                poSource = new VRTSimpleSource();
1665
0
            }
1666
0
            if (pszResampling)
1667
0
                poSource->SetResampling(pszResampling);
1668
0
            assert(poMaskVRTBand);
1669
0
            poMaskVRTBand->ConfigureSource(
1670
0
                poSource,
1671
0
                GDALRasterBand::FromHandle(GDALGetRasterBand(hSourceDS, 1)),
1672
0
                TRUE, dfSrcXOff, dfSrcYOff, dfSrcXSize, dfSrcYSize, dfDstXOff,
1673
0
                dfDstYOff, dfDstXSize, dfDstYSize);
1674
1675
0
            if (bWriteAbsolutePath)
1676
0
                WriteAbsolutePath(poSource, dsFileName);
1677
1678
0
            poMaskVRTBand->AddSource(poSource);
1679
0
        }
1680
1681
0
        if (bDropRef)
1682
0
        {
1683
0
            GDALDereferenceDataset(hSourceDS);
1684
0
        }
1685
0
    }
1686
1687
0
    for (int i : anIdxValidDatasets)
1688
0
    {
1689
0
        const DatasetProperty *psDatasetProperties = &asDatasetProperties[i];
1690
0
        for (auto oIter = anOverviewFactorsSet.begin();
1691
0
             oIter != anOverviewFactorsSet.end();)
1692
0
        {
1693
0
            const int nGlobalOvrFactor = *oIter;
1694
0
            auto oIterNext = oIter;
1695
0
            ++oIterNext;
1696
1697
0
            if (psDatasetProperties->nRasterXSize / nGlobalOvrFactor < 128 &&
1698
0
                psDatasetProperties->nRasterYSize / nGlobalOvrFactor < 128)
1699
0
            {
1700
0
                break;
1701
0
            }
1702
0
            if (std::find(psDatasetProperties->anOverviewFactors.begin(),
1703
0
                          psDatasetProperties->anOverviewFactors.end(),
1704
0
                          nGlobalOvrFactor) ==
1705
0
                psDatasetProperties->anOverviewFactors.end())
1706
0
            {
1707
0
                anOverviewFactorsSet.erase(oIter);
1708
0
            }
1709
1710
0
            oIter = oIterNext;
1711
0
        }
1712
0
    }
1713
0
    if (!anOverviewFactorsSet.empty() &&
1714
0
        CPLTestBool(CPLGetConfigOption("VRT_VIRTUAL_OVERVIEWS", "YES")))
1715
0
    {
1716
0
        std::vector<int> anOverviewFactors;
1717
0
        anOverviewFactors.insert(anOverviewFactors.end(),
1718
0
                                 anOverviewFactorsSet.begin(),
1719
0
                                 anOverviewFactorsSet.end());
1720
0
        const char *const apszOptions[] = {"VRT_VIRTUAL_OVERVIEWS=YES",
1721
0
                                           nullptr};
1722
0
        poVRTDS->BuildOverviews(pszResampling ? pszResampling : "nearest",
1723
0
                                static_cast<int>(anOverviewFactors.size()),
1724
0
                                &anOverviewFactors[0], 0, nullptr, nullptr,
1725
0
                                nullptr, apszOptions);
1726
0
    }
1727
0
}
1728
1729
/************************************************************************/
1730
/*                               Build()                                */
1731
/************************************************************************/
1732
1733
std::unique_ptr<GDALDataset> VRTBuilder::Build(GDALProgressFunc pfnProgress,
1734
                                               void *pProgressData)
1735
0
{
1736
0
    if (bHasRunBuild)
1737
0
        return nullptr;
1738
0
    bHasRunBuild = TRUE;
1739
1740
0
    if (pfnProgress == nullptr)
1741
0
        pfnProgress = GDALDummyProgress;
1742
1743
0
    bUserExtent = (minX != 0 || minY != 0 || maxX != 0 || maxY != 0);
1744
0
    if (bUserExtent)
1745
0
    {
1746
0
        if (minX >= maxX || minY >= maxY)
1747
0
        {
1748
0
            CPLError(CE_Failure, CPLE_IllegalArg, "Invalid user extent");
1749
0
            return nullptr;
1750
0
        }
1751
0
    }
1752
1753
0
    if (resolutionStrategy == USER_RESOLUTION)
1754
0
    {
1755
0
        if (we_res <= 0 || ns_res <= 0)
1756
0
        {
1757
0
            CPLError(CE_Failure, CPLE_IllegalArg, "Invalid user resolution");
1758
0
            return nullptr;
1759
0
        }
1760
1761
        /* We work with negative north-south resolution in all the following
1762
         * code */
1763
0
        ns_res = -ns_res;
1764
0
    }
1765
0
    else
1766
0
    {
1767
0
        we_res = ns_res = 0;
1768
0
    }
1769
1770
0
    asDatasetProperties.resize(nInputFiles);
1771
1772
0
    if (pszSrcNoData != nullptr)
1773
0
    {
1774
0
        if (EQUAL(pszSrcNoData, "none"))
1775
0
        {
1776
0
            bAllowSrcNoData = FALSE;
1777
0
        }
1778
0
        else
1779
0
        {
1780
0
            char **papszTokens = CSLTokenizeString(pszSrcNoData);
1781
0
            nSrcNoDataCount = CSLCount(papszTokens);
1782
0
            padfSrcNoData = static_cast<double *>(
1783
0
                CPLMalloc(sizeof(double) * nSrcNoDataCount));
1784
0
            for (int i = 0; i < nSrcNoDataCount; i++)
1785
0
            {
1786
0
                if (!ArgIsNumeric(papszTokens[i]) &&
1787
0
                    !EQUAL(papszTokens[i], "nan") &&
1788
0
                    !EQUAL(papszTokens[i], "-inf") &&
1789
0
                    !EQUAL(papszTokens[i], "inf"))
1790
0
                {
1791
0
                    CPLError(CE_Failure, CPLE_IllegalArg,
1792
0
                             "Invalid -srcnodata value");
1793
0
                    CSLDestroy(papszTokens);
1794
0
                    return nullptr;
1795
0
                }
1796
0
                padfSrcNoData[i] = CPLAtofM(papszTokens[i]);
1797
0
            }
1798
0
            CSLDestroy(papszTokens);
1799
0
        }
1800
0
    }
1801
1802
0
    if (pszVRTNoData != nullptr)
1803
0
    {
1804
0
        if (EQUAL(pszVRTNoData, "none"))
1805
0
        {
1806
0
            bAllowVRTNoData = FALSE;
1807
0
        }
1808
0
        else
1809
0
        {
1810
0
            char **papszTokens = CSLTokenizeString(pszVRTNoData);
1811
0
            nVRTNoDataCount = CSLCount(papszTokens);
1812
0
            padfVRTNoData = static_cast<double *>(
1813
0
                CPLMalloc(sizeof(double) * nVRTNoDataCount));
1814
0
            for (int i = 0; i < nVRTNoDataCount; i++)
1815
0
            {
1816
0
                if (!ArgIsNumeric(papszTokens[i]) &&
1817
0
                    !EQUAL(papszTokens[i], "nan") &&
1818
0
                    !EQUAL(papszTokens[i], "-inf") &&
1819
0
                    !EQUAL(papszTokens[i], "inf"))
1820
0
                {
1821
0
                    CPLError(CE_Failure, CPLE_IllegalArg,
1822
0
                             "Invalid -vrtnodata value");
1823
0
                    CSLDestroy(papszTokens);
1824
0
                    return nullptr;
1825
0
                }
1826
0
                padfVRTNoData[i] = CPLAtofM(papszTokens[i]);
1827
0
            }
1828
0
            CSLDestroy(papszTokens);
1829
0
        }
1830
0
    }
1831
1832
0
    bool bFoundValid = false;
1833
0
    for (int i = 0; ppszInputFilenames != nullptr && i < nInputFiles; i++)
1834
0
    {
1835
0
        const char *dsFileName = ppszInputFilenames[i];
1836
1837
0
        if (!pfnProgress(1.0 * (i + 1) / nInputFiles, nullptr, pProgressData))
1838
0
        {
1839
0
            return nullptr;
1840
0
        }
1841
1842
0
        GDALDatasetH hDS = (pahSrcDS)
1843
0
                               ? pahSrcDS[i]
1844
0
                               : GDALOpenEx(dsFileName, GDAL_OF_RASTER, nullptr,
1845
0
                                            papszOpenOptions, nullptr);
1846
0
        asDatasetProperties[i].isFileOK = FALSE;
1847
1848
0
        if (hDS)
1849
0
        {
1850
0
            const auto osErrorMsg = AnalyseRaster(hDS, &asDatasetProperties[i]);
1851
0
            if (osErrorMsg.empty())
1852
0
            {
1853
0
                asDatasetProperties[i].isFileOK = TRUE;
1854
0
                bFoundValid = true;
1855
0
                bFirst = FALSE;
1856
0
            }
1857
0
            if (pahSrcDS == nullptr)
1858
0
                GDALClose(hDS);
1859
0
            if (!osErrorMsg.empty() && osErrorMsg != "SILENTLY_IGNORE")
1860
0
            {
1861
0
                if (bStrict)
1862
0
                {
1863
0
                    CPLError(CE_Failure, CPLE_AppDefined, "%s",
1864
0
                             osErrorMsg.c_str());
1865
0
                    return nullptr;
1866
0
                }
1867
0
                else
1868
0
                {
1869
0
                    CPLError(CE_Warning, CPLE_AppDefined, "%s Skipping %s",
1870
0
                             osErrorMsg.c_str(), dsFileName);
1871
0
                }
1872
0
            }
1873
0
        }
1874
0
        else
1875
0
        {
1876
0
            if (bStrict)
1877
0
            {
1878
0
                CPLError(CE_Failure, CPLE_AppDefined, "Can't open %s.",
1879
0
                         dsFileName);
1880
0
                return nullptr;
1881
0
            }
1882
0
            else
1883
0
            {
1884
0
                CPLError(CE_Warning, CPLE_AppDefined,
1885
0
                         "Can't open %s. Skipping it", dsFileName);
1886
0
            }
1887
0
        }
1888
0
    }
1889
1890
0
    if (!bFoundValid)
1891
0
        return nullptr;
1892
1893
0
    if (bHasGeoTransform)
1894
0
    {
1895
0
        if (bTargetAlignedPixels)
1896
0
        {
1897
0
            minX = floor(minX / we_res) * we_res;
1898
0
            maxX = ceil(maxX / we_res) * we_res;
1899
0
            minY = floor(minY / -ns_res) * -ns_res;
1900
0
            maxY = ceil(maxY / -ns_res) * -ns_res;
1901
0
        }
1902
1903
0
        nRasterXSize = static_cast<int>(0.5 + (maxX - minX) / we_res);
1904
0
        nRasterYSize = static_cast<int>(0.5 + (maxY - minY) / -ns_res);
1905
0
    }
1906
1907
0
    if (nRasterXSize == 0 || nRasterYSize == 0)
1908
0
    {
1909
0
        CPLError(CE_Failure, CPLE_AppDefined,
1910
0
                 "Computed VRT dimension is invalid. You've probably "
1911
0
                 "specified inappropriate resolution.");
1912
0
        return nullptr;
1913
0
    }
1914
1915
0
    auto poDS = VRTDataset::CreateVRTDataset(pszOutputFilename, nRasterXSize,
1916
0
                                             nRasterYSize, 0, GDT_Unknown,
1917
0
                                             aosCreateOptions.List());
1918
0
    if (!poDS)
1919
0
    {
1920
0
        return nullptr;
1921
0
    }
1922
1923
0
    if (pszOutputSRS)
1924
0
    {
1925
0
        poDS->SetProjection(pszOutputSRS);
1926
0
    }
1927
0
    else if (pszProjectionRef)
1928
0
    {
1929
0
        poDS->SetProjection(pszProjectionRef);
1930
0
    }
1931
1932
0
    if (bHasGeoTransform)
1933
0
    {
1934
0
        GDALGeoTransform gt;
1935
0
        gt[GEOTRSFRM_TOPLEFT_X] = minX;
1936
0
        gt[GEOTRSFRM_WE_RES] = we_res;
1937
0
        gt[GEOTRSFRM_ROTATION_PARAM1] = 0;
1938
0
        gt[GEOTRSFRM_TOPLEFT_Y] = maxY;
1939
0
        gt[GEOTRSFRM_ROTATION_PARAM2] = 0;
1940
0
        gt[GEOTRSFRM_NS_RES] = ns_res;
1941
0
        poDS->SetGeoTransform(gt);
1942
0
    }
1943
1944
0
    if (bSeparate)
1945
0
    {
1946
0
        CreateVRTSeparate(poDS.get());
1947
0
    }
1948
0
    else
1949
0
    {
1950
0
        CreateVRTNonSeparate(poDS.get());
1951
0
    }
1952
1953
0
    return poDS;
1954
0
}
1955
1956
/************************************************************************/
1957
/*                          add_file_to_list()                          */
1958
/************************************************************************/
1959
1960
static bool add_file_to_list(const char *filename, const char *tile_index,
1961
                             CPLStringList &aosList)
1962
0
{
1963
1964
0
    if (EQUAL(CPLGetExtensionSafe(filename).c_str(), "SHP"))
1965
0
    {
1966
        /* Handle gdaltindex Shapefile as a special case */
1967
0
        auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(filename));
1968
0
        if (poDS == nullptr)
1969
0
        {
1970
0
            CPLError(CE_Failure, CPLE_AppDefined,
1971
0
                     "Unable to open shapefile `%s'.", filename);
1972
0
            return false;
1973
0
        }
1974
1975
0
        auto poLayer = poDS->GetLayer(0);
1976
0
        const auto poFDefn = poLayer->GetLayerDefn();
1977
1978
0
        if (poFDefn->GetFieldIndex("LOCATION") >= 0 &&
1979
0
            strcmp("LOCATION", tile_index) != 0)
1980
0
        {
1981
0
            CPLError(CE_Failure, CPLE_AppDefined,
1982
0
                     "This shapefile seems to be a tile index of "
1983
0
                     "OGR features and not GDAL products.");
1984
0
        }
1985
0
        const int ti_field = poFDefn->GetFieldIndex(tile_index);
1986
0
        if (ti_field < 0)
1987
0
        {
1988
0
            CPLError(CE_Failure, CPLE_AppDefined,
1989
0
                     "Unable to find field `%s' in DBF file `%s'.", tile_index,
1990
0
                     filename);
1991
0
            return false;
1992
0
        }
1993
1994
        /* Load in memory existing file names in SHP */
1995
0
        const auto nTileIndexFiles = poLayer->GetFeatureCount(TRUE);
1996
0
        if (nTileIndexFiles == 0)
1997
0
        {
1998
0
            CPLError(CE_Warning, CPLE_AppDefined,
1999
0
                     "Tile index %s is empty. Skipping it.", filename);
2000
0
            return true;
2001
0
        }
2002
0
        if (nTileIndexFiles > 100 * 1024 * 1024)
2003
0
        {
2004
0
            CPLError(CE_Failure, CPLE_AppDefined,
2005
0
                     "Too large feature count in tile index");
2006
0
            return false;
2007
0
        }
2008
2009
0
        for (auto &&poFeature : poLayer)
2010
0
        {
2011
0
            aosList.AddString(poFeature->GetFieldAsString(ti_field));
2012
0
        }
2013
0
    }
2014
0
    else
2015
0
    {
2016
0
        aosList.AddString(filename);
2017
0
    }
2018
2019
0
    return true;
2020
0
}
2021
2022
/************************************************************************/
2023
/*                         GDALBuildVRTOptions                          */
2024
/************************************************************************/
2025
2026
/** Options for use with GDALBuildVRT(). GDALBuildVRTOptions* must be allocated
2027
 * and freed with GDALBuildVRTOptionsNew() and GDALBuildVRTOptionsFree()
2028
 * respectively.
2029
 */
2030
struct GDALBuildVRTOptions
2031
{
2032
    std::string osProgramName = "gdalbuildvrt";
2033
    std::string osTileIndex = "location";
2034
    bool bStrict = false;
2035
    std::string osResolution{};
2036
    bool bSeparate = false;
2037
    bool bAllowProjectionDifference = false;
2038
    double we_res = 0;
2039
    double ns_res = 0;
2040
    bool bTargetAlignedPixels = false;
2041
    double xmin = 0;
2042
    double ymin = 0;
2043
    double xmax = 0;
2044
    double ymax = 0;
2045
    bool bAddAlpha = false;
2046
    bool bHideNoData = false;
2047
    int nSubdataset = -1;
2048
    std::string osSrcNoData{};
2049
    std::string osVRTNoData{};
2050
    std::string osOutputSRS{};
2051
    std::vector<int> anSelectedBandList{};
2052
    std::string osResampling{};
2053
    CPLStringList aosOpenOptions{};
2054
    CPLStringList aosCreateOptions{};
2055
    bool bUseSrcMaskBand = true;
2056
    bool bNoDataFromMask = false;
2057
    double dfMaskValueThreshold = 0;
2058
    bool bWriteAbsolutePath = false;
2059
    std::string osPixelFunction{};
2060
    CPLStringList aosPixelFunctionArgs{};
2061
2062
    /*! allow or suppress progress monitor and other non-error output */
2063
    bool bQuiet = true;
2064
2065
    /*! the progress function to use */
2066
    GDALProgressFunc pfnProgress = GDALDummyProgress;
2067
2068
    /*! pointer to the progress data variable */
2069
    void *pProgressData = nullptr;
2070
};
2071
2072
/************************************************************************/
2073
/*                            GDALBuildVRT()                            */
2074
/************************************************************************/
2075
2076
/* clang-format off */
2077
/**
2078
 * Build a VRT from a list of datasets.
2079
 *
2080
 * This is the equivalent of the
2081
 * <a href="/programs/gdalbuildvrt.html">gdalbuildvrt</a> utility.
2082
 *
2083
 * GDALBuildVRTOptions* must be allocated and freed with
2084
 * GDALBuildVRTOptionsNew() and GDALBuildVRTOptionsFree() respectively. pahSrcDS
2085
 * and papszSrcDSNames cannot be used at the same time.
2086
 *
2087
 * @param pszDest the destination dataset path.
2088
 * @param nSrcCount the number of input datasets.
2089
 * @param pahSrcDS the list of input datasets (or NULL, exclusive with
2090
 * papszSrcDSNames). For practical purposes, the type
2091
 * of this argument should be considered as "const GDALDatasetH* const*", that
2092
 * is neither the array nor its values are mutated by this function.
2093
 * @param papszSrcDSNames the list of input dataset names (or NULL, exclusive
2094
 * with pahSrcDS)
2095
 * @param psOptionsIn the options struct returned by GDALBuildVRTOptionsNew() or
2096
 * NULL.
2097
 * @param pbUsageError pointer to a integer output variable to store if any
2098
 * usage error has occurred.
2099
 * @return the output dataset (new dataset that must be closed using
2100
 * GDALClose()) or NULL in case of error. If using pahSrcDS, the returned VRT
2101
 * dataset has a reference to each pahSrcDS[] element. Hence pahSrcDS[] elements
2102
 * should be closed after the returned dataset if using GDALClose().
2103
 * A safer alternative is to use GDALReleaseDataset() instead of using
2104
 * GDALClose(), in which case you can close datasets in any order.
2105
2106
 *
2107
 * @since GDAL 2.1
2108
 */
2109
/* clang-format on */
2110
2111
GDALDatasetH GDALBuildVRT(const char *pszDest, int nSrcCount,
2112
                          GDALDatasetH *pahSrcDS,
2113
                          const char *const *papszSrcDSNames,
2114
                          const GDALBuildVRTOptions *psOptionsIn,
2115
                          int *pbUsageError)
2116
0
{
2117
0
    if (pszDest == nullptr)
2118
0
        pszDest = "";
2119
2120
0
    if (nSrcCount == 0)
2121
0
    {
2122
0
        CPLError(CE_Failure, CPLE_AppDefined, "No input dataset specified.");
2123
2124
0
        if (pbUsageError)
2125
0
            *pbUsageError = TRUE;
2126
0
        return nullptr;
2127
0
    }
2128
2129
    // cppcheck-suppress unreadVariable
2130
0
    GDALBuildVRTOptions sOptions(psOptionsIn ? *psOptionsIn
2131
0
                                             : GDALBuildVRTOptions());
2132
2133
0
    if (sOptions.we_res != 0 && sOptions.ns_res != 0 &&
2134
0
        !sOptions.osResolution.empty() &&
2135
0
        !EQUAL(sOptions.osResolution.c_str(), "user"))
2136
0
    {
2137
0
        CPLError(CE_Failure, CPLE_NotSupported,
2138
0
                 "-tr option is not compatible with -resolution %s",
2139
0
                 sOptions.osResolution.c_str());
2140
0
        if (pbUsageError)
2141
0
            *pbUsageError = TRUE;
2142
0
        return nullptr;
2143
0
    }
2144
2145
0
    if (sOptions.bTargetAlignedPixels && sOptions.we_res == 0 &&
2146
0
        sOptions.ns_res == 0)
2147
0
    {
2148
0
        CPLError(CE_Failure, CPLE_NotSupported,
2149
0
                 "-tap option cannot be used without using -tr");
2150
0
        if (pbUsageError)
2151
0
            *pbUsageError = TRUE;
2152
0
        return nullptr;
2153
0
    }
2154
2155
0
    if (sOptions.bAddAlpha && sOptions.bSeparate)
2156
0
    {
2157
0
        CPLError(CE_Failure, CPLE_NotSupported,
2158
0
                 "-addalpha option is not compatible with -separate.");
2159
0
        if (pbUsageError)
2160
0
            *pbUsageError = TRUE;
2161
0
        return nullptr;
2162
0
    }
2163
2164
0
    ResolutionStrategy eStrategy = AVERAGE_RESOLUTION;
2165
0
    if (sOptions.osResolution.empty() ||
2166
0
        EQUAL(sOptions.osResolution.c_str(), "user"))
2167
0
    {
2168
0
        if (sOptions.we_res != 0 || sOptions.ns_res != 0)
2169
0
            eStrategy = USER_RESOLUTION;
2170
0
        else if (EQUAL(sOptions.osResolution.c_str(), "user"))
2171
0
        {
2172
0
            CPLError(CE_Failure, CPLE_NotSupported,
2173
0
                     "-tr option must be used with -resolution user.");
2174
0
            if (pbUsageError)
2175
0
                *pbUsageError = TRUE;
2176
0
            return nullptr;
2177
0
        }
2178
0
    }
2179
0
    else if (EQUAL(sOptions.osResolution.c_str(), "average"))
2180
0
        eStrategy = AVERAGE_RESOLUTION;
2181
0
    else if (EQUAL(sOptions.osResolution.c_str(), "highest"))
2182
0
        eStrategy = HIGHEST_RESOLUTION;
2183
0
    else if (EQUAL(sOptions.osResolution.c_str(), "lowest"))
2184
0
        eStrategy = LOWEST_RESOLUTION;
2185
0
    else if (EQUAL(sOptions.osResolution.c_str(), "same"))
2186
0
        eStrategy = SAME_RESOLUTION;
2187
0
    else if (EQUAL(sOptions.osResolution.c_str(), "common"))
2188
0
        eStrategy = COMMON_RESOLUTION;
2189
2190
    /* If -srcnodata is specified, use it as the -vrtnodata if the latter is not
2191
     */
2192
    /* specified */
2193
0
    if (!sOptions.osSrcNoData.empty() && sOptions.osVRTNoData.empty())
2194
0
        sOptions.osVRTNoData = sOptions.osSrcNoData;
2195
2196
0
    VRTBuilder oBuilder(
2197
0
        sOptions.bStrict, pszDest, nSrcCount, papszSrcDSNames, pahSrcDS,
2198
0
        sOptions.anSelectedBandList.empty()
2199
0
            ? nullptr
2200
0
            : sOptions.anSelectedBandList.data(),
2201
0
        static_cast<int>(sOptions.anSelectedBandList.size()), eStrategy,
2202
0
        sOptions.we_res, sOptions.ns_res, sOptions.bTargetAlignedPixels,
2203
0
        sOptions.xmin, sOptions.ymin, sOptions.xmax, sOptions.ymax,
2204
0
        sOptions.bSeparate, sOptions.bAllowProjectionDifference,
2205
0
        sOptions.bAddAlpha, sOptions.bHideNoData, sOptions.nSubdataset,
2206
0
        sOptions.osSrcNoData.empty() ? nullptr : sOptions.osSrcNoData.c_str(),
2207
0
        sOptions.osVRTNoData.empty() ? nullptr : sOptions.osVRTNoData.c_str(),
2208
0
        sOptions.bUseSrcMaskBand, sOptions.bNoDataFromMask,
2209
0
        sOptions.dfMaskValueThreshold,
2210
0
        sOptions.osOutputSRS.empty() ? nullptr : sOptions.osOutputSRS.c_str(),
2211
0
        sOptions.osResampling.empty() ? nullptr : sOptions.osResampling.c_str(),
2212
0
        sOptions.osPixelFunction.empty() ? nullptr
2213
0
                                         : sOptions.osPixelFunction.c_str(),
2214
0
        sOptions.aosPixelFunctionArgs, sOptions.aosOpenOptions.List(),
2215
0
        sOptions.aosCreateOptions, sOptions.bWriteAbsolutePath);
2216
0
    oBuilder.m_osProgramName = sOptions.osProgramName;
2217
2218
0
    return GDALDataset::ToHandle(
2219
0
        oBuilder.Build(sOptions.pfnProgress, sOptions.pProgressData).release());
2220
0
}
2221
2222
/************************************************************************/
2223
/*                             SanitizeSRS                              */
2224
/************************************************************************/
2225
2226
static char *SanitizeSRS(const char *pszUserInput)
2227
2228
0
{
2229
0
    OGRSpatialReferenceH hSRS;
2230
0
    char *pszResult = nullptr;
2231
2232
0
    CPLErrorReset();
2233
2234
0
    hSRS = OSRNewSpatialReference(nullptr);
2235
0
    if (OSRSetFromUserInput(hSRS, pszUserInput) == OGRERR_NONE)
2236
0
        OSRExportToWkt(hSRS, &pszResult);
2237
0
    else
2238
0
    {
2239
0
        CPLError(CE_Failure, CPLE_AppDefined, "Translating SRS failed:\n%s",
2240
0
                 pszUserInput);
2241
0
    }
2242
2243
0
    OSRDestroySpatialReference(hSRS);
2244
2245
0
    return pszResult;
2246
0
}
2247
2248
/************************************************************************/
2249
/*                    GDALBuildVRTOptionsGetParser()                    */
2250
/************************************************************************/
2251
2252
static std::unique_ptr<GDALArgumentParser>
2253
GDALBuildVRTOptionsGetParser(GDALBuildVRTOptions *psOptions,
2254
                             GDALBuildVRTOptionsForBinary *psOptionsForBinary)
2255
0
{
2256
0
    auto argParser = std::make_unique<GDALArgumentParser>(
2257
0
        "gdalbuildvrt", /* bForBinary=*/psOptionsForBinary != nullptr);
2258
2259
0
    argParser->add_description(_("Builds a VRT from a list of datasets."));
2260
2261
0
    argParser->add_epilog(_(
2262
0
        "\n"
2263
0
        "e.g.\n"
2264
0
        "  % gdalbuildvrt doq_index.vrt doq/*.tif\n"
2265
0
        "  % gdalbuildvrt -input_file_list my_list.txt doq_index.vrt\n"
2266
0
        "\n"
2267
0
        "NOTES:\n"
2268
0
        "  o With -separate, each files goes into a separate band in the VRT "
2269
0
        "band.\n"
2270
0
        "    Otherwise, the files are considered as tiles of a larger mosaic.\n"
2271
0
        "  o -b option selects a band to add into vrt.  Multiple bands can be "
2272
0
        "listed.\n"
2273
0
        "    By default all bands are queried.\n"
2274
0
        "  o The default tile index field is 'location' unless otherwise "
2275
0
        "specified by\n"
2276
0
        "    -tileindex.\n"
2277
0
        "  o In case the resolution of all input files is not the same, the "
2278
0
        "-resolution\n"
2279
0
        "    flag enable the user to control the way the output resolution is "
2280
0
        "computed.\n"
2281
0
        "    Average is the default.\n"
2282
0
        "  o Input files may be any valid GDAL dataset or a GDAL raster tile "
2283
0
        "index.\n"
2284
0
        "  o For a GDAL raster tile index, all entries will be added to the "
2285
0
        "VRT.\n"
2286
0
        "  o If one GDAL dataset is made of several subdatasets and has 0 "
2287
0
        "raster bands,\n"
2288
0
        "    its datasets will be added to the VRT rather than the dataset "
2289
0
        "itself.\n"
2290
0
        "    Single subdataset could be selected by its number using the -sd "
2291
0
        "option.\n"
2292
0
        "  o By default, only datasets of same projection and band "
2293
0
        "characteristics\n"
2294
0
        "    may be added to the VRT.\n"
2295
0
        "\n"
2296
0
        "For more details, consult "
2297
0
        "https://gdal.org/programs/gdalbuildvrt.html"));
2298
2299
0
    argParser->add_quiet_argument(
2300
0
        psOptionsForBinary ? &psOptionsForBinary->bQuiet : nullptr);
2301
2302
0
    {
2303
0
        auto &group = argParser->add_mutually_exclusive_group();
2304
2305
0
        group.add_argument("-strict")
2306
0
            .flag()
2307
0
            .store_into(psOptions->bStrict)
2308
0
            .help(_("Turn warnings as failures."));
2309
2310
0
        group.add_argument("-non_strict")
2311
0
            .flag()
2312
0
            .action([psOptions](const std::string &)
2313
0
                    { psOptions->bStrict = false; })
2314
0
            .help(_("Skip source datasets that have issues with warnings, and "
2315
0
                    "continue processing."));
2316
0
    }
2317
2318
0
    argParser->add_argument("-tile_index")
2319
0
        .metavar("<field_name>")
2320
0
        .store_into(psOptions->osTileIndex)
2321
0
        .help(_("Use the specified value as the tile index field, instead of "
2322
0
                "the default value which is 'location'."));
2323
2324
0
    argParser->add_argument("-resolution")
2325
0
        .metavar("user|average|common|highest|lowest|same")
2326
0
        .action(
2327
0
            [psOptions](const std::string &s)
2328
0
            {
2329
0
                psOptions->osResolution = s;
2330
0
                if (!EQUAL(psOptions->osResolution.c_str(), "user") &&
2331
0
                    !EQUAL(psOptions->osResolution.c_str(), "average") &&
2332
0
                    !EQUAL(psOptions->osResolution.c_str(), "highest") &&
2333
0
                    !EQUAL(psOptions->osResolution.c_str(), "lowest") &&
2334
0
                    !EQUAL(psOptions->osResolution.c_str(), "same") &&
2335
0
                    !EQUAL(psOptions->osResolution.c_str(), "common"))
2336
0
                {
2337
0
                    throw std::invalid_argument(
2338
0
                        CPLSPrintf("Illegal resolution value (%s).",
2339
0
                                   psOptions->osResolution.c_str()));
2340
0
                }
2341
0
            })
2342
0
        .help(_("Control the way the output resolution is computed."));
2343
2344
0
    argParser->add_argument("-tr")
2345
0
        .metavar("<xres> <yres>")
2346
0
        .nargs(2)
2347
0
        .scan<'g', double>()
2348
0
        .help(_("Set target resolution."));
2349
2350
0
    if (psOptionsForBinary)
2351
0
    {
2352
0
        argParser->add_argument("-input_file_list")
2353
0
            .metavar("<filename>")
2354
0
            .action(
2355
0
                [psOptions, psOptionsForBinary](const std::string &s)
2356
0
                {
2357
0
                    const char *input_file_list = s.c_str();
2358
0
                    auto f = VSIVirtualHandleUniquePtr(
2359
0
                        VSIFOpenL(input_file_list, "r"));
2360
0
                    if (f)
2361
0
                    {
2362
0
                        while (1)
2363
0
                        {
2364
0
                            const char *filename = CPLReadLineL(f.get());
2365
0
                            if (filename == nullptr)
2366
0
                                break;
2367
0
                            if (!add_file_to_list(
2368
0
                                    filename, psOptions->osTileIndex.c_str(),
2369
0
                                    psOptionsForBinary->aosSrcFiles))
2370
0
                            {
2371
0
                                throw std::invalid_argument(
2372
0
                                    std::string("Cannot add ")
2373
0
                                        .append(filename)
2374
0
                                        .append(" to input file list"));
2375
0
                            }
2376
0
                        }
2377
0
                    }
2378
0
                })
2379
0
            .help(_("Text file with an input filename on each line"));
2380
0
    }
2381
2382
0
    {
2383
0
        auto &group = argParser->add_mutually_exclusive_group();
2384
2385
0
        group.add_argument("-separate")
2386
0
            .flag()
2387
0
            .store_into(psOptions->bSeparate)
2388
0
            .help(_("Place each input file into a separate band."));
2389
2390
0
        group.add_argument("-pixel-function")
2391
0
            .metavar("<function>")
2392
0
            .action(
2393
0
                [psOptions](const std::string &s)
2394
0
                {
2395
0
                    auto *poPixFun =
2396
0
                        VRTDerivedRasterBand::GetPixelFunction(s.c_str());
2397
0
                    if (poPixFun == nullptr)
2398
0
                    {
2399
0
                        throw std::invalid_argument(
2400
0
                            s + " is not a registered pixel function.");
2401
0
                    }
2402
2403
0
                    psOptions->osPixelFunction = s;
2404
0
                })
2405
2406
0
            .help("Function to calculate value from overlapping inputs");
2407
0
    }
2408
2409
0
    argParser->add_argument("-pixel-function-arg")
2410
0
        .metavar("<NAME>=<VALUE>")
2411
0
        .append()
2412
0
        .action([psOptions](const std::string &s)
2413
0
                { psOptions->aosPixelFunctionArgs.AddString(s); })
2414
0
        .help(_("Pixel function argument(s)"));
2415
2416
0
    argParser->add_argument("-allow_projection_difference")
2417
0
        .flag()
2418
0
        .store_into(psOptions->bAllowProjectionDifference)
2419
0
        .help(_("Accept source files not in the same projection (but without "
2420
0
                "reprojecting them!)."));
2421
2422
0
    argParser->add_argument("-sd")
2423
0
        .metavar("<n>")
2424
0
        .store_into(psOptions->nSubdataset)
2425
0
        .help(_("Use subdataset of specified index (starting at 1), instead of "
2426
0
                "the source dataset itself."));
2427
2428
0
    argParser->add_argument("-tap")
2429
0
        .flag()
2430
0
        .store_into(psOptions->bTargetAlignedPixels)
2431
0
        .help(_("Align the coordinates of the extent of the output file to the "
2432
0
                "values of the resolution."));
2433
2434
0
    argParser->add_argument("-te")
2435
0
        .metavar("<xmin> <ymin> <xmax> <ymax>")
2436
0
        .nargs(4)
2437
0
        .scan<'g', double>()
2438
0
        .help(_("Set georeferenced extents of output file to be created."));
2439
2440
0
    argParser->add_argument("-addalpha")
2441
0
        .flag()
2442
0
        .store_into(psOptions->bAddAlpha)
2443
0
        .help(_("Adds an alpha mask band to the VRT when the source raster "
2444
0
                "have none."));
2445
2446
0
    argParser->add_argument("-b")
2447
0
        .metavar("<band>")
2448
0
        .append()
2449
0
        .store_into(psOptions->anSelectedBandList)
2450
0
        .help(_("Specify input band(s) number."));
2451
2452
0
    argParser->add_argument("-hidenodata")
2453
0
        .flag()
2454
0
        .store_into(psOptions->bHideNoData)
2455
0
        .help(_("Makes the VRT band not report the NoData."));
2456
2457
0
    if (psOptionsForBinary)
2458
0
    {
2459
0
        argParser->add_argument("-overwrite")
2460
0
            .flag()
2461
0
            .store_into(psOptionsForBinary->bOverwrite)
2462
0
            .help(_("Overwrite the VRT if it already exists."));
2463
0
    }
2464
2465
0
    argParser->add_argument("-srcnodata")
2466
0
        .metavar("\"<value>[ <value>]...\"")
2467
0
        .store_into(psOptions->osSrcNoData)
2468
0
        .help(_("Set nodata values for input bands."));
2469
2470
0
    argParser->add_argument("-vrtnodata")
2471
0
        .metavar("\"<value>[ <value>]...\"")
2472
0
        .store_into(psOptions->osVRTNoData)
2473
0
        .help(_("Set nodata values at the VRT band level."));
2474
2475
0
    argParser->add_argument("-a_srs")
2476
0
        .metavar("<srs_def>")
2477
0
        .action(
2478
0
            [psOptions](const std::string &s)
2479
0
            {
2480
0
                char *pszSRS = SanitizeSRS(s.c_str());
2481
0
                if (pszSRS == nullptr)
2482
0
                {
2483
0
                    throw std::invalid_argument("Invalid value for -a_srs");
2484
0
                }
2485
0
                psOptions->osOutputSRS = pszSRS;
2486
0
                CPLFree(pszSRS);
2487
0
            })
2488
0
        .help(_("Override the projection for the output file.."));
2489
2490
0
    argParser->add_argument("-r")
2491
0
        .metavar("nearest|bilinear|cubic|cubicspline|lanczos|average|mode")
2492
0
        .store_into(psOptions->osResampling)
2493
0
        .help(_("Resampling algorithm."));
2494
2495
0
    argParser->add_open_options_argument(&psOptions->aosOpenOptions);
2496
2497
0
    argParser->add_creation_options_argument(psOptions->aosCreateOptions);
2498
2499
0
    argParser->add_argument("-write_absolute_path")
2500
0
        .flag()
2501
0
        .store_into(psOptions->bWriteAbsolutePath)
2502
0
        .help(_("Write the absolute path of the raster files in the tile index "
2503
0
                "file."));
2504
2505
0
    argParser->add_argument("-ignore_srcmaskband")
2506
0
        .flag()
2507
0
        .action([psOptions](const std::string &)
2508
0
                { psOptions->bUseSrcMaskBand = false; })
2509
0
        .help(_("Cause mask band of sources will not be taken into account."));
2510
2511
0
    argParser->add_argument("-nodata_max_mask_threshold")
2512
0
        .metavar("<threshold>")
2513
0
        .scan<'g', double>()
2514
0
        .action(
2515
0
            [psOptions](const std::string &s)
2516
0
            {
2517
0
                psOptions->bNoDataFromMask = true;
2518
0
                psOptions->dfMaskValueThreshold = CPLAtofM(s.c_str());
2519
0
            })
2520
0
        .help(_("Replaces the value of the source with the value of -vrtnodata "
2521
0
                "when the value of the mask band of the source is less or "
2522
0
                "equal to the threshold."));
2523
2524
0
    argParser->add_argument("-program_name")
2525
0
        .store_into(psOptions->osProgramName)
2526
0
        .hidden();
2527
2528
0
    if (psOptionsForBinary)
2529
0
    {
2530
0
        if (psOptionsForBinary->osDstFilename.empty())
2531
0
        {
2532
            // We normally go here, unless undocumented -o switch is used
2533
0
            argParser->add_argument("vrt_dataset_name")
2534
0
                .metavar("<vrt_dataset_name>")
2535
0
                .store_into(psOptionsForBinary->osDstFilename)
2536
0
                .help(_("Output VRT."));
2537
0
        }
2538
2539
0
        argParser->add_argument("src_dataset_name")
2540
0
            .metavar("<src_dataset_name>")
2541
0
            .nargs(argparse::nargs_pattern::any)
2542
0
            .action(
2543
0
                [psOptions, psOptionsForBinary](const std::string &s)
2544
0
                {
2545
0
                    if (!add_file_to_list(s.c_str(),
2546
0
                                          psOptions->osTileIndex.c_str(),
2547
0
                                          psOptionsForBinary->aosSrcFiles))
2548
0
                    {
2549
0
                        throw std::invalid_argument(
2550
0
                            std::string("Cannot add ")
2551
0
                                .append(s)
2552
0
                                .append(" to input file list"));
2553
0
                    }
2554
0
                })
2555
0
            .help(_("Input dataset(s)."));
2556
0
    }
2557
2558
0
    return argParser;
2559
0
}
2560
2561
/************************************************************************/
2562
/*                     GDALBuildVRTGetParserUsage()                     */
2563
/************************************************************************/
2564
2565
std::string GDALBuildVRTGetParserUsage()
2566
0
{
2567
0
    try
2568
0
    {
2569
0
        GDALBuildVRTOptions sOptions;
2570
0
        GDALBuildVRTOptionsForBinary sOptionsForBinary;
2571
0
        auto argParser =
2572
0
            GDALBuildVRTOptionsGetParser(&sOptions, &sOptionsForBinary);
2573
0
        return argParser->usage();
2574
0
    }
2575
0
    catch (const std::exception &err)
2576
0
    {
2577
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
2578
0
                 err.what());
2579
0
        return std::string();
2580
0
    }
2581
0
}
2582
2583
/************************************************************************/
2584
/*                       GDALBuildVRTOptionsNew()                       */
2585
/************************************************************************/
2586
2587
/**
2588
 * Allocates a GDALBuildVRTOptions struct.
2589
 *
2590
 * @param papszArgv NULL terminated list of options (potentially including
2591
 * filename and open options too), or NULL. The accepted options are the ones of
2592
 * the <a href="/programs/gdalbuildvrt.html">gdalbuildvrt</a> utility.
2593
 * @param psOptionsForBinary (output) may be NULL (and should generally be
2594
 * NULL), otherwise (gdalbuildvrt_bin.cpp use case) must be allocated with
2595
 * GDALBuildVRTOptionsForBinaryNew() prior to this function. Will be filled
2596
 * with potentially present filename, open options,...
2597
 * @return pointer to the allocated GDALBuildVRTOptions struct. Must be freed
2598
 * with GDALBuildVRTOptionsFree().
2599
 *
2600
 * @since GDAL 2.1
2601
 */
2602
2603
GDALBuildVRTOptions *
2604
GDALBuildVRTOptionsNew(char **papszArgv,
2605
                       GDALBuildVRTOptionsForBinary *psOptionsForBinary)
2606
0
{
2607
0
    auto psOptions = std::make_unique<GDALBuildVRTOptions>();
2608
2609
0
    CPLStringList aosArgv;
2610
0
    const int nArgc = CSLCount(papszArgv);
2611
0
    for (int i = 0;
2612
0
         i < nArgc && papszArgv != nullptr && papszArgv[i] != nullptr; i++)
2613
0
    {
2614
0
        if (psOptionsForBinary && EQUAL(papszArgv[i], "-o") && i + 1 < nArgc &&
2615
0
            papszArgv[i + 1] != nullptr)
2616
0
        {
2617
            // Undocumented alternate way of specifying the destination file
2618
0
            psOptionsForBinary->osDstFilename = papszArgv[i + 1];
2619
0
            ++i;
2620
0
        }
2621
        // argparser will be confused if the value of a string argument
2622
        // starts with a negative sign.
2623
0
        else if (EQUAL(papszArgv[i], "-srcnodata") && i + 1 < nArgc)
2624
0
        {
2625
0
            ++i;
2626
0
            psOptions->osSrcNoData = papszArgv[i];
2627
0
        }
2628
        // argparser will be confused if the value of a string argument
2629
        // starts with a negative sign.
2630
0
        else if (EQUAL(papszArgv[i], "-vrtnodata") && i + 1 < nArgc)
2631
0
        {
2632
0
            ++i;
2633
0
            psOptions->osVRTNoData = papszArgv[i];
2634
0
        }
2635
2636
0
        else
2637
0
        {
2638
0
            aosArgv.AddString(papszArgv[i]);
2639
0
        }
2640
0
    }
2641
2642
0
    try
2643
0
    {
2644
0
        auto argParser =
2645
0
            GDALBuildVRTOptionsGetParser(psOptions.get(), psOptionsForBinary);
2646
2647
0
        argParser->parse_args_without_binary_name(aosArgv.List());
2648
2649
0
        if (auto adfTargetRes = argParser->present<std::vector<double>>("-tr"))
2650
0
        {
2651
0
            psOptions->we_res = (*adfTargetRes)[0];
2652
0
            psOptions->ns_res = (*adfTargetRes)[1];
2653
0
        }
2654
2655
0
        if (auto oTE = argParser->present<std::vector<double>>("-te"))
2656
0
        {
2657
0
            psOptions->xmin = (*oTE)[0];
2658
0
            psOptions->ymin = (*oTE)[1];
2659
0
            psOptions->xmax = (*oTE)[2];
2660
0
            psOptions->ymax = (*oTE)[3];
2661
0
        }
2662
2663
0
        if (psOptions->osPixelFunction.empty() &&
2664
0
            !psOptions->aosPixelFunctionArgs.empty())
2665
0
        {
2666
0
            throw std::runtime_error(
2667
0
                "Pixel function arguments provided without a pixel function");
2668
0
        }
2669
2670
0
        return psOptions.release();
2671
0
    }
2672
0
    catch (const std::exception &err)
2673
0
    {
2674
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s", err.what());
2675
0
        return nullptr;
2676
0
    }
2677
0
}
2678
2679
/************************************************************************/
2680
/*                      GDALBuildVRTOptionsFree()                       */
2681
/************************************************************************/
2682
2683
/**
2684
 * Frees the GDALBuildVRTOptions struct.
2685
 *
2686
 * @param psOptions the options struct for GDALBuildVRT().
2687
 *
2688
 * @since GDAL 2.1
2689
 */
2690
2691
void GDALBuildVRTOptionsFree(GDALBuildVRTOptions *psOptions)
2692
0
{
2693
0
    delete psOptions;
2694
0
}
2695
2696
/************************************************************************/
2697
/*                   GDALBuildVRTOptionsSetProgress()                   */
2698
/************************************************************************/
2699
2700
/**
2701
 * Set a progress function.
2702
 *
2703
 * @param psOptions the options struct for GDALBuildVRT().
2704
 * @param pfnProgress the progress callback.
2705
 * @param pProgressData the user data for the progress callback.
2706
 *
2707
 * @since GDAL 2.1
2708
 */
2709
2710
void GDALBuildVRTOptionsSetProgress(GDALBuildVRTOptions *psOptions,
2711
                                    GDALProgressFunc pfnProgress,
2712
                                    void *pProgressData)
2713
0
{
2714
0
    psOptions->pfnProgress = pfnProgress ? pfnProgress : GDALDummyProgress;
2715
0
    psOptions->pProgressData = pProgressData;
2716
0
    if (pfnProgress == GDALTermProgress)
2717
0
        psOptions->bQuiet = false;
2718
0
}