Coverage Report

Created: 2026-07-25 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalwarp_lib.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  High Performance Image Reprojector
4
 * Purpose:  Test program for high performance warper API.
5
 * Author:   Frank Warmerdam <warmerdam@pobox.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2002, i3 - information integration and imaging
9
 *                          Fort Collins, CO
10
 * Copyright (c) 2007-2015, Even Rouault <even dot rouault at spatialys.com>
11
 * Copyright (c) 2015, Faza Mahamood
12
 *
13
 * SPDX-License-Identifier: MIT
14
 ****************************************************************************/
15
16
#include "cpl_port.h"
17
#include "gdal_utils.h"
18
#include "gdal_utils_priv.h"
19
#include "gdalargumentparser.h"
20
21
#include <cctype>
22
#include <cmath>
23
#include <cstdio>
24
#include <cstdlib>
25
#include <cstring>
26
27
#include <algorithm>
28
#include <array>
29
#include <limits>
30
#include <set>
31
#include <utility>
32
#include <vector>
33
34
// Suppress deprecation warning for GDALOpenVerticalShiftGrid and
35
// GDALApplyVerticalShiftGrid
36
#ifndef CPL_WARN_DEPRECATED_GDALOpenVerticalShiftGrid
37
#define CPL_WARN_DEPRECATED_GDALOpenVerticalShiftGrid(x)
38
#define CPL_WARN_DEPRECATED_GDALApplyVerticalShiftGrid(x)
39
#endif
40
41
#include "commonutils.h"
42
#include "cpl_conv.h"
43
#include "cpl_error.h"
44
#include "cpl_progress.h"
45
#include "cpl_string.h"
46
#include "gdal.h"
47
#include "gdal_alg.h"
48
#include "gdal_alg_priv.h"
49
#include "gdal_priv.h"
50
#include "gdalwarper.h"
51
#include "ogr_api.h"
52
#include "ogr_core.h"
53
#include "ogr_geometry.h"
54
#include "ogr_spatialref.h"
55
#include "ogr_srs_api.h"
56
#include "ogr_proj_p.h"
57
#include "ogrct_priv.h"
58
#include "ogrsf_frmts.h"
59
#include "vrtdataset.h"
60
#include "../frmts/gtiff/cogdriver.h"
61
62
/************************************************************************/
63
/*                          GDALWarpAppOptions                          */
64
/************************************************************************/
65
66
/** Options for use with GDALWarp(). GDALWarpAppOptions* must be allocated and
67
 * freed with GDALWarpAppOptionsNew() and GDALWarpAppOptionsFree() respectively.
68
 */
69
struct GDALWarpAppOptions
70
{
71
    /*! Raw program arguments */
72
    CPLStringList aosArgs{};
73
74
    /*! set georeferenced extents of output file to be created (in target SRS by
75
       default, or in the SRS specified with pszTE_SRS) */
76
    double dfMinX = 0;
77
    double dfMinY = 0;
78
    double dfMaxX = 0;
79
    double dfMaxY = 0;
80
81
    /*! the SRS in which to interpret the coordinates given in
82
       GDALWarpAppOptions::dfMinX, GDALWarpAppOptions::dfMinY,
83
       GDALWarpAppOptions::dfMaxX and GDALWarpAppOptions::dfMaxY. The SRS may be
84
       any of the usual GDAL/OGR forms, complete WKT, PROJ.4, EPSG:n or a file
85
       containing the WKT. It is a convenience e.g. when knowing the output
86
       coordinates in a geodetic long/lat SRS, but still wanting a result in a
87
       projected coordinate system. */
88
    std::string osTE_SRS{};
89
90
    /*! set output file resolution (in target georeferenced units) */
91
    double dfXRes = 0;
92
    double dfYRes = 0;
93
94
    /*! whether target pixels should have dfXRes == dfYRes */
95
    bool bSquarePixels = false;
96
97
    /*! align the coordinates of the extent of the output file to the values of
98
       the GDALWarpAppOptions::dfXRes and GDALWarpAppOptions::dfYRes, such that
99
       the aligned extent includes the minimum extent. */
100
    bool bTargetAlignedPixels = false;
101
102
    /*! set output file size in pixels and lines. If
103
       GDALWarpAppOptions::nForcePixels or GDALWarpAppOptions::nForceLines is
104
       set to 0, the other dimension will be guessed from the computed
105
       resolution. Note that GDALWarpAppOptions::nForcePixels and
106
        GDALWarpAppOptions::nForceLines cannot be used with
107
       GDALWarpAppOptions::dfXRes and GDALWarpAppOptions::dfYRes. */
108
    int nForcePixels = 0;
109
    int nForceLines = 0;
110
111
    /*! allow or suppress progress monitor and other non-error output */
112
    bool bQuiet = true;
113
114
    /*! the progress function to use */
115
    GDALProgressFunc pfnProgress = GDALDummyProgress;
116
117
    /*! pointer to the progress data variable */
118
    void *pProgressData = nullptr;
119
120
    /*! creates an output alpha band to identify nodata (unset/transparent)
121
       pixels when set to true */
122
    bool bEnableDstAlpha = false;
123
124
    /*! forces the last band of an input file to be considered as alpha band. */
125
    bool bEnableSrcAlpha = false;
126
127
    /*! Prevent a source alpha band from being considered as such */
128
    bool bDisableSrcAlpha = false;
129
130
    /*! output format. Use the short format name. */
131
    std::string osFormat{};
132
133
    bool bCreateOutput = false;
134
135
    /*! list of warp options. ("NAME1=VALUE1","NAME2=VALUE2",...). The
136
        GDALWarpOptions::aosWarpOptions docs show all options. */
137
    CPLStringList aosWarpOptions{};
138
139
    double dfErrorThreshold = -1;
140
141
    /*! the amount of memory (in megabytes) that the warp API is allowed
142
        to use for caching. */
143
    double dfWarpMemoryLimit = 0;
144
145
    /*! list of create options for the output format driver. See format
146
        specific documentation for legal creation options for each format. */
147
    CPLStringList aosCreateOptions{};
148
149
    /*! the data type of the output bands */
150
    GDALDataType eOutputType = GDT_Unknown;
151
152
    /*! working pixel data type. The data type of pixels in the source
153
        image and destination image buffers. */
154
    GDALDataType eWorkingType = GDT_Unknown;
155
156
    /*! the resampling method. Available methods are: near, bilinear,
157
        cubic, cubicspline, lanczos, average, mode, max, min, med,
158
        q1, q3, sum */
159
    GDALResampleAlg eResampleAlg = GRA_NearestNeighbour;
160
161
    /*! whether -r was specified */
162
    bool bResampleAlgSpecifiedByUser = false;
163
164
    /*! nodata masking values for input bands (different values can be supplied
165
        for each band). ("value1 value2 ..."). Masked values will not be used
166
        in interpolation. Use a value of "None" to ignore intrinsic nodata
167
        settings on the source dataset. */
168
    std::string osSrcNodata{};
169
170
    /*! nodata values for output bands (different values can be supplied for
171
        each band). ("value1 value2 ..."). New files will be initialized to
172
        this value and if possible the nodata value will be recorded in the
173
        output file. Use a value of "None" to ensure that nodata is not defined.
174
        If this argument is not used then nodata values will be copied from
175
        the source dataset. */
176
    std::string osDstNodata{};
177
178
    /*! use multithreaded warping implementation. Multiple threads will be used
179
        to process chunks of image and perform input/output operation
180
       simultaneously. */
181
    bool bMulti = false;
182
183
    /*! list of transformer options suitable to pass to
184
       GDALCreateGenImgProjTransformer2().
185
        ("NAME1=VALUE1","NAME2=VALUE2",...) */
186
    CPLStringList aosTransformerOptions{};
187
188
    /*! enable use of a blend cutline from a vector dataset name or a WKT
189
     * geometry
190
     */
191
    std::string osCutlineDSNameOrWKT{};
192
193
    /*! cutline SRS */
194
    std::string osCutlineSRS{};
195
196
    /*! the named layer to be selected from the cutline datasource */
197
    std::string osCLayer{};
198
199
    /*! restrict desired cutline features based on attribute query */
200
    std::string osCWHERE{};
201
202
    /*! SQL query to select the cutline features instead of from a layer
203
        with osCLayer */
204
    std::string osCSQL{};
205
206
    /*! crop the extent of the target dataset to the extent of the cutline */
207
    bool bCropToCutline = false;
208
209
    /*! copy dataset and band metadata will be copied from the first source
210
       dataset. Items that differ between source datasets will be set "*" (see
211
       GDALWarpAppOptions::pszMDConflictValue) */
212
    bool bCopyMetadata = true;
213
214
    /*! copy band information from the first source dataset */
215
    bool bCopyBandInfo = true;
216
217
    /*! value to set metadata items that conflict between source datasets
218
       (default is "*"). Use "" to remove conflicting items. */
219
    std::string osMDConflictValue = "*";
220
221
    /*! set the color interpretation of the bands of the target dataset from the
222
     * source dataset */
223
    bool bSetColorInterpretation = false;
224
225
    /*! overview level of source files to be used */
226
    int nOvLevel = OVR_LEVEL_AUTO;
227
228
    /*! Whether to enable vertical shift adjustment */
229
    bool bVShift = false;
230
231
    /*! Whether to disable vertical shift adjustment */
232
    bool bNoVShift = false;
233
234
    /*! Source bands */
235
    std::vector<int> anSrcBands{};
236
237
    /*! Destination bands */
238
    std::vector<int> anDstBands{};
239
240
    /*! Used when using a temporary TIFF file while warping */
241
    bool bDeleteOutputFileOnceCreated = false;
242
243
    /*! set to true to customize error messages when called from "new" (GDAL 3.11) CLI or Algorithm API */
244
    bool bInvokedFromGdalAlgorithm = false;
245
};
246
247
static CPLErr
248
LoadCutline(const std::string &osCutlineDSNameOrWKT, const std::string &osSRS,
249
            const std::string &oszCLayer, const std::string &osCWHERE,
250
            const std::string &osCSQL, OGRGeometryH *phCutlineRet);
251
static CPLErr TransformCutlineToSource(GDALDataset *poSrcDS,
252
                                       OGRGeometry *poCutline,
253
                                       char ***ppapszWarpOptions,
254
                                       CSLConstList papszTO);
255
256
static GDALDatasetH GDALWarpCreateOutput(
257
    int nSrcCount, GDALDatasetH *pahSrcDS, const char *pszFilename,
258
    const char *pszFormat, char **papszTO, CSLConstList papszCreateOptions,
259
    GDALDataType eDT, GDALTransformerArgUniquePtr &hTransformArg,
260
    bool bSetColorInterpretation, GDALWarpAppOptions *psOptions,
261
    bool bUpdateTransformerWithDestGT);
262
263
static void RemoveConflictingMetadata(GDALMajorObjectH hObj,
264
                                      CSLConstList papszMetadata,
265
                                      const char *pszValueConflict);
266
267
static double GetAverageSegmentLength(const OGRGeometry *poGeom)
268
0
{
269
0
    if (!poGeom)
270
0
        return 0;
271
0
    switch (wkbFlatten(poGeom->getGeometryType()))
272
0
    {
273
0
        case wkbLineString:
274
0
        {
275
0
            const auto *poLS = poGeom->toLineString();
276
0
            double dfSum = 0;
277
0
            const int nPoints = poLS->getNumPoints();
278
0
            if (nPoints == 0)
279
0
                return 0;
280
0
            for (int i = 0; i < nPoints - 1; i++)
281
0
            {
282
0
                double dfX1 = poLS->getX(i);
283
0
                double dfY1 = poLS->getY(i);
284
0
                double dfX2 = poLS->getX(i + 1);
285
0
                double dfY2 = poLS->getY(i + 1);
286
0
                double dfDX = dfX2 - dfX1;
287
0
                double dfDY = dfY2 - dfY1;
288
0
                dfSum += sqrt(dfDX * dfDX + dfDY * dfDY);
289
0
            }
290
0
            return dfSum / nPoints;
291
0
        }
292
293
0
        case wkbPolygon:
294
0
        {
295
0
            if (poGeom->IsEmpty())
296
0
                return 0;
297
0
            double dfSum = 0;
298
0
            for (const auto *poLS : poGeom->toPolygon())
299
0
            {
300
0
                dfSum += GetAverageSegmentLength(poLS);
301
0
            }
302
0
            return dfSum / (1 + poGeom->toPolygon()->getNumInteriorRings());
303
0
        }
304
305
0
        case wkbMultiPolygon:
306
0
        case wkbMultiLineString:
307
0
        case wkbGeometryCollection:
308
0
        {
309
0
            if (poGeom->IsEmpty())
310
0
                return 0;
311
0
            double dfSum = 0;
312
0
            for (const auto *poSubGeom : poGeom->toGeometryCollection())
313
0
            {
314
0
                dfSum += GetAverageSegmentLength(poSubGeom);
315
0
            }
316
0
            return dfSum / poGeom->toGeometryCollection()->getNumGeometries();
317
0
        }
318
319
0
        default:
320
0
            return 0;
321
0
    }
322
0
}
323
324
/************************************************************************/
325
/*                           FetchSrcMethod()                           */
326
/************************************************************************/
327
328
static const char *FetchSrcMethod(CSLConstList papszTO,
329
                                  const char *pszDefault = nullptr)
330
0
{
331
0
    const char *pszMethod = CSLFetchNameValue(papszTO, "SRC_METHOD");
332
0
    if (!pszMethod)
333
0
        pszMethod = CSLFetchNameValueDef(papszTO, "METHOD", pszDefault);
334
0
    return pszMethod;
335
0
}
336
337
static const char *FetchSrcMethod(const CPLStringList &aosTO,
338
                                  const char *pszDefault = nullptr)
339
0
{
340
0
    const char *pszMethod = aosTO.FetchNameValue("SRC_METHOD");
341
0
    if (!pszMethod)
342
0
        pszMethod = aosTO.FetchNameValueDef("METHOD", pszDefault);
343
0
    return pszMethod;
344
0
}
345
346
/************************************************************************/
347
/*                          GetSrcDSProjection()                        */
348
/*                                                                      */
349
/* Takes into account SRC_SRS transformer option in priority, and then  */
350
/* dataset characteristics as well as the METHOD transformer            */
351
/* option to determine the source SRS.                                  */
352
/************************************************************************/
353
354
static CPLString GetSrcDSProjection(GDALDatasetH hDS, CSLConstList papszTO)
355
0
{
356
0
    const char *pszProjection = CSLFetchNameValue(papszTO, "SRC_SRS");
357
0
    if (pszProjection != nullptr || hDS == nullptr)
358
0
    {
359
0
        return pszProjection ? pszProjection : "";
360
0
    }
361
362
0
    const char *pszMethod = FetchSrcMethod(papszTO);
363
0
    CSLConstList papszMD = nullptr;
364
0
    const OGRSpatialReferenceH hSRS = GDALGetSpatialRef(hDS);
365
0
    const char *pszGeolocationDataset =
366
0
        CSLFetchNameValueDef(papszTO, "SRC_GEOLOC_ARRAY",
367
0
                             CSLFetchNameValue(papszTO, "GEOLOC_ARRAY"));
368
0
    if (pszGeolocationDataset != nullptr &&
369
0
        (pszMethod == nullptr || EQUAL(pszMethod, "GEOLOC_ARRAY")))
370
0
    {
371
0
        auto aosMD =
372
0
            GDALCreateGeolocationMetadata(hDS, pszGeolocationDataset, true);
373
0
        pszProjection = aosMD.FetchNameValue("SRS");
374
0
        if (pszProjection)
375
0
            return pszProjection;  // return in this scope so that aosMD is
376
                                   // still valid
377
0
    }
378
0
    else if (hSRS && (pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")))
379
0
    {
380
0
        char *pszWKT = nullptr;
381
0
        {
382
0
            CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
383
0
            if (OSRExportToWkt(hSRS, &pszWKT) != OGRERR_NONE)
384
0
            {
385
0
                CPLFree(pszWKT);
386
0
                pszWKT = nullptr;
387
0
                const char *const apszOptions[] = {"FORMAT=WKT2", nullptr};
388
0
                OSRExportToWktEx(hSRS, &pszWKT, apszOptions);
389
0
            }
390
0
        }
391
0
        CPLString osWKT = pszWKT ? pszWKT : "";
392
0
        CPLFree(pszWKT);
393
0
        return osWKT;
394
0
    }
395
0
    else if (GDALGetGCPProjection(hDS) != nullptr &&
396
0
             strlen(GDALGetGCPProjection(hDS)) > 0 &&
397
0
             GDALGetGCPCount(hDS) > 1 &&
398
0
             (pszMethod == nullptr || STARTS_WITH_CI(pszMethod, "GCP_")))
399
0
    {
400
0
        pszProjection = GDALGetGCPProjection(hDS);
401
0
    }
402
0
    else if (GDALGetMetadata(hDS, GDAL_MDD_RPC) != nullptr &&
403
0
             (pszMethod == nullptr || EQUAL(pszMethod, GDAL_MDD_RPC)))
404
0
    {
405
0
        pszProjection = SRS_WKT_WGS84_LAT_LONG;
406
0
    }
407
0
    else if ((papszMD = GDALGetMetadata(hDS, GDAL_MDD_GEOLOCATION)) !=
408
0
                 nullptr &&
409
0
             (pszMethod == nullptr || EQUAL(pszMethod, "GEOLOC_ARRAY")))
410
0
    {
411
0
        pszProjection = CSLFetchNameValue(papszMD, "SRS");
412
0
    }
413
0
    return pszProjection ? pszProjection : "";
414
0
}
415
416
/************************************************************************/
417
/*                        CreateCTCutlineToSrc()                        */
418
/************************************************************************/
419
420
static std::unique_ptr<OGRCoordinateTransformation> CreateCTCutlineToSrc(
421
    const OGRSpatialReference *poRasterSRS, const OGRSpatialReference *poDstSRS,
422
    const OGRSpatialReference *poCutlineSRS, CSLConstList papszTO)
423
0
{
424
0
    const OGRSpatialReference *poCutlineOrTargetSRS =
425
0
        poCutlineSRS ? poCutlineSRS : poDstSRS;
426
0
    std::unique_ptr<OGRCoordinateTransformation> poCTCutlineToSrc;
427
0
    if (poCutlineOrTargetSRS && poRasterSRS &&
428
0
        !poCutlineOrTargetSRS->IsSame(poRasterSRS))
429
0
    {
430
0
        OGRCoordinateTransformationOptions oOptions;
431
        // If the cutline SRS is the same as the target SRS and there is
432
        // an explicit -ct between the source SRS and the target SRS, then
433
        // use it in the reverse way to transform from the cutline SRS to
434
        // the source SRS.
435
0
        if (poDstSRS && poCutlineOrTargetSRS->IsSame(poDstSRS))
436
0
        {
437
0
            const char *pszCT =
438
0
                CSLFetchNameValue(papszTO, "COORDINATE_OPERATION");
439
0
            if (pszCT)
440
0
            {
441
0
                oOptions.SetCoordinateOperation(pszCT, /* bInverse = */ true);
442
0
            }
443
0
        }
444
0
        poCTCutlineToSrc.reset(OGRCreateCoordinateTransformation(
445
0
            poCutlineOrTargetSRS, poRasterSRS, oOptions));
446
0
    }
447
0
    return poCTCutlineToSrc;
448
0
}
449
450
/************************************************************************/
451
/*                           CropToCutline()                            */
452
/************************************************************************/
453
454
static CPLErr CropToCutline(const OGRGeometry *poCutline, CSLConstList papszTO,
455
                            CSLConstList papszWarpOptions, int nSrcCount,
456
                            GDALDatasetH *pahSrcDS, double &dfMinX,
457
                            double &dfMinY, double &dfMaxX, double &dfMaxY,
458
                            const GDALWarpAppOptions *psOptions)
459
0
{
460
    // We could possibly directly reproject from cutline SRS to target SRS,
461
    // but when applying the cutline, it is reprojected to source raster image
462
    // space using the source SRS. To be consistent, we reproject
463
    // the cutline from cutline SRS to source SRS and then from source SRS to
464
    // target SRS.
465
0
    const OGRSpatialReference *poCutlineSRS = poCutline->getSpatialReference();
466
0
    const char *pszThisTargetSRS = CSLFetchNameValue(papszTO, "DST_SRS");
467
0
    std::unique_ptr<OGRSpatialReference> poSrcSRS;
468
0
    std::unique_ptr<OGRSpatialReference> poDstSRS;
469
470
0
    const CPLString osThisSourceSRS =
471
0
        GetSrcDSProjection(nSrcCount > 0 ? pahSrcDS[0] : nullptr, papszTO);
472
0
    if (!osThisSourceSRS.empty())
473
0
    {
474
0
        poSrcSRS = std::make_unique<OGRSpatialReference>();
475
0
        poSrcSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
476
0
        if (poSrcSRS->SetFromUserInput(osThisSourceSRS) != OGRERR_NONE)
477
0
        {
478
0
            CPLError(CE_Failure, CPLE_AppDefined,
479
0
                     "Cannot compute bounding box of cutline.");
480
0
            return CE_Failure;
481
0
        }
482
0
    }
483
0
    else if (!pszThisTargetSRS && !poCutlineSRS)
484
0
    {
485
0
        OGREnvelope sEnvelope;
486
0
        poCutline->getEnvelope(&sEnvelope);
487
488
0
        dfMinX = sEnvelope.MinX;
489
0
        dfMinY = sEnvelope.MinY;
490
0
        dfMaxX = sEnvelope.MaxX;
491
0
        dfMaxY = sEnvelope.MaxY;
492
493
0
        return CE_None;
494
0
    }
495
0
    else
496
0
    {
497
0
        CPLError(CE_Failure, CPLE_AppDefined,
498
0
                 "Cannot compute bounding box of cutline. Cannot find "
499
0
                 "source SRS");
500
0
        return CE_Failure;
501
0
    }
502
503
0
    if (pszThisTargetSRS)
504
0
    {
505
0
        poDstSRS = std::make_unique<OGRSpatialReference>();
506
0
        poDstSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
507
0
        if (poDstSRS->SetFromUserInput(pszThisTargetSRS) != OGRERR_NONE)
508
0
        {
509
0
            CPLError(CE_Failure, CPLE_AppDefined,
510
0
                     "Cannot compute bounding box of cutline.");
511
0
            return CE_Failure;
512
0
        }
513
0
    }
514
0
    else
515
0
    {
516
0
        poDstSRS.reset(poSrcSRS->Clone());
517
0
    }
518
519
0
    auto poCutlineGeom = std::unique_ptr<OGRGeometry>(poCutline->clone());
520
0
    auto poCTCutlineToSrc = CreateCTCutlineToSrc(poSrcSRS.get(), poDstSRS.get(),
521
0
                                                 poCutlineSRS, papszTO);
522
523
0
    std::unique_ptr<OGRCoordinateTransformation> poCTSrcToDst;
524
0
    if (!poSrcSRS->IsSame(poDstSRS.get()))
525
0
    {
526
0
        poCTSrcToDst.reset(
527
0
            OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get()));
528
0
    }
529
530
    // Reproject cutline to target SRS, by doing intermediate vertex
531
    // densification in source SRS.
532
0
    if (poCTSrcToDst || poCTCutlineToSrc)
533
0
    {
534
0
        OGREnvelope sLastEnvelope, sCurEnvelope;
535
0
        std::unique_ptr<OGRGeometry> poTransformedGeom;
536
0
        auto poGeomInSrcSRS =
537
0
            std::unique_ptr<OGRGeometry>(poCutlineGeom->clone());
538
0
        if (poCTCutlineToSrc)
539
0
        {
540
0
            poGeomInSrcSRS.reset(OGRGeometryFactory::transformWithOptions(
541
0
                poGeomInSrcSRS.get(), poCTCutlineToSrc.get(), nullptr));
542
0
            if (!poGeomInSrcSRS)
543
0
                return CE_Failure;
544
0
        }
545
546
        // Do not use a smaller epsilon, otherwise it could cause useless
547
        // segmentization (https://github.com/OSGeo/gdal/issues/4826)
548
0
        constexpr double epsilon = 1e-10;
549
0
        for (int nIter = 0; nIter < 10; nIter++)
550
0
        {
551
0
            poTransformedGeom.reset(poGeomInSrcSRS->clone());
552
0
            if (poCTSrcToDst)
553
0
            {
554
0
                poTransformedGeom.reset(
555
0
                    OGRGeometryFactory::transformWithOptions(
556
0
                        poTransformedGeom.get(), poCTSrcToDst.get(), nullptr));
557
0
                if (!poTransformedGeom)
558
0
                    return CE_Failure;
559
0
            }
560
0
            poTransformedGeom->getEnvelope(&sCurEnvelope);
561
0
            if (nIter > 0 || !poCTSrcToDst)
562
0
            {
563
0
                if (std::abs(sCurEnvelope.MinX - sLastEnvelope.MinX) <=
564
0
                        epsilon *
565
0
                            std::abs(sCurEnvelope.MinX + sLastEnvelope.MinX) &&
566
0
                    std::abs(sCurEnvelope.MinY - sLastEnvelope.MinY) <=
567
0
                        epsilon *
568
0
                            std::abs(sCurEnvelope.MinY + sLastEnvelope.MinY) &&
569
0
                    std::abs(sCurEnvelope.MaxX - sLastEnvelope.MaxX) <=
570
0
                        epsilon *
571
0
                            std::abs(sCurEnvelope.MaxX + sLastEnvelope.MaxX) &&
572
0
                    std::abs(sCurEnvelope.MaxY - sLastEnvelope.MaxY) <=
573
0
                        epsilon *
574
0
                            std::abs(sCurEnvelope.MaxY + sLastEnvelope.MaxY))
575
0
                {
576
0
                    break;
577
0
                }
578
0
            }
579
0
            double dfAverageSegmentLength =
580
0
                GetAverageSegmentLength(poGeomInSrcSRS.get());
581
0
            poGeomInSrcSRS->segmentize(dfAverageSegmentLength / 4);
582
583
0
            sLastEnvelope = sCurEnvelope;
584
0
        }
585
586
0
        poCutlineGeom = std::move(poTransformedGeom);
587
0
    }
588
589
0
    OGREnvelope sEnvelope;
590
0
    poCutlineGeom->getEnvelope(&sEnvelope);
591
592
0
    dfMinX = sEnvelope.MinX;
593
0
    dfMinY = sEnvelope.MinY;
594
0
    dfMaxX = sEnvelope.MaxX;
595
0
    dfMaxY = sEnvelope.MaxY;
596
0
    if (!poCTSrcToDst && nSrcCount > 0 && psOptions->dfXRes == 0.0 &&
597
0
        psOptions->dfYRes == 0.0)
598
0
    {
599
        // No raster reprojection: stick on exact pixel boundaries of the source
600
        // to preserve resolution and avoid resampling
601
0
        double adfGT[6];
602
0
        if (GDALGetGeoTransform(pahSrcDS[0], adfGT) == CE_None)
603
0
        {
604
            // We allow for a relative error in coordinates up to 0.1% of the
605
            // pixel size for rounding purposes.
606
0
            constexpr double REL_EPS_PIXEL = 1e-3;
607
0
            if (CPLFetchBool(papszWarpOptions, "CUTLINE_ALL_TOUCHED", false))
608
0
            {
609
                // All touched ? Then make the extent a bit larger than the
610
                // cutline envelope
611
0
                dfMinX = adfGT[0] +
612
0
                         floor((dfMinX - adfGT[0]) / adfGT[1] + REL_EPS_PIXEL) *
613
0
                             adfGT[1];
614
0
                dfMinY = adfGT[3] +
615
0
                         ceil((dfMinY - adfGT[3]) / adfGT[5] - REL_EPS_PIXEL) *
616
0
                             adfGT[5];
617
0
                dfMaxX = adfGT[0] +
618
0
                         ceil((dfMaxX - adfGT[0]) / adfGT[1] - REL_EPS_PIXEL) *
619
0
                             adfGT[1];
620
0
                dfMaxY = adfGT[3] +
621
0
                         floor((dfMaxY - adfGT[3]) / adfGT[5] + REL_EPS_PIXEL) *
622
0
                             adfGT[5];
623
0
            }
624
0
            else
625
0
            {
626
                // Otherwise, make it a bit smaller
627
0
                dfMinX = adfGT[0] +
628
0
                         ceil((dfMinX - adfGT[0]) / adfGT[1] - REL_EPS_PIXEL) *
629
0
                             adfGT[1];
630
0
                dfMinY = adfGT[3] +
631
0
                         floor((dfMinY - adfGT[3]) / adfGT[5] + REL_EPS_PIXEL) *
632
0
                             adfGT[5];
633
0
                dfMaxX = adfGT[0] +
634
0
                         floor((dfMaxX - adfGT[0]) / adfGT[1] + REL_EPS_PIXEL) *
635
0
                             adfGT[1];
636
0
                dfMaxY = adfGT[3] +
637
0
                         ceil((dfMaxY - adfGT[3]) / adfGT[5] - REL_EPS_PIXEL) *
638
0
                             adfGT[5];
639
0
            }
640
0
        }
641
0
    }
642
643
0
    return CE_None;
644
0
}
645
646
static bool MustApplyVerticalShift(GDALDatasetH hWrkSrcDS,
647
                                   const GDALWarpAppOptions *psOptions,
648
                                   OGRSpatialReference &oSRSSrc,
649
                                   OGRSpatialReference &oSRSDst,
650
                                   bool &bSrcHasVertAxis, bool &bDstHasVertAxis)
651
0
{
652
0
    bool bApplyVShift = psOptions->bVShift;
653
654
    // Check if we must do vertical shift grid transform
655
0
    const char *pszSrcWKT =
656
0
        psOptions->aosTransformerOptions.FetchNameValue("SRC_SRS");
657
0
    if (pszSrcWKT)
658
0
        oSRSSrc.SetFromUserInput(pszSrcWKT);
659
0
    else
660
0
    {
661
0
        auto hSRS = GDALGetSpatialRef(hWrkSrcDS);
662
0
        if (hSRS)
663
0
            oSRSSrc = *(OGRSpatialReference::FromHandle(hSRS));
664
0
        else
665
0
            return false;
666
0
    }
667
668
0
    const char *pszDstWKT =
669
0
        psOptions->aosTransformerOptions.FetchNameValue("DST_SRS");
670
0
    if (pszDstWKT)
671
0
        oSRSDst.SetFromUserInput(pszDstWKT);
672
0
    else
673
0
        return false;
674
675
0
    if (oSRSSrc.IsSame(&oSRSDst))
676
0
        return false;
677
678
0
    bSrcHasVertAxis = oSRSSrc.IsCompound() ||
679
0
                      ((oSRSSrc.IsProjected() || oSRSSrc.IsGeographic()) &&
680
0
                       oSRSSrc.GetAxesCount() == 3);
681
682
0
    bDstHasVertAxis = oSRSDst.IsCompound() ||
683
0
                      ((oSRSDst.IsProjected() || oSRSDst.IsGeographic()) &&
684
0
                       oSRSDst.GetAxesCount() == 3);
685
686
0
    if ((GDALGetRasterCount(hWrkSrcDS) == 1 || psOptions->bVShift) &&
687
0
        (bSrcHasVertAxis || bDstHasVertAxis))
688
0
    {
689
0
        bApplyVShift = true;
690
0
    }
691
0
    return bApplyVShift;
692
0
}
693
694
/************************************************************************/
695
/*                         ApplyVerticalShift()                         */
696
/************************************************************************/
697
698
static bool ApplyVerticalShift(GDALDatasetH hWrkSrcDS,
699
                               const GDALWarpAppOptions *psOptions,
700
                               GDALWarpOptions *psWO)
701
0
{
702
0
    if (psOptions->bVShift)
703
0
    {
704
0
        psWO->papszWarpOptions = CSLSetNameValue(psWO->papszWarpOptions,
705
0
                                                 "APPLY_VERTICAL_SHIFT", "YES");
706
0
    }
707
708
0
    OGRSpatialReference oSRSSrc;
709
0
    OGRSpatialReference oSRSDst;
710
0
    bool bSrcHasVertAxis = false;
711
0
    bool bDstHasVertAxis = false;
712
0
    bool bApplyVShift =
713
0
        MustApplyVerticalShift(hWrkSrcDS, psOptions, oSRSSrc, oSRSDst,
714
0
                               bSrcHasVertAxis, bDstHasVertAxis);
715
716
0
    if ((GDALGetRasterCount(hWrkSrcDS) == 1 || psOptions->bVShift) &&
717
0
        (bSrcHasVertAxis || bDstHasVertAxis))
718
0
    {
719
0
        bApplyVShift = true;
720
0
        psWO->papszWarpOptions = CSLSetNameValue(psWO->papszWarpOptions,
721
0
                                                 "APPLY_VERTICAL_SHIFT", "YES");
722
723
0
        if (CSLFetchNameValue(psWO->papszWarpOptions,
724
0
                              "MULT_FACTOR_VERTICAL_SHIFT") == nullptr)
725
0
        {
726
            // Select how to go from input dataset units to meters
727
0
            double dfToMeterSrc = 1.0;
728
0
            const char *pszUnit =
729
0
                GDALGetRasterUnitType(GDALGetRasterBand(hWrkSrcDS, 1));
730
731
0
            double dfToMeterSrcAxis = 1.0;
732
0
            if (bSrcHasVertAxis)
733
0
            {
734
0
                oSRSSrc.GetAxis(nullptr, 2, nullptr, &dfToMeterSrcAxis);
735
0
            }
736
737
0
            if (pszUnit && (EQUAL(pszUnit, "m") || EQUAL(pszUnit, "meter") ||
738
0
                            EQUAL(pszUnit, "metre")))
739
0
            {
740
0
            }
741
0
            else if (pszUnit &&
742
0
                     (EQUAL(pszUnit, "ft") || EQUAL(pszUnit, "foot")))
743
0
            {
744
0
                dfToMeterSrc = CPLAtof(SRS_UL_FOOT_CONV);
745
0
            }
746
0
            else if (pszUnit && (EQUAL(pszUnit, "US survey foot")))
747
0
            {
748
0
                dfToMeterSrc = CPLAtof(SRS_UL_US_FOOT_CONV);
749
0
            }
750
0
            else if (pszUnit && !EQUAL(pszUnit, ""))
751
0
            {
752
0
                if (bSrcHasVertAxis)
753
0
                {
754
0
                    dfToMeterSrc = dfToMeterSrcAxis;
755
0
                }
756
0
                else
757
0
                {
758
0
                    CPLError(CE_Warning, CPLE_AppDefined,
759
0
                             "Unknown units=%s. Assuming metre.", pszUnit);
760
0
                }
761
0
            }
762
0
            else
763
0
            {
764
0
                if (bSrcHasVertAxis)
765
0
                    oSRSSrc.GetAxis(nullptr, 2, nullptr, &dfToMeterSrc);
766
0
            }
767
768
0
            double dfToMeterDst = 1.0;
769
0
            if (bDstHasVertAxis)
770
0
                oSRSDst.GetAxis(nullptr, 2, nullptr, &dfToMeterDst);
771
772
0
            if (dfToMeterSrc > 0 && dfToMeterDst > 0)
773
0
            {
774
0
                const double dfMultFactorVerticalShift =
775
0
                    dfToMeterSrc / dfToMeterDst;
776
0
                CPLDebug("WARP", "Applying MULT_FACTOR_VERTICAL_SHIFT=%.18g",
777
0
                         dfMultFactorVerticalShift);
778
0
                psWO->papszWarpOptions = CSLSetNameValue(
779
0
                    psWO->papszWarpOptions, "MULT_FACTOR_VERTICAL_SHIFT",
780
0
                    CPLSPrintf("%.18g", dfMultFactorVerticalShift));
781
782
0
                const double dfMultFactorVerticalShiftPipeline =
783
0
                    dfToMeterSrcAxis / dfToMeterDst;
784
0
                CPLDebug("WARP",
785
0
                         "Applying MULT_FACTOR_VERTICAL_SHIFT_PIPELINE=%.18g",
786
0
                         dfMultFactorVerticalShiftPipeline);
787
0
                psWO->papszWarpOptions = CSLSetNameValue(
788
0
                    psWO->papszWarpOptions,
789
0
                    "MULT_FACTOR_VERTICAL_SHIFT_PIPELINE",
790
0
                    CPLSPrintf("%.18g", dfMultFactorVerticalShiftPipeline));
791
0
            }
792
0
        }
793
0
    }
794
795
0
    return bApplyVShift;
796
0
}
797
798
/************************************************************************/
799
/*                           CanUseBuildVRT()                           */
800
/************************************************************************/
801
802
static bool CanUseBuildVRT(int nSrcCount, GDALDatasetH *pahSrcDS)
803
0
{
804
805
0
    bool bCanUseBuildVRT = true;
806
0
    std::vector<std::array<double, 4>> aoExtents;
807
0
    bool bSrcHasAlpha = false;
808
0
    int nPrevBandCount = 0;
809
0
    OGRSpatialReference oSRSPrev;
810
0
    double dfLastResX = 0;
811
0
    double dfLastResY = 0;
812
0
    for (int i = 0; i < nSrcCount; i++)
813
0
    {
814
0
        double adfGT[6];
815
0
        auto hSrcDS = pahSrcDS[i];
816
0
        if (EQUAL(GDALGetDescription(hSrcDS), ""))
817
0
        {
818
0
            bCanUseBuildVRT = false;
819
0
            break;
820
0
        }
821
0
        if (GDALGetGeoTransform(hSrcDS, adfGT) != CE_None || adfGT[2] != 0 ||
822
0
            adfGT[4] != 0 || adfGT[5] > 0)
823
0
        {
824
0
            bCanUseBuildVRT = false;
825
0
            break;
826
0
        }
827
0
        const double dfMinX = adfGT[0];
828
0
        const double dfMinY = adfGT[3] + GDALGetRasterYSize(hSrcDS) * adfGT[5];
829
0
        const double dfMaxX = adfGT[0] + GDALGetRasterXSize(hSrcDS) * adfGT[1];
830
0
        const double dfMaxY = adfGT[3];
831
0
        const int nBands = GDALGetRasterCount(hSrcDS);
832
0
        if (nBands > 1 && GDALGetRasterColorInterpretation(GDALGetRasterBand(
833
0
                              hSrcDS, nBands)) == GCI_AlphaBand)
834
0
        {
835
0
            bSrcHasAlpha = true;
836
0
        }
837
0
        aoExtents.emplace_back(
838
0
            std::array<double, 4>{{dfMinX, dfMinY, dfMaxX, dfMaxY}});
839
0
        const auto poSRS = GDALDataset::FromHandle(hSrcDS)->GetSpatialRef();
840
0
        if (i == 0)
841
0
        {
842
0
            nPrevBandCount = nBands;
843
0
            if (poSRS)
844
0
                oSRSPrev = *poSRS;
845
0
            dfLastResX = adfGT[1];
846
0
            dfLastResY = adfGT[5];
847
0
        }
848
0
        else
849
0
        {
850
0
            if (nPrevBandCount != nBands)
851
0
            {
852
0
                bCanUseBuildVRT = false;
853
0
                break;
854
0
            }
855
0
            if (poSRS == nullptr && !oSRSPrev.IsEmpty())
856
0
            {
857
0
                bCanUseBuildVRT = false;
858
0
                break;
859
0
            }
860
0
            if (poSRS != nullptr &&
861
0
                (oSRSPrev.IsEmpty() || !poSRS->IsSame(&oSRSPrev)))
862
0
            {
863
0
                bCanUseBuildVRT = false;
864
0
                break;
865
0
            }
866
0
            if (dfLastResX != adfGT[1] || dfLastResY != adfGT[5])
867
0
            {
868
0
                bCanUseBuildVRT = false;
869
0
                break;
870
0
            }
871
0
        }
872
0
    }
873
0
    if (bSrcHasAlpha && bCanUseBuildVRT)
874
0
    {
875
        // Quadratic performance loop. If that happens to be an issue,
876
        // we might need to build a quad tree
877
0
        for (size_t i = 0; i < aoExtents.size(); i++)
878
0
        {
879
0
            const double dfMinX = aoExtents[i][0];
880
0
            const double dfMinY = aoExtents[i][1];
881
0
            const double dfMaxX = aoExtents[i][2];
882
0
            const double dfMaxY = aoExtents[i][3];
883
0
            for (size_t j = i + 1; j < aoExtents.size(); j++)
884
0
            {
885
0
                const double dfOtherMinX = aoExtents[j][0];
886
0
                const double dfOtherMinY = aoExtents[j][1];
887
0
                const double dfOtherMaxX = aoExtents[j][2];
888
0
                const double dfOtherMaxY = aoExtents[j][3];
889
0
                if (dfMinX < dfOtherMaxX && dfOtherMinX < dfMaxX &&
890
0
                    dfMinY < dfOtherMaxY && dfOtherMinY < dfMaxY)
891
0
                {
892
0
                    bCanUseBuildVRT = false;
893
0
                    break;
894
0
                }
895
0
            }
896
0
            if (!bCanUseBuildVRT)
897
0
                break;
898
0
        }
899
0
    }
900
0
    return bCanUseBuildVRT;
901
0
}
902
903
#ifdef HAVE_TIFF
904
905
/************************************************************************/
906
/*                         DealWithCOGOptions()                         */
907
/************************************************************************/
908
909
static bool DealWithCOGOptions(CPLStringList &aosCreateOptions, int nSrcCount,
910
                               GDALDatasetH *pahSrcDS,
911
                               GDALWarpAppOptions *psOptions,
912
                               GDALTransformerArgUniquePtr &hUniqueTransformArg)
913
0
{
914
0
    const auto SetDstSRS = [psOptions](const std::string &osTargetSRS)
915
0
    {
916
0
        const char *pszExistingDstSRS =
917
0
            psOptions->aosTransformerOptions.FetchNameValue("DST_SRS");
918
0
        if (pszExistingDstSRS)
919
0
        {
920
0
            OGRSpatialReference oSRS1;
921
0
            oSRS1.SetFromUserInput(pszExistingDstSRS);
922
0
            OGRSpatialReference oSRS2;
923
0
            oSRS2.SetFromUserInput(osTargetSRS.c_str());
924
0
            if (!oSRS1.IsSame(&oSRS2))
925
0
            {
926
0
                const char *pszOutputArg = psOptions->bInvokedFromGdalAlgorithm
927
0
                                               ? "--output-crs"
928
0
                                               : "-t_srs";
929
930
0
                CPLError(CE_Failure, CPLE_AppDefined,
931
0
                         "Target SRS implied by COG creation options is not "
932
0
                         "the same as the one specified by %s",
933
0
                         pszOutputArg);
934
0
                return false;
935
0
            }
936
0
        }
937
0
        psOptions->aosTransformerOptions.SetNameValue("DST_SRS",
938
0
                                                      osTargetSRS.c_str());
939
0
        return true;
940
0
    };
941
942
0
    CPLString osTargetSRS;
943
0
    if (COGGetTargetSRS(aosCreateOptions.List(), osTargetSRS))
944
0
    {
945
0
        if (!SetDstSRS(osTargetSRS))
946
0
            return false;
947
0
    }
948
949
0
    if (!(psOptions->dfMinX == 0 && psOptions->dfMinY == 0 &&
950
0
          psOptions->dfMaxX == 0 && psOptions->dfMaxY == 0 &&
951
0
          psOptions->dfXRes == 0 && psOptions->dfYRes == 0 &&
952
0
          psOptions->nForcePixels == 0 && psOptions->nForceLines == 0))
953
0
    {
954
0
        if (!psOptions->bResampleAlgSpecifiedByUser && nSrcCount > 0)
955
0
        {
956
0
            try
957
0
            {
958
0
                GDALGetWarpResampleAlg(
959
0
                    COGGetResampling(GDALDataset::FromHandle(pahSrcDS[0]),
960
0
                                     aosCreateOptions.List())
961
0
                        .c_str(),
962
0
                    psOptions->eResampleAlg);
963
0
            }
964
0
            catch (const std::invalid_argument &)
965
0
            {
966
                // Cannot happen actually. Coverity Scan false positive...
967
0
                CPLAssert(false);
968
0
            }
969
0
        }
970
0
        return true;
971
0
    }
972
973
0
    GDALWarpAppOptions oClonedOptions(*psOptions);
974
0
    oClonedOptions.bQuiet = true;
975
0
    const CPLString osTmpFilename(
976
0
        VSIMemGenerateHiddenFilename("gdalwarp_tmp.tif"));
977
0
    CPLStringList aosTmpGTiffCreateOptions;
978
0
    aosTmpGTiffCreateOptions.SetNameValue("SPARSE_OK", "YES");
979
0
    aosTmpGTiffCreateOptions.SetNameValue("TILED", "YES");
980
0
    aosTmpGTiffCreateOptions.SetNameValue("BLOCKXSIZE", "4096");
981
0
    aosTmpGTiffCreateOptions.SetNameValue("BLOCKYSIZE", "4096");
982
0
    auto hTmpDS = GDALWarpCreateOutput(
983
0
        nSrcCount, pahSrcDS, osTmpFilename, "GTiff",
984
0
        oClonedOptions.aosTransformerOptions.List(),
985
0
        aosTmpGTiffCreateOptions.List(), oClonedOptions.eOutputType,
986
0
        hUniqueTransformArg, false, &oClonedOptions,
987
0
        /* bUpdateTransformerWithDestGT = */ false);
988
0
    if (hTmpDS == nullptr)
989
0
    {
990
0
        return false;
991
0
    }
992
993
0
    CPLString osResampling;
994
0
    int nXSize = 0;
995
0
    int nYSize = 0;
996
0
    double dfMinX = 0;
997
0
    double dfMinY = 0;
998
0
    double dfMaxX = 0;
999
0
    double dfMaxY = 0;
1000
0
    bool bRet = true;
1001
0
    if (COGGetWarpingCharacteristics(GDALDataset::FromHandle(hTmpDS),
1002
0
                                     aosCreateOptions.List(), osResampling,
1003
0
                                     osTargetSRS, nXSize, nYSize, dfMinX,
1004
0
                                     dfMinY, dfMaxX, dfMaxY))
1005
0
    {
1006
0
        if (!psOptions->bResampleAlgSpecifiedByUser)
1007
0
        {
1008
0
            try
1009
0
            {
1010
0
                GDALGetWarpResampleAlg(osResampling, psOptions->eResampleAlg);
1011
0
            }
1012
0
            catch (const std::invalid_argument &)
1013
0
            {
1014
                // Cannot happen actually. Coverity Scan false positive...
1015
0
                CPLAssert(false);
1016
0
            }
1017
0
        }
1018
1019
0
        psOptions->dfMinX = dfMinX;
1020
0
        psOptions->dfMinY = dfMinY;
1021
0
        psOptions->dfMaxX = dfMaxX;
1022
0
        psOptions->dfMaxY = dfMaxY;
1023
0
        psOptions->nForcePixels = nXSize;
1024
0
        psOptions->nForceLines = nYSize;
1025
0
        COGRemoveWarpingOptions(aosCreateOptions);
1026
0
    }
1027
0
    GDALClose(hTmpDS);
1028
0
    VSIUnlink(osTmpFilename);
1029
0
    return bRet;
1030
0
}
1031
1032
#endif
1033
1034
/************************************************************************/
1035
/*                          GDALWarpIndirect()                          */
1036
/************************************************************************/
1037
1038
static GDALDatasetH
1039
GDALWarpDirect(const char *pszDest, GDALDatasetH hDstDS, int nSrcCount,
1040
               GDALDatasetH *pahSrcDS,
1041
               GDALTransformerArgUniquePtr hUniqueTransformArg,
1042
               GDALWarpAppOptions *psOptions, int *pbUsageError);
1043
1044
static int CPL_STDCALL myScaledProgress(double dfProgress, const char *,
1045
                                        void *pProgressData)
1046
0
{
1047
0
    return GDALScaledProgress(dfProgress, nullptr, pProgressData);
1048
0
}
1049
1050
static GDALDatasetH GDALWarpIndirect(const char *pszDest, GDALDriverH hDriver,
1051
                                     int nSrcCount, GDALDatasetH *pahSrcDS,
1052
                                     GDALWarpAppOptions *psOptions,
1053
                                     int *pbUsageError)
1054
0
{
1055
0
    CPLStringList aosCreateOptions(psOptions->aosCreateOptions);
1056
0
    psOptions->aosCreateOptions.Clear();
1057
1058
    // Do not use a warped VRT input for COG output, because that would cause
1059
    // warping to be done both during overview computation and creation of
1060
    // full resolution image. Better materialize a temporary GTiff a bit later
1061
    // in that method.
1062
0
    if (nSrcCount == 1 && !EQUAL(psOptions->osFormat.c_str(), "COG"))
1063
0
    {
1064
0
        psOptions->osFormat = "VRT";
1065
0
        auto pfnProgress = psOptions->pfnProgress;
1066
0
        psOptions->pfnProgress = GDALDummyProgress;
1067
0
        auto pProgressData = psOptions->pProgressData;
1068
0
        psOptions->pProgressData = nullptr;
1069
1070
0
        auto hTmpDS = GDALWarpDirect("", nullptr, nSrcCount, pahSrcDS, nullptr,
1071
0
                                     psOptions, pbUsageError);
1072
0
        if (hTmpDS)
1073
0
        {
1074
0
            auto hRet = GDALCreateCopy(hDriver, pszDest, hTmpDS, FALSE,
1075
0
                                       aosCreateOptions.List(), pfnProgress,
1076
0
                                       pProgressData);
1077
0
            GDALClose(hTmpDS);
1078
0
            return hRet;
1079
0
        }
1080
0
        return nullptr;
1081
0
    }
1082
1083
    // Detect a pure mosaicing situation where a BuildVRT approach is
1084
    // sufficient.
1085
0
    GDALDatasetH hTmpDS = nullptr;
1086
0
    if (psOptions->aosTransformerOptions.empty() &&
1087
0
        psOptions->eOutputType == GDT_Unknown && psOptions->dfMinX == 0 &&
1088
0
        psOptions->dfMinY == 0 && psOptions->dfMaxX == 0 &&
1089
0
        psOptions->dfMaxY == 0 && psOptions->dfXRes == 0 &&
1090
0
        psOptions->dfYRes == 0 && psOptions->nForcePixels == 0 &&
1091
0
        psOptions->nForceLines == 0 &&
1092
0
        psOptions->osCutlineDSNameOrWKT.empty() &&
1093
0
        CanUseBuildVRT(nSrcCount, pahSrcDS))
1094
0
    {
1095
0
        CPLStringList aosArgv;
1096
0
        const int nBands = GDALGetRasterCount(pahSrcDS[0]);
1097
0
        if ((nBands == 1 ||
1098
0
             (nBands > 1 && GDALGetRasterColorInterpretation(GDALGetRasterBand(
1099
0
                                pahSrcDS[0], nBands)) != GCI_AlphaBand)) &&
1100
0
            (psOptions->bEnableDstAlpha
1101
0
#ifdef HAVE_TIFF
1102
0
             || (EQUAL(psOptions->osFormat.c_str(), "COG") &&
1103
0
                 COGHasWarpingOptions(aosCreateOptions.List()) &&
1104
0
                 CPLTestBool(
1105
0
                     aosCreateOptions.FetchNameValueDef("ADD_ALPHA", "YES")))
1106
0
#endif
1107
0
                 ))
1108
0
        {
1109
0
            aosArgv.AddString("-addalpha");
1110
0
        }
1111
0
        auto psBuildVRTOptions =
1112
0
            GDALBuildVRTOptionsNew(aosArgv.List(), nullptr);
1113
0
        hTmpDS = GDALBuildVRT("", nSrcCount, pahSrcDS, nullptr,
1114
0
                              psBuildVRTOptions, nullptr);
1115
0
        GDALBuildVRTOptionsFree(psBuildVRTOptions);
1116
0
    }
1117
0
    auto pfnProgress = psOptions->pfnProgress;
1118
0
    auto pProgressData = psOptions->pProgressData;
1119
0
    CPLString osTmpFilename;
1120
0
    double dfStartPctCreateCopy = 0.0;
1121
0
    if (hTmpDS == nullptr)
1122
0
    {
1123
0
        GDALTransformerArgUniquePtr hUniqueTransformArg;
1124
0
#ifdef HAVE_TIFF
1125
        // Special processing for COG output. As some of its options do
1126
        // on-the-fly reprojection, take them into account now, and remove them
1127
        // from the COG creation stage.
1128
0
        if (EQUAL(psOptions->osFormat.c_str(), "COG") &&
1129
0
            !DealWithCOGOptions(aosCreateOptions, nSrcCount, pahSrcDS,
1130
0
                                psOptions, hUniqueTransformArg))
1131
0
        {
1132
0
            return nullptr;
1133
0
        }
1134
0
#endif
1135
1136
        // Materialize a temporary GeoTIFF with the result of the warp
1137
0
        psOptions->osFormat = "GTiff";
1138
0
        psOptions->aosCreateOptions.AddString("SPARSE_OK=YES");
1139
0
        psOptions->aosCreateOptions.AddString("COMPRESS=LZW");
1140
0
        psOptions->aosCreateOptions.AddString("TILED=YES");
1141
0
        psOptions->aosCreateOptions.AddString("BIGTIFF=YES");
1142
0
        psOptions->pfnProgress = myScaledProgress;
1143
0
        dfStartPctCreateCopy = 2. / 3;
1144
0
        psOptions->pProgressData = GDALCreateScaledProgress(
1145
0
            0, dfStartPctCreateCopy, pfnProgress, pProgressData);
1146
0
        psOptions->bDeleteOutputFileOnceCreated = true;
1147
0
        osTmpFilename = CPLGenerateTempFilenameSafe(CPLGetFilename(pszDest));
1148
0
        hTmpDS = GDALWarpDirect(osTmpFilename, nullptr, nSrcCount, pahSrcDS,
1149
0
                                std::move(hUniqueTransformArg), psOptions,
1150
0
                                pbUsageError);
1151
0
        GDALDestroyScaledProgress(psOptions->pProgressData);
1152
0
        psOptions->pfnProgress = nullptr;
1153
0
        psOptions->pProgressData = nullptr;
1154
0
    }
1155
0
    if (hTmpDS)
1156
0
    {
1157
0
        auto pScaledProgressData = GDALCreateScaledProgress(
1158
0
            dfStartPctCreateCopy, 1.0, pfnProgress, pProgressData);
1159
0
        auto hRet = GDALCreateCopy(hDriver, pszDest, hTmpDS, FALSE,
1160
0
                                   aosCreateOptions.List(), myScaledProgress,
1161
0
                                   pScaledProgressData);
1162
0
        GDALDestroyScaledProgress(pScaledProgressData);
1163
0
        GDALClose(hTmpDS);
1164
0
        VSIStatBufL sStat;
1165
0
        if (!osTmpFilename.empty() &&
1166
0
            VSIStatL(osTmpFilename.c_str(), &sStat) == 0)
1167
0
        {
1168
0
            GDALDeleteDataset(GDALGetDriverByName("GTiff"), osTmpFilename);
1169
0
        }
1170
0
        return hRet;
1171
0
    }
1172
0
    return nullptr;
1173
0
}
1174
1175
/************************************************************************/
1176
/*                              GDALWarp()                              */
1177
/************************************************************************/
1178
1179
/**
1180
 * Image reprojection and warping function.
1181
 *
1182
 * This is the equivalent of the <a href="/programs/gdalwarp.html">gdalwarp</a>
1183
 * utility.
1184
 *
1185
 * GDALWarpAppOptions* must be allocated and freed with GDALWarpAppOptionsNew()
1186
 * and GDALWarpAppOptionsFree() respectively.
1187
 * pszDest and hDstDS cannot be used at the same time.
1188
 *
1189
 * @param pszDest the destination dataset path or NULL.
1190
 * @param hDstDS the destination dataset or NULL.
1191
 * @param nSrcCount the number of input datasets.
1192
 * @param pahSrcDS the list of input datasets. For practical purposes, the type
1193
 * of this argument should be considered as "const GDALDatasetH* const*", that
1194
 * is neither the array nor its values are mutated by this function.
1195
 * @param psOptionsIn the options struct returned by GDALWarpAppOptionsNew() or
1196
 * NULL.
1197
 * @param pbUsageError pointer to a integer output variable to store if any
1198
 * usage error has occurred, or NULL.
1199
 * @return the output dataset (new dataset that must be closed using
1200
 * GDALClose(), or hDstDS if not NULL) or NULL in case of error. If the output
1201
 * format is a VRT dataset, then the returned VRT dataset has a reference to
1202
 * pahSrcDS[0]. Hence pahSrcDS[0] should be closed after the returned dataset
1203
 * if using GDALClose().
1204
 * A safer alternative is to use GDALReleaseDataset() instead of using
1205
 * GDALClose(), in which case you can close datasets in any order.
1206
 *
1207
 * @since GDAL 2.1
1208
 */
1209
1210
GDALDatasetH GDALWarp(const char *pszDest, GDALDatasetH hDstDS, int nSrcCount,
1211
                      GDALDatasetH *pahSrcDS,
1212
                      const GDALWarpAppOptions *psOptionsIn, int *pbUsageError)
1213
0
{
1214
0
    CPLErrorReset();
1215
1216
0
    for (int i = 0; i < nSrcCount; i++)
1217
0
    {
1218
0
        if (!pahSrcDS[i])
1219
0
            return nullptr;
1220
0
    }
1221
1222
0
    GDALWarpAppOptions oOptionsTmp;
1223
0
    if (psOptionsIn)
1224
0
        oOptionsTmp = *psOptionsIn;
1225
0
    GDALWarpAppOptions *psOptions = &oOptionsTmp;
1226
1227
0
    if (hDstDS == nullptr)
1228
0
    {
1229
0
        if (psOptions->osFormat.empty())
1230
0
        {
1231
0
            psOptions->osFormat = GetOutputDriverForRaster(pszDest);
1232
0
            if (psOptions->osFormat.empty())
1233
0
            {
1234
0
                return nullptr;
1235
0
            }
1236
0
        }
1237
1238
0
        auto hDriver = GDALGetDriverByName(psOptions->osFormat.c_str());
1239
0
        if (hDriver != nullptr &&
1240
0
            (EQUAL(psOptions->osFormat.c_str(), "COG") ||
1241
0
             (GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATE, nullptr) ==
1242
0
                  nullptr &&
1243
0
              GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATECOPY, nullptr) !=
1244
0
                  nullptr)))
1245
0
        {
1246
0
            auto ret = GDALWarpIndirect(pszDest, hDriver, nSrcCount, pahSrcDS,
1247
0
                                        psOptions, pbUsageError);
1248
0
            return ret;
1249
0
        }
1250
0
    }
