Coverage Report

Created: 2026-04-09 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/transformations/helmert.cpp
Line
Count
Source
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
90.5k
#define R00 (Q->R[0][0])
90
96.5k
#define R01 (Q->R[0][1])
91
96.5k
#define R02 (Q->R[0][2])
92
93
96.5k
#define R10 (Q->R[1][0])
94
90.5k
#define R11 (Q->R[1][1])
95
96.5k
#define R12 (Q->R[1][2])
96
97
96.5k
#define R20 (Q->R[2][0])
98
96.5k
#define R21 (Q->R[2][1])
99
90.5k
#define R22 (Q->R[2][2])
100
101
/**************************************************************************/
102
47.4k
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
47.4k
    struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque;
129
47.4k
    double dt = Q->t_obs - Q->t_epoch;
130
131
47.4k
    Q->xyz.x = Q->xyz_0.x + Q->dxyz.x * dt;
132
47.4k
    Q->xyz.y = Q->xyz_0.y + Q->dxyz.y * dt;
133
47.4k
    Q->xyz.z = Q->xyz_0.z + Q->dxyz.z * dt;
134
135
47.4k
    Q->opk.o = Q->opk_0.o + Q->dopk.o * dt;
136
47.4k
    Q->opk.p = Q->opk_0.p + Q->dopk.p * dt;
137
47.4k
    Q->opk.k = Q->opk_0.k + Q->dopk.k * dt;
138
139
47.4k
    Q->scale = Q->scale_0 + Q->dscale * dt;
140
141
47.4k
    Q->theta = Q->theta_0 + Q->dtheta * dt;
142
143
    /* debugging output */
144
47.4k
    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
47.4k
}
159
160
/**************************************************************************/
161
47.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
47.5k
    struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque;
229
230
47.5k
    double f, t, p;    /* phi/fi , theta, psi  */
231
47.5k
    double cf, ct, cp; /* cos (fi, theta, psi) */
232
47.5k
    double sf, st, sp; /* sin (fi, theta, psi) */
233
234
    /* rename   (omega, phi, kappa)   to   (fi, theta, psi)   */
235
47.5k
    f = Q->opk.o;
236
47.5k
    t = Q->opk.p;
