Coverage Report

Created: 2025-08-28 06:57

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