1251
1252
0
    auto ret = GDALWarpDirect(pszDest, hDstDS, nSrcCount, pahSrcDS, nullptr,
1253
0
                              psOptions, pbUsageError);
1254
1255
0
    return ret;
1256
0
}
1257
1258
/************************************************************************/
1259
/*                    UseTEAndTSAndTRConsistently()                     */
1260
/************************************************************************/
1261
1262
static bool UseTEAndTSAndTRConsistently(const GDALWarpAppOptions *psOptions)
1263
0
{
1264
    // We normally don't allow -te, -ts and -tr together, unless they are all
1265
    // consistent. The interest of this is to use the -tr values to produce
1266
    // exact pixel size, rather than inferring it from -te and -ts
1267
1268
    // Constant and logic to be kept in sync with cogdriver.cpp
1269
0
    constexpr double RELATIVE_ERROR_RES_SHARED_BY_COG_AND_GDALWARP = 1e-8;
1270
0
    return psOptions->nForcePixels != 0 && psOptions->nForceLines != 0 &&
1271
0
           psOptions->dfXRes != 0 && psOptions->dfYRes != 0 &&
1272
0
           !(psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
1273
0
             psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0) &&
1274
0
           fabs((psOptions->dfMaxX - psOptions->dfMinX) / psOptions->dfXRes -
1275
0
                psOptions->nForcePixels) <=
1276
0
               RELATIVE_ERROR_RES_SHARED_BY_COG_AND_GDALWARP &&
1277
0
           fabs((psOptions->dfMaxY - psOptions->dfMinY) / psOptions->dfYRes -
1278
0
                psOptions->nForceLines) <=
1279
0
               RELATIVE_ERROR_RES_SHARED_BY_COG_AND_GDALWARP;
1280
0
}
1281
1282
/************************************************************************/
1283
/*                            CheckOptions()                            */
1284
/************************************************************************/
1285
1286
static bool CheckOptions(const char *pszDest, GDALDatasetH hDstDS,
1287
                         int nSrcCount, GDALDatasetH *pahSrcDS,
1288
                         GDALWarpAppOptions *psOptions, bool &bVRT,
1289
                         int *pbUsageError)
1290
0
{
1291
1292
0
    if (hDstDS)
1293
0
    {
1294
0
        if (psOptions->bCreateOutput == true)
1295
0
        {
1296
0
            CPLError(CE_Warning, CPLE_AppDefined,
1297
0
                     "All options related to creation ignored in update mode");
1298
0
            psOptions->bCreateOutput = false;
1299
0
        }
1300
0
    }
1301
1302
0
    if ((psOptions->osFormat.empty() &&
1303
0
         EQUAL(CPLGetExtensionSafe(pszDest).c_str(), "VRT")) ||
1304
0
        (EQUAL(psOptions->osFormat.c_str(), "VRT")))
1305
0
    {
1306
0
        if (hDstDS != nullptr)
1307
0
        {
1308
0
            CPLError(CE_Warning, CPLE_NotSupported,
1309
0
                     "VRT output not compatible with existing dataset.");
1310
0
            return false;
1311
0
        }
1312
1313
0
        bVRT = true;
1314
1315
0
        if (nSrcCount > 1)
1316
0
        {
1317
0
            CPLError(CE_Warning, CPLE_AppDefined,
1318
0
                     "gdalwarp -of VRT just takes into account "
1319
0
                     "the first source dataset.\nIf all source datasets "
1320
0
                     "are in the same projection, try making a mosaic of\n"
1321
0
                     "them with gdalbuildvrt, and use the resulting "
1322
0
                     "VRT file as the input of\ngdalwarp -of VRT.");
1323
0
        }
1324
0
    }
1325
1326
    /* -------------------------------------------------------------------- */
1327
    /*      Check that incompatible options are not used                    */
1328
    /* -------------------------------------------------------------------- */
1329
1330
0
    if ((psOptions->nForcePixels != 0 || psOptions->nForceLines != 0) &&
1331
0
        (psOptions->dfXRes != 0 && psOptions->dfYRes != 0) &&
1332
0
        !UseTEAndTSAndTRConsistently(psOptions))
1333
0
    {
1334
0
        CPLError(CE_Failure, CPLE_IllegalArg,
1335
0
                 "-tr and -ts options cannot be used at the same time.");
1336
0
        if (pbUsageError)
1337
0
            *pbUsageError = TRUE;
1338
0
        return false;
1339
0
    }
1340
1341
0
    if (psOptions->bTargetAlignedPixels && psOptions->dfXRes == 0 &&
1342
0
        psOptions->dfYRes == 0)
1343
0
    {
1344
0
        CPLError(CE_Failure, CPLE_IllegalArg,
1345
0
                 "-tap option cannot be used without using -tr.");
1346
0
        if (pbUsageError)
1347
0
            *pbUsageError = TRUE;
1348
0
        return false;
1349
0
    }
1350
1351
0
    if (!psOptions->bQuiet &&
1352
0
        !(psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
1353
0
          psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0))
1354
0
    {
1355
0
        if (psOptions->dfMinX >= psOptions->dfMaxX)
1356
0
            CPLError(CE_Warning, CPLE_AppDefined,
1357
0
                     "-te values have minx >= maxx. This will result in a "
1358
0
                     "horizontally flipped image.");
1359
0
        if (psOptions->dfMinY >= psOptions->dfMaxY)
1360
0
            CPLError(CE_Warning, CPLE_AppDefined,
1361
0
                     "-te values have miny >= maxy. This will result in a "
1362
0
                     "vertically flipped image.");
1363
0
    }
1364
1365
0
    if (psOptions->dfErrorThreshold < 0)
1366
0
    {
1367
        // By default, use approximate transformer unless RPC_DEM is specified
1368
0
        if (psOptions->aosTransformerOptions.FetchNameValue("RPC_DEM") !=
1369
0
            nullptr)
1370
0
            psOptions->dfErrorThreshold = 0.0;
1371
0
        else
1372
0
            psOptions->dfErrorThreshold = 0.125;
1373
0
    }
1374
1375
    /* -------------------------------------------------------------------- */
1376
    /*      -te_srs option                                                  */
1377
    /* -------------------------------------------------------------------- */
1378
0
    if (!psOptions->osTE_SRS.empty())
1379
0
    {
1380
0
        if (psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
1381
0
            psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0)
1382
0
        {
1383
0
            CPLError(CE_Warning, CPLE_AppDefined,
1384
0
                     "-te_srs ignored since -te is not specified.");
1385
0
        }
1386
0
        else
1387
0
        {
1388
0
            OGRSpatialReference oSRSIn;
1389
0
            oSRSIn.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1390
0
            oSRSIn.SetFromUserInput(psOptions->osTE_SRS.c_str());
1391
0
            OGRSpatialReference oSRSDS;
1392
0
            oSRSDS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1393
0
            bool bOK = false;
1394
0
            if (psOptions->aosTransformerOptions.FetchNameValue("DST_SRS") !=
1395
0
                nullptr)
1396
0
            {
1397
0
                oSRSDS.SetFromUserInput(
1398
0
                    psOptions->aosTransformerOptions.FetchNameValue("DST_SRS"));
1399
0
                bOK = true;
1400
0
            }
1401
0
            else if (psOptions->aosTransformerOptions.FetchNameValue(
1402
0
                         "SRC_SRS") != nullptr)
1403
0
            {
1404
0
                oSRSDS.SetFromUserInput(
1405
0
                    psOptions->aosTransformerOptions.FetchNameValue("SRC_SRS"));
1406
0
                bOK = true;
1407
0
            }
1408
0
            else if (nSrcCount)
1409
0
            {
1410
0
                const auto poSrcSRS =
1411
0
                    GDALDataset::FromHandle(pahSrcDS[0])->GetSpatialRef();
1412
0
                if (poSrcSRS)
1413
0
                {
1414
0
                    oSRSDS = *poSrcSRS;
1415
0
                    bOK = true;
1416
0
                }
1417
0
            }
1418
0
            if (!bOK)
1419
0
            {
1420
0
                CPLError(CE_Failure, CPLE_AppDefined,
1421
0
                         "-te_srs ignored since none of -t_srs, -s_srs is "
1422
0
                         "specified or the input dataset has no projection.");
1423
0
                return false;
1424
0
            }
1425
0
            if (!oSRSIn.IsSame(&oSRSDS))
1426
0
            {
1427
0
                double dfWestLongitudeDeg = 0.0;
1428
0
                double dfSouthLatitudeDeg = 0.0;
1429
0
                double dfEastLongitudeDeg = 0.0;
1430
0
                double dfNorthLatitudeDeg = 0.0;
1431
1432
0
                OGRCoordinateTransformationOptions options;
1433
0
                if (GDALComputeAreaOfInterest(
1434
0
                        &oSRSIn, psOptions->dfMinX, psOptions->dfMinY,
1435
0
                        psOptions->dfMaxX, psOptions->dfMaxY,
1436
0
                        dfWestLongitudeDeg, dfSouthLatitudeDeg,
1437
0
                        dfEastLongitudeDeg, dfNorthLatitudeDeg))
1438
0
                {
1439
0
                    options.SetAreaOfInterest(
1440
0
                        dfWestLongitudeDeg, dfSouthLatitudeDeg,
1441
0
                        dfEastLongitudeDeg, dfNorthLatitudeDeg);
1442
0
                }
1443
0
                auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
1444
0
                    OGRCreateCoordinateTransformation(&oSRSIn, &oSRSDS,
1445
0
                                                      options));
1446
0
                constexpr int DENSIFY_PTS = 21;
1447
0
                if (!(poCT && poCT->TransformBounds(
1448
0
                                  psOptions->dfMinX, psOptions->dfMinY,
1449
0
                                  psOptions->dfMaxX, psOptions->dfMaxY,
1450
0
                                  &(psOptions->dfMinX), &(psOptions->dfMinY),
1451
0
                                  &(psOptions->dfMaxX), &(psOptions->dfMaxY),
1452
0
                                  DENSIFY_PTS)))
1453
0
                {
1454
0
                    CPLError(CE_Failure, CPLE_AppDefined,
1455
0
                             "-te_srs ignored since coordinate transformation "
1456
0
                             "failed.");
1457
0
                    return false;
1458
0
                }
1459
0
            }
1460
0
        }
1461
0
    }
1462
0
    return true;
1463
0
}
1464
1465
/************************************************************************/
1466
/*                       ProcessCutlineOptions()                        */
1467
/************************************************************************/
1468
1469
static bool ProcessCutlineOptions(int nSrcCount, GDALDatasetH *pahSrcDS,
1470
                                  GDALWarpAppOptions *psOptions,
1471
                                  std::unique_ptr<OGRGeometry> &poCutline)
1472
0
{
1473
0
    if (!psOptions->osCutlineDSNameOrWKT.empty())
1474
0
    {
1475
0
        CPLErr eError;
1476
0
        OGRGeometryH hCutline = nullptr;
1477
0
        eError = LoadCutline(psOptions->osCutlineDSNameOrWKT,
1478
0
                             psOptions->osCutlineSRS, psOptions->osCLayer,
1479
0
                             psOptions->osCWHERE, psOptions->osCSQL, &hCutline);
1480
0
        poCutline.reset(OGRGeometry::FromHandle(hCutline));
1481
0
        if (eError == CE_Failure)
1482
0
        {
1483
0
            return false;
1484
0
        }
1485
0
    }
1486
1487
0
    if (psOptions->bCropToCutline && poCutline)
1488
0
    {
1489
0
        CPLErr eError;
1490
0
        eError = CropToCutline(poCutline.get(),
1491
0
                               psOptions->aosTransformerOptions.List(),
1492
0
                               psOptions->aosWarpOptions.List(), nSrcCount,
1493
0
                               pahSrcDS, psOptions->dfMinX, psOptions->dfMinY,
1494
0
                               psOptions->dfMaxX, psOptions->dfMaxY, psOptions);
1495
0
        if (eError == CE_Failure)
1496
0
        {
1497
0
            return false;
1498
0
        }
1499
0
    }
1500
1501
0
    const char *pszWarpThreads =
1502
0
        psOptions->aosWarpOptions.FetchNameValue("NUM_THREADS");
1503
0
    if (pszWarpThreads != nullptr)
1504
0
    {
1505
        /* Used by TPS transformer to parallelize direct and inverse matrix
1506
         * computation */
1507
0
        psOptions->aosTransformerOptions.SetNameValue("NUM_THREADS",
1508
0
                                                      pszWarpThreads);
1509
0
    }
1510
1511
0
    return true;
1512
0
}
1513
1514
/************************************************************************/
1515
/*                            CreateOutput()                            */
1516
/************************************************************************/
1517
1518
static GDALDatasetH
1519
CreateOutput(const char *pszDest, int nSrcCount, GDALDatasetH *pahSrcDS,
1520
             GDALWarpAppOptions *psOptions, const bool bInitDestSetByUser,
1521
             GDALTransformerArgUniquePtr &hUniqueTransformArg)
1522
0
{
1523
0
    if (nSrcCount == 1 && !psOptions->bDisableSrcAlpha)
1524
0
    {
1525
0
        if (GDALGetRasterCount(pahSrcDS[0]) > 0 &&
1526
0
            GDALGetRasterColorInterpretation(GDALGetRasterBand(
1527
0
                pahSrcDS[0], GDALGetRasterCount(pahSrcDS[0]))) == GCI_AlphaBand)
1528
0
        {
1529
0
            psOptions->bEnableSrcAlpha = true;
1530
0
            psOptions->bEnableDstAlpha = true;
1531
0
            if (!psOptions->bQuiet)
1532
0
                printf("Using band %d of source image as alpha.\n",
1533
0
                       GDALGetRasterCount(pahSrcDS[0]));
1534
0
        }
1535
0
    }
1536
1537
0
    auto hDstDS = GDALWarpCreateOutput(
1538
0
        nSrcCount, pahSrcDS, pszDest, psOptions->osFormat.c_str(),
1539
0
        psOptions->aosTransformerOptions.List(),
1540
0
        psOptions->aosCreateOptions.List(), psOptions->eOutputType,
1541
0
        hUniqueTransformArg, psOptions->bSetColorInterpretation, psOptions,
1542
0
        /* bUpdateTransformerWithDestGT = */ true);
1543
0
    if (hDstDS == nullptr)
1544
0
    {
1545
0
        return nullptr;
1546
0
    }
1547
0
    psOptions->bCreateOutput = true;
1548
1549
0
    if (!bInitDestSetByUser)
1550
0
    {
1551
0
        if (psOptions->osDstNodata.empty())
1552
0
        {
1553
0
            psOptions->aosWarpOptions.SetNameValue("INIT_DEST", "0");
1554
0
        }
1555
0
        else
1556
0
        {
1557
0
            psOptions->aosWarpOptions.SetNameValue("INIT_DEST", "NO_DATA");
1558
0
        }
1559
0
    }
1560
1561
0
    return hDstDS;
1562
0
}
1563
1564
/************************************************************************/
1565
/*                    EditISIS3ForMetadataChanges()                     */
1566
/************************************************************************/
1567
1568
static std::string
1569
EditISIS3ForMetadataChanges(const char *pszJSON,
1570
                            const GDALWarpAppOptions *psOptions)
1571
0
{
1572
0
    CPLJSONDocument oJSONDocument;
1573
0
    if (!oJSONDocument.LoadMemory(pszJSON))
1574
0
    {
1575
0
        return std::string();
1576
0
    }
1577
1578
0
    auto oRoot = oJSONDocument.GetRoot();
1579
0
    if (!oRoot.IsValid())
1580
0
    {
1581
0
        return std::string();
1582
0
    }
1583
1584
0
    auto oGDALHistory = oRoot.GetObj("GDALHistory");
1585
0
    if (!oGDALHistory.IsValid())
1586
0
    {
1587
0
        oGDALHistory = CPLJSONObject();
1588
0
        oRoot.Add("GDALHistory", oGDALHistory);
1589
0
    }
1590
0
    oGDALHistory["_type"] = "object";
1591
1592
0
    char szFullFilename[2048] = {0};
1593
0
    if (!CPLGetExecPath(szFullFilename, sizeof(szFullFilename) - 1))
1594
0
        strcpy(szFullFilename, "unknown_program");
1595
0
    const CPLString osProgram(CPLGetBasenameSafe(szFullFilename));
1596
0
    const CPLString osPath(CPLGetPathSafe(szFullFilename));
1597
1598
0
    oGDALHistory["GdalVersion"] = GDALVersionInfo("RELEASE_NAME");
1599
0
    oGDALHistory["Program"] = osProgram;
1600
0
    if (osPath != ".")
1601
0
        oGDALHistory["ProgramPath"] = osPath;
1602
1603
0
    std::string osArgs;
1604
0
    for (const char *pszArg : psOptions->aosArgs)
1605
0
    {
1606
0
        if (!osArgs.empty())
1607
0
            osArgs += ' ';
1608
0
        osArgs += pszArg;
1609
0
    }
1610
0
    oGDALHistory["ProgramArguments"] = osArgs;
1611
1612
0
    oGDALHistory.Add(
1613
0
        "Comment",
1614
0
        "Part of that metadata might be invalid due to a reprojection "
1615
0
        "operation having been performed by GDAL tools");
1616
1617
0
    return oRoot.Format(CPLJSONObject::PrettyFormat::Pretty);
1618
0
}
1619
1620
/************************************************************************/
1621
/*                          ProcessMetadata()                           */
1622
/************************************************************************/
1623
1624
static void ProcessMetadata(int iSrc, GDALDatasetH hSrcDS, GDALDatasetH hDstDS,
1625
                            const GDALWarpAppOptions *psOptions,
1626
                            const bool bEnableDstAlpha,
1627
                            bool bVerticalShiftApplied)
