/src/PROJ/src/transformations/helmert.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /*********************************************************************** |
2 | | |
3 | | 3-, 4-and 7-parameter shifts, and their 6-, 8- |
4 | | and 14-parameter kinematic counterparts. |
5 | | |
6 | | Thomas Knudsen, 2016-05-24 |
7 | | |
8 | | ************************************************************************ |
9 | | |
10 | | Implements 3(6)-, 4(8) and 7(14)-parameter Helmert transformations for |
11 | | 3D data. |
12 | | |
13 | | Also incorporates Molodensky-Badekas variant of 7-parameter Helmert |
14 | | transformation, where the rotation is not applied regarding the centre |
15 | | of the spheroid, but given a reference point. |
16 | | |
17 | | Primarily useful for implementation of datum shifts in transformation |
18 | | pipelines. |
19 | | |
20 | | ************************************************************************ |
21 | | |
22 | | Thomas Knudsen, thokn@sdfe.dk, 2016-05-24/06-05 |
23 | | Kristian Evers, kreve@sdfe.dk, 2017-05-01 |
24 | | Even Rouault, even.rouault@spatialys.com |
25 | | Last update: 2018-10-26 |
26 | | |
27 | | ************************************************************************ |
28 | | * Copyright (c) 2016, Thomas Knudsen / SDFE |
29 | | * |
30 | | * Permission is hereby granted, free of charge, to any person obtaining a |
31 | | * copy of this software and associated documentation files (the "Software"), |
32 | | * to deal in the Software without restriction, including without limitation |
33 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
34 | | * and/or sell copies of the Software, and to permit persons to whom the |
35 | | * Software is furnished to do so, subject to the following conditions: |
36 | | * |
37 | | * The above copyright notice and this permission notice shall be included |
38 | | * in all copies or substantial portions of the Software. |
39 | | * |
40 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
41 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
42 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
43 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
44 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
45 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
46 | | * DEALINGS IN THE SOFTWARE. |
47 | | * |
48 | | ***********************************************************************/ |
49 | | |
50 | | #include <errno.h> |
51 | | #include <math.h> |
52 | | |
53 | | #include "proj_internal.h" |
54 | | |
55 | | PROJ_HEAD(helmert, "3(6)-, 4(8)- and 7(14)-parameter Helmert shift"); |
56 | | PROJ_HEAD(molobadekas, "Molodensky-Badekas transformation"); |
57 | | |
58 | | static PJ_XYZ helmert_forward_3d(PJ_LPZ lpz, PJ *P); |
59 | | static PJ_LPZ helmert_reverse_3d(PJ_XYZ xyz, PJ *P); |
60 | | |
61 | | /***********************************************************************/ |
62 | | namespace { // anonymous namespace |
63 | | struct pj_opaque_helmert { |
64 | | /************************************************************************ |
65 | | Projection specific elements for the "helmert" PJ object |
66 | | ************************************************************************/ |
67 | | PJ_XYZ xyz; |
68 | | PJ_XYZ xyz_0; |
69 | | PJ_XYZ dxyz; |
70 | | PJ_XYZ refp; |
71 | | PJ_OPK opk; |
72 | | PJ_OPK opk_0; |
73 | | PJ_OPK dopk; |
74 | | double scale; |
75 | | double scale_0; |
76 | | double dscale; |
77 | | double theta; |
78 | | double theta_0; |
79 | | double dtheta; |
80 | | double R[3][3]; |
81 | | double t_epoch, t_obs; |
82 | | int no_rotation, exact, fourparam; |
83 | | int is_position_vector; /* 1 = position_vector, 0 = coordinate_frame */ |
84 | | }; |
85 | | } // anonymous namespace |
86 | | |
87 | | /* Make the maths of the rotation operations somewhat more readable and textbook |
88 | | * like */ |
89 | 62.2k | #define R00 (Q->R[0][0]) |
90 | 66.1k | #define R01 (Q->R[0][1]) |
91 | 66.1k | #define R02 (Q->R[0][2]) |
92 | | |
93 | 66.1k | #define R10 (Q->R[1][0]) |
94 | 62.2k | #define R11 (Q->R[1][1]) |
95 | 66.1k | #define R12 (Q->R[1][2]) |
96 | | |
97 | 66.1k | #define R20 (Q->R[2][0]) |
98 | 66.1k | #define R21 (Q->R[2][1]) |
99 | 62.2k | #define R22 (Q->R[2][2]) |
100 | | |
101 | | /**************************************************************************/ |
102 | 52.5k | static void update_parameters(PJ *P) { |
103 | | /*************************************************************************** |
104 | | |
105 | | Update transformation parameters. |
106 | | --------------------------------- |
107 | | |
108 | | The 14-parameter Helmert transformation is at it's core the same as the |
109 | | 7-parameter transformation, since the transformation parameters are |
110 | | projected forward or backwards in time via the rate of changes of the |
111 | | parameters. The transformation parameters are calculated for a specific |
112 | | epoch before the actual Helmert transformation is carried out. |
113 | | |
114 | | The transformation parameters are updated with the following equation |
115 | | [0]: |
116 | | |
117 | | . |
118 | | P(t) = P(EPOCH) + P * (t - EPOCH) |
119 | | |
120 | | . |
121 | | where EPOCH is the epoch indicated in the above table and P is the rate |
122 | | of that parameter. |
123 | | |
124 | | [0] http://itrf.ign.fr/doc_ITRF/Transfo-ITRF2008_ITRFs.txt |
125 | | |
126 | | *******************************************************************************/ |
127 | | |
128 | 52.5k | struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; |
129 | 52.5k | double dt = Q->t_obs - Q->t_epoch; |
130 | | |
131 | 52.5k | Q->xyz.x = Q->xyz_0.x + Q->dxyz.x * dt; |
132 | 52.5k | Q->xyz.y = Q->xyz_0.y + Q->dxyz.y * dt; |
133 | 52.5k | Q->xyz.z = Q->xyz_0.z + Q->dxyz.z * dt; |
134 | | |
135 | 52.5k | Q->opk.o = Q->opk_0.o + Q->dopk.o * dt; |
136 | 52.5k | Q->opk.p = Q->opk_0.p + Q->dopk.p * dt; |
137 | 52.5k | Q->opk.k = Q->opk_0.k + Q->dopk.k * dt; |
138 | | |
139 | 52.5k | Q->scale = Q->scale_0 + Q->dscale * dt; |
140 | | |
141 | 52.5k | Q->theta = Q->theta_0 + Q->dtheta * dt; |
142 | | |
143 | | /* debugging output */ |
144 | 52.5k | if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_TRACE) { |
145 | 0 | proj_log_trace(P, |
146 | 0 | "Transformation parameters for observation " |
147 | 0 | "t_obs=%g (t_epoch=%g):", |
148 | 0 | Q->t_obs, Q->t_epoch); |
149 | 0 | proj_log_trace(P, "x: %g", Q->xyz.x); |
150 | 0 | proj_log_trace(P, "y: %g", Q->xyz.y); |
151 | 0 | proj_log_trace(P, "z: %g", Q->xyz.z); |
152 | 0 | proj_log_trace(P, "s: %g", Q->scale * 1e-6); |
153 | 0 | proj_log_trace(P, "rx: %g", Q->opk.o); |
154 | 0 | proj_log_trace(P, "ry: %g", Q->opk.p); |
155 | 0 | proj_log_trace(P, "rz: %g", Q->opk.k); |
156 | 0 | proj_log_trace(P, "theta: %g", Q->theta); |
157 | 0 | } |
158 | 52.5k | } |
159 | | |
160 | | /**************************************************************************/ |
161 | 52.5k | static void build_rot_matrix(PJ *P) { |
162 | | /*************************************************************************** |
163 | | |
164 | | Build rotation matrix. |
165 | | ---------------------- |
166 | | |
167 | | Here we rename rotation indices from omega, phi, kappa (opk), to |
168 | | fi (i.e. phi), theta, psi (ftp), in order to reduce the mental agility |
169 | | needed to implement the expression for the rotation matrix derived over |
170 | | at https://en.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions |
171 | | The relevant section is Euler angles ( z-’-x" intrinsic) -> Rotation |
172 | | matrix |
173 | | |
174 | | By default small angle approximations are used: |
175 | | The matrix elements are approximated by expanding the trigonometric |
176 | | functions to linear order (i.e. cos(x) = 1, sin(x) = x), and discarding |
177 | | products of second order. |
178 | | |
179 | | This was a useful hack when calculating by hand was the only option, |
180 | | but in general, today, should be avoided because: |
181 | | |
182 | | 1. It does not save much computation time, as the rotation matrix |
183 | | is built only once and probably used many times (except when |
184 | | transforming spatio-temporal coordinates). |
185 | | |
186 | | 2. The error induced may be too large for ultra high accuracy |
187 | | applications: the Earth is huge and the linear error is |
188 | | approximately the angular error multiplied by the Earth radius. |
189 | | |
190 | | However, in many cases the approximation is necessary, since it has |
191 | | been used historically: Rotation angles from older published datum |
192 | | shifts may actually be a least squares fit to the linearized rotation |
193 | | approximation, hence not being strictly valid for deriving the exact |
194 | | rotation matrix. In fact, most publicly available transformation |
195 | | parameters are based on the approximate Helmert transform, which is why |
196 | | we use that as the default setting, even though it is more correct to |
197 | | use the exact form of the equations. |
198 | | |
199 | | So in order to fit historically derived coordinates, the access to |
200 | | the approximate rotation matrix is necessary - at least in principle. |
201 | | |
202 | | Also, when using any published datum transformation information, one |
203 | | should always check which convention (exact or approximate rotation |
204 | | matrix) is expected, and whether the induced error for selecting |
205 | | the opposite convention is acceptable (which it often is). |
206 | | |
207 | | |
208 | | Sign conventions |
209 | | ---------------- |
210 | | |
211 | | Take care: Two different sign conventions exist for the rotation terms. |
212 | | |
213 | | Conceptually they relate to whether we rotate the coordinate system |
214 | | or the "position vector" (the vector going from the coordinate system |
215 | | origin to the point being transformed, i.e. the point coordinates |
216 | | interpreted as vector coordinates). |
217 | | |
218 | | Switching between the "position vector" and "coordinate system" |
219 | | conventions is simply a matter of switching the sign of the rotation |
220 | | angles, which algebraically also translates into a transposition of |
221 | | the rotation matrix. |
222 | | |
223 | | Hence, as geodetic constants should preferably be referred to exactly |
224 | | as published, the "convention" option provides the ability to switch |
225 | | between the conventions. |
226 | | |
227 | | ***************************************************************************/ |
228 | 52.5k | struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; |
229 | | |
230 | 52.5k | double f, t, p; /* phi/fi , theta, psi */ |
231 | 52.5k | double cf, ct, cp; /* cos (fi, theta, psi) */ |
232 | 52.5k | double sf, st, sp; /* sin (fi, theta, psi) */ |
233 | | |
234 | | /* rename (omega, phi, kappa) to (fi, theta, psi) */ |
235 | 52.5k | f = Q->opk.o; |
236 | 52.5k | t = Q->opk.p; |
237 | 52.5k | p = Q->opk.k; |
238 | | |
239 | | /* Those equations are given assuming coordinate frame convention. */ |
240 | | /* For the position vector convention, we transpose the matrix just after. |
241 | | */ |
242 | 52.5k | if (Q->exact) { |
243 | 34.6k | cf = cos(f); |
244 | 34.6k | sf = sin(f); |
245 | 34.6k | ct = cos(t); |
246 | 34.6k | st = sin(t); |
247 | 34.6k | cp = cos(p); |
248 | 34.6k | sp = sin(p); |
249 | | |
250 | 34.6k | R00 = ct * cp; |
251 | 34.6k | R01 = cf * sp + sf * st * cp; |
252 | 34.6k | R02 = sf * sp - cf * st * cp; |
253 | | |
254 | 34.6k | R10 = -ct * sp; |
255 | 34.6k | R11 = cf * cp - sf * st * sp; |
256 | 34.6k | R12 = sf * cp + cf * st * sp; |
257 | | |
258 | 34.6k | R20 = st; |
259 | 34.6k | R21 = -sf * ct; |
260 | 34.6k | R22 = cf * ct; |
261 | 34.6k | } else { |
262 | 17.9k | R00 = 1; |
263 | 17.9k | R01 = p; |
264 | 17.9k | R02 = -t; |
265 | | |
266 | 17.9k | R10 = -p; |
267 | 17.9k | R11 = 1; |
268 | 17.9k | R12 = f; |
269 | | |
270 | 17.9k | R20 = t; |
271 | 17.9k | R21 = -f; |
272 | 17.9k | R22 = 1; |
273 | 17.9k | } |
274 | | |
275 | | /* |
276 | | For comparison: Description from Engsager/Poder implementation |
277 | | in set_dtm_1.c (trlib) |
278 | | |
279 | | DATUM SHIFT: |
280 | | TO = scale * ROTZ * ROTY * ROTX * FROM + TRANSLA |
281 | | |
282 | | ( cz sz 0) (cy 0 -sy) (1 0 0) |
283 | | ROTZ=(-sz cz 0), ROTY=(0 1 0), ROTX=(0 cx sx) |
284 | | ( 0 0 1) (sy 0 cy) (0 -sx cx) |
285 | | |
286 | | trp->r11 = cos_ry*cos_rz; |
287 | | trp->r12 = cos_rx*sin_rz + sin_rx*sin_ry*cos_rz; |
288 | | trp->r13 = sin_rx*sin_rz - cos_rx*sin_ry*cos_rz; |
289 | | |
290 | | trp->r21 = -cos_ry*sin_rz; |
291 | | trp->r22 = cos_rx*cos_rz - sin_rx*sin_ry*sin_rz; |
292 | | trp->r23 = sin_rx*cos_rz + cos_rx*sin_ry*sin_rz; |
293 | | |
294 | | trp->r31 = sin_ry; |
295 | | trp->r32 = -sin_rx*cos_ry; |
296 | | trp->r33 = cos_rx*cos_ry; |
297 | | |
298 | | trp->scale = 1.0 + scale; |
299 | | */ |
300 | | |
301 | 52.5k | if (Q->is_position_vector) { |
302 | 1.91k | double r; |
303 | 1.91k | r = R01; |
304 | 1.91k | R01 = R10; |
305 | 1.91k | R10 = r; |
306 | 1.91k | r = R02; |
307 | 1.91k | R02 = R20; |
308 | 1.91k | R20 = r; |
309 | 1.91k | r = R12; |
310 | 1.91k | R12 = R21; |
311 | 1.91k | R21 = r; |
312 | 1.91k | } |
313 | | |
314 | | /* some debugging output */ |
315 | 52.5k | if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_TRACE) { |
316 | 0 | proj_log_trace(P, "Rotation Matrix:"); |
317 | 0 | proj_log_trace(P, " | % 6.6g % 6.6g % 6.6g |", R00, R01, R02); |
318 | 0 | proj_log_trace(P, " | % 6.6g % 6.6g % 6.6g |", R10, R11, R12); |
319 | 0 | proj_log_trace(P, " | % 6.6g % 6.6g % 6.6g |", R20, R21, R22); |
320 | 0 | } |
321 | 52.5k | } |
322 | | |
323 | | /***********************************************************************/ |
324 | 0 | static PJ_XY helmert_forward(PJ_LP lp, PJ *P) { |
325 | | /***********************************************************************/ |
326 | 0 | struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; |
327 | 0 | PJ_COORD point = {{0, 0, 0, 0}}; |
328 | 0 | double x, y, cr, sr; |
329 | 0 | point.lp = lp; |
330 | |
|
331 | 0 | cr = cos(Q->theta) * Q->scale; |
332 | 0 | sr = sin(Q->theta) * Q->scale; |
333 | 0 | x = point.xy.x; |
334 | 0 | y = point.xy.y; |
335 | |
|
336 | 0 | point.xy.x = cr * x + sr * y + Q->xyz_0.x; |
337 | 0 | point.xy.y = -sr * x + cr * y + Q->xyz_0.y; |
338 | |
|
339 | 0 | return point.xy; |
340 | 0 | } |
341 | | |
342 | | /***********************************************************************/ |
343 | 0 | static PJ_LP helmert_reverse(PJ_XY xy, PJ *P) { |
344 | | /***********************************************************************/ |
345 | 0 | struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; |
346 | 0 | PJ_COORD point = {{0, 0, 0, 0}}; |
347 | 0 | double x, y, sr, cr; |
348 | 0 | point.xy = xy; |
349 | |
|
350 | 0 | cr = cos(Q->theta) / Q->scale; |
351 | 0 | sr = sin(Q->theta) / Q->scale; |
352 | 0 | x = point.xy.x - Q->xyz_0.x; |
353 | 0 | y = point.xy.y - Q->xyz_0.y; |
354 | |
|
355 | 0 | point.xy.x = x * cr - y * sr; |
356 | 0 | point.xy.y = x * sr + y * cr; |
357 | |
|
358 | 0 | return point.lp; |
359 | 0 | } |
360 | | |
361 | | /***********************************************************************/ |
362 | 162k | static PJ_XYZ helmert_forward_3d(PJ_LPZ lpz, PJ *P) { |
363 | | /***********************************************************************/ |
364 | 162k | struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; |
365 | 162k | PJ_COORD point = {{0, 0, 0, 0}}; |
366 | 162k | double X, Y, Z, scale; |
367 | | |
368 | 162k | point.lpz = lpz; |
369 | | |
370 | 162k | if (Q->fourparam) { |
371 | 0 | const auto xy = helmert_forward(point.lp, P); |
372 | 0 | point.xy = xy; |
373 | 0 | return point.xyz; |
374 | 0 | } |
375 | | |
376 | 162k | if (Q->no_rotation && Q->scale == 0) { |
377 | 159k | point.xyz.x = lpz.lam + Q->xyz.x; |
378 | 159k | point.xyz.y = lpz.phi + Q->xyz.y; |
379 | 159k | point.xyz.z = lpz.z + Q->xyz.z; |
380 | 159k | return point.xyz; |
381 | 159k | } |
382 | | |
383 | 3.19k | scale = 1 + Q->scale * 1e-6; |
384 | | |
385 | 3.19k | X = lpz.lam - Q->refp.x; |
386 | 3.19k | Y = lpz.phi - Q->refp.y; |
387 | 3.19k | Z = lpz.z - Q->refp.z; |
388 | | |
389 | 3.19k | point.xyz.x = scale * (R00 * X + R01 * Y + R02 * Z); |
390 | 3.19k | point.xyz.y = scale * (R10 * X + R11 * Y + R12 * Z); |
391 | 3.19k | point.xyz.z = scale * (R20 * X + R21 * Y + R22 * Z); |
392 | | |
393 | 3.19k | point.xyz.x += Q->xyz.x; /* for Molodensky-Badekas, Q->xyz already |
394 | | incorporates the Q->refp offset */ |
395 | 3.19k | point.xyz.y += Q->xyz.y; |
396 | 3.19k | point.xyz.z += Q->xyz.z; |
397 | | |
398 | 3.19k | return point.xyz; |
399 | 162k | } |
400 | | |
401 | | /***********************************************************************/ |
402 | 261k | static PJ_LPZ helmert_reverse_3d(PJ_XYZ xyz, PJ *P) { |
403 | | /***********************************************************************/ |
404 | 261k | struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; |
405 | 261k | PJ_COORD point = {{0, 0, 0, 0}}; |
406 | 261k | double X, Y, Z, scale; |
407 | | |
408 | 261k | point.xyz = xyz; |
409 | | |
410 | 261k | if (Q->fourparam) { |
411 | 0 | const auto lp = helmert_reverse(point.xy, P); |
412 | 0 | point.lp = lp; |
413 | 0 | return point.lpz; |
414 | 0 | } |
415 | | |
416 | 261k | if (Q->no_rotation && Q->scale == 0) { |
417 | 254k | point.xyz.x = xyz.x - Q->xyz.x; |
418 | 254k | point.xyz.y = xyz.y - Q->xyz.y; |
419 | 254k | point.xyz.z = xyz.z - Q->xyz.z; |
420 | 254k | return point.lpz; |
421 | 254k | } |
422 | | |
423 | 6.55k | scale = 1 + Q->scale * 1e-6; |
424 | | |
425 | | /* Unscale and deoffset */ |
426 | 6.55k | X = (xyz.x - Q->xyz.x) / scale; |
427 | 6.55k | Y = (xyz.y - Q->xyz.y) / scale; |
428 | 6.55k | Z = (xyz.z - Q->xyz.z) / scale; |
429 | | |
430 | | /* Inverse rotation through transpose multiplication */ |
431 | 6.55k | point.xyz.x = (R00 * X + R10 * Y + R20 * Z) + Q->refp.x; |
432 | 6.55k | point.xyz.y = (R01 * X + R11 * Y + R21 * Z) + Q->refp.y; |
433 | 6.55k | point.xyz.z = (R02 * X + R12 * Y + R22 * Z) + Q->refp.z; |
434 | | |
435 | 6.55k | return point.lpz; |
436 | 261k | } |
437 | | |
438 | 162k | static void helmert_forward_4d(PJ_COORD &point, PJ *P) { |
439 | 162k | struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; |
440 | | |
441 | | /* We only need to rebuild the rotation matrix if the |
442 | | * observation time is different from the last call */ |
443 | 162k | double t_obs = (point.xyzt.t == HUGE_VAL) ? Q->t_epoch : point.xyzt.t; |
444 | 162k | if (t_obs != Q->t_obs) { |
445 | 0 | Q->t_obs = t_obs; |
446 | 0 | update_parameters(P); |
447 | 0 | build_rot_matrix(P); |
448 | 0 | } |
449 | | |
450 | | // Assigning in 2 steps avoids cppcheck warning |
451 | | // "Overlapping read/write of union is undefined behavior" |
452 | | // Cf https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 |
453 | 162k | const auto xyz = helmert_forward_3d(point.lpz, P); |
454 | 162k | point.xyz = xyz; |
455 | 162k | } |
456 | | |
457 | 261k | static void helmert_reverse_4d(PJ_COORD &point, PJ *P) { |
458 | 261k | struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; |
459 | | |
460 | | /* We only need to rebuild the rotation matrix if the |
461 | | * observation time is different from the last call */ |
462 | 261k | double t_obs = (point.xyzt.t == HUGE_VAL) ? Q->t_epoch : point.xyzt.t; |
463 | 261k | if (t_obs != Q->t_obs) { |
464 | 0 | Q->t_obs = t_obs; |
465 | 0 | update_parameters(P); |
466 | 0 | build_rot_matrix(P); |
467 | 0 | } |
468 | | |
469 | | // Assigning in 2 steps avoids cppcheck warning |
470 | | // "Overlapping read/write of union is undefined behavior" |
471 | | // Cf https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 |
472 | 261k | const auto lpz = helmert_reverse_3d(point.xyz, P); |
473 | 261k | point.lpz = lpz; |
474 | 261k | } |
475 | | |
476 | | /* Arcsecond to radians */ |
477 | 9.64k | #define ARCSEC_TO_RAD (DEG_TO_RAD / 3600.0) |
478 | | |
479 | 52.6k | static PJ *init_helmert_six_parameters(PJ *P) { |
480 | 52.6k | struct pj_opaque_helmert *Q = static_cast<struct pj_opaque_helmert *>( |
481 | 52.6k | calloc(1, sizeof(struct pj_opaque_helmert))); |
482 | 52.6k | if (nullptr == Q) |
483 | 0 | return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); |
484 | 52.6k | P->opaque = (void *)Q; |
485 | | |
486 | | /* In most cases, we work on 3D cartesian coordinates */ |
487 | 52.6k | P->left = PJ_IO_UNITS_CARTESIAN; |
488 | 52.6k | P->right = PJ_IO_UNITS_CARTESIAN; |
489 | | |
490 | | /* Translations */ |
491 | 52.6k | if (pj_param(P->ctx, P->params, "tx").i) |
492 | 17.2k | Q->xyz_0.x = pj_param(P->ctx, P->params, "dx").f; |
493 | | |
494 | 52.6k | if (pj_param(P->ctx, P->params, "ty").i) |
495 | 17.3k | Q->xyz_0.y = pj_param(P->ctx, P->params, "dy").f; |
496 | | |
497 | 52.6k | if (pj_param(P->ctx, P->params, "tz").i) |
498 | 17.1k | Q->xyz_0.z = pj_param(P->ctx, P->params, "dz").f; |
499 | | |
500 | | /* Rotations */ |
501 | 52.6k | if (pj_param(P->ctx, P->params, "trx").i) |
502 | 2.45k | Q->opk_0.o = pj_param(P->ctx, P->params, "drx").f * ARCSEC_TO_RAD; |
503 | | |
504 | 52.6k | if (pj_param(P->ctx, P->params, "try").i) |
505 | 2.45k | Q->opk_0.p = pj_param(P->ctx, P->params, "dry").f * ARCSEC_TO_RAD; |
506 | | |
507 | 52.6k | if (pj_param(P->ctx, P->params, "trz").i) |
508 | 2.45k | Q->opk_0.k = pj_param(P->ctx, P->params, "drz").f * ARCSEC_TO_RAD; |
509 | | |
510 | | /* Use small angle approximations? */ |
511 | 52.6k | if (pj_param(P->ctx, P->params, "bexact").i) |
512 | 34.6k | Q->exact = 1; |
513 | | |
514 | 52.6k | return P; |
515 | 52.6k | } |
516 | | |
517 | 52.6k | static PJ *read_convention(PJ *P) { |
518 | | |
519 | 52.6k | struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; |
520 | | |
521 | | /* In case there are rotational terms, we require an explicit convention |
522 | | * to be provided. */ |
523 | 52.6k | if (!Q->no_rotation) { |
524 | 3.56k | const char *convention = pj_param(P->ctx, P->params, "sconvention").s; |
525 | 3.56k | if (!convention) { |
526 | 18 | proj_log_error(P, _("helmert: missing 'convention' argument")); |
527 | 18 | return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
528 | 18 | } |
529 | 3.54k | if (strcmp(convention, "position_vector") == 0) { |
530 | 1.91k | Q->is_position_vector = 1; |
531 | 1.91k | } else if (strcmp(convention, "coordinate_frame") == 0) { |
532 | 1.61k | Q->is_position_vector = 0; |
533 | 1.61k | } else { |
534 | 14 | proj_log_error( |
535 | 14 | P, _("helmert: invalid value for 'convention' argument")); |
536 | 14 | return pj_default_destructor(P, |
537 | 14 | PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
538 | 14 | } |
539 | | |
540 | | /* historically towgs84 in PROJ has always been using position_vector |
541 | | * convention. Accepting coordinate_frame would be confusing. */ |
542 | 3.52k | if (pj_param_exists(P->params, "towgs84")) { |
543 | 1.32k | if (!Q->is_position_vector) { |
544 | 65 | proj_log_error(P, _("helmert: towgs84 should only be used with " |
545 | 65 | "convention=position_vector")); |
546 | 65 | return pj_default_destructor( |
547 | 65 | P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
548 | 65 | } |
549 | 1.32k | } |
550 | 3.52k | } |
551 | | |
552 | 52.5k | return P; |
553 | 52.6k | } |
554 | | |
555 | | /***********************************************************************/ |
556 | 52.5k | PJ *PJ_TRANSFORMATION(helmert, 0) { |
557 | | /***********************************************************************/ |
558 | | |
559 | 52.5k | struct pj_opaque_helmert *Q; |
560 | | |
561 | 52.5k | if (!init_helmert_six_parameters(P)) { |
562 | 0 | return nullptr; |
563 | 0 | } |
564 | | |
565 | | /* In the 2D case, the coordinates are projected */ |
566 | 52.5k | if (pj_param_exists(P->params, "theta")) { |
567 | 49 | P->left = PJ_IO_UNITS_PROJECTED; |
568 | 49 | P->right = PJ_IO_UNITS_PROJECTED; |
569 | 49 | P->fwd = helmert_forward; |
570 | 49 | P->inv = helmert_reverse; |
571 | 49 | } |
572 | | |
573 | 52.5k | P->fwd4d = helmert_forward_4d; |
574 | 52.5k | P->inv4d = helmert_reverse_4d; |
575 | 52.5k | P->fwd3d = helmert_forward_3d; |
576 | 52.5k | P->inv3d = helmert_reverse_3d; |
577 | | |
578 | 52.5k | Q = (struct pj_opaque_helmert *)P->opaque; |
579 | | |
580 | | /* Detect obsolete transpose flag and error out if found */ |
581 | 52.5k | if (pj_param(P->ctx, P->params, "ttranspose").i) { |
582 | 0 | proj_log_error(P, _("helmert: 'transpose' argument is no longer valid. " |
583 | 0 | "Use convention=position_vector/coordinate_frame")); |
584 | 0 | return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
585 | 0 | } |
586 | | |
587 | | /* Support the classic PROJ towgs84 parameter, but allow later overrides.*/ |
588 | | /* Note that if towgs84 is specified, the datum_params array is set up */ |
589 | | /* for us automagically by the pj_datum_set call in pj_init_ctx */ |
590 | 52.5k | if (pj_param_exists(P->params, "towgs84")) { |
591 | 37.1k | Q->xyz_0.x = P->datum_params[0]; |
592 | 37.1k | Q->xyz_0.y = P->datum_params[1]; |
593 | 37.1k | Q->xyz_0.z = P->datum_params[2]; |
594 | | |
595 | 37.1k | Q->opk_0.o = P->datum_params[3]; |
596 | 37.1k | Q->opk_0.p = P->datum_params[4]; |
597 | 37.1k | Q->opk_0.k = P->datum_params[5]; |
598 | | |
599 | | /* We must undo conversion to absolute scale from pj_datum_set */ |
600 | 37.1k | if (0 == P->datum_params[6]) |
601 | 35.8k | Q->scale_0 = 0; |
602 | 1.30k | else |
603 | 1.30k | Q->scale_0 = (P->datum_params[6] - 1) * 1e6; |
604 | 37.1k | } |
605 | | |
606 | 52.5k | if (pj_param(P->ctx, P->params, "ttheta").i) { |
607 | 49 | Q->theta_0 = pj_param(P->ctx, P->params, "dtheta").f * ARCSEC_TO_RAD; |
608 | 49 | Q->fourparam = 1; |
609 | 49 | Q->scale_0 = 1.0; /* default scale for the 4-param shift */ |
610 | 49 | } |
611 | | |
612 | | /* Scale */ |
613 | 52.5k | if (pj_param(P->ctx, P->params, "ts").i) { |
614 | 2.47k | Q->scale_0 = pj_param(P->ctx, P->params, "ds").f; |
615 | 2.47k | if (Q->scale_0 <= -1.0e6) { |
616 | 1 | proj_log_error(P, _("helmert: invalid value for s.")); |
617 | 1 | return pj_default_destructor(P, |
618 | 1 | PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
619 | 1 | } |
620 | 2.47k | if (pj_param(P->ctx, P->params, "ttheta").i && Q->scale_0 == 0.0) { |
621 | 1 | proj_log_error(P, _("helmert: invalid value for s.")); |
622 | 1 | return pj_default_destructor(P, |
623 | 1 | PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
624 | 1 | } |
625 | 2.47k | } |
626 | | |
627 | | /* Translation rates */ |
628 | 52.5k | if (pj_param(P->ctx, P->params, "tdx").i) |
629 | 735 | Q->dxyz.x = pj_param(P->ctx, P->params, "ddx").f; |
630 | | |
631 | 52.5k | if (pj_param(P->ctx, P->params, "tdy").i) |
632 | 735 | Q->dxyz.y = pj_param(P->ctx, P->params, "ddy").f; |
633 | | |
634 | 52.5k | if (pj_param(P->ctx, P->params, "tdz").i) |
635 | 735 | Q->dxyz.z = pj_param(P->ctx, P->params, "ddz").f; |
636 | | |
637 | | /* Rotations rates */ |
638 | 52.5k | if (pj_param(P->ctx, P->params, "tdrx").i) |
639 | 735 | Q->dopk.o = pj_param(P->ctx, P->params, "ddrx").f * ARCSEC_TO_RAD; |
640 | | |
641 | 52.5k | if (pj_param(P->ctx, P->params, "tdry").i) |
642 | 738 | Q->dopk.p = pj_param(P->ctx, P->params, "ddry").f * ARCSEC_TO_RAD; |
643 | | |
644 | 52.5k | if (pj_param(P->ctx, P->params, "tdrz").i) |
645 | 737 | Q->dopk.k = pj_param(P->ctx, P->params, "ddrz").f * ARCSEC_TO_RAD; |
646 | | |
647 | 52.5k | if (pj_param(P->ctx, P->params, "tdtheta").i) |
648 | 18 | Q->dtheta = pj_param(P->ctx, P->params, "ddtheta").f * ARCSEC_TO_RAD; |
649 | | |
650 | | /* Scale rate */ |
651 | 52.5k | if (pj_param(P->ctx, P->params, "tds").i) |
652 | 735 | Q->dscale = pj_param(P->ctx, P->params, "dds").f; |
653 | | |
654 | | /* Epoch */ |
655 | 52.5k | if (pj_param(P->ctx, P->params, "tt_epoch").i) |
656 | 741 | Q->t_epoch = pj_param(P->ctx, P->params, "dt_epoch").f; |
657 | | |
658 | 52.5k | Q->xyz = Q->xyz_0; |
659 | 52.5k | Q->opk = Q->opk_0; |
660 | 52.5k | Q->scale = Q->scale_0; |
661 | 52.5k | Q->theta = Q->theta_0; |
662 | | |
663 | 52.5k | if ((Q->opk.o == 0) && (Q->opk.p == 0) && (Q->opk.k == 0) && |
664 | 52.5k | (Q->dopk.o == 0) && (Q->dopk.p == 0) && (Q->dopk.k == 0)) { |
665 | 49.0k | Q->no_rotation = 1; |
666 | 49.0k | } |
667 | | |
668 | 52.5k | if (!read_convention(P)) { |
669 | 85 | return nullptr; |
670 | 85 | } |
671 | | |
672 | | /* Let's help with debugging */ |
673 | 52.5k | if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_TRACE) { |
674 | 0 | proj_log_trace(P, "Helmert parameters:"); |
675 | 0 | proj_log_trace(P, "x= %8.5f y= %8.5f z= %8.5f", Q->xyz.x, Q->xyz.y, |
676 | 0 | Q->xyz.z); |
677 | 0 | proj_log_trace(P, "rx= %8.5f ry= %8.5f rz= %8.5f", |
678 | 0 | Q->opk.o / ARCSEC_TO_RAD, Q->opk.p / ARCSEC_TO_RAD, |
679 | 0 | Q->opk.k / ARCSEC_TO_RAD); |
680 | 0 | proj_log_trace(P, "s= %8.5f exact=%d%s", Q->scale, Q->exact, |
681 | 0 | Q->no_rotation ? "" |
682 | 0 | : Q->is_position_vector |
683 | 0 | ? " convention=position_vector" |
684 | 0 | : " convention=coordinate_frame"); |
685 | 0 | proj_log_trace(P, "dx= %8.5f dy= %8.5f dz= %8.5f", Q->dxyz.x, |
686 | 0 | Q->dxyz.y, Q->dxyz.z); |
687 | 0 | proj_log_trace(P, "drx=%8.5f dry=%8.5f drz=%8.5f", Q->dopk.o, |
688 | 0 | Q->dopk.p, Q->dopk.k); |
689 | 0 | proj_log_trace(P, "ds= %8.5f t_epoch=%8.5f", Q->dscale, Q->t_epoch); |
690 | 0 | } |
691 | | |
692 | 52.5k | update_parameters(P); |
693 | 52.5k | build_rot_matrix(P); |
694 | | |
695 | 52.5k | return P; |
696 | 52.5k | } |
697 | | |
698 | | /***********************************************************************/ |
699 | 40 | PJ *PJ_TRANSFORMATION(molobadekas, 0) { |
700 | | /***********************************************************************/ |
701 | | |
702 | 40 | struct pj_opaque_helmert *Q; |
703 | | |
704 | 40 | if (!init_helmert_six_parameters(P)) { |
705 | 0 | return nullptr; |
706 | 0 | } |
707 | | |
708 | 40 | P->fwd3d = helmert_forward_3d; |
709 | 40 | P->inv3d = helmert_reverse_3d; |
710 | | |
711 | 40 | Q = (struct pj_opaque_helmert *)P->opaque; |
712 | | |
713 | | /* Scale */ |
714 | 40 | if (pj_param(P->ctx, P->params, "ts").i) { |
715 | 13 | Q->scale_0 = pj_param(P->ctx, P->params, "ds").f; |
716 | 13 | } |
717 | | |
718 | 40 | Q->opk = Q->opk_0; |
719 | 40 | Q->scale = Q->scale_0; |
720 | | |
721 | 40 | if (!read_convention(P)) { |
722 | 12 | return nullptr; |
723 | 12 | } |
724 | | |
725 | | /* Reference point */ |
726 | 28 | if (pj_param(P->ctx, P->params, "tpx").i) |
727 | 10 | Q->refp.x = pj_param(P->ctx, P->params, "dpx").f; |
728 | | |
729 | 28 | if (pj_param(P->ctx, P->params, "tpy").i) |
730 | 13 | Q->refp.y = pj_param(P->ctx, P->params, "dpy").f; |
731 | | |
732 | 28 | if (pj_param(P->ctx, P->params, "tpz").i) |
733 | 10 | Q->refp.z = pj_param(P->ctx, P->params, "dpz").f; |
734 | | |
735 | | /* Let's help with debugging */ |
736 | 28 | if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_TRACE) { |
737 | 0 | proj_log_trace(P, "Molodensky-Badekas parameters:"); |
738 | 0 | proj_log_trace(P, "x= %8.5f y= %8.5f z= %8.5f", Q->xyz_0.x, |
739 | 0 | Q->xyz_0.y, Q->xyz_0.z); |
740 | 0 | proj_log_trace(P, "rx= %8.5f ry= %8.5f rz= %8.5f", |
741 | 0 | Q->opk.o / ARCSEC_TO_RAD, Q->opk.p / ARCSEC_TO_RAD, |
742 | 0 | Q->opk.k / ARCSEC_TO_RAD); |
743 | 0 | proj_log_trace(P, "s= %8.5f exact=%d%s", Q->scale, Q->exact, |
744 | 0 | Q->is_position_vector ? " convention=position_vector" |
745 | 0 | : " convention=coordinate_frame"); |
746 | 0 | proj_log_trace(P, "px= %8.5f py= %8.5f pz= %8.5f", Q->refp.x, |
747 | 0 | Q->refp.y, Q->refp.z); |
748 | 0 | } |
749 | | |
750 | | /* as an optimization, we incorporate the refp in the translation terms */ |
751 | 28 | Q->xyz_0.x += Q->refp.x; |
752 | 28 | Q->xyz_0.y += Q->refp.y; |
753 | 28 | Q->xyz_0.z += Q->refp.z; |
754 | | |
755 | 28 | Q->xyz = Q->xyz_0; |
756 | | |
757 | 28 | build_rot_matrix(P); |
758 | | |
759 | 28 | return P; |
760 | 40 | } |