LCOV - code coverage report
Current view: top level - apps - gdalwarp_lib.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2624 2897 90.6 %
Date: 2025-01-18 12:42:00 Functions: 79 95 83.2 %

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

Generated by: LCOV version 1.14