1628
0
{
1629
0
    if (psOptions->bCopyMetadata)
1630
0
    {
1631
0
        const char *pszSrcInfo = nullptr;
1632
0
        GDALRasterBandH hSrcBand = nullptr;
1633
0
        GDALRasterBandH hDstBand = nullptr;
1634
1635
        /* copy metadata from first dataset */
1636
0
        if (iSrc == 0)
1637
0
        {
1638
0
            CPLDebug(
1639
0
                "WARP",
1640
0
                "Copying metadata from first source to destination dataset");
1641
            /* copy dataset-level metadata */
1642
0
            CSLConstList papszMetadata = GDALGetMetadata(hSrcDS, nullptr);
1643
1644
0
            char **papszMetadataNew = nullptr;
1645
0
            for (int i = 0;
1646
0
                 papszMetadata != nullptr && papszMetadata[i] != nullptr; i++)
1647
0
            {
1648
                // Do not preserve NODATA_VALUES when the output includes an
1649
                // alpha band
1650
0
                if (bEnableDstAlpha &&
1651
0
                    STARTS_WITH_CI(papszMetadata[i], "NODATA_VALUES="))
1652
0
                {
1653
0
                    continue;
1654
0
                }
1655
                // Do not preserve the CACHE_PATH from the WMS driver
1656
0
                if (STARTS_WITH_CI(papszMetadata[i], "CACHE_PATH="))
1657
0
                {
1658
0
                    continue;
1659
0
                }
1660
1661
0
                papszMetadataNew =
1662
0
                    CSLAddString(papszMetadataNew, papszMetadata[i]);
1663
0
            }
1664
1665
0
            if (CSLCount(papszMetadataNew) > 0)
1666
0
            {
1667
0
                if (GDALSetMetadata(hDstDS, papszMetadataNew, nullptr) !=
1668
0
                    CE_None)
1669
0
                    CPLError(CE_Warning, CPLE_AppDefined,
1670
0
                             "error copying metadata to destination dataset.");
1671
0
            }
1672
1673
0
            CSLDestroy(papszMetadataNew);
1674
1675
            /* ISIS3 -> ISIS3 special case */
1676
0
            if (EQUAL(psOptions->osFormat.c_str(), "ISIS3") ||
1677
0
                EQUAL(psOptions->osFormat.c_str(), "PDS4") ||
1678
0
                EQUAL(psOptions->osFormat.c_str(), "GTIFF") ||
1679
0
                EQUAL(psOptions->osFormat.c_str(), "COG"))
1680
0
            {
1681
0
                CSLConstList papszMD_ISIS3 =
1682
0
                    GDALGetMetadata(hSrcDS, "json:ISIS3");
1683
0
                if (papszMD_ISIS3 != nullptr && papszMD_ISIS3[0])
1684
0
                {
1685
0
                    std::string osJSON = papszMD_ISIS3[0];
1686
0
                    osJSON =
1687
0
                        EditISIS3ForMetadataChanges(osJSON.c_str(), psOptions);
1688
0
                    if (!osJSON.empty())
1689
0
                    {
1690
0
                        char *apszMD[] = {osJSON.data(), nullptr};
1691
0
                        GDALSetMetadata(hDstDS, apszMD, "json:ISIS3");
1692
0
                    }
1693
0
                }
1694
0
            }
1695
0
            else if (EQUAL(psOptions->osFormat.c_str(), "PDS4"))
1696
0
            {
1697
0
                CSLConstList papszMD_PDS4 = GDALGetMetadata(hSrcDS, "xml:PDS4");
1698
0
                if (papszMD_PDS4 != nullptr)
1699
0
                    GDALSetMetadata(hDstDS, papszMD_PDS4, "xml:PDS4");
1700
0
            }
1701
0
            else if (EQUAL(psOptions->osFormat.c_str(), "VICAR"))
1702
0
            {
1703
0
                CSLConstList papszMD_VICAR =
1704
0
                    GDALGetMetadata(hSrcDS, "json:VICAR");
1705
0
                if (papszMD_VICAR != nullptr)
1706
0
                    GDALSetMetadata(hDstDS, papszMD_VICAR, "json:VICAR");
1707
0
            }
1708
1709
            /* copy band-level metadata and other info */
1710
0
            if (GDALGetRasterCount(hSrcDS) == GDALGetRasterCount(hDstDS))
1711
0
            {
1712
0
                for (int iBand = 0; iBand < GDALGetRasterCount(hSrcDS); iBand++)
1713
0
                {
1714
0
                    hSrcBand = GDALGetRasterBand(hSrcDS, iBand + 1);
1715
0
                    hDstBand = GDALGetRasterBand(hDstDS, iBand + 1);
1716
                    /* copy metadata, except stats (#5319) */
1717
0
                    papszMetadata = GDALGetMetadata(hSrcBand, nullptr);
1718
0
                    if (CSLCount(papszMetadata) > 0)
1719
0
                    {
1720
                        // GDALSetMetadata( hDstBand, papszMetadata, NULL );
1721
0
                        papszMetadataNew = nullptr;
1722
0
                        for (int i = 0; papszMetadata != nullptr &&
1723
0
                                        papszMetadata[i] != nullptr;
1724
0
                             i++)
1725
0
                        {
1726
0
                            if (!STARTS_WITH(papszMetadata[i], "STATISTICS_"))
1727
0
                                papszMetadataNew = CSLAddString(
1728
0
                                    papszMetadataNew, papszMetadata[i]);
1729
0
                        }
1730
0
                        GDALSetMetadata(hDstBand, papszMetadataNew, nullptr);
1731
0
                        CSLDestroy(papszMetadataNew);
1732
0
                    }
1733
                    /* copy other info (Description, Unit Type) - what else? */
1734
0
                    if (psOptions->bCopyBandInfo && !bVerticalShiftApplied)
1735
0
                    {
1736
0
                        pszSrcInfo = GDALGetDescription(hSrcBand);
1737
0
                        if (pszSrcInfo != nullptr && strlen(pszSrcInfo) > 0)
1738
0
                            GDALSetDescription(hDstBand, pszSrcInfo);
1739
0
                        pszSrcInfo = GDALGetRasterUnitType(hSrcBand);
1740
0
                        if (pszSrcInfo != nullptr && strlen(pszSrcInfo) > 0)
1741
0
                            GDALSetRasterUnitType(hDstBand, pszSrcInfo);
1742
0
                    }
1743
0
                }
1744
0
            }
1745
0
        }
1746
        /* remove metadata that conflicts between datasets */
1747
0
        else
1748
0
        {
1749
0
            CPLDebug("WARP",
1750
0
                     "Removing conflicting metadata from destination dataset "
1751
0
                     "(source #%d)",
1752
0
                     iSrc);
1753
            /* remove conflicting dataset-level metadata */
1754
0
            RemoveConflictingMetadata(hDstDS, GDALGetMetadata(hSrcDS, nullptr),
1755
0
                                      psOptions->osMDConflictValue.c_str());
1756
1757
            /* remove conflicting copy band-level metadata and other info */
1758
0
            if (GDALGetRasterCount(hSrcDS) == GDALGetRasterCount(hDstDS))
1759
0
            {
1760
0
                for (int iBand = 0; iBand < GDALGetRasterCount(hSrcDS); iBand++)
1761
0
                {
1762
0
                    hSrcBand = GDALGetRasterBand(hSrcDS, iBand + 1);
1763
0
                    hDstBand = GDALGetRasterBand(hDstDS, iBand + 1);
1764
                    /* remove conflicting metadata */
1765
0
                    RemoveConflictingMetadata(
1766
0
                        hDstBand, GDALGetMetadata(hSrcBand, nullptr),
1767
0
                        psOptions->osMDConflictValue.c_str());
1768
                    /* remove conflicting info */
1769
0
                    if (psOptions->bCopyBandInfo)
1770
0
                    {
1771
0
                        pszSrcInfo = GDALGetDescription(hSrcBand);
1772
0
                        const char *pszDstInfo = GDALGetDescription(hDstBand);
1773
0
                        if (!(pszSrcInfo != nullptr && strlen(pszSrcInfo) > 0 &&
1774
0
                              pszDstInfo != nullptr && strlen(pszDstInfo) > 0 &&
1775
0
                              EQUAL(pszSrcInfo, pszDstInfo)))
1776
0
                            GDALSetDescription(hDstBand, "");
1777
0
                        pszSrcInfo = GDALGetRasterUnitType(hSrcBand);
1778
0
                        pszDstInfo = GDALGetRasterUnitType(hDstBand);
1779
0
                        if (!(pszSrcInfo != nullptr && strlen(pszSrcInfo) > 0 &&
1780
0
                              pszDstInfo != nullptr && strlen(pszDstInfo) > 0 &&
1781
0
                              EQUAL(pszSrcInfo, pszDstInfo)))
1782
0
                            GDALSetRasterUnitType(hDstBand, "");
1783
0
                    }
1784
0
                }
1785
0
            }
1786
0
        }
1787
0
    }
1788
0
}
1789
1790
/************************************************************************/
1791
/*                            SetupNoData()                             */
1792
/************************************************************************/
1793
1794
static CPLErr SetupNoData(const char *pszDest, int iSrc, GDALDatasetH hSrcDS,
1795
                          GDALDatasetH hWrkSrcDS, GDALDatasetH hDstDS,
1796
                          GDALWarpOptions *psWO, GDALWarpAppOptions *psOptions,
1797
                          const bool bEnableDstAlpha,
1798
                          const bool bInitDestSetByUser)
1799
0
{
1800
0
    if (!psOptions->osSrcNodata.empty() &&
1801
0
        !EQUAL(psOptions->osSrcNodata.c_str(), "none"))
1802
0
    {
1803
0
        CPLStringList aosTokens(
1804
0
            CSLTokenizeString(psOptions->osSrcNodata.c_str()));
1805
0
        const int nTokenCount = aosTokens.Count();
1806
1807
0
        psWO->padfSrcNoDataReal =
1808
0
            static_cast<double *>(CPLMalloc(psWO->nBandCount * sizeof(double)));
1809
0
        psWO->padfSrcNoDataImag = nullptr;
1810
1811
0
        for (int i = 0; i < psWO->nBandCount; i++)
1812
0
        {
1813
0
            if (i < nTokenCount)
1814
0
            {
1815
0
                double dfNoDataReal;
1816
0
                double dfNoDataImag;
1817
1818
0
                if (CPLStringToComplex(aosTokens[i], &dfNoDataReal,
1819
0
                                       &dfNoDataImag) != CE_None)
1820
0
                {
1821
0
                    CPLError(CE_Failure, CPLE_AppDefined,
1822
0
                             "Error parsing srcnodata for band %d", i + 1);
1823
0
                    return CE_Failure;
1824
0
                }
1825
1826
0
                psWO->padfSrcNoDataReal[i] =
1827
0
                    GDALAdjustNoDataCloseToFloatMax(dfNoDataReal);
1828
1829
0
                if (strchr(aosTokens[i], 'i') != nullptr)
1830
0
                {
1831
0
                    if (psWO->padfSrcNoDataImag == nullptr)
1832
0
                    {
1833
0
                        psWO->padfSrcNoDataImag = static_cast<double *>(
1834
0
                            CPLCalloc(psWO->nBandCount, sizeof(double)));
1835
0
                    }
1836
0
                    psWO->padfSrcNoDataImag[i] =
1837
0
                        GDALAdjustNoDataCloseToFloatMax(dfNoDataImag);
1838
0
                }
1839
0
            }
1840
0
            else
1841
0
            {
1842
0
                psWO->padfSrcNoDataReal[i] = psWO->padfSrcNoDataReal[i - 1];
1843
0
                if (psWO->padfSrcNoDataImag != nullptr)
1844
0
                {
1845
0
                    psWO->padfSrcNoDataImag[i] = psWO->padfSrcNoDataImag[i - 1];
1846
0
                }
1847
0
            }
1848
0
        }
1849
1850
0
        if (psWO->nBandCount > 1 &&
1851
0
            CSLFetchNameValue(psWO->papszWarpOptions, "UNIFIED_SRC_NODATA") ==
1852
0
                nullptr)
1853
0
        {
1854
0
            CPLDebug("WARP", "Set UNIFIED_SRC_NODATA=YES");
1855
0
            psWO->papszWarpOptions = CSLSetNameValue(
1856
0
                psWO->papszWarpOptions, "UNIFIED_SRC_NODATA", "YES");
1857
0
        }
1858
0
    }
1859
1860
    /* -------------------------------------------------------------------- */
1861
    /*      If -srcnodata was not specified, but the data has nodata        */
1862
    /*      values, use them.                                               */
1863
    /* -------------------------------------------------------------------- */
1864
0
    if (psOptions->osSrcNodata.empty())
1865
0
    {
1866
0
        int bHaveNodata = FALSE;
1867
0
        double dfReal = 0.0;
1868
1869
0
        for (int i = 0; !bHaveNodata && i < psWO->nBandCount; i++)
1870
0
        {
1871
0
            GDALRasterBandH hBand =
1872
0
                GDALGetRasterBand(hWrkSrcDS, psWO->panSrcBands[i]);
1873
0
            dfReal = GDALGetRasterNoDataValue(hBand, &bHaveNodata);
1874
0
        }
1875
1876
0
        if (bHaveNodata)
1877
0
        {
1878
0
            if (!psOptions->bQuiet)
1879
0
            {
1880
0
                if (std::isnan(dfReal))
1881
0
                    printf("Using internal nodata values (e.g. nan) for image "
1882
0
                           "%s.\n",
1883
0
                           GDALGetDescription(hSrcDS));
1884
0
                else
1885
0
                    printf("Using internal nodata values (e.g. %g) for image "
1886
0
                           "%s.\n",
1887
0
                           dfReal, GDALGetDescription(hSrcDS));
1888
0
            }
1889
0
            psWO->padfSrcNoDataReal = static_cast<double *>(
1890
0
                CPLMalloc(psWO->nBandCount * sizeof(double)));
1891
1892
0
            for (int i = 0; i < psWO->nBandCount; i++)
1893
0
            {
1894
0
                GDALRasterBandH hBand =
1895
0
                    GDALGetRasterBand(hWrkSrcDS, psWO->panSrcBands[i]);
1896
1897
0
                dfReal = GDALGetRasterNoDataValue(hBand, &bHaveNodata);
1898
1899
0
                if (bHaveNodata)
1900
0
                {
1901
0
                    psWO->padfSrcNoDataReal[i] = dfReal;
1902
0
                }
1903
0
                else
1904
0
                {
1905
0
                    psWO->padfSrcNoDataReal[i] = -123456.789;
1906
0
                }
1907
0
            }
1908
0
        }
1909
0
    }
1910
1911
    /* -------------------------------------------------------------------- */
1912
    /*      If the output dataset was created, and we have a destination    */
1913
    /*      nodata value, go through marking the bands with the information.*/
1914
    /* -------------------------------------------------------------------- */
1915
0
    if (!psOptions->osDstNodata.empty() &&
1916
0
        !EQUAL(psOptions->osDstNodata.c_str(), "none"))
1917
0
    {
1918
0
        CPLStringList aosTokens(
1919
0
            CSLTokenizeString(psOptions->osDstNodata.c_str()));
1920
0
        const int nTokenCount = aosTokens.Count();
1921
0
        bool bDstNoDataNone = true;
1922
1923
0
        psWO->padfDstNoDataReal =
1924
0
            static_cast<double *>(CPLMalloc(psWO->nBandCount * sizeof(double)));
1925
0
        psWO->padfDstNoDataImag =
1926
0
            static_cast<double *>(CPLMalloc(psWO->nBandCount * sizeof(double)));
1927
1928
0
        for (int i = 0; i < psWO->nBandCount; i++)
1929
0
        {
1930
0
            psWO->padfDstNoDataReal[i] = -1.1e20;
1931
0
            psWO->padfDstNoDataImag[i] = 0.0;
1932
1933
0
            if (i < nTokenCount)
1934
0
            {
1935
0
                if (aosTokens[i] != nullptr && EQUAL(aosTokens[i], "none"))
1936
0
                {
1937
0
                    CPLDebug("WARP", "dstnodata of band %d not set", i);
1938
0
                    bDstNoDataNone = true;
1939
0
                    continue;
1940
0
                }
1941
0
                else if (aosTokens[i] ==
1942
0
                         nullptr)  // this should not happen, but just in case
1943
0
                {
1944
0
                    CPLError(CE_Failure, CPLE_AppDefined,
1945
0
                             "Error parsing dstnodata arg #%d", i);
1946
0
                    bDstNoDataNone = true;
1947
0
                    continue;
1948
0
                }
1949
1950
0
                if (CPLStringToComplex(aosTokens[i],
1951
0
                                       psWO->padfDstNoDataReal + i,
1952
0
                                       psWO->padfDstNoDataImag + i) != CE_None)
1953
0
                {
1954
1955
0
                    CPLError(CE_Failure, CPLE_AppDefined,
1956
0
                             "Error parsing dstnodata for band %d", i + 1);
1957
0
                    return CE_Failure;
1958
0
                }
1959
1960
0
                psWO->padfDstNoDataReal[i] =
1961
0
                    GDALAdjustNoDataCloseToFloatMax(psWO->padfDstNoDataReal[i]);
1962
0
                psWO->padfDstNoDataImag[i] =
1963
0
                    GDALAdjustNoDataCloseToFloatMax(psWO->padfDstNoDataImag[i]);
1964
0
                bDstNoDataNone = false;
1965
0
                CPLDebug("WARP", "dstnodata of band %d set to %f", i,
1966
0
                         psWO->padfDstNoDataReal[i]);
1967
0
            }
1968
0
            else
1969
0
            {
1970
0
                if (!bDstNoDataNone)
1971
0
                {
1972
0
                    psWO->padfDstNoDataReal[i] = psWO->padfDstNoDataReal[i - 1];
1973
0
                    psWO->padfDstNoDataImag[i] = psWO->padfDstNoDataImag[i - 1];
1974
0
                    CPLDebug("WARP",
1975
0
                             "dstnodata of band %d set from previous band", i);
1976
0
                }
1977
0
                else
1978
0
                {
1979
0
                    CPLDebug("WARP", "dstnodata value of band %d not set", i);
1980
0
                    continue;
1981
0
                }
1982
0
            }
1983
1984
0
            GDALRasterBandH hBand =
1985
0
                GDALGetRasterBand(hDstDS, psWO->panDstBands[i]);
1986
0
            int bClamped = FALSE;
1987
0
            int bRounded = FALSE;
1988
0
            psWO->padfDstNoDataReal[i] = GDALAdjustValueToDataType(
1989
0
                GDALGetRasterDataType(hBand), psWO->padfDstNoDataReal[i],
1990
0
                &bClamped, &bRounded);
1991
1992
0
            if (bClamped)
1993
0
            {
1994
0
                CPLError(
1995
0
                    CE_Warning, CPLE_AppDefined,
1996
0
                    "for band %d, destination nodata value has been clamped "
1997
0
                    "to %.0f, the original value being out of range.",
1998
0
                    psWO->panDstBands[i], psWO->padfDstNoDataReal[i]);
1999
0
            }
2000
0
            else if (bRounded)
2001
0
            {
2002
0
                CPLError(
2003
0
                    CE_Warning, CPLE_AppDefined,
2004
0
                    "for band %d, destination nodata value has been rounded "
2005
0
                    "to %.0f, %s being an integer datatype.",
2006
0
                    psWO->panDstBands[i], psWO->padfDstNoDataReal[i],
2007
0
                    GDALGetDataTypeName(GDALGetRasterDataType(hBand)));
2008
0
            }
2009
2010
0
            if (psOptions->bCreateOutput && iSrc == 0)
2011
0
            {
2012
0
                GDALSetRasterNoDataValue(
2013
0
                    GDALGetRasterBand(hDstDS, psWO->panDstBands[i]),
2014
0
                    psWO->padfDstNoDataReal[i]);
2015
0
            }
2016
0
        }
2017
0
    }
2018
2019
    /* check if the output dataset has already nodata */
2020
0
    if (psOptions->osDstNodata.empty())
2021
0
    {
2022
0
        int bHaveNodataAll = TRUE;
2023
0
        for (int i = 0; i < psWO->nBandCount; i++)
2024
0
        {
2025
0
            GDALRasterBandH hBand =
2026
0
                GDALGetRasterBand(hDstDS, psWO->panDstBands[i]);
2027
0
            int bHaveNodata = FALSE;
2028
0
            GDALGetRasterNoDataValue(hBand, &bHaveNodata);
2029
0
            bHaveNodataAll &= bHaveNodata;
2030
0
        }
2031
0
        if (bHaveNodataAll)
2032
0
        {
2033
0
            psWO->padfDstNoDataReal = static_cast<double *>(
2034
0
                CPLMalloc(psWO->nBandCount * sizeof(double)));
2035
0
            for (int i = 0; i < psWO->nBandCount; i++)
2036
0
            {
2037
0
                GDALRasterBandH hBand =
2038
0
                    GDALGetRasterBand(hDstDS, psWO->panDstBands[i]);
2039
0
                int bHaveNodata = FALSE;
2040
0
                psWO->padfDstNoDataReal[i] =
2041
0
                    GDALGetRasterNoDataValue(hBand, &bHaveNodata);
2042
0
                CPLDebug("WARP", "band=%d dstNoData=%f", i,
2043
0
                         psWO->padfDstNoDataReal[i]);
2044
0
            }
2045
0
        }
2046
0
    }
2047
2048
    // If creating a new file that has default nodata value,
2049
    // try to override the default output nodata values with the source ones.
2050
0
    if (psOptions->osDstNodata.empty() && psWO->padfSrcNoDataReal != nullptr &&
2051
0
        psWO->padfDstNoDataReal != nullptr && psOptions->bCreateOutput &&
2052
0
        iSrc == 0 && !bEnableDstAlpha)
2053
0
    {
2054
0
        for (int i = 0; i < psWO->nBandCount; i++)
2055
0
        {
2056
0
            GDALRasterBandH hBand =
2057
0
                GDALGetRasterBand(hDstDS, psWO->panDstBands[i]);
2058
0
            int bHaveNodata = FALSE;
2059
0
            CPLPushErrorHandler(CPLQuietErrorHandler);
2060
0
            bool bRedefinedOK =
2061
0
                (GDALSetRasterNoDataValue(hBand, psWO->padfSrcNoDataReal[i]) ==
2062
0
                     CE_None &&
2063
0
                 GDALGetRasterNoDataValue(hBand, &bHaveNodata) ==
2064
0
                     psWO->padfSrcNoDataReal[i] &&
2065
0
                 bHaveNodata);
2066
0
            CPLPopErrorHandler();
2067
0
            if (bRedefinedOK)
2068
0
            {
2069
0
                if (i == 0 && !psOptions->bQuiet)
2070
0
                    printf("Copying nodata values from source %s "
2071
0
                           "to destination %s.\n",
2072
0
                           GDALGetDescription(hSrcDS), pszDest);
2073
0
                psWO->padfDstNoDataReal[i] = psWO->padfSrcNoDataReal[i];
2074
2075
0
                if (i == 0 && !bInitDestSetByUser)
2076
0
                {
2077
                    /* As we didn't know at the beginning if there was source
2078
                     * nodata */
2079
                    /* we have initialized INIT_DEST=0. Override this with
2080
                     * NO_DATA now */
2081
0
                    psWO->papszWarpOptions = CSLSetNameValue(
2082
0
                        psWO->papszWarpOptions, "INIT_DEST", "NO_DATA");
2083
0
                }
2084
0
            }
2085
0
            else
2086
0
            {
2087
0
                break;
2088
0
            }
2089
0
        }
2090
0
    }
2091
2092
    /* else try to fill dstNoData from source bands, unless -dstalpha is
2093
     * specified */
2094
0
    else if (psOptions->osDstNodata.empty() &&
2095
0
             psWO->padfSrcNoDataReal != nullptr &&
2096
0
             psWO->padfDstNoDataReal == nullptr && !bEnableDstAlpha)
2097
0
    {
2098
0
        psWO->padfDstNoDataReal =
2099
0
            static_cast<double *>(CPLMalloc(psWO->nBandCount * sizeof(double)));
2100
2101
0
        if (psWO->padfSrcNoDataImag != nullptr)
2102
0
        {
2103
0
            psWO->padfDstNoDataImag = static_cast<double *>(
2104
0
                CPLMalloc(psWO->nBandCount * sizeof(double)));
2105
0
        }
2106
2107
0
        if (!psOptions->bQuiet)
2108
0
            printf("Copying nodata values from source %s to destination %s.\n",
2109
0
                   GDALGetDescription(hSrcDS), pszDest);
2110
2111
0
        for (int i = 0; i < psWO->nBandCount; i++)
2112
0
        {
2113
0
            psWO->padfDstNoDataReal[i] = psWO->padfSrcNoDataReal[i];
2114
0
            if (psWO->padfSrcNoDataImag != nullptr)
2115
0
            {
2116
0
                psWO->padfDstNoDataImag[i] = psWO->padfSrcNoDataImag[i];
2117
0
            }
2118
0
            CPLDebug("WARP", "srcNoData=%f dstNoData=%f",
2119
0
                     psWO->padfSrcNoDataReal[i], psWO->padfDstNoDataReal[i]);
2120
2121
0
            if (psOptions->bCreateOutput && iSrc == 0)
2122
0
            {
2123
0
                CPLDebug("WARP",
2124
0
                         "calling GDALSetRasterNoDataValue() for band#%d", i);
2125
0
                GDALSetRasterNoDataValue(
2126
0
                    GDALGetRasterBand(hDstDS, psWO->panDstBands[i]),
2127
0
                    psWO->padfDstNoDataReal[i]);
2128
0
            }
2129
0
        }
2130
2131
0
        if (psOptions->bCreateOutput && !bInitDestSetByUser && iSrc == 0)
2132
0
        {
2133
            /* As we didn't know at the beginning if there was source nodata */
2134
            /* we have initialized INIT_DEST=0. Override this with NO_DATA now
2135
             */
2136
0
            psWO->papszWarpOptions =
2137
0
                CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "NO_DATA");
2138
0
        }
2139
0
    }
2140
2141
0
    return CE_None;
2142
0
}
2143
2144
/************************************************************************/
2145
/*                         SetupSkipNoSource()                          */
2146
/************************************************************************/
2147
2148
static void SetupSkipNoSource(int iSrc, GDALDatasetH hDstDS,
2149
                              GDALWarpOptions *psWO,
2150
                              GDALWarpAppOptions *psOptions)
2151
0
{
2152
0
    if (psOptions->bCreateOutput && iSrc == 0 &&
2153
0
        CSLFetchNameValue(psWO->papszWarpOptions, "SKIP_NOSOURCE") == nullptr &&
2154
0
        CSLFetchNameValue(psWO->papszWarpOptions, "STREAMABLE_OUTPUT") ==
2155
0
            nullptr &&
2156
        // This white list of drivers could potentially be extended.
2157
0
        (EQUAL(psOptions->osFormat.c_str(), "MEM") ||
2158
0
         EQUAL(psOptions->osFormat.c_str(), "GTiff") ||
2159
0
         EQUAL(psOptions->osFormat.c_str(), "GPKG")))
2160
0
    {
2161
        // We can enable the optimization only if the user didn't specify
2162
        // a INIT_DEST value that would contradict the destination nodata.
2163
2164
0
        bool bOKRegardingInitDest = false;
2165
0
        const char *pszInitDest =
2166
0
            CSLFetchNameValue(psWO->papszWarpOptions, "INIT_DEST");
2167
0
        if (pszInitDest == nullptr || EQUAL(pszInitDest, "NO_DATA"))
2168
0
        {
2169
0
            bOKRegardingInitDest = true;
2170
2171
            // The MEM driver will return non-initialized blocks at 0
2172
            // so make sure that the nodata value is 0.
2173
0
            if (EQUAL(psOptions->osFormat.c_str(), "MEM"))
2174
0
            {
2175
0
                for (int i = 0; i < GDALGetRasterCount(hDstDS); i++)
2176
0
                {
2177
0
                    int bHasNoData = false;
2178
0
                    double dfDstNoDataVal = GDALGetRasterNoDataValue(
2179
0
                        GDALGetRasterBand(hDstDS, i + 1), &bHasNoData);
2180
0
                    if (bHasNoData && dfDstNoDataVal != 0.0)
2181
0
                    {
2182
0
                        bOKRegardingInitDest = false;
2183
0
                        break;
2184
0
                    }
2185
0
                }
2186
0
            }
2187
0
        }
2188
0
        else
2189
0
        {
2190
0
            char **papszTokensInitDest = CSLTokenizeString(pszInitDest);
2191
0
            const int nTokenCountInitDest = CSLCount(papszTokensInitDest);
2192
0
            if (nTokenCountInitDest == 1 ||
2193
0
                nTokenCountInitDest == GDALGetRasterCount(hDstDS))
2194
0
            {
2195
0
                bOKRegardingInitDest = true;
2196
0
                for (int i = 0; i < GDALGetRasterCount(hDstDS); i++)
2197
0
                {
2198
0
                    double dfInitVal = GDALAdjustNoDataCloseToFloatMax(
2199
0
                        CPLAtofM(papszTokensInitDest[std::min(
2200
0
                            i, nTokenCountInitDest - 1)]));
2201
0
                    int bHasNoData = false;
2202
0
                    double dfDstNoDataVal = GDALGetRasterNoDataValue(
2203
0
                        GDALGetRasterBand(hDstDS, i + 1), &bHasNoData);
2204
0
                    if (!((bHasNoData && dfInitVal == dfDstNoDataVal) ||
2205
0
                          (!bHasNoData && dfInitVal == 0.0)))
2206
0
                    {
2207
0
                        bOKRegardingInitDest = false;
2208
0
                        break;
2209
0
                    }
2210
0
                    if (EQUAL(psOptions->osFormat.c_str(), "MEM") &&
2211
0
                        bHasNoData && dfDstNoDataVal != 0.0)
2212
0
                    {
2213
0
                        bOKRegardingInitDest = false;
2214
0
                        break;
2215
0
                    }
2216
0
                }
2217
0
            }
2218
0
            CSLDestroy(papszTokensInitDest);
2219
0
        }
2220
2221
0
        if (bOKRegardingInitDest)
2222
0
        {
2223
0
            CPLDebug("GDALWARP", "Defining SKIP_NOSOURCE=YES");
2224
0
            psWO->papszWarpOptions =
2225
0
                CSLSetNameValue(psWO->papszWarpOptions, "SKIP_NOSOURCE", "YES");
2226
0
        }
2227
0
    }
2228
0
}
2229
2230
/************************************************************************/
2231
/*                      AdjustOutputExtentForRPC()                      */
2232
/************************************************************************/
2233
2234
/** Returns false if there's no intersection between source extent defined
2235
 * by RPC and target extent.
2236
 */
2237
static bool AdjustOutputExtentForRPC(GDALDatasetH hSrcDS, GDALDatasetH hDstDS,
2238
                                     GDALTransformerFunc pfnTransformer,
2239
                                     void *hTransformArg, GDALWarpOptions *psWO,
2240
                                     GDALWarpAppOptions *psOptions,
2241
                                     int &nWarpDstXOff, int &nWarpDstYOff,
2242
                                     int &nWarpDstXSize, int &nWarpDstYSize)
2243
0
{
2244
0
    if (CPLTestBool(CSLFetchNameValueDef(psWO->papszWarpOptions,
2245
0
                                         "SKIP_NOSOURCE", "NO")) &&
2246
0
        GDALGetMetadata(hSrcDS, GDAL_MDD_RPC) != nullptr &&
2247
0
        EQUAL(FetchSrcMethod(psOptions->aosTransformerOptions, GDAL_MDD_RPC),
2248
0
              GDAL_MDD_RPC) &&
2249
0
        CPLTestBool(
2250
0
            CPLGetConfigOption("RESTRICT_OUTPUT_DATASET_UPDATE", "YES")))
2251
0
    {
2252
0
        double adfSuggestedGeoTransform[6];
2253
0
        double adfExtent[4];
2254
0
        int nPixels, nLines;
2255
0
        if (GDALSuggestedWarpOutput2(hSrcDS, pfnTransformer, hTransformArg,
2256
0
                                     adfSuggestedGeoTransform, &nPixels,
2257
0
                                     &nLines, adfExtent, 0) == CE_None)
2258
0
        {
2259
0
            const double dfMinX = adfExtent[0];
2260
0
            const double dfMinY = adfExtent[1];
2261
0
            const double dfMaxX = adfExtent[2];
2262
0
            const double dfMaxY = adfExtent[3];
2263
0
            const double dfThreshold = static_cast<double>(INT_MAX) / 2;
2264
0
            if (std::fabs(dfMinX) < dfThreshold &&
2265
0
                std::fabs(dfMinY) < dfThreshold &&
2266
0
                std::fabs(dfMaxX) < dfThreshold &&
2267
0
                std::fabs(dfMaxY) < dfThreshold)
2268
0
            {
2269
0
                const int nPadding = 5;
2270
0
                nWarpDstXOff =
2271
0
                    std::max(nWarpDstXOff,
2272
0
                             static_cast<int>(std::floor(dfMinX)) - nPadding);
2273
0
                nWarpDstYOff =
2274
0
                    std::max(nWarpDstYOff,
2275
0
                             static_cast<int>(std::floor(dfMinY)) - nPadding);
2276
0
                nWarpDstXSize = std::min(nWarpDstXSize - nWarpDstXOff,
2277
0
                                         static_cast<int>(std::ceil(dfMaxX)) +
2278
0
                                             nPadding - nWarpDstXOff);
2279
0
                nWarpDstYSize = std::min(nWarpDstYSize - nWarpDstYOff,
2280
0
                                         static_cast<int>(std::ceil(dfMaxY)) +
2281
0
                                             nPadding - nWarpDstYOff);
2282
0
                if (nWarpDstXSize <= 0 || nWarpDstYSize <= 0)
2283
0
                {
2284
0
                    CPLDebug("WARP",
2285
0
                             "No intersection between source extent defined "
2286
0
                             "by RPC and target extent");
2287
0
                    return false;
2288
0
                }
2289
0
                if (nWarpDstXOff != 0 || nWarpDstYOff != 0 ||
2290
0
                    nWarpDstXSize != GDALGetRasterXSize(hDstDS) ||
2291
0
                    nWarpDstYSize != GDALGetRasterYSize(hDstDS))
2292
0
                {
2293
0
                    CPLDebug("WARP",
2294
0
                             "Restricting warping to output dataset window "
2295
0
                             "%d,%d,%dx%d",
2296
0
                             nWarpDstXOff, nWarpDstYOff, nWarpDstXSize,
2297
0
                             nWarpDstYSize);
2298
0
                }
2299
0
            }
2300
0
        }
2301
0
    }
2302
0
    return true;
2303
0
}
2304
2305
/************************************************************************/
2306
/*                           GDALWarpDirect()                           */
2307
/************************************************************************/
2308
2309
static GDALDatasetH
2310
GDALWarpDirect(const char *pszDest, GDALDatasetH hDstDS, int nSrcCount,
2311
               GDALDatasetH *pahSrcDS,
2312
               GDALTransformerArgUniquePtr hUniqueTransformArg,
2313
               GDALWarpAppOptions *psOptions, int *pbUsageError)
