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