LCOV - code coverage report
Current view: top level - apps - gdalwarp_lib.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2651 2909 91.1 %
Date: 2025-05-15 18:21:54 Functions: 81 97 83.5 %

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

Generated by: LCOV version 1.14