2314
0
{
2315
0
    CPLErrorReset();
2316
0
    if (pszDest == nullptr && hDstDS == nullptr)
2317
0
    {
2318
0
        CPLError(CE_Failure, CPLE_AppDefined,
2319
0
                 "pszDest == NULL && hDstDS == NULL");
2320
2321
0
        if (pbUsageError)
2322
0
            *pbUsageError = TRUE;
2323
0
        return nullptr;
2324
0
    }
2325
0
    if (pszDest == nullptr)
2326
0
        pszDest = GDALGetDescription(hDstDS);
2327
2328
0
#ifdef DEBUG
2329
0
    GDALDataset *poDstDS = GDALDataset::FromHandle(hDstDS);
2330
0
    const int nExpectedRefCountAtEnd =
2331
0
        (poDstDS != nullptr) ? poDstDS->GetRefCount() : 1;
2332
0
    (void)nExpectedRefCountAtEnd;
2333
0
#endif
2334
0
    const bool bDropDstDSRef = (hDstDS != nullptr);
2335
0
    if (hDstDS != nullptr)
2336
0
        GDALReferenceDataset(hDstDS);
2337
2338
0
    bool bVerticalShiftApplied = false;
2339
2340
0
    if (psOptions->bNoVShift)
2341
0
    {
2342
0
        psOptions->aosTransformerOptions.SetNameValue("@STRIP_VERT_CS", "YES");
2343
0
    }
2344
0
    else if (nSrcCount)
2345
0
    {
2346
0
        bool bSrcHasVertAxis = false;
2347
0
        bool bDstHasVertAxis = false;
2348
0
        OGRSpatialReference oSRSSrc;
2349
0
        OGRSpatialReference oSRSDst;
2350
2351
0
        bVerticalShiftApplied =
2352
0
            MustApplyVerticalShift(pahSrcDS[0], psOptions, oSRSSrc, oSRSDst,
2353
0
                                   bSrcHasVertAxis, bDstHasVertAxis);
2354
0
        if (bVerticalShiftApplied)
2355
0
        {
2356
0
            psOptions->aosTransformerOptions.SetNameValue("PROMOTE_TO_3D",
2357
0
                                                          "YES");
2358
0
        }
2359
0
    }
2360
2361
0
    bool bVRT = false;
2362
0
    if (!CheckOptions(pszDest, hDstDS, nSrcCount, pahSrcDS, psOptions, bVRT,
2363
0
                      pbUsageError))
2364
0
    {
2365
0
        return nullptr;
2366
0
    }
2367
2368
    /* -------------------------------------------------------------------- */
2369
    /*      If we have a cutline datasource read it and attach it in the    */
2370
    /*      warp options.                                                   */
2371
    /* -------------------------------------------------------------------- */
2372
0
    std::unique_ptr<OGRGeometry> poCutline;
2373
0
    if (!ProcessCutlineOptions(nSrcCount, pahSrcDS, psOptions, poCutline))
2374
0
    {
2375
0
        return nullptr;
2376
0
    }
2377
2378
    /* -------------------------------------------------------------------- */
2379
    /*      If the target dataset does not exist, we need to create it.     */
2380
    /* -------------------------------------------------------------------- */
2381
0
    const bool bInitDestSetByUser =
2382
0
        (psOptions->aosWarpOptions.FetchNameValue("INIT_DEST") != nullptr);
2383
2384
0
    const bool bFigureoutCorrespondingWindow =
2385
0
        (hDstDS != nullptr) ||
2386
0
        (((psOptions->nForcePixels != 0 && psOptions->nForceLines != 0) ||
2387
0
          (psOptions->dfXRes != 0 && psOptions->dfYRes != 0)) &&
2388
0
         !(psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
2389
0
           psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0));
2390
2391
0
    const char *pszMethod = FetchSrcMethod(psOptions->aosTransformerOptions);
2392
0
    if (pszMethod && EQUAL(pszMethod, "GCP_TPS") &&
2393
0
        psOptions->dfErrorThreshold > 0 &&
2394
0
        !psOptions->aosTransformerOptions.FetchNameValue(
2395
0
            "SRC_APPROX_ERROR_IN_PIXEL"))
2396
0
    {
2397
0
        psOptions->aosTransformerOptions.SetNameValue(
2398
0
            "SRC_APPROX_ERROR_IN_PIXEL",
2399
0
            CPLSPrintf("%g", psOptions->dfErrorThreshold));
2400
0
    }
2401
2402
0
    if (hDstDS == nullptr)
2403
0
    {
2404
0
        hDstDS = CreateOutput(pszDest, nSrcCount, pahSrcDS, psOptions,
2405
0
                              bInitDestSetByUser, hUniqueTransformArg);
2406
0
        if (!hDstDS)
2407
0
        {
2408
0
            return nullptr;
2409
0
        }
2410
0
#ifdef DEBUG
2411
        // Do not remove this if the #ifdef DEBUG before is still there !
2412
0
        poDstDS = GDALDataset::FromHandle(hDstDS);
2413
0
        CPL_IGNORE_RET_VAL(poDstDS);
2414
0
#endif
2415
0
    }
2416
0
    else
2417
0
    {
2418
0
        if (psOptions->aosWarpOptions.FetchNameValue("SKIP_NOSOURCE") ==
2419
0
            nullptr)
2420
0
        {
2421
0
            CPLDebug("GDALWARP", "Defining SKIP_NOSOURCE=YES");
2422
0
            psOptions->aosWarpOptions.SetNameValue("SKIP_NOSOURCE", "YES");
2423
0
        }
2424
0
    }
2425
2426
    /* -------------------------------------------------------------------- */
2427
    /*      Detect if output has alpha channel.                             */
2428
    /* -------------------------------------------------------------------- */
2429
0
    bool bEnableDstAlpha = psOptions->bEnableDstAlpha;
2430
0
    if (!bEnableDstAlpha && GDALGetRasterCount(hDstDS) &&
2431
0
        GDALGetRasterColorInterpretation(GDALGetRasterBand(
2432
0
            hDstDS, GDALGetRasterCount(hDstDS))) == GCI_AlphaBand &&
2433
0
        !psOptions->bDisableSrcAlpha)
2434
0
    {
2435
0
        if (!psOptions->bQuiet)
2436
0
            printf("Using band %d of destination image as alpha.\n",
2437
0
                   GDALGetRasterCount(hDstDS));
2438
2439
0
        bEnableDstAlpha = true;
2440
0
    }
2441
2442
    /* -------------------------------------------------------------------- */
2443
    /*      Create global progress function.                                */
2444
    /* -------------------------------------------------------------------- */
2445
0
    struct Progress
2446
0
    {
2447
0
        GDALProgressFunc pfnExternalProgress;
2448
0
        void *pExternalProgressData;
2449
0
        int iSrc;
2450
0
        int nSrcCount;
2451
0
        GDALDatasetH *pahSrcDS;
2452
2453
0
        int Do(double dfComplete)
2454
0
        {
2455
0
            CPLString osMsg;
2456
0
            osMsg.Printf("Processing %s [%d/%d]",
2457
0
                         CPLGetFilename(GDALGetDescription(pahSrcDS[iSrc])),
2458
0
                         iSrc + 1, nSrcCount);
2459
0
            return pfnExternalProgress((iSrc + dfComplete) / nSrcCount,
2460
0
                                       osMsg.c_str(), pExternalProgressData);
2461
0
        }
2462
2463
0
        static int CPL_STDCALL ProgressFunc(double dfComplete, const char *,
2464
0
                                            void *pThis)
2465
0
        {
2466
0
            return static_cast<Progress *>(pThis)->Do(dfComplete);
2467
0
        }
2468
0
    };
2469
2470
0
    Progress oProgress;
2471
0
    oProgress.pfnExternalProgress = psOptions->pfnProgress;
2472
0
    oProgress.pExternalProgressData = psOptions->pProgressData;
2473
0
    oProgress.nSrcCount = nSrcCount;
2474
0
    oProgress.pahSrcDS = pahSrcDS;
2475
2476
    /* -------------------------------------------------------------------- */
2477
    /*      Loop over all source files, processing each in turn.            */
2478
    /* -------------------------------------------------------------------- */
2479
0
    bool bHasGotErr = false;
2480
0
    for (int iSrc = 0; iSrc < nSrcCount; iSrc++)
2481
0
    {
2482
0
        GDALDatasetH hSrcDS;
2483
2484
        /* --------------------------------------------------------------------
2485
         */
2486
        /*      Open this file. */
2487
        /* --------------------------------------------------------------------
2488
         */
2489
0
        hSrcDS = pahSrcDS[iSrc];
2490
0
        oProgress.iSrc = iSrc;
2491
2492
        /* --------------------------------------------------------------------
2493
         */
2494
        /*      Check that there's at least one raster band */
2495
        /* --------------------------------------------------------------------
2496
         */
2497
0
        if (GDALGetRasterCount(hSrcDS) == 0)
2498
0
        {
2499
0
            CPLError(CE_Failure, CPLE_AppDefined,
2500
0
                     "Input file %s has no raster bands.",
2501
0
                     GDALGetDescription(hSrcDS));
2502
0
            GDALReleaseDataset(hDstDS);
2503
0
            return nullptr;
2504
0
        }
2505
2506
        /* --------------------------------------------------------------------
2507
         */
2508
        /*      Do we have a source alpha band? */
2509
        /* --------------------------------------------------------------------
2510
         */
2511
0
        bool bEnableSrcAlpha = psOptions->bEnableSrcAlpha;
2512
0
        if (GDALGetRasterColorInterpretation(GDALGetRasterBand(
2513
0
                hSrcDS, GDALGetRasterCount(hSrcDS))) == GCI_AlphaBand &&
2514
0
            !bEnableSrcAlpha && !psOptions->bDisableSrcAlpha)
2515
0
        {
2516
0
            bEnableSrcAlpha = true;
2517
0
            if (!psOptions->bQuiet)
2518
0
                printf("Using band %d of source image as alpha.\n",
2519
0
                       GDALGetRasterCount(hSrcDS));
2520
0
        }
2521
2522
        /* --------------------------------------------------------------------
2523
         */
2524
        /*      Get the metadata of the first source DS and copy it to the */
2525
        /*      destination DS. Copy Band-level metadata and other info, only */
2526
        /*      if source and destination band count are equal. Any values that
2527
         */
2528
        /*      conflict between source datasets are set to pszMDConflictValue.
2529
         */
2530
        /* --------------------------------------------------------------------
2531
         */
2532
0
        ProcessMetadata(iSrc, hSrcDS, hDstDS, psOptions, bEnableDstAlpha,
2533
0
                        bVerticalShiftApplied);
2534
2535
        /* --------------------------------------------------------------------
2536
         */
2537
        /*      Warns if the file has a color table and something more */
2538
        /*      complicated than nearest neighbour resampling is asked */
2539
        /* --------------------------------------------------------------------
2540
         */
2541
2542
0
        if (psOptions->eResampleAlg != GRA_NearestNeighbour &&
2543
0
            psOptions->eResampleAlg != GRA_Mode &&
2544
0
            GDALGetRasterColorTable(GDALGetRasterBand(hSrcDS, 1)) != nullptr)
2545
0
        {
2546
0
            if (!psOptions->bQuiet)
2547
0
                CPLError(
2548
0
                    CE_Warning, CPLE_AppDefined,
2549
0
                    "Input file %s has a color table, which will likely lead "
2550
0
                    "to "
2551
0
                    "bad results when using a resampling method other than "
2552
0
                    "nearest neighbour or mode. Converting the dataset prior "
2553
0
                    "to 24/32 bit "
2554
0
                    "is advised.",
2555
0
                    GDALGetDescription(hSrcDS));
2556
0
        }
2557
2558
        // For RPC warping add a few extra source pixels by default
2559
        // (probably mostly needed in the RPC DEM case)
2560
0
        if (iSrc == 0 &&
2561
0
            (GDALGetMetadata(hSrcDS, GDAL_MDD_RPC) != nullptr &&
2562
0
             (pszMethod == nullptr || EQUAL(pszMethod, GDAL_MDD_RPC))))
2563
0
        {
2564
0
            if (!psOptions->aosWarpOptions.FetchNameValue("SOURCE_EXTRA"))
2565
0
            {
2566
0
                CPLDebug(
2567
0
                    "WARP",
2568
0
                    "Set SOURCE_EXTRA=5 warping options due to RPC warping");
2569
0
                psOptions->aosWarpOptions.SetNameValue("SOURCE_EXTRA", "5");
2570
0
            }
2571
2572
0
            if (!psOptions->aosWarpOptions.FetchNameValue("SAMPLE_STEPS") &&
2573
0
                !psOptions->aosWarpOptions.FetchNameValue("SAMPLE_GRID") &&
2574
0
                psOptions->aosTransformerOptions.FetchNameValue("RPC_DEM"))
2575
0
            {
2576
0
                CPLDebug("WARP", "Set SAMPLE_STEPS=ALL warping options due to "
2577
0
                                 "RPC DEM warping");
2578
0
                psOptions->aosWarpOptions.SetNameValue("SAMPLE_STEPS", "ALL");
2579
0
            }
2580
0
        }
2581
        // Also do the same for GCP TPS warping, e.g. to solve use case of
2582
        // https://github.com/OSGeo/gdal/issues/12736
2583
0
        else if (iSrc == 0 &&
2584
0
                 (GDALGetGCPCount(hSrcDS) > 0 &&
2585
0
                  (pszMethod == nullptr || EQUAL(pszMethod, "TPS"))))
2586
0
        {
2587
0
            if (!psOptions->aosWarpOptions.FetchNameValue("SOURCE_EXTRA"))
2588
0
            {
2589
0
                CPLDebug(
2590
0
                    "WARP",
2591
0
                    "Set SOURCE_EXTRA=5 warping options due to TPS warping");
2592
0
            }
2593
0
        }
2594
2595
0
        if (iSrc > 0)
2596
0
            psOptions->aosWarpOptions.SetNameValue("RESET_DEST_PIXELS",
2597
0
                                                   nullptr);
2598
2599
        /* --------------------------------------------------------------------
2600
         */
2601
        /*      Create a transformation object from the source to */
2602
        /*      destination coordinate system. */
2603
        /* --------------------------------------------------------------------
2604
         */
2605
0
        GDALTransformerArgUniquePtr hTransformArg;
2606
0
        if (hUniqueTransformArg)
2607
0
            hTransformArg = std::move(hUniqueTransformArg);
2608
0
        else
2609
0
        {
2610
0
            hTransformArg.reset(GDALCreateGenImgProjTransformer2(
2611
0
                hSrcDS, hDstDS, psOptions->aosTransformerOptions.List()));
2612
0
            if (hTransformArg == nullptr)
2613
0
            {
2614
0
                GDALReleaseDataset(hDstDS);
2615
0
                return nullptr;
2616
0
            }
2617
0
        }
2618
2619
0
        GDALTransformerFunc pfnTransformer = GDALGenImgProjTransform;
2620
2621
        // Check if transformation is inversible
2622
0
        {
2623
0
            double dfX = GDALGetRasterXSize(hDstDS) / 2.0;
2624
0
            double dfY = GDALGetRasterYSize(hDstDS) / 2.0;
2625
0
            double dfZ = 0;
2626
0
            int bSuccess = false;
2627
0
            const auto nErrorCounterBefore = CPLGetErrorCounter();
2628
0
            pfnTransformer(hTransformArg.get(), TRUE, 1, &dfX, &dfY, &dfZ,
2629
0
                           &bSuccess);
2630
0
            if (!bSuccess && CPLGetErrorCounter() > nErrorCounterBefore &&
2631
0
                strstr(CPLGetLastErrorMsg(), "No inverse operation"))
2632
0
            {
2633
0
                GDALReleaseDataset(hDstDS);
2634
0
                return nullptr;
2635
0
            }
2636
0
        }
2637
2638
        /* --------------------------------------------------------------------
2639
         */
2640
        /*      Determine if we must work with the full-resolution source */
2641
        /*      dataset, or one of its overview level. */
2642
        /* --------------------------------------------------------------------
2643
         */
2644
0
        GDALDataset *poSrcDS = static_cast<GDALDataset *>(hSrcDS);
2645
0
        GDALDataset *poSrcOvrDS = nullptr;
2646
0
        int nOvCount = poSrcDS->GetRasterBand(1)->GetOverviewCount();
2647
0
        if (psOptions->nOvLevel <= OVR_LEVEL_AUTO && nOvCount > 0)
2648
0
        {
2649
0
            double dfTargetRatio = 0;
2650
0
            double dfTargetRatioX = 0;
2651
0
            double dfTargetRatioY = 0;
2652
2653
0
            if (bFigureoutCorrespondingWindow)
2654
0
            {
2655
                // If the user has explicitly set the target bounds and
2656
                // resolution, or we're updating an existing file, then figure
2657
                // out which source window corresponds to the target raster.
2658
0
                constexpr int nPointsOneDim = 10;
2659
0
                constexpr int nPoints = nPointsOneDim * nPointsOneDim;
2660
0
                std::vector<double> adfX(nPoints);
2661
0
                std::vector<double> adfY(nPoints);
2662
0
                std::vector<double> adfZ(nPoints);
2663
0
                const int nDstXSize = GDALGetRasterXSize(hDstDS);
2664
0
                const int nDstYSize = GDALGetRasterYSize(hDstDS);
2665
0
                int iPoint = 0;
2666
0
                for (int iX = 0; iX < nPointsOneDim; ++iX)
2667
0
                {
2668
0
                    for (int iY = 0; iY < nPointsOneDim; ++iY)
2669
0
                    {
2670
0
                        adfX[iPoint] = nDstXSize * static_cast<double>(iX) /
2671
0
                                       (nPointsOneDim - 1);
2672
0
                        adfY[iPoint] = nDstYSize * static_cast<double>(iY) /
2673
0
                                       (nPointsOneDim - 1);
2674
0
                        iPoint++;
2675
0
                    }
2676
0
                }
2677
0
                std::vector<int> abSuccess(nPoints);
2678
0
                pfnTransformer(hTransformArg.get(), TRUE, nPoints, &adfX[0],
2679
0
                               &adfY[0], &adfZ[0], &abSuccess[0]);
2680
2681
0
                double dfMinSrcX = std::numeric_limits<double>::infinity();
2682
0
                double dfMaxSrcX = -std::numeric_limits<double>::infinity();
2683
0
                double dfMinSrcY = std::numeric_limits<double>::infinity();
2684
0
                double dfMaxSrcY = -std::numeric_limits<double>::infinity();
2685
0
                for (int i = 0; i < nPoints; i++)
2686
0
                {
2687
0
                    if (abSuccess[i])
2688
0
                    {
2689
0
                        dfMinSrcX = std::min(dfMinSrcX, adfX[i]);
2690
0
                        dfMaxSrcX = std::max(dfMaxSrcX, adfX[i]);
2691
0
                        dfMinSrcY = std::min(dfMinSrcY, adfY[i]);
2692
0
                        dfMaxSrcY = std::max(dfMaxSrcY, adfY[i]);
2693
0
                    }
2694
0
                }
2695
0
                if (dfMaxSrcX > dfMinSrcX)
2696
0
                {
2697
0
                    dfTargetRatioX =
2698
0
                        (dfMaxSrcX - dfMinSrcX) / GDALGetRasterXSize(hDstDS);
2699
0
                }
2700
0
                if (dfMaxSrcY > dfMinSrcY)
2701
0
                {
2702
0
                    dfTargetRatioY =
2703
0
                        (dfMaxSrcY - dfMinSrcY) / GDALGetRasterYSize(hDstDS);
2704
0
                }
2705
                // take the minimum of these ratios #7019
2706
0
                dfTargetRatio = std::min(dfTargetRatioX, dfTargetRatioY);
2707
0
            }
2708
0
            else
2709
0
            {
2710
                /* Compute what the "natural" output resolution (in pixels)
2711
                 * would be for this */
2712
                /* input dataset */
2713
0
                double adfSuggestedGeoTransform[6];
2714
0
                int nPixels, nLines;
2715
0
                if (GDALSuggestedWarpOutput(
2716
0
                        hSrcDS, pfnTransformer, hTransformArg.get(),
2717
0
                        adfSuggestedGeoTransform, &nPixels, &nLines) == CE_None)
2718
0
                {
2719
0
                    dfTargetRatio = 1.0 / adfSuggestedGeoTransform[1];
2720
0
                }
2721
0
            }
2722
2723
0
            if (dfTargetRatio > 1.0)
2724
0
            {
2725
0
                const char *pszOversampligThreshold = CPLGetConfigOption(
2726
0
                    "GDALWARP_OVERSAMPLING_THRESHOLD", nullptr);
2727
0
                const double dfOversamplingThreshold =
2728
0
                    pszOversampligThreshold ? CPLAtof(pszOversampligThreshold)
2729
0
                                            : 1.0;
2730
2731
0
                const int iBestOvr = GDALBandGetBestOverviewLevel(
2732
0
                    poSrcDS->GetRasterBand(1), dfTargetRatio,
2733
0
                    dfOversamplingThreshold);
2734
0
                const int iOvr =
2735
0
                    iBestOvr + (psOptions->nOvLevel - OVR_LEVEL_AUTO);
2736
0
                if (iOvr >= 0)
2737
0
                {
2738
0
                    CPLDebug("WARP", "Selecting overview level %d for %s", iOvr,
2739
0
                             GDALGetDescription(hSrcDS));
2740
0
                    poSrcOvrDS =
2741
0
                        GDALCreateOverviewDataset(poSrcDS, iOvr,
2742
0
                                                  /* bThisLevelOnly = */ false);
2743
0
                }
2744
0
            }
2745
0
        }
2746
0
        else if (psOptions->nOvLevel >= 0)
2747
0
        {
2748
0
            poSrcOvrDS = GDALCreateOverviewDataset(poSrcDS, psOptions->nOvLevel,
2749
0
                                                   /* bThisLevelOnly = */ true);
2750
0
            if (poSrcOvrDS == nullptr)
2751
0
            {
2752
0
                if (!psOptions->bQuiet)
2753
0
                {
2754
0
                    CPLError(CE_Warning, CPLE_AppDefined,
2755
0
                             "cannot get overview level %d for "
2756
0
                             "dataset %s. Defaulting to level %d",
2757
0
                             psOptions->nOvLevel, GDALGetDescription(hSrcDS),
2758
0
                             nOvCount - 1);
2759
0
                }
2760
0
                if (nOvCount > 0)
2761
0
                    poSrcOvrDS =
2762
0
                        GDALCreateOverviewDataset(poSrcDS, nOvCount - 1,
2763
0
                                                  /* bThisLevelOnly = */ false);
2764
0
            }
2765
0
            else
2766
0
            {
2767
0
                CPLDebug("WARP", "Selecting overview level %d for %s",
2768
0
                         psOptions->nOvLevel, GDALGetDescription(hSrcDS));
2769
0
            }
2770
0
        }
2771
2772
0
        if (poSrcOvrDS == nullptr)
2773
0
            GDALReferenceDataset(hSrcDS);
2774
2775
0
        GDALDatasetH hWrkSrcDS =
2776
0
            poSrcOvrDS ? static_cast<GDALDatasetH>(poSrcOvrDS) : hSrcDS;
2777
2778
        /* --------------------------------------------------------------------
2779
         */
2780
        /*      Clear temporary INIT_DEST settings after the first image. */
2781
        /* --------------------------------------------------------------------
2782
         */
2783
0
        if (psOptions->bCreateOutput && iSrc == 1)
2784
0
            psOptions->aosWarpOptions.SetNameValue("INIT_DEST", nullptr);
2785
2786
        /* --------------------------------------------------------------------
2787
         */
2788
        /*      Define SKIP_NOSOURCE after the first image (since
2789
         * initialization*/
2790
        /*      has already be done). */
2791
        /* --------------------------------------------------------------------
2792
         */
2793
0
        if (iSrc == 1 && psOptions->aosWarpOptions.FetchNameValue(
2794
0
                             "SKIP_NOSOURCE") == nullptr)
2795
0
        {
2796
0
            CPLDebug("GDALWARP", "Defining SKIP_NOSOURCE=YES");
2797
0
            psOptions->aosWarpOptions.SetNameValue("SKIP_NOSOURCE", "YES");
2798
0
        }
2799
2800
        /* --------------------------------------------------------------------
2801
         */
2802
        /*      Setup warp options. */
2803
        /* --------------------------------------------------------------------
2804
         */
2805
0
        std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)>
2806
0
            psWO(GDALCreateWarpOptions(), GDALDestroyWarpOptions);
2807
2808
0
        psWO->papszWarpOptions = CSLDuplicate(psOptions->aosWarpOptions.List());
2809
0
        psWO->eWorkingDataType = psOptions->eWorkingType;
2810
2811
0
        psWO->eResampleAlg = psOptions->eResampleAlg;
2812
2813
0
        psWO->hSrcDS = hWrkSrcDS;
2814
0
        psWO->hDstDS = hDstDS;
2815
2816
0
        if (!bVRT)
2817
0
        {
2818
0
            if (psOptions->pfnProgress == GDALDummyProgress)
2819
0
            {
2820
0
                psWO->pfnProgress = GDALDummyProgress;
2821
0
                psWO->pProgressArg = nullptr;
2822
0
            }
2823
0
            else
2824
0
            {
2825
0
                psWO->pfnProgress = Progress::ProgressFunc;
2826
0
                psWO->pProgressArg = &oProgress;
2827
0
            }
2828
0
        }
2829
2830
0
        if (psOptions->dfWarpMemoryLimit != 0.0)
2831
0
            psWO->dfWarpMemoryLimit = psOptions->dfWarpMemoryLimit;
2832
2833
        /* --------------------------------------------------------------------
2834
         */
2835
        /*      Setup band mapping. */
2836
        /* --------------------------------------------------------------------
2837
         */
2838
0
        if (psOptions->anSrcBands.empty())
2839
0
        {
2840
0
            if (bEnableSrcAlpha)
2841
0
                psWO->nBandCount = GDALGetRasterCount(hWrkSrcDS) - 1;
2842
0
            else
2843
0
                psWO->nBandCount = GDALGetRasterCount(hWrkSrcDS);
2844
0
        }
2845
0
        else
2846
0
        {
2847
0
            psWO->nBandCount = static_cast<int>(psOptions->anSrcBands.size());
2848
0
        }
2849
2850
0
        const int nNeededDstBands =
2851
0
            psWO->nBandCount + (bEnableDstAlpha ? 1 : 0);
2852
0
        if (nNeededDstBands > GDALGetRasterCount(hDstDS))
2853
0
        {
2854
0
            CPLError(CE_Failure, CPLE_AppDefined,
2855
0
                     "Destination dataset has %d bands, but at least %d "
2856
0
                     "are needed",
2857
0
                     GDALGetRasterCount(hDstDS), nNeededDstBands);
2858
0
            GDALReleaseDataset(hWrkSrcDS);
2859
0
            GDALReleaseDataset(hDstDS);
2860
0
            return nullptr;
2861
0
        }
2862
2863
0
        psWO->panSrcBands =
2864
0
            static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
2865
0
        psWO->panDstBands =
2866
0
            static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
2867
0
        if (psOptions->anSrcBands.empty())
2868
0
        {
2869
0
            for (int i = 0; i < psWO->nBandCount; i++)
2870
0
            {
2871
0
                psWO->panSrcBands[i] = i + 1;
2872
0
                psWO->panDstBands[i] = i + 1;
2873
0
            }
2874
0
        }
2875
0
        else
2876
0
        {
2877
0
            for (int i = 0; i < psWO->nBandCount; i++)
2878
0
            {
2879
0
                if (psOptions->anSrcBands[i] <= 0 ||
2880
0
                    psOptions->anSrcBands[i] > GDALGetRasterCount(hSrcDS))
2881
0
                {
2882
0
                    CPLError(CE_Failure, CPLE_AppDefined,
2883
0
                             "-srcband[%d] = %d is invalid", i,
2884
0
                             psOptions->anSrcBands[i]);
2885
0
                    GDALReleaseDataset(hWrkSrcDS);
2886
0
                    GDALReleaseDataset(hDstDS);
2887
0
                    return nullptr;
2888
0
                }
2889
0
                if (psOptions->anDstBands[i] <= 0 ||
2890
0
                    psOptions->anDstBands[i] > GDALGetRasterCount(hDstDS))
2891
0
                {
2892
0
                    CPLError(CE_Failure, CPLE_AppDefined,
2893
0
                             "-dstband[%d] = %d is invalid", i,
2894
0
                             psOptions->anDstBands[i]);
2895
0
                    GDALReleaseDataset(hWrkSrcDS);
2896
0
                    GDALReleaseDataset(hDstDS);
2897
0
                    return nullptr;
2898
0
                }
2899
0
                psWO->panSrcBands[i] = psOptions->anSrcBands[i];
2900
0
                psWO->panDstBands[i] = psOptions->anDstBands[i];
2901
0
            }
2902
0
        }
2903
2904
        /* --------------------------------------------------------------------
2905
         */
2906
        /*      Setup alpha bands used if any. */
2907
        /* --------------------------------------------------------------------
2908
         */
2909
0
        if (bEnableSrcAlpha)
2910
0
            psWO->nSrcAlphaBand = GDALGetRasterCount(hWrkSrcDS);
2911
2912
0
        if (bEnableDstAlpha)
2913
0
        {
2914
0
            if (psOptions->anSrcBands.empty())
2915
0
                psWO->nDstAlphaBand = GDALGetRasterCount(hDstDS);
2916
0
            else
2917
0
                psWO->nDstAlphaBand =
2918
0
                    static_cast<int>(psOptions->anDstBands.size()) + 1;
2919
0
        }
2920
2921
        /* ------------------------------------------------------------------ */
2922
        /*      Setup NODATA options.                                         */
2923
        /* ------------------------------------------------------------------ */
2924
0
        if (SetupNoData(pszDest, iSrc, hSrcDS, hWrkSrcDS, hDstDS, psWO.get(),
2925
0
                        psOptions, bEnableDstAlpha,
2926
0
                        bInitDestSetByUser) != CE_None)
2927
0
        {
2928
0
            GDALReleaseDataset(hWrkSrcDS);
2929
0
            GDALReleaseDataset(hDstDS);
2930
0
            return nullptr;
2931
0
        }
2932
2933
0
        oProgress.Do(0);
2934
2935
        /* --------------------------------------------------------------------
2936
         */
2937
        /*      For the first source image of a newly created dataset, decide */
2938
        /*      if we can safely enable SKIP_NOSOURCE optimization. */
2939
        /* --------------------------------------------------------------------
2940
         */
2941
0
        SetupSkipNoSource(iSrc, hDstDS, psWO.get(), psOptions);
2942
2943
        /* --------------------------------------------------------------------
2944
         */
2945
        /*      In some cases, RPC evaluation can find valid input pixel for */
2946
        /*      output pixels that are outside the footprint of the source */
2947
        /*      dataset, so limit the area we update in the target dataset from
2948
         */
2949
        /*      the suggested warp output (only in cases where
2950
         * SKIP_NOSOURCE=YES) */
2951
        /* --------------------------------------------------------------------
2952
         */
2953
0
        int nWarpDstXOff = 0;
2954
0
        int nWarpDstYOff = 0;
2955
0
        int nWarpDstXSize = GDALGetRasterXSize(hDstDS);
2956
0
        int nWarpDstYSize = GDALGetRasterYSize(hDstDS);
2957
2958
0
        if (!AdjustOutputExtentForRPC(hSrcDS, hDstDS, pfnTransformer,
2959
0
                                      hTransformArg.get(), psWO.get(),
2960
0
                                      psOptions, nWarpDstXOff, nWarpDstYOff,
2961
0
                                      nWarpDstXSize, nWarpDstYSize))
2962
0
        {
2963
0
            GDALReleaseDataset(hWrkSrcDS);
2964
0
            continue;
2965
0
        }
2966
2967
        /* We need to recreate the transform when operating on an overview */
2968
0
        if (poSrcOvrDS != nullptr)
2969
0
        {
2970
0
            hTransformArg.reset(GDALCreateGenImgProjTransformer2(
2971
0
                hWrkSrcDS, hDstDS, psOptions->aosTransformerOptions.List()));
2972
0
        }
2973
2974
0
        bool bUseApproxTransformer = psOptions->dfErrorThreshold != 0.0;
2975
2976
0
        if (!psOptions->bNoVShift)
2977
0
        {
2978
            // Can modify psWO->papszWarpOptions
2979
0
            if (ApplyVerticalShift(hWrkSrcDS, psOptions, psWO.get()))
2980
0
            {
2981
0
                bUseApproxTransformer = false;
2982
0
            }
2983
0
        }
2984
2985
        /* --------------------------------------------------------------------
2986
         */
2987
        /*      Warp the transformer with a linear approximator unless the */
2988
        /*      acceptable error is zero. */
2989
        /* --------------------------------------------------------------------
2990
         */
2991
0
        if (bUseApproxTransformer)
2992
0
        {
2993
0
            hTransformArg.reset(GDALCreateApproxTransformer(
2994
0
                GDALGenImgProjTransform, hTransformArg.release(),
2995
0
                psOptions->dfErrorThreshold));
2996
0
            pfnTransformer = GDALApproxTransform;
2997
0
            GDALApproxTransformerOwnsSubtransformer(hTransformArg.get(), TRUE);
2998
0
        }
2999
3000
        /* --------------------------------------------------------------------
3001
         */
3002
        /*      If we have a cutline, transform it into the source */
3003
        /*      pixel/line coordinate system and insert into warp options. */
3004
        /* --------------------------------------------------------------------
3005
         */
3006
0
        if (poCutline)
3007
0
        {
3008
0
            CPLErr eError;
3009
0
            eError = TransformCutlineToSource(
3010
0
                GDALDataset::FromHandle(hWrkSrcDS), poCutline.get(),
3011
0
                &(psWO->papszWarpOptions),
3012
0
                psOptions->aosTransformerOptions.List());
3013
0
            if (eError == CE_Failure)
3014
0
            {
3015
0
                GDALReleaseDataset(hWrkSrcDS);
3016
0
                GDALReleaseDataset(hDstDS);
3017
0
                return nullptr;
3018
0
            }
3019
0
        }
3020
3021
        /* --------------------------------------------------------------------
3022
         */
3023
        /*      If we are producing VRT output, then just initialize it with */
3024
        /*      the warp options and write out now rather than proceeding */
3025
        /*      with the operations. */
3026
        /* --------------------------------------------------------------------
3027
         */
3028
0
        if (bVRT)
3029
0
        {
3030
0
            GDALSetMetadataItem(hDstDS, "SrcOvrLevel",
3031
0
                                CPLSPrintf("%d", psOptions->nOvLevel), nullptr);
3032
3033
            // In case of success, hDstDS has become the owner of hTransformArg
3034
            // so we need to release it
3035
0
            psWO->pfnTransformer = pfnTransformer;
3036
0
            psWO->pTransformerArg = hTransformArg.release();
3037
0
            CPLErr eErr = GDALInitializeWarpedVRT(hDstDS, psWO.get());
3038
0
            if (eErr != CE_None)
3039
0
            {
3040
                // In case of error, reacquire psWO->pTransformerArg
3041
0
                hTransformArg.reset(psWO->pTransformerArg);
3042
0
            }
3043
0
            GDALReleaseDataset(hWrkSrcDS);
3044
0
            if (eErr != CE_None)
3045
0
            {
3046
0
                GDALReleaseDataset(hDstDS);
3047
0
                return nullptr;
3048
0
            }
3049
3050
0
            if (!EQUAL(pszDest, ""))
3051
0
            {
3052
0
                const bool bWasFailureBefore =
3053
0
                    (CPLGetLastErrorType() == CE_Failure);
3054
0
                GDALFlushCache(hDstDS);
3055
0
                if (!bWasFailureBefore && CPLGetLastErrorType() == CE_Failure)
3056
0
                {
3057
0
                    GDALReleaseDataset(hDstDS);
3058
0
                    hDstDS = nullptr;
3059
0
                }
3060
0
            }
3061
3062
0
            if (hDstDS)
3063
0
                oProgress.Do(1);
3064
3065
0
            return hDstDS;
3066
0
        }
3067
3068
        /* --------------------------------------------------------------------
3069
         */
3070
        /*      Initialize and execute the warp. */
3071
        /* --------------------------------------------------------------------
3072
         */
3073
0
        GDALWarpOperation oWO;
3074
3075
0
        if (oWO.Initialize(psWO.get(), pfnTransformer,
3076
0
                           std::move(hTransformArg)) == CE_None)
3077
0
        {
3078
0
            CPLErr eErr;
3079
0
            if (psOptions->bMulti)
3080
0
                eErr = oWO.ChunkAndWarpMulti(nWarpDstXOff, nWarpDstYOff,
3081
0
                                             nWarpDstXSize, nWarpDstYSize);
3082
0
            else
3083
0
                eErr = oWO.ChunkAndWarpImage(nWarpDstXOff, nWarpDstYOff,
3084
0
                                             nWarpDstXSize, nWarpDstYSize);
3085
0
            if (eErr != CE_None)
3086
0
                bHasGotErr = true;
3087
0
        }
3088
0
        else
3089
0
        {
3090
0
            bHasGotErr = true;
3091
0
        }
3092
3093
        /* --------------------------------------------------------------------
3094
         */
3095
        /*      Cleanup */
3096
        /* --------------------------------------------------------------------
3097
         */
3098
0
        GDALReleaseDataset(hWrkSrcDS);
3099
0
    }
3100
3101
    /* -------------------------------------------------------------------- */
3102
    /*      Final Cleanup.                                                  */
3103
    /* -------------------------------------------------------------------- */
3104
0
    const bool bWasFailureBefore = (CPLGetLastErrorType() == CE_Failure);
3105
0
    GDALFlushCache(hDstDS);
3106
0
    if (!bWasFailureBefore && CPLGetLastErrorType() == CE_Failure)
3107
0
    {
3108
0
        bHasGotErr = true;
3109
0
    }
3110
3111
0
    if (bHasGotErr || bDropDstDSRef)
3112
0
        GDALReleaseDataset(hDstDS);
3113
3114
0
#ifdef DEBUG
3115
0
    if (!bHasGotErr || bDropDstDSRef)
3116
0
    {
3117
0
        CPLAssert(poDstDS->GetRefCount() == nExpectedRefCountAtEnd);
3118
0
    }
3119
0
#endif
3120
3121
0
    return bHasGotErr ? nullptr : hDstDS;