237
47.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
47.5k
    if (Q->exact) {
243
31.5k
        cf = cos(f);
244
31.5k
        sf = sin(f);
245
31.5k
        ct = cos(t);
246
31.5k
        st = sin(t);
247
31.5k
        cp = cos(p);
248
31.5k
        sp = sin(p);
249
250
31.5k
        R00 = ct * cp;
251
31.5k
        R01 = cf * sp + sf * st * cp;
252
31.5k
        R02 = sf * sp - cf * st * cp;
253
254
31.5k
        R10 = -ct * sp;
255
31.5k
        R11 = cf * cp - sf * st * sp;
256
31.5k
        R12 = sf * cp + cf * st * sp;
257
258
31.5k
        R20 = st;
259
31.5k
        R21 = -sf * ct;
260
31.5k
        R22 = cf * ct;
261
31.5k
    } else {
262
15.9k
        R00 = 1;
263
15.9k
        R01 = p;
264
15.9k
        R02 = -t;
265
266
15.9k
        R10 = -p;
267
15.9k
        R11 = 1;
268
15.9k
        R12 = f;
269
270
15.9k
        R20 = t;
271
15.9k
        R21 = -f;
272
15.9k
        R22 = 1;
273
15.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
47.5k
    if (Q->is_position_vector) {
302
2.99k
        double r;
303
2.99k
        r = R01;
304
2.99k
        R01 = R10;
305
2.99k
        R10 = r;
306
2.99k
        r = R02;
307
2.99k
        R02 = R20;
308
2.99k
        R20 = r;
309
2.99k
        r = R12;
310
2.99k
        R12 = R21;
311
2.99k
        R21 = r;
312
2.99k
    }
313
314
    /* some debugging output */
315
47.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
47.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
140k
static PJ_XYZ helmert_forward_3d(PJ_LPZ lpz, PJ *P) {
363
    /***********************************************************************/
364
140k
    struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque;
365
140k
    PJ_COORD point = {{0, 0, 0, 0}};
366
140k
    double X, Y, Z, scale;
367
368
140k
    point.lpz = lpz;
369
370
140k
    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
140k
    if (Q->no_rotation && Q->scale == 0) {
377
121k
        point.xyz.x = lpz.lam + Q->xyz.x;
378
121k
        point.xyz.y = lpz.phi + Q->xyz.y;
379
121k
        point.xyz.z = lpz.z + Q->xyz.z;
380
121k
        return point.xyz;
381
121k
    }
382
383
18.3k
    scale = 1 + Q->scale * 1e-6;
384
385
18.3k
    X = lpz.lam - Q->refp.x;
386
18.3k
    Y = lpz.phi - Q->refp.y;
387
18.3k
    Z = lpz.z - Q->refp.z;
388
389
18.3k
    point.xyz.x = scale * (R00 * X + R01 * Y + R02 * Z);
390
18.3k
    point.xyz.y = scale * (R10 * X + R11 * Y + R12 * Z);
391
18.3k
    point.xyz.z = scale * (R20 * X + R21 * Y + R22 * Z);
392
393
18.3k
    point.xyz.x += Q->xyz.x; /* for Molodensky-Badekas, Q->xyz already
394
                                incorporates the Q->refp offset */
395
18.3k
    point.xyz.y += Q->xyz.y;
396
18.3k
    point.xyz.z += Q->xyz.z;
397
398
18.3k
    return point.xyz;
399
140k
}
400
401
/***********************************************************************/
402
320k
static PJ_LPZ helmert_reverse_3d(PJ_XYZ xyz, PJ *P) {
403
    /***********************************************************************/
404
320k
    struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque;
405
320k
    PJ_COORD point = {{0, 0, 0, 0}};
406
320k
    double X, Y, Z, scale;
407
408
320k
    point.xyz = xyz;
409
410
320k
    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
320k
    if (Q->no_rotation && Q->scale == 0) {
417
295k
        point.xyz.x = xyz.x - Q->xyz.x;
418
295k
        point.xyz.y = xyz.y - Q->xyz.y;
419
295k
        point.xyz.z = xyz.z - Q->xyz.z;
420
295k
        return point.lpz;
421
295k
    }
422
423
24.6k
    scale = 1 + Q->scale * 1e-6;
424
425
    /* Unscale and deoffset */
426
24.6k
    X = (xyz.x - Q->xyz.x) / scale;
427
24.6k
    Y = (xyz.y - Q->xyz.y) / scale;
428
24.6k
    Z = (xyz.z - Q->xyz.z) / scale;
429
430
    /* Inverse rotation through transpose multiplication */
431
24.6k
    point.xyz.x = (R00 * X + R10 * Y + R20 * Z) + Q->refp.x;
432
24.6k
    point.xyz.y = (R01 * X + R11 * Y + R21 * Z) + Q->refp.y;
433
24.6k
    point.xyz.z = (R02 * X + R12 * Y + R22 * Z) + Q->refp.z;
434
435
24.6k
    return point.lpz;
436
320k
}
437
438
140k
static void helmert_forward_4d(PJ_COORD &point, PJ *P) {
439
140k
    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
140k
    double t_obs = (point.xyzt.t == HUGE_VAL) ? Q->t_epoch : point.xyzt.t;
444
140k
    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
140k
    const auto xyz = helmert_forward_3d(point.lpz, P);
454
140k
    point.xyz = xyz;
455
140k
}
456
457
320k
static void helmert_reverse_4d(PJ_COORD &point, PJ *P) {
458
320k
    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
320k
    double t_obs = (point.xyzt.t == HUGE_VAL) ? Q->t_epoch : point.xyzt.t;
463
320k
    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
320k
    const auto lpz = helmert_reverse_3d(point.xyz, P);
473
320k
    point.lpz = lpz;
474
320k
}
475
476
/* Arcsecond to radians */
477
15.5k
#define ARCSEC_TO_RAD (DEG_TO_RAD / 3600.0)
478
479
47.6k
static PJ *init_helmert_six_parameters(PJ *P) {
480
47.6k
    struct pj_opaque_helmert *Q = static_cast<struct pj_opaque_helmert *>(
481
47.6k
        calloc(1, sizeof(struct pj_opaque_helmert)));
482
47.6k
    if (nullptr == Q)
483
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
484
47.6k
    P->opaque = (void *)Q;
485
486
    /* In most cases, we work on 3D cartesian coordinates */
487
47.6k
    P->left = PJ_IO_UNITS_CARTESIAN;
488
47.6k
    P->right = PJ_IO_UNITS_CARTESIAN;
489
490
    /* Translations */
491
47.6k
    if (pj_param(P->ctx, P->params, "tx").i)
492
15.7k
        Q->xyz_0.x = pj_param(P->ctx, P->params, "dx").f;
493
494
47.6k
    if (pj_param(P->ctx, P->params, "ty").i)
495
15.7k
        Q->xyz_0.y = pj_param(P->ctx, P->params, "dy").f;
496
497
47.6k
    if (pj_param(P->ctx, P->params, "tz").i)
498
15.6k
        Q->xyz_0.z = pj_param(P->ctx, P->params, "dz").f;
499
500
    /* Rotations */
501
47.6k
    if (pj_param(P->ctx, P->params, "trx").i)
502
3.58k
        Q->opk_0.o = pj_param(P->ctx, P->params, "drx").f * ARCSEC_TO_RAD;
503
504
47.6k
    if (pj_param(P->ctx, P->params, "try").i)
505
3.58k
        Q->opk_0.p = pj_param(P->ctx, P->params, "dry").f * ARCSEC_TO_RAD;
506
507
47.6k
    if (pj_param(P->ctx, P->params, "trz").i)
508
3.58k
        Q->opk_0.k = pj_param(P->ctx, P->params, "drz").f * ARCSEC_TO_RAD;
509
510
    /* Use small angle approximations? */
511
47.6k
    if (pj_param(P->ctx, P->params, "bexact").i)
512
31.5k
        Q->exact = 1;
513
514
47.6k
    return P;
515
47.6k
}
516
517
47.6k
static PJ *read_convention(PJ *P) {
518
519
47.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
47.6k
    if (!Q->no_rotation) {
524
5.24k
        const char *convention = pj_param(P->ctx, P->params, "sconvention").s;
525
5.24k
        if (!convention) {
526
78
            proj_log_error(P, _("helmert: missing 'convention' argument"));
527
78
            return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
528
78
        }
529
5.16k
        if (strcmp(convention, "position_vector") == 0) {
530
2.99k
            Q->is_position_vector = 1;
531
2.99k
        } else if (strcmp(convention, "coordinate_frame") == 0) {
532
2.16k
            Q->is_position_vector = 0;
533
2.16k
        } else {
534
3
            proj_log_error(
535
3
                P, _("helmert: invalid value for 'convention' argument"));
536
3
            return pj_default_destructor(P,
537
3
                                         PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
538
3
        }
539
540
        /* historically towgs84 in PROJ has always been using position_vector
541
         * convention. Accepting coordinate_frame would be confusing. */
542
5.15k
        if (pj_param_exists(P->params, "towgs84")) {
543
1.93k
            if (!Q->is_position_vector) {
544
30
                proj_log_error(P, _("helmert: towgs84 should only be used with "
545
30
                                    "convention=position_vector"));
546
30
                return pj_default_destructor(
547
30
                    P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
548
30
            }
549
1.93k
        }
550
5.15k
    }
551
552
47.5k
    return P;
553
47.6k
}
554
555
/***********************************************************************/
556
47.5k
PJ *PJ_TRANSFORMATION(helmert, 0) {
557
    /***********************************************************************/
558
559
47.5k
    struct pj_opaque_helmert *Q;
560
561
47.5k
    if (!init_helmert_six_parameters(P)) {
562
0
        return nullptr;
563
0
    }
564
565
    /* In the 2D case, the coordinates are projected */
566
47.5k
    if (pj_param_exists(P->params, "theta")) {
567
23
        P->left = PJ_IO_UNITS_PROJECTED;
568
23
        P->right = PJ_IO_UNITS_PROJECTED;
569
23
        P->fwd = helmert_forward;
570
23
        P->inv = helmert_reverse;
571
23
    }
572
573
47.5k
    P->fwd4d = helmert_forward_4d;
574
47.5k
    P->inv4d = helmert_reverse_4d;
575
47.5k
    P->fwd3d = helmert_forward_3d;
576
47.5k
    P->inv3d = helmert_reverse_3d;
577
578
47.5k
    Q = (struct pj_opaque_helmert *)P->opaque;
579
580
    /* Detect obsolete transpose flag and error out if found */
581
47.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
47.5k
    if (pj_param_exists(P->params, "towgs84")) {
591
33.8k
        Q->xyz_0.x = P->datum_params[0];
592
33.8k
        Q->xyz_0.y = P->datum_params[1];
593
33.8k
        Q->xyz_0.z = P->datum_params[2];
594
595
33.8k
        Q->opk_0.o = P->datum_params[3];
596
33.8k
        Q->opk_0.p = P->datum_params[4];
597
33.8k
        Q->opk_0.k = P->datum_params[5];
598
599
        /* We must undo conversion to absolute scale from pj_datum_set */
600
33.8k
        if (0 == P->datum_params[6])
601
31.8k
            Q->scale_0 = 0;
602
1.96k
        else
603
1.96k
            Q->scale_0 = (P->datum_params[6] - 1) * 1e6;
604
33.8k
    }
605
606
47.5k
    if (pj_param(P->ctx, P->params, "ttheta").i) {
607
23
        Q->theta_0 = pj_param(P->ctx, P->params, "dtheta").f * ARCSEC_TO_RAD;
608
23
        Q->fourparam = 1;
609
23
        Q->scale_0 = 1.0; /* default scale for the 4-param shift */
610
23
    }
611
612
    /* Scale */
613
47.5k
    if (pj_param(P->ctx, P->params, "ts").i) {
614
3.56k
        Q->scale_0 = pj_param(P->ctx, P->params, "ds").f;
615
3.56k
        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
3.56k
        if (pj_param(P->ctx, P->params, "ttheta").i && Q->scale_0 == 0.0) {
621
3
            proj_log_error(P, _("helmert: invalid value for s."));
622
3
            return pj_default_destructor(P,
623
3
                                         PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
624
3
        }
625
3.56k
    }
626
627
    /* Translation rates */
628
47.5k
    if (pj_param(P->ctx, P->params, "tdx").i)
629
1.59k
        Q->dxyz.x = pj_param(P->ctx, P->params, "ddx").f;
630
631
47.5k
    if (pj_param(P->ctx, P->params, "tdy").i)
632
1.59k
        Q->dxyz.y = pj_param(P->ctx, P->params, "ddy").f;
633
634
47.5k
    if (pj_param(P->ctx, P->params, "tdz").i)
635
1.59k
        Q->dxyz.z = pj_param(P->ctx, P->params, "ddz").f;
636
637
    /* Rotations rates */
638
47.5k
    if (pj_param(P->ctx, P->params, "tdrx").i)
639
1.59k
        Q->dopk.o = pj_param(P->ctx, P->params, "ddrx").f * ARCSEC_TO_RAD;
640
641
47.5k
    if (pj_param(P->ctx, P->params, "tdry").i)
642
1.59k
        Q->dopk.p = pj_param(P->ctx, P->params, "ddry").f * ARCSEC_TO_RAD;
643
644
47.5k
    if (pj_param(P->ctx, P->params, "tdrz").i)
645
1.60k
        Q->dopk.k = pj_param(P->ctx, P->params, "ddrz").f * ARCSEC_TO_RAD;
646
647
47.5k
    if (pj_param(P->ctx, P->params, "tdtheta").i)
648
15
        Q->dtheta = pj_param(P->ctx, P->params, "ddtheta").f * ARCSEC_TO_RAD;
649
650
    /* Scale rate */
651
47.5k
    if (pj_param(P->ctx, P->params, "tds").i)
652
1.59k
        Q->dscale = pj_param(P->ctx, P->params, "dds").f;
653
654
    /* Epoch */
655
47.5k
    if (pj_param(P->ctx, P->params, "tt_epoch").i)
656
1.59k
        Q->t_epoch = pj_param(P->ctx, P->params, "dt_epoch").f;
657
658
47.5k
    Q->xyz = Q->xyz_0;
659
47.5k
    Q->opk = Q->opk_0;
660
47.5k
    Q->scale = Q->scale_0;
661
47.5k
    Q->theta = Q->theta_0;
662
663
47.5k
    if ((Q->opk.o == 0) && (Q->opk.p == 0) && (Q->opk.k == 0) &&
664
42.7k
        (Q->dopk.o == 0) && (Q->dopk.p == 0) && (Q->dopk.k == 0)) {
665
42.3k
        Q->no_rotation = 1;
666
42.3k
    }
667
668
47.5k
    if (!read_convention(P)) {
669
90
        return nullptr;
670
90
    }
671
672
    /* Let's help with debugging */
673
47.4k
    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
47.4k
    update_parameters(P);
693
47.4k
    build_rot_matrix(P);
694
695
47.4k
    return P;
696
47.5k
}
697
698
/***********************************************************************/
699
42
PJ *PJ_TRANSFORMATION(molobadekas, 0) {
700
    /***********************************************************************/
701
702
42
    struct pj_opaque_helmert *Q;
703
704
42
    if (!init_helmert_six_parameters(P)) {
705
0
        return nullptr;
706
0
    }
707
708
42
    P->fwd3d = helmert_forward_3d;
709
42
    P->inv3d = helmert_reverse_3d;
710
711
42
    Q = (struct pj_opaque_helmert *)P->opaque;
712
713
    /* Scale */
714
42
    if (pj_param(P->ctx, P->params, "ts").i) {
715
34
        Q->scale_0 = pj_param(P->ctx, P->params, "ds").f;
716
34
    }
717
718
42
    Q->opk = Q->opk_0;
719
42
    Q->scale = Q->scale_0;
720
721
42
    if (!read_convention(P)) {
722
21
        return nullptr;
723
21
    }
724
725
    /* Reference point */
726
21
    if (pj_param(P->ctx, P->params, "tpx").i)
727
20
        Q->refp.x = pj_param(P->ctx, P->params, "dpx").f;
728
729
21
    if (pj_param(P->ctx, P->params, "tpy").i)
730
20
        Q->refp.y = pj_param(P->ctx, P->params, "dpy").f;
731
732
21
    if (pj_param(P->ctx, P->params, "tpz").i)
733
20
        Q->refp.z = pj_param(P->ctx, P->params, "dpz").f;
734
735
    /* Let's help with debugging */
736
21
    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
21
    Q->xyz_0.x += Q->refp.x;
752
21
    Q->xyz_0.y += Q->refp.y;
753
21
    Q->xyz_0.z += Q->refp.z;
754
755
21
    Q->xyz = Q->xyz_0;
756
757
21
    build_rot_matrix(P);
758
759
21
    return P;
760
42
}