LCOV - code coverage report
Current view: top level - apps - gdalwarp_lib.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2659 2910 91.4 %
Date: 2025-06-19 12:30:01 Functions: 82 98 83.7 %

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

Generated by: LCOV version 1.14