3122
0
}
3123
3124
/************************************************************************/
3125
/*                          ValidateCutline()                           */
3126
/*  Same as OGR_G_IsValid() except that it processes polygon per polygon*/
3127
/*  without paying attention to MultiPolygon specific validity rules.   */
3128
/************************************************************************/
3129
3130
static bool ValidateCutline(const OGRGeometry *poGeom, bool bVerbose)
3131
0
{
3132
0
    const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
3133
0
    if (eType == wkbMultiPolygon)
3134
0
    {
3135
0
        for (const auto *poSubGeom : *(poGeom->toMultiPolygon()))
3136
0
        {
3137
0
            if (!ValidateCutline(poSubGeom, bVerbose))
3138
0
                return false;
3139
0
        }
3140
0
    }
3141
0
    else if (eType == wkbPolygon)
3142
0
    {
3143
0
        std::string osReason;
3144
0
        if (OGRGeometryFactory::haveGEOS() && !poGeom->IsValid(&osReason))
3145
0
        {
3146
0
            if (!bVerbose)
3147
0
                return false;
3148
3149
0
            char *pszWKT = nullptr;
3150
0
            poGeom->exportToWkt(&pszWKT);
3151
0
            CPLDebug("GDALWARP", "WKT = \"%s\"", pszWKT ? pszWKT : "(null)");
3152
0
            const char *pszFile =
3153
0
                CPLGetConfigOption("GDALWARP_DUMP_WKT_TO_FILE", nullptr);
3154
0
            if (pszFile && pszWKT)
3155
0
            {
3156
0
                FILE *f =
3157
0
                    EQUAL(pszFile, "stderr") ? stderr : fopen(pszFile, "wb");
3158
0
                if (f)
3159
0
                {
3160
0
                    fprintf(f, "id,WKT\n");
3161
0
                    fprintf(f, "1,\"%s\"\n", pszWKT);
3162
0
                    if (!EQUAL(pszFile, "stderr"))
3163
0
                        fclose(f);
3164
0
                }
3165
0
            }
3166
0
            CPLFree(pszWKT);
3167
3168
0
            if (CPLTestBool(
3169
0
                    CPLGetConfigOption("GDALWARP_IGNORE_BAD_CUTLINE", "NO")))
3170
0
            {
3171
0
                CPLError(CE_Warning, CPLE_AppDefined,
3172
0
                         "Cutline polygon is invalid: %s.", osReason.c_str());
3173
0
            }
3174
0
            else
3175
0
            {
3176
0
                CPLError(CE_Failure, CPLE_AppDefined,
3177
0
                         "Cutline polygon is invalid: %s.", osReason.c_str());
3178
0
                return false;
3179
0
            }
3180
0
        }
3181
0
    }
3182
0
    else
3183
0
    {
3184
0
        if (bVerbose)
3185
0
        {
3186
0
            CPLError(CE_Failure, CPLE_AppDefined,
3187
0
                     "Cutline not of polygon type.");
3188
0
        }
3189
0
        return false;
3190
0
    }
3191
3192
0
    return true;
3193
0
}
3194
3195
/************************************************************************/
3196
/*                            LoadCutline()                             */
3197
/*                                                                      */
3198
/*      Load blend cutline from OGR datasource.                         */
3199
/************************************************************************/
3200
3201
static CPLErr LoadCutline(const std::string &osCutlineDSNameOrWKT,
3202
                          const std::string &osSRS, const std::string &osCLayer,
3203
                          const std::string &osCWHERE,
3204
                          const std::string &osCSQL, OGRGeometryH *phCutlineRet)
3205
3206
0
{
3207
0
    if (STARTS_WITH_CI(osCutlineDSNameOrWKT.c_str(), "POLYGON(") ||
3208
0
        STARTS_WITH_CI(osCutlineDSNameOrWKT.c_str(), "POLYGON (") ||
3209
0
        STARTS_WITH_CI(osCutlineDSNameOrWKT.c_str(), "MULTIPOLYGON(") ||
3210
0
        STARTS_WITH_CI(osCutlineDSNameOrWKT.c_str(), "MULTIPOLYGON ("))
3211
0
    {
3212
0
        OGRSpatialReferenceRefCountedPtr poSRS;
3213
0
        if (!osSRS.empty())
3214
0
        {
3215
0
            poSRS = OGRSpatialReferenceRefCountedPtr::makeInstance();
3216
0
            poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3217
0
            poSRS->SetFromUserInput(osSRS.c_str());
3218
0
        }
3219
3220
0
        auto [poGeom, _] = OGRGeometryFactory::createFromWkt(
3221
0
            osCutlineDSNameOrWKT.c_str(), poSRS.get());
3222
0
        *phCutlineRet = OGRGeometry::ToHandle(poGeom.release());
3223
0
        return *phCutlineRet ? CE_None : CE_Failure;
3224
0
    }
3225
3226
    /* -------------------------------------------------------------------- */
3227
    /*      Open source vector dataset.                                     */
3228
    /* -------------------------------------------------------------------- */
3229
0
    auto poDS = std::unique_ptr<GDALDataset>(
3230
0
        GDALDataset::Open(osCutlineDSNameOrWKT.c_str(), GDAL_OF_VECTOR));
3231
0
    if (poDS == nullptr)
3232
0
    {
3233
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s.",
3234
0
                 osCutlineDSNameOrWKT.c_str());
3235
0
        return CE_Failure;
3236
0
    }
3237
3238
    /* -------------------------------------------------------------------- */
3239
    /*      Get the source layer                                            */
3240
    /* -------------------------------------------------------------------- */
3241
0
    OGRLayer *poLayer = nullptr;
3242
3243
0
    if (!osCSQL.empty())
3244
0
        poLayer = poDS->ExecuteSQL(osCSQL.c_str(), nullptr, nullptr);
3245
0
    else if (!osCLayer.empty())
3246
0
        poLayer = poDS->GetLayerByName(osCLayer.c_str());
3247
0
    else
3248
0
        poLayer = poDS->GetLayer(0);
3249
3250
0
    if (poLayer == nullptr)
3251
0
    {
3252
0
        CPLError(CE_Failure, CPLE_AppDefined,
3253
0
                 "Failed to identify source layer from datasource.");
3254
0
        return CE_Failure;
3255
0
    }
3256
3257
    /* -------------------------------------------------------------------- */
3258
    /*      Apply WHERE clause if there is one.                             */
3259
    /* -------------------------------------------------------------------- */
3260
0
    if (!osCWHERE.empty())
3261
0
        poLayer->SetAttributeFilter(osCWHERE.c_str());
3262
3263
    /* -------------------------------------------------------------------- */
3264
    /*      Collect the geometries from this layer, and build list of       */
3265
    /*      burn values.                                                    */
3266
    /* -------------------------------------------------------------------- */
3267
0
    auto poMultiPolygon = std::make_unique<OGRMultiPolygon>();
3268
3269
0
    for (auto &&poFeature : poLayer)
3270
0
    {
3271
0
        auto poGeom = std::unique_ptr<OGRGeometry>(poFeature->StealGeometry());
3272
0
        if (poGeom == nullptr)
3273
0
        {
3274
0
            CPLError(CE_Failure, CPLE_AppDefined,
3275
0
                     "Cutline feature without a geometry.");
3276
0
            goto error;
3277
0
        }
3278
3279
0
        if (!ValidateCutline(poGeom.get(), true))
3280
0
        {
3281
0
            goto error;
3282
0
        }
3283
3284
0
        OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
3285
3286
0
        if (eType == wkbPolygon)
3287
0
            poMultiPolygon->addGeometry(std::move(poGeom));
3288
0
        else if (eType == wkbMultiPolygon)
3289
0
        {
3290
0
            for (const auto *poSubGeom : poGeom->toMultiPolygon())
3291
0
            {
3292
0
                poMultiPolygon->addGeometry(poSubGeom);
3293
0
            }
3294
0
        }
3295
0
    }
3296
3297
0
    if (poMultiPolygon->IsEmpty())
3298
0
    {
3299
0
        CPLError(CE_Failure, CPLE_AppDefined,
3300
0
                 "Did not get any cutline features.");
3301
0
        goto error;
3302
0
    }
3303
3304
    /* -------------------------------------------------------------------- */
3305
    /*      Ensure the coordinate system gets set on the geometry.          */
3306
    /* -------------------------------------------------------------------- */
3307
0
    if (!osSRS.empty())
3308
0
    {
3309
0
        auto poSRS = OGRSpatialReferenceRefCountedPtr::makeInstance();
3310
0
        poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3311
0
        poSRS->SetFromUserInput(osSRS.c_str());
3312
0
        poMultiPolygon->assignSpatialReference(poSRS.get());
3313
0
    }
3314
0
    else
3315
0
    {
3316
0
        poMultiPolygon->assignSpatialReference(poLayer->GetSpatialRef());
3317
0
    }
3318
3319
0
    *phCutlineRet = OGRGeometry::ToHandle(poMultiPolygon.release());
3320
3321
    /* -------------------------------------------------------------------- */
3322
    /*      Cleanup                                                         */
3323
    /* -------------------------------------------------------------------- */
3324
0
    if (!osCSQL.empty())
3325
0
        poDS->ReleaseResultSet(poLayer);
3326
3327
0
    return CE_None;
3328
3329
0
error:
3330
0
    if (!osCSQL.empty())
3331
0
        poDS->ReleaseResultSet(poLayer);
3332
3333
0
    return CE_Failure;
3334
0
}
3335
3336
/************************************************************************/
3337
/*                        GDALWarpCreateOutput()                        */
3338
/*                                                                      */
3339
/*      Create the output file based on various command line options,   */
3340
/*      and the input file.                                             */
3341
/*      If there's just one source file, then hUniqueTransformArg will  */
3342
/*      be set in order them to be reused by main function. This saves  */
3343
/*      transform recomputation, which can be expensive in the -tps case*/
3344
/************************************************************************/
3345
3346
static GDALDatasetH GDALWarpCreateOutput(
3347
    int nSrcCount, GDALDatasetH *pahSrcDS, const char *pszFilename,
3348
    const char *pszFormat, char **papszTO, CSLConstList papszCreateOptions,
3349
    GDALDataType eDT, GDALTransformerArgUniquePtr &hUniqueTransformArg,
3350
    bool bSetColorInterpretation, GDALWarpAppOptions *psOptions,
3351
    bool bUpdateTransformerWithDestGT)
3352
3353
0
{
3354
0
    GDALDriverH hDriver;
3355
0
    GDALDatasetH hDstDS;
3356
0
    GDALRasterAttributeTableH hRAT = nullptr;
3357
0
    double dfWrkMinX = 0, dfWrkMaxX = 0, dfWrkMinY = 0, dfWrkMaxY = 0;
3358
0
    double dfWrkResX = 0, dfWrkResY = 0;
3359
0
    int nDstBandCount = 0;
3360
0
    std::vector<GDALColorInterp> apeColorInterpretations;
3361
0
    bool bVRT = false;
3362
3363
0
    if (EQUAL(pszFormat, "VRT"))
3364
0
        bVRT = true;
3365
3366
    // Special case for geographic to Mercator (typically EPSG:4326 to EPSG:3857)
3367
    // where latitudes close to 90 go to infinity
3368
    // We clamp latitudes between ~ -85 and ~ 85 degrees.
3369
0
    const char *pszDstSRS = CSLFetchNameValue(papszTO, "DST_SRS");
3370
0
    if (nSrcCount == 1 && pszDstSRS && psOptions->dfMinX == 0.0 &&
3371
0
        psOptions->dfMinY == 0.0 && psOptions->dfMaxX == 0.0 &&
3372
0
        psOptions->dfMaxY == 0.0)
3373
0
    {
3374
0
        auto hSrcDS = pahSrcDS[0];
3375
0
        const auto osSrcSRS = GetSrcDSProjection(pahSrcDS[0], papszTO);
3376
0
        OGRSpatialReference oSrcSRS;
3377
0
        oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3378
0
        oSrcSRS.SetFromUserInput(osSrcSRS.c_str());
3379
0
        OGRSpatialReference oDstSRS;
3380
0
        oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3381
0
        oDstSRS.SetFromUserInput(pszDstSRS);
3382
0
        const char *pszProjection = oDstSRS.GetAttrValue("PROJECTION");
3383
0
        const char *pszMethod = FetchSrcMethod(papszTO);
3384
0
        double adfSrcGT[6] = {0};
3385
        // This MAX_LAT values is equivalent to the semi_major_axis * PI
3386
        // easting/northing value only for EPSG:3857, but it is also quite
3387
        // reasonable for other Mercator projections
3388
0
        constexpr double MAX_LAT = 85.0511287798066;
3389
0
        constexpr double EPS = 1e-3;
3390
0
        const auto GetMinLon = [&adfSrcGT]() { return adfSrcGT[0]; };
3391
0
        const auto GetMaxLon = [&adfSrcGT, hSrcDS]()
3392
0
        { return adfSrcGT[0] + adfSrcGT[1] * GDALGetRasterXSize(hSrcDS); };
3393
0
        const auto GetMinLat = [&adfSrcGT, hSrcDS]()
3394
0
        { return adfSrcGT[3] + adfSrcGT[5] * GDALGetRasterYSize(hSrcDS); };
3395
0
        const auto GetMaxLat = [&adfSrcGT]() { return adfSrcGT[3]; };
3396
0
        if (oSrcSRS.IsGeographic() && !oSrcSRS.IsDerivedGeographic() &&
3397
0
            pszProjection && EQUAL(pszProjection, SRS_PT_MERCATOR_1SP) &&
3398
0
            oDstSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0) == 0 &&
3399
0
            (pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")) &&
3400
0
            CSLFetchNameValue(papszTO, "COORDINATE_OPERATION") == nullptr &&
3401
0
            CSLFetchNameValue(papszTO, "SRC_METHOD") == nullptr &&
3402
0
            CSLFetchNameValue(papszTO, "DST_METHOD") == nullptr &&
3403
0
            GDALGetGeoTransform(hSrcDS, adfSrcGT) == CE_None &&
3404
0
            adfSrcGT[2] == 0 && adfSrcGT[4] == 0 && adfSrcGT[5] < 0 &&
3405
0
            GetMinLon() >= -180 - EPS && GetMaxLon() <= 180 + EPS &&
3406
0
            ((GetMaxLat() > MAX_LAT && GetMinLat() < MAX_LAT) ||
3407
0
             (GetMaxLat() > -MAX_LAT && GetMinLat() < -MAX_LAT)) &&
3408
0
            GDALGetMetadata(hSrcDS, "GEOLOC_ARRAY") == nullptr &&
3409
0
            GDALGetMetadata(hSrcDS, GDAL_MDD_RPC) == nullptr)
3410
0
        {
3411
0
            auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
3412
0
                OGRCreateCoordinateTransformation(&oSrcSRS, &oDstSRS));
3413
0
            if (poCT)
3414
0
            {
3415
0
                double xLL = std::max(GetMinLon(), -180.0);
3416
0
                double yLL = std::max(GetMinLat(), -MAX_LAT);
3417
0
                double xUR = std::min(GetMaxLon(), 180.0);
3418
0
                double yUR = std::min(GetMaxLat(), MAX_LAT);
3419
0
                if (poCT->Transform(1, &xLL, &yLL) &&
3420
0
                    poCT->Transform(1, &xUR, &yUR))
3421
0
                {
3422
0
                    psOptions->dfMinX = xLL;
3423
0
                    psOptions->dfMinY = yLL;
3424
0
                    psOptions->dfMaxX = xUR;
3425
0
                    psOptions->dfMaxY = yUR;
3426
0
                    CPLError(CE_Warning, CPLE_AppDefined,
3427
0
                             "Clamping output bounds to (%f,%f) -> (%f, %f)",
3428
0
                             psOptions->dfMinX, psOptions->dfMinY,
3429
0
                             psOptions->dfMaxX, psOptions->dfMaxY);
3430
0
                }
3431
0
            }
3432
0
        }
3433
0
    }
3434
3435
    /* If (-ts and -te) or (-tr and -te) are specified, we don't need to compute
3436
     * the suggested output extent */
3437
0
    const bool bNeedsSuggestedWarpOutput =
3438
0
        !(((psOptions->nForcePixels != 0 && psOptions->nForceLines != 0) ||
3439
0
           (psOptions->dfXRes != 0 && psOptions->dfYRes != 0)) &&
3440
0
          !(psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
3441
0
            psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0));
3442
3443
    // If -te is specified, not not -tr and -ts
3444
0
    const bool bKnownTargetExtentButNotResolution =
3445
0
        !(psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
3446
0
          psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0) &&
3447
0
        psOptions->nForcePixels == 0 && psOptions->nForceLines == 0 &&
3448
0
        psOptions->dfXRes == 0 && psOptions->dfYRes == 0;
3449
3450
    /* -------------------------------------------------------------------- */
3451
    /*      Find the output driver.                                         */
3452
    /* -------------------------------------------------------------------- */
3453
0
    hDriver = GDALGetDriverByName(pszFormat);
3454
0
    if (hDriver == nullptr ||
3455
0
        (GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATE, nullptr) == nullptr &&
3456
0
         GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATECOPY, nullptr) ==
3457
0
             nullptr))
3458
0
    {
3459
0
        auto poMissingDriver =
3460
0
            GetGDALDriverManager()->GetHiddenDriverByName(pszFormat);
3461
0
        if (poMissingDriver)
3462
0
        {
3463
0
            const std::string msg =
3464
0
                GDALGetMessageAboutMissingPluginDriver(poMissingDriver);
3465
0
            printf("Output driver `%s' not found but is known. However plugin "
3466
0
                   "%s\n",
3467
0
                   pszFormat, msg.c_str());
3468
0
            return nullptr;
3469
0
        }
3470
3471
0
        printf("Output driver `%s' not recognised or does not support\n",
3472
0
               pszFormat);
3473
0
        printf("direct output file creation or CreateCopy. "
3474
0
               "The following format drivers are eligible for warp output:\n");
3475
3476
0
        for (int iDr = 0; iDr < GDALGetDriverCount(); iDr++)
3477
0
        {
3478
0
            hDriver = GDALGetDriver(iDr);
3479
3480
0
            if (GDALGetMetadataItem(hDriver, GDAL_DCAP_RASTER, nullptr) !=
3481
0
                    nullptr &&
3482
0
                (GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATE, nullptr) !=
3483
0
                     nullptr ||
3484
0
                 GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATECOPY, nullptr) !=
3485
0
                     nullptr))
3486
0
            {
3487
0
                printf("  %s: %s\n", GDALGetDriverShortName(hDriver),
3488
0
                       GDALGetDriverLongName(hDriver));
3489
0
            }
3490
0
        }
3491
0
        printf("\n");
3492
0
        return nullptr;
3493
0
    }
3494
3495
    /* -------------------------------------------------------------------- */
3496
    /*      For virtual output files, we have to set a special subclass     */
3497
    /*      of dataset to create.                                           */
3498
    /* -------------------------------------------------------------------- */
3499
0
    CPLStringList aosCreateOptions(CSLDuplicate(papszCreateOptions));
3500
0
    if (bVRT)
3501
0
        aosCreateOptions.SetNameValue("SUBCLASS", "VRTWarpedDataset");
3502
3503
    /* -------------------------------------------------------------------- */
3504
    /*      Loop over all input files to collect extents.                   */
3505
    /* -------------------------------------------------------------------- */
3506
0
    CPLString osThisTargetSRS;
3507
0
    {
3508
0
        const char *pszThisTargetSRS = CSLFetchNameValue(papszTO, "DST_SRS");
3509
0
        if (pszThisTargetSRS != nullptr)
3510
0
            osThisTargetSRS = pszThisTargetSRS;
3511
0
    }
3512
3513
0
    CPLStringList aoTOList(papszTO, FALSE);
3514
3515
0
    double dfResFromSourceAndTargetExtent =
3516
0
        std::numeric_limits<double>::infinity();
3517
3518
    /* -------------------------------------------------------------------- */
3519
    /*      Establish list of files of output dataset if it already exists. */
3520
    /* -------------------------------------------------------------------- */
3521
0
    std::set<std::string> oSetExistingDestFiles;
3522
0
    {
3523
0
        CPLPushErrorHandler(CPLQuietErrorHandler);
3524
0
        const char *const apszAllowedDrivers[] = {pszFormat, nullptr};
3525
0
        auto poExistingOutputDS = std::unique_ptr<GDALDataset>(
3526
0
            GDALDataset::Open(pszFilename, GDAL_OF_RASTER, apszAllowedDrivers));
3527
0
        if (poExistingOutputDS)
3528
0
        {
3529
0
            for (const char *pszFilenameInList :
3530
0
                 CPLStringList(poExistingOutputDS->GetFileList()))
3531
0
            {
3532
0
                oSetExistingDestFiles.insert(
3533
0
                    CPLString(pszFilenameInList).replaceAll('\\', '/'));
3534
0
            }
3535
0
        }
3536
0
        CPLPopErrorHandler();
3537
0
    }
3538
0
    std::set<std::string> oSetExistingDestFilesFoundInSource;
3539
0
    std::unique_ptr<GDALColorTable> poCT;
3540
3541
0
    for (int iSrc = 0; iSrc < nSrcCount; iSrc++)
