Coverage Report

Created: 2026-02-14 06:52

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