Coverage Report

Created: 2025-11-16 06:25

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