3542
0
    {
3543
        /* --------------------------------------------------------------------
3544
         */
3545
        /*      Check that there's at least one raster band */
3546
        /* --------------------------------------------------------------------
3547
         */
3548
0
        GDALDatasetH hSrcDS = pahSrcDS[iSrc];
3549
0
        if (GDALGetRasterCount(hSrcDS) == 0)
3550
0
        {
3551
0
            CPLError(CE_Failure, CPLE_AppDefined,
3552
0
                     "Input file %s has no raster bands.",
3553
0
                     GDALGetDescription(hSrcDS));
3554
0
            return nullptr;
3555
0
        }
3556
3557
        // Examine desired overview level and retrieve the corresponding dataset
3558
        // if it exists.
3559
0
        std::unique_ptr<GDALDataset> oDstDSOverview;
3560
0
        if (psOptions->nOvLevel >= 0)
3561
0
        {
3562
0
            oDstDSOverview.reset(GDALCreateOverviewDataset(
3563
0
                GDALDataset::FromHandle(hSrcDS), psOptions->nOvLevel,
3564
0
                /* bThisLevelOnly = */ true));
3565
0
            if (oDstDSOverview)
3566
0
                hSrcDS = oDstDSOverview.get();
3567
0
        }
3568
3569
        /* --------------------------------------------------------------------
3570
         */
3571
        /*      Check if the source dataset shares some files with the dest
3572
         * one.*/
3573
        /* --------------------------------------------------------------------
3574
         */
3575
0
        if (!oSetExistingDestFiles.empty())
3576
0
        {
3577
            // We need to reopen in a temporary dataset for the particular
3578
            // case of overwritten a .tif.ovr file from a .tif
3579
            // If we probe the file list of the .tif, it will then open the
3580
            // .tif.ovr !
3581
0
            auto poSrcDS = GDALDataset::FromHandle(hSrcDS);
3582
0
            const char *const apszAllowedDrivers[] = {
3583
0
                poSrcDS->GetDriver() ? poSrcDS->GetDriver()->GetDescription()
3584
0
                                     : nullptr,
3585
0
                nullptr};
3586
0
            auto poSrcDSTmp = std::unique_ptr<GDALDataset>(GDALDataset::Open(
3587
0
                poSrcDS->GetDescription(), GDAL_OF_RASTER, apszAllowedDrivers));
3588
0
            if (poSrcDSTmp)
3589
0
            {
3590
0
                for (const char *pszFilenameInList :
3591
0
                     CPLStringList(poSrcDSTmp->GetFileList()))
3592
0
                {
3593
0
                    std::string osFilename =
3594
0
                        CPLString(pszFilenameInList).replaceAll('\\', '/');
3595
0
                    if (oSetExistingDestFiles.find(osFilename) !=
3596
0
                        oSetExistingDestFiles.end())
3597
0
                    {
3598
0
                        oSetExistingDestFilesFoundInSource.insert(
3599
0
                            std::move(osFilename));
3600
0
                    }
3601
0
                }
3602
0
            }
3603
0
        }
3604
3605
0
        if (eDT == GDT_Unknown)
3606
0
            eDT = GDALGetRasterDataType(GDALGetRasterBand(hSrcDS, 1));
3607
3608
        /* --------------------------------------------------------------------
3609
         */
3610
        /*      If we are processing the first file, and it has a raster */
3611
        /*      attribute table, then we will copy it to the destination file.
3612
         */
3613
        /* --------------------------------------------------------------------
3614
         */
3615
0
        if (iSrc == 0)
3616
0
        {
3617
0
            hRAT = GDALGetDefaultRAT(GDALGetRasterBand(hSrcDS, 1));
3618
0
            if (hRAT != nullptr)
3619
0
            {
3620
0
                if (psOptions->eResampleAlg != GRA_NearestNeighbour &&
3621
0
                    psOptions->eResampleAlg != GRA_Mode &&
3622
0
                    GDALRATGetTableType(hRAT) == GRTT_THEMATIC)
3623
0
                {
3624
0
                    if (!psOptions->bQuiet)
3625
0
                    {
3626
0
                        CPLError(CE_Warning, CPLE_AppDefined,
3627
0
                                 "Warning: Input file %s has a thematic RAT, "
3628
0
                                 "which will likely lead "
3629
0
                                 "to bad results when using a resampling "
3630
0
                                 "method other than nearest neighbour "
3631
0
                                 "or mode so we are discarding it.\n",
3632
0
                                 GDALGetDescription(hSrcDS));
3633
0
                    }
3634
0
                    hRAT = nullptr;
3635
0
                }
3636
0
                else
3637
0
                {
3638
0
                    if (!psOptions->bQuiet)
3639
0
                        printf("Copying raster attribute table from %s to new "
3640
0
                               "file.\n",
3641
0
                               GDALGetDescription(hSrcDS));
3642
0
                }
3643
0
            }
3644
0
        }
3645
3646
        /* --------------------------------------------------------------------
3647
         */
3648
        /*      If we are processing the first file, and it has a color */
3649
        /*      table, then we will copy it to the destination file. */
3650
        /* --------------------------------------------------------------------
3651
         */
3652
0
        if (iSrc == 0)
3653
0
        {
3654
0
            auto hCT = GDALGetRasterColorTable(GDALGetRasterBand(hSrcDS, 1));
3655
0
            if (hCT != nullptr)
3656
0
            {
3657
0
                poCT.reset(
3658
0
                    GDALColorTable::FromHandle(GDALCloneColorTable(hCT)));
3659
0
                if (!psOptions->bQuiet)
3660
0
                    printf("Copying color table from %s to new file.\n",
3661
0
                           GDALGetDescription(hSrcDS));
3662
0
            }
3663
3664
0
            if (psOptions->anDstBands.empty())
3665
0
            {
3666
0
                nDstBandCount = GDALGetRasterCount(hSrcDS);
3667
0
                for (int iBand = 0; iBand < nDstBandCount; iBand++)
3668
0
                {
3669
0
                    GDALColorInterp eInterp = GDALGetRasterColorInterpretation(
3670
0
                        GDALGetRasterBand(hSrcDS, iBand + 1));
3671
0
                    apeColorInterpretations.push_back(eInterp);
3672
0
                }
3673
3674
                // Do we want to generate an alpha band in the output file?
3675
0
                if (psOptions->bEnableSrcAlpha)
3676
0
                    nDstBandCount--;
3677
3678
0
                if (psOptions->bEnableDstAlpha)
3679
0
                    nDstBandCount++;
3680
0
            }
3681
0
            else
3682
0
            {
3683
0
                for (int nSrcBand : psOptions->anSrcBands)
3684
0
                {
3685
0
                    auto hBand = GDALGetRasterBand(hSrcDS, nSrcBand);
3686
0
                    GDALColorInterp eInterp =
3687
0
                        hBand ? GDALGetRasterColorInterpretation(hBand)
3688
0
                              : GCI_Undefined;
3689
0
                    apeColorInterpretations.push_back(eInterp);
3690
0
                }
3691
0
                nDstBandCount = static_cast<int>(psOptions->anDstBands.size());
3692
0
                if (psOptions->bEnableDstAlpha)
3693
0
                {
3694
0
                    nDstBandCount++;
3695
0
                    apeColorInterpretations.push_back(GCI_AlphaBand);
3696
0
                }
3697
0
                else if (GDALGetRasterCount(hSrcDS) &&
3698
0
                         GDALGetRasterColorInterpretation(GDALGetRasterBand(
3699
0
                             hSrcDS, GDALGetRasterCount(hSrcDS))) ==
3700
0
                             GCI_AlphaBand &&
3701
0
                         !psOptions->bDisableSrcAlpha)
3702
0
                {
3703
0
                    nDstBandCount++;
3704
0
                    apeColorInterpretations.push_back(GCI_AlphaBand);
3705
0
                }
3706
0
            }
3707
0
        }
3708
3709
        /* --------------------------------------------------------------------
3710
         */
3711
        /*      If we are processing the first file, get the source srs from */
3712
        /*      dataset, if not set already. */
3713
        /* --------------------------------------------------------------------
3714
         */
3715
0
        const auto osThisSourceSRS = GetSrcDSProjection(hSrcDS, papszTO);
3716
0
        if (iSrc == 0 && osThisTargetSRS.empty())
3717
0
        {
3718
0
            if (!osThisSourceSRS.empty())
3719
0
            {
3720
0
                osThisTargetSRS = osThisSourceSRS;
3721
0
                aoTOList.SetNameValue("DST_SRS", osThisSourceSRS);
3722
0
            }
3723
0
        }
3724
3725
        /* --------------------------------------------------------------------
3726
         */
3727
        /*      Create a transformation object from the source to */
3728
        /*      destination coordinate system. */
3729
        /* --------------------------------------------------------------------
3730
         */
3731
0
        GDALTransformerArgUniquePtr hTransformArg;
3732
0
        if (hUniqueTransformArg)
3733
0
            hTransformArg = std::move(hUniqueTransformArg);
3734
0
        else
3735
0
        {
3736
0
            hTransformArg.reset(GDALCreateGenImgProjTransformer2(
3737
0
                hSrcDS, nullptr, aoTOList.List()));
3738
0
            if (hTransformArg == nullptr)
3739
0
            {
3740
0
                return nullptr;
3741
0
            }
3742
0
        }
3743
3744
0
        GDALTransformerInfo *psInfo =
3745
0
            static_cast<GDALTransformerInfo *>(hTransformArg.get());
3746
3747
        /* --------------------------------------------------------------------
3748
         */
3749
        /*      Get approximate output resolution */
3750
        /* --------------------------------------------------------------------
3751
         */
3752
3753
0
        if (bKnownTargetExtentButNotResolution)
3754
0
        {
3755
            // Sample points along a grid in target CRS
3756
0
            constexpr int nPointsX = 10;
3757
0
            constexpr int nPointsY = 10;
3758
0
            constexpr int nPoints = 3 * nPointsX * nPointsY;
3759
0
            std::vector<double> padfX;
3760
0
            std::vector<double> padfY;
3761
0
            std::vector<double> padfZ(nPoints);
3762
0
            std::vector<int> pabSuccess(nPoints);
3763
0
            const double dfEps =
3764
0
                std::min(psOptions->dfMaxX - psOptions->dfMinX,
3765
0
                         std::abs(psOptions->dfMaxY - psOptions->dfMinY)) /
3766
0
                1000;
3767
0
            for (int iY = 0; iY < nPointsY; iY++)
3768
0
            {
3769
0
                for (int iX = 0; iX < nPointsX; iX++)
3770
0
                {
3771
0
                    const double dfX =
3772
0
                        psOptions->dfMinX +
3773
0
                        static_cast<double>(iX) *
3774
0
                            (psOptions->dfMaxX - psOptions->dfMinX) /
3775
0
                            (nPointsX - 1);
3776
0
                    const double dfY =
3777
0
                        psOptions->dfMinY +
3778
0
                        static_cast<double>(iY) *
3779
0
                            (psOptions->dfMaxY - psOptions->dfMinY) /
3780
0
                            (nPointsY - 1);
3781
3782
                    // Reproject each destination sample point and its
3783
                    // neighbours at (x+1,y) and (x,y+1), so as to get the local
3784
                    // scale.
3785
0
                    padfX.push_back(dfX);
3786
0
                    padfY.push_back(dfY);
3787
3788
0
                    padfX.push_back((iX == nPointsX - 1) ? dfX - dfEps
3789
0
                                                         : dfX + dfEps);
3790
0
                    padfY.push_back(dfY);
3791
3792
0
                    padfX.push_back(dfX);
3793
0
                    padfY.push_back((iY == nPointsY - 1) ? dfY - dfEps
3794
0
                                                         : dfY + dfEps);
3795
0
                }
3796
0
            }
3797
3798
0
            bool transformedToSrcCRS{false};
3799
3800
0
            GDALGenImgProjTransformInfo *psTransformInfo{
3801
0
                static_cast<GDALGenImgProjTransformInfo *>(
3802
0
                    hTransformArg.get())};
3803
3804
            // If a transformer is available, use an extent that covers the
3805
            // target extent instead of the real source image extent, but also
3806
            // check for target extent compatibility with source CRS extent
3807
0
            if (psTransformInfo && psTransformInfo->pReprojectArg &&
3808
0
                psTransformInfo->sSrcParams.pTransformer == nullptr)
3809
0
            {
3810
0
                const GDALReprojectionTransformInfo *psRTI =
3811
0
                    static_cast<const GDALReprojectionTransformInfo *>(
3812
0
                        psTransformInfo->pReprojectArg);
3813
0
                if (psRTI->poReverseTransform)
3814
0
                {
3815
3816
                    // Compute new geotransform from transformed target extent
3817
0
                    double adfGeoTransform[6];
3818
0
                    if (GDALGetGeoTransform(hSrcDS, adfGeoTransform) ==
3819
0
                            CE_None &&
3820
0
                        adfGeoTransform[2] == 0 && adfGeoTransform[4] == 0)
3821
0
                    {
3822
3823
                        // Transform target extent to source CRS
3824
0
                        double dfMinX = psOptions->dfMinX;
3825
0
                        double dfMinY = psOptions->dfMinY;
3826
3827
                        // Need this to check if the target extent is compatible with the source extent
3828
0
                        double dfMaxX = psOptions->dfMaxX;
3829
0
                        double dfMaxY = psOptions->dfMaxY;
3830
3831
                        // Clone of psRTI->poReverseTransform with CHECK_WITH_INVERT_PROJ set to TRUE
3832
                        // to detect out of source CRS bounds destination extent and fall back to original
3833
                        // algorithm if needed
3834
0
                        CPLConfigOptionSetter oSetter("CHECK_WITH_INVERT_PROJ",
3835
0
                                                      "TRUE", false);
3836
0
                        OGRCoordinateTransformationOptions options;
3837
0
                        auto poReverseTransform =
3838
0
                            std::unique_ptr<OGRCoordinateTransformation>(
3839
0
                                OGRCreateCoordinateTransformation(
3840
0
                                    psRTI->poReverseTransform->GetSourceCS(),
3841
0
                                    psRTI->poReverseTransform->GetTargetCS(),
3842
0
                                    options));
3843
3844
0
                        if (poReverseTransform)
3845
0
                        {
3846
3847
0
                            poReverseTransform->Transform(
3848
0
                                1, &dfMinX, &dfMinY, nullptr, &pabSuccess[0]);
3849
3850
0
                            if (pabSuccess[0])
3851
0
                            {
3852
0
                                adfGeoTransform[0] = dfMinX;
3853
0
                                adfGeoTransform[3] = dfMinY;
3854
3855
0
                                poReverseTransform->Transform(1, &dfMaxX,
3856
0
                                                              &dfMaxY, nullptr,
3857
0
                                                              &pabSuccess[0]);
3858
3859
0
                                if (pabSuccess[0])
3860
0
                                {
3861
3862
                                    // Reproject to source image CRS
3863
0
                                    psRTI->poReverseTransform->Transform(
3864
0
                                        nPoints, &padfX[0], &padfY[0],
3865
0
                                        &padfZ[0], &pabSuccess[0]);
3866
3867
                                    // Transform back to source image coordinate space using geotransform
3868
0
                                    for (size_t i = 0; i < padfX.size(); i++)
3869
0
                                    {
3870
0
                                        padfX[i] =
3871
0
                                            (padfX[i] - adfGeoTransform[0]) /
3872
0
                                            adfGeoTransform[1];
3873
0
                                        padfY[i] = std::abs(
3874
0
                                            (padfY[i] - adfGeoTransform[3]) /
3875
0
                                            adfGeoTransform[5]);
3876
0
                                    }
3877
3878
0
                                    transformedToSrcCRS = true;
3879
0
                                }
3880
0
                            }
3881
0
                        }
3882
0
                    }
3883
0
                }
3884
0
            }
3885
3886
0
            if (!transformedToSrcCRS)
3887
0
            {
3888
                // Transform to source image coordinate space
3889
0
                psInfo->pfnTransform(hTransformArg.get(), TRUE, nPoints,
3890
0
                                     &padfX[0], &padfY[0], &padfZ[0],
3891
0
                                     &pabSuccess[0]);
3892
0
            }
3893
3894
            // Compute the resolution at sampling points
3895
0
            std::vector<std::pair<double, double>> aoResPairs;
3896
3897
0
            const auto Distance = [](double x, double y)
3898
0
            { return sqrt(x * x + y * y); };
3899
3900
0
            const int nSrcXSize = GDALGetRasterXSize(hSrcDS);
3901
0
            const int nSrcYSize = GDALGetRasterYSize(hSrcDS);
3902
3903
0
            for (int i = 0; i < nPoints; i += 3)
3904
0
            {
3905
0
                if (pabSuccess[i] && pabSuccess[i + 1] && pabSuccess[i + 2] &&
3906
0
                    padfX[i] >= 0 && padfY[i] >= 0 &&
3907
0
                    (transformedToSrcCRS ||
3908
0
                     (padfX[i] <= nSrcXSize && padfY[i] <= nSrcYSize)))
3909
0
                {
3910
0
                    const double dfRes1 =
3911
0
                        std::abs(dfEps) / Distance(padfX[i + 1] - padfX[i],
3912
0
                                                   padfY[i + 1] - padfY[i]);
3913
0
                    const double dfRes2 =
3914
0
                        std::abs(dfEps) / Distance(padfX[i + 2] - padfX[i],
3915
0
                                                   padfY[i + 2] - padfY[i]);
3916
0
                    if (std::isfinite(dfRes1) && std::isfinite(dfRes2))
3917
0
                    {
3918
0
                        aoResPairs.push_back(
3919
0
                            std::pair<double, double>(dfRes1, dfRes2));
3920
0
                    }
3921
0
                }
3922
0
            }
3923
3924
            // Find the minimum resolution that is at least 10 times greater
3925
            // than the median, to remove outliers.
3926
            // Start first by doing that on dfRes1, then dfRes2 and then their
3927
            // average.
3928
0
            std::sort(aoResPairs.begin(), aoResPairs.end(),
3929
0
                      [](const std::pair<double, double> &oPair1,
3930
0
                         const std::pair<double, double> &oPair2)
3931
0
                      { return oPair1.first < oPair2.first; });
3932
3933
0
            if (!aoResPairs.empty())
3934
0
            {
3935
0
                std::vector<std::pair<double, double>> aoResPairsNew;
3936
0
                const double dfMedian1 =
3937
0
                    aoResPairs[aoResPairs.size() / 2].first;
3938
0
                for (const auto &oPair : aoResPairs)
3939
0
                {
3940
0
                    if (oPair.first > dfMedian1 / 10)
3941
0
                    {
3942
0
                        aoResPairsNew.push_back(oPair);
3943
0
                    }
3944
0
                }
3945
3946
0
                aoResPairs = std::move(aoResPairsNew);
3947
0
                std::sort(aoResPairs.begin(), aoResPairs.end(),
3948
0
                          [](const std::pair<double, double> &oPair1,
3949
0
                             const std::pair<double, double> &oPair2)
3950
0
                          { return oPair1.second < oPair2.second; });
3951
0
                if (!aoResPairs.empty())
3952
0
                {
3953
0
                    std::vector<double> adfRes;
3954
0
                    const double dfMedian2 =
3955
0
                        aoResPairs[aoResPairs.size() / 2].second;
3956
0
                    for (const auto &oPair : aoResPairs)
3957
0
                    {
3958
0
                        if (oPair.second > dfMedian2 / 10)
3959
0
                        {
3960
0
                            adfRes.push_back((oPair.first + oPair.second) / 2);
3961
0
                        }
3962
0
                    }
3963
3964
0
                    std::sort(adfRes.begin(), adfRes.end());
3965
0
                    if (!adfRes.empty())
3966
0
                    {
3967
0
                        const double dfMedian = adfRes[adfRes.size() / 2];
3968
0
                        for (const double dfRes : adfRes)
3969
0
                        {
3970
0
                            if (dfRes > dfMedian / 10)
3971
0
                            {
3972
0
                                dfResFromSourceAndTargetExtent = std::min(
3973
0
                                    dfResFromSourceAndTargetExtent, dfRes);
3974
0
                                break;
3975
0
                            }
3976
0
                        }
3977
0
                    }
3978
0
                }
3979
0
            }
3980
0
        }
3981
3982
        /* --------------------------------------------------------------------
3983
         */
3984
        /*      Get approximate output definition. */
3985
        /* --------------------------------------------------------------------
3986
         */
3987
0
        double adfThisGeoTransform[6];
3988
0
        double adfExtent[4];
3989
0
        if (bNeedsSuggestedWarpOutput)
3990
0
        {
3991
0
            int nThisPixels, nThisLines;
3992
3993
            // For sum, round-up dimension, to be sure that the output extent
3994
            // includes all source pixels, to have the sum preserving property.
3995
0
            int nOptions = (psOptions->eResampleAlg == GRA_Sum)
3996
0
                               ? GDAL_SWO_ROUND_UP_SIZE
3997
0
                               : 0;
3998
0
            if (psOptions->bSquarePixels)
3999
0
            {
4000
0
                nOptions |= GDAL_SWO_FORCE_SQUARE_PIXEL;
4001
0
            }
4002
4003
0
            if (GDALSuggestedWarpOutput2(
4004
0
                    hSrcDS, psInfo->pfnTransform, hTransformArg.get(),
4005
0
                    adfThisGeoTransform, &nThisPixels, &nThisLines, adfExtent,
4006
0
                    nOptions) != CE_None)
4007
0
            {
4008
0
                return nullptr;
4009
0
            }
4010
4011
0
            if (CPLGetConfigOption("CHECK_WITH_INVERT_PROJ", nullptr) ==
4012
0
                nullptr)
4013
0
            {
4014
0
                double MinX = adfExtent[0];
4015
0
                double MaxX = adfExtent[2];
4016
0
                double MaxY = adfExtent[3];
4017
0
                double MinY = adfExtent[1];
4018
0
                int bSuccess = TRUE;
4019
4020
                // +/-180 deg in longitude do not roundtrip sometimes
4021
0
                if (MinX == -180)
4022
0
                    MinX += 1e-6;
4023
0
                if (MaxX == 180)
4024
0
                    MaxX -= 1e-6;
4025
4026
                // +/-90 deg in latitude do not roundtrip sometimes
4027
0
                if (MinY == -90)
4028
0
                    MinY += 1e-6;
4029
0
                if (MaxY == 90)
4030
0
                    MaxY -= 1e-6;
4031
4032
                /* Check that the edges of the target image are in the validity
4033
                 * area */
4034
                /* of the target projection */
4035
0
                const int N_STEPS = 20;
4036
0
                for (int i = 0; i <= N_STEPS && bSuccess; i++)
4037
0
                {
4038
0
                    for (int j = 0; j <= N_STEPS && bSuccess; j++)
4039
0
                    {
4040
0
                        const double dfRatioI = i * 1.0 / N_STEPS;
4041
0
                        const double dfRatioJ = j * 1.0 / N_STEPS;
4042
0
                        const double expected_x =
4043
0
                            (1 - dfRatioI) * MinX + dfRatioI * MaxX;
4044
0
                        const double expected_y =
4045
0
                            (1 - dfRatioJ) * MinY + dfRatioJ * MaxY;
4046
0
                        double x = expected_x;
4047
0
                        double y = expected_y;
4048
0
                        double z = 0;
4049
                        /* Target SRS coordinates to source image pixel
4050
                         * coordinates */
4051
0
                        if (!psInfo->pfnTransform(hTransformArg.get(), TRUE, 1,
4052
0
                                                  &x, &y, &z, &bSuccess) ||
4053
0
                            !bSuccess)
4054
0
                            bSuccess = FALSE;
4055
                        /* Source image pixel coordinates to target SRS
4056
                         * coordinates */
4057
0
                        if (!psInfo->pfnTransform(hTransformArg.get(), FALSE, 1,
4058
0
                                                  &x, &y, &z, &bSuccess) ||
4059
0
                            !bSuccess)
4060
0
                            bSuccess = FALSE;
4061
0
                        if (fabs(x - expected_x) >
4062
0
                                (MaxX - MinX) / nThisPixels ||
4063
0
                            fabs(y - expected_y) > (MaxY - MinY) / nThisLines)
4064
0
                            bSuccess = FALSE;
4065
0
                    }
4066
0
                }
4067
4068
                /* If not, retry with CHECK_WITH_INVERT_PROJ=TRUE that forces
4069
                 * ogrct.cpp */
4070
                /* to check the consistency of each requested projection result
4071
                 * with the */
4072
                /* invert projection */
4073
0
                if (!bSuccess)
4074
0
                {
4075
0
                    CPLSetThreadLocalConfigOption("CHECK_WITH_INVERT_PROJ",
4076
0
                                                  "TRUE");
4077
0
                    CPLDebug("WARP", "Recompute out extent with "
4078
0
                                     "CHECK_WITH_INVERT_PROJ=TRUE");
4079
4080
0
                    const CPLErr eErr = GDALSuggestedWarpOutput2(
4081
0
                        hSrcDS, psInfo->pfnTransform, hTransformArg.get(),
4082
0
                        adfThisGeoTransform, &nThisPixels, &nThisLines,
4083
0
                        adfExtent, 0);
4084
0
                    CPLSetThreadLocalConfigOption("CHECK_WITH_INVERT_PROJ",
4085
0
                                                  nullptr);
4086
0
                    if (eErr != CE_None)
4087
0
                    {
4088
0
                        return nullptr;
4089
0
                    }
4090
0
                }
4091
0
            }
4092
0
        }
4093
4094
        // If no reprojection or geometry change is involved, and that the
4095
        // source image is north-up, preserve source resolution instead of
4096
        // forcing square pixels.
4097
0
        const char *pszMethod = FetchSrcMethod(papszTO);
4098
0
        double adfThisGeoTransformTmp[6];
4099
0
        if (!psOptions->bSquarePixels && bNeedsSuggestedWarpOutput &&
4100
0
            psOptions->dfXRes == 0 && psOptions->dfYRes == 0 &&
4101
0
            psOptions->nForcePixels == 0 && psOptions->nForceLines == 0 &&
4102
0
            (pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")) &&
4103
0
            CSLFetchNameValue(papszTO, "COORDINATE_OPERATION") == nullptr &&
4104
0
            CSLFetchNameValue(papszTO, "SRC_METHOD") == nullptr &&
4105
0
            CSLFetchNameValue(papszTO, "DST_METHOD") == nullptr &&
4106
0
            GDALGetGeoTransform(hSrcDS, adfThisGeoTransformTmp) == CE_None &&
4107
0
            adfThisGeoTransformTmp[2] == 0 && adfThisGeoTransformTmp[4] == 0 &&
4108
0
            adfThisGeoTransformTmp[5] < 0 &&
4109
0
            GDALGetMetadata(hSrcDS, "GEOLOC_ARRAY") == nullptr &&
4110
0
            GDALGetMetadata(hSrcDS, GDAL_MDD_RPC) == nullptr)
4111
0
        {
4112
0
            bool bIsSameHorizontal = osThisSourceSRS == osThisTargetSRS;
4113
0
            if (!bIsSameHorizontal)
4114
0
            {
4115
0
                OGRSpatialReference oSrcSRS;
4116
0
                OGRSpatialReference oDstSRS;
4117
0
                CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
4118
0
                if (oSrcSRS.SetFromUserInput(osThisSourceSRS.c_str()) ==
4119
0
                        OGRERR_NONE &&
4120
0
                    oDstSRS.SetFromUserInput(osThisTargetSRS.c_str()) ==
4121
0
                        OGRERR_NONE &&
4122
0
                    (oSrcSRS.GetAxesCount() == 3 ||
4123
0
                     oDstSRS.GetAxesCount() == 3) &&
4124
0
                    oSrcSRS.DemoteTo2D(nullptr) == OGRERR_NONE &&
4125
0
                    oDstSRS.DemoteTo2D(nullptr) == OGRERR_NONE)
4126
0
                {
4127
0
                    bIsSameHorizontal = CPL_TO_BOOL(oSrcSRS.IsSame(&oDstSRS));
4128
0
                }
4129
0
            }
4130
0
            if (bIsSameHorizontal)
4131
0
            {
4132
0
                memcpy(adfThisGeoTransform, adfThisGeoTransformTmp,
4133
0
                       6 * sizeof(double));
4134
0
                adfExtent[0] = adfThisGeoTransform[0];
4135
0
                adfExtent[1] =
4136
0
                    adfThisGeoTransform[3] +
4137
0
                    GDALGetRasterYSize(hSrcDS) * adfThisGeoTransform[5];
4138
0
                adfExtent[2] =
4139
0
                    adfThisGeoTransform[0] +
4140
0
                    GDALGetRasterXSize(hSrcDS) * adfThisGeoTransform[1];
4141
0
                adfExtent[3] = adfThisGeoTransform[3];
4142
0
                dfResFromSourceAndTargetExtent =
4143
0
                    std::numeric_limits<double>::infinity();
4144
0
            }
4145
0
        }
4146
4147
0
        if (bNeedsSuggestedWarpOutput)
4148
0
        {
4149
            /* --------------------------------------------------------------------
4150
             */
4151
            /*      Expand the working bounds to include this region, ensure the
4152
             */
4153
            /*      working resolution is no more than this resolution. */
4154
            /* --------------------------------------------------------------------
4155
             */
4156
0
            if (dfWrkMaxX == 0.0 && dfWrkMinX == 0.0)
4157
0
            {
4158
0
                dfWrkMinX = adfExtent[0];
4159
0
                dfWrkMaxX = adfExtent[2];
4160
0
                dfWrkMaxY = adfExtent[3];
4161
0
                dfWrkMinY = adfExtent[1];
4162
0
                dfWrkResX = adfThisGeoTransform[1];
4163
0
                dfWrkResY = std::abs(adfThisGeoTransform[5]);
4164
0
            }
4165
0
            else
4166
0
            {
4167
0
                dfWrkMinX = std::min(dfWrkMinX, adfExtent[0]);
4168
0
                dfWrkMaxX = std::max(dfWrkMaxX, adfExtent[2]);
4169
0
                dfWrkMaxY = std::max(dfWrkMaxY, adfExtent[3]);
4170
0
                dfWrkMinY = std::min(dfWrkMinY, adfExtent[1]);
4171
0
                dfWrkResX = std::min(dfWrkResX, adfThisGeoTransform[1]);
4172
0
                dfWrkResY =
4173
0
                    std::min(dfWrkResY, std::abs(adfThisGeoTransform[5]));
4174
0
            }
4175
0
        }
4176
4177
0
        if (nSrcCount == 1)
4178
0
        {
4179
0
            hUniqueTransformArg = std::move(hTransformArg);
4180
0
        }
4181
0
    }
4182
4183
    // If the source file(s) and the dest one share some files in common,
4184
    // only remove the files that are *not* in common
4185
0
    if (!oSetExistingDestFilesFoundInSource.empty())
4186
0
    {
4187
0
        for (const std::string &osFilename : oSetExistingDestFiles)
4188
0
        {
4189
0
            if (oSetExistingDestFilesFoundInSource.find(osFilename) ==
4190
0
                oSetExistingDestFilesFoundInSource.end())
4191
0
            {
4192
0
                VSIUnlink(osFilename.c_str());
4193
0
            }
4194
0
        }
4195
0
    }
4196
4197
0
    if (std::isfinite(dfResFromSourceAndTargetExtent))
4198
0
    {
4199
0
        dfWrkResX = dfResFromSourceAndTargetExtent;
4200
0
        dfWrkResY = dfResFromSourceAndTargetExtent;
4201
0
    }
4202
4203
    /* -------------------------------------------------------------------- */
4204
    /*      Did we have any usable sources?                                 */
4205
    /* -------------------------------------------------------------------- */
4206
0
    if (nDstBandCount == 0)
4207
0
    {
4208
0
        CPLError(CE_Failure, CPLE_AppDefined, "No usable source images.");
4209
0
        return nullptr;
4210
0
    }
4211
4212
    /* -------------------------------------------------------------------- */
4213
    /*      Turn the suggested region into a geotransform and suggested     */
4214
    /*      number of pixels and lines.                                     */
4215
    /* -------------------------------------------------------------------- */
4216
0
    double adfDstGeoTransform[6] = {0, 0, 0, 0, 0, 0};
4217
0
    int nPixels = 0;
4218
0
    int nLines = 0;
4219
4220
0
    const auto ComputePixelsFromResAndExtent = [psOptions]()
4221
0
    {
4222
0
        return std::max(1.0,
4223
0
                        std::round((psOptions->dfMaxX - psOptions->dfMinX) /
4224
0
                                   psOptions->dfXRes));
4225
0
    };
4226
4227
0
    const auto ComputeLinesFromResAndExtent = [psOptions]()
4228
0
    {
4229
0
        return std::max(
4230
0
            1.0, std::round(std::fabs(psOptions->dfMaxY - psOptions->dfMinY) /
4231
0
                            psOptions->dfYRes));
4232
0
    };
4233
4234
0
    if (bNeedsSuggestedWarpOutput)
4235
0
    {
4236
0
        adfDstGeoTransform[0] = dfWrkMinX;
4237
0
        adfDstGeoTransform[1] = dfWrkResX;
4238
0
        adfDstGeoTransform[2] = 0.0;
4239
0
        adfDstGeoTransform[3] = dfWrkMaxY;
4240
0
        adfDstGeoTransform[4] = 0.0;
4241
0
        adfDstGeoTransform[5] = -1 * dfWrkResY;
4242
4243
0
        const double dfPixels = (dfWrkMaxX - dfWrkMinX) / dfWrkResX;
4244
0
        const double dfLines = (dfWrkMaxY - dfWrkMinY) / dfWrkResY;
4245
        // guaranteed by GDALSuggestedWarpOutput2() behavior
4246
0
        CPLAssert(std::round(dfPixels) <= INT_MAX);
4247
0
        CPLAssert(std::round(dfLines) <= INT_MAX);
4248
0
        nPixels =
4249
0
            static_cast<int>(std::min<double>(std::round(dfPixels), INT_MAX));
4250
0
        nLines =
4251
0
            static_cast<int>(std::min<double>(std::round(dfLines), INT_MAX));
4252
0
    }
4253
4254
    /* -------------------------------------------------------------------- */
4255
    /*      Did the user override some parameters?                          */
4256
    /* -------------------------------------------------------------------- */
4257
0
    if (UseTEAndTSAndTRConsistently(psOptions))
4258
0
    {
4259
0
        adfDstGeoTransform[0] = psOptions->dfMinX;
4260
0
        adfDstGeoTransform[3] = psOptions->dfMaxY;
4261
0
        adfDstGeoTransform[1] = psOptions->dfXRes;
4262
0
        adfDstGeoTransform[5] = -psOptions->dfYRes;
4263
4264
0
        nPixels = psOptions->nForcePixels;
4265
0
        nLines = psOptions->nForceLines;
4266
0
    }
4267
0
    else if (psOptions->dfXRes != 0.0 && psOptions->dfYRes != 0.0)
4268
0
    {
4269
0
        bool bDetectBlankBorders = false;
4270
4271
0
        if (psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
4272
0
            psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0)
4273
0
        {
4274
0
            bDetectBlankBorders = bNeedsSuggestedWarpOutput;
4275
4276
0
            psOptions->dfMinX = adfDstGeoTransform[0];
4277
0
            psOptions->dfMaxX =
4278
0
                adfDstGeoTransform[0] + adfDstGeoTransform[1] * nPixels;
4279
0
            psOptions->dfMaxY = adfDstGeoTransform[3];
4280
0
            psOptions->dfMinY =
4281
0
                adfDstGeoTransform[3] + adfDstGeoTransform[5] * nLines;
4282
0
        }
4283
4284
0
        if (psOptions->bTargetAlignedPixels ||
4285
0
            (psOptions->bCropToCutline &&
4286
0
             psOptions->aosWarpOptions.FetchBool("CUTLINE_ALL_TOUCHED", false)))
4287
0
        {
4288
0
            if ((psOptions->bTargetAlignedPixels &&
4289
0
                 bNeedsSuggestedWarpOutput) ||
4290
0
                (psOptions->bCropToCutline &&
4291
0
                 psOptions->aosWarpOptions.FetchBool("CUTLINE_ALL_TOUCHED",
4292
0
                                                     false)))
4293
0
            {
4294
0
                bDetectBlankBorders = true;
4295
0
            }
4296
0
            constexpr double EPS = 1e-8;
4297
0
            psOptions->dfMinX =
4298
0
                floor(psOptions->dfMinX / psOptions->dfXRes + EPS) *
4299
0
                psOptions->dfXRes;
4300
0
            psOptions->dfMaxX =
4301
0
                ceil(psOptions->dfMaxX / psOptions->dfXRes - EPS) *
4302
0
                psOptions->dfXRes;
4303
0
            psOptions->dfMinY =
4304
0
                floor(psOptions->dfMinY / psOptions->dfYRes + EPS) *
4305
0
                psOptions->dfYRes;
4306
0
            psOptions->dfMaxY =
4307
0
                ceil(psOptions->dfMaxY / psOptions->dfYRes - EPS) *
4308
0
                psOptions->dfYRes;
4309
0
        }
4310
4311
0
        const auto UpdateGeoTransformandAndPixelLines = [&]()
4312
0
        {
4313
0
            const double dfPixels = ComputePixelsFromResAndExtent();
4314
0
            const double dfLines = ComputeLinesFromResAndExtent();
4315
0
            if (dfPixels > INT_MAX || dfLines > INT_MAX)
4316
0
            {
4317
0
                CPLError(CE_Failure, CPLE_AppDefined,
4318
0
                         "Too large output raster size: %f x %f", dfPixels,
4319
0
                         dfLines);
4320
0
                return false;
4321
0
            }
4322
4323
0
            nPixels = static_cast<int>(dfPixels);
4324
0
            nLines = static_cast<int>(dfLines);
4325
0
            adfDstGeoTransform[0] = psOptions->dfMinX;
4326
0
            adfDstGeoTransform[3] = psOptions->dfMaxY;
4327
0
            adfDstGeoTransform[1] = psOptions->dfXRes;
4328
0
            adfDstGeoTransform[5] = (psOptions->dfMaxY > psOptions->dfMinY)
4329
0
                                        ? -psOptions->dfYRes
4330
0
                                        : psOptions->dfYRes;
4331
0
            return true;
4332
0
        };
4333
4334
0
        if (bDetectBlankBorders && nSrcCount == 1 && hUniqueTransformArg &&
4335
            // to avoid too large memory allocations
4336
0
            std::max(nPixels, nLines) < 100 * 1000 * 1000)
4337
0
        {
4338
            // Try to detect if the edge of the raster would be blank
4339
            // Cf https://github.com/OSGeo/gdal/issues/7905
4340
0
            while (nPixels > 1 || nLines > 1)
4341
0
            {
4342
0
                if (!UpdateGeoTransformandAndPixelLines())
4343
0
                    return nullptr;
4344
4345
0
                GDALSetGenImgProjTransformerDstGeoTransform(
4346
0
                    hUniqueTransformArg.get(), adfDstGeoTransform);
4347
4348
0
                std::vector<double> adfX(std::max(nPixels, nLines));
4349
0
                std::vector<double> adfY(adfX.size());
4350
0
                std::vector<double> adfZ(adfX.size());
4351
0
                std::vector<int> abSuccess(adfX.size());
4352
4353
0
                const auto DetectBlankBorder =
4354
0
                    [&](int nValues,
4355
0
                        std::function<bool(double, double)> funcIsOK)
4356
0
                {
4357
0
                    if (nValues > 3)
4358
0
                    {
4359
                        // First try with just a subsample of 3 points
4360
0
                        double adf3X[3] = {adfX[0], adfX[nValues / 2],
4361
0
                                           adfX[nValues - 1]};
4362
0
                        double adf3Y[3] = {adfY[0], adfY[nValues / 2],
4363
0
                                           adfY[nValues - 1]};
4364
0
                        double adf3Z[3] = {0};
4365
0
                        GDALGenImgProjTransform(hUniqueTransformArg.get(), TRUE,
4366
0
                                                3, &adf3X[0], &adf3Y[0],
4367
0
                                                &adf3Z[0], &abSuccess[0]);
4368
0
                        for (int i = 0; i < 3; ++i)
4369
0
                        {
4370
0
                            if (abSuccess[i] && funcIsOK(adf3X[i], adf3Y[i]))
4371
0
                            {
4372
0
                                return false;
4373
0
                            }
4374
0
                        }
4375
0
                    }
4376
4377
                    // Do on full border to confirm
4378
0
                    GDALGenImgProjTransform(hUniqueTransformArg.get(), TRUE,
4379
0
                                            nValues, &adfX[0], &adfY[0],
4380
0
                                            &adfZ[0], &abSuccess[0]);
4381
0
                    for (int i = 0; i < nValues; ++i)
4382
0
                    {
4383
0
                        if (abSuccess[i] && funcIsOK(adfX[i], adfY[i]))
4384
0
                        {
4385
0
                            return false;
4386
0
                        }
4387
0
                    }
4388
4389
0
                    return true;
4390
0
                };
4391
4392
0
                for (int i = 0; i < nPixels; ++i)
4393
0
                {
4394
0
                    adfX[i] = i + 0.5;
4395
0
                    adfY[i] = 0.5;
4396
0
                    adfZ[i] = 0;
4397
0
                }
4398
0
                const bool bTopBlankLine = DetectBlankBorder(
4399
0
                    nPixels, [](double, double y) { return y >= 0; });
4400
4401
0
                for (int i = 0; i < nPixels; ++i)
4402
0
                {
4403
0
                    adfX[i] = i + 0.5;
4404
0
                    adfY[i] = nLines - 0.5;
4405
0
                    adfZ[i] = 0;
4406
0
                }
4407
0
                const int nSrcLines = GDALGetRasterYSize(pahSrcDS[0]);
4408
0
                const bool bBottomBlankLine =
4409
0
                    DetectBlankBorder(nPixels, [nSrcLines](double, double y)
4410
0
                                      { return y <= nSrcLines; });
4411
4412
0
                for (int i = 0; i < nLines; ++i)
4413
0
                {
4414
0
                    adfX[i] = 0.5;
4415
0
                    adfY[i] = i + 0.5;
4416
0
                    adfZ[i] = 0;
4417
0
                }
4418
0
                const bool bLeftBlankCol = DetectBlankBorder(
4419
0
                    nLines, [](double x, double) { return x >= 0; });
4420
4421
0
                for (int i = 0; i < nLines; ++i)
4422
0
                {
4423
0
                    adfX[i] = nPixels - 0.5;
4424
0
                    adfY[i] = i + 0.5;
4425
0
                    adfZ[i] = 0;
4426
0
                }
4427
0
                const int nSrcCols = GDALGetRasterXSize(pahSrcDS[0]);
4428
0
                const bool bRightBlankCol =
4429
0
                    DetectBlankBorder(nLines, [nSrcCols](double x, double)
4430
0
                                      { return x <= nSrcCols; });
4431
4432
0
                if (!bTopBlankLine && !bBottomBlankLine && !bLeftBlankCol &&
4433
0
                    !bRightBlankCol)
4434
0
                    break;
4435
4436
0
                if (bTopBlankLine)
4437
0
                {
4438
0
                    if (psOptions->dfMaxY - psOptions->dfMinY <=
4439
0
                        2 * psOptions->dfYRes)
4440
0
                        break;
4441
0
                    psOptions->dfMaxY -= psOptions->dfYRes;
4442
0
                }
4443
0
                if (bBottomBlankLine)
4444
0
                {
4445
0
                    if (psOptions->dfMaxY - psOptions->dfMinY <=
4446
0
                        2 * psOptions->dfYRes)
4447
0
                        break;
4448
0
                    psOptions->dfMinY += psOptions->dfYRes;
4449
0
                }
4450
0
                if (bLeftBlankCol)
4451
0
                {
4452
0
                    if (psOptions->dfMaxX - psOptions->dfMinX <=
4453
0
                        2 * psOptions->dfXRes)
4454
0
                        break;
4455
0
                    psOptions->dfMinX += psOptions->dfXRes;
4456
0
                }
4457
0
                if (bRightBlankCol)
4458
0
                {
4459
0
                    if (psOptions->dfMaxX - psOptions->dfMinX <=
4460
0
                        2 * psOptions->dfXRes)
4461
0
                        break;
4462
0
                    psOptions->dfMaxX -= psOptions->dfXRes;
4463
0
                }
4464
0
            }
4465
0
        }
4466
4467
0
        if (!UpdateGeoTransformandAndPixelLines())
4468
0
            return nullptr;
4469
0
    }
4470
4471
0
    else if (psOptions->nForcePixels != 0 && psOptions->nForceLines != 0)
4472
0
    {
4473
0
        if (psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
4474
0
            psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0)
4475
0
        {
4476
0
            psOptions->dfMinX = dfWrkMinX;
4477
0
            psOptions->dfMaxX = dfWrkMaxX;
4478
0
            psOptions->dfMaxY = dfWrkMaxY;
4479
0
            psOptions->dfMinY = dfWrkMinY;
4480
0
        }
4481
4482
0
        psOptions->dfXRes =
4483
0
            (psOptions->dfMaxX - psOptions->dfMinX) / psOptions->nForcePixels;
4484
0
        psOptions->dfYRes =
4485
0
            (psOptions->dfMaxY - psOptions->dfMinY) / psOptions->nForceLines;
4486
4487
0
        adfDstGeoTransform[0] = psOptions->dfMinX;
4488
0
        adfDstGeoTransform[3] = psOptions->dfMaxY;
4489
0
        adfDstGeoTransform[1] = psOptions->dfXRes;
4490
0
        adfDstGeoTransform[5] = -psOptions->dfYRes;
4491
4492
0
        nPixels = psOptions->nForcePixels;
4493
0
        nLines = psOptions->nForceLines;
4494
0
    }
4495
4496
0
    else if (psOptions->nForcePixels != 0)
4497
0
    {
4498
0
        if (psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
4499
0
            psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0)
4500
0
        {
4501
0
            psOptions->dfMinX = dfWrkMinX;
4502
0
            psOptions->dfMaxX = dfWrkMaxX;
4503
0
            psOptions->dfMaxY = dfWrkMaxY;
4504
0
            psOptions->dfMinY = dfWrkMinY;
4505
0
        }
4506
4507
0
        psOptions->dfXRes =
4508
0
            (psOptions->dfMaxX - psOptions->dfMinX) / psOptions->nForcePixels;
4509
0
        psOptions->dfYRes = psOptions->dfXRes;
4510
4511
0
        adfDstGeoTransform[0] = psOptions->dfMinX;
4512
0
        adfDstGeoTransform[3] = psOptions->dfMaxY;
4513
0
        adfDstGeoTransform[1] = psOptions->dfXRes;
4514
0
        adfDstGeoTransform[5] = (psOptions->dfMaxY > psOptions->dfMinY)
4515
0
                                    ? -psOptions->dfYRes
4516
0
                                    : psOptions->dfYRes;
4517
4518
0
        nPixels = psOptions->nForcePixels;
4519
0
        const double dfLines = ComputeLinesFromResAndExtent();
4520
0
        if (dfLines > INT_MAX)
4521
0
        {
4522
0
            CPLError(CE_Failure, CPLE_AppDefined,
4523
0
                     "Too large output raster size: %d x %f", nPixels, dfLines);
4524
0
            return nullptr;
4525
0
        }
4526
0
        nLines = static_cast<int>(dfLines);
4527
0
    }
4528
4529
0
    else if (psOptions->nForceLines != 0)
4530
0
    {
4531
0
        if (psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
4532
0
            psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0)
4533
0
        {
4534
0
            psOptions->dfMinX = dfWrkMinX;
4535
0
            psOptions->dfMaxX = dfWrkMaxX;
4536
0
            psOptions->dfMaxY = dfWrkMaxY;
4537
0
            psOptions->dfMinY = dfWrkMinY;
4538
0
        }
4539
4540
0
        psOptions->dfYRes =
4541
0
            (psOptions->dfMaxY - psOptions->dfMinY) / psOptions->nForceLines;
4542
0
        psOptions->dfXRes = std::fabs(psOptions->dfYRes);
4543
4544
0
        adfDstGeoTransform[0] = psOptions->dfMinX;
4545
0
        adfDstGeoTransform[3] = psOptions->dfMaxY;
4546
0
        adfDstGeoTransform[1] = psOptions->dfXRes;
4547
0
        adfDstGeoTransform[5] = -psOptions->dfYRes;
4548
4549
0
        const double dfPixels = ComputePixelsFromResAndExtent();
4550
0
        nLines = psOptions->nForceLines;
4551
0
        if (dfPixels > INT_MAX)
4552
0
        {
4553
0
            CPLError(CE_Failure, CPLE_AppDefined,
4554
0
                     "Too large output raster size: %f x %d", dfPixels, nLines);
4555
0
            return nullptr;
4556
0
        }
4557
0
        nPixels = static_cast<int>(dfPixels);
4558
0
    }
4559
4560
0
    else if (psOptions->dfMinX != 0.0 || psOptions->dfMinY != 0.0 ||
4561
0
             psOptions->dfMaxX != 0.0 || psOptions->dfMaxY != 0.0)
4562
0
    {
4563
0
        psOptions->dfXRes = adfDstGeoTransform[1];
4564
0
        psOptions->dfYRes = fabs(adfDstGeoTransform[5]);
4565
4566
0
        const double dfPixels = ComputePixelsFromResAndExtent();
4567
0
        const double dfLines = ComputeLinesFromResAndExtent();
4568
0
        if (dfPixels > INT_MAX || dfLines > INT_MAX)
4569
0
        {
4570
0
            CPLError(CE_Failure, CPLE_AppDefined,
4571
0
                     "Too large output raster size: %f x %f", dfPixels,
4572
0
                     dfLines);
4573
0
            return nullptr;
4574
0
        }
4575
4576
0
        nPixels = static_cast<int>(dfPixels);
4577
0
        nLines = static_cast<int>(dfLines);
4578
4579
0
        psOptions->dfXRes = (psOptions->dfMaxX - psOptions->dfMinX) / nPixels;
4580
0
        psOptions->dfYRes = (psOptions->dfMaxY - psOptions->dfMinY) / nLines;
4581
4582
0
        adfDstGeoTransform[0] = psOptions->dfMinX;
4583
0
        adfDstGeoTransform[3] = psOptions->dfMaxY;
4584
0
        adfDstGeoTransform[1] = psOptions->dfXRes;
4585
0
        adfDstGeoTransform[5] = -psOptions->dfYRes;
4586
0
    }
4587
4588
0
    if (EQUAL(pszFormat, "GTiff"))
4589
0
    {
4590
4591
        /* --------------------------------------------------------------------
4592
         */
4593
        /*      Automatically set PHOTOMETRIC=RGB for GTiff when appropriate */
4594
        /* --------------------------------------------------------------------
4595
         */
4596
0
        if (apeColorInterpretations.size() >= 3 &&
4597
0
            apeColorInterpretations[0] == GCI_RedBand &&
4598
0
            apeColorInterpretations[1] == GCI_GreenBand &&
4599
0
            apeColorInterpretations[2] == GCI_BlueBand &&
4600
0
            aosCreateOptions.FetchNameValue("PHOTOMETRIC") == nullptr)
4601
0
        {
4602
0
            aosCreateOptions.SetNameValue("PHOTOMETRIC", "RGB");
4603
4604
            // Preserve potential ALPHA=PREMULTIPLIED from source alpha band
4605
0
            if (aosCreateOptions.FetchNameValue("ALPHA") == nullptr &&
4606
0
                apeColorInterpretations.size() == 4 &&
4607
0
                apeColorInterpretations[3] == GCI_AlphaBand &&
4608
0
                GDALGetRasterCount(pahSrcDS[0]) == 4)
4609
0
            {
4610
0
                const char *pszAlpha =
4611
0
                    GDALGetMetadataItem(GDALGetRasterBand(pahSrcDS[0], 4),
4612
0
                                        "ALPHA", GDAL_MDD_IMAGE_STRUCTURE);
4613
0
                if (pszAlpha)
4614
0
                {
4615
0
                    aosCreateOptions.SetNameValue("ALPHA", pszAlpha);
4616
0
                }
4617
0
            }
4618
0
        }
4619
4620
        /* The GTiff driver now supports writing band color interpretation */
4621
        /* in the TIFF_GDAL_METADATA tag */
4622
0
        bSetColorInterpretation = true;
4623
0
    }
4624
4625
    /* -------------------------------------------------------------------- */
4626
    /*      Create the output file.                                         */
4627
    /* -------------------------------------------------------------------- */
4628
0
    if (!psOptions->bQuiet)
4629
0
        printf("Creating output file that is %dP x %dL.\n", nPixels, nLines);
4630
4631
0
    hDstDS = GDALCreate(hDriver, pszFilename, nPixels, nLines, nDstBandCount,
4632
0
                        eDT, aosCreateOptions.List());
4633
4634
0
    if (hDstDS == nullptr)
4635
0
    {
4636
0
        return nullptr;
4637
0
    }
4638
4639
0
    if (psOptions->bDeleteOutputFileOnceCreated)
4640
0
    {
4641
0
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
4642
0
        GDALDeleteDataset(hDriver, pszFilename);
4643
0
    }
4644
4645
    /* -------------------------------------------------------------------- */
4646
    /*      Write out the projection definition.                            */
4647
    /* -------------------------------------------------------------------- */
4648
0
    const char *pszDstMethod = CSLFetchNameValue(papszTO, "DST_METHOD");
4649
0
    if (pszDstMethod == nullptr || !EQUAL(pszDstMethod, "NO_GEOTRANSFORM"))
4650
0
    {
4651
0
        OGRSpatialReference oTargetSRS;
4652
0
        oTargetSRS.SetFromUserInput(osThisTargetSRS);
4653
0
        oTargetSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4654
4655
0
        if (oTargetSRS.IsDynamic())
4656
0
        {
4657
0
            double dfCoordEpoch = CPLAtof(CSLFetchNameValueDef(
4658
0
                papszTO, "DST_COORDINATE_EPOCH",
4659
0
                CSLFetchNameValueDef(papszTO, "COORDINATE_EPOCH", "0")));
4660
0
            if (dfCoordEpoch == 0)
4661
0
            {
4662
0
                const OGRSpatialReferenceH hSrcSRS =
4663
0
                    GDALGetSpatialRef(pahSrcDS[0]);
4664
0
                const char *pszMethod = FetchSrcMethod(papszTO);
4665
0
                if (hSrcSRS &&
4666
0
                    (pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")))
4667
0
                {
4668
0
                    dfCoordEpoch = OSRGetCoordinateEpoch(hSrcSRS);
4669
0
                }
4670
0
            }
4671
0
            if (dfCoordEpoch > 0)
4672
0
                oTargetSRS.SetCoordinateEpoch(dfCoordEpoch);
4673
0
        }
4674
4675
0
        if (GDALSetSpatialRef(hDstDS, OGRSpatialReference::ToHandle(
4676
0
                                          &oTargetSRS)) == CE_Failure ||
4677
0
            GDALSetGeoTransform(hDstDS, adfDstGeoTransform) == CE_Failure)
4678
0
        {
4679
0
            GDALClose(hDstDS);
4680
0
            return nullptr;
4681
0
        }
4682
0
    }
4683
0
    else
4684
0
    {
4685
0
        adfDstGeoTransform[3] += adfDstGeoTransform[5] * nLines;
4686
0
        adfDstGeoTransform[5] = fabs(adfDstGeoTransform[5]);
4687
0
    }
4688
4689
0
    if (hUniqueTransformArg && bUpdateTransformerWithDestGT)
4690
0
    {
4691
0
        GDALSetGenImgProjTransformerDstGeoTransform(hUniqueTransformArg.get(),
4692
0
                                                    adfDstGeoTransform);
4693
4694
0
        void *pTransformerArg = hUniqueTransformArg.get();
4695
0
        if (GDALIsTransformer(pTransformerArg,
4696
0
                              GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
4697
0
        {
4698
            // Detect if there is a change of coordinate operation in the area of
4699
            // interest. The underlying proj_trans_get_last_used_operation() is
4700
            // quite costly due to using proj_clone() internally, so only do that
4701
            // on a restricted set of points.
4702
0
            GDALGenImgProjTransformInfo *psTransformInfo{
4703
0
                static_cast<GDALGenImgProjTransformInfo *>(pTransformerArg)};
4704
0
            GDALTransformerInfo *psInfo = &psTransformInfo->sTI;
4705
4706
0
            void *pReprojectArg = psTransformInfo->pReprojectArg;
4707
0
            if (GDALIsTransformer(pReprojectArg,
4708
0
                                  GDAL_APPROX_TRANSFORMER_CLASS_NAME))
4709
0
            {
4710
0
                const auto *pApproxInfo =
4711
0
                    static_cast<const GDALApproxTransformInfo *>(pReprojectArg);
4712
0
                pReprojectArg = pApproxInfo->pBaseCBData;
4713
0
            }
4714
4715
0
            if (GDALIsTransformer(pReprojectArg,
4716
0
                                  GDAL_REPROJECTION_TRANSFORMER_CLASS_NAME))
4717
0
            {
4718
0
                const GDALReprojectionTransformInfo *psRTI =
4719
0
                    static_cast<const GDALReprojectionTransformInfo *>(
4720
0
                        pReprojectArg);
4721
0
                if (psRTI->poReverseTransform)
4722
0
                {
4723
0
                    std::vector<double> adfX, adfY, adfZ;
4724
0
                    std::vector<int> abSuccess;
4725
4726
0
                    GDALDatasetH hSrcDS = pahSrcDS[0];
4727
4728
                    // Sample points on a N x N grid in the source raster
4729
0
                    constexpr int N = 10;
4730
0
                    const int nSrcXSize = GDALGetRasterXSize(hSrcDS);
4731
0
                    const int nSrcYSize = GDALGetRasterYSize(hSrcDS);
4732
0
                    for (int j = 0; j <= N; ++j)
4733
0
                    {
4734
0
                        for (int i = 0; i <= N; ++i)
4735
0
                        {
4736
0
                            adfX.push_back(static_cast<double>(i) / N *
4737
0
                                           nSrcXSize);
4738
0
                            adfY.push_back(static_cast<double>(j) / N *
4739
0
                                           nSrcYSize);
4740
0
                            adfZ.push_back(0);
4741
0
                            abSuccess.push_back(0);
4742
0
                        }
4743
0
                    }
4744
4745
0
                    {
4746
0
                        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
4747
4748
                        // Transform from source raster coordinates to target raster
4749
                        // coordinates
4750
0
                        psInfo->pfnTransform(hUniqueTransformArg.get(), FALSE,
4751
0
                                             static_cast<int>(adfX.size()),
4752
0
                                             &adfX[0], &adfY[0], &adfZ[0],
4753
0
                                             &abSuccess[0]);
4754
4755
0
                        const int nDstXSize = nPixels;
4756
0
                        const int nDstYSize = nLines;
4757
4758
                        // Clamp target raster coordinates
4759
0
                        for (size_t i = 0; i < adfX.size(); ++i)
4760
0
                        {
4761
0
                            if (adfX[i] < 0)
4762
0
                                adfX[i] = 0;
4763
0
                            if (adfX[i] > nDstXSize)
4764
0
                                adfX[i] = nDstXSize;
4765
0
                            if (adfY[i] < 0)
4766
0
                                adfY[i] = 0;
4767
0
                            if (adfY[i] > nDstYSize)
4768
0
                                adfY[i] = nDstYSize;
4769
0
                        }
4770
4771
                        // Start recording if different coordinate operations are
4772
                        // going to be used
4773
0
                        OGRProjCTDifferentOperationsStart(
4774
0
                            psRTI->poReverseTransform);
4775
4776
                        // Transform back to source raster coordinates.
4777
0
                        psInfo->pfnTransform(hUniqueTransformArg.get(), TRUE,
4778
0
                                             static_cast<int>(adfX.size()),
4779
0
                                             &adfX[0], &adfY[0], &adfZ[0],
4780
0
                                             &abSuccess[0]);
4781
0
                    }
4782
4783
0
                    if (OGRProjCTDifferentOperationsUsed(
4784
0
                            psRTI->poReverseTransform))
4785
0
                    {
4786
0
                        const char *pszTransformOption =
4787
0
                            psOptions->bInvokedFromGdalAlgorithm
4788
0
                                ? "--transform-option"
4789
0
                                : "-to";
4790
0
                        const char *pszCoordinateOperation =
4791
0
                            psOptions->bInvokedFromGdalAlgorithm
4792
0
                                ? ""
4793
0
                                : ", or specify a particular coordinate "
4794
0
                                  "operation with -ct";
4795
4796
0
                        CPLError(
4797
0
                            CE_Warning, CPLE_AppDefined,
4798
0
                            "Several coordinate operations are going to be "
4799
0
                            "used. Artifacts may appear. You may consider "
4800
0
                            "using the %s ALLOW_BALLPARK=NO and/or "
4801
0
                            "%s ONLY_BEST=YES transform options%s",
4802
0
                            pszTransformOption, pszTransformOption,
4803
0
                            pszCoordinateOperation);
4804
0
                    }
4805
4806
                    // Stop recording
4807
0
                    OGRProjCTDifferentOperationsStop(psRTI->poReverseTransform);
4808
0
                }
4809
0
            }
4810
0
        }
4811
0
    }
4812
4813
    /* -------------------------------------------------------------------- */
4814
    /*      Try to set color interpretation of source bands to target       */
4815
    /*      dataset.                                                        */
4816
    /*      FIXME? We should likely do that for other drivers than VRT &    */
4817
    /*      GTiff  but it might create spurious .aux.xml files (at least    */
4818
    /*      with HFA, and netCDF)                                           */
4819
    /* -------------------------------------------------------------------- */
4820
0
    if (bVRT || bSetColorInterpretation)
4821
0
    {
4822
0
        int nBandsToCopy = static_cast<int>(apeColorInterpretations.size());
4823
0
        if (psOptions->bEnableSrcAlpha)
4824
0
            nBandsToCopy--;
4825
0
        for (int iBand = 0; iBand < nBandsToCopy; iBand++)
4826
0
        {
4827
0
            GDALSetRasterColorInterpretation(
4828
0
                GDALGetRasterBand(hDstDS, iBand + 1),
4829
0
                apeColorInterpretations[iBand]);
4830
0
        }
4831
0
    }
4832
4833
    /* -------------------------------------------------------------------- */
4834
    /*      Try to set color interpretation of output file alpha band.      */
4835
    /* -------------------------------------------------------------------- */
4836
0
    if (psOptions->bEnableDstAlpha)
4837
0
    {
4838
0
        GDALSetRasterColorInterpretation(
4839
0
            GDALGetRasterBand(hDstDS, nDstBandCount), GCI_AlphaBand);
4840
0
    }
4841
4842
    /* -------------------------------------------------------------------- */
4843
    /*      Copy the raster attribute table, if required.                   */
4844
    /* -------------------------------------------------------------------- */
4845
0
    if (hRAT != nullptr)
4846
0
    {
4847
0
        GDALSetDefaultRAT(GDALGetRasterBand(hDstDS, 1), hRAT);
4848
0
    }
4849
4850
    /* -------------------------------------------------------------------- */
4851
    /*      Copy the color table, if required.                              */
4852
    /* -------------------------------------------------------------------- */
4853
0
    if (poCT)
4854
0
    {
4855
0
        GDALSetRasterColorTable(GDALGetRasterBand(hDstDS, 1),
4856
0
                                GDALColorTable::ToHandle(poCT.get()));
4857
0
    }
4858
4859
    /* -------------------------------------------------------------------- */
4860
    /*      Copy scale/offset if found on source                            */
4861
    /* -------------------------------------------------------------------- */
4862
0
    if (nSrcCount == 1)
4863
0
    {
4864
0
        GDALDataset *poSrcDS = GDALDataset::FromHandle(pahSrcDS[0]);
4865
0
        GDALDataset *poDstDS = GDALDataset::FromHandle(hDstDS);
4866
4867
0
        int nBandsToCopy = nDstBandCount;
4868
0
        if (psOptions->bEnableDstAlpha)
4869
0
            nBandsToCopy--;
4870
0
        nBandsToCopy = std::min(nBandsToCopy, poSrcDS->GetRasterCount());
4871
4872
0
        for (int i = 0; i < nBandsToCopy; i++)
4873
0
        {
4874
0
            auto poSrcBand = poSrcDS->GetRasterBand(
4875
0
                psOptions->anSrcBands.empty() ? i + 1
4876
0
                                              : psOptions->anSrcBands[i]);
4877
0
            auto poDstBand = poDstDS->GetRasterBand(
4878
0
                psOptions->anDstBands.empty() ? i + 1
4879
0
                                              : psOptions->anDstBands[i]);
4880
0
            if (poSrcBand && poDstBand)
4881
0
            {
4882
0
                int bHasScale = FALSE;
4883
0
                const double dfScale = poSrcBand->GetScale(&bHasScale);
4884
0
                if (bHasScale)
4885
0
                    poDstBand->SetScale(dfScale);
4886
4887
0
                int bHasOffset = FALSE;
4888
0
                const double dfOffset = poSrcBand->GetOffset(&bHasOffset);
4889
0
                if (bHasOffset)
4890
0
                    poDstBand->SetOffset(dfOffset);
4891
0
            }
4892
0
        }
4893
0
    }
4894
4895
0
    return hDstDS;
4896
0
}
4897
4898
/************************************************************************/
4899
/*                      GeoTransform_Transformer()                      */
4900
/*                                                                      */
4901
/*      Convert points from georef coordinates to pixel/line based      */
4902
/*      on a geotransform.                                              */
4903
/************************************************************************/
4904
namespace
4905
{
4906
class CutlineTransformer final : public OGRCoordinateTransformation
4907
{
4908
    CPL_DISALLOW_COPY_ASSIGN(CutlineTransformer)
4909
4910
  public:
4911
    void *hSrcImageTransformer = nullptr;
4912
4913
    explicit CutlineTransformer(void *hTransformArg)
4914
0
        : hSrcImageTransformer(hTransformArg)
4915
0
    {
4916
0
    }
4917
4918
    const OGRSpatialReference *GetSourceCS() const override
4919
0
    {
4920
0
        return nullptr;
4921
0
    }
4922
4923
    const OGRSpatialReference *GetTargetCS() const override
4924
0
    {
4925
0
        return nullptr;
4926
0
    }
4927
4928
    ~CutlineTransformer() override;
4929
4930
    virtual int Transform(size_t nCount, double *x, double *y, double *z,
4931
                          double * /* t */, int *pabSuccess) override
4932
0
    {
4933
0
        CPLAssert(nCount <=
4934
0
                  static_cast<size_t>(std::numeric_limits<int>::max()));
4935
0
        return GDALGenImgProjTransform(hSrcImageTransformer, TRUE,
4936
0
                                       static_cast<int>(nCount), x, y, z,
4937
0
                                       pabSuccess);
4938
0
    }
4939
4940
    OGRCoordinateTransformation *Clone() const override
4941
0
    {
4942
0
        return new CutlineTransformer(
4943
0
            GDALCloneTransformer(hSrcImageTransformer));
4944
0
    }
4945
4946
    OGRCoordinateTransformation *GetInverse() const override
4947
0
    {
4948
0
        return nullptr;
4949
0
    }
4950
};
4951
4952
CutlineTransformer::~CutlineTransformer()
4953
0
{
4954
0
    GDALDestroyTransformer(hSrcImageTransformer);
4955
0
}
4956
}  // namespace
4957
4958
static double GetMaximumSegmentLength(const OGRGeometry *poGeom)
4959
0
{
4960
0
    switch (wkbFlatten(poGeom->getGeometryType()))
4961
0
    {
4962
0
        case wkbLineString:
4963
0
        {
4964
0
            const OGRLineString *poLS = poGeom->toLineString();
4965
0
            double dfMaxSquaredLength = 0.0;
4966
0
            for (int i = 0; i < poLS->getNumPoints() - 1; i++)
4967
0
            {
4968
0
                double dfDeltaX = poLS->getX(i + 1) - poLS->getX(i);
4969
0
                double dfDeltaY = poLS->getY(i + 1) - poLS->getY(i);
4970
0
                double dfSquaredLength =
4971
0
                    dfDeltaX * dfDeltaX + dfDeltaY * dfDeltaY;
4972
0
                dfMaxSquaredLength =
4973
0
                    std::max(dfMaxSquaredLength, dfSquaredLength);
4974
0
            }
4975
0
            return sqrt(dfMaxSquaredLength);
4976
0
        }
4977
4978
0
        case wkbPolygon:
4979
0
        {
4980
0
            const OGRPolygon *poPoly = poGeom->toPolygon();
4981
0
            double dfMaxLength = 0;
4982
0
            for (const auto *poRing : *poPoly)
4983
0
            {
4984
0
                dfMaxLength =
4985
0
                    std::max(dfMaxLength, GetMaximumSegmentLength(poRing));
4986
0
            }
4987
0
            return dfMaxLength;
4988
0
        }
4989
4990
0
        case wkbMultiPolygon:
4991
0
        {
4992
0
            const OGRMultiPolygon *poMP = poGeom->toMultiPolygon();
4993
0
            double dfMaxLength = 0.0;
4994
0
            for (const auto *poPoly : *poMP)
4995
0
            {
4996
0
                dfMaxLength =
4997
0
                    std::max(dfMaxLength, GetMaximumSegmentLength(poPoly));
4998
0
            }
4999
0
            return dfMaxLength;
5000
0
        }
5001
5002
0
        default:
5003
0
            CPLAssert(false);
5004
0
            return 0.0;
5005
0
    }
5006
0
}
5007
5008
/************************************************************************/
5009
/*                      RemoveZeroWidthSlivers()                        */
5010
/*                                                                      */
5011
/* Such slivers can cause issues after reprojection.                    */
5012
/************************************************************************/
5013
5014
static void RemoveZeroWidthSlivers(OGRGeometry *poGeom)
5015
0
{
5016
0
    const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
5017
0
    if (eType == wkbMultiPolygon)
5018
0
    {
5019
0
        auto poMP = poGeom->toMultiPolygon();
5020
0
        int nNumGeometries = poMP->getNumGeometries();
5021
0
        for (int i = 0; i < nNumGeometries; /* incremented in loop */)
5022
0
        {
5023
0
            auto poPoly = poMP->getGeometryRef(i);
5024
0
            RemoveZeroWidthSlivers(poPoly);
5025
0
            if (poPoly->IsEmpty())
5026
0
            {
5027
0
                CPLDebug("WARP",
5028
0
                         "RemoveZeroWidthSlivers: removing empty polygon");
5029
0
                poMP->removeGeometry(i, /* bDelete = */ true);
5030
0
                --nNumGeometries;
5031
0
            }
5032
0
            else
5033
0
            {
5034
0
                ++i;
5035
0
            }
5036
0
        }
5037
0
    }
5038
0
    else if (eType == wkbPolygon)
5039
0
    {
5040
0
        auto poPoly = poGeom->toPolygon();
5041
0
        if (auto poExteriorRing = poPoly->getExteriorRing())
5042
0
        {
5043
0
            RemoveZeroWidthSlivers(poExteriorRing);
5044
0
            if (poExteriorRing->getNumPoints() < 4)
5045
0
            {
5046
0
                poPoly->empty();
5047
0
                return;
5048
0
            }
5049
0
        }
5050
0
        int nNumInteriorRings = poPoly->getNumInteriorRings();
5051
0
        for (int i = 0; i < nNumInteriorRings; /* incremented in loop */)
5052
0
        {
5053
0
            auto poRing = poPoly->getInteriorRing(i);
5054
0
            RemoveZeroWidthSlivers(poRing);
5055
0
            if (poRing->getNumPoints() < 4)
5056
0
            {
5057
0
                CPLDebug(
5058
0
                    "WARP",
5059
0
                    "RemoveZeroWidthSlivers: removing empty interior ring");
5060
0
                constexpr int OFFSET_EXTERIOR_RING = 1;
5061
0
                poPoly->removeRing(i + OFFSET_EXTERIOR_RING,
5062
0
                                   /* bDelete = */ true);
5063
0
                --nNumInteriorRings;
5064
0
            }
5065
0
            else
5066
0
            {
5067
0
                ++i;
5068
0
            }
5069
0
        }
5070
0
    }
5071
0
    else if (eType == wkbLineString)
5072
0
    {
5073
0
        OGRLineString *poLS = poGeom->toLineString();
5074
0
        int numPoints = poLS->getNumPoints();
5075
0
        for (int i = 1; i < numPoints - 1;)
5076
0
        {
5077
0
            const double x1 = poLS->getX(i - 1);
5078
0
            const double y1 = poLS->getY(i - 1);
5079
0
            const double x2 = poLS->getX(i);
5080
0
            const double y2 = poLS->getY(i);
5081
0
            const double x3 = poLS->getX(i + 1);
5082
0
            const double y3 = poLS->getY(i + 1);
5083
0
            const double dx1 = x2 - x1;
5084
0
            const double dy1 = y2 - y1;
5085
0
            const double dx2 = x3 - x2;
5086
0
            const double dy2 = y3 - y2;
5087
0
            const double scalar_product = dx1 * dx2 + dy1 * dy2;
5088
0
            const double square_scalar_product =
5089
0
                scalar_product * scalar_product;
5090
0
            const double square_norm1 = dx1 * dx1 + dy1 * dy1;
5091
0
            const double square_norm2 = dx2 * dx2 + dy2 * dy2;
5092
0
            const double square_norm1_mult_norm2 = square_norm1 * square_norm2;
5093
0
            if (scalar_product < 0 &&
5094
0
                fabs(square_scalar_product - square_norm1_mult_norm2) <=
5095
0
                    1e-15 * square_norm1_mult_norm2)
5096
0
            {
5097
0
                CPLDebug("WARP",
5098
0
                         "RemoveZeroWidthSlivers: removing point %.10g %.10g",
5099
0
                         x2, y2);
5100
0
                poLS->removePoint(i);
5101
0
                numPoints--;
5102
0
            }
5103
0
            else
5104
0
            {
5105
0
                ++i;
5106
0
            }
5107
0
        }
5108
0
    }
5109
0
}
5110
5111
/************************************************************************/
5112
/*                      TransformCutlineToSource()                      */
5113
/*                                                                      */
5114
/*      Transform cutline from its SRS to source pixel/line coordinates.*/
5115
/************************************************************************/
5116
static CPLErr TransformCutlineToSource(GDALDataset *poSrcDS,
5117
                                       OGRGeometry *poCutline,
5118
                                       char ***ppapszWarpOptions,
5119
                                       CSLConstList papszTO_In)
5120
5121
0
{
5122
0
    RemoveZeroWidthSlivers(poCutline);
5123
5124
0
    auto poMultiPolygon = std::unique_ptr<OGRGeometry>(poCutline->clone());
5125
5126
    /* -------------------------------------------------------------------- */
5127
    /*      Checkout that if there's a cutline SRS, there's also a raster   */
5128
    /*      one.                                                            */
5129
    /* -------------------------------------------------------------------- */
5130
0
    std::unique_ptr<OGRSpatialReference> poRasterSRS;
5131
0
    const CPLString osProjection =
5132
0
        GetSrcDSProjection(GDALDataset::ToHandle(poSrcDS), papszTO_In);
5133
0
    if (!osProjection.empty())
5134
0
    {
5135
0
        poRasterSRS = std::make_unique<OGRSpatialReference>();
5136
0
        poRasterSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
5137
0
        if (poRasterSRS->SetFromUserInput(osProjection) != OGRERR_NONE)
5138
0
        {
5139
0
            poRasterSRS.reset();
5140
0
        }
5141
0
    }
5142
5143
0
    std::unique_ptr<OGRSpatialReference> poDstSRS;
5144
0
    const char *pszThisTargetSRS = CSLFetchNameValue(papszTO_In, "DST_SRS");
5145
0
    if (pszThisTargetSRS)
5146
0
    {
5147
0
        poDstSRS = std::make_unique<OGRSpatialReference>();
5148
0
        poDstSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
5149
0
        if (poDstSRS->SetFromUserInput(pszThisTargetSRS) != OGRERR_NONE)
5150
0
        {
5151
0
            return CE_Failure;
5152
0
        }
5153
0
    }
5154
0
    else if (poRasterSRS)
5155
0
    {
5156
0
        poDstSRS.reset(poRasterSRS->Clone());
5157
0
    }
5158
5159
    /* -------------------------------------------------------------------- */
5160
    /*      Extract the cutline SRS.                                        */
5161
    /* -------------------------------------------------------------------- */
5162
0
    const OGRSpatialReference *poCutlineSRS =
5163
0
        poMultiPolygon->getSpatialReference();
5164
5165
    /* -------------------------------------------------------------------- */
5166
    /*      Detect if there's no transform at all involved, in which case   */
5167
    /*      we can avoid densification.                                     */
5168
    /* -------------------------------------------------------------------- */
5169
0
    bool bMayNeedDensify = true;
5170
0
    if (poRasterSRS && poCutlineSRS && poRasterSRS->IsSame(poCutlineSRS) &&
5171
0
        poSrcDS->GetGCPCount() == 0 && !poSrcDS->GetMetadata(GDAL_MDD_RPC) &&
5172
0
        !poSrcDS->GetMetadata(GDAL_MDD_GEOLOCATION) &&
5173
0
        !CSLFetchNameValue(papszTO_In, "GEOLOC_ARRAY") &&
5174
0
        !CSLFetchNameValue(papszTO_In, "SRC_GEOLOC_ARRAY"))
5175
0
    {
5176
0
        CPLStringList aosTOTmp(papszTO_In);
5177
0
        aosTOTmp.SetNameValue("SRC_SRS", nullptr);
5178
0
        aosTOTmp.SetNameValue("DST_SRS", nullptr);
5179
0
        if (aosTOTmp.size() == 0)
5180
0
        {
5181
0
            bMayNeedDensify = false;
5182
0
        }
5183
0
    }
5184
5185
    /* -------------------------------------------------------------------- */
5186
    /*      Compare source raster SRS and cutline SRS                       */
5187
    /* -------------------------------------------------------------------- */
5188
0
    if (poRasterSRS && poCutlineSRS)
5189
0
    {
5190
        /* OK, we will reproject */
5191
0
    }
5192
0
    else if (poRasterSRS && !poCutlineSRS)
5193
0
    {
5194
0
        CPLError(
5195
0
            CE_Warning, CPLE_AppDefined,
5196
0
            "the source raster dataset has a SRS, but the cutline features\n"
5197
0
            "not.  We assume that the cutline coordinates are expressed in the "
5198
0
            "destination SRS.\n"
5199
0
            "If not, cutline results may be incorrect.");
5200
0
    }
5201
0
    else if (!poRasterSRS && poCutlineSRS)
5202
0
    {
5203
0
        CPLError(CE_Warning, CPLE_AppDefined,
5204
0
                 "the input vector layer has a SRS, but the source raster "
5205
0
                 "dataset does not.\n"
5206
0
                 "Cutline results may be incorrect.");
5207
0
    }
5208
5209
0
    auto poCTCutlineToSrc = CreateCTCutlineToSrc(
5210
0
        poRasterSRS.get(), poDstSRS.get(), poCutlineSRS, papszTO_In);
5211
5212
0
    CPLStringList aosTO(papszTO_In);
5213
5214
0
    if (pszThisTargetSRS && !osProjection.empty())
5215
0
    {
5216
        // Avoid any reprojection when using the GenImgProjTransformer
5217
0
        aosTO.SetNameValue("DST_SRS", osProjection.c_str());
5218
0
    }
5219
0
    aosTO.SetNameValue("SRC_COORDINATE_EPOCH", nullptr);
5220
0
    aosTO.SetNameValue("DST_COORDINATE_EPOCH", nullptr);
5221
0
    aosTO.SetNameValue("COORDINATE_OPERATION", nullptr);
5222
5223
    /* -------------------------------------------------------------------- */
5224
    /*      It may be unwise to let the mask geometry be re-wrapped by      */
5225
    /*      the CENTER_LONG machinery as this can easily screw up world     */
5226
    /*      spanning masks and invert the mask topology.                    */
5227
    /* -------------------------------------------------------------------- */
5228
0
    aosTO.SetNameValue("INSERT_CENTER_LONG", "FALSE");
5229
5230
    /* -------------------------------------------------------------------- */
5231
    /*      Transform the geometry to pixel/line coordinates.               */
5232
    /* -------------------------------------------------------------------- */
5233
    /* The cutline transformer will *invert* the hSrcImageTransformer */
5234
    /* so it will convert from the source SRS to the source pixel/line */
5235
    /* coordinates */
5236
0
    CutlineTransformer oTransformer(GDALCreateGenImgProjTransformer2(
5237
0
        GDALDataset::ToHandle(poSrcDS), nullptr, aosTO.List()));
5238
5239
0
    if (oTransformer.hSrcImageTransformer == nullptr)
5240
0
    {
5241
0
        return CE_Failure;
5242
0
    }
5243
5244
    // Some transforms like RPC can transform a valid geometry into an invalid
5245
    // one if the node density of the input geometry isn't sufficient before
5246
    // reprojection. So after an initial reprojection, we check that the
5247
    // maximum length of a segment is no longer than 1 pixel, and if not,
5248
    // we densify the input geometry before doing a new reprojection
5249
0
    const double dfMaxLengthInSpatUnits =
5250
0
        GetMaximumSegmentLength(poMultiPolygon.get());
5251
0
    OGRErr eErr = OGRERR_NONE;
5252
0
    if (poCTCutlineToSrc)
5253
0
    {
5254
0
        poMultiPolygon.reset(OGRGeometryFactory::transformWithOptions(
5255
0
            poMultiPolygon.get(), poCTCutlineToSrc.get(), nullptr));
5256
0
        if (!poMultiPolygon)
5257
0
        {
5258
0
            eErr = OGRERR_FAILURE;
5259
0
            poMultiPolygon.reset(poCutline->clone());
5260
0
            poMultiPolygon->transform(poCTCutlineToSrc.get());
5261
0
        }
5262
0
    }
5263
0
    if (poMultiPolygon->transform(&oTransformer) != OGRERR_NONE)
5264
0
    {
5265
0
        CPLError(CE_Failure, CPLE_AppDefined,
5266
0
                 "poMultiPolygon->transform(&oTransformer) failed at line %d",
5267
0
                 __LINE__);
5268
0
        eErr = OGRERR_FAILURE;
5269
0
    }
5270
0
    const double dfInitialMaxLengthInPixels =
5271
0
        GetMaximumSegmentLength(poMultiPolygon.get());
5272
5273
0
    CPLPushErrorHandler(CPLQuietErrorHandler);
5274
0
    const bool bWasValidInitially =
5275
0
        ValidateCutline(poMultiPolygon.get(), false);
5276
0
    CPLPopErrorHandler();
5277
0
    if (!bWasValidInitially)
5278
0
    {
5279
0
        CPLDebug("WARP", "Cutline is not valid after initial reprojection");
5280
0
        char *pszWKT = nullptr;
5281
0
        poMultiPolygon->exportToWkt(&pszWKT);
5282
0
        CPLDebug("GDALWARP", "WKT = \"%s\"", pszWKT ? pszWKT : "(null)");
5283
0
        CPLFree(pszWKT);
5284
0
    }
5285
5286
0
    bool bDensify = false;
5287
0
    if (bMayNeedDensify && eErr == OGRERR_NONE &&
5288
0
        dfInitialMaxLengthInPixels > 1.0)
5289
0
    {
5290
0
        const char *pszDensifyCutline =
5291
0
            CPLGetConfigOption("GDALWARP_DENSIFY_CUTLINE", "YES");
5292
0
        if (EQUAL(pszDensifyCutline, "ONLY_IF_INVALID"))
5293
0
        {
5294
0
            bDensify = (OGRGeometryFactory::haveGEOS() && !bWasValidInitially);
5295
0
        }
5296
0
        else if (CSLFetchNameValue(*ppapszWarpOptions, "CUTLINE_BLEND_DIST") !=
5297
0
                     nullptr &&
5298
0
                 CPLGetConfigOption("GDALWARP_DENSIFY_CUTLINE", nullptr) ==
5299
0
                     nullptr)
5300
0
        {
5301
            // TODO: we should only emit this message if a
5302
            // transform/reprojection will be actually done
5303
0
            CPLDebug("WARP",
5304
0
                     "Densification of cutline could perhaps be useful but as "
5305
0
                     "CUTLINE_BLEND_DIST is used, this could be very slow. So "
5306
0
                     "disabled "
5307
0
                     "unless GDALWARP_DENSIFY_CUTLINE=YES is explicitly "
5308
0
                     "specified as configuration option");
5309
0
        }
5310
0
        else
5311
0
        {
5312
0
            bDensify = CPLTestBool(pszDensifyCutline);
5313
0
        }
5314
0
    }
5315
0
    if (bDensify)
5316
0
    {
5317
0
        CPLDebug("WARP",
5318
0
                 "Cutline maximum segment size was %.0f pixel after "
5319
0
                 "reprojection to source coordinates.",
5320
0
                 dfInitialMaxLengthInPixels);
5321
5322
        // Densify and reproject with the aim of having a 1 pixel density
5323
0
        double dfSegmentSize =
5324
0
            dfMaxLengthInSpatUnits / dfInitialMaxLengthInPixels;
5325
0
        const int MAX_ITERATIONS = 10;
5326
0
        for (int i = 0; i < MAX_ITERATIONS; i++)
5327
0
        {
5328
0
            poMultiPolygon.reset(poCutline->clone());
5329
0
            poMultiPolygon->segmentize(dfSegmentSize);
5330
0
            if (i == MAX_ITERATIONS - 1)
5331
0
            {
5332
0
                char *pszWKT = nullptr;
5333
0
                poMultiPolygon->exportToWkt(&pszWKT);
5334
0
                CPLDebug("WARP",
5335
0
                         "WKT of polygon after densification with segment size "
5336
0
                         "= %f: %s",
5337
0
                         dfSegmentSize, pszWKT);
5338
0
                CPLFree(pszWKT);
5339
0
            }
5340
0
            eErr = OGRERR_NONE;
5341
0
            if (poCTCutlineToSrc)
5342
0
            {
5343
0
                poMultiPolygon.reset(OGRGeometryFactory::transformWithOptions(
5344
0
                    poMultiPolygon.get(), poCTCutlineToSrc.get(), nullptr));
5345
0
                if (!poMultiPolygon)
5346
0
                {
5347
0
                    eErr = OGRERR_FAILURE;
5348
0
                    break;
5349
0
                }
5350
0
            }
5351
0
            if (poMultiPolygon->transform(&oTransformer) != OGRERR_NONE)
5352
0
                eErr = OGRERR_FAILURE;
5353
0
            if (eErr == OGRERR_NONE)
5354
0
            {
5355
0
                const double dfMaxLengthInPixels =
5356
0
                    GetMaximumSegmentLength(poMultiPolygon.get());
5357
0
                if (bWasValidInitially)
5358
0
                {
5359
                    // In some cases, the densification itself results in a
5360
                    // reprojected invalid polygon due to the non-linearity of
5361
                    // RPC DEM transformation, so in those cases, try a less
5362
                    // dense cutline
5363
0
                    CPLPushErrorHandler(CPLQuietErrorHandler);
5364
0
                    const bool bIsValid =
5365
0
                        ValidateCutline(poMultiPolygon.get(), false);
5366
0
                    CPLPopErrorHandler();
5367
0
                    if (!bIsValid)
5368
0
                    {
5369
0
                        if (i == MAX_ITERATIONS - 1)
5370
0
                        {
5371
0
                            char *pszWKT = nullptr;
5372
0
                            poMultiPolygon->exportToWkt(&pszWKT);
5373
0
                            CPLDebug("WARP",
5374
0
                                     "After densification, cutline maximum "
5375
0
                                     "segment size is now %.0f pixel, "
5376
0
                                     "but cutline is invalid. %s",
5377
0
                                     dfMaxLengthInPixels, pszWKT);
5378
0
                            CPLFree(pszWKT);
5379
0
                            break;
5380
0
                        }
5381
0
                        CPLDebug("WARP",
5382
0
                                 "After densification, cutline maximum segment "
5383
0
                                 "size is now %.0f pixel, "
5384
0
                                 "but cutline is invalid. So trying a less "
5385
0
                                 "dense cutline.",
5386
0
                                 dfMaxLengthInPixels);
5387
0
                        dfSegmentSize *= 2;
5388
0
                        continue;
5389
0
                    }
5390
0
                }
5391
0
                CPLDebug("WARP",
5392
0
                         "After densification, cutline maximum segment size is "
5393
0
                         "now %.0f pixel.",
5394
0
                         dfMaxLengthInPixels);
5395
0
            }
5396
0
            break;
5397
0
        }
5398
0
    }
5399
5400
0
    if (eErr == OGRERR_FAILURE)
5401
0
    {
5402
0
        if (CPLTestBool(
5403
0
                CPLGetConfigOption("GDALWARP_IGNORE_BAD_CUTLINE", "NO")))
5404
0
            CPLError(CE_Warning, CPLE_AppDefined,
5405
0
                     "Cutline transformation failed");
5406
0
        else
5407
0
        {
5408
0
            CPLError(CE_Failure, CPLE_AppDefined,
5409
0
                     "Cutline transformation failed");
5410
0
            return CE_Failure;
5411
0
        }
5412
0
    }
5413
0
    else if (!ValidateCutline(poMultiPolygon.get(), true))
5414
0
    {
5415
0
        return CE_Failure;
5416
0
    }
5417
5418
    // Optimization: if the cutline contains the footprint of the source
5419
    // dataset, no need to use the cutline.
5420
0
    if (OGRGeometryFactory::haveGEOS()
5421
0
#ifdef DEBUG
5422
        // Env var just for debugging purposes
5423
0
        && !CPLTestBool(CPLGetConfigOption(
5424
0
               "GDALWARP_SKIP_CUTLINE_CONTAINMENT_TEST", "NO"))
5425
0
#endif
5426
0
    )
5427
0
    {
5428
0
        const double dfCutlineBlendDist = CPLAtof(CSLFetchNameValueDef(
5429
0
            *ppapszWarpOptions, "CUTLINE_BLEND_DIST", "0"));
5430
0
        auto poRing = std::make_unique<OGRLinearRing>();
5431
0
        poRing->addPoint(-dfCutlineBlendDist, -dfCutlineBlendDist);
5432
0
        poRing->addPoint(-dfCutlineBlendDist,
5433
0
                         dfCutlineBlendDist + poSrcDS->GetRasterYSize());
5434
0
        poRing->addPoint(dfCutlineBlendDist + poSrcDS->GetRasterXSize(),
5435
0
                         dfCutlineBlendDist + poSrcDS->GetRasterYSize());
5436
0
        poRing->addPoint(dfCutlineBlendDist + poSrcDS->GetRasterXSize(),
5437
0
                         -dfCutlineBlendDist);
5438
0
        poRing->addPoint(-dfCutlineBlendDist, -dfCutlineBlendDist);
5439
0
        OGRPolygon oSrcDSFootprint;
5440
0
        oSrcDSFootprint.addRing(std::move(poRing));
5441
0
        OGREnvelope sSrcDSEnvelope;
5442
0
        oSrcDSFootprint.getEnvelope(&sSrcDSEnvelope);
5443
0
        OGREnvelope sCutlineEnvelope;
5444
0
        poMultiPolygon->getEnvelope(&sCutlineEnvelope);
5445
0
        if (sCutlineEnvelope.Contains(sSrcDSEnvelope) &&
5446
0
            poMultiPolygon->Contains(&oSrcDSFootprint))
5447
0
        {
5448
0
            CPLDebug("WARP", "Source dataset fully contained within cutline.");
5449
0
            return CE_None;
5450
0
        }
5451
0
    }
5452
5453
    /* -------------------------------------------------------------------- */
5454
    /*      Convert aggregate geometry into WKT.                            */
5455
    /* -------------------------------------------------------------------- */
5456
0
    char *pszWKT = nullptr;
5457
0
    poMultiPolygon->exportToWkt(&pszWKT);
5458
    // fprintf(stderr, "WKT = \"%s\"\n", pszWKT ? pszWKT : "(null)");
5459
5460
0
    *ppapszWarpOptions = CSLSetNameValue(*ppapszWarpOptions, "CUTLINE", pszWKT);
5461
0
    CPLFree(pszWKT);
5462
0
    return CE_None;
5463
0
}
5464
5465
static void RemoveConflictingMetadata(GDALMajorObjectH hObj,
5466
                                      CSLConstList papszSrcMetadata,
5467
                                      const char *pszValueConflict)
5468
0
{
5469
0
    if (hObj == nullptr)
5470
0
        return;
5471
5472
0
    for (const auto &[pszKey, pszValue] :
5473
0
         cpl::IterateNameValue(papszSrcMetadata))
5474
0
    {
5475
0
        const char *pszValueComp = GDALGetMetadataItem(hObj, pszKey, nullptr);
5476
0
        if (pszValueComp == nullptr || (!EQUAL(pszValue, pszValueComp) &&
5477
0
                                        !EQUAL(pszValueComp, pszValueConflict)))
5478
0
        {
5479
0
            if (STARTS_WITH(pszKey, "STATISTICS_"))
5480
0
                GDALSetMetadataItem(hObj, pszKey, nullptr, nullptr);
5481
0
            else
5482
0
                GDALSetMetadataItem(hObj, pszKey, pszValueConflict, nullptr);
5483
0
        }
5484
0
    }
5485
0
}
5486
5487
/************************************************************************/
5488
/*                              IsValidSRS                              */
5489
/************************************************************************/
5490
5491
static bool IsValidSRS(const char *pszUserInput)
5492
5493
0
{
5494
0
    OGRSpatialReferenceH hSRS;
5495
0
    bool bRes = true;
5496
5497
0
    hSRS = OSRNewSpatialReference(nullptr);
5498
0
    if (OSRSetFromUserInput(hSRS, pszUserInput) != OGRERR_NONE)
5499
0
    {
5500
0
        bRes = false;
5501
0
    }
5502
5503
0
    OSRDestroySpatialReference(hSRS);
5504
5505
0
    return bRes;
5506
0
}
5507
5508
/************************************************************************/
5509
/*                    GDALWarpAppOptionsGetParser()                     */
5510
/************************************************************************/
5511
5512
static std::unique_ptr<GDALArgumentParser>
5513
GDALWarpAppOptionsGetParser(GDALWarpAppOptions *psOptions,
5514
                            GDALWarpAppOptionsForBinary *psOptionsForBinary)
5515
0
{
5516
0
    auto argParser = std::make_unique<GDALArgumentParser>(
5517
0
        "gdalwarp", /* bForBinary=*/psOptionsForBinary != nullptr);
5518
5519
0
    argParser->add_description(_("Image reprojection and warping utility."));
5520
5521
0
    argParser->add_epilog(
5522
0
        _("For more details, consult https://gdal.org/programs/gdalwarp.html"));
5523
5524
0
    argParser->add_quiet_argument(
5525
0
        psOptionsForBinary ? &psOptionsForBinary->bQuiet : nullptr);
5526
5527
0
    argParser->add_argument("-overwrite")
5528
0
        .flag()
5529
0
        .action(
5530
0
            [psOptionsForBinary](const std::string &)
5531
0
            {
5532
0
                if (psOptionsForBinary)
5533
0
                    psOptionsForBinary->bOverwrite = true;
5534
0
            })
5535
0
        .help(_("Overwrite the target dataset if it already exists."));
5536
5537
0
    argParser->add_output_format_argument(psOptions->osFormat);
5538
5539
0
    argParser->add_argument("-co")
5540
0
        .metavar("<NAME>=<VALUE>")
5541
0
        .append()
5542
0
        .action(
5543
0
            [psOptions, psOptionsForBinary](const std::string &s)
5544
0
            {
5545
0
                psOptions->aosCreateOptions.AddString(s.c_str());
5546
0
                psOptions->bCreateOutput = true;
5547
5548
0
                if (psOptionsForBinary)
5549
0
                    psOptionsForBinary->aosCreateOptions.AddString(s.c_str());
5550
0
            })
5551
0
        .help(_("Creation option(s)."));
5552
5553
0
    argParser->add_argument("-s_srs")
5554
0
        .metavar("<srs_def>")
5555
0
        .action(
5556
0
            [psOptions](const std::string &s)
5557
0
            {
5558
0
                if (!IsValidSRS(s.c_str()))
5559
0
                {
5560
0
                    throw std::invalid_argument("Invalid SRS for -s_srs");
5561
0
                }
5562
0
                psOptions->aosTransformerOptions.SetNameValue("SRC_SRS",
5563
0
                                                              s.c_str());
5564
0
            })
5565
0
        .help(_("Set source spatial reference."));
5566
5567
0
    argParser->add_argument("-t_srs")
5568
0
        .metavar("<srs_def>")
5569
0
        .action(
5570
0
            [psOptions](const std::string &s)
5571
0
            {
5572
0
                if (!IsValidSRS(s.c_str()))
5573
0
                {
5574
0
                    throw std::invalid_argument("Invalid SRS for -t_srs");
5575
0
                }
5576
0
                psOptions->aosTransformerOptions.SetNameValue("DST_SRS",
5577
0
                                                              s.c_str());
5578
0
            })
5579
0
        .help(_("Set target spatial reference."));
5580
5581
0
    {
5582
0
        auto &group = argParser->add_mutually_exclusive_group();
5583
0
        group.add_argument("-srcalpha")
5584
0
            .flag()
5585
0
            .store_into(psOptions->bEnableSrcAlpha)
5586
0
            .help(_("Force the last band of a source image to be considered as "
5587
0
                    "a source alpha band."));
5588
0
        group.add_argument("-nosrcalpha")
5589
0
            .flag()
5590
0
            .store_into(psOptions->bDisableSrcAlpha)
5591
0
            .help(_("Prevent the alpha band of a source image to be considered "
5592
0
                    "as such."));
5593
0
    }
5594
5595
0
    argParser->add_argument("-dstalpha")
5596
0
        .flag()
5597
0
        .store_into(psOptions->bEnableDstAlpha)
5598
0
        .help(_("Create an output alpha band to identify nodata "
5599
0
                "(unset/transparent) pixels."));
5600
5601
    // Parsing of that option is done in a preprocessing stage
5602
0
    argParser->add_argument("-tr")
5603
0
        .metavar("<xres> <yres>|square")
5604
0
        .help(_("Target resolution."));
5605
5606
0
    argParser->add_argument("-ts")
5607
0
        .metavar("<width> <height>")
5608
0
        .nargs(2)
5609
0
        .scan<'i', int>()
5610
0
        .help(_("Set output file size in pixels and lines."));
5611
5612
0
    argParser->add_argument("-te")
5613
0
        .metavar("<xmin> <ymin> <xmax> <ymax>")
5614
0
        .nargs(4)
5615
0
        .scan<'g', double>()
5616
0
        .help(_("Set georeferenced extents of output file to be created."));
5617
5618
0
    argParser->add_argument("-te_srs")
5619
0
        .metavar("<srs_def>")
5620
0
        .action(
5621
0
            [psOptions](const std::string &s)
5622
0
            {
5623
0
                if (!IsValidSRS(s.c_str()))
5624
0
                {
5625
0
                    throw std::invalid_argument("Invalid SRS for -te_srs");
5626
0
                }
5627
0
                psOptions->osTE_SRS = s;
5628
0
                psOptions->bCreateOutput = true;
5629
0
            })
5630
0
        .help(_("Set source spatial reference."));
5631
5632
0
    argParser->add_argument("-r")
5633
0
        .metavar("near|bilinear|cubic|cubicspline|lanczos|average|rms|mode|min|"
5634
0
                 "max|med|q1|q3|sum")
5635
0
        .action(
5636
0
            [psOptions](const std::string &s)
5637
0
            {
5638
0
                GDALGetWarpResampleAlg(s.c_str(), psOptions->eResampleAlg,
5639
0
                                       /*bThrow=*/true);
5640
0
                psOptions->bResampleAlgSpecifiedByUser = true;
5641
0
            })
5642
0
        .help(_("Resampling method to use."));
5643
5644
0
    argParser->add_output_type_argument(psOptions->eOutputType);
5645
5646
    ///////////////////////////////////////////////////////////////////////
5647
0
    argParser->add_group("Advanced options");
5648
5649
0
    const auto CheckSingleMethod = [psOptions]()
5650
0
    {
5651
0
        const char *pszMethod =
5652
0
            FetchSrcMethod(psOptions->aosTransformerOptions);
5653
0
        if (pszMethod)
5654
0
            CPLError(CE_Warning, CPLE_IllegalArg,
5655
0
                     "Warning: only one METHOD can be used. Method %s is "
5656
0
                     "already defined.",
5657
0
                     pszMethod);
5658
0
        const char *pszMAX_GCP_ORDER =
5659
0
            psOptions->aosTransformerOptions.FetchNameValue("MAX_GCP_ORDER");
5660
0
        if (pszMAX_GCP_ORDER)
5661
0
            CPLError(CE_Warning, CPLE_IllegalArg,
5662
0
                     "Warning: only one METHOD can be used. -order %s "
5663
0
                     "option was specified, so it is likely that "
5664
0
                     "GCP_POLYNOMIAL was implied.",
5665
0
                     pszMAX_GCP_ORDER);
5666
0
    };
5667
5668
0
    argParser->add_argument("-wo")
5669
0
        .metavar("<NAME>=<VALUE>")
5670
0
        .append()
5671
0
        .action([psOptions](const std::string &s)
5672
0
                { psOptions->aosWarpOptions.AddString(s.c_str()); })
5673
0
        .help(_("Warping option(s)."));
5674
5675
0
    argParser->add_argument("-multi")
5676
0
        .flag()
5677
0
        .store_into(psOptions->bMulti)
5678
0
        .help(_("Multithreaded input/output."));
5679
5680
0
    argParser->add_argument("-s_coord_epoch")
5681
0
        .metavar("<epoch>")
5682
0
        .action(
5683
0
            [psOptions](const std::string &s)
5684
0
            {
5685
0
                psOptions->aosTransformerOptions.SetNameValue(
5686
0
                    "SRC_COORDINATE_EPOCH", s.c_str());
5687
0
            })
5688
0
        .help(_("Assign a coordinate epoch, linked with the source SRS when "
5689
0
                "-s_srs is used."));
5690
5691
0
    argParser->add_argument("-t_coord_epoch")
5692
0
        .metavar("<epoch>")
5693
0
        .action(
5694
0
            [psOptions](const std::string &s)
5695
0
            {
5696
0
                psOptions->aosTransformerOptions.SetNameValue(
5697
0
                    "DST_COORDINATE_EPOCH", s.c_str());
5698
0
            })
5699
0
        .help(_("Assign a coordinate epoch, linked with the output SRS when "
5700
0
                "-t_srs is used."));
5701
5702
0
    argParser->add_argument("-ct")
5703
0
        .metavar("<string>")
5704
0
        .action(
5705
0
            [psOptions](const std::string &s)
5706
0
            {
5707
0
                psOptions->aosTransformerOptions.SetNameValue(
5708
0
                    "COORDINATE_OPERATION", s.c_str());
5709
0
            })
5710
0
        .help(_("Set a coordinate transformation."));
5711
5712
0
    {
5713
0
        auto &group = argParser->add_mutually_exclusive_group();
5714
0
        group.add_argument("-tps")
5715
0
            .flag()
5716
0
            .action(
5717
0
                [psOptions, CheckSingleMethod](const std::string &)
5718
0
                {
5719
0
                    CheckSingleMethod();
5720
0
                    psOptions->aosTransformerOptions.SetNameValue("SRC_METHOD",
5721
0
                                                                  "GCP_TPS");
5722
0
                })
5723
0
            .help(_("Force use of thin plate spline transformer based on "
5724
0
                    "available GCPs."));
5725
5726
0
        group.add_argument("-rpc")
5727
0
            .flag()
5728
0
            .action(
5729
0
                [psOptions, CheckSingleMethod](const std::string &)
5730
0
                {
5731
0
                    CheckSingleMethod();
5732
0
                    psOptions->aosTransformerOptions.SetNameValue("SRC_METHOD",
5733
0
                                                                  GDAL_MDD_RPC);
5734
0
                })
5735
0
            .help(_("Force use of RPCs."));
5736
5737
0
        group.add_argument("-geoloc")
5738
0
            .flag()
5739
0
            .action(
5740
0
                [psOptions, CheckSingleMethod](const std::string &)
5741
0
                {
5742
0
                    CheckSingleMethod();
5743
0
                    psOptions->aosTransformerOptions.SetNameValue(
5744
0
                        "SRC_METHOD", "GEOLOC_ARRAY");
5745
0
                })
5746
0
            .help(_("Force use of Geolocation Arrays."));
5747
0
    }
5748
5749
0
    argParser->add_argument("-order")
5750
0
        .metavar("<1|2|3>")
5751
0
        .choices("1", "2", "3")
5752
0
        .action(
5753
0
            [psOptions](const std::string &s)
5754
0
            {
5755
0
                const char *pszMethod =
5756
0
                    FetchSrcMethod(psOptions->aosTransformerOptions);
5757
0
                if (pszMethod)
5758
0
                    CPLError(
5759
0
                        CE_Warning, CPLE_IllegalArg,
5760
0
                        "Warning: only one METHOD can be used. Method %s is "
5761
0
                        "already defined",
5762
0
                        pszMethod);
5763
0
                psOptions->aosTransformerOptions.SetNameValue("MAX_GCP_ORDER",
5764
0
                                                              s.c_str());
5765
0
            })
5766
0
        .help(_("Order of polynomial used for GCP warping."));
5767
5768
    // Parsing of that option is done in a preprocessing stage
5769
0
    argParser->add_argument("-refine_gcps")
5770
0
        .metavar("<tolerance> [<minimum_gcps>]")
5771
0
        .help(_("Refines the GCPs by automatically eliminating outliers."));
5772
5773
0
    argParser->add_argument("-to")
5774
0
        .metavar("<NAME>=<VALUE>")
5775
0
        .append()
5776
0
        .action([psOptions](const std::string &s)
5777
0
                { psOptions->aosTransformerOptions.AddString(s.c_str()); })
5778
0
        .help(_("Transform option(s)."));
5779
5780
0
    argParser->add_argument("-et")
5781
0
        .metavar("<err_threshold>")
5782
0
        .store_into(psOptions->dfErrorThreshold)
5783
0
        .action(
5784
0
            [psOptions](const std::string &)
5785
0
            {
5786
0
                if (psOptions->dfErrorThreshold < 0)
5787
0
                {
5788
0
                    throw std::invalid_argument(
5789
0
                        "Invalid value for error threshold");
5790
0
                }
5791
0
                psOptions->aosWarpOptions.AddString(CPLSPrintf(
5792
0
                    "ERROR_THRESHOLD=%.16g", psOptions->dfErrorThreshold));
5793
0
            })
5794
0
        .help(_("Error threshold."));
5795
5796
0
    argParser->add_argument("-wm")
5797
0
        .metavar("<memory_in_mb>")
5798
0
        .action(
5799
0
            [psOptions](const std::string &s)
5800
0
            {
5801
0
                bool bUnitSpecified = false;
5802
0
                GIntBig nBytes;
5803
0
                if (CPLParseMemorySize(s.c_str(), &nBytes, &bUnitSpecified) ==
5804
0
                    CE_None)
5805
0
                {
5806
0
                    if (!bUnitSpecified && nBytes < 10000)
5807
0
                    {
5808
0
                        nBytes *= (1024 * 1024);
5809
0
                    }
5810
0
                    psOptions->dfWarpMemoryLimit = static_cast<double>(nBytes);
5811
0
                }
5812
0
                else
5813
0
                {
5814
0
                    throw std::invalid_argument("Failed to parse value of -wm");
5815
0
                }
5816
0
            })
5817
0
        .help(_("Set max warp memory."));
5818
5819
0
    argParser->add_argument("-srcnodata")
5820
0
        .metavar("\"<value>[ <value>]...\"")
5821
0
        .store_into(psOptions->osSrcNodata)
5822
0
        .help(_("Nodata masking values for input bands."));
5823
5824
0
    argParser->add_argument("-dstnodata")
5825
0
        .metavar("\"<value>[ <value>]...\"")
5826
0
        .store_into(psOptions->osDstNodata)
5827
0
        .help(_("Nodata masking values for output bands."));
5828
5829
0
    argParser->add_argument("-tap")
5830
0
        .flag()
5831
0
        .store_into(psOptions->bTargetAlignedPixels)
5832
0
        .help(_("Force target aligned pixels."));
5833
5834
0
    argParser->add_argument("-wt")
5835
0
        .metavar("Byte|Int8|[U]Int{16|32|64}|CInt{16|32}|[C]Float{32|64}")
5836
0
        .action(
5837
0
            [psOptions](const std::string &s)
5838
0
            {
5839
0
                psOptions->eWorkingType = GDALGetDataTypeByName(s.c_str());
5840
0
                if (psOptions->eWorkingType == GDT_Unknown)
5841
0
                {
5842
0
                    throw std::invalid_argument(
5843
0
                        std::string("Unknown output pixel type: ").append(s));
5844
0
                }
5845
0
            })
5846
0
        .help(_("Working data type."));
5847
5848
    // Non-documented alias of -r nearest
5849
0
    argParser->add_argument("-rn")
5850
0
        .flag()
5851
0
        .hidden()
5852
0
        .action([psOptions](const std::string &)
5853
0
                { psOptions->eResampleAlg = GRA_NearestNeighbour; })
5854
0
        .help(_("Nearest neighbour resampling."));
5855
5856
    // Non-documented alias of -r bilinear
5857
0
    argParser->add_argument("-rb")
5858
0
        .flag()
5859
0
        .hidden()
5860
0
        .action([psOptions](const std::string &)
5861
0
                { psOptions->eResampleAlg = GRA_Bilinear; })
5862
0
        .help(_("Bilinear resampling."));
5863
5864
    // Non-documented alias of -r cubic
5865
0
    argParser->add_argument("-rc")
5866
0
        .flag()
5867
0
        .hidden()
5868
0
        .action([psOptions](const std::string &)
5869
0
                { psOptions->eResampleAlg = GRA_Cubic; })
5870
0
        .help(_("Cubic resampling."));
5871
5872
    // Non-documented alias of -r cubicspline
5873
0
    argParser->add_argument("-rcs")
5874
0
        .flag()
5875
0
        .hidden()
5876
0
        .action([psOptions](const std::string &)
5877
0
                { psOptions->eResampleAlg = GRA_CubicSpline; })
5878
0
        .help(_("Cubic spline resampling."));
5879
5880
    // Non-documented alias of -r lanczos
5881
0
    argParser->add_argument("-rl")
5882
0
        .flag()
5883
0
        .hidden()
5884
0
        .action([psOptions](const std::string &)
5885
0
                { psOptions->eResampleAlg = GRA_Lanczos; })
5886
0
        .help(_("Lanczos resampling."));
5887
5888
    // Non-documented alias of -r average
5889
0
    argParser->add_argument("-ra")
5890
0
        .flag()
5891
0
        .hidden()
5892
0
        .action([psOptions](const std::string &)
5893
0
                { psOptions->eResampleAlg = GRA_Average; })
5894
0
        .help(_("Average resampling."));
5895
5896
    // Non-documented alias of -r rms
5897
0
    argParser->add_argument("-rrms")
5898
0
        .flag()
5899
0
        .hidden()
5900
0
        .action([psOptions](const std::string &)
5901
0
                { psOptions->eResampleAlg = GRA_RMS; })
5902
0
        .help(_("RMS resampling."));
5903
5904
    // Non-documented alias of -r mode
5905
0
    argParser->add_argument("-rm")
5906
0
        .flag()
5907
0
        .hidden()
5908
0
        .action([psOptions](const std::string &)
5909
0
                { psOptions->eResampleAlg = GRA_Mode; })
5910
0
        .help(_("Mode resampling."));
5911
5912
0
    argParser->add_argument("-cutline")
5913
0
        .metavar("<datasource>|<WKT>")
5914
0
        .store_into(psOptions->osCutlineDSNameOrWKT)
5915
0
        .help(_("Enable use of a blend cutline from the name of a vector "
5916
0
                "dataset or a WKT geometry."));
5917
5918
0
    argParser->add_argument("-cutline_srs")
5919
0
        .metavar("<srs_def>")
5920
0
        .action(
5921
0
            [psOptions](const std::string &s)
5922
0
            {
5923
0
                if (!IsValidSRS(s.c_str()))
5924
0
                {
5925
0
                    throw std::invalid_argument("Invalid SRS for -cutline_srs");
5926
0
                }
5927
0
                psOptions->osCutlineSRS = s;
5928
0
            })
5929
0
        .help(_("Sets/overrides cutline SRS."));
5930
5931
0
    argParser->add_argument("-cwhere")
5932
0
        .metavar("<expression>")
5933
0
        .store_into(psOptions->osCWHERE)
5934
0
        .help(_("Restrict desired cutline features based on attribute query."));
5935
5936
0
    {
5937
0
        auto &group = argParser->add_mutually_exclusive_group();
5938
0
        group.add_argument("-cl")
5939
0
            .metavar("<layername>")
5940
0
            .store_into(psOptions->osCLayer)
5941
0
            .help(_("Select the named layer from the cutline datasource."));
5942
5943
0
        group.add_argument("-csql")
5944
0
            .metavar("<query>")
5945
0
            .store_into(psOptions->osCSQL)
5946
0
            .help(_("Select cutline features using an SQL query."));
5947
0
    }
5948
5949
0
    argParser->add_argument("-cblend")
5950
0
        .metavar("<distance>")
5951
0
        .action(
5952
0
            [psOptions](const std::string &s)
5953
0
            {
5954
0
                psOptions->aosWarpOptions.SetNameValue("CUTLINE_BLEND_DIST",
5955
0
                                                       s.c_str());
5956
0
            })
5957
0
        .help(_(
5958
0
            "Set a blend distance to use to blend over cutlines (in pixels)."));
5959
5960
0
    argParser->add_argument("-crop_to_cutline")
5961
0
        .flag()
5962
0
        .action(
5963
0
            [psOptions](const std::string &)
5964
0
            {
5965
0
                psOptions->bCropToCutline = true;
5966
0
                psOptions->bCreateOutput = true;
5967
0
            })
5968
0
        .help(_("Crop the extent of the target dataset to the extent of the "
5969
0
                "cutline."));
5970
5971
0
    argParser->add_argument("-nomd")
5972
0
        .flag()
5973
0
        .action(
5974
0
            [psOptions](const std::string &)
5975
0
            {
5976
0
                psOptions->bCopyMetadata = false;
5977
0
                psOptions->bCopyBandInfo = false;
5978
0
            })
5979
0
        .help(_("Do not copy metadata."));
5980
5981
0
    argParser->add_argument("-cvmd")
5982
0
        .metavar("<meta_conflict_value>")
5983
0
        .store_into(psOptions->osMDConflictValue)
5984
0
        .help(_("Value to set metadata items that conflict between source "
5985
0
                "datasets."));
5986
5987
0
    argParser->add_argument("-setci")
5988
0
        .flag()
5989
0
        .store_into(psOptions->bSetColorInterpretation)
5990
0
        .help(_("Set the color interpretation of the bands of the target "
5991
0
                "dataset from the source dataset."));
5992
5993
0
    argParser->add_open_options_argument(
5994
0
        psOptionsForBinary ? &(psOptionsForBinary->aosOpenOptions) : nullptr);
5995
5996
0
    argParser->add_argument("-doo")
5997
0
        .metavar("<NAME>=<VALUE>")
5998
0
        .append()
5999
0
        .action(
6000
0
            [psOptionsForBinary](const std::string &s)
6001
0
            {
6002
0
                if (psOptionsForBinary)
6003
0
                    psOptionsForBinary->aosDestOpenOptions.AddString(s.c_str());
6004
0
            })
6005
0
        .help(_("Open option(s) for output dataset."));
6006
6007
0
    argParser->add_argument("-ovr")
6008
0
        .metavar("<level>|AUTO|AUTO-<n>|NONE")
6009
0
        .action(
6010
0
            [psOptions](const std::string &s)
6011
0
            {
6012
0
                const char *pszOvLevel = s.c_str();
6013
0
                if (EQUAL(pszOvLevel, "AUTO"))
6014
0
                    psOptions->nOvLevel = OVR_LEVEL_AUTO;
6015
0
                else if (STARTS_WITH_CI(pszOvLevel, "AUTO-"))
6016
0
                    psOptions->nOvLevel =
6017
0
                        OVR_LEVEL_AUTO - atoi(pszOvLevel + strlen("AUTO-"));
6018
0
                else if (EQUAL(pszOvLevel, "NONE"))
6019
0
                    psOptions->nOvLevel = OVR_LEVEL_NONE;
6020
0
                else if (CPLGetValueType(pszOvLevel) == CPL_VALUE_INTEGER)
6021
0
                    psOptions->nOvLevel = atoi(pszOvLevel);
6022
0
                else
6023
0
                {
6024
0
                    throw std::invalid_argument(CPLSPrintf(
6025
0
                        "Invalid value '%s' for -ov option", pszOvLevel));
6026
0
                }
6027
0
            })
6028
0
        .help(_("Specify which overview level of source files must be used."));
6029
6030
0
    {
6031
0
        auto &group = argParser->add_mutually_exclusive_group();
6032
0
        group.add_argument("-vshift")
6033
0
            .flag()
6034
0
            .store_into(psOptions->bVShift)
6035
0
            .help(_("Force the use of vertical shift."));
6036
0
        group.add_argument("-novshift", "-novshiftgrid")
6037
0
            .flag()
6038
0
            .store_into(psOptions->bNoVShift)
6039
0
            .help(_("Disable the use of vertical shift."));
6040
0
    }
6041
6042
0
    argParser->add_input_format_argument(
6043
0
        psOptionsForBinary ? &psOptionsForBinary->aosAllowedInputDrivers
6044
0
                           : nullptr);
6045
6046
0
    argParser->add_argument("-b", "-srcband")
6047
0
        .metavar("<band>")
6048
0
        .append()
6049
0
        .store_into(psOptions->anSrcBands)
6050
0
        .help(_("Specify input band(s) number to warp."));
6051
6052
0
    argParser->add_argument("-dstband")
6053
0
        .metavar("<band>")
6054
0
        .append()
6055
0
        .store_into(psOptions->anDstBands)
6056
0
        .help(_("Specify the output band number in which to warp."));
6057
6058
    // Undocumented option used by gdal vector * algorithms
6059
0
    argParser->add_argument("--invoked-from-gdal-algorithm")
6060
0
        .store_into(psOptions->bInvokedFromGdalAlgorithm)
6061
0
        .hidden();
6062
6063
0
    if (psOptionsForBinary)
6064
0
    {
6065
0
        argParser->add_argument("src_dataset_name")
6066
0
            .metavar("<src_dataset_name>")
6067
0
            .nargs(argparse::nargs_pattern::at_least_one)
6068
0
            .action([psOptionsForBinary](const std::string &s)
6069
0
                    { psOptionsForBinary->aosSrcFiles.AddString(s.c_str()); })
6070
0
            .help(_("Input dataset(s)."));
6071
6072
0
        argParser->add_argument("dst_dataset_name")
6073
0
            .metavar("<dst_dataset_name>")
6074
0
            .store_into(psOptionsForBinary->osDstFilename)
6075
0
            .help(_("Output dataset."));
6076
0
    }
6077
6078
0
    return argParser;
6079
0
}
6080
6081
/************************************************************************/
6082
/*                     GDALWarpAppGetParserUsage()                      */
6083
/************************************************************************/
6084
6085
std::string GDALWarpAppGetParserUsage()
6086
0
{
6087
0
    try
6088
0
    {
6089
0
        GDALWarpAppOptions sOptions;
6090
0
        GDALWarpAppOptionsForBinary sOptionsForBinary;
6091
0
        auto argParser =
6092
0
            GDALWarpAppOptionsGetParser(&sOptions, &sOptionsForBinary);
6093
0
        return argParser->usage();
6094
0
    }
6095
0
    catch (const std::exception &err)
6096
0
    {
6097
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
6098
0
                 err.what());
6099
0
        return std::string();
6100
0
    }
6101
0
}
6102
6103
/************************************************************************/
6104
/*                       GDALWarpAppOptionsNew()                        */
6105
/************************************************************************/
6106
6107
#ifndef CheckHasEnoughAdditionalArgs_defined
6108
#define CheckHasEnoughAdditionalArgs_defined
6109
6110
static bool CheckHasEnoughAdditionalArgs(CSLConstList papszArgv, int i,
6111
                                         int nExtraArg, int nArgc)
6112
0
{
6113
0
    if (i + nExtraArg >= nArgc)
6114
0
    {
6115
0
        CPLError(CE_Failure, CPLE_IllegalArg,
6116
0
                 "%s option requires %d argument%s", papszArgv[i], nExtraArg,
6117
0
                 nExtraArg == 1 ? "" : "s");
6118
0
        return false;
6119
0
    }
6120
0
    return true;
6121
0
}
6122
#endif
6123
6124
#define CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(nExtraArg)                            \
6125
0
    if (!CheckHasEnoughAdditionalArgs(papszArgv, i, nExtraArg, nArgc))         \
6126
0
    {                                                                          \
6127
0
        return nullptr;                                                        \
6128
0
    }
6129
6130
/**
6131
 * Allocates a GDALWarpAppOptions struct.
6132
 *
6133
 * @param papszArgv NULL terminated list of options (potentially including
6134
 * filename and open options too), or NULL. The accepted options are the ones of
6135
 * the <a href="/programs/gdalwarp.html">gdalwarp</a> utility.
6136
 * @param psOptionsForBinary (output) may be NULL (and should generally be
6137
 * NULL), otherwise (gdal_translate_bin.cpp use case) must be allocated with
6138
 *                           GDALWarpAppOptionsForBinaryNew() prior to this
6139
 * function. Will be filled with potentially present filename, open options,...
6140
 * @return pointer to the allocated GDALWarpAppOptions struct. Must be freed
6141
 * with GDALWarpAppOptionsFree().
6142
 *
6143
 * @since GDAL 2.1
6144
 */
6145
6146
GDALWarpAppOptions *
6147
GDALWarpAppOptionsNew(char **papszArgv,
6148
                      GDALWarpAppOptionsForBinary *psOptionsForBinary)
6149
0
{
6150
0
    auto psOptions = std::make_unique<GDALWarpAppOptions>();
6151
6152
0
    psOptions->aosArgs.Assign(CSLDuplicate(papszArgv), true);
6153
6154
    /* -------------------------------------------------------------------- */
6155
    /*      Pre-processing for custom syntax that ArgumentParser does not   */
6156
    /*      support.                                                        */
6157
    /* -------------------------------------------------------------------- */
6158
6159
0
    CPLStringList aosArgv;
6160
0
    const int nArgc = CSLCount(papszArgv);
6161
0
    for (int i = 0;
6162
0
         i < nArgc && papszArgv != nullptr && papszArgv[i] != nullptr; i++)
6163
0
    {
6164
0
        if (EQUAL(papszArgv[i], "-refine_gcps"))
6165
0
        {
6166
0
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
6167
0
            psOptions->aosTransformerOptions.SetNameValue("REFINE_TOLERANCE",
6168
0
                                                          papszArgv[++i]);
6169
0
            if (CPLAtof(papszArgv[i]) < 0)
6170
0
            {
6171
0
                CPLError(CE_Failure, CPLE_IllegalArg,
6172
0
                         "The tolerance for -refine_gcps may not be negative.");
6173
0
                return nullptr;
6174
0
            }
6175
0
            if (i < nArgc - 1 && atoi(papszArgv[i + 1]) >= 0 &&
6176
0
                isdigit(static_cast<unsigned char>(papszArgv[i + 1][0])))
6177
0
            {
6178
0
                psOptions->aosTransformerOptions.SetNameValue(
6179
0
                    "REFINE_MINIMUM_GCPS", papszArgv[++i]);
6180
0
            }
6181
0
            else
6182
0
            {
6183
0
                psOptions->aosTransformerOptions.SetNameValue(
6184
0
                    "REFINE_MINIMUM_GCPS", "-1");
6185
0
            }
6186
0
        }
6187
0
        else if (EQUAL(papszArgv[i], "-tr") && i + 1 < nArgc &&
6188
0
                 EQUAL(papszArgv[i + 1], "square"))
6189
0
        {
6190
0
            ++i;
6191
0
            psOptions->bSquarePixels = true;
6192
0
            psOptions->bCreateOutput = true;
6193
0
        }
6194
0
        else if (EQUAL(papszArgv[i], "-tr"))
6195
0
        {
6196
0
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(2);
6197
0
            psOptions->dfXRes = CPLAtofM(papszArgv[++i]);
6198
0
            psOptions->dfYRes = fabs(CPLAtofM(papszArgv[++i]));
6199
0
            if (psOptions->dfXRes == 0 || psOptions->dfYRes == 0)
6200
0
            {
6201
0
                CPLError(CE_Failure, CPLE_IllegalArg,
6202
0
                         "Wrong value for -tr parameters.");
6203
0
                return nullptr;
6204
0
            }
6205
0
            psOptions->bCreateOutput = true;
6206
0
        }
6207
        // argparser will be confused if the value of a string argument
6208
        // starts with a negative sign.
6209
0
        else if (EQUAL(papszArgv[i], "-srcnodata") && i + 1 < nArgc)
6210
0
        {
6211
0
            ++i;
6212
0
            psOptions->osSrcNodata = papszArgv[i];
6213
0
        }
6214
        // argparser will be confused if the value of a string argument
6215
        // starts with a negative sign.
6216
0
        else if (EQUAL(papszArgv[i], "-dstnodata") && i + 1 < nArgc)
6217
0
        {
6218
0
            ++i;
6219
0
            psOptions->osDstNodata = papszArgv[i];
6220
0
        }
6221
0
        else
6222
0
        {
6223
0
            aosArgv.AddString(papszArgv[i]);
6224
0
        }
6225
0
    }
6226
6227
0
    try
6228
0
    {
6229
0
        auto argParser =
6230
0
            GDALWarpAppOptionsGetParser(psOptions.get(), psOptionsForBinary);
6231
6232
0
        argParser->parse_args_without_binary_name(aosArgv.List());
6233
6234
0
        if (auto oTS = argParser->present<std::vector<int>>("-ts"))
6235
0
        {
6236
0
            psOptions->nForcePixels = (*oTS)[0];
6237
0
            psOptions->nForceLines = (*oTS)[1];
6238
0
            psOptions->bCreateOutput = true;
6239
0
        }
6240
6241
0
        if (auto oTE = argParser->present<std::vector<double>>("-te"))
6242
0
        {
6243
0
            psOptions->dfMinX = (*oTE)[0];
6244
0
            psOptions->dfMinY = (*oTE)[1];
6245
0
            psOptions->dfMaxX = (*oTE)[2];
6246
0
            psOptions->dfMaxY = (*oTE)[3];
6247
0
            psOptions->aosTransformerOptions.SetNameValue(
6248
0
                "TARGET_EXTENT",
6249
0
                CPLSPrintf("%.17g,%.17g,%.17g,%.17g", psOptions->dfMinX,
6250
0
                           psOptions->dfMinY, psOptions->dfMaxX,
6251
0
                           psOptions->dfMaxY));
6252
0
            psOptions->bCreateOutput = true;
6253
0
        }
6254
6255
0
        if (!psOptions->anDstBands.empty() &&
6256
0
            psOptions->anSrcBands.size() != psOptions->anDstBands.size())
6257
0
        {
6258
0
            CPLError(
6259
0
                CE_Failure, CPLE_IllegalArg,
6260
0
                "-srcband should be specified as many times as -dstband is");
6261
0
            return nullptr;
6262
0
        }
6263
0
        else if (!psOptions->anSrcBands.empty() &&
6264
0
                 psOptions->anDstBands.empty())
6265
0
        {
6266
0
            for (int i = 0; i < static_cast<int>(psOptions->anSrcBands.size());
6267
0
                 ++i)
6268
0
            {
6269
0
                psOptions->anDstBands.push_back(i + 1);
6270
0
            }
6271
0
        }
6272
6273
0
        if (!psOptions->osFormat.empty() ||
6274
0
            psOptions->eOutputType != GDT_Unknown)
6275
0
        {
6276
0
            psOptions->bCreateOutput = true;
6277
0
        }
6278
6279
0
        if (psOptionsForBinary)
6280
0
            psOptionsForBinary->bCreateOutput = psOptions->bCreateOutput;
6281
6282
0
        return psOptions.release();
6283
0
    }
6284
0
    catch (const std::exception &err)
6285
0
    {
6286
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s", err.what());
6287
0
        return nullptr;
6288
0
    }
6289
0
}
6290
6291
/************************************************************************/
6292
/*                       GDALWarpAppOptionsFree()                       */
6293
/************************************************************************/
6294
6295
/**
6296
 * Frees the GDALWarpAppOptions struct.
6297
 *
6298
 * @param psOptions the options struct for GDALWarp().
6299
 *
6300
 * @since GDAL 2.1
6301
 */
6302
6303
void GDALWarpAppOptionsFree(GDALWarpAppOptions *psOptions)
6304
0
{
6305
0
    delete psOptions;
6306
0
}
6307
6308
/************************************************************************/
6309
/*                   GDALWarpAppOptionsSetProgress()                    */
6310
/************************************************************************/
6311
6312
/**
6313
 * Set a progress function.
6314
 *
6315
 * @param psOptions the options struct for GDALWarp().
6316
 * @param pfnProgress the progress callback.
6317
 * @param pProgressData the user data for the progress callback.
6318
 *
6319
 * @since GDAL 2.1
6320
 */
6321
6322
void GDALWarpAppOptionsSetProgress(GDALWarpAppOptions *psOptions,
6323
                                   GDALProgressFunc pfnProgress,
6324
                                   void *pProgressData)
6325
0
{
6326
0
    psOptions->pfnProgress = pfnProgress ? pfnProgress : GDALDummyProgress;
6327
0
    psOptions->pProgressData = pProgressData;
6328
0
    if (pfnProgress == GDALTermProgress)
6329
0
        psOptions->bQuiet = false;
6330
0
}
6331
6332
/************************************************************************/
6333
/*                     GDALWarpAppOptionsSetQuiet()                     */
6334
/************************************************************************/
6335
6336
/**
6337
 * Set a progress function.
6338
 *
6339
 * @param psOptions the options struct for GDALWarp().
6340
 * @param bQuiet whether GDALWarp() should emit messages on stdout.
6341
 *
6342
 * @since GDAL 2.3
6343
 */
6344
6345
void GDALWarpAppOptionsSetQuiet(GDALWarpAppOptions *psOptions, int bQuiet)
6346
0
{
6347
0
    psOptions->bQuiet = CPL_TO_BOOL(bQuiet);
6348
0
}
6349
6350
/************************************************************************/
6351
/*                  GDALWarpAppOptionsSetWarpOption()                   */
6352
/************************************************************************/
6353
6354
/**
6355
 * Set a warp option
6356
 *
6357
 * @param psOptions the options struct for GDALWarp().
6358
 * @param pszKey key.
6359
 * @param pszValue value.
6360
 *
6361
 * @since GDAL 2.1
6362
 */
6363
6364
void GDALWarpAppOptionsSetWarpOption(GDALWarpAppOptions *psOptions,
6365
                                     const char *pszKey, const char *pszValue)
6366
0
{
6367
0
    psOptions->aosWarpOptions.SetNameValue(pszKey, pszValue);
6368
0
}
6369
6370
#undef CHECK_HAS_ENOUGH_ADDITIONAL_ARGS