LCOV - code coverage report
Current view: top level - apps - gdalwarp_lib.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2703 2959 91.3 %
Date: 2026-02-01 11:59:10 Functions: 83 99 83.8 %

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

Generated by: LCOV version 1.14