/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 | } |