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

Generated by: LCOV version 1.14