/src/gdal/alg/gdalgenericinverse.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GDAL |
4 | | * Purpose: Generic method to compute inverse coordinate transformation from |
5 | | * forward method |
6 | | * Author: Even Rouault <even dot rouault at spatialys dot com> |
7 | | * |
8 | | ****************************************************************************** |
9 | | * Copyright (c) 2023, Even Rouault <even dot rouault at spatialys dot com> |
10 | | * |
11 | | * SPDX-License-Identifier: MIT |
12 | | ****************************************************************************/ |
13 | | |
14 | | #include <algorithm> |
15 | | #include <cmath> |
16 | | |
17 | | #include "gdalgenericinverse.h" |
18 | | #include <cstdio> |
19 | | |
20 | | /** Compute (xOut, yOut) corresponding to input (xIn, yIn) using |
21 | | * the provided forward transformation to emulate the reverse direction. |
22 | | * |
23 | | * Uses Newton-Raphson method, extended to 2D variables, that is using |
24 | | * inversion of the Jacobian 2D matrix of partial derivatives. The derivatives |
25 | | * are estimated numerically from the forward method evaluated at close points. |
26 | | * |
27 | | * Starts with initial guess provided by user in (guessedXOut, guessedYOut) |
28 | | * |
29 | | * It iterates at most for 15 iterations or as soon as we get below the specified |
30 | | * tolerance (on input coordinates) |
31 | | */ |
32 | | bool GDALGenericInverse2D(double xIn, double yIn, double guessedXOut, |
33 | | double guessedYOut, |
34 | | GDALForwardCoordTransformer pfnForwardTranformer, |
35 | | void *pfnForwardTranformerUserData, double &xOut, |
36 | | double &yOut, |
37 | | bool computeJacobianMatrixOnlyAtFirstIter, |
38 | | double toleranceOnInputCoordinates, |
39 | | double toleranceOnOutputCoordinates) |
40 | 0 | { |
41 | 0 | const double dfAbsValOut = std::max(fabs(guessedXOut), fabs(guessedYOut)); |
42 | 0 | const double dfEps = dfAbsValOut > 0 ? dfAbsValOut * 1e-6 : 1e-6; |
43 | 0 | if (toleranceOnInputCoordinates == 0) |
44 | 0 | { |
45 | 0 | const double dfAbsValIn = std::max(fabs(xIn), fabs(yIn)); |
46 | 0 | toleranceOnInputCoordinates = |
47 | 0 | dfAbsValIn > 0 ? dfAbsValIn * 1e-12 : 1e-12; |
48 | 0 | } |
49 | 0 | xOut = guessedXOut; |
50 | 0 | yOut = guessedYOut; |
51 | 0 | double deriv_lam_X = 0; |
52 | 0 | double deriv_lam_Y = 0; |
53 | 0 | double deriv_phi_X = 0; |
54 | 0 | double deriv_phi_Y = 0; |
55 | 0 | for (int i = 0; i < 15; i++) |
56 | 0 | { |
57 | 0 | double xApprox; |
58 | 0 | double yApprox; |
59 | 0 | if (!pfnForwardTranformer(xOut, yOut, xApprox, yApprox, |
60 | 0 | pfnForwardTranformerUserData)) |
61 | 0 | return false; |
62 | 0 | const double deltaX = xApprox - xIn; |
63 | 0 | const double deltaY = yApprox - yIn; |
64 | 0 | if (fabs(deltaX) < toleranceOnInputCoordinates && |
65 | 0 | fabs(deltaY) < toleranceOnInputCoordinates) |
66 | 0 | { |
67 | 0 | return true; |
68 | 0 | } |
69 | | |
70 | 0 | if (i == 0 || !computeJacobianMatrixOnlyAtFirstIter) |
71 | 0 | { |
72 | | // Compute Jacobian matrix |
73 | 0 | double xTmp = xOut + dfEps; |
74 | 0 | double yTmp = yOut; |
75 | 0 | double xTmpOut; |
76 | 0 | double yTmpOut; |
77 | 0 | if (!pfnForwardTranformer(xTmp, yTmp, xTmpOut, yTmpOut, |
78 | 0 | pfnForwardTranformerUserData)) |
79 | 0 | return false; |
80 | 0 | const double deriv_X_lam = (xTmpOut - xApprox) / dfEps; |
81 | 0 | const double deriv_Y_lam = (yTmpOut - yApprox) / dfEps; |
82 | |
|
83 | 0 | xTmp = xOut; |
84 | 0 | yTmp = yOut + dfEps; |
85 | 0 | if (!pfnForwardTranformer(xTmp, yTmp, xTmpOut, yTmpOut, |
86 | 0 | pfnForwardTranformerUserData)) |
87 | 0 | return false; |
88 | 0 | const double deriv_X_phi = (xTmpOut - xApprox) / dfEps; |
89 | 0 | const double deriv_Y_phi = (yTmpOut - yApprox) / dfEps; |
90 | | |
91 | | // Inverse of Jacobian matrix |
92 | 0 | const double det = |
93 | 0 | deriv_X_lam * deriv_Y_phi - deriv_X_phi * deriv_Y_lam; |
94 | 0 | if (det != 0) |
95 | 0 | { |
96 | 0 | deriv_lam_X = deriv_Y_phi / det; |
97 | 0 | deriv_lam_Y = -deriv_X_phi / det; |
98 | 0 | deriv_phi_X = -deriv_Y_lam / det; |
99 | 0 | deriv_phi_Y = deriv_X_lam / det; |
100 | 0 | } |
101 | 0 | else |
102 | 0 | { |
103 | 0 | return false; |
104 | 0 | } |
105 | 0 | } |
106 | | |
107 | 0 | const double xOutDelta = deltaX * deriv_lam_X + deltaY * deriv_lam_Y; |
108 | 0 | const double yOutDelta = deltaX * deriv_phi_X + deltaY * deriv_phi_Y; |
109 | 0 | xOut -= xOutDelta; |
110 | 0 | yOut -= yOutDelta; |
111 | 0 | if (toleranceOnOutputCoordinates > 0 && |
112 | 0 | fabs(xOutDelta) < toleranceOnOutputCoordinates && |
113 | 0 | fabs(yOutDelta) < toleranceOnOutputCoordinates) |
114 | 0 | { |
115 | 0 | return true; |
116 | 0 | } |
117 | 0 | } |
118 | 0 | return false; |
119 | 0 | } |