Coverage Report

Created: 2025-06-13 06:18

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