/src/gdal/alg/gdaltransformer.cpp
Line  | Count  | Source (jump to first uncovered line)  | 
1  |  | /******************************************************************************  | 
2  |  |  *  | 
3  |  |  * Project:  Mapinfo Image Warper  | 
4  |  |  * Purpose:  Implementation of one or more GDALTrasformerFunc types, including  | 
5  |  |  *           the GenImgProj (general image reprojector) transformer.  | 
6  |  |  * Author:   Frank Warmerdam, warmerdam@pobox.com  | 
7  |  |  *  | 
8  |  |  ******************************************************************************  | 
9  |  |  * Copyright (c) 2002, i3 - information integration and imaging  | 
10  |  |  *                          Fort Collin, CO  | 
11  |  |  * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>  | 
12  |  |  * Copyright (c) 2021, CLS  | 
13  |  |  *  | 
14  |  |  * SPDX-License-Identifier: MIT  | 
15  |  |  ****************************************************************************/  | 
16  |  |  | 
17  |  | #include "cpl_port.h"  | 
18  |  | #include "gdal_alg.h"  | 
19  |  | #include "gdal_alg_priv.h"  | 
20  |  |  | 
21  |  | #include <climits>  | 
22  |  | #include <cmath>  | 
23  |  | #include <cstddef>  | 
24  |  | #include <cstdlib>  | 
25  |  | #include <cstring>  | 
26  |  |  | 
27  |  | #include <algorithm>  | 
28  |  | #include <limits>  | 
29  |  | #include <utility>  | 
30  |  |  | 
31  |  | #include "cpl_conv.h"  | 
32  |  | #include "cpl_error.h"  | 
33  |  | #include "cpl_list.h"  | 
34  |  | #include "cpl_minixml.h"  | 
35  |  | #include "cpl_multiproc.h"  | 
36  |  | #include "cpl_string.h"  | 
37  |  | #include "cpl_vsi.h"  | 
38  |  | #include "gdal.h"  | 
39  |  | #include "gdal_priv.h"  | 
40  |  | #include "ogr_core.h"  | 
41  |  | #include "ogr_spatialref.h"  | 
42  |  | #include "ogr_srs_api.h"  | 
43  |  |  | 
44  |  | CPL_C_START  | 
45  |  | void *GDALDeserializeGCPTransformer(CPLXMLNode *psTree);  | 
46  |  | void *GDALDeserializeTPSTransformer(CPLXMLNode *psTree);  | 
47  |  | void *GDALDeserializeGeoLocTransformer(CPLXMLNode *psTree);  | 
48  |  | void *GDALDeserializeRPCTransformer(CPLXMLNode *psTree);  | 
49  |  | void *GDALDeserializeHomographyTransformer(CPLXMLNode *psTree);  | 
50  |  | CPL_C_END  | 
51  |  |  | 
52  |  | static CPLXMLNode *GDALSerializeReprojectionTransformer(void *pTransformArg);  | 
53  |  | static void *GDALDeserializeReprojectionTransformer(CPLXMLNode *psTree);  | 
54  |  |  | 
55  |  | static CPLXMLNode *GDALSerializeGenImgProjTransformer(void *pTransformArg);  | 
56  |  | static void *GDALDeserializeGenImgProjTransformer(CPLXMLNode *psTree);  | 
57  |  |  | 
58  |  | static void *GDALCreateApproxTransformer2(GDALTransformerFunc pfnRawTransformer,  | 
59  |  |                                           void *pRawTransformerArg,  | 
60  |  |                                           double dfMaxErrorForward,  | 
61  |  |                                           double dfMaxErrorReverse);  | 
62  |  |  | 
63  |  | /************************************************************************/  | 
64  |  | /*                            GDALIsTransformer()                       */  | 
65  |  | /************************************************************************/  | 
66  |  |  | 
67  |  | bool GDALIsTransformer(void *hTransformerArg, const char *pszClassName)  | 
68  | 0  | { | 
69  | 0  |     if (!hTransformerArg)  | 
70  | 0  |         return false;  | 
71  |  |     // All transformers should have a GDALTransformerInfo member as their first members  | 
72  | 0  |     GDALTransformerInfo *psInfo =  | 
73  | 0  |         static_cast<GDALTransformerInfo *>(hTransformerArg);  | 
74  | 0  |     return memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,  | 
75  | 0  |                   strlen(GDAL_GTI2_SIGNATURE)) == 0 &&  | 
76  | 0  |            strcmp(psInfo->pszClassName, pszClassName) == 0;  | 
77  | 0  | }  | 
78  |  |  | 
79  |  | /************************************************************************/  | 
80  |  | /*                          GDALTransformFunc                           */  | 
81  |  | /*                                                                      */  | 
82  |  | /*      Documentation for GDALTransformFunc typedef.                    */  | 
83  |  | /************************************************************************/  | 
84  |  |  | 
85  |  | /*!  | 
86  |  |  | 
87  |  | \typedef typedef int (*GDALTransformerFunc)( void *pTransformerArg, int  | 
88  |  | bDstToSrc, int nPointCount, double *x, double *y, double *z, int *panSuccess );  | 
89  |  |  | 
90  |  | Generic signature for spatial point transformers.  | 
91  |  |  | 
92  |  | This function signature is used for a variety of functions that accept  | 
93  |  | passed in functions used to transform point locations between two coordinate  | 
94  |  | spaces.  | 
95  |  |  | 
96  |  | The GDALCreateGenImgProjTransformer(), GDALCreateReprojectionTransformerEx(),  | 
97  |  | GDALCreateGCPTransformer() and GDALCreateApproxTransformer() functions can  | 
98  |  | be used to prepare argument data for some built-in transformers.  As well,  | 
99  |  | applications can implement their own transformers to the following signature.  | 
100  |  |  | 
101  |  | \code  | 
102  |  | typedef int  | 
103  |  | (*GDALTransformerFunc)( void *pTransformerArg,  | 
104  |  |                         int bDstToSrc, int nPointCount,  | 
105  |  |                         double *x, double *y, double *z, int *panSuccess );  | 
106  |  | \endcode  | 
107  |  |  | 
108  |  | @param pTransformerArg application supplied callback data used by the  | 
109  |  | transformer.  | 
110  |  |  | 
111  |  | @param bDstToSrc if TRUE the transformation will be from the destination  | 
112  |  | coordinate space to the source coordinate system, otherwise the transformation  | 
113  |  | will be from the source coordinate system to the destination coordinate system.  | 
114  |  |  | 
115  |  | @param nPointCount number of points in the x, y and z arrays.  | 
116  |  |  | 
117  |  | @param[in,out] x input X coordinates.  Results returned in same array.  | 
118  |  |  | 
119  |  | @param[in,out] y input Y coordinates.  Results returned in same array.  | 
120  |  |  | 
121  |  | @param[in,out] z input Z coordinates.  Results returned in same array.  | 
122  |  |  | 
123  |  | @param[out] panSuccess array of ints in which success (TRUE) or failure (FALSE)  | 
124  |  | flags are returned for the translation of each point. Must not be NULL.  | 
125  |  |  | 
126  |  | @return TRUE if all points have been successfully transformed (changed in 3.11,  | 
127  |  | previously was TRUE if some points have been successfully transformed)  | 
128  |  |  | 
129  |  | */  | 
130  |  |  | 
131  |  | /************************************************************************/  | 
132  |  | /*                      GDALSuggestedWarpOutput()                       */  | 
133  |  | /************************************************************************/  | 
134  |  |  | 
135  |  | /**  | 
136  |  |  * Suggest output file size.  | 
137  |  |  *  | 
138  |  |  * This function is used to suggest the size, and georeferenced extents  | 
139  |  |  * appropriate given the indicated transformation and input file.  It walks  | 
140  |  |  * the edges of the input file (approximately 20 sample points along each  | 
141  |  |  * edge) transforming into output coordinates in order to get an extents box.  | 
142  |  |  *  | 
143  |  |  * Then a resolution is computed with the intent that the length of the  | 
144  |  |  * distance from the top left corner of the output imagery to the bottom right  | 
145  |  |  * corner would represent the same number of pixels as in the source image.  | 
146  |  |  * Note that if the image is somewhat rotated the diagonal taken isn't of the  | 
147  |  |  * whole output bounding rectangle, but instead of the locations where the  | 
148  |  |  * top/left and bottom/right corners transform.  The output pixel size is  | 
149  |  |  * always square.  This is intended to approximately preserve the resolution  | 
150  |  |  * of the input data in the output file.  | 
151  |  |  *  | 
152  |  |  * The values returned in padfGeoTransformOut, pnPixels and pnLines are  | 
153  |  |  * the suggested number of pixels and lines for the output file, and the  | 
154  |  |  * geotransform relating those pixels to the output georeferenced coordinates.  | 
155  |  |  *  | 
156  |  |  * The trickiest part of using the function is ensuring that the  | 
157  |  |  * transformer created is from source file pixel/line coordinates to  | 
158  |  |  * output file georeferenced coordinates.  This can be accomplished with  | 
159  |  |  * GDALCreateGenImgProjTransformer() by passing a NULL for the hDstDS.  | 
160  |  |  *  | 
161  |  |  * @param hSrcDS the input image (it is assumed the whole input image is  | 
162  |  |  * being transformed).  | 
163  |  |  * @param pfnTransformer the transformer function.  | 
164  |  |  * @param pTransformArg the callback data for the transformer function.  | 
165  |  |  * @param padfGeoTransformOut the array of six doubles in which the suggested  | 
166  |  |  * geotransform is returned.  | 
167  |  |  * @param pnPixels int in which the suggest pixel width of output is returned.  | 
168  |  |  * @param pnLines int in which the suggest pixel height of output is returned.  | 
169  |  |  *  | 
170  |  |  * @return CE_None if successful or CE_Failure otherwise.  | 
171  |  |  */  | 
172  |  |  | 
173  |  | CPLErr CPL_STDCALL GDALSuggestedWarpOutput(GDALDatasetH hSrcDS,  | 
174  |  |                                            GDALTransformerFunc pfnTransformer,  | 
175  |  |                                            void *pTransformArg,  | 
176  |  |                                            double *padfGeoTransformOut,  | 
177  |  |                                            int *pnPixels, int *pnLines)  | 
178  |  |  | 
179  | 0  | { | 
180  | 0  |     VALIDATE_POINTER1(hSrcDS, "GDALSuggestedWarpOutput", CE_Failure);  | 
181  |  |  | 
182  | 0  |     double adfExtent[4] = {}; | 
183  |  | 
  | 
184  | 0  |     return GDALSuggestedWarpOutput2(hSrcDS, pfnTransformer, pTransformArg,  | 
185  | 0  |                                     padfGeoTransformOut, pnPixels, pnLines,  | 
186  | 0  |                                     adfExtent, 0);  | 
187  | 0  | }  | 
188  |  |  | 
189  |  | static bool GDALSuggestedWarpOutput2_MustAdjustForRightBorder(  | 
190  |  |     GDALTransformerFunc pfnTransformer, void *pTransformArg, double *padfExtent,  | 
191  |  |     int /* nPixels*/, int nLines, double dfPixelSizeX, double dfPixelSizeY)  | 
192  | 0  | { | 
193  | 0  |     double adfX[21] = {}; | 
194  | 0  |     double adfY[21] = {}; | 
195  |  | 
  | 
196  | 0  |     const double dfMaxXOut = padfExtent[2];  | 
197  | 0  |     const double dfMaxYOut = padfExtent[3];  | 
198  |  |  | 
199  |  |     // Take 20 steps.  | 
200  | 0  |     int nSamplePoints = 0;  | 
201  | 0  |     for (double dfRatio = 0.0; dfRatio <= 1.01; dfRatio += 0.05)  | 
202  | 0  |     { | 
203  |  |         // Ensure we end exactly at the end.  | 
204  | 0  |         if (dfRatio > 0.99)  | 
205  | 0  |             dfRatio = 1.0;  | 
206  |  |  | 
207  |  |         // Along right.  | 
208  | 0  |         adfX[nSamplePoints] = dfMaxXOut;  | 
209  | 0  |         adfY[nSamplePoints] = dfMaxYOut - dfPixelSizeY * dfRatio * nLines;  | 
210  | 0  |         nSamplePoints++;  | 
211  | 0  |     }  | 
212  | 0  |     double adfZ[21] = {}; | 
213  |  | 
  | 
214  | 0  |     int abSuccess[21] = {}; | 
215  |  | 
  | 
216  | 0  |     pfnTransformer(pTransformArg, TRUE, nSamplePoints, adfX, adfY, adfZ,  | 
217  | 0  |                    abSuccess);  | 
218  |  | 
  | 
219  | 0  |     int abSuccess2[21] = {}; | 
220  |  | 
  | 
221  | 0  |     pfnTransformer(pTransformArg, FALSE, nSamplePoints, adfX, adfY, adfZ,  | 
222  | 0  |                    abSuccess2);  | 
223  |  | 
  | 
224  | 0  |     nSamplePoints = 0;  | 
225  | 0  |     int nBadCount = 0;  | 
226  | 0  |     for (double dfRatio = 0.0; dfRatio <= 1.01; dfRatio += 0.05)  | 
227  | 0  |     { | 
228  | 0  |         const double expected_x = dfMaxXOut;  | 
229  | 0  |         const double expected_y = dfMaxYOut - dfPixelSizeY * dfRatio * nLines;  | 
230  | 0  |         if (!abSuccess[nSamplePoints] || !abSuccess2[nSamplePoints] ||  | 
231  | 0  |             fabs(adfX[nSamplePoints] - expected_x) > dfPixelSizeX ||  | 
232  | 0  |             fabs(adfY[nSamplePoints] - expected_y) > dfPixelSizeY)  | 
233  | 0  |         { | 
234  | 0  |             nBadCount++;  | 
235  | 0  |         }  | 
236  | 0  |         nSamplePoints++;  | 
237  | 0  |     }  | 
238  |  | 
  | 
239  | 0  |     return nBadCount == nSamplePoints;  | 
240  | 0  | }  | 
241  |  |  | 
242  |  | static bool GDALSuggestedWarpOutput2_MustAdjustForBottomBorder(  | 
243  |  |     GDALTransformerFunc pfnTransformer, void *pTransformArg, double *padfExtent,  | 
244  |  |     int nPixels, int /* nLines */, double dfPixelSizeX, double dfPixelSizeY)  | 
245  | 0  | { | 
246  | 0  |     double adfX[21] = {}; | 
247  | 0  |     double adfY[21] = {}; | 
248  |  | 
  | 
249  | 0  |     const double dfMinXOut = padfExtent[0];  | 
250  | 0  |     const double dfMinYOut = padfExtent[1];  | 
251  |  |  | 
252  |  |     // Take 20 steps.  | 
253  | 0  |     int nSamplePoints = 0;  | 
254  | 0  |     for (double dfRatio = 0.0; dfRatio <= 1.01; dfRatio += 0.05)  | 
255  | 0  |     { | 
256  |  |         // Ensure we end exactly at the end.  | 
257  | 0  |         if (dfRatio > 0.99)  | 
258  | 0  |             dfRatio = 1.0;  | 
259  |  |  | 
260  |  |         // Along right.  | 
261  | 0  |         adfX[nSamplePoints] = dfMinXOut + dfPixelSizeX * dfRatio * nPixels;  | 
262  | 0  |         adfY[nSamplePoints] = dfMinYOut;  | 
263  | 0  |         nSamplePoints++;  | 
264  | 0  |     }  | 
265  | 0  |     double adfZ[21] = {}; | 
266  |  | 
  | 
267  | 0  |     int abSuccess[21] = {}; | 
268  |  | 
  | 
269  | 0  |     pfnTransformer(pTransformArg, TRUE, nSamplePoints, adfX, adfY, adfZ,  | 
270  | 0  |                    abSuccess);  | 
271  |  | 
  | 
272  | 0  |     int abSuccess2[21] = {}; | 
273  |  | 
  | 
274  | 0  |     pfnTransformer(pTransformArg, FALSE, nSamplePoints, adfX, adfY, adfZ,  | 
275  | 0  |                    abSuccess2);  | 
276  |  | 
  | 
277  | 0  |     nSamplePoints = 0;  | 
278  | 0  |     int nBadCount = 0;  | 
279  | 0  |     for (double dfRatio = 0.0; dfRatio <= 1.01; dfRatio += 0.05)  | 
280  | 0  |     { | 
281  | 0  |         const double expected_x = dfMinXOut + dfPixelSizeX * dfRatio * nPixels;  | 
282  | 0  |         const double expected_y = dfMinYOut;  | 
283  | 0  |         if (!abSuccess[nSamplePoints] || !abSuccess2[nSamplePoints] ||  | 
284  | 0  |             fabs(adfX[nSamplePoints] - expected_x) > dfPixelSizeX ||  | 
285  | 0  |             fabs(adfY[nSamplePoints] - expected_y) > dfPixelSizeY)  | 
286  | 0  |         { | 
287  | 0  |             nBadCount++;  | 
288  | 0  |         }  | 
289  | 0  |         nSamplePoints++;  | 
290  | 0  |     }  | 
291  |  | 
  | 
292  | 0  |     return nBadCount == nSamplePoints;  | 
293  | 0  | }  | 
294  |  |  | 
295  |  | /************************************************************************/  | 
296  |  | /*                      GDALSuggestedWarpOutput2()                      */  | 
297  |  | /************************************************************************/  | 
298  |  |  | 
299  |  | /**  | 
300  |  |  * Suggest output file size.  | 
301  |  |  *  | 
302  |  |  * This function is used to suggest the size, and georeferenced extents  | 
303  |  |  * appropriate given the indicated transformation and input file.  It walks  | 
304  |  |  * the edges of the input file (approximately 20 sample points along each  | 
305  |  |  * edge) transforming into output coordinates in order to get an extents box.  | 
306  |  |  *  | 
307  |  |  * Then a resolution is computed with the intent that the length of the  | 
308  |  |  * distance from the top left corner of the output imagery to the bottom right  | 
309  |  |  * corner would represent the same number of pixels as in the source image.  | 
310  |  |  * Note that if the image is somewhat rotated the diagonal taken isn't of the  | 
311  |  |  * whole output bounding rectangle, but instead of the locations where the  | 
312  |  |  * top/left and bottom/right corners transform.  The output pixel size is  | 
313  |  |  * always square.  This is intended to approximately preserve the resolution  | 
314  |  |  * of the input data in the output file.  | 
315  |  |  *  | 
316  |  |  * The values returned in padfGeoTransformOut, pnPixels and pnLines are  | 
317  |  |  * the suggested number of pixels and lines for the output file, and the  | 
318  |  |  * geotransform relating those pixels to the output georeferenced coordinates.  | 
319  |  |  *  | 
320  |  |  * The trickiest part of using the function is ensuring that the  | 
321  |  |  * transformer created is from source file pixel/line coordinates to  | 
322  |  |  * output file georeferenced coordinates.  This can be accomplished with  | 
323  |  |  * GDALCreateGenImgProjTransformer() by passing a NULL for the hDstDS.  | 
324  |  |  *  | 
325  |  |  * @param hSrcDS the input image (it is assumed the whole input image is  | 
326  |  |  * being transformed).  | 
327  |  |  * @param pfnTransformer the transformer function.  | 
328  |  |  * @param pTransformArg the callback data for the transformer function.  | 
329  |  |  * @param padfGeoTransformOut the array of six doubles in which the suggested  | 
330  |  |  * geotransform is returned.  | 
331  |  |  * @param pnPixels int in which the suggest pixel width of output is returned.  | 
332  |  |  * @param pnLines int in which the suggest pixel height of output is returned.  | 
333  |  |  * @param padfExtent Four entry array to return extents as (xmin, ymin, xmax,  | 
334  |  |  * ymax).  | 
335  |  |  * @param nOptions Options flags. Zero or GDAL_SWO_ROUND_UP_SIZE  to ask *pnPixels  | 
336  |  |  * and *pnLines to be rounded up instead of being rounded to the closes integer, or  | 
337  |  |  * GDAL_SWO_FORCE_SQUARE_PIXEL to indicate that the generated pixel size is a square.  | 
338  |  |  *  | 
339  |  |  * @return CE_None if successful or CE_Failure otherwise.  | 
340  |  |  */  | 
341  |  |  | 
342  |  | CPLErr CPL_STDCALL GDALSuggestedWarpOutput2(GDALDatasetH hSrcDS,  | 
343  |  |                                             GDALTransformerFunc pfnTransformer,  | 
344  |  |                                             void *pTransformArg,  | 
345  |  |                                             double *padfGeoTransformOut,  | 
346  |  |                                             int *pnPixels, int *pnLines,  | 
347  |  |                                             double *padfExtent, int nOptions)  | 
348  | 0  | { | 
349  | 0  |     VALIDATE_POINTER1(hSrcDS, "GDALSuggestedWarpOutput2", CE_Failure);  | 
350  |  |  | 
351  | 0  |     const bool bIsGDALGenImgProjTransform{ | 
352  | 0  |         pTransformArg &&  | 
353  | 0  |         GDALIsTransformer(pTransformArg, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME)};  | 
354  |  |  | 
355  |  |     /* -------------------------------------------------------------------- */  | 
356  |  |     /*      Setup sample points all around the edge of the input raster.    */  | 
357  |  |     /* -------------------------------------------------------------------- */  | 
358  | 0  |     if (bIsGDALGenImgProjTransform)  | 
359  | 0  |     { | 
360  |  |         // In case CHECK_WITH_INVERT_PROJ has been modified.  | 
361  | 0  |         GDALRefreshGenImgProjTransformer(pTransformArg);  | 
362  | 0  |     }  | 
363  | 0  |     else if (GDALIsTransformer(pTransformArg,  | 
364  | 0  |                                GDAL_APPROX_TRANSFORMER_CLASS_NAME))  | 
365  | 0  |     { | 
366  |  |         // In case CHECK_WITH_INVERT_PROJ has been modified.  | 
367  | 0  |         GDALRefreshApproxTransformer(pTransformArg);  | 
368  | 0  |     }  | 
369  |  | 
  | 
370  | 0  |     const int nInXSize = GDALGetRasterXSize(hSrcDS);  | 
371  | 0  |     const int nInYSize = GDALGetRasterYSize(hSrcDS);  | 
372  |  |  | 
373  |  |     /* ------------------------------------------------------------- */  | 
374  |  |     /* Special case for warping on the same (or null) CRS.           */  | 
375  |  |     /* ------------------------------------------------------------- */  | 
376  | 0  |     if ((!nOptions || (nOptions & GDAL_SWO_FORCE_SQUARE_PIXEL) == 0) &&  | 
377  | 0  |         pTransformArg && bIsGDALGenImgProjTransform)  | 
378  | 0  |     { | 
379  | 0  |         const GDALGenImgProjTransformInfo *psInfo =  | 
380  | 0  |             static_cast<const GDALGenImgProjTransformInfo *>(pTransformArg);  | 
381  |  | 
  | 
382  | 0  |         if (!psInfo->sSrcParams.pTransformer &&  | 
383  | 0  |             !psInfo->bHasCustomTransformationPipeline &&  | 
384  | 0  |             !psInfo->sDstParams.pTransformer &&  | 
385  | 0  |             psInfo->sSrcParams.adfGeoTransform[2] == 0 &&  | 
386  | 0  |             psInfo->sSrcParams.adfGeoTransform[4] == 0 &&  | 
387  | 0  |             psInfo->sDstParams.adfGeoTransform[0] == 0 &&  | 
388  | 0  |             psInfo->sDstParams.adfGeoTransform[1] == 1 &&  | 
389  | 0  |             psInfo->sDstParams.adfGeoTransform[2] == 0 &&  | 
390  | 0  |             psInfo->sDstParams.adfGeoTransform[3] == 0 &&  | 
391  | 0  |             psInfo->sDstParams.adfGeoTransform[4] == 0 &&  | 
392  | 0  |             psInfo->sDstParams.adfGeoTransform[5] == 1)  | 
393  | 0  |         { | 
394  | 0  |             const OGRSpatialReference *poSourceCRS = nullptr;  | 
395  | 0  |             const OGRSpatialReference *poTargetCRS = nullptr;  | 
396  |  | 
  | 
397  | 0  |             if (psInfo->pReprojectArg)  | 
398  | 0  |             { | 
399  | 0  |                 const GDALReprojectionTransformInfo *psRTI =  | 
400  | 0  |                     static_cast<const GDALReprojectionTransformInfo *>(  | 
401  | 0  |                         psInfo->pReprojectArg);  | 
402  | 0  |                 poSourceCRS = psRTI->poForwardTransform->GetSourceCS();  | 
403  | 0  |                 poTargetCRS = psRTI->poForwardTransform->GetTargetCS();  | 
404  | 0  |             }  | 
405  |  | 
  | 
406  | 0  |             if ((!poSourceCRS && !poTargetCRS) ||  | 
407  | 0  |                 (poSourceCRS && poTargetCRS &&  | 
408  | 0  |                  poSourceCRS->IsSame(poTargetCRS)))  | 
409  | 0  |             { | 
410  |  | 
  | 
411  | 0  |                 const bool bNorthUp{psInfo->sSrcParams.adfGeoTransform[5] < | 
412  | 0  |                                     0.0};  | 
413  |  | 
  | 
414  | 0  |                 memcpy(padfGeoTransformOut, psInfo->sSrcParams.adfGeoTransform,  | 
415  | 0  |                        sizeof(double) * 6);  | 
416  |  | 
  | 
417  | 0  |                 if (!bNorthUp)  | 
418  | 0  |                 { | 
419  | 0  |                     padfGeoTransformOut[3] = padfGeoTransformOut[3] +  | 
420  | 0  |                                              nInYSize * padfGeoTransformOut[5];  | 
421  | 0  |                     padfGeoTransformOut[5] = -padfGeoTransformOut[5];  | 
422  | 0  |                 }  | 
423  |  | 
  | 
424  | 0  |                 *pnPixels = nInXSize;  | 
425  | 0  |                 *pnLines = nInYSize;  | 
426  |  |  | 
427  |  |                 // Calculate extent from hSrcDS  | 
428  | 0  |                 if (padfExtent)  | 
429  | 0  |                 { | 
430  | 0  |                     padfExtent[0] = psInfo->sSrcParams.adfGeoTransform[0];  | 
431  | 0  |                     padfExtent[1] =  | 
432  | 0  |                         psInfo->sSrcParams.adfGeoTransform[3] +  | 
433  | 0  |                         nInYSize * psInfo->sSrcParams.adfGeoTransform[5];  | 
434  | 0  |                     padfExtent[2] =  | 
435  | 0  |                         psInfo->sSrcParams.adfGeoTransform[0] +  | 
436  | 0  |                         nInXSize * psInfo->sSrcParams.adfGeoTransform[1];  | 
437  | 0  |                     padfExtent[3] = psInfo->sSrcParams.adfGeoTransform[3];  | 
438  | 0  |                     if (!bNorthUp)  | 
439  | 0  |                     { | 
440  | 0  |                         std::swap(padfExtent[1], padfExtent[3]);  | 
441  | 0  |                     }  | 
442  | 0  |                 }  | 
443  | 0  |                 return CE_None;  | 
444  | 0  |             }  | 
445  | 0  |         }  | 
446  | 0  |     }  | 
447  |  |  | 
448  | 0  |     const int N_PIXELSTEP = 50;  | 
449  | 0  |     int nSteps = static_cast<int>(  | 
450  | 0  |         static_cast<double>(std::min(nInYSize, nInXSize)) / N_PIXELSTEP + 0.5);  | 
451  | 0  |     if (nSteps < 20)  | 
452  | 0  |         nSteps = 20;  | 
453  | 0  |     else if (nSteps > 100)  | 
454  | 0  |         nSteps = 100;  | 
455  |  |  | 
456  |  |     // TODO(rouault): How is this goto retry supposed to work?  Added in r20537.  | 
457  |  |     // Does redoing the same malloc multiple times work?  If it is needed, can  | 
458  |  |     // it be converted to a tigher while loop around the MALLOC3s and free?  Is  | 
459  |  |     // the point to try with the full requested steps.  Then, if there is not  | 
460  |  |     // enough memory, back off and try with just 20 steps?  | 
461  | 0  | retry:  | 
462  | 0  |     int nStepsPlusOne = nSteps + 1;  | 
463  | 0  |     int nSampleMax = nStepsPlusOne * nStepsPlusOne;  | 
464  |  | 
  | 
465  | 0  |     double dfStep = 1.0 / nSteps;  | 
466  | 0  |     double *padfY = nullptr;  | 
467  | 0  |     double *padfZ = nullptr;  | 
468  | 0  |     double *padfYRevert = nullptr;  | 
469  | 0  |     double *padfZRevert = nullptr;  | 
470  |  | 
  | 
471  | 0  |     int *pabSuccess = static_cast<int *>(  | 
472  | 0  |         VSI_MALLOC3_VERBOSE(sizeof(int), nStepsPlusOne, nStepsPlusOne));  | 
473  | 0  |     double *padfX = static_cast<double *>(  | 
474  | 0  |         VSI_MALLOC3_VERBOSE(sizeof(double) * 3, nStepsPlusOne, nStepsPlusOne));  | 
475  | 0  |     double *padfXRevert = static_cast<double *>(  | 
476  | 0  |         VSI_MALLOC3_VERBOSE(sizeof(double) * 3, nStepsPlusOne, nStepsPlusOne));  | 
477  | 0  |     if (pabSuccess == nullptr || padfX == nullptr || padfXRevert == nullptr)  | 
478  | 0  |     { | 
479  | 0  |         CPLFree(padfX);  | 
480  | 0  |         CPLFree(padfXRevert);  | 
481  | 0  |         CPLFree(pabSuccess);  | 
482  | 0  |         if (nSteps > 20)  | 
483  | 0  |         { | 
484  | 0  |             nSteps = 20;  | 
485  | 0  |             goto retry;  | 
486  | 0  |         }  | 
487  | 0  |         return CE_Failure;  | 
488  | 0  |     }  | 
489  |  |  | 
490  | 0  |     padfY = padfX + nSampleMax;  | 
491  | 0  |     padfZ = padfX + nSampleMax * 2;  | 
492  | 0  |     padfYRevert = padfXRevert + nSampleMax;  | 
493  | 0  |     padfZRevert = padfXRevert + nSampleMax * 2;  | 
494  |  |  | 
495  |  |     // Take N_STEPS steps.  | 
496  | 0  |     for (int iStep = 0; iStep <= nSteps; iStep++)  | 
497  | 0  |     { | 
498  | 0  |         double dfRatio = (iStep == nSteps) ? 1.0 : iStep * dfStep;  | 
499  | 0  |         int iStep2 = iStep;  | 
500  |  |  | 
501  |  |         // Along top.  | 
502  | 0  |         padfX[iStep2] = dfRatio * nInXSize;  | 
503  | 0  |         padfY[iStep2] = 0.0;  | 
504  | 0  |         padfZ[iStep2] = 0.0;  | 
505  |  |  | 
506  |  |         // Along bottom.  | 
507  | 0  |         iStep2 += nStepsPlusOne;  | 
508  | 0  |         padfX[iStep2] = dfRatio * nInXSize;  | 
509  | 0  |         padfY[iStep2] = nInYSize;  | 
510  | 0  |         padfZ[iStep2] = 0.0;  | 
511  |  |  | 
512  |  |         // Along left.  | 
513  | 0  |         iStep2 += nStepsPlusOne;  | 
514  | 0  |         padfX[iStep2] = 0.0;  | 
515  | 0  |         padfY[iStep2] = dfRatio * nInYSize;  | 
516  | 0  |         padfZ[iStep2] = 0.0;  | 
517  |  |  | 
518  |  |         // Along right.  | 
519  | 0  |         iStep2 += nStepsPlusOne;  | 
520  | 0  |         padfX[iStep2] = nInXSize;  | 
521  | 0  |         padfY[iStep2] = dfRatio * nInYSize;  | 
522  | 0  |         padfZ[iStep2] = 0.0;  | 
523  | 0  |     }  | 
524  |  | 
  | 
525  | 0  |     int nSamplePoints = 4 * nStepsPlusOne;  | 
526  |  | 
  | 
527  | 0  |     memset(pabSuccess, 1, sizeof(int) * nSampleMax);  | 
528  |  |  | 
529  |  |     /* -------------------------------------------------------------------- */  | 
530  |  |     /*      Transform them to the output coordinate system.                 */  | 
531  |  |     /* -------------------------------------------------------------------- */  | 
532  | 0  |     { | 
533  | 0  |         CPLTurnFailureIntoWarningBackuper oErrorsToWarnings{}; | 
534  | 0  |         pfnTransformer(pTransformArg, FALSE, nSamplePoints, padfX, padfY, padfZ,  | 
535  | 0  |                        pabSuccess);  | 
536  | 0  |     }  | 
537  | 0  |     constexpr int SIGN_FINAL_UNINIT = -2;  | 
538  | 0  |     constexpr int SIGN_FINAL_INVALID = 0;  | 
539  | 0  |     int iSignDiscontinuity = SIGN_FINAL_UNINIT;  | 
540  | 0  |     int nFailedCount = 0;  | 
541  | 0  |     const int iSignArray[2] = {-1, 1}; | 
542  | 0  |     for (int i = 0; i < nSamplePoints; i++)  | 
543  | 0  |     { | 
544  | 0  |         if (pabSuccess[i])  | 
545  | 0  |         { | 
546  |  |             // Fix for https://trac.osgeo.org/gdal/ticket/7243  | 
547  |  |             // where echo "-2050000.000 2050000.000" |  | 
548  |  |             //              gdaltransform -s_srs EPSG:3411 -t_srs EPSG:4326  | 
549  |  |             // gives "-180 63.691332898492"  | 
550  |  |             // but we would rather like 180  | 
551  | 0  |             if (iSignDiscontinuity == 1 || iSignDiscontinuity == -1)  | 
552  | 0  |             { | 
553  | 0  |                 if (!((iSignDiscontinuity * padfX[i] > 0 &&  | 
554  | 0  |                        iSignDiscontinuity * padfX[i] <= 180.0) ||  | 
555  | 0  |                       (fabs(padfX[i] - iSignDiscontinuity * -180.0) < 1e-8)))  | 
556  | 0  |                 { | 
557  | 0  |                     iSignDiscontinuity = SIGN_FINAL_INVALID;  | 
558  | 0  |                 }  | 
559  | 0  |             }  | 
560  | 0  |             else if (iSignDiscontinuity == SIGN_FINAL_UNINIT)  | 
561  | 0  |             { | 
562  | 0  |                 for (const auto &iSign : iSignArray)  | 
563  | 0  |                 { | 
564  | 0  |                     if ((iSign * padfX[i] > 0 && iSign * padfX[i] <= 180.0) ||  | 
565  | 0  |                         (fabs(padfX[i] - iSign * -180.0) < 1e-8))  | 
566  | 0  |                     { | 
567  | 0  |                         iSignDiscontinuity = iSign;  | 
568  | 0  |                         break;  | 
569  | 0  |                     }  | 
570  | 0  |                 }  | 
571  | 0  |                 if (iSignDiscontinuity == SIGN_FINAL_UNINIT)  | 
572  | 0  |                 { | 
573  | 0  |                     iSignDiscontinuity = SIGN_FINAL_INVALID;  | 
574  | 0  |                 }  | 
575  | 0  |             }  | 
576  | 0  |         }  | 
577  | 0  |         else  | 
578  | 0  |         { | 
579  | 0  |             nFailedCount++;  | 
580  | 0  |         }  | 
581  | 0  |     }  | 
582  |  | 
  | 
583  | 0  |     if (iSignDiscontinuity == 1 || iSignDiscontinuity == -1)  | 
584  | 0  |     { | 
585  | 0  |         for (int i = 0; i < nSamplePoints; i++)  | 
586  | 0  |         { | 
587  | 0  |             if (pabSuccess[i])  | 
588  | 0  |             { | 
589  | 0  |                 if (fabs(padfX[i] - iSignDiscontinuity * -180.0) < 1e-8)  | 
590  | 0  |                 { | 
591  | 0  |                     double axTemp[2] = {iSignDiscontinuity * -180.0, | 
592  | 0  |                                         iSignDiscontinuity * 180.0};  | 
593  | 0  |                     double ayTemp[2] = {padfY[i], padfY[i]}; | 
594  | 0  |                     double azTemp[2] = {padfZ[i], padfZ[i]}; | 
595  | 0  |                     int abSuccess[2] = {FALSE, FALSE}; | 
596  | 0  |                     CPLTurnFailureIntoWarningBackuper oErrorsToWarnings{}; | 
597  | 0  |                     if (pfnTransformer(pTransformArg, TRUE, 2, axTemp, ayTemp,  | 
598  | 0  |                                        azTemp, abSuccess) &&  | 
599  | 0  |                         fabs(axTemp[0] - axTemp[1]) < 1e-8 &&  | 
600  | 0  |                         fabs(ayTemp[0] - ayTemp[1]) < 1e-8)  | 
601  | 0  |                     { | 
602  | 0  |                         padfX[i] = iSignDiscontinuity * 180.0;  | 
603  | 0  |                     }  | 
604  | 0  |                 }  | 
605  | 0  |             }  | 
606  | 0  |         }  | 
607  | 0  |     }  | 
608  |  |  | 
609  |  |     /* -------------------------------------------------------------------- */  | 
610  |  |     /*      Check if the computed target coordinates are revertable.        */  | 
611  |  |     /*      If not, try the detailed grid sampling.                         */  | 
612  |  |     /* -------------------------------------------------------------------- */  | 
613  | 0  |     if (nFailedCount)  | 
614  | 0  |     { | 
615  | 0  |         CPLDebug("WARP", "At least one point failed after direct transform"); | 
616  | 0  |     }  | 
617  | 0  |     else  | 
618  | 0  |     { | 
619  | 0  |         memcpy(padfXRevert, padfX, nSamplePoints * sizeof(double));  | 
620  | 0  |         memcpy(padfYRevert, padfY, nSamplePoints * sizeof(double));  | 
621  | 0  |         memcpy(padfZRevert, padfZ, nSamplePoints * sizeof(double));  | 
622  | 0  |         { | 
623  | 0  |             CPLTurnFailureIntoWarningBackuper oErrorsToWarnings{}; | 
624  | 0  |             pfnTransformer(pTransformArg, TRUE, nSamplePoints, padfXRevert,  | 
625  | 0  |                            padfYRevert, padfZRevert, pabSuccess);  | 
626  | 0  |         }  | 
627  |  | 
  | 
628  | 0  |         for (int i = 0; nFailedCount == 0 && i < nSamplePoints; i++)  | 
629  | 0  |         { | 
630  | 0  |             if (!pabSuccess[i])  | 
631  | 0  |             { | 
632  | 0  |                 nFailedCount++;  | 
633  | 0  |                 break;  | 
634  | 0  |             }  | 
635  |  |  | 
636  | 0  |             double dfRatio = (i % nStepsPlusOne) * dfStep;  | 
637  | 0  |             if (dfRatio > 0.99)  | 
638  | 0  |                 dfRatio = 1.0;  | 
639  |  | 
  | 
640  | 0  |             double dfExpectedX = 0.0;  | 
641  | 0  |             double dfExpectedY = 0.0;  | 
642  | 0  |             if (i < nStepsPlusOne)  | 
643  | 0  |             { | 
644  | 0  |                 dfExpectedX = dfRatio * nInXSize;  | 
645  | 0  |             }  | 
646  | 0  |             else if (i < 2 * nStepsPlusOne)  | 
647  | 0  |             { | 
648  | 0  |                 dfExpectedX = dfRatio * nInXSize;  | 
649  | 0  |                 dfExpectedY = nInYSize;  | 
650  | 0  |             }  | 
651  | 0  |             else if (i < 3 * nStepsPlusOne)  | 
652  | 0  |             { | 
653  | 0  |                 dfExpectedY = dfRatio * nInYSize;  | 
654  | 0  |             }  | 
655  | 0  |             else  | 
656  | 0  |             { | 
657  | 0  |                 dfExpectedX = nInXSize;  | 
658  | 0  |                 dfExpectedY = dfRatio * nInYSize;  | 
659  | 0  |             }  | 
660  |  | 
  | 
661  | 0  |             if (fabs(padfXRevert[i] - dfExpectedX) >  | 
662  | 0  |                     nInXSize / static_cast<double>(nSteps) ||  | 
663  | 0  |                 fabs(padfYRevert[i] - dfExpectedY) >  | 
664  | 0  |                     nInYSize / static_cast<double>(nSteps))  | 
665  | 0  |                 nFailedCount++;  | 
666  | 0  |         }  | 
667  | 0  |         if (nFailedCount != 0)  | 
668  | 0  |             CPLDebug("WARP", | 
669  | 0  |                      "At least one point failed after revert transform");  | 
670  | 0  |     }  | 
671  |  |  | 
672  |  |     /* -------------------------------------------------------------------- */  | 
673  |  |     /*      If any of the edge points failed to transform, we need to       */  | 
674  |  |     /*      build a fairly detailed internal grid of points instead to      */  | 
675  |  |     /*      help identify the area that is transformable.                   */  | 
676  |  |     /* -------------------------------------------------------------------- */  | 
677  | 0  |     if (nFailedCount)  | 
678  | 0  |     { | 
679  | 0  |         nSamplePoints = 0;  | 
680  |  |  | 
681  |  |         // Take N_STEPS steps.  | 
682  | 0  |         for (int iStep = 0; iStep <= nSteps; iStep++)  | 
683  | 0  |         { | 
684  | 0  |             double dfRatio = (iStep == nSteps) ? 1.0 : iStep * dfStep;  | 
685  |  | 
  | 
686  | 0  |             for (int iStep2 = 0; iStep2 <= nSteps; iStep2++)  | 
687  | 0  |             { | 
688  | 0  |                 const double dfRatio2 =  | 
689  | 0  |                     iStep2 == nSteps ? 1.0 : iStep2 * dfStep;  | 
690  |  |  | 
691  |  |                 // From top to bottom, from left to right.  | 
692  | 0  |                 padfX[nSamplePoints] = dfRatio2 * nInXSize;  | 
693  | 0  |                 padfY[nSamplePoints] = dfRatio * nInYSize;  | 
694  | 0  |                 padfZ[nSamplePoints] = 0.0;  | 
695  | 0  |                 nSamplePoints++;  | 
696  | 0  |             }  | 
697  | 0  |         }  | 
698  |  | 
  | 
699  | 0  |         CPLAssert(nSamplePoints == nSampleMax);  | 
700  |  |  | 
701  | 0  |         { | 
702  | 0  |             CPLTurnFailureIntoWarningBackuper oErrorsToWarnings{}; | 
703  | 0  |             pfnTransformer(pTransformArg, FALSE, nSamplePoints, padfX, padfY,  | 
704  | 0  |                            padfZ, pabSuccess);  | 
705  | 0  |         }  | 
706  | 0  |     }  | 
707  |  |  | 
708  |  |     /* -------------------------------------------------------------------- */  | 
709  |  |     /*      Collect the bounds, ignoring any failed points.                 */  | 
710  |  |     /* -------------------------------------------------------------------- */  | 
711  | 0  |     double dfMinXOut = 0.0;  | 
712  | 0  |     double dfMinYOut = 0.0;  | 
713  | 0  |     double dfMaxXOut = 0.0;  | 
714  | 0  |     double dfMaxYOut = 0.0;  | 
715  | 0  |     bool bGotInitialPoint = false;  | 
716  |  | 
  | 
717  | 0  |     nFailedCount = 0;  | 
718  | 0  |     for (int i = 0; i < nSamplePoints; i++)  | 
719  | 0  |     { | 
720  | 0  |         int x_i = 0;  | 
721  | 0  |         int y_i = 0;  | 
722  |  | 
  | 
723  | 0  |         if (nSamplePoints == nSampleMax)  | 
724  | 0  |         { | 
725  | 0  |             x_i = i % nStepsPlusOne;  | 
726  | 0  |             y_i = i / nStepsPlusOne;  | 
727  | 0  |         }  | 
728  | 0  |         else  | 
729  | 0  |         { | 
730  | 0  |             if (i < 2 * nStepsPlusOne)  | 
731  | 0  |             { | 
732  | 0  |                 x_i = i % nStepsPlusOne;  | 
733  | 0  |                 y_i = (i < nStepsPlusOne) ? 0 : nSteps;  | 
734  | 0  |             }  | 
735  | 0  |         }  | 
736  |  | 
  | 
737  | 0  |         if (x_i > 0 && (pabSuccess[i - 1] || pabSuccess[i]))  | 
738  | 0  |         { | 
739  | 0  |             double x_out_before = padfX[i - 1];  | 
740  | 0  |             double x_out_after = padfX[i];  | 
741  | 0  |             int nIter = 0;  | 
742  | 0  |             double x_in_before =  | 
743  | 0  |                 static_cast<double>(x_i - 1) * nInXSize / nSteps;  | 
744  | 0  |             double x_in_after = static_cast<double>(x_i) * nInXSize / nSteps;  | 
745  | 0  |             int invalid_before = !(pabSuccess[i - 1]);  | 
746  | 0  |             int invalid_after = !(pabSuccess[i]);  | 
747  |  |  | 
748  |  |             // Detect discontinuity in target coordinates when the target x  | 
749  |  |             // coordinates change sign. This may be a false positive when the  | 
750  |  |             // target tx is around 0 Dichotomic search to reduce the interval  | 
751  |  |             // to near the discontinuity and get a better out extent.  | 
752  | 0  |             while ((invalid_before || invalid_after ||  | 
753  | 0  |                     x_out_before * x_out_after < 0.0) &&  | 
754  | 0  |                    nIter < 16)  | 
755  | 0  |             { | 
756  | 0  |                 double x = (x_in_before + x_in_after) / 2.0;  | 
757  | 0  |                 double y = static_cast<double>(y_i) * nInYSize / nSteps;  | 
758  | 0  |                 double z = 0.0;  | 
759  | 0  |                 int bSuccess = TRUE;  | 
760  | 0  |                 if (pfnTransformer(pTransformArg, FALSE, 1, &x, &y, &z,  | 
761  | 0  |                                    &bSuccess) &&  | 
762  | 0  |                     bSuccess)  | 
763  | 0  |                 { | 
764  | 0  |                     if (bGotInitialPoint)  | 
765  | 0  |                     { | 
766  | 0  |                         dfMinXOut = std::min(dfMinXOut, x);  | 
767  | 0  |                         dfMinYOut = std::min(dfMinYOut, y);  | 
768  | 0  |                         dfMaxXOut = std::max(dfMaxXOut, x);  | 
769  | 0  |                         dfMaxYOut = std::max(dfMaxYOut, y);  | 
770  | 0  |                     }  | 
771  | 0  |                     else  | 
772  | 0  |                     { | 
773  | 0  |                         bGotInitialPoint = true;  | 
774  | 0  |                         dfMinXOut = x;  | 
775  | 0  |                         dfMaxXOut = x;  | 
776  | 0  |                         dfMinYOut = y;  | 
777  | 0  |                         dfMaxYOut = y;  | 
778  | 0  |                     }  | 
779  |  | 
  | 
780  | 0  |                     if (invalid_before || x_out_before * x < 0)  | 
781  | 0  |                     { | 
782  | 0  |                         invalid_after = FALSE;  | 
783  | 0  |                         x_in_after = (x_in_before + x_in_after) / 2.0;  | 
784  | 0  |                         x_out_after = x;  | 
785  | 0  |                     }  | 
786  | 0  |                     else  | 
787  | 0  |                     { | 
788  | 0  |                         invalid_before = FALSE;  | 
789  | 0  |                         x_out_before = x;  | 
790  | 0  |                         x_in_before = (x_in_before + x_in_after) / 2.0;  | 
791  | 0  |                     }  | 
792  | 0  |                 }  | 
793  | 0  |                 else  | 
794  | 0  |                 { | 
795  | 0  |                     if (invalid_before)  | 
796  | 0  |                     { | 
797  | 0  |                         x_in_before = (x_in_before + x_in_after) / 2.0;  | 
798  | 0  |                     }  | 
799  | 0  |                     else if (invalid_after)  | 
800  | 0  |                     { | 
801  | 0  |                         x_in_after = (x_in_before + x_in_after) / 2.0;  | 
802  | 0  |                     }  | 
803  | 0  |                     else  | 
804  | 0  |                     { | 
805  | 0  |                         break;  | 
806  | 0  |                     }  | 
807  | 0  |                 }  | 
808  | 0  |                 nIter++;  | 
809  | 0  |             }  | 
810  | 0  |         }  | 
811  |  | 
  | 
812  | 0  |         if (!pabSuccess[i])  | 
813  | 0  |         { | 
814  | 0  |             nFailedCount++;  | 
815  | 0  |             continue;  | 
816  | 0  |         }  | 
817  |  |  | 
818  | 0  |         if (bGotInitialPoint)  | 
819  | 0  |         { | 
820  | 0  |             dfMinXOut = std::min(dfMinXOut, padfX[i]);  | 
821  | 0  |             dfMinYOut = std::min(dfMinYOut, padfY[i]);  | 
822  | 0  |             dfMaxXOut = std::max(dfMaxXOut, padfX[i]);  | 
823  | 0  |             dfMaxYOut = std::max(dfMaxYOut, padfY[i]);  | 
824  | 0  |         }  | 
825  | 0  |         else  | 
826  | 0  |         { | 
827  | 0  |             bGotInitialPoint = true;  | 
828  | 0  |             dfMinXOut = padfX[i];  | 
829  | 0  |             dfMaxXOut = padfX[i];  | 
830  | 0  |             dfMinYOut = padfY[i];  | 
831  | 0  |             dfMaxYOut = padfY[i];  | 
832  | 0  |         }  | 
833  | 0  |     }  | 
834  |  | 
  | 
835  | 0  |     if (nFailedCount > nSamplePoints - 10)  | 
836  | 0  |     { | 
837  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
838  | 0  |                  "Too many points (%d out of %d) failed to transform, "  | 
839  | 0  |                  "unable to compute output bounds.",  | 
840  | 0  |                  nFailedCount, nSamplePoints);  | 
841  |  | 
  | 
842  | 0  |         CPLFree(padfX);  | 
843  | 0  |         CPLFree(padfXRevert);  | 
844  | 0  |         CPLFree(pabSuccess);  | 
845  |  | 
  | 
846  | 0  |         return CE_Failure;  | 
847  | 0  |     }  | 
848  |  |  | 
849  | 0  |     if (nFailedCount)  | 
850  | 0  |         CPLDebug("GDAL", | 
851  | 0  |                  "GDALSuggestedWarpOutput(): %d out of %d points failed to "  | 
852  | 0  |                  "transform.",  | 
853  | 0  |                  nFailedCount, nSamplePoints);  | 
854  |  | 
  | 
855  | 0  |     bool bIsGeographicCoordsDeg = false;  | 
856  | 0  |     if (bIsGDALGenImgProjTransform)  | 
857  | 0  |     { | 
858  | 0  |         const GDALGenImgProjTransformInfo *pGIPTI =  | 
859  | 0  |             static_cast<const GDALGenImgProjTransformInfo *>(pTransformArg);  | 
860  | 0  |         if (pGIPTI->sSrcParams.pTransformer == GDALGeoLocTransform &&  | 
861  | 0  |             pGIPTI->sDstParams.pTransformer == nullptr &&  | 
862  | 0  |             pGIPTI->sDstParams.adfGeoTransform[0] == 0 &&  | 
863  | 0  |             pGIPTI->sDstParams.adfGeoTransform[1] == 1 &&  | 
864  | 0  |             pGIPTI->sDstParams.adfGeoTransform[2] == 0 &&  | 
865  | 0  |             pGIPTI->sDstParams.adfGeoTransform[3] == 0 &&  | 
866  | 0  |             pGIPTI->sDstParams.adfGeoTransform[4] == 0 &&  | 
867  | 0  |             pGIPTI->sDstParams.adfGeoTransform[5] == 1)  | 
868  | 0  |         { | 
869  |  |             /* --------------------------------------------------------------------  | 
870  |  |              */  | 
871  |  |             /*      Special case for geolocation array, to quickly find the  | 
872  |  |              * bounds. */  | 
873  |  |             /* --------------------------------------------------------------------  | 
874  |  |              */  | 
875  | 0  |             const GDALGeoLocTransformInfo *pGLTI =  | 
876  | 0  |                 static_cast<const GDALGeoLocTransformInfo *>(  | 
877  | 0  |                     pGIPTI->sSrcParams.pTransformArg);  | 
878  |  | 
  | 
879  | 0  |             if (pGIPTI->pReproject == nullptr)  | 
880  | 0  |             { | 
881  | 0  |                 const char *pszGLSRS =  | 
882  | 0  |                     CSLFetchNameValue(pGLTI->papszGeolocationInfo, "SRS");  | 
883  | 0  |                 if (pszGLSRS == nullptr)  | 
884  | 0  |                 { | 
885  | 0  |                     bIsGeographicCoordsDeg = true;  | 
886  | 0  |                 }  | 
887  | 0  |                 else  | 
888  | 0  |                 { | 
889  | 0  |                     OGRSpatialReference oSRS;  | 
890  | 0  |                     if (oSRS.SetFromUserInput(pszGLSRS) == OGRERR_NONE &&  | 
891  | 0  |                         oSRS.IsGeographic() &&  | 
892  | 0  |                         std::fabs(oSRS.GetAngularUnits() -  | 
893  | 0  |                                   CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-9)  | 
894  | 0  |                     { | 
895  | 0  |                         bIsGeographicCoordsDeg = true;  | 
896  | 0  |                     }  | 
897  | 0  |                 }  | 
898  | 0  |             }  | 
899  |  | 
  | 
900  | 0  |             for (const auto &xy :  | 
901  | 0  |                  {std::pair<double, double>(pGLTI->dfMinX, pGLTI->dfYAtMinX), | 
902  | 0  |                   std::pair<double, double>(pGLTI->dfXAtMinY, pGLTI->dfMinY),  | 
903  | 0  |                   std::pair<double, double>(pGLTI->dfMaxX, pGLTI->dfYAtMaxX),  | 
904  | 0  |                   std::pair<double, double>(pGLTI->dfXAtMaxY, pGLTI->dfMaxY)})  | 
905  | 0  |             { | 
906  | 0  |                 double x = xy.first;  | 
907  | 0  |                 double y = xy.second;  | 
908  | 0  |                 if (pGLTI->bSwapXY)  | 
909  | 0  |                 { | 
910  | 0  |                     std::swap(x, y);  | 
911  | 0  |                 }  | 
912  | 0  |                 double xOut = std::numeric_limits<double>::quiet_NaN();  | 
913  | 0  |                 double yOut = std::numeric_limits<double>::quiet_NaN();  | 
914  | 0  |                 if (pGIPTI->pReproject == nullptr ||  | 
915  | 0  |                     pGIPTI->pReproject(pGIPTI->pReprojectArg, false, 1, &x, &y,  | 
916  | 0  |                                        nullptr, nullptr))  | 
917  | 0  |                 { | 
918  | 0  |                     xOut = x;  | 
919  | 0  |                     yOut = y;  | 
920  | 0  |                 }  | 
921  | 0  |                 dfMinXOut = std::min(dfMinXOut, xOut);  | 
922  | 0  |                 dfMinYOut = std::min(dfMinYOut, yOut);  | 
923  | 0  |                 dfMaxXOut = std::max(dfMaxXOut, xOut);  | 
924  | 0  |                 dfMaxYOut = std::max(dfMaxYOut, yOut);  | 
925  | 0  |             }  | 
926  | 0  |         }  | 
927  | 0  |         else if (pGIPTI->sSrcParams.pTransformer == nullptr &&  | 
928  | 0  |                  pGIPTI->sDstParams.pTransformer == nullptr &&  | 
929  | 0  |                  pGIPTI->pReproject == GDALReprojectionTransform &&  | 
930  | 0  |                  pGIPTI->sDstParams.adfGeoTransform[0] == 0 &&  | 
931  | 0  |                  pGIPTI->sDstParams.adfGeoTransform[1] == 1 &&  | 
932  | 0  |                  pGIPTI->sDstParams.adfGeoTransform[2] == 0 &&  | 
933  | 0  |                  pGIPTI->sDstParams.adfGeoTransform[3] == 0 &&  | 
934  | 0  |                  pGIPTI->sDstParams.adfGeoTransform[4] == 0 &&  | 
935  | 0  |                  pGIPTI->sDstParams.adfGeoTransform[5] == 1)  | 
936  | 0  |         { | 
937  |  |             /* ------------------------------------------------------------- */  | 
938  |  |             /* Special case for warping using source geotransform and        */  | 
939  |  |             /* reprojection to deal with the poles.                          */  | 
940  |  |             /* ------------------------------------------------------------- */  | 
941  | 0  |             const GDALReprojectionTransformInfo *psRTI =  | 
942  | 0  |                 static_cast<const GDALReprojectionTransformInfo *>(  | 
943  | 0  |                     pGIPTI->pReprojectArg);  | 
944  | 0  |             const OGRSpatialReference *poSourceCRS =  | 
945  | 0  |                 psRTI->poForwardTransform->GetSourceCS();  | 
946  | 0  |             const OGRSpatialReference *poTargetCRS =  | 
947  | 0  |                 psRTI->poForwardTransform->GetTargetCS();  | 
948  | 0  |             if (poTargetCRS != nullptr &&  | 
949  | 0  |                 psRTI->poReverseTransform != nullptr &&  | 
950  | 0  |                 poTargetCRS->IsGeographic() &&  | 
951  | 0  |                 fabs(poTargetCRS->GetAngularUnits() -  | 
952  | 0  |                      CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-9 &&  | 
953  | 0  |                 (!poSourceCRS || !poSourceCRS->IsGeographic()))  | 
954  | 0  |             { | 
955  | 0  |                 bIsGeographicCoordsDeg = true;  | 
956  |  | 
  | 
957  | 0  |                 std::unique_ptr<CPLConfigOptionSetter> poSetter;  | 
958  | 0  |                 if (pGIPTI->bCheckWithInvertPROJ)  | 
959  | 0  |                 { | 
960  |  |                     // CHECK_WITH_INVERT_PROJ=YES prevent reliable  | 
961  |  |                     // transformation of poles.  | 
962  | 0  |                     poSetter = std::make_unique<CPLConfigOptionSetter>(  | 
963  | 0  |                         "CHECK_WITH_INVERT_PROJ", "NO", false);  | 
964  | 0  |                     GDALRefreshGenImgProjTransformer(pTransformArg);  | 
965  |  |                     // GDALRefreshGenImgProjTransformer() has invalidated psRTI  | 
966  | 0  |                     psRTI = static_cast<const GDALReprojectionTransformInfo *>(  | 
967  | 0  |                         pGIPTI->pReprojectArg);  | 
968  | 0  |                 }  | 
969  |  | 
  | 
970  | 0  |                 for (const auto &sign : iSignArray)  | 
971  | 0  |                 { | 
972  | 0  |                     double X = 0.0;  | 
973  | 0  |                     const double Yinit = 90.0 * sign;  | 
974  | 0  |                     double Y = Yinit;  | 
975  | 0  |                     if (psRTI->poReverseTransform->Transform(1, &X, &Y))  | 
976  | 0  |                     { | 
977  | 0  |                         const auto invGT =  | 
978  | 0  |                             pGIPTI->sSrcParams.adfInvGeoTransform;  | 
979  | 0  |                         const double x = invGT[0] + X * invGT[1] + Y * invGT[2];  | 
980  | 0  |                         const double y = invGT[3] + X * invGT[4] + Y * invGT[5];  | 
981  | 0  |                         constexpr double EPSILON = 1e-5;  | 
982  | 0  |                         if (x >= -EPSILON && x <= nInXSize + EPSILON &&  | 
983  | 0  |                             y >= -EPSILON && y <= nInYSize + EPSILON)  | 
984  | 0  |                         { | 
985  | 0  |                             if (psRTI->poForwardTransform->Transform(1, &X,  | 
986  | 0  |                                                                      &Y) &&  | 
987  | 0  |                                 fabs(Y - Yinit) <= 1e-6)  | 
988  | 0  |                             { | 
989  | 0  |                                 bool bMinXMaxXSet = false;  | 
990  | 0  |                                 if (poSourceCRS)  | 
991  | 0  |                                 { | 
992  | 0  |                                     const char *pszProjection =  | 
993  | 0  |                                         poSourceCRS->GetAttrValue("PROJECTION"); | 
994  | 0  |                                     if (pszProjection &&  | 
995  | 0  |                                         EQUAL(pszProjection,  | 
996  | 0  |                                               SRS_PT_ORTHOGRAPHIC))  | 
997  | 0  |                                     { | 
998  | 0  |                                         const double dfLon0 =  | 
999  | 0  |                                             poSourceCRS->GetNormProjParm(  | 
1000  | 0  |                                                 SRS_PP_CENTRAL_MERIDIAN, 0.0);  | 
1001  | 0  |                                         dfMinXOut = dfLon0 - 90;  | 
1002  | 0  |                                         dfMaxXOut = dfLon0 + 90;  | 
1003  | 0  |                                         bMinXMaxXSet = true;  | 
1004  | 0  |                                     }  | 
1005  | 0  |                                 }  | 
1006  | 0  |                                 if (!bMinXMaxXSet)  | 
1007  | 0  |                                 { | 
1008  | 0  |                                     dfMinXOut = -180;  | 
1009  | 0  |                                     dfMaxXOut = 180;  | 
1010  | 0  |                                 }  | 
1011  | 0  |                                 if (sign < 0)  | 
1012  | 0  |                                     dfMinYOut = Yinit;  | 
1013  | 0  |                                 else  | 
1014  | 0  |                                     dfMaxYOut = Yinit;  | 
1015  | 0  |                             }  | 
1016  | 0  |                         }  | 
1017  | 0  |                     }  | 
1018  | 0  |                 }  | 
1019  |  | 
  | 
1020  | 0  |                 if (poSetter)  | 
1021  | 0  |                 { | 
1022  | 0  |                     poSetter.reset();  | 
1023  | 0  |                     GDALRefreshGenImgProjTransformer(pTransformArg);  | 
1024  | 0  |                     pGIPTI = static_cast<const GDALGenImgProjTransformInfo *>(  | 
1025  | 0  |                         pTransformArg);  | 
1026  | 0  |                     psRTI = static_cast<const GDALReprojectionTransformInfo *>(  | 
1027  | 0  |                         pGIPTI->pReprojectArg);  | 
1028  | 0  |                     poSourceCRS = psRTI->poForwardTransform->GetSourceCS();  | 
1029  | 0  |                     poTargetCRS = psRTI->poForwardTransform->GetTargetCS();  | 
1030  | 0  |                 }  | 
1031  | 0  |             }  | 
1032  |  |  | 
1033  |  |             // Use TransformBounds() to handle more particular cases  | 
1034  | 0  |             if (poSourceCRS != nullptr && poTargetCRS != nullptr &&  | 
1035  | 0  |                 pGIPTI->sSrcParams.adfGeoTransform[1] != 0 &&  | 
1036  | 0  |                 pGIPTI->sSrcParams.adfGeoTransform[2] == 0 &&  | 
1037  | 0  |                 pGIPTI->sSrcParams.adfGeoTransform[4] == 0 &&  | 
1038  | 0  |                 pGIPTI->sSrcParams.adfGeoTransform[5] != 0)  | 
1039  | 0  |             { | 
1040  | 0  |                 const double dfULX = pGIPTI->sSrcParams.adfGeoTransform[0];  | 
1041  | 0  |                 const double dfULY = pGIPTI->sSrcParams.adfGeoTransform[3];  | 
1042  | 0  |                 const double dfLRX =  | 
1043  | 0  |                     dfULX + pGIPTI->sSrcParams.adfGeoTransform[1] * nInXSize;  | 
1044  | 0  |                 const double dfLRY =  | 
1045  | 0  |                     dfULY + pGIPTI->sSrcParams.adfGeoTransform[5] * nInYSize;  | 
1046  | 0  |                 const double dfMinSrcX = std::min(dfULX, dfLRX);  | 
1047  | 0  |                 const double dfMinSrcY = std::min(dfULY, dfLRY);  | 
1048  | 0  |                 const double dfMaxSrcX = std::max(dfULX, dfLRX);  | 
1049  | 0  |                 const double dfMaxSrcY = std::max(dfULY, dfLRY);  | 
1050  | 0  |                 double dfTmpMinXOut = std::numeric_limits<double>::max();  | 
1051  | 0  |                 double dfTmpMinYOut = std::numeric_limits<double>::max();  | 
1052  | 0  |                 double dfTmpMaxXOut = std::numeric_limits<double>::min();  | 
1053  | 0  |                 double dfTmpMaxYOut = std::numeric_limits<double>::min();  | 
1054  | 0  |                 if (psRTI->poForwardTransform->TransformBounds(  | 
1055  | 0  |                         dfMinSrcX, dfMinSrcY, dfMaxSrcX, dfMaxSrcY,  | 
1056  | 0  |                         &dfTmpMinXOut, &dfTmpMinYOut, &dfTmpMaxXOut,  | 
1057  | 0  |                         &dfTmpMaxYOut,  | 
1058  | 0  |                         2))  // minimum number of points as we already have a  | 
1059  |  |                              // logic above to sample  | 
1060  | 0  |                 { | 
1061  | 0  |                     dfMinXOut = std::min(dfMinXOut, dfTmpMinXOut);  | 
1062  | 0  |                     dfMinYOut = std::min(dfMinYOut, dfTmpMinYOut);  | 
1063  | 0  |                     dfMaxXOut = std::max(dfMaxXOut, dfTmpMaxXOut);  | 
1064  | 0  |                     dfMaxYOut = std::max(dfMaxYOut, dfTmpMaxYOut);  | 
1065  | 0  |                 }  | 
1066  | 0  |             }  | 
1067  | 0  |         }  | 
1068  | 0  |     }  | 
1069  |  |  | 
1070  |  |     /* -------------------------------------------------------------------- */  | 
1071  |  |     /*      Compute the distance in "georeferenced" units from the top      */  | 
1072  |  |     /*      corner of the transformed input image to the bottom left        */  | 
1073  |  |     /*      corner of the transformed input.  Use this distance to          */  | 
1074  |  |     /*      compute an approximate pixel size in the output                 */  | 
1075  |  |     /*      georeferenced coordinates.                                      */  | 
1076  |  |     /* -------------------------------------------------------------------- */  | 
1077  | 0  |     double dfDiagonalDist = 0.0;  | 
1078  | 0  |     double dfDeltaX = 0.0;  | 
1079  | 0  |     double dfDeltaY = 0.0;  | 
1080  |  | 
  | 
1081  | 0  |     if (pabSuccess[0] && pabSuccess[nSamplePoints - 1])  | 
1082  | 0  |     { | 
1083  | 0  |         dfDeltaX = padfX[nSamplePoints - 1] - padfX[0];  | 
1084  | 0  |         dfDeltaY = padfY[nSamplePoints - 1] - padfY[0];  | 
1085  |  |         // In some cases this can result in 0 values. See #5980  | 
1086  |  |         // Fallback to safer method in that case.  | 
1087  | 0  |     }  | 
1088  | 0  |     if (dfDeltaX == 0.0 || dfDeltaY == 0.0)  | 
1089  | 0  |     { | 
1090  | 0  |         dfDeltaX = dfMaxXOut - dfMinXOut;  | 
1091  | 0  |         dfDeltaY = dfMaxYOut - dfMinYOut;  | 
1092  | 0  |     }  | 
1093  |  | 
  | 
1094  | 0  |     dfDiagonalDist = sqrt(dfDeltaX * dfDeltaX + dfDeltaY * dfDeltaY);  | 
1095  |  |  | 
1096  |  |     /* -------------------------------------------------------------------- */  | 
1097  |  |     /*      Compute a pixel size from this.                                 */  | 
1098  |  |     /* -------------------------------------------------------------------- */  | 
1099  | 0  |     const double dfPixelSize =  | 
1100  | 0  |         dfDiagonalDist / sqrt(static_cast<double>(nInXSize) * nInXSize +  | 
1101  | 0  |                               static_cast<double>(nInYSize) * nInYSize);  | 
1102  |  | 
  | 
1103  | 0  |     const double dfPixels = (dfMaxXOut - dfMinXOut) / dfPixelSize;  | 
1104  | 0  |     const double dfLines = (dfMaxYOut - dfMinYOut) / dfPixelSize;  | 
1105  |  | 
  | 
1106  | 0  |     const int knIntMaxMinusOne = std::numeric_limits<int>::max() - 1;  | 
1107  | 0  |     if (dfPixels > knIntMaxMinusOne || dfLines > knIntMaxMinusOne)  | 
1108  | 0  |     { | 
1109  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
1110  | 0  |                  "Computed dimensions are too big : %.0f x %.0f",  | 
1111  | 0  |                  dfPixels + 0.5, dfLines + 0.5);  | 
1112  |  | 
  | 
1113  | 0  |         CPLFree(padfX);  | 
1114  | 0  |         CPLFree(padfXRevert);  | 
1115  | 0  |         CPLFree(pabSuccess);  | 
1116  |  | 
  | 
1117  | 0  |         return CE_Failure;  | 
1118  | 0  |     }  | 
1119  |  |  | 
1120  | 0  |     if ((nOptions & GDAL_SWO_ROUND_UP_SIZE) != 0)  | 
1121  | 0  |     { | 
1122  | 0  |         constexpr double EPS = 1e-5;  | 
1123  | 0  |         *pnPixels = static_cast<int>(std::ceil(dfPixels - EPS));  | 
1124  | 0  |         *pnLines = static_cast<int>(std::ceil(dfLines - EPS));  | 
1125  | 0  |     }  | 
1126  | 0  |     else  | 
1127  | 0  |     { | 
1128  | 0  |         *pnPixels = static_cast<int>(dfPixels + 0.5);  | 
1129  | 0  |         *pnLines = static_cast<int>(dfLines + 0.5);  | 
1130  | 0  |     }  | 
1131  |  | 
  | 
1132  | 0  |     double dfPixelSizeX = dfPixelSize;  | 
1133  | 0  |     double dfPixelSizeY = dfPixelSize;  | 
1134  |  | 
  | 
1135  | 0  |     const double adfRatioArray[] = {0.000, 0.001, 0.010, 0.100, 1.000}; | 
1136  |  |  | 
1137  |  |     /* -------------------------------------------------------------------- */  | 
1138  |  |     /*      Check that the right border is not completely out of source     */  | 
1139  |  |     /*      image. If so, adjust the x pixel size a bit in the hope it will */  | 
1140  |  |     /*      fit.                                                            */  | 
1141  |  |     /* -------------------------------------------------------------------- */  | 
1142  | 0  |     for (const auto &dfRatio : adfRatioArray)  | 
1143  | 0  |     { | 
1144  | 0  |         const double dfTryPixelSizeX =  | 
1145  | 0  |             dfPixelSizeX - dfPixelSizeX * dfRatio / *pnPixels;  | 
1146  | 0  |         double adfExtent[4] = {dfMinXOut, dfMaxYOut - (*pnLines) * dfPixelSizeY, | 
1147  | 0  |                                dfMinXOut + (*pnPixels) * dfTryPixelSizeX,  | 
1148  | 0  |                                dfMaxYOut};  | 
1149  | 0  |         if (!GDALSuggestedWarpOutput2_MustAdjustForRightBorder(  | 
1150  | 0  |                 pfnTransformer, pTransformArg, adfExtent, *pnPixels, *pnLines,  | 
1151  | 0  |                 dfTryPixelSizeX, dfPixelSizeY))  | 
1152  | 0  |         { | 
1153  | 0  |             dfPixelSizeX = dfTryPixelSizeX;  | 
1154  | 0  |             break;  | 
1155  | 0  |         }  | 
1156  | 0  |     }  | 
1157  |  |  | 
1158  |  |     /* -------------------------------------------------------------------- */  | 
1159  |  |     /*      Check that the bottom border is not completely out of source    */  | 
1160  |  |     /*      image. If so, adjust the y pixel size a bit in the hope it will */  | 
1161  |  |     /*      fit.                                                            */  | 
1162  |  |     /* -------------------------------------------------------------------- */  | 
1163  | 0  |     for (const auto &dfRatio : adfRatioArray)  | 
1164  | 0  |     { | 
1165  | 0  |         const double dfTryPixelSizeY =  | 
1166  | 0  |             dfPixelSizeY - dfPixelSizeY * dfRatio / *pnLines;  | 
1167  | 0  |         double adfExtent[4] = { | 
1168  | 0  |             dfMinXOut, dfMaxYOut - (*pnLines) * dfTryPixelSizeY,  | 
1169  | 0  |             dfMinXOut + (*pnPixels) * dfPixelSizeX, dfMaxYOut};  | 
1170  | 0  |         if (!GDALSuggestedWarpOutput2_MustAdjustForBottomBorder(  | 
1171  | 0  |                 pfnTransformer, pTransformArg, adfExtent, *pnPixels, *pnLines,  | 
1172  | 0  |                 dfPixelSizeX, dfTryPixelSizeY))  | 
1173  | 0  |         { | 
1174  | 0  |             dfPixelSizeY = dfTryPixelSizeY;  | 
1175  | 0  |             break;  | 
1176  | 0  |         }  | 
1177  | 0  |     }  | 
1178  |  |  | 
1179  |  |     /* -------------------------------------------------------------------- */  | 
1180  |  |     /*      Recompute some bounds so that all return values are consistent  */  | 
1181  |  |     /* -------------------------------------------------------------------- */  | 
1182  | 0  |     double dfMaxXOutNew = dfMinXOut + (*pnPixels) * dfPixelSizeX;  | 
1183  | 0  |     if (bIsGeographicCoordsDeg &&  | 
1184  | 0  |         ((dfMaxXOut <= 180 && dfMaxXOutNew > 180) || dfMaxXOut == 180))  | 
1185  | 0  |     { | 
1186  | 0  |         dfMaxXOut = 180;  | 
1187  | 0  |         dfPixelSizeX = (dfMaxXOut - dfMinXOut) / *pnPixels;  | 
1188  | 0  |     }  | 
1189  | 0  |     else  | 
1190  | 0  |     { | 
1191  | 0  |         dfMaxXOut = dfMaxXOutNew;  | 
1192  | 0  |     }  | 
1193  |  | 
  | 
1194  | 0  |     double dfMinYOutNew = dfMaxYOut - (*pnLines) * dfPixelSizeY;  | 
1195  | 0  |     if (bIsGeographicCoordsDeg && dfMinYOut >= -90 && dfMinYOutNew < -90)  | 
1196  | 0  |     { | 
1197  | 0  |         dfMinYOut = -90;  | 
1198  | 0  |         dfPixelSizeY = (dfMaxYOut - dfMinYOut) / *pnLines;  | 
1199  | 0  |     }  | 
1200  | 0  |     else  | 
1201  | 0  |     { | 
1202  | 0  |         dfMinYOut = dfMinYOutNew;  | 
1203  | 0  |     }  | 
1204  |  |  | 
1205  |  |     /* -------------------------------------------------------------------- */  | 
1206  |  |     /*      Return raw extents.                                             */  | 
1207  |  |     /* -------------------------------------------------------------------- */  | 
1208  | 0  |     padfExtent[0] = dfMinXOut;  | 
1209  | 0  |     padfExtent[1] = dfMinYOut;  | 
1210  | 0  |     padfExtent[2] = dfMaxXOut;  | 
1211  | 0  |     padfExtent[3] = dfMaxYOut;  | 
1212  |  |  | 
1213  |  |     /* -------------------------------------------------------------------- */  | 
1214  |  |     /*      Set the output geotransform.                                    */  | 
1215  |  |     /* -------------------------------------------------------------------- */  | 
1216  | 0  |     padfGeoTransformOut[0] = dfMinXOut;  | 
1217  | 0  |     padfGeoTransformOut[1] = dfPixelSizeX;  | 
1218  | 0  |     padfGeoTransformOut[2] = 0.0;  | 
1219  | 0  |     padfGeoTransformOut[3] = dfMaxYOut;  | 
1220  | 0  |     padfGeoTransformOut[4] = 0.0;  | 
1221  | 0  |     padfGeoTransformOut[5] = -dfPixelSizeY;  | 
1222  |  | 
  | 
1223  | 0  |     CPLFree(padfX);  | 
1224  | 0  |     CPLFree(padfXRevert);  | 
1225  | 0  |     CPLFree(pabSuccess);  | 
1226  |  | 
  | 
1227  | 0  |     return CE_None;  | 
1228  | 0  | }  | 
1229  |  |  | 
1230  |  | /************************************************************************/  | 
1231  |  | /*                    GetCurrentCheckWithInvertPROJ()                   */  | 
1232  |  | /************************************************************************/  | 
1233  |  |  | 
1234  |  | static bool GetCurrentCheckWithInvertPROJ()  | 
1235  | 0  | { | 
1236  | 0  |     return CPLTestBool(CPLGetConfigOption("CHECK_WITH_INVERT_PROJ", "NO")); | 
1237  | 0  | }  | 
1238  |  |  | 
1239  |  | /************************************************************************/  | 
1240  |  | /*               GDALCreateGenImgProjTransformerInternal()              */  | 
1241  |  | /************************************************************************/  | 
1242  |  |  | 
1243  |  | static void *GDALCreateSimilarGenImgProjTransformer(void *hTransformArg,  | 
1244  |  |                                                     double dfRatioX,  | 
1245  |  |                                                     double dfRatioY);  | 
1246  |  |  | 
1247  |  | static GDALGenImgProjTransformInfo *GDALCreateGenImgProjTransformerInternal()  | 
1248  | 0  | { | 
1249  |  |     /* -------------------------------------------------------------------- */  | 
1250  |  |     /*      Initialize the transform info.                                  */  | 
1251  |  |     /* -------------------------------------------------------------------- */  | 
1252  | 0  |     GDALGenImgProjTransformInfo *psInfo =  | 
1253  | 0  |         static_cast<GDALGenImgProjTransformInfo *>(  | 
1254  | 0  |             CPLCalloc(sizeof(GDALGenImgProjTransformInfo), 1));  | 
1255  |  | 
  | 
1256  | 0  |     memcpy(psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,  | 
1257  | 0  |            strlen(GDAL_GTI2_SIGNATURE));  | 
1258  | 0  |     psInfo->sTI.pszClassName = GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME;  | 
1259  | 0  |     psInfo->sTI.pfnTransform = GDALGenImgProjTransform;  | 
1260  | 0  |     psInfo->sTI.pfnCleanup = GDALDestroyGenImgProjTransformer;  | 
1261  | 0  |     psInfo->sTI.pfnSerialize = GDALSerializeGenImgProjTransformer;  | 
1262  | 0  |     psInfo->sTI.pfnCreateSimilar = GDALCreateSimilarGenImgProjTransformer;  | 
1263  |  | 
  | 
1264  | 0  |     psInfo->bCheckWithInvertPROJ = GetCurrentCheckWithInvertPROJ();  | 
1265  | 0  |     psInfo->bHasCustomTransformationPipeline = false;  | 
1266  |  | 
  | 
1267  | 0  |     return psInfo;  | 
1268  | 0  | }  | 
1269  |  |  | 
1270  |  | /************************************************************************/  | 
1271  |  | /*                GDALCreateSimilarGenImgProjTransformer()              */  | 
1272  |  | /************************************************************************/  | 
1273  |  |  | 
1274  |  | static void *GDALCreateSimilarGenImgProjTransformer(void *hTransformArg,  | 
1275  |  |                                                     double dfRatioX,  | 
1276  |  |                                                     double dfRatioY)  | 
1277  | 0  | { | 
1278  | 0  |     VALIDATE_POINTER1(hTransformArg, "GDALCreateSimilarGenImgProjTransformer",  | 
1279  | 0  |                       nullptr);  | 
1280  |  |  | 
1281  | 0  |     GDALGenImgProjTransformInfo *psInfo =  | 
1282  | 0  |         static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);  | 
1283  |  | 
  | 
1284  | 0  |     GDALGenImgProjTransformInfo *psClonedInfo =  | 
1285  | 0  |         GDALCreateGenImgProjTransformerInternal();  | 
1286  |  | 
  | 
1287  | 0  |     memcpy(psClonedInfo, psInfo, sizeof(GDALGenImgProjTransformInfo));  | 
1288  |  | 
  | 
1289  | 0  |     psClonedInfo->bCheckWithInvertPROJ = GetCurrentCheckWithInvertPROJ();  | 
1290  |  | 
  | 
1291  | 0  |     if (psClonedInfo->sSrcParams.pTransformArg)  | 
1292  | 0  |         psClonedInfo->sSrcParams.pTransformArg = GDALCreateSimilarTransformer(  | 
1293  | 0  |             psInfo->sSrcParams.pTransformArg, dfRatioX, dfRatioY);  | 
1294  | 0  |     else if (dfRatioX != 1.0 || dfRatioY != 1.0)  | 
1295  | 0  |     { | 
1296  | 0  |         if (psClonedInfo->sSrcParams.adfGeoTransform[2] == 0.0 &&  | 
1297  | 0  |             psClonedInfo->sSrcParams.adfGeoTransform[4] == 0.0)  | 
1298  | 0  |         { | 
1299  | 0  |             psClonedInfo->sSrcParams.adfGeoTransform[1] *= dfRatioX;  | 
1300  | 0  |             psClonedInfo->sSrcParams.adfGeoTransform[5] *= dfRatioY;  | 
1301  | 0  |         }  | 
1302  | 0  |         else  | 
1303  | 0  |         { | 
1304  |  |             // If the x and y ratios are not equal, then we cannot really  | 
1305  |  |             // compute a geotransform.  | 
1306  | 0  |             psClonedInfo->sSrcParams.adfGeoTransform[1] *= dfRatioX;  | 
1307  | 0  |             psClonedInfo->sSrcParams.adfGeoTransform[2] *= dfRatioX;  | 
1308  | 0  |             psClonedInfo->sSrcParams.adfGeoTransform[4] *= dfRatioX;  | 
1309  | 0  |             psClonedInfo->sSrcParams.adfGeoTransform[5] *= dfRatioX;  | 
1310  | 0  |         }  | 
1311  | 0  |         if (!GDALInvGeoTransform(psClonedInfo->sSrcParams.adfGeoTransform,  | 
1312  | 0  |                                  psClonedInfo->sSrcParams.adfInvGeoTransform))  | 
1313  | 0  |         { | 
1314  | 0  |             CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform");  | 
1315  | 0  |             GDALDestroyGenImgProjTransformer(psClonedInfo);  | 
1316  | 0  |             return nullptr;  | 
1317  | 0  |         }  | 
1318  | 0  |     }  | 
1319  |  |  | 
1320  | 0  |     if (psClonedInfo->pReprojectArg)  | 
1321  | 0  |         psClonedInfo->pReprojectArg =  | 
1322  | 0  |             GDALCloneTransformer(psInfo->pReprojectArg);  | 
1323  |  | 
  | 
1324  | 0  |     if (psClonedInfo->sDstParams.pTransformArg)  | 
1325  | 0  |         psClonedInfo->sDstParams.pTransformArg =  | 
1326  | 0  |             GDALCloneTransformer(psInfo->sDstParams.pTransformArg);  | 
1327  |  | 
  | 
1328  | 0  |     return psClonedInfo;  | 
1329  | 0  | }  | 
1330  |  |  | 
1331  |  | /************************************************************************/  | 
1332  |  | /*                  GDALCreateGenImgProjTransformer()                   */  | 
1333  |  | /************************************************************************/  | 
1334  |  |  | 
1335  |  | /**  | 
1336  |  |  * Create image to image transformer.  | 
1337  |  |  *  | 
1338  |  |  * This function creates a transformation object that maps from pixel/line  | 
1339  |  |  * coordinates on one image to pixel/line coordinates on another image.  The  | 
1340  |  |  * images may potentially be georeferenced in different coordinate systems,  | 
1341  |  |  * and may used GCPs to map between their pixel/line coordinates and  | 
1342  |  |  * georeferenced coordinates (as opposed to the default assumption that their  | 
1343  |  |  * geotransform should be used).  | 
1344  |  |  *  | 
1345  |  |  * This transformer potentially performs three concatenated transformations.  | 
1346  |  |  *  | 
1347  |  |  * The first stage is from source image pixel/line coordinates to source  | 
1348  |  |  * image georeferenced coordinates, and may be done using the geotransform,  | 
1349  |  |  * or if not defined using a polynomial model derived from GCPs.  If GCPs  | 
1350  |  |  * are used this stage is accomplished using GDALGCPTransform().  | 
1351  |  |  *  | 
1352  |  |  * The second stage is to change projections from the source coordinate system  | 
1353  |  |  * to the destination coordinate system, assuming they differ.  This is  | 
1354  |  |  * accomplished internally using GDALReprojectionTransform().  | 
1355  |  |  *  | 
1356  |  |  * The third stage is converting from destination image georeferenced  | 
1357  |  |  * coordinates to destination image coordinates.  This is done using the  | 
1358  |  |  * destination image geotransform, or if not available, using a polynomial  | 
1359  |  |  * model derived from GCPs. If GCPs are used this stage is accomplished using  | 
1360  |  |  * GDALGCPTransform().  This stage is skipped if hDstDS is NULL when the  | 
1361  |  |  * transformation is created.  | 
1362  |  |  *  | 
1363  |  |  * @param hSrcDS source dataset, or NULL.  | 
1364  |  |  * @param pszSrcWKT the coordinate system for the source dataset.  If NULL,  | 
1365  |  |  * it will be read from the dataset itself.  | 
1366  |  |  * @param hDstDS destination dataset (or NULL).  | 
1367  |  |  * @param pszDstWKT the coordinate system for the destination dataset.  If  | 
1368  |  |  * NULL, and hDstDS not NULL, it will be read from the destination dataset.  | 
1369  |  |  * @param bGCPUseOK TRUE if GCPs should be used if the geotransform is not  | 
1370  |  |  * available on the source dataset (not destination).  | 
1371  |  |  * @param dfGCPErrorThreshold ignored/deprecated.  | 
1372  |  |  * @param nOrder the maximum order to use for GCP derived polynomials if  | 
1373  |  |  * possible.  Use 0 to autoselect, or -1 for thin plate splines.  | 
1374  |  |  *  | 
1375  |  |  * @return handle suitable for use GDALGenImgProjTransform(), and to be  | 
1376  |  |  * deallocated with GDALDestroyGenImgProjTransformer().  | 
1377  |  |  */  | 
1378  |  |  | 
1379  |  | void *GDALCreateGenImgProjTransformer(GDALDatasetH hSrcDS,  | 
1380  |  |                                       const char *pszSrcWKT,  | 
1381  |  |                                       GDALDatasetH hDstDS,  | 
1382  |  |                                       const char *pszDstWKT, int bGCPUseOK,  | 
1383  |  |                                       CPL_UNUSED double dfGCPErrorThreshold,  | 
1384  |  |                                       int nOrder)  | 
1385  | 0  | { | 
1386  | 0  |     char **papszOptions = nullptr;  | 
1387  |  | 
  | 
1388  | 0  |     if (pszSrcWKT != nullptr)  | 
1389  | 0  |         papszOptions = CSLSetNameValue(papszOptions, "SRC_SRS", pszSrcWKT);  | 
1390  | 0  |     if (pszDstWKT != nullptr)  | 
1391  | 0  |         papszOptions = CSLSetNameValue(papszOptions, "DST_SRS", pszDstWKT);  | 
1392  | 0  |     if (!bGCPUseOK)  | 
1393  | 0  |         papszOptions = CSLSetNameValue(papszOptions, "GCPS_OK", "FALSE");  | 
1394  | 0  |     if (nOrder != 0)  | 
1395  | 0  |         papszOptions = CSLSetNameValue(papszOptions, "MAX_GCP_ORDER",  | 
1396  | 0  |                                        CPLString().Printf("%d", nOrder)); | 
1397  |  | 
  | 
1398  | 0  |     void *pRet = GDALCreateGenImgProjTransformer2(hSrcDS, hDstDS, papszOptions);  | 
1399  | 0  |     CSLDestroy(papszOptions);  | 
1400  |  | 
  | 
1401  | 0  |     return pRet;  | 
1402  | 0  | }  | 
1403  |  |  | 
1404  |  | /************************************************************************/  | 
1405  |  | /*                          InsertCenterLong()                          */  | 
1406  |  | /*                                                                      */  | 
1407  |  | /*      Insert a CENTER_LONG Extension entry on a GEOGCS to indicate    */  | 
1408  |  | /*      the center longitude of the dataset for wrapping purposes.      */  | 
1409  |  | /************************************************************************/  | 
1410  |  |  | 
1411  |  | static void InsertCenterLong(GDALDatasetH hDS, OGRSpatialReference *poSRS,  | 
1412  |  |                              CPLStringList &aosOptions)  | 
1413  |  |  | 
1414  | 0  | { | 
1415  | 0  |     if (!poSRS->IsGeographic() || std::fabs(poSRS->GetAngularUnits() -  | 
1416  | 0  |                                             CPLAtof(SRS_UA_DEGREE_CONV)) > 1e-9)  | 
1417  | 0  |     { | 
1418  | 0  |         return;  | 
1419  | 0  |     }  | 
1420  |  |  | 
1421  | 0  |     if (poSRS->GetExtension(nullptr, "CENTER_LONG"))  | 
1422  | 0  |         return;  | 
1423  |  |  | 
1424  |  |     /* -------------------------------------------------------------------- */  | 
1425  |  |     /*      For now we only do this if we have a geotransform since         */  | 
1426  |  |     /*      other forms require a bunch of extra work.                      */  | 
1427  |  |     /* -------------------------------------------------------------------- */  | 
1428  | 0  |     double adfGeoTransform[6] = {}; | 
1429  |  | 
  | 
1430  | 0  |     if (GDALGetGeoTransform(hDS, adfGeoTransform) != CE_None)  | 
1431  | 0  |         return;  | 
1432  |  |  | 
1433  |  |     /* -------------------------------------------------------------------- */  | 
1434  |  |     /*      Compute min/max longitude based on testing the four corners.    */  | 
1435  |  |     /* -------------------------------------------------------------------- */  | 
1436  | 0  |     const int nXSize = GDALGetRasterXSize(hDS);  | 
1437  | 0  |     const int nYSize = GDALGetRasterYSize(hDS);  | 
1438  |  | 
  | 
1439  | 0  |     const double dfMinLong =  | 
1440  | 0  |         std::min(std::min(adfGeoTransform[0] + 0 * adfGeoTransform[1] +  | 
1441  | 0  |                               0 * adfGeoTransform[2],  | 
1442  | 0  |                           adfGeoTransform[0] + nXSize * adfGeoTransform[1] +  | 
1443  | 0  |                               0 * adfGeoTransform[2]),  | 
1444  | 0  |                  std::min(adfGeoTransform[0] + 0 * adfGeoTransform[1] +  | 
1445  | 0  |                               nYSize * adfGeoTransform[2],  | 
1446  | 0  |                           adfGeoTransform[0] + nXSize * adfGeoTransform[1] +  | 
1447  | 0  |                               nYSize * adfGeoTransform[2]));  | 
1448  | 0  |     const double dfMaxLong =  | 
1449  | 0  |         std::max(std::max(adfGeoTransform[0] + 0 * adfGeoTransform[1] +  | 
1450  | 0  |                               0 * adfGeoTransform[2],  | 
1451  | 0  |                           adfGeoTransform[0] + nXSize * adfGeoTransform[1] +  | 
1452  | 0  |                               0 * adfGeoTransform[2]),  | 
1453  | 0  |                  std::max(adfGeoTransform[0] + 0 * adfGeoTransform[1] +  | 
1454  | 0  |                               nYSize * adfGeoTransform[2],  | 
1455  | 0  |                           adfGeoTransform[0] + nXSize * adfGeoTransform[1] +  | 
1456  | 0  |                               nYSize * adfGeoTransform[2]));  | 
1457  |  | 
  | 
1458  | 0  |     const double dfEpsilon =  | 
1459  | 0  |         std::max(std::fabs(adfGeoTransform[1]), std::fabs(adfGeoTransform[2]));  | 
1460  |  |     // If the raster covers more than 360 degree (allow an extra pixel),  | 
1461  |  |     // give up  | 
1462  | 0  |     constexpr double RELATIVE_EPSILON = 0.05;  // for numeric precision issues  | 
1463  | 0  |     if (dfMaxLong - dfMinLong > 360.0 + dfEpsilon * (1 + RELATIVE_EPSILON))  | 
1464  | 0  |         return;  | 
1465  |  |  | 
1466  |  |     /* -------------------------------------------------------------------- */  | 
1467  |  |     /*      Insert center long.                                             */  | 
1468  |  |     /* -------------------------------------------------------------------- */  | 
1469  | 0  |     const double dfCenterLong = (dfMaxLong + dfMinLong) / 2.0;  | 
1470  | 0  |     aosOptions.SetNameValue("CENTER_LONG", CPLSPrintf("%g", dfCenterLong)); | 
1471  | 0  | }  | 
1472  |  |  | 
1473  |  | /************************************************************************/  | 
1474  |  | /*                      GDALComputeAreaOfInterest()                     */  | 
1475  |  | /************************************************************************/  | 
1476  |  |  | 
1477  |  | bool GDALComputeAreaOfInterest(OGRSpatialReference *poSRS, double adfGT[6],  | 
1478  |  |                                int nXSize, int nYSize,  | 
1479  |  |                                double &dfWestLongitudeDeg,  | 
1480  |  |                                double &dfSouthLatitudeDeg,  | 
1481  |  |                                double &dfEastLongitudeDeg,  | 
1482  |  |                                double &dfNorthLatitudeDeg)  | 
1483  | 0  | { | 
1484  | 0  |     bool ret = false;  | 
1485  |  | 
  | 
1486  | 0  |     if (!poSRS)  | 
1487  | 0  |         return false;  | 
1488  |  |  | 
1489  | 0  |     OGRSpatialReference oSrcSRSHoriz(*poSRS);  | 
1490  | 0  |     if (oSrcSRSHoriz.IsCompound())  | 
1491  | 0  |     { | 
1492  | 0  |         oSrcSRSHoriz.StripVertical();  | 
1493  | 0  |     }  | 
1494  |  | 
  | 
1495  | 0  |     OGRSpatialReference *poGeog = oSrcSRSHoriz.CloneGeogCS();  | 
1496  | 0  |     if (poGeog)  | 
1497  | 0  |     { | 
1498  | 0  |         poGeog->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
1499  | 0  |         poGeog->SetAngularUnits(SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV));  | 
1500  |  | 
  | 
1501  | 0  |         auto poCT = OGRCreateCoordinateTransformation(&oSrcSRSHoriz, poGeog);  | 
1502  | 0  |         if (poCT)  | 
1503  | 0  |         { | 
1504  | 0  |             poCT->SetEmitErrors(false);  | 
1505  |  | 
  | 
1506  | 0  |             double x[4], y[4];  | 
1507  | 0  |             x[0] = adfGT[0];  | 
1508  | 0  |             y[0] = adfGT[3];  | 
1509  | 0  |             x[1] = adfGT[0] + nXSize * adfGT[1];  | 
1510  | 0  |             y[1] = adfGT[3];  | 
1511  | 0  |             x[2] = adfGT[0];  | 
1512  | 0  |             y[2] = adfGT[3] + nYSize * adfGT[5];  | 
1513  | 0  |             x[3] = x[1];  | 
1514  | 0  |             y[3] = y[2];  | 
1515  | 0  |             int validity[4] = {false, false, false, false}; | 
1516  | 0  |             poCT->Transform(4, x, y, nullptr, validity);  | 
1517  | 0  |             dfWestLongitudeDeg = std::numeric_limits<double>::max();  | 
1518  | 0  |             dfSouthLatitudeDeg = std::numeric_limits<double>::max();  | 
1519  | 0  |             dfEastLongitudeDeg = -std::numeric_limits<double>::max();  | 
1520  | 0  |             dfNorthLatitudeDeg = -std::numeric_limits<double>::max();  | 
1521  | 0  |             for (int i = 0; i < 4; i++)  | 
1522  | 0  |             { | 
1523  | 0  |                 if (validity[i])  | 
1524  | 0  |                 { | 
1525  | 0  |                     ret = true;  | 
1526  | 0  |                     dfWestLongitudeDeg = std::min(dfWestLongitudeDeg, x[i]);  | 
1527  | 0  |                     dfSouthLatitudeDeg = std::min(dfSouthLatitudeDeg, y[i]);  | 
1528  | 0  |                     dfEastLongitudeDeg = std::max(dfEastLongitudeDeg, x[i]);  | 
1529  | 0  |                     dfNorthLatitudeDeg = std::max(dfNorthLatitudeDeg, y[i]);  | 
1530  | 0  |                 }  | 
1531  | 0  |             }  | 
1532  | 0  |             if (validity[0] && validity[1] && x[0] > x[1])  | 
1533  | 0  |             { | 
1534  | 0  |                 dfWestLongitudeDeg = x[0];  | 
1535  | 0  |                 dfEastLongitudeDeg = x[1];  | 
1536  | 0  |             }  | 
1537  | 0  |             if (ret && std::fabs(dfWestLongitudeDeg) <= 180 &&  | 
1538  | 0  |                 std::fabs(dfEastLongitudeDeg) <= 180 &&  | 
1539  | 0  |                 std::fabs(dfSouthLatitudeDeg) <= 90 &&  | 
1540  | 0  |                 std::fabs(dfNorthLatitudeDeg) <= 90)  | 
1541  | 0  |             { | 
1542  | 0  |                 CPLDebug("GDAL", "Computing area of interest: %g, %g, %g, %g", | 
1543  | 0  |                          dfWestLongitudeDeg, dfSouthLatitudeDeg,  | 
1544  | 0  |                          dfEastLongitudeDeg, dfNorthLatitudeDeg);  | 
1545  | 0  |             }  | 
1546  | 0  |             else  | 
1547  | 0  |             { | 
1548  | 0  |                 CPLDebug("GDAL", "Could not compute area of interest"); | 
1549  | 0  |                 dfWestLongitudeDeg = 0;  | 
1550  | 0  |                 dfSouthLatitudeDeg = 0;  | 
1551  | 0  |                 dfEastLongitudeDeg = 0;  | 
1552  | 0  |                 dfNorthLatitudeDeg = 0;  | 
1553  | 0  |             }  | 
1554  | 0  |             OGRCoordinateTransformation::DestroyCT(poCT);  | 
1555  | 0  |         }  | 
1556  |  | 
  | 
1557  | 0  |         delete poGeog;  | 
1558  | 0  |     }  | 
1559  |  | 
  | 
1560  | 0  |     return ret;  | 
1561  | 0  | }  | 
1562  |  |  | 
1563  |  | bool GDALComputeAreaOfInterest(OGRSpatialReference *poSRS, double dfX1,  | 
1564  |  |                                double dfY1, double dfX2, double dfY2,  | 
1565  |  |                                double &dfWestLongitudeDeg,  | 
1566  |  |                                double &dfSouthLatitudeDeg,  | 
1567  |  |                                double &dfEastLongitudeDeg,  | 
1568  |  |                                double &dfNorthLatitudeDeg)  | 
1569  | 0  | { | 
1570  | 0  |     bool ret = false;  | 
1571  |  | 
  | 
1572  | 0  |     if (!poSRS)  | 
1573  | 0  |         return false;  | 
1574  |  |  | 
1575  | 0  |     OGRSpatialReference oSrcSRSHoriz(*poSRS);  | 
1576  | 0  |     if (oSrcSRSHoriz.IsCompound())  | 
1577  | 0  |     { | 
1578  | 0  |         oSrcSRSHoriz.StripVertical();  | 
1579  | 0  |     }  | 
1580  |  | 
  | 
1581  | 0  |     OGRSpatialReference *poGeog = oSrcSRSHoriz.CloneGeogCS();  | 
1582  | 0  |     if (poGeog)  | 
1583  | 0  |     { | 
1584  | 0  |         poGeog->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
1585  |  | 
  | 
1586  | 0  |         auto poCT = OGRCreateCoordinateTransformation(&oSrcSRSHoriz, poGeog);  | 
1587  | 0  |         if (poCT)  | 
1588  | 0  |         { | 
1589  | 0  |             double x[4], y[4];  | 
1590  | 0  |             x[0] = dfX1;  | 
1591  | 0  |             y[0] = dfY1;  | 
1592  | 0  |             x[1] = dfX2;  | 
1593  | 0  |             y[1] = dfY1;  | 
1594  | 0  |             x[2] = dfX1;  | 
1595  | 0  |             y[2] = dfY2;  | 
1596  | 0  |             x[3] = dfX2;  | 
1597  | 0  |             y[3] = dfY2;  | 
1598  | 0  |             int validity[4] = {false, false, false, false}; | 
1599  | 0  |             poCT->Transform(4, x, y, nullptr, validity);  | 
1600  | 0  |             dfWestLongitudeDeg = std::numeric_limits<double>::max();  | 
1601  | 0  |             dfSouthLatitudeDeg = std::numeric_limits<double>::max();  | 
1602  | 0  |             dfEastLongitudeDeg = -std::numeric_limits<double>::max();  | 
1603  | 0  |             dfNorthLatitudeDeg = -std::numeric_limits<double>::max();  | 
1604  | 0  |             for (int i = 0; i < 4; i++)  | 
1605  | 0  |             { | 
1606  | 0  |                 if (validity[i])  | 
1607  | 0  |                 { | 
1608  | 0  |                     ret = true;  | 
1609  | 0  |                     dfWestLongitudeDeg = std::min(dfWestLongitudeDeg, x[i]);  | 
1610  | 0  |                     dfSouthLatitudeDeg = std::min(dfSouthLatitudeDeg, y[i]);  | 
1611  | 0  |                     dfEastLongitudeDeg = std::max(dfEastLongitudeDeg, x[i]);  | 
1612  | 0  |                     dfNorthLatitudeDeg = std::max(dfNorthLatitudeDeg, y[i]);  | 
1613  | 0  |                 }  | 
1614  | 0  |             }  | 
1615  | 0  |             if (validity[0] && validity[1] && (dfX1 - dfX2) * (x[0] - x[1]) < 0)  | 
1616  | 0  |             { | 
1617  | 0  |                 dfWestLongitudeDeg = x[0];  | 
1618  | 0  |                 dfEastLongitudeDeg = x[1];  | 
1619  | 0  |             }  | 
1620  | 0  |             if (ret)  | 
1621  | 0  |             { | 
1622  | 0  |                 CPLDebug("GDAL", "Computing area of interest: %g, %g, %g, %g", | 
1623  | 0  |                          dfWestLongitudeDeg, dfSouthLatitudeDeg,  | 
1624  | 0  |                          dfEastLongitudeDeg, dfNorthLatitudeDeg);  | 
1625  | 0  |             }  | 
1626  | 0  |             else  | 
1627  | 0  |             { | 
1628  | 0  |                 CPLDebug("GDAL", "Could not compute area of interest"); | 
1629  | 0  |                 dfWestLongitudeDeg = 0;  | 
1630  | 0  |                 dfSouthLatitudeDeg = 0;  | 
1631  | 0  |                 dfEastLongitudeDeg = 0;  | 
1632  | 0  |                 dfNorthLatitudeDeg = 0;  | 
1633  | 0  |             }  | 
1634  | 0  |             delete poCT;  | 
1635  | 0  |         }  | 
1636  |  | 
  | 
1637  | 0  |         delete poGeog;  | 
1638  | 0  |     }  | 
1639  |  | 
  | 
1640  | 0  |     return ret;  | 
1641  | 0  | }  | 
1642  |  |  | 
1643  |  | /************************************************************************/  | 
1644  |  | /*                    GDALGCPAntimeridianUnwrap()                       */  | 
1645  |  | /************************************************************************/  | 
1646  |  |  | 
1647  |  | /* Deal with discontinuties of dfGCPX longitudes around the anti-meridian.  | 
1648  |  |  * Cf https://github.com/OSGeo/gdal/issues/8371  | 
1649  |  |  */  | 
1650  |  | static void GDALGCPAntimeridianUnwrap(int nGCPCount, GDAL_GCP *pasGCPList,  | 
1651  |  |                                       const OGRSpatialReference &oSRS,  | 
1652  |  |                                       CSLConstList papszOptions)  | 
1653  | 0  | { | 
1654  | 0  |     const char *pszGCPAntimeridianUnwrap =  | 
1655  | 0  |         CSLFetchNameValueDef(papszOptions, "GCP_ANTIMERIDIAN_UNWRAP", "AUTO");  | 
1656  | 0  |     const bool bForced = EQUAL(pszGCPAntimeridianUnwrap, "YES") ||  | 
1657  | 0  |                          EQUAL(pszGCPAntimeridianUnwrap, "ON") ||  | 
1658  | 0  |                          EQUAL(pszGCPAntimeridianUnwrap, "TRUE") ||  | 
1659  | 0  |                          EQUAL(pszGCPAntimeridianUnwrap, "1");  | 
1660  | 0  |     if (bForced || (!oSRS.IsEmpty() && oSRS.IsGeographic() &&  | 
1661  | 0  |                     fabs(oSRS.GetAngularUnits(nullptr) -  | 
1662  | 0  |                          CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-8 &&  | 
1663  | 0  |                     EQUAL(pszGCPAntimeridianUnwrap, "AUTO")))  | 
1664  | 0  |     { | 
1665  | 0  |         if (!bForced)  | 
1666  | 0  |         { | 
1667  |  |             // Proceed to unwrapping only if the longitudes are within  | 
1668  |  |             // [-180, -170] or [170, 180]  | 
1669  | 0  |             for (int i = 0; i < nGCPCount; ++i)  | 
1670  | 0  |             { | 
1671  | 0  |                 const double dfLongAbs = fabs(pasGCPList[i].dfGCPX);  | 
1672  | 0  |                 if (dfLongAbs > 180 || dfLongAbs < 170)  | 
1673  | 0  |                 { | 
1674  | 0  |                     return;  | 
1675  | 0  |                 }  | 
1676  | 0  |             }  | 
1677  | 0  |         }  | 
1678  |  |  | 
1679  | 0  |         bool bDone = false;  | 
1680  | 0  |         for (int i = 0; i < nGCPCount; ++i)  | 
1681  | 0  |         { | 
1682  | 0  |             if (pasGCPList[i].dfGCPX < 0)  | 
1683  | 0  |             { | 
1684  | 0  |                 if (!bDone)  | 
1685  | 0  |                 { | 
1686  | 0  |                     bDone = true;  | 
1687  | 0  |                     CPLDebug("WARP", "GCP longitude unwrapping"); | 
1688  | 0  |                 }  | 
1689  | 0  |                 pasGCPList[i].dfGCPX += 360;  | 
1690  | 0  |             }  | 
1691  | 0  |         }  | 
1692  | 0  |     }  | 
1693  | 0  | }  | 
1694  |  |  | 
1695  |  | /************************************************************************/  | 
1696  |  | /*              GDALGetGenImgProjTranformerOptionList()                 */  | 
1697  |  | /************************************************************************/  | 
1698  |  |  | 
1699  |  | /** Return a XML string describing options accepted by  | 
1700  |  |  * GDALCreateGenImgProjTransformer2().  | 
1701  |  |  *  | 
1702  |  |  * @since 3.11  | 
1703  |  |  */  | 
1704  |  | const char *GDALGetGenImgProjTranformerOptionList(void)  | 
1705  | 0  | { | 
1706  | 0  |     return "<OptionList>"  | 
1707  | 0  |            "<Option name='SRC_SRS' type='string' description='WKT SRS, or any "  | 
1708  | 0  |            "string recognized by OGRSpatialReference::SetFromUserInput(), to "  | 
1709  | 0  |            "be used as an override for CRS of input dataset'/>"  | 
1710  | 0  |            "<Option name='DST_SRS' type='string' description='WKT SRS, or any "  | 
1711  | 0  |            "string recognized by OGRSpatialReference::SetFromUserInput(), to "  | 
1712  | 0  |            "be used as an override for CRS of output dataset'/>"  | 
1713  | 0  |            "<Option name='PROMOTE_TO_3D' type='boolean' description='"  | 
1714  | 0  |            "Whether to promote SRC_SRS / DST_SRS to 3D.' "  | 
1715  | 0  |            "default='NO'/>"  | 
1716  | 0  |            "<Option name='COORDINATE_OPERATION' type='string' description='"  | 
1717  | 0  |            "Coordinate operation, as a PROJ or WKT string, used as an override "  | 
1718  | 0  |            "over the normally computed pipeline. The pipeline must take into "  | 
1719  | 0  |            "account the axis order of the source and target SRS.'/>"  | 
1720  | 0  |            "<Option name='ALLOW_BALLPARK' type='boolean' description='"  | 
1721  | 0  |            "Whether ballpark coordinate operations are allowed.' "  | 
1722  | 0  |            "default='YES'/>"  | 
1723  | 0  |            "<Option name='ONLY_BEST' type='string-select' "  | 
1724  | 0  |            "description='"  | 
1725  | 0  |            "By default (at least in the PROJ 9.x series), PROJ may use "  | 
1726  | 0  |            "coordinate operations that are not the \"best\" if resources "  | 
1727  | 0  |            "(typically grids) needed to use them are missing. It will then "  | 
1728  | 0  |            "fallback to other coordinate operations that have a lesser "  | 
1729  | 0  |            "accuracy, for example using Helmert transformations, or in the "  | 
1730  | 0  |            "absence of such operations, to ones with potential very rough "  | 
1731  | 0  |            " accuracy, using \"ballpark\" transformations (see "  | 
1732  | 0  |            "https://proj.org/glossary.html). "  | 
1733  | 0  |            "When calling this method with YES, PROJ will only consider the "  | 
1734  | 0  |            "\"best\" operation, and error out (at Transform() time) if they "  | 
1735  | 0  |            "cannot be used. This method may be used together with "  | 
1736  | 0  |            "ALLOW_BALLPARK=NO to only allow best operations that have a known "  | 
1737  | 0  |            "accuracy. Note that this method has no effect on PROJ versions "  | 
1738  | 0  |            "before 9.2. The default value for this option can be also set with "  | 
1739  | 0  |            "the PROJ_ONLY_BEST_DEFAULT environment variable, or with the "  | 
1740  | 0  |            "\"only_best_default\" setting of proj.ini. Setting "  | 
1741  | 0  |            "ONLY_BEST=YES/NO overrides such default value' default='AUTO'>"  | 
1742  | 0  |            "  <Value>AUTO</Value>"  | 
1743  | 0  |            "  <Value>YES</Value>"  | 
1744  | 0  |            "  <Value>NO</Value>"  | 
1745  | 0  |            "</Option>"  | 
1746  | 0  |            "<Option name='COORDINATE_EPOCH' type='float' description='"  | 
1747  | 0  |            "Coordinate epoch, expressed as a decimal year. Useful for "  | 
1748  | 0  |            "time-dependent coordinate operations.'/>"  | 
1749  | 0  |            "<Option name='SRC_COORDINATE_EPOCH' type='float' description='"  | 
1750  | 0  |            "Coordinate epoch of source CRS, expressed as a decimal year. "  | 
1751  | 0  |            "Useful for time-dependent coordinate operations.'/>"  | 
1752  | 0  |            "<Option name='DST_COORDINATE_EPOCH' type='float' description='"  | 
1753  | 0  |            "Coordinate epoch of target CRS, expressed as a decimal year. "  | 
1754  | 0  |            "Useful for time-dependent coordinate operations.'/>"  | 
1755  | 0  |            "<Option name='GCPS_OK' type='boolean' description='"  | 
1756  | 0  |            "Allow use of GCPs.' default='YES'/>"  | 
1757  | 0  |            "<Option name='REFINE_MINIMUM_GCPS' type='int' description='"  | 
1758  | 0  |            "The minimum amount of GCPs that should be available after the "  | 
1759  | 0  |            "refinement'/>"  | 
1760  | 0  |            "<Option name='REFINE_TOLERANCE' type='float' description='"  | 
1761  | 0  |            "The tolerance that specifies when a GCP will be eliminated.'/>"  | 
1762  | 0  |            "<Option name='MAX_GCP_ORDER' type='int' description='"  | 
1763  | 0  |            "The maximum order to use for GCP derived polynomials if possible. "  | 
1764  | 0  |            "The default is to autoselect based on the number of GCPs. A value "  | 
1765  | 0  |            "of -1 triggers use of Thin Plate Spline instead of polynomials.'/>"  | 
1766  | 0  |            "<Option name='GCP_ANTIMERIDIAN_UNWRAP' type='string-select' "  | 
1767  | 0  |            "description='"  | 
1768  | 0  |            "Whether to \"unwrap\" longitudes of ground control points that "  | 
1769  | 0  |            "span the antimeridian. For datasets with GCPs in "  | 
1770  | 0  |            "longitude/latitude coordinate space spanning the antimeridian, "  | 
1771  | 0  |            "longitudes will have a discontinuity on +/- 180 deg, and will "  | 
1772  | 0  |            "result in a subset of the GCPs with longitude in the [-180,-170] "  | 
1773  | 0  |            "range and another subset in [170, 180]. By default (AUTO), that "  | 
1774  | 0  |            "situation will be detected and longitudes in [-180,-170] will be "  | 
1775  | 0  |            "shifted to [180, 190] to get a continuous set. This option can be "  | 
1776  | 0  |            "set to YES to force that behavior (useful if no SRS information is "  | 
1777  | 0  |            "available), or to NO to disable it.' default='AUTO'>"  | 
1778  | 0  |            "  <Value>AUTO</Value>"  | 
1779  | 0  |            "  <Value>YES</Value>"  | 
1780  | 0  |            "  <Value>NO</Value>"  | 
1781  | 0  |            "</Option>"  | 
1782  | 0  |            "<Option name='SRC_METHOD' alias='METHOD' type='string-select' "  | 
1783  | 0  |            "description='"  | 
1784  | 0  |            "Force only one geolocation method to be considered on the source "  | 
1785  | 0  |            "dataset. Will be used for pixel/line to georef transformation on "  | 
1786  | 0  |            "the source dataset. NO_GEOTRANSFORM can be used to specify the "  | 
1787  | 0  |            "identity geotransform (ungeoreferenced image)'>"  | 
1788  | 0  |            "  <Value>GEOTRANSFORM</Value>"  | 
1789  | 0  |            "  <Value>GCP_POLYNOMIAL</Value>"  | 
1790  | 0  |            "  <Value>GCP_TPS</Value>"  | 
1791  | 0  |            "  <Value>GCP_HOMOGRAPHY</Value>"  | 
1792  | 0  |            "  <Value>GEOLOC_ARRAY</Value>"  | 
1793  | 0  |            "  <Value>RPC</Value>"  | 
1794  | 0  |            "  <Value>NO_GEOTRANSFORM</Value>"  | 
1795  | 0  |            "</Option>"  | 
1796  | 0  |            "<Option name='DST_METHOD' type='string-select' description='"  | 
1797  | 0  |            "Force only one geolocation method to be considered on the target "  | 
1798  | 0  |            "dataset. Will be used for pixel/line to georef transformation on "  | 
1799  | 0  |            "the targe dataset. NO_GEOTRANSFORM can be used to specify the "  | 
1800  | 0  |            "identity geotransform (ungeoreferenced image)'>"  | 
1801  | 0  |            "  <Value>GEOTRANSFORM</Value>"  | 
1802  | 0  |            "  <Value>GCP_POLYNOMIAL</Value>"  | 
1803  | 0  |            "  <Value>GCP_TPS</Value>"  | 
1804  | 0  |            "  <Value>GCP_HOMOGRAPHY</Value>"  | 
1805  | 0  |            "  <Value>GEOLOC_ARRAY</Value>"  | 
1806  | 0  |            "  <Value>RPC</Value>"  | 
1807  | 0  |            "  <Value>NO_GEOTRANSFORM</Value>"  | 
1808  | 0  |            "</Option>"  | 
1809  | 0  |            "<Option name='RPC_HEIGHT' type='float' description='"  | 
1810  | 0  |            "A fixed height to be used with RPC calculations. If RPC_HEIGHT and "  | 
1811  | 0  |            "RPC_DEM are not specified but that the RPC metadata domain contains"  | 
1812  | 0  |            " a HEIGHT_DEFAULT item (for example, the DIMAP driver may fill it),"  | 
1813  | 0  |            "this value will be used as the RPC_HEIGHT. Otherwise, if none of "  | 
1814  | 0  |            "RPC_HEIGHT and RPC_DEM are specified as transformer options and "  | 
1815  | 0  |            "if HEIGHT_DEFAULT is no available, a height of 0 will be used.'/>"  | 
1816  | 0  |            "<Option name='RPC_DEM' type='string' description='"  | 
1817  | 0  |            "Name of a GDAL dataset (a DEM file typically) used to extract "  | 
1818  | 0  |            "elevation offsets from. In this situation the Z passed into the "  | 
1819  | 0  |            "transformation function is assumed to be height above ground. "  | 
1820  | 0  |            "This option should be used in replacement of RPC_HEIGHT to provide "  | 
1821  | 0  |            "a way of defining a non uniform ground for the target scene.'/>"  | 
1822  | 0  |            "<Option name='RPC_HEIGHT_SCALE' type='float' description='"  | 
1823  | 0  |            "Factor used to multiply heights above ground. Useful when "  | 
1824  | 0  |            "elevation offsets of the DEM are not expressed in meters.'/>"  | 
1825  | 0  |            "<Option name='RPC_DEMINTERPOLATION' type='string-select' "  | 
1826  | 0  |            "description='DEM interpolation method' default='BILINEAR'>"  | 
1827  | 0  |            "  <Value>NEAR</Value>"  | 
1828  | 0  |            "  <Value>BILINEAR</Value>"  | 
1829  | 0  |            "  <Value>CUBIC</Value>"  | 
1830  | 0  |            "</Option>"  | 
1831  | 0  |            "<Option name='RPC_DEM_MISSING_VALUE' type='float' description='"  | 
1832  | 0  |            "Value of DEM height that must be used in case the DEM has nodata "  | 
1833  | 0  |            "value at the sampling point, or if its extent does not cover the "  | 
1834  | 0  |            "requested coordinate. When not specified, missing values will "  | 
1835  | 0  |            "cause a failed transform.'/>"  | 
1836  | 0  |            "<Option name='RPC_DEM_SRS' type='string' description='"  | 
1837  | 0  |            "WKT SRS, or any string recognized by "  | 
1838  | 0  |            "OGRSpatialReference::SetFromUserInput(), to be used as an "  | 
1839  | 0  |            "override for DEM SRS. Useful if DEM SRS does not have an explicit "  | 
1840  | 0  |            "vertical component.'/>"  | 
1841  | 0  |            "<Option name='RPC_DEM_APPLY_VDATUM_SHIFT' type='boolean' "  | 
1842  | 0  |            "description='"  | 
1843  | 0  |            "Whether the vertical component of a compound SRS for the DEM "  | 
1844  | 0  |            "should be used (when it is present). This is useful so as to "  | 
1845  | 0  |            "be able to transform the raw values from the DEM expressed with "  | 
1846  | 0  |            "respect to a geoid to the heights with respect to the WGS84 "  | 
1847  | 0  |            "ellipsoid. When this is enabled, the GTIFF_REPORT_COMPD_CS "  | 
1848  | 0  |            "configuration option will be also set temporarily so as to get "  | 
1849  | 0  |            "the vertical information from GeoTIFF files.' default='YES'/>"  | 
1850  | 0  |            "<Option name='RPC_PIXEL_ERROR_THRESHOLD' type='float' description='"  | 
1851  | 0  |            "Overrides the dfPixErrThreshold parameter, i.e. the error "  | 
1852  | 0  |            "(measured in pixels) allowed in the iterative solution of "  | 
1853  | 0  |            "pixel/line to lat/long computations (the other way is always "  | 
1854  | 0  |            "exact given the equations).'/>"  | 
1855  | 0  |            "<Option name='RPC_MAX_ITERATIONS' type='int' description='"  | 
1856  | 0  |            "Maximum number of iterations allowed in the iterative solution of "  | 
1857  | 0  |            "pixel/line to lat/long computations. Default value is 10 in the "  | 
1858  | 0  |            "absence of a DEM, or 20 if there is a DEM.'/>"  | 
1859  | 0  |            "<Option name='RPC_FOOTPRINT' type='string' description='"  | 
1860  | 0  |            "WKT or GeoJSON polygon (in long / lat coordinate space) with a "  | 
1861  | 0  |            "validity footprint for the RPC. Any coordinate transformation that "  | 
1862  | 0  |            "goes from or arrive outside this footprint will be considered "  | 
1863  | 0  |            "invalid. This* is useful in situations where the RPC values become "  | 
1864  | 0  |            "highly unstable outside of the area on which they have been "  | 
1865  | 0  |            "computed for, potentially leading to undesirable \"echoes\" / "  | 
1866  | 0  |            "false positives. This requires GDAL to be built against GEOS..'/>"  | 
1867  | 0  |            "<Option name='RPC_MAX_ITERATIONS' type='int' description='"  | 
1868  | 0  |            "Maximum number of iterations allowed in the iterative solution of "  | 
1869  | 0  |            "pixel/line to lat/long computations. Default value is 10 in the "  | 
1870  | 0  |            "absence of a DEM, or 20 if there is a DEM.'/>"  | 
1871  | 0  |            "<Option name='INSERT_CENTER_LONG' type='boolean' description='"  | 
1872  | 0  |            "May be set to FALSE to disable setting up a CENTER_LONG value on "  | 
1873  | 0  |            "the coordinate system to rewrap things around the center of the "  | 
1874  | 0  |            "image.' default='YES'/>"  | 
1875  | 0  |            "<Option name='SRC_APPROX_ERROR_IN_SRS_UNIT' type='float' "  | 
1876  | 0  |            "description='"  | 
1877  | 0  |            "Use an approximate transformer for the source transformer. Must be "  | 
1878  | 0  |            "defined together with SRC_APPROX_ERROR_IN_PIXEL to be taken into "  | 
1879  | 0  |            "account.'/>"  | 
1880  | 0  |            "<Option name='SRC_APPROX_ERROR_IN_PIXEL' type='float' "  | 
1881  | 0  |            "description='"  | 
1882  | 0  |            "Use an approximate transformer for the source transformer. Must be "  | 
1883  | 0  |            "defined together with SRC_APPROX_ERROR_IN_SRS_UNIT to be taken "  | 
1884  | 0  |            "into "  | 
1885  | 0  |            "account.'/>"  | 
1886  | 0  |            "<Option name='DST_APPROX_ERROR_IN_SRS_UNIT' type='float' "  | 
1887  | 0  |            "description='"  | 
1888  | 0  |            "Use an approximate transformer for the target transformer. Must be "  | 
1889  | 0  |            "defined together with DST_APPROX_ERROR_IN_PIXEL to be taken into "  | 
1890  | 0  |            "account.'/>"  | 
1891  | 0  |            "<Option name='DST_APPROX_ERROR_IN_PIXEL' type='float' "  | 
1892  | 0  |            "description='"  | 
1893  | 0  |            "Use an approximate transformer for the target transformer. Must be "  | 
1894  | 0  |            "defined together with DST_APPROX_ERROR_IN_SRS_UNIT to be taken "  | 
1895  | 0  |            "into "  | 
1896  | 0  |            "account.'/>"  | 
1897  | 0  |            "<Option name='REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT' "  | 
1898  | 0  |            "type='float' "  | 
1899  | 0  |            "description='"  | 
1900  | 0  |            "Use an approximate transformer for the coordinate reprojection. "  | 
1901  | 0  |            "Must be used together with "  | 
1902  | 0  |            "REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT to be taken into "  | 
1903  | 0  |            "account.'/>"  | 
1904  | 0  |            "<Option name='REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT' "  | 
1905  | 0  |            "type='float' "  | 
1906  | 0  |            "description='"  | 
1907  | 0  |            "Use an approximate transformer for the coordinate reprojection. "  | 
1908  | 0  |            "Must be used together with "  | 
1909  | 0  |            "REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT to be taken into "  | 
1910  | 0  |            "account.'/>"  | 
1911  | 0  |            "<Option name='AREA_OF_INTEREST' type='string' "  | 
1912  | 0  |            "description='"  | 
1913  | 0  |            "Area of interest, as "  | 
1914  | 0  |            "west_lon_deg,south_lat_deg,east_lon_deg,north_lat_deg, used to "  | 
1915  | 0  |            "compute the best coordinate operation between the source and "  | 
1916  | 0  |            "target SRS. If not specified, the bounding box of the source "  | 
1917  | 0  |            "raster will be used.'/>"  | 
1918  | 0  |            "<Option name='GEOLOC_BACKMAP_OVERSAMPLE_FACTOR' type='float' "  | 
1919  | 0  |            "min='0.1' max='2' description='"  | 
1920  | 0  |            "Oversample factor used to derive the size of the \"backmap\" used "  | 
1921  | 0  |            "for geolocation array transformers.' default='1.3'/>"  | 
1922  | 0  |            "<Option name='GEOLOC_USE_TEMP_DATASETS' type='boolean' "  | 
1923  | 0  |            "description='"  | 
1924  | 0  |            "Whether temporary GeoTIFF datasets should be used to store the "  | 
1925  | 0  |            "backmap. The default is NO, that is to use in-memory arrays, "  | 
1926  | 0  |            "unless the number of pixels of the geolocation array is greater "  | 
1927  | 0  |            "than 16 megapixels.' default='NO'/>"  | 
1928  | 0  |            "<Option name='GEOLOC_ARRAY' alias='SRC_GEOLOC_ARRAY' type='string' "  | 
1929  | 0  |            "description='"  | 
1930  | 0  |            "Name of a GDAL dataset containing a geolocation array and "  | 
1931  | 0  |            "associated metadata. This is an alternative to having geolocation "  | 
1932  | 0  |            "information described in the GEOLOCATION metadata domain of the "  | 
1933  | 0  |            "source dataset. The dataset specified may have a GEOLOCATION "  | 
1934  | 0  |            "metadata domain containing appropriate metadata, however default "  | 
1935  | 0  |            "values are assigned for all omitted items. X_BAND defaults to 1 "  | 
1936  | 0  |            "and Y_BAND to 2, however the dataset must contain exactly 2 bands. "  | 
1937  | 0  |            "PIXEL_OFFSET and LINE_OFFSET default to 0. PIXEL_STEP and "  | 
1938  | 0  |            "LINE_STEP default to the ratio of the width/height of the source "  | 
1939  | 0  |            "dataset divided by the with/height of the geolocation array. "  | 
1940  | 0  |            "SRS defaults to the spatial reference system of the geolocation "  | 
1941  | 0  |            "array dataset, if set, otherwise WGS84 is used. "  | 
1942  | 0  |            "GEOREFERENCING_CONVENTION is selected from the main metadata "  | 
1943  | 0  |            "domain if it is omitted from the GEOLOCATION domain, and if not "  | 
1944  | 0  |            "available TOP_LEFT_CORNER is assigned as a default. "  | 
1945  | 0  |            "If GEOLOC_ARRAY is set SRC_METHOD defaults to GEOLOC_ARRAY.'/>"  | 
1946  | 0  |            "<Option name='DST_GEOLOC_ARRAY' type='string' "  | 
1947  | 0  |            "description='"  | 
1948  | 0  |            "Name of a GDAL dataset that contains at least 2 bands with the X "  | 
1949  | 0  |            "and Y geolocation bands. This is an alternative to having "  | 
1950  | 0  |            "geolocation information described in the GEOLOCATION metadata "  | 
1951  | 0  |            "domain of the destination dataset. See SRC_GEOLOC_ARRAY "  | 
1952  | 0  |            "description for details, assumptions, and defaults. If this "  | 
1953  | 0  |            "option is set, DST_METHOD=GEOLOC_ARRAY will be assumed if not "  | 
1954  | 0  |            "set.'/>"  | 
1955  | 0  |            "<Option name='NUM_THREADS' type='string' "  | 
1956  | 0  |            "description='Number of threads to use'/>"  | 
1957  | 0  |            "</OptionList>";  | 
1958  | 0  | }  | 
1959  |  |  | 
1960  |  | /************************************************************************/  | 
1961  |  | /*                  GDALCreateGenImgProjTransformer2()                  */  | 
1962  |  | /************************************************************************/  | 
1963  |  |  | 
1964  |  | /* clang-format off */  | 
1965  |  | /**  | 
1966  |  |  * Create image to image transformer.  | 
1967  |  |  *  | 
1968  |  |  * This function creates a transformation object that maps from pixel/line  | 
1969  |  |  * coordinates on one image to pixel/line coordinates on another image.  The  | 
1970  |  |  * images may potentially be georeferenced in different coordinate systems,  | 
1971  |  |  * and may used GCPs to map between their pixel/line coordinates and  | 
1972  |  |  * georeferenced coordinates (as opposed to the default assumption that their  | 
1973  |  |  * geotransform should be used).  | 
1974  |  |  *  | 
1975  |  |  * This transformer potentially performs three concatenated transformations.  | 
1976  |  |  *  | 
1977  |  |  * The first stage is from source image pixel/line coordinates to source  | 
1978  |  |  * image georeferenced coordinates, and may be done using the geotransform,  | 
1979  |  |  * or if not defined using a polynomial model derived from GCPs.  If GCPs  | 
1980  |  |  * are used this stage is accomplished using GDALGCPTransform().  | 
1981  |  |  *  | 
1982  |  |  * The second stage is to change projections from the source coordinate system  | 
1983  |  |  * to the destination coordinate system, assuming they differ.  This is  | 
1984  |  |  * accomplished internally using GDALReprojectionTransform().  | 
1985  |  |  *  | 
1986  |  |  * The third stage is converting from destination image georeferenced  | 
1987  |  |  * coordinates to destination image coordinates.  This is done using the  | 
1988  |  |  * destination image geotransform, or if not available, using a polynomial  | 
1989  |  |  * model derived from GCPs. If GCPs are used this stage is accomplished using  | 
1990  |  |  * GDALGCPTransform().  This stage is skipped if hDstDS is NULL when the  | 
1991  |  |  * transformation is created.  | 
1992  |  |  *  | 
1993  |  |  * Supported Options (specified with the -to switch of gdalwarp for example):  | 
1994  |  |  * <ul>  | 
1995  |  |  * <li> SRC_SRS: WKT SRS, or any string recognized by  | 
1996  |  |  * OGRSpatialReference::SetFromUserInput(), to be used as an override for  | 
1997  |  |  * hSrcDS.</li>  | 
1998  |  |  * <li> DST_SRS: WKT SRS, or any string recognized by  | 
1999  |  |  * OGRSpatialReference::SetFromUserInput(),  to be used as an override for  | 
2000  |  |  * hDstDS.  | 
2001  |  |  * </li>  | 
2002  |  |  * <li>PROMOTE_TO_3D=YES/NO: whether to promote SRC_SRS / DST_SRS to 3D.  | 
2003  |  |  * Default is NO</li>  | 
2004  |  |  * <li> COORDINATE_OPERATION: (GDAL >= 3.0) Coordinate operation, as  | 
2005  |  |  * a PROJ or WKT string, used as an override over the normally computed  | 
2006  |  |  * pipeline. The pipeline must take into account the axis order of the source  | 
2007  |  |  * and target SRS.  | 
2008  |  |  * </li>  | 
2009  |  |  * <li> ALLOW_BALLPARK=YES/NO: (GDAL >= 3.11) Whether ballpark coordinate  | 
2010  |  |  * operations are allowed. Defaults to YES.</li>  | 
2011  |  |  * <li> ONLY_BEST=YES/NO/AUTO: (GDAL >= 3.11) By default (at least in the  | 
2012  |  |  * PROJ 9.x series), PROJ may use coordinate  | 
2013  |  |  * operations that are not the "best" if resources (typically grids) needed  | 
2014  |  |  * to use them are missing. It will then fallback to other coordinate operations  | 
2015  |  |  * that have a lesser accuracy, for example using Helmert transformations,  | 
2016  |  |  * or in the absence of such operations, to ones with potential very rought  | 
2017  |  |  * accuracy, using "ballpark" transformations  | 
2018  |  |  * (see https://proj.org/glossary.html).  | 
2019  |  |  * When calling this method with YES, PROJ will only consider the  | 
2020  |  |  * "best" operation, and error out (at Transform() time) if they cannot be  | 
2021  |  |  * used.  | 
2022  |  |  * This method may be used together with ALLOW_BALLPARK=NO to  | 
2023  |  |  * only allow best operations that have a known accuracy.  | 
2024  |  |  * Note that this method has no effect on PROJ versions before 9.2.  | 
2025  |  |  * The default value for this option can be also set with the  | 
2026  |  |  * PROJ_ONLY_BEST_DEFAULT environment variable, or with the "only_best_default"  | 
2027  |  |  * setting of proj.ini. Calling SetOnlyBest() overrides such default value.</li>  | 
2028  |  |  * <li> COORDINATE_EPOCH: (GDAL >= 3.0) Coordinate epoch,  | 
2029  |  |  * expressed as a decimal year. Useful for time-dependent coordinate operations.  | 
2030  |  |  * </li>  | 
2031  |  |  * <li> SRC_COORDINATE_EPOCH: (GDAL >= 3.4) Coordinate epoch of source CRS,  | 
2032  |  |  * expressed as a decimal year. Useful for time-dependent coordinate operations.  | 
2033  |  |  * </li>  | 
2034  |  |  * <li> DST_COORDINATE_EPOCH: (GDAL >= 3.4) Coordinate epoch of target CRS,  | 
2035  |  |  * expressed as a decimal year. Useful for time-dependent coordinate operations.  | 
2036  |  |  * </li>  | 
2037  |  |  * <li> GCPS_OK: If false, GCPs will not be used, default is TRUE.  | 
2038  |  |  * </li>  | 
2039  |  |  * <li> REFINE_MINIMUM_GCPS: The minimum amount of GCPs that should be available  | 
2040  |  |  * after the refinement.  | 
2041  |  |  * </li>  | 
2042  |  |  * <li> REFINE_TOLERANCE: The tolerance that specifies when a GCP will be  | 
2043  |  |  * eliminated.  | 
2044  |  |  * </li>  | 
2045  |  |  * <li> MAX_GCP_ORDER: the maximum order to use for GCP derived polynomials if  | 
2046  |  |  * possible.  The default is to autoselect based on the number of GCPs.  | 
2047  |  |  * A value of -1 triggers use of Thin Plate Spline instead of polynomials.  | 
2048  |  |  * </li>  | 
2049  |  |  * <li>GCP_ANTIMERIDIAN_UNWRAP=AUTO/YES/NO. (GDAL >= 3.8) Whether to  | 
2050  |  |  * "unwrap" longitudes of ground control points that span the antimeridian.  | 
2051  |  |  * For datasets with GCPs in longitude/latitude coordinate space spanning the  | 
2052  |  |  * antimeridian, longitudes will have a discontinuity on +/- 180 deg, and  | 
2053  |  |  * will result in a subset of the GCPs with longitude in the [-180,-170] range  | 
2054  |  |  * and another subset in [170, 180]. By default (AUTO), that situation will be  | 
2055  |  |  * detected and longitudes in [-180,-170] will be shifted to [180, 190] to get  | 
2056  |  |  * a continuous set. This option can be set to YES to force that behavior  | 
2057  |  |  * (useful if no SRS information is available), or to NO to disable it.  | 
2058  |  |  * </li>  | 
2059  |  |  * <li> SRC_METHOD: may have a value which is one of GEOTRANSFORM, GCP_HOMOGRAPHY,  | 
2060  |  |  * GCP_POLYNOMIAL, GCP_TPS, GEOLOC_ARRAY, RPC to force only one geolocation  | 
2061  |  |  * method to be considered on the source dataset. Will be used for pixel/line  | 
2062  |  |  * to georef transformation on the source dataset. NO_GEOTRANSFORM can be  | 
2063  |  |  * used to specify the identity geotransform (ungeoreferenced image)  | 
2064  |  |  * </li>  | 
2065  |  |  * <li> DST_METHOD: may have a value which is one of GEOTRANSFORM,  | 
2066  |  |  * GCP_POLYNOMIAL, GCP_HOMOGRAPHY, GCP_TPS, GEOLOC_ARRAY (added in 3.5), RPC to  | 
2067  |  |  * force only one  | 
2068  |  |  * geolocation method to be considered on the target dataset.  Will be used for  | 
2069  |  |  * pixel/line to georef transformation on the destination dataset.  | 
2070  |  |  * NO_GEOTRANSFORM can be used to specify the identity geotransform  | 
2071  |  |  * (ungeoreferenced image)  | 
2072  |  |  * </li>  | 
2073  |  |  * <li> RPC_HEIGHT: A fixed height to be used with RPC  | 
2074  |  |  * calculations. If RPC_HEIGHT and RPC_DEM are not specified but that the RPC  | 
2075  |  |  * metadata domain contains a HEIGHT_DEFAULT item (for example, the DIMAP driver  | 
2076  |  |  * may fill it), this value will be used as the RPC_HEIGHT. Otherwise, if none  | 
2077  |  |  * of RPC_HEIGHT and RPC_DEM are specified as transformer  | 
2078  |  |  * options and if HEIGHT_DEFAULT is no available, a height of 0 will be used.  | 
2079  |  |  * </li>  | 
2080  |  |  * <li> RPC_DEM: The name of a DEM file to be used with RPC  | 
2081  |  |  * calculations. See GDALCreateRPCTransformerV2() for more details.  | 
2082  |  |  * </li>  | 
2083  |  |  * <li> Other RPC related options. See GDALCreateRPCTransformerV2()  | 
2084  |  |  * </li>  | 
2085  |  |  * <li>  | 
2086  |  |  * INSERT_CENTER_LONG: May be set to FALSE to disable setting up a CENTER_LONG  | 
2087  |  |  * value on the coordinate system to rewrap things around the center of the  | 
2088  |  |  * image.  | 
2089  |  |  * </li>  | 
2090  |  |  * <li> SRC_APPROX_ERROR_IN_SRS_UNIT=err_threshold_in_SRS_units. (GDAL  | 
2091  |  |  * >= 2.2) Use an approximate transformer for the source transformer. Must be  | 
2092  |  |  * defined together with SRC_APPROX_ERROR_IN_PIXEL to be taken into account.  | 
2093  |  |  * </li>  | 
2094  |  |  * <li> SRC_APPROX_ERROR_IN_PIXEL=err_threshold_in_pixel. (GDAL >= 2.2) Use  | 
2095  |  |  * an approximate transformer for the source transformer.. Must be defined  | 
2096  |  |  * together with SRC_APPROX_ERROR_IN_SRS_UNIT to be taken into account.  | 
2097  |  |  * </li>  | 
2098  |  |  * <li>  | 
2099  |  |  * DST_APPROX_ERROR_IN_SRS_UNIT=err_threshold_in_SRS_units. (GDAL >= 2.2) Use  | 
2100  |  |  * an approximate transformer for the destination transformer. Must be defined  | 
2101  |  |  * together with DST_APPROX_ERROR_IN_PIXEL to be taken into account.  | 
2102  |  |  * </li>  | 
2103  |  |  * <li>  | 
2104  |  |  * DST_APPROX_ERROR_IN_PIXEL=err_threshold_in_pixel. (GDAL >= 2.2) Use an  | 
2105  |  |  * approximate transformer for the destination transformer. Must be defined  | 
2106  |  |  * together with DST_APPROX_ERROR_IN_SRS_UNIT to be taken into account.  | 
2107  |  |  * </li>  | 
2108  |  |  * <li>  | 
2109  |  |  * REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT=err_threshold_in_src_SRS_units.  | 
2110  |  |  * (GDAL >= 2.2) Use an approximate transformer for the coordinate  | 
2111  |  |  * reprojection. Must be used together with  | 
2112  |  |  * REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT to be taken into account.  | 
2113  |  |  * </li>  | 
2114  |  |  * <li>  | 
2115  |  |  * REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT=err_threshold_in_dst_SRS_units.  | 
2116  |  |  * (GDAL >= 2.2) Use an approximate transformer for the coordinate  | 
2117  |  |  * reprojection. Must be used together with  | 
2118  |  |  * REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT to be taken into account.  | 
2119  |  |  * </li>  | 
2120  |  |  * <li>  | 
2121  |  |  * AREA_OF_INTEREST=west_lon_deg,south_lat_deg,east_lon_deg,north_lat_deg. (GDAL  | 
2122  |  |  * >= 3.0) Area of interest, used to compute the best coordinate operation  | 
2123  |  |  * between the source and target SRS. If not specified, the bounding box of the  | 
2124  |  |  * source raster will be used.  | 
2125  |  |  * </li>  | 
2126  |  |  * <li> GEOLOC_BACKMAP_OVERSAMPLE_FACTOR=[0.1,2]. (GDAL >= 3.5) Oversample  | 
2127  |  |  * factor used to derive the size of the "backmap" used for geolocation array  | 
2128  |  |  * transformers. Default value is 1.3.  | 
2129  |  |  * </li>  | 
2130  |  |  * <li> GEOLOC_USE_TEMP_DATASETS=YES/NO.  | 
2131  |  |  * (GDAL >= 3.5) Whether temporary GeoTIFF datasets should be used to store  | 
2132  |  |  * the backmap. The default is NO, that is to use in-memory arrays, unless the  | 
2133  |  |  * number of pixels of the geolocation array is greater than 16 megapixels.  | 
2134  |  |  * </li>  | 
2135  |  |  * <li>  | 
2136  |  |  * GEOLOC_ARRAY/SRC_GEOLOC_ARRAY=filename. (GDAL >= 3.5.2) Name of a GDAL  | 
2137  |  |  * dataset containing a geolocation array and associated metadata. This is an  | 
2138  |  |  * alternative to having geolocation information described in the GEOLOCATION  | 
2139  |  |  * metadata domain of the source dataset. The dataset specified may have a  | 
2140  |  |  * GEOLOCATION metadata domain containing appropriate metadata, however default  | 
2141  |  |  * values are assigned for all omitted items. X_BAND defaults to 1 and Y_BAND to  | 
2142  |  |  * 2, however the dataset must contain exactly 2 bands. PIXEL_OFFSET and  | 
2143  |  |  * LINE_OFFSET default to 0. PIXEL_STEP and LINE_STEP default to the ratio of  | 
2144  |  |  * the width/height of the source dataset divided by the with/height of the  | 
2145  |  |  * geolocation array. SRS defaults to the geolocation array dataset's spatial  | 
2146  |  |  * reference system if set, otherwise WGS84 is used.  | 
2147  |  |  * GEOREFERENCING_CONVENTION is selected from the main metadata domain if it  | 
2148  |  |  * is omitted from the GEOLOCATION domain, and if not available  | 
2149  |  |  * TOP_LEFT_CORNER is assigned as a default.  | 
2150  |  |  * If GEOLOC_ARRAY is set SRC_METHOD  | 
2151  |  |  * defaults to GEOLOC_ARRAY.  | 
2152  |  |  * </li>  | 
2153  |  |  * <li>DST_GEOLOC_ARRAY=filename. (GDAL >= 3.5.2) Name of a  | 
2154  |  |  * GDAL dataset that contains at least 2 bands with the X and Y geolocation  | 
2155  |  |  * bands. This is an alternative to having geolocation information described in  | 
2156  |  |  * the GEOLOCATION metadata domain of the destination dataset. See  | 
2157  |  |  * SRC_GEOLOC_ARRAY description for details, assumptions, and defaults. If this  | 
2158  |  |  * option is set, DST_METHOD=GEOLOC_ARRAY will be assumed if not set.  | 
2159  |  |  * </li>  | 
2160  |  |  * </ul>  | 
2161  |  |  *  | 
2162  |  |  * The use case for the *_APPROX_ERROR_* options is when defining an approximate  | 
2163  |  |  * transformer on top of the GenImgProjTransformer globally is not practical.  | 
2164  |  |  * Such a use case is when the source dataset has RPC with a RPC DEM. In such  | 
2165  |  |  * case we don't want to use the approximate transformer on the RPC  | 
2166  |  |  * transformation, as the RPC DEM generally involves non-linearities that the  | 
2167  |  |  * approximate transformer will not detect. In such case, we must a  | 
2168  |  |  * non-approximated GenImgProjTransformer, but it might be worthwhile to use  | 
2169  |  |  * approximate sub- transformers, for example on coordinate reprojection. For  | 
2170  |  |  * example if warping from a source dataset with RPC to a destination dataset  | 
2171  |  |  * with a UTM projection, since the inverse UTM transformation is rather costly.  | 
2172  |  |  * In which case, one can use the REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT and  | 
2173  |  |  * REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT options.  | 
2174  |  |  *  | 
2175  |  |  * The list of supported options can also be programmatically obtained with  | 
2176  |  |  * GDALGetGenImgProjTranformerOptionList().  | 
2177  |  |  *  | 
2178  |  |  * @param hSrcDS source dataset, or NULL.  | 
2179  |  |  * @param hDstDS destination dataset (or NULL).  | 
2180  |  |  * @param papszOptions NULL-terminated list of string options (or NULL).  | 
2181  |  |  *  | 
2182  |  |  * @return handle suitable for use GDALGenImgProjTransform(), and to be  | 
2183  |  |  * deallocated with GDALDestroyGenImgProjTransformer() or NULL on failure.  | 
2184  |  |  */  | 
2185  |  | /* clang-format on */  | 
2186  |  |  | 
2187  |  | void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS,  | 
2188  |  |                                        CSLConstList papszOptions)  | 
2189  |  |  | 
2190  | 0  | { | 
2191  | 0  |     GDALValidateOptions(GDALGetGenImgProjTranformerOptionList(), papszOptions,  | 
2192  | 0  |                         "option", "transformer options");  | 
2193  |  | 
  | 
2194  | 0  |     double dfWestLongitudeDeg = 0.0;  | 
2195  | 0  |     double dfSouthLatitudeDeg = 0.0;  | 
2196  | 0  |     double dfEastLongitudeDeg = 0.0;  | 
2197  | 0  |     double dfNorthLatitudeDeg = 0.0;  | 
2198  | 0  |     bool bHasAreaOfInterest = false;  | 
2199  | 0  |     if (const char *pszAreaOfInterest =  | 
2200  | 0  |             CSLFetchNameValue(papszOptions, "AREA_OF_INTEREST"))  | 
2201  | 0  |     { | 
2202  | 0  |         const CPLStringList aosTokens(  | 
2203  | 0  |             CSLTokenizeString2(pszAreaOfInterest, ", ", 0));  | 
2204  | 0  |         if (aosTokens.size() == 4)  | 
2205  | 0  |         { | 
2206  | 0  |             dfWestLongitudeDeg = CPLAtof(aosTokens[0]);  | 
2207  | 0  |             dfSouthLatitudeDeg = CPLAtof(aosTokens[1]);  | 
2208  | 0  |             dfEastLongitudeDeg = CPLAtof(aosTokens[2]);  | 
2209  | 0  |             dfNorthLatitudeDeg = CPLAtof(aosTokens[3]);  | 
2210  | 0  |             bHasAreaOfInterest = true;  | 
2211  | 0  |         }  | 
2212  | 0  |     }  | 
2213  |  | 
  | 
2214  | 0  |     const char *pszCO = CSLFetchNameValue(papszOptions, "COORDINATE_OPERATION");  | 
2215  |  | 
  | 
2216  | 0  |     const auto SetAxisMapping =  | 
2217  | 0  |         [papszOptions](OGRSpatialReference &oSRS, const char *pszPrefix)  | 
2218  | 0  |     { | 
2219  | 0  |         const char *pszMapping = CSLFetchNameValue(  | 
2220  | 0  |             papszOptions, std::string(pszPrefix)  | 
2221  | 0  |                               .append("_DATA_AXIS_TO_SRS_AXIS_MAPPING") | 
2222  | 0  |                               .c_str());  | 
2223  | 0  |         if (pszMapping)  | 
2224  | 0  |         { | 
2225  | 0  |             CPLStringList aosTokens(CSLTokenizeString2(pszMapping, ",", 0));  | 
2226  | 0  |             std::vector<int> anMapping;  | 
2227  | 0  |             for (int i = 0; i < aosTokens.size(); ++i)  | 
2228  | 0  |                 anMapping.push_back(atoi(aosTokens[i]));  | 
2229  | 0  |             oSRS.SetDataAxisToSRSAxisMapping(anMapping);  | 
2230  | 0  |         }  | 
2231  | 0  |         else  | 
2232  | 0  |         { | 
2233  | 0  |             const char *pszStrategy = CSLFetchNameValueDef(  | 
2234  | 0  |                 papszOptions,  | 
2235  | 0  |                 std::string(pszPrefix).append("_AXIS_MAPPING_STRATEGY").c_str(), | 
2236  | 0  |                 "TRADITIONAL_GIS_ORDER");  | 
2237  | 0  |             if (EQUAL(pszStrategy, "TRADITIONAL_GIS_ORDER"))  | 
2238  | 0  |                 oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
2239  | 0  |             else if (EQUAL(pszStrategy, "AUTHORITY_COMPLIANT"))  | 
2240  | 0  |                 oSRS.SetAxisMappingStrategy(OAMS_AUTHORITY_COMPLIANT);  | 
2241  | 0  |             else  | 
2242  | 0  |             { | 
2243  | 0  |                 CPLError(CE_Warning, CPLE_AppDefined,  | 
2244  | 0  |                          "Unrecognized value '%s' for %s", pszStrategy,  | 
2245  | 0  |                          std::string(pszPrefix)  | 
2246  | 0  |                              .append("_AXIS_MAPPING_STRATEGY") | 
2247  | 0  |                              .c_str());  | 
2248  | 0  |                 return false;  | 
2249  | 0  |             }  | 
2250  | 0  |         }  | 
2251  | 0  |         return true;  | 
2252  | 0  |     };  | 
2253  |  |  | 
2254  |  |     /* -------------------------------------------------------------------- */  | 
2255  |  |     /*      Initialize the transform info.                                  */  | 
2256  |  |     /* -------------------------------------------------------------------- */  | 
2257  | 0  |     GDALGenImgProjTransformInfo *psInfo =  | 
2258  | 0  |         GDALCreateGenImgProjTransformerInternal();  | 
2259  |  | 
  | 
2260  | 0  |     const auto DealWithForwardOrInverse =  | 
2261  | 0  |         [bHasAreaOfInterest, &dfWestLongitudeDeg, &dfSouthLatitudeDeg,  | 
2262  | 0  |          &dfEastLongitudeDeg, &dfNorthLatitudeDeg, pszCO, papszOptions,  | 
2263  | 0  |          &SetAxisMapping](GDALGenImgProjTransformPart &part, GDALDatasetH hDS,  | 
2264  | 0  |                           const char *pszPrefix, OGRSpatialReference &oSRS,  | 
2265  | 0  |                           bool &bCanUseGeoTransform)  | 
2266  | 0  |     { | 
2267  | 0  |         const int nOrder =  | 
2268  | 0  |             atoi(CSLFetchNameValueDef(papszOptions, "MAX_GCP_ORDER", "0"));  | 
2269  |  | 
  | 
2270  | 0  |         const bool bGCPUseOK =  | 
2271  | 0  |             CPLTestBool(CSLFetchNameValueDef(papszOptions, "GCPS_OK", "YES"));  | 
2272  | 0  |         const int nMinimumGcps = atoi(  | 
2273  | 0  |             CSLFetchNameValueDef(papszOptions, "REFINE_MINIMUM_GCPS", "-1"));  | 
2274  |  | 
  | 
2275  | 0  |         const char *pszRefineTolerance =  | 
2276  | 0  |             CSLFetchNameValue(papszOptions, "REFINE_TOLERANCE");  | 
2277  | 0  |         const bool bRefine = pszRefineTolerance != nullptr;  | 
2278  | 0  |         const double dfTolerance =  | 
2279  | 0  |             pszRefineTolerance ? CPLAtof(pszRefineTolerance) : 0.0;  | 
2280  |  | 
  | 
2281  | 0  |         const std::string osSRSOptionName =  | 
2282  | 0  |             std::string(pszPrefix).append("_SRS"); | 
2283  | 0  |         const char *pszSRS =  | 
2284  | 0  |             CSLFetchNameValue(papszOptions, osSRSOptionName.c_str());  | 
2285  | 0  |         if (pszSRS)  | 
2286  | 0  |         { | 
2287  | 0  |             if (pszSRS[0] != '\0' &&  | 
2288  | 0  |                 oSRS.SetFromUserInput(pszSRS) != OGRERR_NONE)  | 
2289  | 0  |             { | 
2290  | 0  |                 CPLError(CE_Failure, CPLE_AppDefined,  | 
2291  | 0  |                          "Failed to import coordinate system `%s'.", pszSRS);  | 
2292  | 0  |                 return false;  | 
2293  | 0  |             }  | 
2294  | 0  |             if (!SetAxisMapping(oSRS, osSRSOptionName.c_str()))  | 
2295  | 0  |                 return false;  | 
2296  | 0  |         }  | 
2297  |  |  | 
2298  | 0  |         CSLConstList papszMD = nullptr;  | 
2299  | 0  |         GDALRPCInfoV2 sRPCInfo;  | 
2300  |  | 
  | 
2301  | 0  |         bCanUseGeoTransform = false;  | 
2302  |  | 
  | 
2303  | 0  |         const char *pszMethod = CSLFetchNameValue(  | 
2304  | 0  |             papszOptions, std::string(pszPrefix).append("_METHOD").c_str()); | 
2305  | 0  |         if (!pszMethod && EQUAL(pszPrefix, "SRC"))  | 
2306  | 0  |             pszMethod = CSLFetchNameValue(papszOptions, "METHOD");  | 
2307  |  | 
  | 
2308  | 0  |         const char *pszGeolocArray = CSLFetchNameValue(  | 
2309  | 0  |             papszOptions,  | 
2310  | 0  |             std::string(pszPrefix).append("_GEOLOC_ARRAY").c_str()); | 
2311  | 0  |         if (!pszGeolocArray && EQUAL(pszPrefix, "SRC"))  | 
2312  | 0  |             pszGeolocArray = CSLFetchNameValue(papszOptions, "GEOLOC_ARRAY");  | 
2313  | 0  |         if (!pszMethod && pszGeolocArray != nullptr)  | 
2314  | 0  |             pszMethod = "GEOLOC_ARRAY";  | 
2315  |  |  | 
2316  |  |         /* -------------------------------------------------------------------- */  | 
2317  |  |         /*      Get forward and inverse geotransform for the source image.      */  | 
2318  |  |         /* -------------------------------------------------------------------- */  | 
2319  | 0  |         if (hDS == nullptr ||  | 
2320  | 0  |             (pszMethod != nullptr && EQUAL(pszMethod, "NO_GEOTRANSFORM")))  | 
2321  | 0  |         { | 
2322  | 0  |             part.adfGeoTransform[0] = 0.0;  | 
2323  | 0  |             part.adfGeoTransform[1] = 1.0;  | 
2324  | 0  |             part.adfGeoTransform[2] = 0.0;  | 
2325  | 0  |             part.adfGeoTransform[3] = 0.0;  | 
2326  | 0  |             part.adfGeoTransform[4] = 0.0;  | 
2327  | 0  |             part.adfGeoTransform[5] = 1.0;  | 
2328  | 0  |             memcpy(part.adfInvGeoTransform, part.adfGeoTransform,  | 
2329  | 0  |                    sizeof(double) * 6);  | 
2330  | 0  |         }  | 
2331  | 0  |         else if ((pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")) &&  | 
2332  | 0  |                  GDALGetGeoTransform(hDS, part.adfGeoTransform) == CE_None)  | 
2333  | 0  |         { | 
2334  | 0  |             if (!GDALInvGeoTransform(part.adfGeoTransform,  | 
2335  | 0  |                                      part.adfInvGeoTransform))  | 
2336  | 0  |             { | 
2337  | 0  |                 CPLError(CE_Failure, CPLE_AppDefined,  | 
2338  | 0  |                          "Cannot invert geotransform");  | 
2339  | 0  |                 return false;  | 
2340  | 0  |             }  | 
2341  | 0  |             if (pszSRS == nullptr)  | 
2342  | 0  |             { | 
2343  | 0  |                 auto hSRS = GDALGetSpatialRef(hDS);  | 
2344  | 0  |                 if (hSRS)  | 
2345  | 0  |                     oSRS = *(OGRSpatialReference::FromHandle(hSRS));  | 
2346  | 0  |             }  | 
2347  | 0  |             if (EQUAL(pszPrefix, "SRC"))  | 
2348  | 0  |             { | 
2349  | 0  |                 if (!bHasAreaOfInterest && pszCO == nullptr && !oSRS.IsEmpty())  | 
2350  | 0  |                 { | 
2351  | 0  |                     GDALComputeAreaOfInterest(  | 
2352  | 0  |                         &oSRS, part.adfGeoTransform, GDALGetRasterXSize(hDS),  | 
2353  | 0  |                         GDALGetRasterYSize(hDS), dfWestLongitudeDeg,  | 
2354  | 0  |                         dfSouthLatitudeDeg, dfEastLongitudeDeg,  | 
2355  | 0  |                         dfNorthLatitudeDeg);  | 
2356  | 0  |                 }  | 
2357  | 0  |                 bCanUseGeoTransform = true;  | 
2358  | 0  |             }  | 
2359  | 0  |         }  | 
2360  | 0  |         else if (bGCPUseOK &&  | 
2361  | 0  |                  ((pszMethod == nullptr && GDALGetGCPCount(hDS) >= 4 &&  | 
2362  | 0  |                    GDALGetGCPCount(hDS) < 6) ||  | 
2363  | 0  |                   (pszMethod != nullptr &&  | 
2364  | 0  |                    EQUAL(pszMethod, "GCP_HOMOGRAPHY"))) &&  | 
2365  | 0  |                  GDALGetGCPCount(hDS) > 0)  | 
2366  | 0  |         { | 
2367  | 0  |             if (pszSRS == nullptr)  | 
2368  | 0  |             { | 
2369  | 0  |                 auto hSRS = GDALGetGCPSpatialRef(hDS);  | 
2370  | 0  |                 if (hSRS)  | 
2371  | 0  |                     oSRS = *(OGRSpatialReference::FromHandle(hSRS));  | 
2372  | 0  |             }  | 
2373  |  | 
  | 
2374  | 0  |             const auto nGCPCount = GDALGetGCPCount(hDS);  | 
2375  | 0  |             auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDS));  | 
2376  | 0  |             GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oSRS,  | 
2377  | 0  |                                       papszOptions);  | 
2378  |  | 
  | 
2379  | 0  |             part.pTransformArg =  | 
2380  | 0  |                 GDALCreateHomographyTransformerFromGCPs(nGCPCount, pasGCPList);  | 
2381  |  | 
  | 
2382  | 0  |             GDALDeinitGCPs(nGCPCount, pasGCPList);  | 
2383  | 0  |             CPLFree(pasGCPList);  | 
2384  |  | 
  | 
2385  | 0  |             if (part.pTransformArg == nullptr)  | 
2386  | 0  |             { | 
2387  | 0  |                 return false;  | 
2388  | 0  |             }  | 
2389  | 0  |             part.pTransformer = GDALHomographyTransform;  | 
2390  | 0  |         }  | 
2391  | 0  |         else if (bGCPUseOK &&  | 
2392  | 0  |                  (pszMethod == nullptr || EQUAL(pszMethod, "GCP_POLYNOMIAL")) &&  | 
2393  | 0  |                  GDALGetGCPCount(hDS) > 0 && nOrder >= 0)  | 
2394  | 0  |         { | 
2395  | 0  |             if (pszSRS == nullptr)  | 
2396  | 0  |             { | 
2397  | 0  |                 auto hSRS = GDALGetGCPSpatialRef(hDS);  | 
2398  | 0  |                 if (hSRS)  | 
2399  | 0  |                     oSRS = *(OGRSpatialReference::FromHandle(hSRS));  | 
2400  | 0  |             }  | 
2401  |  | 
  | 
2402  | 0  |             const auto nGCPCount = GDALGetGCPCount(hDS);  | 
2403  | 0  |             auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDS));  | 
2404  | 0  |             GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oSRS,  | 
2405  | 0  |                                       papszOptions);  | 
2406  |  | 
  | 
2407  | 0  |             if (bRefine)  | 
2408  | 0  |             { | 
2409  | 0  |                 part.pTransformArg = GDALCreateGCPRefineTransformer(  | 
2410  | 0  |                     nGCPCount, pasGCPList, nOrder, FALSE, dfTolerance,  | 
2411  | 0  |                     nMinimumGcps);  | 
2412  | 0  |             }  | 
2413  | 0  |             else  | 
2414  | 0  |             { | 
2415  | 0  |                 part.pTransformArg = GDALCreateGCPTransformer(  | 
2416  | 0  |                     nGCPCount, pasGCPList, nOrder, FALSE);  | 
2417  | 0  |             }  | 
2418  |  | 
  | 
2419  | 0  |             GDALDeinitGCPs(nGCPCount, pasGCPList);  | 
2420  | 0  |             CPLFree(pasGCPList);  | 
2421  |  | 
  | 
2422  | 0  |             if (part.pTransformArg == nullptr)  | 
2423  | 0  |             { | 
2424  | 0  |                 return false;  | 
2425  | 0  |             }  | 
2426  | 0  |             part.pTransformer = GDALGCPTransform;  | 
2427  | 0  |         }  | 
2428  |  |  | 
2429  | 0  |         else if (bGCPUseOK && GDALGetGCPCount(hDS) > 0 && nOrder <= 0 &&  | 
2430  | 0  |                  (pszMethod == nullptr || EQUAL(pszMethod, "GCP_TPS")))  | 
2431  | 0  |         { | 
2432  | 0  |             if (pszSRS == nullptr)  | 
2433  | 0  |             { | 
2434  | 0  |                 auto hSRS = GDALGetGCPSpatialRef(hDS);  | 
2435  | 0  |                 if (hSRS)  | 
2436  | 0  |                     oSRS = *(OGRSpatialReference::FromHandle(hSRS));  | 
2437  | 0  |             }  | 
2438  |  | 
  | 
2439  | 0  |             const auto nGCPCount = GDALGetGCPCount(hDS);  | 
2440  | 0  |             auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDS));  | 
2441  | 0  |             GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oSRS,  | 
2442  | 0  |                                       papszOptions);  | 
2443  |  | 
  | 
2444  | 0  |             part.pTransformArg = GDALCreateTPSTransformerInt(  | 
2445  | 0  |                 nGCPCount, pasGCPList, FALSE, papszOptions);  | 
2446  |  | 
  | 
2447  | 0  |             GDALDeinitGCPs(nGCPCount, pasGCPList);  | 
2448  | 0  |             CPLFree(pasGCPList);  | 
2449  |  | 
  | 
2450  | 0  |             if (part.pTransformArg == nullptr)  | 
2451  | 0  |             { | 
2452  | 0  |                 return false;  | 
2453  | 0  |             }  | 
2454  | 0  |             part.pTransformer = GDALTPSTransform;  | 
2455  | 0  |         }  | 
2456  |  |  | 
2457  | 0  |         else if ((pszMethod == nullptr || EQUAL(pszMethod, "RPC")) &&  | 
2458  | 0  |                  (papszMD = GDALGetMetadata(hDS, "RPC")) != nullptr &&  | 
2459  | 0  |                  GDALExtractRPCInfoV2(papszMD, &sRPCInfo))  | 
2460  | 0  |         { | 
2461  | 0  |             CPLStringList aosOptions(papszOptions);  | 
2462  | 0  |             if (!CSLFetchNameValue(papszOptions, "RPC_HEIGHT") &&  | 
2463  | 0  |                 !CSLFetchNameValue(papszOptions, "RPC_DEM"))  | 
2464  | 0  |             { | 
2465  | 0  |                 if (const char *pszHEIGHT_DEFAULT =  | 
2466  | 0  |                         CSLFetchNameValue(papszMD, "HEIGHT_DEFAULT"))  | 
2467  | 0  |                 { | 
2468  | 0  |                     CPLDebug("GDAL", | 
2469  | 0  |                              "For %s, using RPC_HEIGHT = HEIGHT_DEFAULT = %s",  | 
2470  | 0  |                              pszPrefix, pszHEIGHT_DEFAULT);  | 
2471  | 0  |                     aosOptions.SetNameValue("RPC_HEIGHT", pszHEIGHT_DEFAULT); | 
2472  | 0  |                 }  | 
2473  | 0  |             }  | 
2474  | 0  |             part.pTransformArg = GDALCreateRPCTransformerV2(&sRPCInfo, FALSE, 0,  | 
2475  | 0  |                                                             aosOptions.List());  | 
2476  | 0  |             if (part.pTransformArg == nullptr)  | 
2477  | 0  |             { | 
2478  | 0  |                 return false;  | 
2479  | 0  |             }  | 
2480  | 0  |             part.pTransformer = GDALRPCTransform;  | 
2481  | 0  |             if (pszSRS == nullptr)  | 
2482  | 0  |             { | 
2483  | 0  |                 oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);  | 
2484  | 0  |                 oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
2485  | 0  |             }  | 
2486  | 0  |         }  | 
2487  |  |  | 
2488  | 0  |         else if ((pszMethod == nullptr || EQUAL(pszMethod, "GEOLOC_ARRAY")) &&  | 
2489  | 0  |                  ((papszMD = GDALGetMetadata(hDS, "GEOLOCATION")) != nullptr ||  | 
2490  | 0  |                   pszGeolocArray != nullptr))  | 
2491  | 0  |         { | 
2492  | 0  |             CPLStringList aosGeolocMD;  // keep in this scope  | 
2493  | 0  |             if (pszGeolocArray != nullptr)  | 
2494  | 0  |             { | 
2495  | 0  |                 if (papszMD != nullptr)  | 
2496  | 0  |                 { | 
2497  | 0  |                     CPLError(  | 
2498  | 0  |                         CE_Warning, CPLE_AppDefined,  | 
2499  | 0  |                         "Both GEOLOCATION metadata domain on the source "  | 
2500  | 0  |                         "dataset "  | 
2501  | 0  |                         "and [%s_]GEOLOC_ARRAY transformer option are set. "  | 
2502  | 0  |                         "Only using the later.",  | 
2503  | 0  |                         pszPrefix);  | 
2504  | 0  |                 }  | 
2505  | 0  |                 aosGeolocMD = GDALCreateGeolocationMetadata(  | 
2506  | 0  |                     hDS, pszGeolocArray,  | 
2507  | 0  |                     /* bIsSource= */ EQUAL(pszPrefix, "SRC"));  | 
2508  | 0  |                 if (aosGeolocMD.empty())  | 
2509  | 0  |                 { | 
2510  | 0  |                     return false;  | 
2511  | 0  |                 }  | 
2512  | 0  |                 papszMD = aosGeolocMD.List();  | 
2513  | 0  |             }  | 
2514  |  |  | 
2515  | 0  |             part.pTransformArg = GDALCreateGeoLocTransformerEx(  | 
2516  | 0  |                 hDS, papszMD, FALSE, nullptr, papszOptions);  | 
2517  | 0  |             if (part.pTransformArg == nullptr)  | 
2518  | 0  |             { | 
2519  | 0  |                 return false;  | 
2520  | 0  |             }  | 
2521  | 0  |             part.pTransformer = GDALGeoLocTransform;  | 
2522  | 0  |             if (pszSRS == nullptr)  | 
2523  | 0  |             { | 
2524  | 0  |                 pszSRS = CSLFetchNameValue(papszMD, "SRS");  | 
2525  | 0  |                 if (pszSRS)  | 
2526  | 0  |                 { | 
2527  | 0  |                     oSRS.SetFromUserInput(pszSRS);  | 
2528  | 0  |                     oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
2529  | 0  |                 }  | 
2530  | 0  |             }  | 
2531  | 0  |         }  | 
2532  |  |  | 
2533  | 0  |         else if (pszMethod != nullptr && EQUAL(pszPrefix, "SRC"))  | 
2534  | 0  |         { | 
2535  | 0  |             CPLError(CE_Failure, CPLE_AppDefined,  | 
2536  | 0  |                      "Unable to compute a %s based transformation between "  | 
2537  | 0  |                      "pixel/line and georeferenced coordinates for %s.",  | 
2538  | 0  |                      pszMethod, GDALGetDescription(hDS));  | 
2539  |  | 
  | 
2540  | 0  |             return false;  | 
2541  | 0  |         }  | 
2542  |  |  | 
2543  | 0  |         else  | 
2544  | 0  |         { | 
2545  | 0  |             CPLError(CE_Failure, CPLE_AppDefined,  | 
2546  | 0  |                      "Unable to compute a transformation between pixel/line "  | 
2547  | 0  |                      "and georeferenced coordinates for %s. "  | 
2548  | 0  |                      "There is no affine transformation and no GCPs. "  | 
2549  | 0  |                      "Specify transformation option %s_METHOD=NO_GEOTRANSFORM "  | 
2550  | 0  |                      "to bypass this check.",  | 
2551  | 0  |                      GDALGetDescription(hDS), pszPrefix);  | 
2552  |  | 
  | 
2553  | 0  |             return false;  | 
2554  | 0  |         }  | 
2555  |  |  | 
2556  |  |         /* ---------------------------------------------------------------- */  | 
2557  |  |         /*      Handle optional source approximation transformer.           */  | 
2558  |  |         /* ---------------------------------------------------------------- */  | 
2559  | 0  |         if (part.pTransformer)  | 
2560  | 0  |         { | 
2561  | 0  |             const char *pszApproxErrorFwd = CSLFetchNameValue(  | 
2562  | 0  |                 papszOptions, std::string(pszPrefix)  | 
2563  | 0  |                                   .append("_APPROX_ERROR_IN_SRS_UNIT") | 
2564  | 0  |                                   .c_str());  | 
2565  | 0  |             const char *pszApproxErrorReverse = CSLFetchNameValue(  | 
2566  | 0  |                 papszOptions, std::string(pszPrefix)  | 
2567  | 0  |                                   .append("_APPROX_ERROR_IN_PIXEL") | 
2568  | 0  |                                   .c_str());  | 
2569  | 0  |             if (pszApproxErrorFwd && pszApproxErrorReverse)  | 
2570  | 0  |             { | 
2571  | 0  |                 void *pArg = GDALCreateApproxTransformer2(  | 
2572  | 0  |                     part.pTransformer, part.pTransformArg,  | 
2573  | 0  |                     CPLAtof(pszApproxErrorFwd), CPLAtof(pszApproxErrorReverse));  | 
2574  | 0  |                 if (pArg == nullptr)  | 
2575  | 0  |                 { | 
2576  | 0  |                     return false;  | 
2577  | 0  |                 }  | 
2578  | 0  |                 part.pTransformArg = pArg;  | 
2579  | 0  |                 part.pTransformer = GDALApproxTransform;  | 
2580  | 0  |                 GDALApproxTransformerOwnsSubtransformer(part.pTransformArg,  | 
2581  | 0  |                                                         TRUE);  | 
2582  | 0  |             }  | 
2583  | 0  |         }  | 
2584  |  |  | 
2585  | 0  |         return true;  | 
2586  | 0  |     };  | 
2587  |  |  | 
2588  |  |     /* -------------------------------------------------------------------- */  | 
2589  |  |     /*      Get forward and inverse geotransform for the source image.      */  | 
2590  |  |     /* -------------------------------------------------------------------- */  | 
2591  | 0  |     bool bCanUseSrcGeoTransform = false;  | 
2592  | 0  |     OGRSpatialReference oSrcSRS;  | 
2593  | 0  |     if (!DealWithForwardOrInverse(psInfo->sSrcParams, hSrcDS, "SRC", oSrcSRS,  | 
2594  | 0  |                                   bCanUseSrcGeoTransform))  | 
2595  | 0  |     { | 
2596  | 0  |         GDALDestroyGenImgProjTransformer(psInfo);  | 
2597  | 0  |         return nullptr;  | 
2598  | 0  |     }  | 
2599  |  |  | 
2600  |  |     /* -------------------------------------------------------------------- */  | 
2601  |  |     /*      Get forward and inverse geotransform for destination image.     */  | 
2602  |  |     /*      If we have no destination use a unit transform.                 */  | 
2603  |  |     /* -------------------------------------------------------------------- */  | 
2604  | 0  |     bool bIgnored = false;  | 
2605  | 0  |     OGRSpatialReference oDstSRS;  | 
2606  | 0  |     if (!DealWithForwardOrInverse(psInfo->sDstParams, hDstDS, "DST", oDstSRS,  | 
2607  | 0  |                                   bIgnored))  | 
2608  | 0  |     { | 
2609  | 0  |         GDALDestroyGenImgProjTransformer(psInfo);  | 
2610  | 0  |         return nullptr;  | 
2611  | 0  |     }  | 
2612  |  |  | 
2613  |  |     /* -------------------------------------------------------------------- */  | 
2614  |  |     /*      Setup reprojection.                                             */  | 
2615  |  |     /* -------------------------------------------------------------------- */  | 
2616  |  |  | 
2617  | 0  |     if (CPLFetchBool(papszOptions, "STRIP_VERT_CS", false))  | 
2618  | 0  |     { | 
2619  | 0  |         if (oSrcSRS.IsCompound())  | 
2620  | 0  |         { | 
2621  | 0  |             oSrcSRS.StripVertical();  | 
2622  | 0  |         }  | 
2623  | 0  |         if (oDstSRS.IsCompound())  | 
2624  | 0  |         { | 
2625  | 0  |             oDstSRS.StripVertical();  | 
2626  | 0  |         }  | 
2627  | 0  |     }  | 
2628  |  | 
  | 
2629  | 0  |     const bool bMayInsertCenterLong =  | 
2630  | 0  |         (bCanUseSrcGeoTransform && !oSrcSRS.IsEmpty() && hSrcDS &&  | 
2631  | 0  |          CPLFetchBool(papszOptions, "INSERT_CENTER_LONG", true));  | 
2632  | 0  |     const char *pszSrcCoordEpoch =  | 
2633  | 0  |         CSLFetchNameValue(papszOptions, "SRC_COORDINATE_EPOCH");  | 
2634  | 0  |     const char *pszDstCoordEpoch =  | 
2635  | 0  |         CSLFetchNameValue(papszOptions, "DST_COORDINATE_EPOCH");  | 
2636  | 0  |     if ((!oSrcSRS.IsEmpty() && !oDstSRS.IsEmpty() &&  | 
2637  | 0  |          (pszSrcCoordEpoch || pszDstCoordEpoch || !oSrcSRS.IsSame(&oDstSRS) ||  | 
2638  | 0  |           (oSrcSRS.IsGeographic() && bMayInsertCenterLong))) ||  | 
2639  | 0  |         pszCO)  | 
2640  | 0  |     { | 
2641  | 0  |         CPLStringList aosOptions;  | 
2642  |  | 
  | 
2643  | 0  |         if (bMayInsertCenterLong)  | 
2644  | 0  |         { | 
2645  | 0  |             InsertCenterLong(hSrcDS, &oSrcSRS, aosOptions);  | 
2646  | 0  |         }  | 
2647  |  | 
  | 
2648  | 0  |         if (CPLFetchBool(papszOptions, "PROMOTE_TO_3D", false))  | 
2649  | 0  |         { | 
2650  | 0  |             oSrcSRS.PromoteTo3D(nullptr);  | 
2651  | 0  |             oDstSRS.PromoteTo3D(nullptr);  | 
2652  | 0  |         }  | 
2653  |  | 
  | 
2654  | 0  |         if (!(dfWestLongitudeDeg == 0.0 && dfSouthLatitudeDeg == 0.0 &&  | 
2655  | 0  |               dfEastLongitudeDeg == 0.0 && dfNorthLatitudeDeg == 0.0))  | 
2656  | 0  |         { | 
2657  | 0  |             aosOptions.SetNameValue(  | 
2658  | 0  |                 "AREA_OF_INTEREST",  | 
2659  | 0  |                 CPLSPrintf("%.16g,%.16g,%.16g,%.16g", dfWestLongitudeDeg, | 
2660  | 0  |                            dfSouthLatitudeDeg, dfEastLongitudeDeg,  | 
2661  | 0  |                            dfNorthLatitudeDeg));  | 
2662  | 0  |         }  | 
2663  | 0  |         if (pszCO)  | 
2664  | 0  |         { | 
2665  | 0  |             aosOptions.SetNameValue("COORDINATE_OPERATION", pszCO); | 
2666  | 0  |         }  | 
2667  |  | 
  | 
2668  | 0  |         const char *pszCoordEpoch =  | 
2669  | 0  |             CSLFetchNameValue(papszOptions, "COORDINATE_EPOCH");  | 
2670  | 0  |         if (pszCoordEpoch)  | 
2671  | 0  |         { | 
2672  | 0  |             aosOptions.SetNameValue("COORDINATE_EPOCH", pszCoordEpoch); | 
2673  | 0  |         }  | 
2674  |  | 
  | 
2675  | 0  |         if (pszSrcCoordEpoch)  | 
2676  | 0  |         { | 
2677  | 0  |             aosOptions.SetNameValue("SRC_COORDINATE_EPOCH", pszSrcCoordEpoch); | 
2678  | 0  |             oSrcSRS.SetCoordinateEpoch(CPLAtof(pszSrcCoordEpoch));  | 
2679  | 0  |         }  | 
2680  |  | 
  | 
2681  | 0  |         if (pszDstCoordEpoch)  | 
2682  | 0  |         { | 
2683  | 0  |             aosOptions.SetNameValue("DST_COORDINATE_EPOCH", pszDstCoordEpoch); | 
2684  | 0  |             oDstSRS.SetCoordinateEpoch(CPLAtof(pszDstCoordEpoch));  | 
2685  | 0  |         }  | 
2686  |  | 
  | 
2687  | 0  |         if (const char *pszAllowBallpark =  | 
2688  | 0  |                 CSLFetchNameValue(papszOptions, "ALLOW_BALLPARK"))  | 
2689  | 0  |         { | 
2690  | 0  |             aosOptions.SetNameValue("ALLOW_BALLPARK", pszAllowBallpark); | 
2691  | 0  |         }  | 
2692  |  | 
  | 
2693  | 0  |         if (const char *pszOnlyBest =  | 
2694  | 0  |                 CSLFetchNameValue(papszOptions, "ONLY_BEST"))  | 
2695  | 0  |         { | 
2696  | 0  |             aosOptions.SetNameValue("ONLY_BEST", pszOnlyBest); | 
2697  | 0  |         }  | 
2698  |  | 
  | 
2699  | 0  |         psInfo->pReprojectArg = GDALCreateReprojectionTransformerEx(  | 
2700  | 0  |             !oSrcSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oSrcSRS)  | 
2701  | 0  |                                : nullptr,  | 
2702  | 0  |             !oDstSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oDstSRS)  | 
2703  | 0  |                                : nullptr,  | 
2704  | 0  |             aosOptions.List());  | 
2705  |  | 
  | 
2706  | 0  |         if (pszCO)  | 
2707  | 0  |         { | 
2708  | 0  |             psInfo->bHasCustomTransformationPipeline = true;  | 
2709  | 0  |         }  | 
2710  |  | 
  | 
2711  | 0  |         if (psInfo->pReprojectArg == nullptr)  | 
2712  | 0  |         { | 
2713  | 0  |             GDALDestroyGenImgProjTransformer(psInfo);  | 
2714  | 0  |             return nullptr;  | 
2715  | 0  |         }  | 
2716  | 0  |         psInfo->pReproject = GDALReprojectionTransform;  | 
2717  |  |  | 
2718  |  |         /* --------------------------------------------------------------------  | 
2719  |  |          */  | 
2720  |  |         /*      Handle optional reprojection approximation transformer. */  | 
2721  |  |         /* --------------------------------------------------------------------  | 
2722  |  |          */  | 
2723  | 0  |         const char *psApproxErrorFwd = CSLFetchNameValue(  | 
2724  | 0  |             papszOptions, "REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT");  | 
2725  | 0  |         const char *psApproxErrorReverse = CSLFetchNameValue(  | 
2726  | 0  |             papszOptions, "REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT");  | 
2727  | 0  |         if (psApproxErrorFwd && psApproxErrorReverse)  | 
2728  | 0  |         { | 
2729  | 0  |             void *pArg = GDALCreateApproxTransformer2(  | 
2730  | 0  |                 psInfo->pReproject, psInfo->pReprojectArg,  | 
2731  | 0  |                 CPLAtof(psApproxErrorFwd), CPLAtof(psApproxErrorReverse));  | 
2732  | 0  |             if (pArg == nullptr)  | 
2733  | 0  |             { | 
2734  | 0  |                 GDALDestroyGenImgProjTransformer(psInfo);  | 
2735  | 0  |                 return nullptr;  | 
2736  | 0  |             }  | 
2737  | 0  |             psInfo->pReprojectArg = pArg;  | 
2738  | 0  |             psInfo->pReproject = GDALApproxTransform;  | 
2739  | 0  |             GDALApproxTransformerOwnsSubtransformer(psInfo->pReprojectArg,  | 
2740  | 0  |                                                     TRUE);  | 
2741  | 0  |         }  | 
2742  | 0  |     }  | 
2743  |  |  | 
2744  | 0  |     return psInfo;  | 
2745  | 0  | }  | 
2746  |  |  | 
2747  |  | /************************************************************************/  | 
2748  |  | /*                  GDALRefreshGenImgProjTransformer()                  */  | 
2749  |  | /************************************************************************/  | 
2750  |  |  | 
2751  |  | void GDALRefreshGenImgProjTransformer(void *hTransformArg)  | 
2752  | 0  | { | 
2753  | 0  |     GDALGenImgProjTransformInfo *psInfo =  | 
2754  | 0  |         static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);  | 
2755  |  | 
  | 
2756  | 0  |     if (psInfo->pReprojectArg &&  | 
2757  | 0  |         psInfo->bCheckWithInvertPROJ != GetCurrentCheckWithInvertPROJ())  | 
2758  | 0  |     { | 
2759  | 0  |         psInfo->bCheckWithInvertPROJ = !psInfo->bCheckWithInvertPROJ;  | 
2760  |  | 
  | 
2761  | 0  |         CPLXMLNode *psXML =  | 
2762  | 0  |             GDALSerializeTransformer(psInfo->pReproject, psInfo->pReprojectArg);  | 
2763  | 0  |         GDALDestroyTransformer(psInfo->pReprojectArg);  | 
2764  | 0  |         GDALDeserializeTransformer(psXML, &psInfo->pReproject,  | 
2765  | 0  |                                    &psInfo->pReprojectArg);  | 
2766  | 0  |         CPLDestroyXMLNode(psXML);  | 
2767  | 0  |     }  | 
2768  | 0  | }  | 
2769  |  |  | 
2770  |  | /************************************************************************/  | 
2771  |  | /*                  GDALCreateGenImgProjTransformer3()                  */  | 
2772  |  | /************************************************************************/  | 
2773  |  |  | 
2774  |  | /**  | 
2775  |  |  * Create image to image transformer.  | 
2776  |  |  *  | 
2777  |  |  * This function creates a transformation object that maps from pixel/line  | 
2778  |  |  * coordinates on one image to pixel/line coordinates on another image.  The  | 
2779  |  |  * images may potentially be georeferenced in different coordinate systems,  | 
2780  |  |  * and may used GCPs to map between their pixel/line coordinates and  | 
2781  |  |  * georeferenced coordinates (as opposed to the default assumption that their  | 
2782  |  |  * geotransform should be used).  | 
2783  |  |  *  | 
2784  |  |  * This transformer potentially performs three concatenated transformations.  | 
2785  |  |  *  | 
2786  |  |  * The first stage is from source image pixel/line coordinates to source  | 
2787  |  |  * image georeferenced coordinates, and may be done using the geotransform,  | 
2788  |  |  * or if not defined using a polynomial model derived from GCPs.  If GCPs  | 
2789  |  |  * are used this stage is accomplished using GDALGCPTransform().  | 
2790  |  |  *  | 
2791  |  |  * The second stage is to change projections from the source coordinate system  | 
2792  |  |  * to the destination coordinate system, assuming they differ.  This is  | 
2793  |  |  * accomplished internally using GDALReprojectionTransform().  | 
2794  |  |  *  | 
2795  |  |  * The third stage is converting from destination image georeferenced  | 
2796  |  |  * coordinates to destination image coordinates.  This is done using the  | 
2797  |  |  * destination image geotransform, or if not available, using a polynomial  | 
2798  |  |  * model derived from GCPs. If GCPs are used this stage is accomplished using  | 
2799  |  |  * GDALGCPTransform().  This stage is skipped if hDstDS is NULL when the  | 
2800  |  |  * transformation is created.  | 
2801  |  |  *  | 
2802  |  |  * @param pszSrcWKT source WKT (or NULL).  | 
2803  |  |  * @param padfSrcGeoTransform source geotransform (or NULL).  | 
2804  |  |  * @param pszDstWKT destination WKT (or NULL).  | 
2805  |  |  * @param padfDstGeoTransform destination geotransform (or NULL).  | 
2806  |  |  *  | 
2807  |  |  * @return handle suitable for use GDALGenImgProjTransform(), and to be  | 
2808  |  |  * deallocated with GDALDestroyGenImgProjTransformer() or NULL on failure.  | 
2809  |  |  */  | 
2810  |  |  | 
2811  |  | void *GDALCreateGenImgProjTransformer3(const char *pszSrcWKT,  | 
2812  |  |                                        const double *padfSrcGeoTransform,  | 
2813  |  |                                        const char *pszDstWKT,  | 
2814  |  |                                        const double *padfDstGeoTransform)  | 
2815  |  |  | 
2816  | 0  | { | 
2817  | 0  |     OGRSpatialReference oSrcSRS;  | 
2818  | 0  |     if (pszSrcWKT)  | 
2819  | 0  |     { | 
2820  | 0  |         oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
2821  | 0  |         if (pszSrcWKT[0] != '\0' &&  | 
2822  | 0  |             oSrcSRS.importFromWkt(pszSrcWKT) != OGRERR_NONE)  | 
2823  | 0  |         { | 
2824  | 0  |             CPLError(CE_Failure, CPLE_AppDefined,  | 
2825  | 0  |                      "Failed to import coordinate system `%s'.", pszSrcWKT);  | 
2826  | 0  |             return nullptr;  | 
2827  | 0  |         }  | 
2828  | 0  |     }  | 
2829  |  |  | 
2830  | 0  |     OGRSpatialReference oDstSRS;  | 
2831  | 0  |     if (pszDstWKT)  | 
2832  | 0  |     { | 
2833  | 0  |         oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
2834  | 0  |         if (pszDstWKT[0] != '\0' &&  | 
2835  | 0  |             oDstSRS.importFromWkt(pszDstWKT) != OGRERR_NONE)  | 
2836  | 0  |         { | 
2837  | 0  |             CPLError(CE_Failure, CPLE_AppDefined,  | 
2838  | 0  |                      "Failed to import coordinate system `%s'.", pszDstWKT);  | 
2839  | 0  |             return nullptr;  | 
2840  | 0  |         }  | 
2841  | 0  |     }  | 
2842  | 0  |     return GDALCreateGenImgProjTransformer4(  | 
2843  | 0  |         OGRSpatialReference::ToHandle(&oSrcSRS), padfSrcGeoTransform,  | 
2844  | 0  |         OGRSpatialReference::ToHandle(&oDstSRS), padfDstGeoTransform, nullptr);  | 
2845  | 0  | }  | 
2846  |  |  | 
2847  |  | /************************************************************************/  | 
2848  |  | /*                  GDALCreateGenImgProjTransformer4()                  */  | 
2849  |  | /************************************************************************/  | 
2850  |  |  | 
2851  |  | /**  | 
2852  |  |  * Create image to image transformer.  | 
2853  |  |  *  | 
2854  |  |  * Similar to GDALCreateGenImgProjTransformer3(), except that it takes  | 
2855  |  |  * OGRSpatialReferenceH objects and options.  | 
2856  |  |  * The options are the ones supported by GDALCreateReprojectionTransformerEx()  | 
2857  |  |  *  | 
2858  |  |  * @since GDAL 3.0  | 
2859  |  |  */  | 
2860  |  | void *GDALCreateGenImgProjTransformer4(OGRSpatialReferenceH hSrcSRS,  | 
2861  |  |                                        const double *padfSrcGeoTransform,  | 
2862  |  |                                        OGRSpatialReferenceH hDstSRS,  | 
2863  |  |                                        const double *padfDstGeoTransform,  | 
2864  |  |                                        const char *const *papszOptions)  | 
2865  | 0  | { | 
2866  |  |     /* -------------------------------------------------------------------- */  | 
2867  |  |     /*      Initialize the transform info.                                  */  | 
2868  |  |     /* -------------------------------------------------------------------- */  | 
2869  | 0  |     GDALGenImgProjTransformInfo *psInfo =  | 
2870  | 0  |         GDALCreateGenImgProjTransformerInternal();  | 
2871  |  |  | 
2872  |  |     /* -------------------------------------------------------------------- */  | 
2873  |  |     /*      Get forward and inverse geotransform for the source image.      */  | 
2874  |  |     /* -------------------------------------------------------------------- */  | 
2875  |  | 
  | 
2876  | 0  |     const auto SetParams =  | 
2877  | 0  |         [](GDALGenImgProjTransformPart &part, const double *padfGT)  | 
2878  | 0  |     { | 
2879  | 0  |         if (padfGT)  | 
2880  | 0  |         { | 
2881  | 0  |             memcpy(part.adfGeoTransform, padfGT, sizeof(part.adfGeoTransform));  | 
2882  | 0  |             if (!GDALInvGeoTransform(part.adfGeoTransform,  | 
2883  | 0  |                                      part.adfInvGeoTransform))  | 
2884  | 0  |             { | 
2885  | 0  |                 CPLError(CE_Failure, CPLE_AppDefined,  | 
2886  | 0  |                          "Cannot invert geotransform");  | 
2887  | 0  |                 return false;  | 
2888  | 0  |             }  | 
2889  | 0  |         }  | 
2890  | 0  |         else  | 
2891  | 0  |         { | 
2892  | 0  |             part.adfGeoTransform[0] = 0.0;  | 
2893  | 0  |             part.adfGeoTransform[1] = 1.0;  | 
2894  | 0  |             part.adfGeoTransform[2] = 0.0;  | 
2895  | 0  |             part.adfGeoTransform[3] = 0.0;  | 
2896  | 0  |             part.adfGeoTransform[4] = 0.0;  | 
2897  | 0  |             part.adfGeoTransform[5] = 1.0;  | 
2898  | 0  |             memcpy(part.adfInvGeoTransform, part.adfGeoTransform,  | 
2899  | 0  |                    sizeof(double) * 6);  | 
2900  | 0  |         }  | 
2901  | 0  |         return true;  | 
2902  | 0  |     };  | 
2903  |  | 
  | 
2904  | 0  |     if (!SetParams(psInfo->sSrcParams, padfSrcGeoTransform))  | 
2905  | 0  |     { | 
2906  | 0  |         GDALDestroyGenImgProjTransformer(psInfo);  | 
2907  | 0  |         return nullptr;  | 
2908  | 0  |     }  | 
2909  |  |  | 
2910  |  |     /* -------------------------------------------------------------------- */  | 
2911  |  |     /*      Setup reprojection.                                             */  | 
2912  |  |     /* -------------------------------------------------------------------- */  | 
2913  | 0  |     OGRSpatialReference *poSrcSRS = OGRSpatialReference::FromHandle(hSrcSRS);  | 
2914  | 0  |     OGRSpatialReference *poDstSRS = OGRSpatialReference::FromHandle(hDstSRS);  | 
2915  | 0  |     if (!poSrcSRS->IsEmpty() && !poDstSRS->IsEmpty() &&  | 
2916  | 0  |         !poSrcSRS->IsSame(poDstSRS))  | 
2917  | 0  |     { | 
2918  | 0  |         psInfo->pReprojectArg =  | 
2919  | 0  |             GDALCreateReprojectionTransformerEx(hSrcSRS, hDstSRS, papszOptions);  | 
2920  | 0  |         if (psInfo->pReprojectArg == nullptr)  | 
2921  | 0  |         { | 
2922  | 0  |             GDALDestroyGenImgProjTransformer(psInfo);  | 
2923  | 0  |             return nullptr;  | 
2924  | 0  |         }  | 
2925  | 0  |         psInfo->pReproject = GDALReprojectionTransform;  | 
2926  | 0  |     }  | 
2927  |  |  | 
2928  |  |     /* -------------------------------------------------------------------- */  | 
2929  |  |     /*      Get forward and inverse geotransform for destination image.     */  | 
2930  |  |     /*      If we have no destination matrix use a unit transform.          */  | 
2931  |  |     /* -------------------------------------------------------------------- */  | 
2932  | 0  |     if (!SetParams(psInfo->sDstParams, padfDstGeoTransform))  | 
2933  | 0  |     { | 
2934  | 0  |         GDALDestroyGenImgProjTransformer(psInfo);  | 
2935  | 0  |         return nullptr;  | 
2936  | 0  |     }  | 
2937  |  |  | 
2938  | 0  |     return psInfo;  | 
2939  | 0  | }  | 
2940  |  |  | 
2941  |  | /************************************************************************/  | 
2942  |  | /*            GDALSetGenImgProjTransformerDstGeoTransform()             */  | 
2943  |  | /************************************************************************/  | 
2944  |  |  | 
2945  |  | /**  | 
2946  |  |  * Set GenImgProj output geotransform.  | 
2947  |  |  *  | 
2948  |  |  * Normally the "destination geotransform", or transformation between  | 
2949  |  |  * georeferenced output coordinates and pixel/line coordinates on the  | 
2950  |  |  * destination file is extracted from the destination file by  | 
2951  |  |  * GDALCreateGenImgProjTransformer() and stored in the GenImgProj private  | 
2952  |  |  * info.  However, sometimes it is inconvenient to have an output file  | 
2953  |  |  * handle with appropriate geotransform information when creating the  | 
2954  |  |  * transformation.  For these cases, this function can be used to apply  | 
2955  |  |  * the destination geotransform.  | 
2956  |  |  *  | 
2957  |  |  * @param hTransformArg the handle to update.  | 
2958  |  |  * @param padfGeoTransform the destination geotransform to apply (six doubles).  | 
2959  |  |  */  | 
2960  |  |  | 
2961  |  | void GDALSetGenImgProjTransformerDstGeoTransform(void *hTransformArg,  | 
2962  |  |                                                  const double *padfGeoTransform)  | 
2963  |  |  | 
2964  | 0  | { | 
2965  | 0  |     VALIDATE_POINTER0(hTransformArg,  | 
2966  | 0  |                       "GDALSetGenImgProjTransformerDstGeoTransform");  | 
2967  |  |  | 
2968  | 0  |     GDALGenImgProjTransformInfo *psInfo =  | 
2969  | 0  |         static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);  | 
2970  |  | 
  | 
2971  | 0  |     memcpy(psInfo->sDstParams.adfGeoTransform, padfGeoTransform,  | 
2972  | 0  |            sizeof(double) * 6);  | 
2973  | 0  |     if (!GDALInvGeoTransform(psInfo->sDstParams.adfGeoTransform,  | 
2974  | 0  |                              psInfo->sDstParams.adfInvGeoTransform))  | 
2975  | 0  |     { | 
2976  | 0  |         CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform");  | 
2977  | 0  |     }  | 
2978  | 0  | }  | 
2979  |  |  | 
2980  |  | /************************************************************************/  | 
2981  |  | /*                  GDALDestroyGenImgProjTransformer()                  */  | 
2982  |  | /************************************************************************/  | 
2983  |  |  | 
2984  |  | /**  | 
2985  |  |  * GenImgProjTransformer deallocator.  | 
2986  |  |  *  | 
2987  |  |  * This function is used to deallocate the handle created with  | 
2988  |  |  * GDALCreateGenImgProjTransformer().  | 
2989  |  |  *  | 
2990  |  |  * @param hTransformArg the handle to deallocate.  | 
2991  |  |  */  | 
2992  |  |  | 
2993  |  | void GDALDestroyGenImgProjTransformer(void *hTransformArg)  | 
2994  |  |  | 
2995  | 0  | { | 
2996  | 0  |     if (hTransformArg == nullptr)  | 
2997  | 0  |         return;  | 
2998  |  |  | 
2999  | 0  |     GDALGenImgProjTransformInfo *psInfo =  | 
3000  | 0  |         static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);  | 
3001  |  | 
  | 
3002  | 0  |     if (psInfo->sSrcParams.pTransformArg != nullptr)  | 
3003  | 0  |         GDALDestroyTransformer(psInfo->sSrcParams.pTransformArg);  | 
3004  |  | 
  | 
3005  | 0  |     if (psInfo->sDstParams.pTransformArg != nullptr)  | 
3006  | 0  |         GDALDestroyTransformer(psInfo->sDstParams.pTransformArg);  | 
3007  |  | 
  | 
3008  | 0  |     if (psInfo->pReprojectArg != nullptr)  | 
3009  | 0  |         GDALDestroyTransformer(psInfo->pReprojectArg);  | 
3010  |  | 
  | 
3011  | 0  |     CPLFree(psInfo);  | 
3012  | 0  | }  | 
3013  |  |  | 
3014  |  | /************************************************************************/  | 
3015  |  | /*                      GDALGenImgProjTransform()                       */  | 
3016  |  | /************************************************************************/  | 
3017  |  |  | 
3018  |  | /**  | 
3019  |  |  * Perform general image reprojection transformation.  | 
3020  |  |  *  | 
3021  |  |  * Actually performs the transformation setup in  | 
3022  |  |  * GDALCreateGenImgProjTransformer().  This function matches the signature  | 
3023  |  |  * required by the GDALTransformerFunc(), and more details on the arguments  | 
3024  |  |  * can be found in that topic.  | 
3025  |  |  */  | 
3026  |  |  | 
3027  |  | #ifdef DEBUG_APPROX_TRANSFORMER  | 
3028  |  | int countGDALGenImgProjTransform = 0;  | 
3029  |  | #endif  | 
3030  |  |  | 
3031  |  | int GDALGenImgProjTransform(void *pTransformArgIn, int bDstToSrc,  | 
3032  |  |                             int nPointCount, double *padfX, double *padfY,  | 
3033  |  |                             double *padfZ, int *panSuccess)  | 
3034  | 0  | { | 
3035  | 0  |     GDALGenImgProjTransformInfo *psInfo =  | 
3036  | 0  |         static_cast<GDALGenImgProjTransformInfo *>(pTransformArgIn);  | 
3037  |  | 
  | 
3038  |  | #ifdef DEBUG_APPROX_TRANSFORMER  | 
3039  |  |     CPLAssert(nPointCount > 0);  | 
3040  |  |     countGDALGenImgProjTransform += nPointCount;  | 
3041  |  | #endif  | 
3042  |  | 
  | 
3043  | 0  |     for (int i = 0; i < nPointCount; i++)  | 
3044  | 0  |     { | 
3045  | 0  |         panSuccess[i] = (padfX[i] != HUGE_VAL && padfY[i] != HUGE_VAL);  | 
3046  | 0  |     }  | 
3047  |  | 
  | 
3048  | 0  |     int ret = TRUE;  | 
3049  |  |  | 
3050  |  |     /* -------------------------------------------------------------------- */  | 
3051  |  |     /*      Convert from src (dst) pixel/line to src (dst)                  */  | 
3052  |  |     /*      georeferenced coordinates.                                      */  | 
3053  |  |     /* -------------------------------------------------------------------- */  | 
3054  | 0  |     { | 
3055  | 0  |         const auto params = bDstToSrc ? psInfo->sDstParams : psInfo->sSrcParams;  | 
3056  | 0  |         const double *padfGeoTransform = params.adfGeoTransform;  | 
3057  | 0  |         void *pTransformArg = params.pTransformArg;  | 
3058  | 0  |         GDALTransformerFunc pTransformer = params.pTransformer;  | 
3059  |  | 
  | 
3060  | 0  |         if (pTransformArg != nullptr)  | 
3061  | 0  |         { | 
3062  | 0  |             if (!pTransformer(pTransformArg, FALSE, nPointCount, padfX, padfY,  | 
3063  | 0  |                               padfZ, panSuccess))  | 
3064  | 0  |                 ret = FALSE;  | 
3065  | 0  |         }  | 
3066  | 0  |         else  | 
3067  | 0  |         { | 
3068  | 0  |             for (int i = 0; i < nPointCount; i++)  | 
3069  | 0  |             { | 
3070  | 0  |                 if (!panSuccess[i])  | 
3071  | 0  |                     continue;  | 
3072  |  |  | 
3073  | 0  |                 const double dfNewX = padfGeoTransform[0] +  | 
3074  | 0  |                                       padfX[i] * padfGeoTransform[1] +  | 
3075  | 0  |                                       padfY[i] * padfGeoTransform[2];  | 
3076  | 0  |                 const double dfNewY = padfGeoTransform[3] +  | 
3077  | 0  |                                       padfX[i] * padfGeoTransform[4] +  | 
3078  | 0  |                                       padfY[i] * padfGeoTransform[5];  | 
3079  |  | 
  | 
3080  | 0  |                 padfX[i] = dfNewX;  | 
3081  | 0  |                 padfY[i] = dfNewY;  | 
3082  | 0  |             }  | 
3083  | 0  |         }  | 
3084  | 0  |     }  | 
3085  |  |  | 
3086  |  |     /* -------------------------------------------------------------------- */  | 
3087  |  |     /*      Reproject if needed.                                            */  | 
3088  |  |     /* -------------------------------------------------------------------- */  | 
3089  | 0  |     if (psInfo->pReprojectArg)  | 
3090  | 0  |     { | 
3091  | 0  |         if (!psInfo->pReproject(psInfo->pReprojectArg, bDstToSrc, nPointCount,  | 
3092  | 0  |                                 padfX, padfY, padfZ, panSuccess))  | 
3093  | 0  |             ret = FALSE;  | 
3094  | 0  |     }  | 
3095  |  |  | 
3096  |  |     /* -------------------------------------------------------------------- */  | 
3097  |  |     /*      Convert dst (src) georef coordinates back to pixel/line.        */  | 
3098  |  |     /* -------------------------------------------------------------------- */  | 
3099  | 0  |     { | 
3100  | 0  |         const auto params = bDstToSrc ? psInfo->sSrcParams : psInfo->sDstParams;  | 
3101  | 0  |         const double *padfInvGeoTransform = params.adfInvGeoTransform;  | 
3102  | 0  |         void *pTransformArg = params.pTransformArg;  | 
3103  | 0  |         GDALTransformerFunc pTransformer = params.pTransformer;  | 
3104  |  | 
  | 
3105  | 0  |         if (pTransformArg != nullptr)  | 
3106  | 0  |         { | 
3107  | 0  |             if (!pTransformer(pTransformArg, TRUE, nPointCount, padfX, padfY,  | 
3108  | 0  |                               padfZ, panSuccess))  | 
3109  | 0  |                 ret = FALSE;  | 
3110  | 0  |         }  | 
3111  | 0  |         else  | 
3112  | 0  |         { | 
3113  | 0  |             for (int i = 0; i < nPointCount; i++)  | 
3114  | 0  |             { | 
3115  | 0  |                 if (!panSuccess[i])  | 
3116  | 0  |                     continue;  | 
3117  |  |  | 
3118  | 0  |                 const double dfNewX = padfInvGeoTransform[0] +  | 
3119  | 0  |                                       padfX[i] * padfInvGeoTransform[1] +  | 
3120  | 0  |                                       padfY[i] * padfInvGeoTransform[2];  | 
3121  | 0  |                 const double dfNewY = padfInvGeoTransform[3] +  | 
3122  | 0  |                                       padfX[i] * padfInvGeoTransform[4] +  | 
3123  | 0  |                                       padfY[i] * padfInvGeoTransform[5];  | 
3124  |  | 
  | 
3125  | 0  |                 padfX[i] = dfNewX;  | 
3126  | 0  |                 padfY[i] = dfNewY;  | 
3127  | 0  |             }  | 
3128  | 0  |         }  | 
3129  | 0  |     }  | 
3130  |  | 
  | 
3131  | 0  |     return ret;  | 
3132  | 0  | }  | 
3133  |  |  | 
3134  |  | /************************************************************************/  | 
3135  |  | /*              GDALTransformLonLatToDestGenImgProjTransformer()        */  | 
3136  |  | /************************************************************************/  | 
3137  |  |  | 
3138  |  | int GDALTransformLonLatToDestGenImgProjTransformer(void *hTransformArg,  | 
3139  |  |                                                    double *pdfX, double *pdfY)  | 
3140  | 0  | { | 
3141  | 0  |     GDALGenImgProjTransformInfo *psInfo =  | 
3142  | 0  |         static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);  | 
3143  |  | 
  | 
3144  | 0  |     if (psInfo->pReprojectArg == nullptr ||  | 
3145  | 0  |         psInfo->pReproject != GDALReprojectionTransform)  | 
3146  | 0  |         return false;  | 
3147  |  |  | 
3148  | 0  |     GDALReprojectionTransformInfo *psReprojInfo =  | 
3149  | 0  |         static_cast<GDALReprojectionTransformInfo *>(psInfo->pReprojectArg);  | 
3150  | 0  |     if (psReprojInfo->poForwardTransform == nullptr ||  | 
3151  | 0  |         psReprojInfo->poForwardTransform->GetSourceCS() == nullptr)  | 
3152  | 0  |         return false;  | 
3153  |  |  | 
3154  | 0  |     double z = 0;  | 
3155  | 0  |     int success = true;  | 
3156  | 0  |     auto poSourceCRS = psReprojInfo->poForwardTransform->GetSourceCS();  | 
3157  | 0  |     if (poSourceCRS->IsGeographic() &&  | 
3158  | 0  |         std::fabs(poSourceCRS->GetAngularUnits() -  | 
3159  | 0  |                   CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-9)  | 
3160  | 0  |     { | 
3161  |  |         // Optimization to avoid creating a OGRCoordinateTransformation  | 
3162  | 0  |         OGRAxisOrientation eSourceFirstAxisOrient = OAO_Other;  | 
3163  | 0  |         poSourceCRS->GetAxis(nullptr, 0, &eSourceFirstAxisOrient);  | 
3164  | 0  |         const auto &mapping = poSourceCRS->GetDataAxisToSRSAxisMapping();  | 
3165  | 0  |         if ((mapping[0] == 2 && eSourceFirstAxisOrient == OAO_East) ||  | 
3166  | 0  |             (mapping[0] == 1 && eSourceFirstAxisOrient != OAO_East))  | 
3167  | 0  |         { | 
3168  | 0  |             std::swap(*pdfX, *pdfY);  | 
3169  | 0  |         }  | 
3170  | 0  |     }  | 
3171  | 0  |     else  | 
3172  | 0  |     { | 
3173  | 0  |         auto poLongLat =  | 
3174  | 0  |             std::unique_ptr<OGRSpatialReference>(poSourceCRS->CloneGeogCS());  | 
3175  | 0  |         if (poLongLat == nullptr)  | 
3176  | 0  |             return false;  | 
3177  | 0  |         poLongLat->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
3178  |  | 
  | 
3179  | 0  |         const bool bCurrentCheckWithInvertProj =  | 
3180  | 0  |             GetCurrentCheckWithInvertPROJ();  | 
3181  | 0  |         if (!bCurrentCheckWithInvertProj)  | 
3182  | 0  |             CPLSetThreadLocalConfigOption("CHECK_WITH_INVERT_PROJ", "YES"); | 
3183  | 0  |         auto poCT = std::unique_ptr<OGRCoordinateTransformation>(  | 
3184  | 0  |             OGRCreateCoordinateTransformation(poLongLat.get(), poSourceCRS));  | 
3185  | 0  |         if (!bCurrentCheckWithInvertProj)  | 
3186  | 0  |             CPLSetThreadLocalConfigOption("CHECK_WITH_INVERT_PROJ", nullptr); | 
3187  | 0  |         if (poCT == nullptr)  | 
3188  | 0  |             return false;  | 
3189  |  |  | 
3190  | 0  |         poCT->SetEmitErrors(false);  | 
3191  | 0  |         if (!poCT->Transform(1, pdfX, pdfY))  | 
3192  | 0  |             return false;  | 
3193  |  |  | 
3194  | 0  |         if (!psInfo->pReproject(psInfo->pReprojectArg, false, 1, pdfX, pdfY, &z,  | 
3195  | 0  |                                 &success) ||  | 
3196  | 0  |             !success)  | 
3197  | 0  |         { | 
3198  | 0  |             return false;  | 
3199  | 0  |         }  | 
3200  | 0  |     }  | 
3201  |  |  | 
3202  | 0  |     double *padfGeoTransform = psInfo->sDstParams.adfInvGeoTransform;  | 
3203  | 0  |     void *pTransformArg = psInfo->sDstParams.pTransformArg;  | 
3204  | 0  |     GDALTransformerFunc pTransformer = psInfo->sDstParams.pTransformer;  | 
3205  | 0  |     if (pTransformArg != nullptr)  | 
3206  | 0  |     { | 
3207  | 0  |         if (!pTransformer(pTransformArg, TRUE, 1, pdfX, pdfY, &z, &success) ||  | 
3208  | 0  |             !success)  | 
3209  | 0  |         { | 
3210  | 0  |             return false;  | 
3211  | 0  |         }  | 
3212  | 0  |     }  | 
3213  | 0  |     else  | 
3214  | 0  |     { | 
3215  | 0  |         const double dfNewX = padfGeoTransform[0] +  | 
3216  | 0  |                               pdfX[0] * padfGeoTransform[1] +  | 
3217  | 0  |                               pdfY[0] * padfGeoTransform[2];  | 
3218  | 0  |         const double dfNewY = padfGeoTransform[3] +  | 
3219  | 0  |                               pdfX[0] * padfGeoTransform[4] +  | 
3220  | 0  |                               pdfY[0] * padfGeoTransform[5];  | 
3221  |  | 
  | 
3222  | 0  |         pdfX[0] = dfNewX;  | 
3223  | 0  |         pdfY[0] = dfNewY;  | 
3224  | 0  |     }  | 
3225  |  |  | 
3226  | 0  |     return true;  | 
3227  | 0  | }  | 
3228  |  |  | 
3229  |  | /************************************************************************/  | 
3230  |  | /*                 GDALSerializeGenImgProjTransformer()                 */  | 
3231  |  | /************************************************************************/  | 
3232  |  |  | 
3233  |  | static CPLXMLNode *GDALSerializeGenImgProjTransformer(void *pTransformArg)  | 
3234  |  |  | 
3235  | 0  | { | 
3236  | 0  |     GDALGenImgProjTransformInfo *psInfo =  | 
3237  | 0  |         static_cast<GDALGenImgProjTransformInfo *>(pTransformArg);  | 
3238  |  | 
  | 
3239  | 0  |     CPLXMLNode *psTree =  | 
3240  | 0  |         CPLCreateXMLNode(nullptr, CXT_Element, "GenImgProjTransformer");  | 
3241  |  | 
  | 
3242  | 0  |     const auto SerializePart =  | 
3243  | 0  |         [psTree](const char *pszPrefix, const GDALGenImgProjTransformPart &part)  | 
3244  | 0  |     { | 
3245  | 0  |         char szWork[200] = {}; | 
3246  |  |  | 
3247  |  |         /* ------------------------------------------------------------- */  | 
3248  |  |         /*      Handle transformation.                                   */  | 
3249  |  |         /* ------------------------------------------------------------- */  | 
3250  | 0  |         if (part.pTransformArg != nullptr)  | 
3251  | 0  |         { | 
3252  | 0  |             CPLXMLNode *psTransformer =  | 
3253  | 0  |                 GDALSerializeTransformer(part.pTransformer, part.pTransformArg);  | 
3254  | 0  |             if (psTransformer != nullptr)  | 
3255  | 0  |             { | 
3256  | 0  |                 CPLXMLNode *psTransformerContainer = CPLCreateXMLNode(  | 
3257  | 0  |                     psTree, CXT_Element,  | 
3258  | 0  |                     CPLSPrintf("%s%s", pszPrefix, psTransformer->pszValue)); | 
3259  |  | 
  | 
3260  | 0  |                 CPLAddXMLChild(psTransformerContainer, psTransformer);  | 
3261  | 0  |             }  | 
3262  | 0  |         }  | 
3263  |  |  | 
3264  |  |         /* ------------------------------------------------------------- */  | 
3265  |  |         /*      Handle geotransforms.                                    */  | 
3266  |  |         /* ------------------------------------------------------------- */  | 
3267  | 0  |         else  | 
3268  | 0  |         { | 
3269  | 0  |             CPLsnprintf(szWork, sizeof(szWork),  | 
3270  | 0  |                         "%.17g,%.17g,%.17g,%.17g,%.17g,%.17g",  | 
3271  | 0  |                         part.adfGeoTransform[0], part.adfGeoTransform[1],  | 
3272  | 0  |                         part.adfGeoTransform[2], part.adfGeoTransform[3],  | 
3273  | 0  |                         part.adfGeoTransform[4], part.adfGeoTransform[5]);  | 
3274  | 0  |             CPLCreateXMLElementAndValue(  | 
3275  | 0  |                 psTree, CPLSPrintf("%sGeoTransform", pszPrefix), szWork); | 
3276  |  | 
  | 
3277  | 0  |             CPLsnprintf(szWork, sizeof(szWork),  | 
3278  | 0  |                         "%.17g,%.17g,%.17g,%.17g,%.17g,%.17g",  | 
3279  | 0  |                         part.adfInvGeoTransform[0], part.adfInvGeoTransform[1],  | 
3280  | 0  |                         part.adfInvGeoTransform[2], part.adfInvGeoTransform[3],  | 
3281  | 0  |                         part.adfInvGeoTransform[4], part.adfInvGeoTransform[5]);  | 
3282  | 0  |             CPLCreateXMLElementAndValue(  | 
3283  | 0  |                 psTree, CPLSPrintf("%sInvGeoTransform", pszPrefix), szWork); | 
3284  | 0  |         }  | 
3285  | 0  |     };  | 
3286  |  | 
  | 
3287  | 0  |     SerializePart("Src", psInfo->sSrcParams); | 
3288  | 0  |     SerializePart("Dst", psInfo->sDstParams); | 
3289  |  |  | 
3290  |  |     /* -------------------------------------------------------------------- */  | 
3291  |  |     /*      Do we have a reprojection transformer?                          */  | 
3292  |  |     /* -------------------------------------------------------------------- */  | 
3293  | 0  |     if (psInfo->pReprojectArg != nullptr)  | 
3294  | 0  |     { | 
3295  |  | 
  | 
3296  | 0  |         CPLXMLNode *psTransformerContainer =  | 
3297  | 0  |             CPLCreateXMLNode(psTree, CXT_Element, "ReprojectTransformer");  | 
3298  |  | 
  | 
3299  | 0  |         CPLXMLNode *psTransformer =  | 
3300  | 0  |             GDALSerializeTransformer(psInfo->pReproject, psInfo->pReprojectArg);  | 
3301  | 0  |         if (psTransformer != nullptr)  | 
3302  | 0  |             CPLAddXMLChild(psTransformerContainer, psTransformer);  | 
3303  | 0  |     }  | 
3304  |  | 
  | 
3305  | 0  |     return psTree;  | 
3306  | 0  | }  | 
3307  |  |  | 
3308  |  | /************************************************************************/  | 
3309  |  | /*                    GDALDeserializeGeoTransform()                     */  | 
3310  |  | /************************************************************************/  | 
3311  |  |  | 
3312  |  | static void GDALDeserializeGeoTransform(const char *pszGT,  | 
3313  |  |                                         double adfGeoTransform[6])  | 
3314  | 0  | { | 
3315  | 0  |     CPLsscanf(pszGT, "%lf,%lf,%lf,%lf,%lf,%lf", adfGeoTransform + 0,  | 
3316  | 0  |               adfGeoTransform + 1, adfGeoTransform + 2, adfGeoTransform + 3,  | 
3317  | 0  |               adfGeoTransform + 4, adfGeoTransform + 5);  | 
3318  | 0  | }  | 
3319  |  |  | 
3320  |  | /************************************************************************/  | 
3321  |  | /*                GDALDeserializeGenImgProjTransformer()                */  | 
3322  |  | /************************************************************************/  | 
3323  |  |  | 
3324  |  | void *GDALDeserializeGenImgProjTransformer(CPLXMLNode *psTree)  | 
3325  |  |  | 
3326  | 0  | { | 
3327  |  |     /* -------------------------------------------------------------------- */  | 
3328  |  |     /*      Initialize the transform info.                                  */  | 
3329  |  |     /* -------------------------------------------------------------------- */  | 
3330  | 0  |     GDALGenImgProjTransformInfo *psInfo =  | 
3331  | 0  |         GDALCreateGenImgProjTransformerInternal();  | 
3332  |  | 
  | 
3333  | 0  |     const auto DeserializePart =  | 
3334  | 0  |         [psTree](const char *pszPrefix, GDALGenImgProjTransformPart &part)  | 
3335  | 0  |     { | 
3336  |  |         /* ----------------------------------------------------------------- */  | 
3337  |  |         /*      Geotransform                                                 */  | 
3338  |  |         /* ----------------------------------------------------------------- */  | 
3339  | 0  |         if (const auto psGTNode =  | 
3340  | 0  |                 CPLGetXMLNode(psTree, CPLSPrintf("%sGeoTransform", pszPrefix))) | 
3341  | 0  |         { | 
3342  | 0  |             GDALDeserializeGeoTransform(CPLGetXMLValue(psGTNode, "", ""),  | 
3343  | 0  |                                         part.adfGeoTransform);  | 
3344  |  | 
  | 
3345  | 0  |             if (const auto psInvGTNode = CPLGetXMLNode(  | 
3346  | 0  |                     psTree, CPLSPrintf("%sInvGeoTransform", pszPrefix))) | 
3347  | 0  |             { | 
3348  | 0  |                 GDALDeserializeGeoTransform(CPLGetXMLValue(psInvGTNode, "", ""),  | 
3349  | 0  |                                             part.adfInvGeoTransform);  | 
3350  | 0  |             }  | 
3351  | 0  |             else  | 
3352  | 0  |             { | 
3353  | 0  |                 if (!GDALInvGeoTransform(part.adfGeoTransform,  | 
3354  | 0  |                                          part.adfInvGeoTransform))  | 
3355  | 0  |                 { | 
3356  | 0  |                     CPLError(CE_Failure, CPLE_AppDefined,  | 
3357  | 0  |                              "Cannot invert geotransform");  | 
3358  | 0  |                 }  | 
3359  | 0  |             }  | 
3360  | 0  |         }  | 
3361  |  |  | 
3362  |  |         /* ---------------------------------------------------------------- */  | 
3363  |  |         /*      Transform                                                   */  | 
3364  |  |         /* ---------------------------------------------------------------- */  | 
3365  | 0  |         else  | 
3366  | 0  |         { | 
3367  | 0  |             for (CPLXMLNode *psIter = psTree->psChild; psIter != nullptr;  | 
3368  | 0  |                  psIter = psIter->psNext)  | 
3369  | 0  |             { | 
3370  | 0  |                 if (psIter->eType == CXT_Element &&  | 
3371  | 0  |                     STARTS_WITH_CI(psIter->pszValue, pszPrefix))  | 
3372  | 0  |                 { | 
3373  | 0  |                     GDALDeserializeTransformer(psIter->psChild,  | 
3374  | 0  |                                                &part.pTransformer,  | 
3375  | 0  |                                                &part.pTransformArg);  | 
3376  | 0  |                     break;  | 
3377  | 0  |                 }  | 
3378  | 0  |             }  | 
3379  | 0  |         }  | 
3380  | 0  |     };  | 
3381  |  | 
  | 
3382  | 0  |     DeserializePart("Src", psInfo->sSrcParams); | 
3383  | 0  |     DeserializePart("Dst", psInfo->sDstParams); | 
3384  |  |  | 
3385  |  |     /* -------------------------------------------------------------------- */  | 
3386  |  |     /*      Reproject transformer                                           */  | 
3387  |  |     /* -------------------------------------------------------------------- */  | 
3388  | 0  |     CPLXMLNode *psSubtree = CPLGetXMLNode(psTree, "ReprojectTransformer");  | 
3389  | 0  |     if (psSubtree != nullptr && psSubtree->psChild != nullptr)  | 
3390  | 0  |     { | 
3391  | 0  |         GDALDeserializeTransformer(psSubtree->psChild, &psInfo->pReproject,  | 
3392  | 0  |                                    &psInfo->pReprojectArg);  | 
3393  | 0  |     }  | 
3394  |  | 
  | 
3395  | 0  |     return psInfo;  | 
3396  | 0  | }  | 
3397  |  |  | 
3398  |  | /************************************************************************/  | 
3399  |  | /*                 GDALCreateReprojectionTransformer()                  */  | 
3400  |  | /************************************************************************/  | 
3401  |  |  | 
3402  |  | /**  | 
3403  |  |  * Create reprojection transformer.  | 
3404  |  |  *  | 
3405  |  |  * Creates a callback data structure suitable for use with  | 
3406  |  |  * GDALReprojectionTransformation() to represent a transformation from  | 
3407  |  |  * one geographic or projected coordinate system to another.  On input  | 
3408  |  |  * the coordinate systems are described in OpenGIS WKT format.  | 
3409  |  |  *  | 
3410  |  |  * Internally the OGRCoordinateTransformation object is used to implement  | 
3411  |  |  * the reprojection.  | 
3412  |  |  *  | 
3413  |  |  * @param pszSrcWKT the coordinate system for the source coordinate system.  | 
3414  |  |  * @param pszDstWKT the coordinate system for the destination coordinate  | 
3415  |  |  * system.  | 
3416  |  |  *  | 
3417  |  |  * @return Handle for use with GDALReprojectionTransform(), or NULL if the  | 
3418  |  |  * system fails to initialize the reprojection.  | 
3419  |  |  **/  | 
3420  |  |  | 
3421  |  | void *GDALCreateReprojectionTransformer(const char *pszSrcWKT,  | 
3422  |  |                                         const char *pszDstWKT)  | 
3423  |  |  | 
3424  | 0  | { | 
3425  |  |     /* -------------------------------------------------------------------- */  | 
3426  |  |     /*      Ingest the SRS definitions.                                     */  | 
3427  |  |     /* -------------------------------------------------------------------- */  | 
3428  | 0  |     OGRSpatialReference oSrcSRS;  | 
3429  | 0  |     oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
3430  | 0  |     if (oSrcSRS.importFromWkt(pszSrcWKT) != OGRERR_NONE)  | 
3431  | 0  |     { | 
3432  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
3433  | 0  |                  "Failed to import coordinate system `%s'.", pszSrcWKT);  | 
3434  | 0  |         return nullptr;  | 
3435  | 0  |     }  | 
3436  |  |  | 
3437  | 0  |     OGRSpatialReference oDstSRS;  | 
3438  | 0  |     oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
3439  | 0  |     if (oDstSRS.importFromWkt(pszDstWKT) != OGRERR_NONE)  | 
3440  | 0  |     { | 
3441  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
3442  | 0  |                  "Failed to import coordinate system `%s'.", pszSrcWKT);  | 
3443  | 0  |         return nullptr;  | 
3444  | 0  |     }  | 
3445  |  |  | 
3446  | 0  |     return GDALCreateReprojectionTransformerEx(  | 
3447  | 0  |         OGRSpatialReference::ToHandle(&oSrcSRS),  | 
3448  | 0  |         OGRSpatialReference::ToHandle(&oDstSRS), nullptr);  | 
3449  | 0  | }  | 
3450  |  |  | 
3451  |  | /************************************************************************/  | 
3452  |  | /*                 GDALCreateReprojectionTransformerEx()                */  | 
3453  |  | /************************************************************************/  | 
3454  |  |  | 
3455  |  | /**  | 
3456  |  |  * Create reprojection transformer.  | 
3457  |  |  *  | 
3458  |  |  * Creates a callback data structure suitable for use with  | 
3459  |  |  * GDALReprojectionTransformation() to represent a transformation from  | 
3460  |  |  * one geographic or projected coordinate system to another.  | 
3461  |  |  *  | 
3462  |  |  * Internally the OGRCoordinateTransformation object is used to implement  | 
3463  |  |  * the reprojection.  | 
3464  |  |  *  | 
3465  |  |  * @param hSrcSRS the coordinate system for the source coordinate system.  | 
3466  |  |  * @param hDstSRS the coordinate system for the destination coordinate  | 
3467  |  |  * system.  | 
3468  |  |  * @param papszOptions NULL-terminated list of options, or NULL. Currently  | 
3469  |  |  * supported options are:  | 
3470  |  |  * <ul>  | 
3471  |  |  * <li>AREA_OF_INTEREST=west_long,south_lat,east_long,north_lat: Values in  | 
3472  |  |  * degrees. longitudes in [-180,180], latitudes in [-90,90].</li>  | 
3473  |  |  * <li>COORDINATE_OPERATION=string: PROJ or WKT string representing a  | 
3474  |  |  * coordinate operation, overriding the default computed transformation.</li>  | 
3475  |  |  * <li>COORDINATE_EPOCH=decimal_year: Coordinate epoch, expressed as a  | 
3476  |  |  * decimal year. Useful for time-dependent coordinate operations.</li>  | 
3477  |  |  * <li> SRC_COORDINATE_EPOCH: (GDAL >= 3.4) Coordinate epoch of source CRS,  | 
3478  |  |  * expressed as a decimal year. Useful for time-dependent coordinate  | 
3479  |  |  *operations.</li>  | 
3480  |  |  * <li> DST_COORDINATE_EPOCH: (GDAL >= 3.4) Coordinate epoch  | 
3481  |  |  *of target CRS, expressed as a decimal year. Useful for time-dependent  | 
3482  |  |  *coordinate operations.</li>  | 
3483  |  |  * <li> ALLOW_BALLPARK=YES/NO: (GDAL >= 3.11) Whether ballpark coordinate  | 
3484  |  |  * operations are allowed. Defaults to YES.</li>  | 
3485  |  |  * <li> ONLY_BEST=YES/NO/AUTO: (GDAL >= 3.11) By default (at least in the  | 
3486  |  |  * PROJ 9.x series), PROJ may use coordinate  | 
3487  |  |  * operations that are not the "best" if resources (typically grids) needed  | 
3488  |  |  * to use them are missing. It will then fallback to other coordinate operations  | 
3489  |  |  * that have a lesser accuracy, for example using Helmert transformations,  | 
3490  |  |  * or in the absence of such operations, to ones with potential very rought  | 
3491  |  |  * accuracy, using "ballpark" transformations  | 
3492  |  |  * (see https://proj.org/glossary.html).  | 
3493  |  |  * When calling this method with YES, PROJ will only consider the  | 
3494  |  |  * "best" operation, and error out (at Transform() time) if they cannot be  | 
3495  |  |  * used.  | 
3496  |  |  * This method may be used together with ALLOW_BALLPARK=NO to  | 
3497  |  |  * only allow best operations that have a known accuracy.  | 
3498  |  |  * Note that this method has no effect on PROJ versions before 9.2.  | 
3499  |  |  * The default value for this option can be also set with the  | 
3500  |  |  * PROJ_ONLY_BEST_DEFAULT environment variable, or with the "only_best_default"  | 
3501  |  |  * setting of proj.ini. Calling SetOnlyBest() overrides such default value.</li>  | 
3502  |  |  * </ul>  | 
3503  |  |  *  | 
3504  |  |  * @return Handle for use with GDALReprojectionTransform(), or NULL if the  | 
3505  |  |  * system fails to initialize the reprojection.  | 
3506  |  |  *  | 
3507  |  |  * @since GDAL 3.0  | 
3508  |  |  **/  | 
3509  |  |  | 
3510  |  | void *GDALCreateReprojectionTransformerEx(OGRSpatialReferenceH hSrcSRS,  | 
3511  |  |                                           OGRSpatialReferenceH hDstSRS,  | 
3512  |  |                                           const char *const *papszOptions)  | 
3513  | 0  | { | 
3514  | 0  |     OGRSpatialReference *poSrcSRS = OGRSpatialReference::FromHandle(hSrcSRS);  | 
3515  | 0  |     OGRSpatialReference *poDstSRS = OGRSpatialReference::FromHandle(hDstSRS);  | 
3516  |  |  | 
3517  |  |     /* -------------------------------------------------------------------- */  | 
3518  |  |     /*      Build the forward coordinate transformation.                    */  | 
3519  |  |     /* -------------------------------------------------------------------- */  | 
3520  | 0  |     double dfWestLongitudeDeg = 0.0;  | 
3521  | 0  |     double dfSouthLatitudeDeg = 0.0;  | 
3522  | 0  |     double dfEastLongitudeDeg = 0.0;  | 
3523  | 0  |     double dfNorthLatitudeDeg = 0.0;  | 
3524  | 0  |     const char *pszBBOX = CSLFetchNameValue(papszOptions, "AREA_OF_INTEREST");  | 
3525  | 0  |     if (pszBBOX)  | 
3526  | 0  |     { | 
3527  | 0  |         char **papszTokens = CSLTokenizeString2(pszBBOX, ",", 0);  | 
3528  | 0  |         if (CSLCount(papszTokens) == 4)  | 
3529  | 0  |         { | 
3530  | 0  |             dfWestLongitudeDeg = CPLAtof(papszTokens[0]);  | 
3531  | 0  |             dfSouthLatitudeDeg = CPLAtof(papszTokens[1]);  | 
3532  | 0  |             dfEastLongitudeDeg = CPLAtof(papszTokens[2]);  | 
3533  | 0  |             dfNorthLatitudeDeg = CPLAtof(papszTokens[3]);  | 
3534  | 0  |         }  | 
3535  | 0  |         CSLDestroy(papszTokens);  | 
3536  | 0  |     }  | 
3537  | 0  |     const char *pszCO = CSLFetchNameValue(papszOptions, "COORDINATE_OPERATION");  | 
3538  |  | 
  | 
3539  | 0  |     OGRCoordinateTransformationOptions optionsFwd;  | 
3540  | 0  |     if (!(dfWestLongitudeDeg == 0.0 && dfSouthLatitudeDeg == 0.0 &&  | 
3541  | 0  |           dfEastLongitudeDeg == 0.0 && dfNorthLatitudeDeg == 0.0))  | 
3542  | 0  |     { | 
3543  | 0  |         optionsFwd.SetAreaOfInterest(dfWestLongitudeDeg, dfSouthLatitudeDeg,  | 
3544  | 0  |                                      dfEastLongitudeDeg, dfNorthLatitudeDeg);  | 
3545  | 0  |     }  | 
3546  | 0  |     if (pszCO)  | 
3547  | 0  |     { | 
3548  | 0  |         optionsFwd.SetCoordinateOperation(pszCO, false);  | 
3549  | 0  |     }  | 
3550  |  | 
  | 
3551  | 0  |     const char *pszCENTER_LONG = CSLFetchNameValue(papszOptions, "CENTER_LONG");  | 
3552  | 0  |     if (pszCENTER_LONG)  | 
3553  | 0  |     { | 
3554  | 0  |         optionsFwd.SetSourceCenterLong(CPLAtof(pszCENTER_LONG));  | 
3555  | 0  |     }  | 
3556  |  | 
  | 
3557  | 0  |     optionsFwd.SetBallparkAllowed(CPLTestBool(  | 
3558  | 0  |         CSLFetchNameValueDef(papszOptions, "ALLOW_BALLPARK", "YES")));  | 
3559  |  | 
  | 
3560  | 0  |     const char *pszOnlyBest =  | 
3561  | 0  |         CSLFetchNameValueDef(papszOptions, "ONLY_BEST", "AUTO");  | 
3562  | 0  |     if (!EQUAL(pszOnlyBest, "AUTO"))  | 
3563  | 0  |     { | 
3564  | 0  |         optionsFwd.SetOnlyBest(CPLTestBool(pszOnlyBest));  | 
3565  | 0  |     }  | 
3566  |  | 
  | 
3567  | 0  |     OGRCoordinateTransformation *poForwardTransform =  | 
3568  | 0  |         OGRCreateCoordinateTransformation(poSrcSRS, poDstSRS, optionsFwd);  | 
3569  |  | 
  | 
3570  | 0  |     if (poForwardTransform == nullptr)  | 
3571  |  |         // OGRCreateCoordinateTransformation() will report errors on its own.  | 
3572  | 0  |         return nullptr;  | 
3573  |  |  | 
3574  | 0  |     poForwardTransform->SetEmitErrors(false);  | 
3575  |  |  | 
3576  |  |     /* -------------------------------------------------------------------- */  | 
3577  |  |     /*      Create a structure to hold the transform info, and also         */  | 
3578  |  |     /*      build reverse transform.  We assume that if the forward         */  | 
3579  |  |     /*      transform can be created, then so can the reverse one.          */  | 
3580  |  |     /* -------------------------------------------------------------------- */  | 
3581  | 0  |     GDALReprojectionTransformInfo *psInfo = new GDALReprojectionTransformInfo();  | 
3582  |  | 
  | 
3583  | 0  |     psInfo->papszOptions = CSLDuplicate(papszOptions);  | 
3584  | 0  |     psInfo->poForwardTransform = poForwardTransform;  | 
3585  | 0  |     psInfo->dfTime = CPLAtof(CSLFetchNameValueDef(  | 
3586  | 0  |         papszOptions, "COORDINATE_EPOCH",  | 
3587  | 0  |         CSLFetchNameValueDef(  | 
3588  | 0  |             papszOptions, "DST_COORDINATE_EPOCH",  | 
3589  | 0  |             CSLFetchNameValueDef(papszOptions, "SRC_COORDINATE_EPOCH", "0"))));  | 
3590  | 0  |     psInfo->poReverseTransform = poForwardTransform->GetInverse();  | 
3591  |  | 
  | 
3592  | 0  |     if (psInfo->poReverseTransform)  | 
3593  | 0  |         psInfo->poReverseTransform->SetEmitErrors(false);  | 
3594  |  | 
  | 
3595  | 0  |     memcpy(psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,  | 
3596  | 0  |            strlen(GDAL_GTI2_SIGNATURE));  | 
3597  | 0  |     psInfo->sTI.pszClassName = GDAL_REPROJECTION_TRANSFORMER_CLASS_NAME;  | 
3598  | 0  |     psInfo->sTI.pfnTransform = GDALReprojectionTransform;  | 
3599  | 0  |     psInfo->sTI.pfnCleanup = GDALDestroyReprojectionTransformer;  | 
3600  | 0  |     psInfo->sTI.pfnSerialize = GDALSerializeReprojectionTransformer;  | 
3601  |  | 
  | 
3602  | 0  |     return psInfo;  | 
3603  | 0  | }  | 
3604  |  |  | 
3605  |  | /************************************************************************/  | 
3606  |  | /*                 GDALDestroyReprojectionTransformer()                 */  | 
3607  |  | /************************************************************************/  | 
3608  |  |  | 
3609  |  | /**  | 
3610  |  |  * Destroy reprojection transformation.  | 
3611  |  |  *  | 
3612  |  |  * @param pTransformArg the transformation handle returned by  | 
3613  |  |  * GDALCreateReprojectionTransformer().  | 
3614  |  |  */  | 
3615  |  |  | 
3616  |  | void GDALDestroyReprojectionTransformer(void *pTransformArg)  | 
3617  |  |  | 
3618  | 0  | { | 
3619  | 0  |     if (pTransformArg == nullptr)  | 
3620  | 0  |         return;  | 
3621  |  |  | 
3622  | 0  |     GDALReprojectionTransformInfo *psInfo =  | 
3623  | 0  |         static_cast<GDALReprojectionTransformInfo *>(pTransformArg);  | 
3624  |  | 
  | 
3625  | 0  |     if (psInfo->poForwardTransform)  | 
3626  | 0  |         OGRCoordinateTransformation::DestroyCT(psInfo->poForwardTransform);  | 
3627  |  | 
  | 
3628  | 0  |     if (psInfo->poReverseTransform)  | 
3629  | 0  |         OGRCoordinateTransformation::DestroyCT(psInfo->poReverseTransform);  | 
3630  |  | 
  | 
3631  | 0  |     CSLDestroy(psInfo->papszOptions);  | 
3632  |  | 
  | 
3633  | 0  |     delete psInfo;  | 
3634  | 0  | }  | 
3635  |  |  | 
3636  |  | /************************************************************************/  | 
3637  |  | /*                     GDALReprojectionTransform()                      */  | 
3638  |  | /************************************************************************/  | 
3639  |  |  | 
3640  |  | /**  | 
3641  |  |  * Perform reprojection transformation.  | 
3642  |  |  *  | 
3643  |  |  * Actually performs the reprojection transformation described in  | 
3644  |  |  * GDALCreateReprojectionTransformer().  This function matches the  | 
3645  |  |  * GDALTransformerFunc() signature.  Details of the arguments are described  | 
3646  |  |  * there.  | 
3647  |  |  */  | 
3648  |  |  | 
3649  |  | int GDALReprojectionTransform(void *pTransformArg, int bDstToSrc,  | 
3650  |  |                               int nPointCount, double *padfX, double *padfY,  | 
3651  |  |                               double *padfZ, int *panSuccess)  | 
3652  |  |  | 
3653  | 0  | { | 
3654  | 0  |     GDALReprojectionTransformInfo *psInfo =  | 
3655  | 0  |         static_cast<GDALReprojectionTransformInfo *>(pTransformArg);  | 
3656  | 0  |     int bSuccess;  | 
3657  |  | 
  | 
3658  | 0  |     std::vector<double> adfTime;  | 
3659  | 0  |     double *padfT = nullptr;  | 
3660  | 0  |     if (psInfo->dfTime != 0.0 && nPointCount > 0)  | 
3661  | 0  |     { | 
3662  | 0  |         adfTime.resize(nPointCount, psInfo->dfTime);  | 
3663  | 0  |         padfT = &adfTime[0];  | 
3664  | 0  |     }  | 
3665  |  | 
  | 
3666  | 0  |     if (bDstToSrc)  | 
3667  | 0  |     { | 
3668  | 0  |         if (psInfo->poReverseTransform == nullptr)  | 
3669  | 0  |         { | 
3670  | 0  |             CPLError(  | 
3671  | 0  |                 CE_Failure, CPLE_AppDefined,  | 
3672  | 0  |                 "Inverse coordinate transformation cannot be instantiated");  | 
3673  | 0  |             if (panSuccess)  | 
3674  | 0  |             { | 
3675  | 0  |                 for (int i = 0; i < nPointCount; i++)  | 
3676  | 0  |                     panSuccess[i] = FALSE;  | 
3677  | 0  |             }  | 
3678  | 0  |             bSuccess = false;  | 
3679  | 0  |         }  | 
3680  | 0  |         else  | 
3681  | 0  |         { | 
3682  | 0  |             bSuccess = psInfo->poReverseTransform->Transform(  | 
3683  | 0  |                 nPointCount, padfX, padfY, padfZ, padfT, panSuccess);  | 
3684  | 0  |         }  | 
3685  | 0  |     }  | 
3686  | 0  |     else  | 
3687  | 0  |         bSuccess = psInfo->poForwardTransform->Transform(  | 
3688  | 0  |             nPointCount, padfX, padfY, padfZ, padfT, panSuccess);  | 
3689  |  | 
  | 
3690  | 0  |     return bSuccess;  | 
3691  | 0  | }  | 
3692  |  |  | 
3693  |  | /************************************************************************/  | 
3694  |  | /*                GDALSerializeReprojectionTransformer()                */  | 
3695  |  | /************************************************************************/  | 
3696  |  |  | 
3697  |  | static CPLXMLNode *GDALSerializeReprojectionTransformer(void *pTransformArg)  | 
3698  |  |  | 
3699  | 0  | { | 
3700  | 0  |     CPLXMLNode *psTree;  | 
3701  | 0  |     GDALReprojectionTransformInfo *psInfo =  | 
3702  | 0  |         static_cast<GDALReprojectionTransformInfo *>(pTransformArg);  | 
3703  |  | 
  | 
3704  | 0  |     psTree = CPLCreateXMLNode(nullptr, CXT_Element, "ReprojectionTransformer");  | 
3705  |  |  | 
3706  |  |     /* -------------------------------------------------------------------- */  | 
3707  |  |     /*      Handle SourceCS.                                                */  | 
3708  |  |     /* -------------------------------------------------------------------- */  | 
3709  | 0  |     const auto ExportToWkt = [](const OGRSpatialReference *poSRS)  | 
3710  | 0  |     { | 
3711  |  |         // Try first in WKT1 for backward compat  | 
3712  | 0  |         { | 
3713  | 0  |             char *pszWKT = nullptr;  | 
3714  | 0  |             const char *const apszOptions[] = {"FORMAT=WKT1", nullptr}; | 
3715  | 0  |             CPLErrorHandlerPusher oHandler(CPLQuietErrorHandler);  | 
3716  | 0  |             CPLErrorStateBackuper oBackuper;  | 
3717  | 0  |             if (poSRS->exportToWkt(&pszWKT, apszOptions) == OGRERR_NONE)  | 
3718  | 0  |             { | 
3719  | 0  |                 std::string osRet(pszWKT);  | 
3720  | 0  |                 CPLFree(pszWKT);  | 
3721  | 0  |                 return osRet;  | 
3722  | 0  |             }  | 
3723  | 0  |             CPLFree(pszWKT);  | 
3724  | 0  |         }  | 
3725  |  |  | 
3726  | 0  |         char *pszWKT = nullptr;  | 
3727  | 0  |         const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr}; | 
3728  | 0  |         if (poSRS->exportToWkt(&pszWKT, apszOptions) == OGRERR_NONE)  | 
3729  | 0  |         { | 
3730  | 0  |             std::string osRet(pszWKT);  | 
3731  | 0  |             CPLFree(pszWKT);  | 
3732  | 0  |             return osRet;  | 
3733  | 0  |         }  | 
3734  | 0  |         CPLFree(pszWKT);  | 
3735  | 0  |         return std::string();  | 
3736  | 0  |     };  | 
3737  |  | 
  | 
3738  | 0  |     auto poSRS = psInfo->poForwardTransform->GetSourceCS();  | 
3739  | 0  |     if (poSRS)  | 
3740  | 0  |     { | 
3741  | 0  |         const auto osWKT = ExportToWkt(poSRS);  | 
3742  | 0  |         CPLCreateXMLElementAndValue(psTree, "SourceSRS", osWKT.c_str());  | 
3743  | 0  |     }  | 
3744  |  |  | 
3745  |  |     /* -------------------------------------------------------------------- */  | 
3746  |  |     /*      Handle DestinationCS.                                           */  | 
3747  |  |     /* -------------------------------------------------------------------- */  | 
3748  | 0  |     poSRS = psInfo->poForwardTransform->GetTargetCS();  | 
3749  | 0  |     if (poSRS)  | 
3750  | 0  |     { | 
3751  | 0  |         const auto osWKT = ExportToWkt(poSRS);  | 
3752  | 0  |         CPLCreateXMLElementAndValue(psTree, "TargetSRS", osWKT.c_str());  | 
3753  | 0  |     }  | 
3754  |  |  | 
3755  |  |     /* -------------------------------------------------------------------- */  | 
3756  |  |     /*      Serialize options.                                              */  | 
3757  |  |     /* -------------------------------------------------------------------- */  | 
3758  | 0  |     if (psInfo->papszOptions)  | 
3759  | 0  |     { | 
3760  | 0  |         CPLXMLNode *psOptions =  | 
3761  | 0  |             CPLCreateXMLNode(psTree, CXT_Element, "Options");  | 
3762  | 0  |         for (auto iter = psInfo->papszOptions; *iter != nullptr; ++iter)  | 
3763  | 0  |         { | 
3764  | 0  |             char *pszKey = nullptr;  | 
3765  | 0  |             const char *pszValue = CPLParseNameValue(*iter, &pszKey);  | 
3766  | 0  |             if (pszKey && pszValue)  | 
3767  | 0  |             { | 
3768  | 0  |                 auto elt =  | 
3769  | 0  |                     CPLCreateXMLElementAndValue(psOptions, "Option", pszValue);  | 
3770  | 0  |                 CPLAddXMLAttributeAndValue(elt, "key", pszKey);  | 
3771  | 0  |             }  | 
3772  | 0  |             CPLFree(pszKey);  | 
3773  | 0  |         }  | 
3774  | 0  |     }  | 
3775  |  | 
  | 
3776  | 0  |     return psTree;  | 
3777  | 0  | }  | 
3778  |  |  | 
3779  |  | /************************************************************************/  | 
3780  |  | /*               GDALDeserializeReprojectionTransformer()               */  | 
3781  |  | /************************************************************************/  | 
3782  |  |  | 
3783  |  | static void *GDALDeserializeReprojectionTransformer(CPLXMLNode *psTree)  | 
3784  |  |  | 
3785  | 0  | { | 
3786  | 0  |     const char *pszSourceSRS = CPLGetXMLValue(psTree, "SourceSRS", nullptr);  | 
3787  | 0  |     const char *pszTargetSRS = CPLGetXMLValue(psTree, "TargetSRS", nullptr);  | 
3788  | 0  |     char *pszSourceWKT = nullptr, *pszTargetWKT = nullptr;  | 
3789  | 0  |     void *pResult = nullptr;  | 
3790  |  | 
  | 
3791  | 0  |     OGRSpatialReference oSrcSRS;  | 
3792  | 0  |     OGRSpatialReference oDstSRS;  | 
3793  |  | 
  | 
3794  | 0  |     oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
3795  | 0  |     oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
3796  | 0  |     if (pszSourceSRS != nullptr)  | 
3797  | 0  |     { | 
3798  | 0  |         oSrcSRS.SetFromUserInput(pszSourceSRS);  | 
3799  | 0  |     }  | 
3800  |  | 
  | 
3801  | 0  |     if (pszTargetSRS != nullptr)  | 
3802  | 0  |     { | 
3803  | 0  |         oDstSRS.SetFromUserInput(pszTargetSRS);  | 
3804  | 0  |     }  | 
3805  |  | 
  | 
3806  | 0  |     CPLStringList aosList;  | 
3807  | 0  |     const CPLXMLNode *psOptions = CPLGetXMLNode(psTree, "Options");  | 
3808  | 0  |     if (psOptions)  | 
3809  | 0  |     { | 
3810  | 0  |         for (auto iter = psOptions->psChild; iter; iter = iter->psNext)  | 
3811  | 0  |         { | 
3812  | 0  |             if (iter->eType == CXT_Element &&  | 
3813  | 0  |                 strcmp(iter->pszValue, "Option") == 0)  | 
3814  | 0  |             { | 
3815  | 0  |                 const char *pszKey = CPLGetXMLValue(iter, "key", nullptr);  | 
3816  | 0  |                 const char *pszValue = CPLGetXMLValue(iter, nullptr, nullptr);  | 
3817  | 0  |                 if (pszKey && pszValue)  | 
3818  | 0  |                 { | 
3819  | 0  |                     aosList.SetNameValue(pszKey, pszValue);  | 
3820  | 0  |                 }  | 
3821  | 0  |             }  | 
3822  | 0  |         }  | 
3823  | 0  |     }  | 
3824  |  | 
  | 
3825  | 0  |     pResult = GDALCreateReprojectionTransformerEx(  | 
3826  | 0  |         !oSrcSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oSrcSRS) : nullptr,  | 
3827  | 0  |         !oDstSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oDstSRS) : nullptr,  | 
3828  | 0  |         aosList.List());  | 
3829  |  | 
  | 
3830  | 0  |     CPLFree(pszSourceWKT);  | 
3831  | 0  |     CPLFree(pszTargetWKT);  | 
3832  |  | 
  | 
3833  | 0  |     return pResult;  | 
3834  | 0  | }  | 
3835  |  |  | 
3836  |  | /************************************************************************/  | 
3837  |  | /* ==================================================================== */  | 
3838  |  | /*      Approximate transformer.                                        */  | 
3839  |  | /* ==================================================================== */  | 
3840  |  | /************************************************************************/  | 
3841  |  |  | 
3842  |  | /************************************************************************/  | 
3843  |  | /*                  GDALCreateSimilarApproxTransformer()                */  | 
3844  |  | /************************************************************************/  | 
3845  |  |  | 
3846  |  | static void *GDALCreateSimilarApproxTransformer(void *hTransformArg,  | 
3847  |  |                                                 double dfSrcRatioX,  | 
3848  |  |                                                 double dfSrcRatioY)  | 
3849  | 0  | { | 
3850  | 0  |     VALIDATE_POINTER1(hTransformArg, "GDALCreateSimilarApproxTransformer",  | 
3851  | 0  |                       nullptr);  | 
3852  |  |  | 
3853  | 0  |     GDALApproxTransformInfo *psInfo =  | 
3854  | 0  |         static_cast<GDALApproxTransformInfo *>(hTransformArg);  | 
3855  |  | 
  | 
3856  | 0  |     void *pBaseCBData = GDALCreateSimilarTransformer(psInfo->pBaseCBData,  | 
3857  | 0  |                                                      dfSrcRatioX, dfSrcRatioY);  | 
3858  | 0  |     if (pBaseCBData == nullptr)  | 
3859  | 0  |     { | 
3860  | 0  |         return nullptr;  | 
3861  | 0  |     }  | 
3862  |  |  | 
3863  | 0  |     GDALApproxTransformInfo *psClonedInfo =  | 
3864  | 0  |         static_cast<GDALApproxTransformInfo *>(GDALCreateApproxTransformer2(  | 
3865  | 0  |             psInfo->pfnBaseTransformer, pBaseCBData, psInfo->dfMaxErrorForward,  | 
3866  | 0  |             psInfo->dfMaxErrorReverse));  | 
3867  | 0  |     psClonedInfo->bOwnSubtransformer = TRUE;  | 
3868  |  | 
  | 
3869  | 0  |     return psClonedInfo;  | 
3870  | 0  | }  | 
3871  |  |  | 
3872  |  | /************************************************************************/  | 
3873  |  | /*                   GDALSerializeApproxTransformer()                   */  | 
3874  |  | /************************************************************************/  | 
3875  |  |  | 
3876  |  | static CPLXMLNode *GDALSerializeApproxTransformer(void *pTransformArg)  | 
3877  |  |  | 
3878  | 0  | { | 
3879  | 0  |     CPLXMLNode *psTree;  | 
3880  | 0  |     GDALApproxTransformInfo *psInfo =  | 
3881  | 0  |         static_cast<GDALApproxTransformInfo *>(pTransformArg);  | 
3882  |  | 
  | 
3883  | 0  |     psTree = CPLCreateXMLNode(nullptr, CXT_Element, "ApproxTransformer");  | 
3884  |  |  | 
3885  |  |     /* -------------------------------------------------------------------- */  | 
3886  |  |     /*      Attach max error.                                               */  | 
3887  |  |     /* -------------------------------------------------------------------- */  | 
3888  | 0  |     if (psInfo->dfMaxErrorForward == psInfo->dfMaxErrorReverse)  | 
3889  | 0  |     { | 
3890  | 0  |         CPLCreateXMLElementAndValue(  | 
3891  | 0  |             psTree, "MaxError",  | 
3892  | 0  |             CPLString().Printf("%g", psInfo->dfMaxErrorForward)); | 
3893  | 0  |     }  | 
3894  | 0  |     else  | 
3895  | 0  |     { | 
3896  | 0  |         CPLCreateXMLElementAndValue(  | 
3897  | 0  |             psTree, "MaxErrorForward",  | 
3898  | 0  |             CPLString().Printf("%g", psInfo->dfMaxErrorForward)); | 
3899  | 0  |         CPLCreateXMLElementAndValue(  | 
3900  | 0  |             psTree, "MaxErrorReverse",  | 
3901  | 0  |             CPLString().Printf("%g", psInfo->dfMaxErrorReverse)); | 
3902  | 0  |     }  | 
3903  |  |  | 
3904  |  |     /* -------------------------------------------------------------------- */  | 
3905  |  |     /*      Capture underlying transformer.                                 */  | 
3906  |  |     /* -------------------------------------------------------------------- */  | 
3907  | 0  |     CPLXMLNode *psTransformerContainer =  | 
3908  | 0  |         CPLCreateXMLNode(psTree, CXT_Element, "BaseTransformer");  | 
3909  |  | 
  | 
3910  | 0  |     CPLXMLNode *psTransformer = GDALSerializeTransformer(  | 
3911  | 0  |         psInfo->pfnBaseTransformer, psInfo->pBaseCBData);  | 
3912  | 0  |     if (psTransformer != nullptr)  | 
3913  | 0  |         CPLAddXMLChild(psTransformerContainer, psTransformer);  | 
3914  |  | 
  | 
3915  | 0  |     return psTree;  | 
3916  | 0  | }  | 
3917  |  |  | 
3918  |  | /************************************************************************/  | 
3919  |  | /*                    GDALCreateApproxTransformer()                     */  | 
3920  |  | /************************************************************************/  | 
3921  |  |  | 
3922  |  | /**  | 
3923  |  |  * Create an approximating transformer.  | 
3924  |  |  *  | 
3925  |  |  * This function creates a context for an approximated transformer.  Basically  | 
3926  |  |  * a high precision transformer is supplied as input and internally linear  | 
3927  |  |  * approximations are computed to generate results to within a defined  | 
3928  |  |  * precision.  | 
3929  |  |  *  | 
3930  |  |  * The approximation is actually done at the point where GDALApproxTransform()  | 
3931  |  |  * calls are made, and depend on the assumption that they are roughly linear.  | 
3932  |  |  * The first and last point passed in must be the extreme values and the  | 
3933  |  |  * intermediate values should describe a curve between the end points.  The  | 
3934  |  |  * approximator transforms and centers using the approximate transformer, and  | 
3935  |  |  * then compares the true middle transformed value to a linear approximation  | 
3936  |  |  * based on the end points.  If the error is within the supplied threshold then  | 
3937  |  |  * the end points are used to linearly approximate all the values otherwise the  | 
3938  |  |  * input points are split into two smaller sets, and the function is recursively  | 
3939  |  |  * called until a sufficiently small set of points is found that the linear  | 
3940  |  |  * approximation is OK, or that all the points are exactly computed.  | 
3941  |  |  *  | 
3942  |  |  * This function is very suitable for approximating transformation results  | 
3943  |  |  * from output pixel/line space to input coordinates for warpers that operate  | 
3944  |  |  * on one input scanline at a time.  Care should be taken using it in other  | 
3945  |  |  * circumstances as little internal validation is done in order to keep things  | 
3946  |  |  * fast.  | 
3947  |  |  *  | 
3948  |  |  * @param pfnBaseTransformer the high precision transformer which should be  | 
3949  |  |  * approximated.  | 
3950  |  |  * @param pBaseTransformArg the callback argument for the high precision  | 
3951  |  |  * transformer.  | 
3952  |  |  * @param dfMaxError the maximum cartesian error in the "output" space that  | 
3953  |  |  * is to be accepted in the linear approximation, evaluated as a Manhattan  | 
3954  |  |  * distance.  | 
3955  |  |  *  | 
3956  |  |  * @return callback pointer suitable for use with GDALApproxTransform().  It  | 
3957  |  |  * should be deallocated with GDALDestroyApproxTransformer().  | 
3958  |  |  */  | 
3959  |  |  | 
3960  |  | void *GDALCreateApproxTransformer(GDALTransformerFunc pfnBaseTransformer,  | 
3961  |  |                                   void *pBaseTransformArg, double dfMaxError)  | 
3962  |  |  | 
3963  | 0  | { | 
3964  | 0  |     return GDALCreateApproxTransformer2(pfnBaseTransformer, pBaseTransformArg,  | 
3965  | 0  |                                         dfMaxError, dfMaxError);  | 
3966  | 0  | }  | 
3967  |  |  | 
3968  |  | static void *  | 
3969  |  | GDALCreateApproxTransformer2(GDALTransformerFunc pfnBaseTransformer,  | 
3970  |  |                              void *pBaseTransformArg, double dfMaxErrorForward,  | 
3971  |  |                              double dfMaxErrorReverse)  | 
3972  |  |  | 
3973  | 0  | { | 
3974  | 0  |     GDALApproxTransformInfo *psATInfo = new GDALApproxTransformInfo;  | 
3975  | 0  |     psATInfo->pfnBaseTransformer = pfnBaseTransformer;  | 
3976  | 0  |     psATInfo->pBaseCBData = pBaseTransformArg;  | 
3977  | 0  |     psATInfo->dfMaxErrorForward = dfMaxErrorForward;  | 
3978  | 0  |     psATInfo->dfMaxErrorReverse = dfMaxErrorReverse;  | 
3979  | 0  |     psATInfo->bOwnSubtransformer = FALSE;  | 
3980  |  | 
  | 
3981  | 0  |     memcpy(psATInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,  | 
3982  | 0  |            strlen(GDAL_GTI2_SIGNATURE));  | 
3983  | 0  |     psATInfo->sTI.pszClassName = GDAL_APPROX_TRANSFORMER_CLASS_NAME;  | 
3984  | 0  |     psATInfo->sTI.pfnTransform = GDALApproxTransform;  | 
3985  | 0  |     psATInfo->sTI.pfnCleanup = GDALDestroyApproxTransformer;  | 
3986  | 0  |     psATInfo->sTI.pfnSerialize = GDALSerializeApproxTransformer;  | 
3987  | 0  |     psATInfo->sTI.pfnCreateSimilar = GDALCreateSimilarApproxTransformer;  | 
3988  |  | 
  | 
3989  | 0  |     return psATInfo;  | 
3990  | 0  | }  | 
3991  |  |  | 
3992  |  | /************************************************************************/  | 
3993  |  | /*              GDALApproxTransformerOwnsSubtransformer()               */  | 
3994  |  | /************************************************************************/  | 
3995  |  |  | 
3996  |  | /** Set bOwnSubtransformer flag */  | 
3997  |  | void GDALApproxTransformerOwnsSubtransformer(void *pCBData, int bOwnFlag)  | 
3998  |  |  | 
3999  | 0  | { | 
4000  | 0  |     GDALApproxTransformInfo *psATInfo =  | 
4001  | 0  |         static_cast<GDALApproxTransformInfo *>(pCBData);  | 
4002  |  | 
  | 
4003  | 0  |     psATInfo->bOwnSubtransformer = bOwnFlag;  | 
4004  | 0  | }  | 
4005  |  |  | 
4006  |  | /************************************************************************/  | 
4007  |  | /*                    GDALDestroyApproxTransformer()                    */  | 
4008  |  | /************************************************************************/  | 
4009  |  |  | 
4010  |  | /**  | 
4011  |  |  * Cleanup approximate transformer.  | 
4012  |  |  *  | 
4013  |  |  * Deallocates the resources allocated by GDALCreateApproxTransformer().  | 
4014  |  |  *  | 
4015  |  |  * @param pCBData callback data originally returned by  | 
4016  |  |  * GDALCreateApproxTransformer().  | 
4017  |  |  */  | 
4018  |  |  | 
4019  |  | void GDALDestroyApproxTransformer(void *pCBData)  | 
4020  |  |  | 
4021  | 0  | { | 
4022  | 0  |     if (pCBData == nullptr)  | 
4023  | 0  |         return;  | 
4024  |  |  | 
4025  | 0  |     GDALApproxTransformInfo *psATInfo =  | 
4026  | 0  |         static_cast<GDALApproxTransformInfo *>(pCBData);  | 
4027  |  | 
  | 
4028  | 0  |     if (psATInfo->bOwnSubtransformer)  | 
4029  | 0  |         GDALDestroyTransformer(psATInfo->pBaseCBData);  | 
4030  |  | 
  | 
4031  | 0  |     delete psATInfo;  | 
4032  | 0  | }  | 
4033  |  |  | 
4034  |  | /************************************************************************/  | 
4035  |  | /*                  GDALRefreshApproxTransformer()                      */  | 
4036  |  | /************************************************************************/  | 
4037  |  |  | 
4038  |  | void GDALRefreshApproxTransformer(void *hTransformArg)  | 
4039  | 0  | { | 
4040  | 0  |     GDALApproxTransformInfo *psInfo =  | 
4041  | 0  |         static_cast<GDALApproxTransformInfo *>(hTransformArg);  | 
4042  |  | 
  | 
4043  | 0  |     if (GDALIsTransformer(psInfo->pBaseCBData,  | 
4044  | 0  |                           GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))  | 
4045  | 0  |     { | 
4046  | 0  |         GDALRefreshGenImgProjTransformer(psInfo->pBaseCBData);  | 
4047  | 0  |     }  | 
4048  | 0  | }  | 
4049  |  |  | 
4050  |  | /************************************************************************/  | 
4051  |  | /*                      GDALApproxTransformInternal()                   */  | 
4052  |  | /************************************************************************/  | 
4053  |  |  | 
4054  |  | static int GDALApproxTransformInternal(void *pCBData, int bDstToSrc,  | 
4055  |  |                                        int nPoints, double *x, double *y,  | 
4056  |  |                                        double *z, int *panSuccess,  | 
4057  |  |                                        // SME = Start, Middle, End.  | 
4058  |  |                                        const double xSMETransformed[3],  | 
4059  |  |                                        const double ySMETransformed[3],  | 
4060  |  |                                        const double zSMETransformed[3])  | 
4061  | 0  | { | 
4062  | 0  |     GDALApproxTransformInfo *psATInfo =  | 
4063  | 0  |         static_cast<GDALApproxTransformInfo *>(pCBData);  | 
4064  | 0  |     const int nMiddle = (nPoints - 1) / 2;  | 
4065  |  | 
  | 
4066  |  | #ifdef notdef_sanify_check  | 
4067  |  |     { | 
4068  |  |         double x2[3] = {x[0], x[nMiddle], x[nPoints - 1]}; | 
4069  |  |         double y2[3] = {y[0], y[nMiddle], y[nPoints - 1]}; | 
4070  |  |         double z2[3] = {z[0], z[nMiddle], z[nPoints - 1]}; | 
4071  |  |         int anSuccess2[3] = {}; | 
4072  |  |  | 
4073  |  |         const int bSuccess = psATInfo->pfnBaseTransformer(  | 
4074  |  |             psATInfo->pBaseCBData, bDstToSrc, 3, x2, y2, z2, anSuccess2);  | 
4075  |  |         CPLAssert(bSuccess);  | 
4076  |  |         CPLAssert(anSuccess2[0]);  | 
4077  |  |         CPLAssert(anSuccess2[1]);  | 
4078  |  |         CPLAssert(anSuccess2[2]);  | 
4079  |  |         CPLAssert(x2[0] == xSMETransformed[0]);  | 
4080  |  |         CPLAssert(y2[0] == ySMETransformed[0]);  | 
4081  |  |         CPLAssert(z2[0] == zSMETransformed[0]);  | 
4082  |  |         CPLAssert(x2[1] == xSMETransformed[1]);  | 
4083  |  |         CPLAssert(y2[1] == ySMETransformed[1]);  | 
4084  |  |         CPLAssert(z2[1] == zSMETransformed[1]);  | 
4085  |  |         CPLAssert(x2[2] == xSMETransformed[2]);  | 
4086  |  |         CPLAssert(y2[2] == ySMETransformed[2]);  | 
4087  |  |         CPLAssert(z2[2] == zSMETransformed[2]);  | 
4088  |  |     }  | 
4089  |  | #endif  | 
4090  |  | 
  | 
4091  |  | #ifdef DEBUG_APPROX_TRANSFORMER  | 
4092  |  |     fprintf(stderr, "start (%.3f,%.3f) -> (%.3f,%.3f)\n", /*ok*/  | 
4093  |  |             x[0], y[0], xSMETransformed[0], ySMETransformed[0]);  | 
4094  |  |     fprintf(stderr, "middle (%.3f,%.3f) -> (%.3f,%.3f)\n", /*ok*/  | 
4095  |  |             x[nMiddle], y[nMiddle], xSMETransformed[1], ySMETransformed[1]);  | 
4096  |  |     fprintf(stderr, "end (%.3f,%.3f) -> (%.3f,%.3f)\n", /*ok*/  | 
4097  |  |             x[nPoints - 1], y[nPoints - 1], xSMETransformed[2],  | 
4098  |  |             ySMETransformed[2]);  | 
4099  |  | #endif  | 
4100  |  |  | 
4101  |  |     /* -------------------------------------------------------------------- */  | 
4102  |  |     /*      Is the error at the middle acceptable relative to an            */  | 
4103  |  |     /*      interpolation of the middle position?                           */  | 
4104  |  |     /* -------------------------------------------------------------------- */  | 
4105  | 0  |     const double dfDeltaX =  | 
4106  | 0  |         (xSMETransformed[2] - xSMETransformed[0]) / (x[nPoints - 1] - x[0]);  | 
4107  | 0  |     const double dfDeltaY =  | 
4108  | 0  |         (ySMETransformed[2] - ySMETransformed[0]) / (x[nPoints - 1] - x[0]);  | 
4109  | 0  |     const double dfDeltaZ =  | 
4110  | 0  |         (zSMETransformed[2] - zSMETransformed[0]) / (x[nPoints - 1] - x[0]);  | 
4111  |  | 
  | 
4112  | 0  |     const double dfError =  | 
4113  | 0  |         fabs((xSMETransformed[0] + dfDeltaX * (x[nMiddle] - x[0])) -  | 
4114  | 0  |              xSMETransformed[1]) +  | 
4115  | 0  |         fabs((ySMETransformed[0] + dfDeltaY * (x[nMiddle] - x[0])) -  | 
4116  | 0  |              ySMETransformed[1]);  | 
4117  |  | 
  | 
4118  | 0  |     const double dfMaxError =  | 
4119  | 0  |         (bDstToSrc) ? psATInfo->dfMaxErrorReverse : psATInfo->dfMaxErrorForward;  | 
4120  | 0  |     if (dfError > dfMaxError)  | 
4121  | 0  |     { | 
4122  |  | #if DEBUG_VERBOSE  | 
4123  |  |         CPLDebug("GDAL", | 
4124  |  |                  "ApproxTransformer - "  | 
4125  |  |                  "error %g over threshold %g, subdivide %d points.",  | 
4126  |  |                  dfError, dfMaxError, nPoints);  | 
4127  |  | #endif  | 
4128  |  | 
  | 
4129  | 0  |         double xMiddle[3] = {x[(nMiddle - 1) / 2], x[nMiddle - 1], | 
4130  | 0  |                              x[nMiddle + (nPoints - nMiddle - 1) / 2]};  | 
4131  | 0  |         double yMiddle[3] = {y[(nMiddle - 1) / 2], y[nMiddle - 1], | 
4132  | 0  |                              y[nMiddle + (nPoints - nMiddle - 1) / 2]};  | 
4133  | 0  |         double zMiddle[3] = {z[(nMiddle - 1) / 2], z[nMiddle - 1], | 
4134  | 0  |                              z[nMiddle + (nPoints - nMiddle - 1) / 2]};  | 
4135  |  | 
  | 
4136  | 0  |         const bool bUseBaseTransformForHalf1 =  | 
4137  | 0  |             nMiddle <= 5 || y[0] != y[nMiddle - 1] ||  | 
4138  | 0  |             y[0] != y[(nMiddle - 1) / 2] || x[0] == x[nMiddle - 1] ||  | 
4139  | 0  |             x[0] == x[(nMiddle - 1) / 2];  | 
4140  | 0  |         const bool bUseBaseTransformForHalf2 =  | 
4141  | 0  |             nPoints - nMiddle <= 5 || y[nMiddle] != y[nPoints - 1] ||  | 
4142  | 0  |             y[nMiddle] != y[nMiddle + (nPoints - nMiddle - 1) / 2] ||  | 
4143  | 0  |             x[nMiddle] == x[nPoints - 1] ||  | 
4144  | 0  |             x[nMiddle] == x[nMiddle + (nPoints - nMiddle - 1) / 2];  | 
4145  |  | 
  | 
4146  | 0  |         int anSuccess2[3] = {}; | 
4147  | 0  |         int bSuccess = FALSE;  | 
4148  | 0  |         if (!bUseBaseTransformForHalf1 && !bUseBaseTransformForHalf2)  | 
4149  | 0  |             bSuccess = psATInfo->pfnBaseTransformer(  | 
4150  | 0  |                 psATInfo->pBaseCBData, bDstToSrc, 3, xMiddle, yMiddle, zMiddle,  | 
4151  | 0  |                 anSuccess2);  | 
4152  | 0  |         else if (!bUseBaseTransformForHalf1)  | 
4153  | 0  |         { | 
4154  | 0  |             bSuccess = psATInfo->pfnBaseTransformer(  | 
4155  | 0  |                 psATInfo->pBaseCBData, bDstToSrc, 2, xMiddle, yMiddle, zMiddle,  | 
4156  | 0  |                 anSuccess2);  | 
4157  | 0  |             anSuccess2[2] = TRUE;  | 
4158  | 0  |         }  | 
4159  | 0  |         else if (!bUseBaseTransformForHalf2)  | 
4160  | 0  |         { | 
4161  | 0  |             bSuccess = psATInfo->pfnBaseTransformer(  | 
4162  | 0  |                 psATInfo->pBaseCBData, bDstToSrc, 1, xMiddle + 2, yMiddle + 2,  | 
4163  | 0  |                 zMiddle + 2, anSuccess2 + 2);  | 
4164  | 0  |             anSuccess2[0] = TRUE;  | 
4165  | 0  |             anSuccess2[1] = TRUE;  | 
4166  | 0  |         }  | 
4167  |  | 
  | 
4168  | 0  |         if (!bSuccess || !anSuccess2[0] || !anSuccess2[1] || !anSuccess2[2])  | 
4169  | 0  |         { | 
4170  | 0  |             bSuccess = psATInfo->pfnBaseTransformer(  | 
4171  | 0  |                 psATInfo->pBaseCBData, bDstToSrc, nMiddle - 1, x + 1, y + 1,  | 
4172  | 0  |                 z + 1, panSuccess + 1);  | 
4173  | 0  |             bSuccess &= psATInfo->pfnBaseTransformer(  | 
4174  | 0  |                 psATInfo->pBaseCBData, bDstToSrc, nPoints - nMiddle - 2,  | 
4175  | 0  |                 x + nMiddle + 1, y + nMiddle + 1, z + nMiddle + 1,  | 
4176  | 0  |                 panSuccess + nMiddle + 1);  | 
4177  |  | 
  | 
4178  | 0  |             x[0] = xSMETransformed[0];  | 
4179  | 0  |             y[0] = ySMETransformed[0];  | 
4180  | 0  |             z[0] = zSMETransformed[0];  | 
4181  | 0  |             panSuccess[0] = TRUE;  | 
4182  | 0  |             x[nMiddle] = xSMETransformed[1];  | 
4183  | 0  |             y[nMiddle] = ySMETransformed[1];  | 
4184  | 0  |             z[nMiddle] = zSMETransformed[1];  | 
4185  | 0  |             panSuccess[nMiddle] = TRUE;  | 
4186  | 0  |             x[nPoints - 1] = xSMETransformed[2];  | 
4187  | 0  |             y[nPoints - 1] = ySMETransformed[2];  | 
4188  | 0  |             z[nPoints - 1] = zSMETransformed[2];  | 
4189  | 0  |             panSuccess[nPoints - 1] = TRUE;  | 
4190  | 0  |             return bSuccess;  | 
4191  | 0  |         }  | 
4192  |  |  | 
4193  | 0  |         double x2[3] = {}; | 
4194  | 0  |         double y2[3] = {}; | 
4195  | 0  |         double z2[3] = {}; | 
4196  | 0  |         if (!bUseBaseTransformForHalf1)  | 
4197  | 0  |         { | 
4198  | 0  |             x2[0] = xSMETransformed[0];  | 
4199  | 0  |             y2[0] = ySMETransformed[0];  | 
4200  | 0  |             z2[0] = zSMETransformed[0];  | 
4201  | 0  |             x2[1] = xMiddle[0];  | 
4202  | 0  |             y2[1] = yMiddle[0];  | 
4203  | 0  |             z2[1] = zMiddle[0];  | 
4204  | 0  |             x2[2] = xMiddle[1];  | 
4205  | 0  |             y2[2] = yMiddle[1];  | 
4206  | 0  |             z2[2] = zMiddle[1];  | 
4207  |  | 
  | 
4208  | 0  |             bSuccess = GDALApproxTransformInternal(  | 
4209  | 0  |                 psATInfo, bDstToSrc, nMiddle, x, y, z, panSuccess, x2, y2, z2);  | 
4210  | 0  |         }  | 
4211  | 0  |         else  | 
4212  | 0  |         { | 
4213  | 0  |             bSuccess = psATInfo->pfnBaseTransformer(  | 
4214  | 0  |                 psATInfo->pBaseCBData, bDstToSrc, nMiddle - 1, x + 1, y + 1,  | 
4215  | 0  |                 z + 1, panSuccess + 1);  | 
4216  | 0  |             x[0] = xSMETransformed[0];  | 
4217  | 0  |             y[0] = ySMETransformed[0];  | 
4218  | 0  |             z[0] = zSMETransformed[0];  | 
4219  | 0  |             panSuccess[0] = TRUE;  | 
4220  | 0  |         }  | 
4221  |  | 
  | 
4222  | 0  |         if (!bSuccess)  | 
4223  | 0  |             return FALSE;  | 
4224  |  |  | 
4225  | 0  |         if (!bUseBaseTransformForHalf2)  | 
4226  | 0  |         { | 
4227  | 0  |             x2[0] = xSMETransformed[1];  | 
4228  | 0  |             y2[0] = ySMETransformed[1];  | 
4229  | 0  |             z2[0] = zSMETransformed[1];  | 
4230  | 0  |             x2[1] = xMiddle[2];  | 
4231  | 0  |             y2[1] = yMiddle[2];  | 
4232  | 0  |             z2[1] = zMiddle[2];  | 
4233  | 0  |             x2[2] = xSMETransformed[2];  | 
4234  | 0  |             y2[2] = ySMETransformed[2];  | 
4235  | 0  |             z2[2] = zSMETransformed[2];  | 
4236  |  | 
  | 
4237  | 0  |             bSuccess = GDALApproxTransformInternal(  | 
4238  | 0  |                 psATInfo, bDstToSrc, nPoints - nMiddle, x + nMiddle,  | 
4239  | 0  |                 y + nMiddle, z + nMiddle, panSuccess + nMiddle, x2, y2, z2);  | 
4240  | 0  |         }  | 
4241  | 0  |         else  | 
4242  | 0  |         { | 
4243  | 0  |             bSuccess = psATInfo->pfnBaseTransformer(  | 
4244  | 0  |                 psATInfo->pBaseCBData, bDstToSrc, nPoints - nMiddle - 2,  | 
4245  | 0  |                 x + nMiddle + 1, y + nMiddle + 1, z + nMiddle + 1,  | 
4246  | 0  |                 panSuccess + nMiddle + 1);  | 
4247  |  | 
  | 
4248  | 0  |             x[nMiddle] = xSMETransformed[1];  | 
4249  | 0  |             y[nMiddle] = ySMETransformed[1];  | 
4250  | 0  |             z[nMiddle] = zSMETransformed[1];  | 
4251  | 0  |             panSuccess[nMiddle] = TRUE;  | 
4252  | 0  |             x[nPoints - 1] = xSMETransformed[2];  | 
4253  | 0  |             y[nPoints - 1] = ySMETransformed[2];  | 
4254  | 0  |             z[nPoints - 1] = zSMETransformed[2];  | 
4255  | 0  |             panSuccess[nPoints - 1] = TRUE;  | 
4256  | 0  |         }  | 
4257  |  | 
  | 
4258  | 0  |         if (!bSuccess)  | 
4259  | 0  |             return FALSE;  | 
4260  |  |  | 
4261  | 0  |         return TRUE;  | 
4262  | 0  |     }  | 
4263  |  |  | 
4264  |  |     /* -------------------------------------------------------------------- */  | 
4265  |  |     /*      Error is OK since this is just used to compute output bounds    */  | 
4266  |  |     /*      of newly created file for gdalwarper.  So just use affine       */  | 
4267  |  |     /*      approximation of the reverse transform.  Eventually we          */  | 
4268  |  |     /*      should implement iterative searching to find a result within    */  | 
4269  |  |     /*      our error threshold.                                            */  | 
4270  |  |     /*      NOTE: the above comment is not true: gdalwarp uses approximator */  | 
4271  |  |     /*      also to compute the source pixel of each target pixel.          */  | 
4272  |  |     /* -------------------------------------------------------------------- */  | 
4273  | 0  |     for (int i = nPoints - 1; i >= 0; i--)  | 
4274  | 0  |     { | 
4275  |  | #ifdef check_error  | 
4276  |  |         double xtemp = x[i];  | 
4277  |  |         double ytemp = y[i];  | 
4278  |  |         double ztemp = z[i];  | 
4279  |  |         double x_ori = xtemp;  | 
4280  |  |         double y_ori = ytemp;  | 
4281  |  |         int btemp = FALSE;  | 
4282  |  |         psATInfo->pfnBaseTransformer(psATInfo->pBaseCBData, bDstToSrc, 1,  | 
4283  |  |                                      &xtemp, &ytemp, &ztemp, &btemp);  | 
4284  |  | #endif  | 
4285  | 0  |         const double dfDist = (x[i] - x[0]);  | 
4286  | 0  |         x[i] = xSMETransformed[0] + dfDeltaX * dfDist;  | 
4287  | 0  |         y[i] = ySMETransformed[0] + dfDeltaY * dfDist;  | 
4288  | 0  |         z[i] = zSMETransformed[0] + dfDeltaZ * dfDist;  | 
4289  |  | #ifdef check_error  | 
4290  |  |         const double dfError2 = fabs(x[i] - xtemp) + fabs(y[i] - ytemp);  | 
4291  |  |         if (dfError2 > 4 /*10 * dfMaxError*/)  | 
4292  |  |         { | 
4293  |  |             /*ok*/ printf("Error = %f on (%f, %f)\n", dfError2, x_ori, y_ori); | 
4294  |  |         }  | 
4295  |  | #endif  | 
4296  | 0  |         panSuccess[i] = TRUE;  | 
4297  | 0  |     }  | 
4298  |  | 
  | 
4299  | 0  |     return TRUE;  | 
4300  | 0  | }  | 
4301  |  |  | 
4302  |  | /************************************************************************/  | 
4303  |  | /*                        GDALApproxTransform()                         */  | 
4304  |  | /************************************************************************/  | 
4305  |  |  | 
4306  |  | /**  | 
4307  |  |  * Perform approximate transformation.  | 
4308  |  |  *  | 
4309  |  |  * Actually performs the approximate transformation described in  | 
4310  |  |  * GDALCreateApproxTransformer().  This function matches the  | 
4311  |  |  * GDALTransformerFunc() signature.  Details of the arguments are described  | 
4312  |  |  * there.  | 
4313  |  |  */  | 
4314  |  |  | 
4315  |  | int GDALApproxTransform(void *pCBData, int bDstToSrc, int nPoints, double *x,  | 
4316  |  |                         double *y, double *z, int *panSuccess)  | 
4317  |  |  | 
4318  | 0  | { | 
4319  | 0  |     GDALApproxTransformInfo *psATInfo =  | 
4320  | 0  |         static_cast<GDALApproxTransformInfo *>(pCBData);  | 
4321  | 0  |     double x2[3] = {}; | 
4322  | 0  |     double y2[3] = {}; | 
4323  | 0  |     double z2[3] = {}; | 
4324  | 0  |     int anSuccess2[3] = {}; | 
4325  | 0  |     int bSuccess;  | 
4326  |  | 
  | 
4327  | 0  |     const int nMiddle = (nPoints - 1) / 2;  | 
4328  |  |  | 
4329  |  |     /* -------------------------------------------------------------------- */  | 
4330  |  |     /*      Bail if our preconditions are not met, or if error is not       */  | 
4331  |  |     /*      acceptable.                                                     */  | 
4332  |  |     /* -------------------------------------------------------------------- */  | 
4333  | 0  |     int bRet = FALSE;  | 
4334  | 0  |     if (y[0] != y[nPoints - 1] || y[0] != y[nMiddle] ||  | 
4335  | 0  |         x[0] == x[nPoints - 1] || x[0] == x[nMiddle] ||  | 
4336  | 0  |         (psATInfo->dfMaxErrorForward == 0.0 &&  | 
4337  | 0  |          psATInfo->dfMaxErrorReverse == 0.0) ||  | 
4338  | 0  |         nPoints <= 5)  | 
4339  | 0  |     { | 
4340  | 0  |         bRet = psATInfo->pfnBaseTransformer(psATInfo->pBaseCBData, bDstToSrc,  | 
4341  | 0  |                                             nPoints, x, y, z, panSuccess);  | 
4342  | 0  |         goto end;  | 
4343  | 0  |     }  | 
4344  |  |  | 
4345  |  |     /* -------------------------------------------------------------------- */  | 
4346  |  |     /*      Transform first, last and middle point.                         */  | 
4347  |  |     /* -------------------------------------------------------------------- */  | 
4348  | 0  |     x2[0] = x[0];  | 
4349  | 0  |     y2[0] = y[0];  | 
4350  | 0  |     z2[0] = z[0];  | 
4351  | 0  |     x2[1] = x[nMiddle];  | 
4352  | 0  |     y2[1] = y[nMiddle];  | 
4353  | 0  |     z2[1] = z[nMiddle];  | 
4354  | 0  |     x2[2] = x[nPoints - 1];  | 
4355  | 0  |     y2[2] = y[nPoints - 1];  | 
4356  | 0  |     z2[2] = z[nPoints - 1];  | 
4357  |  | 
  | 
4358  | 0  |     bSuccess = psATInfo->pfnBaseTransformer(psATInfo->pBaseCBData, bDstToSrc, 3,  | 
4359  | 0  |                                             x2, y2, z2, anSuccess2);  | 
4360  | 0  |     if (!bSuccess || !anSuccess2[0] || !anSuccess2[1] || !anSuccess2[2])  | 
4361  | 0  |     { | 
4362  | 0  |         bRet = psATInfo->pfnBaseTransformer(psATInfo->pBaseCBData, bDstToSrc,  | 
4363  | 0  |                                             nPoints, x, y, z, panSuccess);  | 
4364  | 0  |         goto end;  | 
4365  | 0  |     }  | 
4366  |  |  | 
4367  | 0  |     bRet = GDALApproxTransformInternal(pCBData, bDstToSrc, nPoints, x, y, z,  | 
4368  | 0  |                                        panSuccess, x2, y2, z2);  | 
4369  |  | 
  | 
4370  | 0  | end:  | 
4371  |  | #ifdef DEBUG_APPROX_TRANSFORMER  | 
4372  |  |     for (int i = 0; i < nPoints; i++)  | 
4373  |  |         fprintf(stderr, "[%d] (%.10f,%.10f) %d\n", /*ok*/  | 
4374  |  |                 i, x[i], y[i], panSuccess[i]);  | 
4375  |  | #endif  | 
4376  |  | 
  | 
4377  | 0  |     return bRet;  | 
4378  | 0  | }  | 
4379  |  |  | 
4380  |  | /************************************************************************/  | 
4381  |  | /*                  GDALDeserializeApproxTransformer()                  */  | 
4382  |  | /************************************************************************/  | 
4383  |  |  | 
4384  |  | static void *GDALDeserializeApproxTransformer(CPLXMLNode *psTree)  | 
4385  |  |  | 
4386  | 0  | { | 
4387  | 0  |     double dfMaxErrorForward = 0.25;  | 
4388  | 0  |     double dfMaxErrorReverse = 0.25;  | 
4389  | 0  |     const char *pszMaxError = CPLGetXMLValue(psTree, "MaxError", nullptr);  | 
4390  | 0  |     if (pszMaxError != nullptr)  | 
4391  | 0  |     { | 
4392  | 0  |         dfMaxErrorForward = CPLAtof(pszMaxError);  | 
4393  | 0  |         dfMaxErrorReverse = dfMaxErrorForward;  | 
4394  | 0  |     }  | 
4395  | 0  |     const char *pszMaxErrorForward =  | 
4396  | 0  |         CPLGetXMLValue(psTree, "MaxErrorForward", nullptr);  | 
4397  | 0  |     if (pszMaxErrorForward != nullptr)  | 
4398  | 0  |     { | 
4399  | 0  |         dfMaxErrorForward = CPLAtof(pszMaxErrorForward);  | 
4400  | 0  |     }  | 
4401  | 0  |     const char *pszMaxErrorReverse =  | 
4402  | 0  |         CPLGetXMLValue(psTree, "MaxErrorReverse", nullptr);  | 
4403  | 0  |     if (pszMaxErrorReverse != nullptr)  | 
4404  | 0  |     { | 
4405  | 0  |         dfMaxErrorReverse = CPLAtof(pszMaxErrorReverse);  | 
4406  | 0  |     }  | 
4407  |  | 
  | 
4408  | 0  |     GDALTransformerFunc pfnBaseTransform = nullptr;  | 
4409  | 0  |     void *pBaseCBData = nullptr;  | 
4410  |  | 
  | 
4411  | 0  |     CPLXMLNode *psContainer = CPLGetXMLNode(psTree, "BaseTransformer");  | 
4412  |  | 
  | 
4413  | 0  |     if (psContainer != nullptr && psContainer->psChild != nullptr)  | 
4414  | 0  |     { | 
4415  | 0  |         GDALDeserializeTransformer(psContainer->psChild, &pfnBaseTransform,  | 
4416  | 0  |                                    &pBaseCBData);  | 
4417  | 0  |     }  | 
4418  |  | 
  | 
4419  | 0  |     if (pfnBaseTransform == nullptr)  | 
4420  | 0  |     { | 
4421  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
4422  | 0  |                  "Cannot get base transform for approx transformer.");  | 
4423  | 0  |         return nullptr;  | 
4424  | 0  |     }  | 
4425  |  |  | 
4426  | 0  |     void *pApproxCBData = GDALCreateApproxTransformer2(  | 
4427  | 0  |         pfnBaseTransform, pBaseCBData, dfMaxErrorForward, dfMaxErrorReverse);  | 
4428  | 0  |     GDALApproxTransformerOwnsSubtransformer(pApproxCBData, TRUE);  | 
4429  |  | 
  | 
4430  | 0  |     return pApproxCBData;  | 
4431  | 0  | }  | 
4432  |  |  | 
4433  |  | /************************************************************************/  | 
4434  |  | /*                 GDALTransformLonLatToDestApproxTransformer()         */  | 
4435  |  | /************************************************************************/  | 
4436  |  |  | 
4437  |  | int GDALTransformLonLatToDestApproxTransformer(void *hTransformArg,  | 
4438  |  |                                                double *pdfX, double *pdfY)  | 
4439  | 0  | { | 
4440  | 0  |     GDALApproxTransformInfo *psInfo =  | 
4441  | 0  |         static_cast<GDALApproxTransformInfo *>(hTransformArg);  | 
4442  |  | 
  | 
4443  | 0  |     if (GDALIsTransformer(psInfo->pBaseCBData,  | 
4444  | 0  |                           GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))  | 
4445  | 0  |     { | 
4446  | 0  |         return GDALTransformLonLatToDestGenImgProjTransformer(  | 
4447  | 0  |             psInfo->pBaseCBData, pdfX, pdfY);  | 
4448  | 0  |     }  | 
4449  | 0  |     return false;  | 
4450  | 0  | }  | 
4451  |  |  | 
4452  |  | /************************************************************************/  | 
4453  |  | /*                       GDALApplyGeoTransform()                        */  | 
4454  |  | /************************************************************************/  | 
4455  |  |  | 
4456  |  | /**  | 
4457  |  |  * Apply GeoTransform to x/y coordinate.  | 
4458  |  |  *  | 
4459  |  |  * Applies the following computation, converting a (pixel, line) coordinate  | 
4460  |  |  * into a georeferenced (geo_x, geo_y) location.  | 
4461  |  |  * \code{.c} | 
4462  |  |  *  *pdfGeoX = padfGeoTransform[0] + dfPixel * padfGeoTransform[1]  | 
4463  |  |  *                                 + dfLine  * padfGeoTransform[2];  | 
4464  |  |  *  *pdfGeoY = padfGeoTransform[3] + dfPixel * padfGeoTransform[4]  | 
4465  |  |  *                                 + dfLine  * padfGeoTransform[5];  | 
4466  |  |  * \endcode  | 
4467  |  |  *  | 
4468  |  |  * @param padfGeoTransform Six coefficient GeoTransform to apply.  | 
4469  |  |  * @param dfPixel Input pixel position.  | 
4470  |  |  * @param dfLine Input line position.  | 
4471  |  |  * @param pdfGeoX output location where geo_x (easting/longitude)  | 
4472  |  |  * location is placed.  | 
4473  |  |  * @param pdfGeoY output location where geo_y (northing/latitude)  | 
4474  |  |  * location is placed.  | 
4475  |  |  */  | 
4476  |  |  | 
4477  |  | void CPL_STDCALL GDALApplyGeoTransform(const double *padfGeoTransform,  | 
4478  |  |                                        double dfPixel, double dfLine,  | 
4479  |  |                                        double *pdfGeoX, double *pdfGeoY)  | 
4480  | 0  | { | 
4481  | 0  |     *pdfGeoX = padfGeoTransform[0] + dfPixel * padfGeoTransform[1] +  | 
4482  | 0  |                dfLine * padfGeoTransform[2];  | 
4483  | 0  |     *pdfGeoY = padfGeoTransform[3] + dfPixel * padfGeoTransform[4] +  | 
4484  | 0  |                dfLine * padfGeoTransform[5];  | 
4485  | 0  | }  | 
4486  |  |  | 
4487  |  | /************************************************************************/  | 
4488  |  | /*                        GDALInvGeoTransform()                         */  | 
4489  |  | /************************************************************************/  | 
4490  |  |  | 
4491  |  | /**  | 
4492  |  |  * Invert Geotransform.  | 
4493  |  |  *  | 
4494  |  |  * This function will invert a standard 3x2 set of GeoTransform coefficients.  | 
4495  |  |  * This converts the equation from being pixel to geo to being geo to pixel.  | 
4496  |  |  *  | 
4497  |  |  * @param gt_in Input geotransform (six doubles - unaltered).  | 
4498  |  |  * @param gt_out Output geotransform (six doubles - updated).  | 
4499  |  |  *  | 
4500  |  |  * @return TRUE on success or FALSE if the equation is uninvertable.  | 
4501  |  |  */  | 
4502  |  |  | 
4503  |  | int CPL_STDCALL GDALInvGeoTransform(const double *gt_in, double *gt_out)  | 
4504  |  |  | 
4505  | 0  | { | 
4506  |  |     // Special case - no rotation - to avoid computing determinate  | 
4507  |  |     // and potential precision issues.  | 
4508  | 0  |     if (gt_in[2] == 0.0 && gt_in[4] == 0.0 && gt_in[1] != 0.0 &&  | 
4509  | 0  |         gt_in[5] != 0.0)  | 
4510  | 0  |     { | 
4511  |  |         /*X = gt_in[0] + x * gt_in[1]  | 
4512  |  |           Y = gt_in[3] + y * gt_in[5]  | 
4513  |  |           -->  | 
4514  |  |           x = -gt_in[0] / gt_in[1] + (1 / gt_in[1]) * X  | 
4515  |  |           y = -gt_in[3] / gt_in[5] + (1 / gt_in[5]) * Y  | 
4516  |  |         */  | 
4517  | 0  |         gt_out[0] = -gt_in[0] / gt_in[1];  | 
4518  | 0  |         gt_out[1] = 1.0 / gt_in[1];  | 
4519  | 0  |         gt_out[2] = 0.0;  | 
4520  | 0  |         gt_out[3] = -gt_in[3] / gt_in[5];  | 
4521  | 0  |         gt_out[4] = 0.0;  | 
4522  | 0  |         gt_out[5] = 1.0 / gt_in[5];  | 
4523  | 0  |         return 1;  | 
4524  | 0  |     }  | 
4525  |  |  | 
4526  |  |     // Assume a 3rd row that is [1 0 0].  | 
4527  |  |  | 
4528  |  |     // Compute determinate.  | 
4529  |  |  | 
4530  | 0  |     const double det = gt_in[1] * gt_in[5] - gt_in[2] * gt_in[4];  | 
4531  | 0  |     const double magnitude = std::max(std::max(fabs(gt_in[1]), fabs(gt_in[2])),  | 
4532  | 0  |                                       std::max(fabs(gt_in[4]), fabs(gt_in[5])));  | 
4533  |  | 
  | 
4534  | 0  |     if (fabs(det) <= 1e-10 * magnitude * magnitude)  | 
4535  | 0  |         return 0;  | 
4536  |  |  | 
4537  | 0  |     const double inv_det = 1.0 / det;  | 
4538  |  |  | 
4539  |  |     // Compute adjoint, and divide by determinate.  | 
4540  |  | 
  | 
4541  | 0  |     gt_out[1] = gt_in[5] * inv_det;  | 
4542  | 0  |     gt_out[4] = -gt_in[4] * inv_det;  | 
4543  |  | 
  | 
4544  | 0  |     gt_out[2] = -gt_in[2] * inv_det;  | 
4545  | 0  |     gt_out[5] = gt_in[1] * inv_det;  | 
4546  |  | 
  | 
4547  | 0  |     gt_out[0] = (gt_in[2] * gt_in[3] - gt_in[0] * gt_in[5]) * inv_det;  | 
4548  | 0  |     gt_out[3] = (-gt_in[1] * gt_in[3] + gt_in[0] * gt_in[4]) * inv_det;  | 
4549  |  | 
  | 
4550  | 0  |     return 1;  | 
4551  | 0  | }  | 
4552  |  |  | 
4553  |  | /************************************************************************/  | 
4554  |  | /*                      GDALSerializeTransformer()                      */  | 
4555  |  | /************************************************************************/  | 
4556  |  |  | 
4557  |  | CPLXMLNode *GDALSerializeTransformer(GDALTransformerFunc /* pfnFunc */,  | 
4558  |  |                                      void *pTransformArg)  | 
4559  | 0  | { | 
4560  | 0  |     VALIDATE_POINTER1(pTransformArg, "GDALSerializeTransformer", nullptr);  | 
4561  |  |  | 
4562  | 0  |     GDALTransformerInfo *psInfo =  | 
4563  | 0  |         static_cast<GDALTransformerInfo *>(pTransformArg);  | 
4564  |  | 
  | 
4565  | 0  |     if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,  | 
4566  | 0  |                                     strlen(GDAL_GTI2_SIGNATURE)) != 0)  | 
4567  | 0  |     { | 
4568  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
4569  | 0  |                  "Attempt to serialize non-GTI2 transformer.");  | 
4570  | 0  |         return nullptr;  | 
4571  | 0  |     }  | 
4572  | 0  |     else if (psInfo->pfnSerialize == nullptr)  | 
4573  | 0  |     { | 
4574  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
4575  | 0  |                  "No serialization function available for this transformer.");  | 
4576  | 0  |         return nullptr;  | 
4577  | 0  |     }  | 
4578  |  |  | 
4579  | 0  |     return psInfo->pfnSerialize(pTransformArg);  | 
4580  | 0  | }  | 
4581  |  |  | 
4582  |  | /************************************************************************/  | 
4583  |  | /*                  GDALRegisterTransformDeserializer()                 */  | 
4584  |  | /************************************************************************/  | 
4585  |  |  | 
4586  |  | static CPLList *psListDeserializer = nullptr;  | 
4587  |  | static CPLMutex *hDeserializerMutex = nullptr;  | 
4588  |  |  | 
4589  |  | typedef struct  | 
4590  |  | { | 
4591  |  |     char *pszTransformName;  | 
4592  |  |     GDALTransformerFunc pfnTransformerFunc;  | 
4593  |  |     GDALTransformDeserializeFunc pfnDeserializeFunc;  | 
4594  |  | } TransformDeserializerInfo;  | 
4595  |  |  | 
4596  |  | void *GDALRegisterTransformDeserializer(  | 
4597  |  |     const char *pszTransformName, GDALTransformerFunc pfnTransformerFunc,  | 
4598  |  |     GDALTransformDeserializeFunc pfnDeserializeFunc)  | 
4599  | 0  | { | 
4600  | 0  |     TransformDeserializerInfo *psInfo =  | 
4601  | 0  |         static_cast<TransformDeserializerInfo *>(  | 
4602  | 0  |             CPLMalloc(sizeof(TransformDeserializerInfo)));  | 
4603  | 0  |     psInfo->pszTransformName = CPLStrdup(pszTransformName);  | 
4604  | 0  |     psInfo->pfnTransformerFunc = pfnTransformerFunc;  | 
4605  | 0  |     psInfo->pfnDeserializeFunc = pfnDeserializeFunc;  | 
4606  |  | 
  | 
4607  | 0  |     CPLMutexHolderD(&hDeserializerMutex);  | 
4608  | 0  |     psListDeserializer = CPLListInsert(psListDeserializer, psInfo, 0);  | 
4609  |  | 
  | 
4610  | 0  |     return psInfo;  | 
4611  | 0  | }  | 
4612  |  |  | 
4613  |  | /************************************************************************/  | 
4614  |  | /*                GDALUnregisterTransformDeserializer()                 */  | 
4615  |  | /************************************************************************/  | 
4616  |  |  | 
4617  |  | void GDALUnregisterTransformDeserializer(void *pData)  | 
4618  | 0  | { | 
4619  | 0  |     CPLMutexHolderD(&hDeserializerMutex);  | 
4620  | 0  |     CPLList *psList = psListDeserializer;  | 
4621  | 0  |     CPLList *psLast = nullptr;  | 
4622  | 0  |     while (psList)  | 
4623  | 0  |     { | 
4624  | 0  |         if (psList->pData == pData)  | 
4625  | 0  |         { | 
4626  | 0  |             TransformDeserializerInfo *psInfo =  | 
4627  | 0  |                 static_cast<TransformDeserializerInfo *>(pData);  | 
4628  | 0  |             CPLFree(psInfo->pszTransformName);  | 
4629  | 0  |             CPLFree(pData);  | 
4630  | 0  |             if (psLast)  | 
4631  | 0  |                 psLast->psNext = psList->psNext;  | 
4632  | 0  |             else  | 
4633  | 0  |                 psListDeserializer = nullptr;  | 
4634  | 0  |             CPLFree(psList);  | 
4635  | 0  |             break;  | 
4636  | 0  |         }  | 
4637  | 0  |         psLast = psList;  | 
4638  | 0  |         psList = psList->psNext;  | 
4639  | 0  |     }  | 
4640  | 0  | }  | 
4641  |  |  | 
4642  |  | /************************************************************************/  | 
4643  |  | /*                GDALUnregisterTransformDeserializer()                 */  | 
4644  |  | /************************************************************************/  | 
4645  |  |  | 
4646  |  | void GDALCleanupTransformDeserializerMutex()  | 
4647  | 0  | { | 
4648  | 0  |     if (hDeserializerMutex != nullptr)  | 
4649  | 0  |     { | 
4650  | 0  |         CPLDestroyMutex(hDeserializerMutex);  | 
4651  | 0  |         hDeserializerMutex = nullptr;  | 
4652  | 0  |     }  | 
4653  | 0  | }  | 
4654  |  |  | 
4655  |  | /************************************************************************/  | 
4656  |  | /*                     GDALDeserializeTransformer()                     */  | 
4657  |  | /************************************************************************/  | 
4658  |  |  | 
4659  |  | CPLErr GDALDeserializeTransformer(CPLXMLNode *psTree,  | 
4660  |  |                                   GDALTransformerFunc *ppfnFunc,  | 
4661  |  |                                   void **ppTransformArg)  | 
4662  |  |  | 
4663  | 0  | { | 
4664  | 0  |     *ppfnFunc = nullptr;  | 
4665  | 0  |     *ppTransformArg = nullptr;  | 
4666  |  | 
  | 
4667  | 0  |     CPLErrorReset();  | 
4668  |  | 
  | 
4669  | 0  |     if (psTree == nullptr || psTree->eType != CXT_Element)  | 
4670  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
4671  | 0  |                  "Malformed element in GDALDeserializeTransformer");  | 
4672  | 0  |     else if (EQUAL(psTree->pszValue, "GenImgProjTransformer"))  | 
4673  | 0  |     { | 
4674  | 0  |         *ppfnFunc = GDALGenImgProjTransform;  | 
4675  | 0  |         *ppTransformArg = GDALDeserializeGenImgProjTransformer(psTree);  | 
4676  | 0  |     }  | 
4677  | 0  |     else if (EQUAL(psTree->pszValue, "ReprojectionTransformer"))  | 
4678  | 0  |     { | 
4679  | 0  |         *ppfnFunc = GDALReprojectionTransform;  | 
4680  | 0  |         *ppTransformArg = GDALDeserializeReprojectionTransformer(psTree);  | 
4681  | 0  |     }  | 
4682  | 0  |     else if (EQUAL(psTree->pszValue, "GCPTransformer"))  | 
4683  | 0  |     { | 
4684  | 0  |         *ppfnFunc = GDALGCPTransform;  | 
4685  | 0  |         *ppTransformArg = GDALDeserializeGCPTransformer(psTree);  | 
4686  | 0  |     }  | 
4687  | 0  |     else if (EQUAL(psTree->pszValue, "TPSTransformer"))  | 
4688  | 0  |     { | 
4689  | 0  |         *ppfnFunc = GDALTPSTransform;  | 
4690  | 0  |         *ppTransformArg = GDALDeserializeTPSTransformer(psTree);  | 
4691  | 0  |     }  | 
4692  | 0  |     else if (EQUAL(psTree->pszValue, "GeoLocTransformer"))  | 
4693  | 0  |     { | 
4694  | 0  |         *ppfnFunc = GDALGeoLocTransform;  | 
4695  | 0  |         *ppTransformArg = GDALDeserializeGeoLocTransformer(psTree);  | 
4696  | 0  |     }  | 
4697  | 0  |     else if (EQUAL(psTree->pszValue, "RPCTransformer"))  | 
4698  | 0  |     { | 
4699  | 0  |         *ppfnFunc = GDALRPCTransform;  | 
4700  | 0  |         *ppTransformArg = GDALDeserializeRPCTransformer(psTree);  | 
4701  | 0  |     }  | 
4702  | 0  |     else if (EQUAL(psTree->pszValue, "ApproxTransformer"))  | 
4703  | 0  |     { | 
4704  | 0  |         *ppfnFunc = GDALApproxTransform;  | 
4705  | 0  |         *ppTransformArg = GDALDeserializeApproxTransformer(psTree);  | 
4706  | 0  |     }  | 
4707  | 0  |     else if (EQUAL(psTree->pszValue, "HomographyTransformer"))  | 
4708  | 0  |     { | 
4709  | 0  |         *ppfnFunc = GDALHomographyTransform;  | 
4710  | 0  |         *ppTransformArg = GDALDeserializeHomographyTransformer(psTree);  | 
4711  | 0  |     }  | 
4712  | 0  |     else  | 
4713  | 0  |     { | 
4714  | 0  |         GDALTransformDeserializeFunc pfnDeserializeFunc = nullptr;  | 
4715  | 0  |         { | 
4716  | 0  |             CPLMutexHolderD(&hDeserializerMutex);  | 
4717  | 0  |             CPLList *psList = psListDeserializer;  | 
4718  | 0  |             while (psList)  | 
4719  | 0  |             { | 
4720  | 0  |                 TransformDeserializerInfo *psInfo =  | 
4721  | 0  |                     static_cast<TransformDeserializerInfo *>(psList->pData);  | 
4722  | 0  |                 if (strcmp(psInfo->pszTransformName, psTree->pszValue) == 0)  | 
4723  | 0  |                 { | 
4724  | 0  |                     *ppfnFunc = psInfo->pfnTransformerFunc;  | 
4725  | 0  |                     pfnDeserializeFunc = psInfo->pfnDeserializeFunc;  | 
4726  | 0  |                     break;  | 
4727  | 0  |                 }  | 
4728  | 0  |                 psList = psList->psNext;  | 
4729  | 0  |             }  | 
4730  | 0  |         }  | 
4731  |  | 
  | 
4732  | 0  |         if (pfnDeserializeFunc != nullptr)  | 
4733  | 0  |         { | 
4734  | 0  |             *ppTransformArg = pfnDeserializeFunc(psTree);  | 
4735  | 0  |         }  | 
4736  | 0  |         else  | 
4737  | 0  |         { | 
4738  | 0  |             CPLError(CE_Failure, CPLE_AppDefined,  | 
4739  | 0  |                      "Unrecognized element '%s' GDALDeserializeTransformer",  | 
4740  | 0  |                      psTree->pszValue);  | 
4741  | 0  |         }  | 
4742  | 0  |     }  | 
4743  |  | 
  | 
4744  | 0  |     return CPLGetLastErrorType();  | 
4745  | 0  | }  | 
4746  |  |  | 
4747  |  | /************************************************************************/  | 
4748  |  | /*                       GDALDestroyTransformer()                       */  | 
4749  |  | /************************************************************************/  | 
4750  |  |  | 
4751  |  | void GDALDestroyTransformer(void *pTransformArg)  | 
4752  |  |  | 
4753  | 0  | { | 
4754  | 0  |     if (pTransformArg == nullptr)  | 
4755  | 0  |         return;  | 
4756  |  |  | 
4757  | 0  |     GDALTransformerInfo *psInfo =  | 
4758  | 0  |         static_cast<GDALTransformerInfo *>(pTransformArg);  | 
4759  |  | 
  | 
4760  | 0  |     if (memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,  | 
4761  | 0  |                strlen(GDAL_GTI2_SIGNATURE)) != 0)  | 
4762  | 0  |     { | 
4763  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
4764  | 0  |                  "Attempt to destroy non-GTI2 transformer.");  | 
4765  | 0  |         return;  | 
4766  | 0  |     }  | 
4767  |  |  | 
4768  | 0  |     psInfo->pfnCleanup(pTransformArg);  | 
4769  | 0  | }  | 
4770  |  |  | 
4771  |  | /************************************************************************/  | 
4772  |  | /*                         GDALUseTransformer()                         */  | 
4773  |  | /************************************************************************/  | 
4774  |  |  | 
4775  |  | int GDALUseTransformer(void *pTransformArg, int bDstToSrc, int nPointCount,  | 
4776  |  |                        double *x, double *y, double *z, int *panSuccess)  | 
4777  | 0  | { | 
4778  | 0  |     GDALTransformerInfo *psInfo =  | 
4779  | 0  |         static_cast<GDALTransformerInfo *>(pTransformArg);  | 
4780  |  | 
  | 
4781  | 0  |     if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,  | 
4782  | 0  |                                     strlen(GDAL_GTI2_SIGNATURE)) != 0)  | 
4783  | 0  |     { | 
4784  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
4785  | 0  |                  "Attempt to use non-GTI2 transformer.");  | 
4786  | 0  |         return FALSE;  | 
4787  | 0  |     }  | 
4788  |  |  | 
4789  | 0  |     return psInfo->pfnTransform(pTransformArg, bDstToSrc, nPointCount, x, y, z,  | 
4790  | 0  |                                 panSuccess);  | 
4791  | 0  | }  | 
4792  |  |  | 
4793  |  | /************************************************************************/  | 
4794  |  | /*                        GDALCloneTransformer()                        */  | 
4795  |  | /************************************************************************/  | 
4796  |  |  | 
4797  |  | void *GDALCloneTransformer(void *pTransformArg)  | 
4798  | 0  | { | 
4799  | 0  |     GDALTransformerInfo *psInfo =  | 
4800  | 0  |         static_cast<GDALTransformerInfo *>(pTransformArg);  | 
4801  |  | 
  | 
4802  | 0  |     if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,  | 
4803  | 0  |                                     strlen(GDAL_GTI2_SIGNATURE)) != 0)  | 
4804  | 0  |     { | 
4805  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
4806  | 0  |                  "Attempt to clone non-GTI2 transformer.");  | 
4807  | 0  |         return nullptr;  | 
4808  | 0  |     }  | 
4809  |  |  | 
4810  | 0  |     if (psInfo->pfnCreateSimilar != nullptr)  | 
4811  | 0  |     { | 
4812  | 0  |         return psInfo->pfnCreateSimilar(psInfo, 1.0, 1.0);  | 
4813  | 0  |     }  | 
4814  |  |  | 
4815  | 0  |     if (psInfo->pfnSerialize == nullptr)  | 
4816  | 0  |     { | 
4817  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
4818  | 0  |                  "No serialization function available for this transformer.");  | 
4819  | 0  |         return nullptr;  | 
4820  | 0  |     }  | 
4821  |  |  | 
4822  | 0  |     CPLXMLNode *pSerialized = psInfo->pfnSerialize(pTransformArg);  | 
4823  | 0  |     if (pSerialized == nullptr)  | 
4824  | 0  |         return nullptr;  | 
4825  | 0  |     GDALTransformerFunc pfnTransformer = nullptr;  | 
4826  | 0  |     void *pClonedTransformArg = nullptr;  | 
4827  | 0  |     if (GDALDeserializeTransformer(pSerialized, &pfnTransformer,  | 
4828  | 0  |                                    &pClonedTransformArg) != CE_None)  | 
4829  | 0  |     { | 
4830  | 0  |         CPLDestroyXMLNode(pSerialized);  | 
4831  | 0  |         CPLFree(pClonedTransformArg);  | 
4832  | 0  |         return nullptr;  | 
4833  | 0  |     }  | 
4834  |  |  | 
4835  | 0  |     CPLDestroyXMLNode(pSerialized);  | 
4836  | 0  |     return pClonedTransformArg;  | 
4837  | 0  | }  | 
4838  |  |  | 
4839  |  | /************************************************************************/  | 
4840  |  | /*                   GDALCreateSimilarTransformer()                     */  | 
4841  |  | /************************************************************************/  | 
4842  |  |  | 
4843  |  | void *GDALCreateSimilarTransformer(void *pTransformArg, double dfRatioX,  | 
4844  |  |                                    double dfRatioY)  | 
4845  | 0  | { | 
4846  | 0  |     GDALTransformerInfo *psInfo =  | 
4847  | 0  |         static_cast<GDALTransformerInfo *>(pTransformArg);  | 
4848  |  | 
  | 
4849  | 0  |     if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,  | 
4850  | 0  |                                     strlen(GDAL_GTI2_SIGNATURE)) != 0)  | 
4851  | 0  |     { | 
4852  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
4853  | 0  |                  "Attempt to call CreateSimilar on a non-GTI2 transformer.");  | 
4854  | 0  |         return nullptr;  | 
4855  | 0  |     }  | 
4856  |  |  | 
4857  | 0  |     if (psInfo->pfnCreateSimilar == nullptr)  | 
4858  | 0  |     { | 
4859  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
4860  | 0  |                  "No CreateSimilar function available for this transformer.");  | 
4861  | 0  |         return nullptr;  | 
4862  | 0  |     }  | 
4863  |  |  | 
4864  | 0  |     return psInfo->pfnCreateSimilar(psInfo, dfRatioX, dfRatioY);  | 
4865  | 0  | }  | 
4866  |  |  | 
4867  |  | /************************************************************************/  | 
4868  |  | /*                      GetGenImgProjTransformInfo()                    */  | 
4869  |  | /************************************************************************/  | 
4870  |  |  | 
4871  |  | static GDALTransformerInfo *GetGenImgProjTransformInfo(const char *pszFunc,  | 
4872  |  |                                                        void *pTransformArg)  | 
4873  | 0  | { | 
4874  | 0  |     GDALTransformerInfo *psInfo =  | 
4875  | 0  |         static_cast<GDALTransformerInfo *>(pTransformArg);  | 
4876  |  | 
  | 
4877  | 0  |     if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,  | 
4878  | 0  |                                     strlen(GDAL_GTI2_SIGNATURE)) != 0)  | 
4879  | 0  |     { | 
4880  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
4881  | 0  |                  "Attempt to call %s on "  | 
4882  | 0  |                  "a non-GTI2 transformer.",  | 
4883  | 0  |                  pszFunc);  | 
4884  | 0  |         return nullptr;  | 
4885  | 0  |     }  | 
4886  |  |  | 
4887  | 0  |     if (EQUAL(psInfo->pszClassName, GDAL_APPROX_TRANSFORMER_CLASS_NAME))  | 
4888  | 0  |     { | 
4889  | 0  |         GDALApproxTransformInfo *psATInfo =  | 
4890  | 0  |             static_cast<GDALApproxTransformInfo *>(pTransformArg);  | 
4891  | 0  |         psInfo = static_cast<GDALTransformerInfo *>(psATInfo->pBaseCBData);  | 
4892  |  | 
  | 
4893  | 0  |         if (psInfo == nullptr ||  | 
4894  | 0  |             memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,  | 
4895  | 0  |                    strlen(GDAL_GTI2_SIGNATURE)) != 0)  | 
4896  | 0  |         { | 
4897  | 0  |             CPLError(CE_Failure, CPLE_AppDefined,  | 
4898  | 0  |                      "Attempt to call %s on "  | 
4899  | 0  |                      "a non-GTI2 transformer.",  | 
4900  | 0  |                      pszFunc);  | 
4901  | 0  |             return nullptr;  | 
4902  | 0  |         }  | 
4903  | 0  |     }  | 
4904  |  |  | 
4905  | 0  |     if (EQUAL(psInfo->pszClassName, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))  | 
4906  | 0  |     { | 
4907  | 0  |         return psInfo;  | 
4908  | 0  |     }  | 
4909  |  |  | 
4910  | 0  |     return nullptr;  | 
4911  | 0  | }  | 
4912  |  |  | 
4913  |  | /************************************************************************/  | 
4914  |  | /*                 GDALSetTransformerDstGeoTransform()                  */  | 
4915  |  | /************************************************************************/  | 
4916  |  |  | 
4917  |  | /**  | 
4918  |  |  * Set ApproxTransformer or GenImgProj output geotransform.  | 
4919  |  |  *  | 
4920  |  |  * This is a layer above GDALSetGenImgProjTransformerDstGeoTransform() that  | 
4921  |  |  * checks that the passed hTransformArg is compatible.  | 
4922  |  |  *  | 
4923  |  |  * Normally the "destination geotransform", or transformation between  | 
4924  |  |  * georeferenced output coordinates and pixel/line coordinates on the  | 
4925  |  |  * destination file is extracted from the destination file by  | 
4926  |  |  * GDALCreateGenImgProjTransformer() and stored in the GenImgProj private  | 
4927  |  |  * info.  However, sometimes it is inconvenient to have an output file  | 
4928  |  |  * handle with appropriate geotransform information when creating the  | 
4929  |  |  * transformation.  For these cases, this function can be used to apply  | 
4930  |  |  * the destination geotransform.  | 
4931  |  |  *  | 
4932  |  |  * @param pTransformArg the handle to update.  | 
4933  |  |  * @param padfGeoTransform the destination geotransform to apply (six doubles).  | 
4934  |  |  */  | 
4935  |  |  | 
4936  |  | void GDALSetTransformerDstGeoTransform(void *pTransformArg,  | 
4937  |  |                                        const double *padfGeoTransform)  | 
4938  | 0  | { | 
4939  | 0  |     VALIDATE_POINTER0(pTransformArg, "GDALSetTransformerDstGeoTransform");  | 
4940  |  |  | 
4941  | 0  |     GDALTransformerInfo *psInfo = GetGenImgProjTransformInfo(  | 
4942  | 0  |         "GDALSetTransformerDstGeoTransform", pTransformArg);  | 
4943  | 0  |     if (psInfo)  | 
4944  | 0  |     { | 
4945  | 0  |         GDALSetGenImgProjTransformerDstGeoTransform(psInfo, padfGeoTransform);  | 
4946  | 0  |     }  | 
4947  | 0  | }  | 
4948  |  |  | 
4949  |  | /************************************************************************/  | 
4950  |  | /*                 GDALGetTransformerDstGeoTransform()                  */  | 
4951  |  | /************************************************************************/  | 
4952  |  |  | 
4953  |  | /**  | 
4954  |  |  * Get ApproxTransformer or GenImgProj output geotransform.  | 
4955  |  |  *  | 
4956  |  |  * @param pTransformArg transformer handle.  | 
4957  |  |  * @param padfGeoTransform (output) the destination geotransform to return (six  | 
4958  |  |  * doubles).  | 
4959  |  |  */  | 
4960  |  |  | 
4961  |  | void GDALGetTransformerDstGeoTransform(void *pTransformArg,  | 
4962  |  |                                        double *padfGeoTransform)  | 
4963  | 0  | { | 
4964  | 0  |     VALIDATE_POINTER0(pTransformArg, "GDALGetTransformerDstGeoTransform");  | 
4965  |  |  | 
4966  | 0  |     GDALTransformerInfo *psInfo = GetGenImgProjTransformInfo(  | 
4967  | 0  |         "GDALGetTransformerDstGeoTransform", pTransformArg);  | 
4968  | 0  |     if (psInfo)  | 
4969  | 0  |     { | 
4970  | 0  |         GDALGenImgProjTransformInfo *psGenImgProjInfo =  | 
4971  | 0  |             reinterpret_cast<GDALGenImgProjTransformInfo *>(psInfo);  | 
4972  |  | 
  | 
4973  | 0  |         memcpy(padfGeoTransform, psGenImgProjInfo->sDstParams.adfGeoTransform,  | 
4974  | 0  |                sizeof(double) * 6);  | 
4975  | 0  |     }  | 
4976  | 0  | }  | 
4977  |  |  | 
4978  |  | /************************************************************************/  | 
4979  |  | /*            GDALTransformIsTranslationOnPixelBoundaries()             */  | 
4980  |  | /************************************************************************/  | 
4981  |  |  | 
4982  |  | bool GDALTransformIsTranslationOnPixelBoundaries(GDALTransformerFunc,  | 
4983  |  |                                                  void *pTransformerArg)  | 
4984  | 0  | { | 
4985  | 0  |     if (GDALIsTransformer(pTransformerArg, GDAL_APPROX_TRANSFORMER_CLASS_NAME))  | 
4986  | 0  |     { | 
4987  | 0  |         const auto *pApproxInfo =  | 
4988  | 0  |             static_cast<const GDALApproxTransformInfo *>(pTransformerArg);  | 
4989  | 0  |         pTransformerArg = pApproxInfo->pBaseCBData;  | 
4990  | 0  |     }  | 
4991  | 0  |     if (GDALIsTransformer(pTransformerArg, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))  | 
4992  | 0  |     { | 
4993  | 0  |         const auto *pGenImgpProjInfo =  | 
4994  | 0  |             static_cast<GDALGenImgProjTransformInfo *>(pTransformerArg);  | 
4995  | 0  |         const auto IsCloseToInteger = [](double dfVal)  | 
4996  | 0  |         { return std::fabs(dfVal - std::round(dfVal)) <= 1e-6; }; | 
4997  | 0  |         return pGenImgpProjInfo->sSrcParams.pTransformArg == nullptr &&  | 
4998  | 0  |                pGenImgpProjInfo->sDstParams.pTransformArg == nullptr &&  | 
4999  | 0  |                pGenImgpProjInfo->pReproject == nullptr &&  | 
5000  | 0  |                pGenImgpProjInfo->sSrcParams.adfGeoTransform[1] ==  | 
5001  | 0  |                    pGenImgpProjInfo->sDstParams.adfGeoTransform[1] &&  | 
5002  | 0  |                pGenImgpProjInfo->sSrcParams.adfGeoTransform[5] ==  | 
5003  | 0  |                    pGenImgpProjInfo->sDstParams.adfGeoTransform[5] &&  | 
5004  | 0  |                pGenImgpProjInfo->sSrcParams.adfGeoTransform[2] ==  | 
5005  | 0  |                    pGenImgpProjInfo->sDstParams.adfGeoTransform[2] &&  | 
5006  | 0  |                pGenImgpProjInfo->sSrcParams.adfGeoTransform[4] ==  | 
5007  | 0  |                    pGenImgpProjInfo->sDstParams.adfGeoTransform[4] &&  | 
5008  |  |                // Check that the georeferenced origin of the destination  | 
5009  |  |                // geotransform is close to be an integer value when transformed  | 
5010  |  |                // to source image coordinates  | 
5011  | 0  |                IsCloseToInteger(  | 
5012  | 0  |                    pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[0] +  | 
5013  | 0  |                    pGenImgpProjInfo->sDstParams.adfGeoTransform[0] *  | 
5014  | 0  |                        pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[1] +  | 
5015  | 0  |                    pGenImgpProjInfo->sDstParams.adfGeoTransform[3] *  | 
5016  | 0  |                        pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[2]) &&  | 
5017  | 0  |                IsCloseToInteger(  | 
5018  | 0  |                    pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[3] +  | 
5019  | 0  |                    pGenImgpProjInfo->sDstParams.adfGeoTransform[0] *  | 
5020  | 0  |                        pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[4] +  | 
5021  | 0  |                    pGenImgpProjInfo->sDstParams.adfGeoTransform[3] *  | 
5022  | 0  |                        pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[5]);  | 
5023  | 0  |     }  | 
5024  | 0  |     return false;  | 
5025  | 0  | }  | 
5026  |  |  | 
5027  |  | /************************************************************************/  | 
5028  |  | /*                   GDALTransformIsAffineNoRotation()                  */  | 
5029  |  | /************************************************************************/  | 
5030  |  |  | 
5031  |  | bool GDALTransformIsAffineNoRotation(GDALTransformerFunc, void *pTransformerArg)  | 
5032  | 0  | { | 
5033  | 0  |     if (GDALIsTransformer(pTransformerArg, GDAL_APPROX_TRANSFORMER_CLASS_NAME))  | 
5034  | 0  |     { | 
5035  | 0  |         const auto *pApproxInfo =  | 
5036  | 0  |             static_cast<const GDALApproxTransformInfo *>(pTransformerArg);  | 
5037  | 0  |         pTransformerArg = pApproxInfo->pBaseCBData;  | 
5038  | 0  |     }  | 
5039  | 0  |     if (GDALIsTransformer(pTransformerArg, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))  | 
5040  | 0  |     { | 
5041  | 0  |         const auto *pGenImgpProjInfo =  | 
5042  | 0  |             static_cast<GDALGenImgProjTransformInfo *>(pTransformerArg);  | 
5043  | 0  |         return pGenImgpProjInfo->sSrcParams.pTransformArg == nullptr &&  | 
5044  | 0  |                pGenImgpProjInfo->sDstParams.pTransformArg == nullptr &&  | 
5045  | 0  |                pGenImgpProjInfo->pReproject == nullptr &&  | 
5046  | 0  |                pGenImgpProjInfo->sSrcParams.adfGeoTransform[2] == 0 &&  | 
5047  | 0  |                pGenImgpProjInfo->sSrcParams.adfGeoTransform[4] == 0 &&  | 
5048  | 0  |                pGenImgpProjInfo->sDstParams.adfGeoTransform[2] == 0 &&  | 
5049  | 0  |                pGenImgpProjInfo->sDstParams.adfGeoTransform[4] == 0;  | 
5050  | 0  |     }  | 
5051  | 0  |     return false;  | 
5052  | 0  | }  | 
5053  |  |  | 
5054  |  | /************************************************************************/  | 
5055  |  | /*                        GDALTransformHasFastClone()                   */  | 
5056  |  | /************************************************************************/  | 
5057  |  |  | 
5058  |  | /** Returns whether GDALCloneTransformer() on this transformer is  | 
5059  |  |  * "fast"  | 
5060  |  |  * Counter-examples are GCPs or TPSs transformers.  | 
5061  |  |  */  | 
5062  |  | bool GDALTransformHasFastClone(void *pTransformerArg)  | 
5063  | 0  | { | 
5064  | 0  |     if (GDALIsTransformer(pTransformerArg, GDAL_APPROX_TRANSFORMER_CLASS_NAME))  | 
5065  | 0  |     { | 
5066  | 0  |         const auto *pApproxInfo =  | 
5067  | 0  |             static_cast<const GDALApproxTransformInfo *>(pTransformerArg);  | 
5068  | 0  |         pTransformerArg = pApproxInfo->pBaseCBData;  | 
5069  |  |         // Fallback to next lines  | 
5070  | 0  |     }  | 
5071  |  | 
  | 
5072  | 0  |     if (GDALIsTransformer(pTransformerArg, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))  | 
5073  | 0  |     { | 
5074  | 0  |         const auto *pGenImgpProjInfo =  | 
5075  | 0  |             static_cast<GDALGenImgProjTransformInfo *>(pTransformerArg);  | 
5076  | 0  |         return (pGenImgpProjInfo->sSrcParams.pTransformArg == nullptr ||  | 
5077  | 0  |                 GDALTransformHasFastClone(  | 
5078  | 0  |                     pGenImgpProjInfo->sSrcParams.pTransformArg)) &&  | 
5079  | 0  |                (pGenImgpProjInfo->sDstParams.pTransformArg == nullptr ||  | 
5080  | 0  |                 GDALTransformHasFastClone(  | 
5081  | 0  |                     pGenImgpProjInfo->sDstParams.pTransformArg));  | 
5082  | 0  |     }  | 
5083  | 0  |     else if (GDALIsTransformer(pTransformerArg,  | 
5084  | 0  |                                GDAL_RPC_TRANSFORMER_CLASS_NAME))  | 
5085  | 0  |     { | 
5086  | 0  |         return true;  | 
5087  | 0  |     }  | 
5088  | 0  |     else  | 
5089  | 0  |     { | 
5090  | 0  |         return false;  | 
5091  | 0  |     }  | 
5092  | 0  | }  |