diff options
Diffstat (limited to 'src/transformations/helmert.cpp')
| -rw-r--r-- | src/transformations/helmert.cpp | 755 |
1 files changed, 755 insertions, 0 deletions
diff --git a/src/transformations/helmert.cpp b/src/transformations/helmert.cpp new file mode 100644 index 00000000..4a3abf4e --- /dev/null +++ b/src/transformations/helmert.cpp @@ -0,0 +1,755 @@ +/*********************************************************************** + + 3-, 4-and 7-parameter shifts, and their 6-, 8- + and 14-parameter kinematic counterparts. + + Thomas Knudsen, 2016-05-24 + +************************************************************************ + + Implements 3(6)-, 4(8) and 7(14)-parameter Helmert transformations for + 3D data. + + Also incorporates Molodensky-Badekas variant of 7-parameter Helmert + transformation, where the rotation is not applied regarding the centre + of the spheroid, but given a reference point. + + Primarily useful for implementation of datum shifts in transformation + pipelines. + +************************************************************************ + +Thomas Knudsen, thokn@sdfe.dk, 2016-05-24/06-05 +Kristian Evers, kreve@sdfe.dk, 2017-05-01 +Even Rouault, even.roault@spatialys.com +Last update: 2018-10-26 + +************************************************************************ +* Copyright (c) 2016, Thomas Knudsen / SDFE +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the "Software"), +* to deal in the Software without restriction, including without limitation +* the rights to use, copy, modify, merge, publish, distribute, sublicense, +* and/or sell copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +* DEALINGS IN THE SOFTWARE. +* +***********************************************************************/ + +#define PJ_LIB__ + +#include <errno.h> +#include <math.h> + +#include "proj_internal.h" +#include "projects.h" +#include "geocent.h" + +PROJ_HEAD(helmert, "3(6)-, 4(8)- and 7(14)-parameter Helmert shift"); +PROJ_HEAD(molobadekas, "Molodensky-Badekas transformation"); + +static XYZ helmert_forward_3d (LPZ lpz, PJ *P); +static LPZ helmert_reverse_3d (XYZ xyz, PJ *P); + + + +/***********************************************************************/ +namespace { // anonymous namespace +struct pj_opaque_helmert { +/************************************************************************ + Projection specific elements for the "helmert" PJ object +************************************************************************/ + XYZ xyz; + XYZ xyz_0; + XYZ dxyz; + XYZ refp; + PJ_OPK opk; + PJ_OPK opk_0; + PJ_OPK dopk; + double scale; + double scale_0; + double dscale; + double theta; + double theta_0; + double dtheta; + double R[3][3]; + double t_epoch, t_obs; + int no_rotation, exact, fourparam; + int is_position_vector; /* 1 = position_vector, 0 = coordinate_frame */ +}; +} // anonymous namespace + + +/* Make the maths of the rotation operations somewhat more readable and textbook like */ +#define R00 (Q->R[0][0]) +#define R01 (Q->R[0][1]) +#define R02 (Q->R[0][2]) + +#define R10 (Q->R[1][0]) +#define R11 (Q->R[1][1]) +#define R12 (Q->R[1][2]) + +#define R20 (Q->R[2][0]) +#define R21 (Q->R[2][1]) +#define R22 (Q->R[2][2]) + +/**************************************************************************/ +static void update_parameters(PJ *P) { +/*************************************************************************** + + Update transformation parameters. + --------------------------------- + + The 14-parameter Helmert transformation is at it's core the same as the + 7-parameter transformation, since the transformation parameters are + projected forward or backwards in time via the rate of changes of the + parameters. The transformation parameters are calculated for a specific + epoch before the actual Helmert transformation is carried out. + + The transformation parameters are updated with the following equation [0]: + + . + P(t) = P(EPOCH) + P * (t - EPOCH) + + . + where EPOCH is the epoch indicated in the above table and P is the rate + of that parameter. + + [0] http://itrf.ign.fr/doc_ITRF/Transfo-ITRF2008_ITRFs.txt + +*******************************************************************************/ + + struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; + double dt = Q->t_obs - Q->t_epoch; + + Q->xyz.x = Q->xyz_0.x + Q->dxyz.x * dt; + Q->xyz.y = Q->xyz_0.y + Q->dxyz.y * dt; + Q->xyz.z = Q->xyz_0.z + Q->dxyz.z * dt; + + Q->opk.o = Q->opk_0.o + Q->dopk.o * dt; + Q->opk.p = Q->opk_0.p + Q->dopk.p * dt; + Q->opk.k = Q->opk_0.k + Q->dopk.k * dt; + + Q->scale = Q->scale_0 + Q->dscale * dt; + + Q->theta = Q->theta_0 + Q->dtheta * dt; + + /* debugging output */ + if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_TRACE) { + proj_log_trace(P, "Transformation parameters for observation " + "t_obs=%g (t_epoch=%g):", Q->t_obs, Q->t_epoch); + proj_log_trace(P, "x: %g", Q->xyz.x); + proj_log_trace(P, "y: %g", Q->xyz.y); + proj_log_trace(P, "z: %g", Q->xyz.z); + proj_log_trace(P, "s: %g", Q->scale*1e-6); + proj_log_trace(P, "rx: %g", Q->opk.o); + proj_log_trace(P, "ry: %g", Q->opk.p); + proj_log_trace(P, "rz: %g", Q->opk.k); + proj_log_trace(P, "theta: %g", Q->theta); + } +} + +/**************************************************************************/ +static void build_rot_matrix(PJ *P) { +/*************************************************************************** + + Build rotation matrix. + ---------------------- + + Here we rename rotation indices from omega, phi, kappa (opk), to + fi (i.e. phi), theta, psi (ftp), in order to reduce the mental agility + needed to implement the expression for the rotation matrix derived over + at https://en.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions + The relevant section is Euler angles ( z-’-x" intrinsic) -> Rotation matrix + + By default small angle approximations are used: + The matrix elements are approximated by expanding the trigonometric + functions to linear order (i.e. cos(x) = 1, sin(x) = x), and discarding + products of second order. + + This was a useful hack when calculating by hand was the only option, + but in general, today, should be avoided because: + + 1. It does not save much computation time, as the rotation matrix + is built only once and probably used many times (except when + transforming spatio-temporal coordinates). + + 2. The error induced may be too large for ultra high accuracy + applications: the Earth is huge and the linear error is + approximately the angular error multiplied by the Earth radius. + + However, in many cases the approximation is necessary, since it has + been used historically: Rotation angles from older published datum + shifts may actually be a least squares fit to the linearized rotation + approximation, hence not being strictly valid for deriving the exact + rotation matrix. In fact, most publicly available transformation + parameters are based on the approximate Helmert transform, which is why + we use that as the default setting, even though it is more correct to + use the exact form of the equations. + + So in order to fit historically derived coordinates, the access to + the approximate rotation matrix is necessary - at least in principle. + + Also, when using any published datum transformation information, one + should always check which convention (exact or approximate rotation + matrix) is expected, and whether the induced error for selecting + the opposite convention is acceptable (which it often is). + + + Sign conventions + ---------------- + + Take care: Two different sign conventions exist for the rotation terms. + + Conceptually they relate to whether we rotate the coordinate system + or the "position vector" (the vector going from the coordinate system + origin to the point being transformed, i.e. the point coordinates + interpreted as vector coordinates). + + Switching between the "position vector" and "coordinate system" + conventions is simply a matter of switching the sign of the rotation + angles, which algebraically also translates into a transposition of + the rotation matrix. + + Hence, as geodetic constants should preferably be referred to exactly + as published, the "convention" option provides the ability to switch + between the conventions. + +***************************************************************************/ + struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; + + double f, t, p; /* phi/fi , theta, psi */ + double cf, ct, cp; /* cos (fi, theta, psi) */ + double sf, st, sp; /* sin (fi, theta, psi) */ + + /* rename (omega, phi, kappa) to (fi, theta, psi) */ + f = Q->opk.o; + t = Q->opk.p; + p = Q->opk.k; + + /* Those equations are given assuming coordinate frame convention. */ + /* For the position vector convention, we transpose the matrix just after. */ + if (Q->exact) { + cf = cos(f); + sf = sin(f); + ct = cos(t); + st = sin(t); + cp = cos(p); + sp = sin(p); + + + R00 = ct*cp; + R01 = cf*sp + sf*st*cp; + R02 = sf*sp - cf*st*cp; + + R10 = -ct*sp; + R11 = cf*cp - sf*st*sp; + R12 = sf*cp + cf*st*sp; + + R20 = st; + R21 = -sf*ct; + R22 = cf*ct; + } else{ + R00 = 1; + R01 = p; + R02 = -t; + + R10 = -p; + R11 = 1; + R12 = f; + + R20 = t; + R21 = -f; + R22 = 1; + } + + + /* + For comparison: Description from Engsager/Poder implementation + in set_dtm_1.c (trlib) + + DATUM SHIFT: + TO = scale * ROTZ * ROTY * ROTX * FROM + TRANSLA + + ( cz sz 0) (cy 0 -sy) (1 0 0) + ROTZ=(-sz cz 0), ROTY=(0 1 0), ROTX=(0 cx sx) + ( 0 0 1) (sy 0 cy) (0 -sx cx) + + trp->r11 = cos_ry*cos_rz; + trp->r12 = cos_rx*sin_rz + sin_rx*sin_ry*cos_rz; + trp->r13 = sin_rx*sin_rz - cos_rx*sin_ry*cos_rz; + + trp->r21 = -cos_ry*sin_rz; + trp->r22 = cos_rx*cos_rz - sin_rx*sin_ry*sin_rz; + trp->r23 = sin_rx*cos_rz + cos_rx*sin_ry*sin_rz; + + trp->r31 = sin_ry; + trp->r32 = -sin_rx*cos_ry; + trp->r33 = cos_rx*cos_ry; + + trp->scale = 1.0 + scale; + */ + + + if (Q->is_position_vector) { + double r; + r = R01; R01 = R10; R10 = r; + r = R02; R02 = R20; R20 = r; + r = R12; R12 = R21; R21 = r; + } + + /* some debugging output */ + if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_TRACE) { + proj_log_trace(P, "Rotation Matrix:"); + proj_log_trace(P, " | % 6.6g % 6.6g % 6.6g |", R00, R01, R02); + proj_log_trace(P, " | % 6.6g % 6.6g % 6.6g |", R10, R11, R12); + proj_log_trace(P, " | % 6.6g % 6.6g % 6.6g |", R20, R21, R22); + } +} + + + + +/***********************************************************************/ +static XY helmert_forward (LP lp, PJ *P) { +/***********************************************************************/ + struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; + PJ_COORD point = {{0,0,0,0}}; + double x, y, cr, sr; + point.lp = lp; + + cr = cos(Q->theta) * Q->scale; + sr = sin(Q->theta) * Q->scale; + x = point.xy.x; + y = point.xy.y; + + point.xy.x = cr*x + sr*y + Q->xyz_0.x; + point.xy.y = -sr*x + cr*y + Q->xyz_0.y; + + return point.xy; +} + + +/***********************************************************************/ +static LP helmert_reverse (XY xy, PJ *P) { +/***********************************************************************/ + struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; + PJ_COORD point = {{0,0,0,0}}; + double x, y, sr, cr; + point.xy = xy; + + cr = cos(Q->theta) / Q->scale; + sr = sin(Q->theta) / Q->scale; + x = point.xy.x - Q->xyz_0.x; + y = point.xy.y - Q->xyz_0.y; + + point.xy.x = x*cr - y*sr; + point.xy.y = x*sr + y*cr; + + return point.lp; +} + + +/***********************************************************************/ +static XYZ helmert_forward_3d (LPZ lpz, PJ *P) { +/***********************************************************************/ + struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; + PJ_COORD point = {{0,0,0,0}}; + double X, Y, Z, scale; + + point.lpz = lpz; + + if (Q->fourparam) { + point.xy = helmert_forward(point.lp, P); + return point.xyz; + } + + if (Q->no_rotation) { + point.xyz.x = lpz.lam + Q->xyz.x; + point.xyz.y = lpz.phi + Q->xyz.y; + point.xyz.z = lpz.z + Q->xyz.z; + return point.xyz; + } + + scale = 1 + Q->scale * 1e-6; + + X = lpz.lam - Q->refp.x; + Y = lpz.phi - Q->refp.y; + Z = lpz.z - Q->refp.z; + + + point.xyz.x = scale * ( R00 * X + R01 * Y + R02 * Z); + point.xyz.y = scale * ( R10 * X + R11 * Y + R12 * Z); + point.xyz.z = scale * ( R20 * X + R21 * Y + R22 * Z); + + point.xyz.x += Q->xyz.x; /* for Molodensky-Badekas, Q->xyz already incorporates the Q->refp offset */ + point.xyz.y += Q->xyz.y; + point.xyz.z += Q->xyz.z; + + return point.xyz; +} + + +/***********************************************************************/ +static LPZ helmert_reverse_3d (XYZ xyz, PJ *P) { +/***********************************************************************/ + struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; + PJ_COORD point = {{0,0,0,0}}; + double X, Y, Z, scale; + + point.xyz = xyz; + + if (Q->fourparam) { + point.lp = helmert_reverse(point.xy, P); + return point.lpz; + } + + if (Q->no_rotation) { + point.xyz.x = xyz.x - Q->xyz.x; + point.xyz.y = xyz.y - Q->xyz.y; + point.xyz.z = xyz.z - Q->xyz.z; + return point.lpz; + } + + scale = 1 + Q->scale * 1e-6; + + /* Unscale and deoffset */ + X = (xyz.x - Q->xyz.x) / scale; + Y = (xyz.y - Q->xyz.y) / scale; + Z = (xyz.z - Q->xyz.z) / scale; + + /* Inverse rotation through transpose multiplication */ + point.xyz.x = ( R00 * X + R10 * Y + R20 * Z) + Q->refp.x; + point.xyz.y = ( R01 * X + R11 * Y + R21 * Z) + Q->refp.y; + point.xyz.z = ( R02 * X + R12 * Y + R22 * Z) + Q->refp.z; + + return point.lpz; +} + + +static PJ_COORD helmert_forward_4d (PJ_COORD point, PJ *P) { + struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; + + /* We only need to rebuild the rotation matrix if the + * observation time is different from the last call */ + double t_obs = (point.xyzt.t == HUGE_VAL) ? Q->t_epoch : point.xyzt.t; + if (t_obs != Q->t_obs) { + Q->t_obs = t_obs; + update_parameters(P); + build_rot_matrix(P); + } + + point.xyz = helmert_forward_3d (point.lpz, P); + + return point; +} + + +static PJ_COORD helmert_reverse_4d (PJ_COORD point, PJ *P) { + struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *) P->opaque; + + /* We only need to rebuild the rotation matrix if the + * observation time is different from the last call */ + double t_obs = (point.xyzt.t == HUGE_VAL) ? Q->t_epoch : point.xyzt.t; + if (t_obs != Q->t_obs) { + Q->t_obs = t_obs; + update_parameters(P); + build_rot_matrix(P); + } + + point.lpz = helmert_reverse_3d (point.xyz, P); + + return point; +} + +/* Arcsecond to radians */ +#define ARCSEC_TO_RAD (DEG_TO_RAD / 3600.0) + + +static PJ* init_helmert_six_parameters(PJ* P) { + struct pj_opaque_helmert *Q = static_cast<struct pj_opaque_helmert*>(pj_calloc (1, sizeof (struct pj_opaque_helmert))); + if (nullptr==Q) + return pj_default_destructor (P, ENOMEM); + P->opaque = (void *) Q; + + /* In most cases, we work on 3D cartesian coordinates */ + P->left = PJ_IO_UNITS_CARTESIAN; + P->right = PJ_IO_UNITS_CARTESIAN; + + /* Translations */ + if (pj_param (P->ctx, P->params, "tx").i) + Q->xyz_0.x = pj_param (P->ctx, P->params, "dx").f; + + if (pj_param (P->ctx, P->params, "ty").i) + Q->xyz_0.y = pj_param (P->ctx, P->params, "dy").f; + + if (pj_param (P->ctx, P->params, "tz").i) + Q->xyz_0.z = pj_param (P->ctx, P->params, "dz").f; + + /* Rotations */ + if (pj_param (P->ctx, P->params, "trx").i) + Q->opk_0.o = pj_param (P->ctx, P->params, "drx").f * ARCSEC_TO_RAD; + + if (pj_param (P->ctx, P->params, "try").i) + Q->opk_0.p = pj_param (P->ctx, P->params, "dry").f * ARCSEC_TO_RAD; + + if (pj_param (P->ctx, P->params, "trz").i) + Q->opk_0.k = pj_param (P->ctx, P->params, "drz").f * ARCSEC_TO_RAD; + + /* Use small angle approximations? */ + if (pj_param (P->ctx, P->params, "bexact").i) + Q->exact = 1; + + return P; +} + + +static PJ* read_convention(PJ* P) { + + struct pj_opaque_helmert *Q = (struct pj_opaque_helmert *)P->opaque; + + /* In case there are rotational terms, we require an explicit convention + * to be provided. */ + if (!Q->no_rotation) { + const char* convention = pj_param (P->ctx, P->params, "sconvention").s; + if( !convention ) { + proj_log_error (P, "helmert: missing 'convention' argument"); + return pj_default_destructor (P, PJD_ERR_MISSING_ARGS); + } + if( strcmp(convention, "position_vector") == 0 ) { + Q->is_position_vector = 1; + } + else if( strcmp(convention, "coordinate_frame") == 0 ) { + Q->is_position_vector = 0; + } + else { + proj_log_error (P, "helmert: invalid value for 'convention' argument"); + return pj_default_destructor (P, PJD_ERR_INVALID_ARG); + } + + /* historically towgs84 in PROJ has always been using position_vector + * convention. Accepting coordinate_frame would be confusing. */ + if (pj_param_exists (P->params, "towgs84")) { + if( !Q->is_position_vector ) { + proj_log_error (P, "helmert: towgs84 should only be used with " + "convention=position_vector"); + return pj_default_destructor (P, PJD_ERR_INVALID_ARG); + } + } + } + + return P; +} + + +/***********************************************************************/ +PJ *TRANSFORMATION(helmert, 0) { +/***********************************************************************/ + + struct pj_opaque_helmert *Q; + + if( !init_helmert_six_parameters(P) ) { + return nullptr; + } + + /* In the 2D case, the coordinates are projected */ + if (pj_param_exists (P->params, "theta")) { + P->left = PJ_IO_UNITS_PROJECTED; + P->right = PJ_IO_UNITS_PROJECTED; + } + + P->fwd4d = helmert_forward_4d; + P->inv4d = helmert_reverse_4d; + P->fwd3d = helmert_forward_3d; + P->inv3d = helmert_reverse_3d; + P->fwd = helmert_forward; + P->inv = helmert_reverse; + + Q = (struct pj_opaque_helmert *)P->opaque; + + /* Detect obsolete transpose flag and error out if found */ + if (pj_param (P->ctx, P->params, "ttranspose").i) { + proj_log_error (P, "helmert: 'transpose' argument is no longer valid. " + "Use convention=position_vector/coordinate_frame"); + return pj_default_destructor (P, PJD_ERR_INVALID_ARG); + } + + /* Support the classic PROJ towgs84 parameter, but allow later overrides.*/ + /* Note that if towgs84 is specified, the datum_params array is set up */ + /* for us automagically by the pj_datum_set call in pj_init_ctx */ + if (pj_param_exists (P->params, "towgs84")) { + Q->xyz_0.x = P->datum_params[0]; + Q->xyz_0.y = P->datum_params[1]; + Q->xyz_0.z = P->datum_params[2]; + + Q->opk_0.o = P->datum_params[3]; + Q->opk_0.p = P->datum_params[4]; + Q->opk_0.k = P->datum_params[5]; + + /* We must undo conversion to absolute scale from pj_datum_set */ + if (0==P->datum_params[6]) + Q->scale_0 = 0; + else + Q->scale_0 = (P->datum_params[6] - 1) * 1e6; + } + + if (pj_param (P->ctx, P->params, "ttheta").i) { + Q->theta_0 = pj_param (P->ctx, P->params, "dtheta").f * ARCSEC_TO_RAD; + Q->fourparam = 1; + Q->scale_0 = 1.0; /* default scale for the 4-param shift */ + } + + /* Scale */ + if (pj_param (P->ctx, P->params, "ts").i) { + Q->scale_0 = pj_param (P->ctx, P->params, "ds").f; + if (pj_param (P->ctx, P->params, "ttheta").i && Q->scale_0 == 0.0) + return pj_default_destructor (P, PJD_ERR_INVALID_SCALE); + } + + /* Translation rates */ + if (pj_param(P->ctx, P->params, "tdx").i) + Q->dxyz.x = pj_param (P->ctx, P->params, "ddx").f; + + if (pj_param(P->ctx, P->params, "tdy").i) + Q->dxyz.y = pj_param (P->ctx, P->params, "ddy").f; + + if (pj_param(P->ctx, P->params, "tdz").i) + Q->dxyz.z = pj_param (P->ctx, P->params, "ddz").f; + + /* Rotations rates */ + if (pj_param (P->ctx, P->params, "tdrx").i) + Q->dopk.o = pj_param (P->ctx, P->params, "ddrx").f * ARCSEC_TO_RAD; + + if (pj_param (P->ctx, P->params, "tdry").i) + Q->dopk.p = pj_param (P->ctx, P->params, "ddry").f * ARCSEC_TO_RAD; + + if (pj_param (P->ctx, P->params, "tdrz").i) + Q->dopk.k = pj_param (P->ctx, P->params, "ddrz").f * ARCSEC_TO_RAD; + + if (pj_param (P->ctx, P->params, "tdtheta").i) + Q->dtheta = pj_param (P->ctx, P->params, "ddtheta").f * ARCSEC_TO_RAD; + + /* Scale rate */ + if (pj_param (P->ctx, P->params, "tds").i) + Q->dscale = pj_param (P->ctx, P->params, "dds").f; + + + /* Epoch */ + if (pj_param(P->ctx, P->params, "tt_epoch").i) + Q->t_epoch = pj_param (P->ctx, P->params, "dt_epoch").f; + + if (pj_param(P->ctx, P->params, "tt_obs").i) + Q->t_obs = pj_param (P->ctx, P->params, "dt_obs").f; + + Q->xyz = Q->xyz_0; + Q->opk = Q->opk_0; + Q->scale = Q->scale_0; + Q->theta = Q->theta_0; + + if ((Q->opk.o==0) && (Q->opk.p==0) && (Q->opk.k==0) && (Q->scale==0) && + (Q->dopk.o==0) && (Q->dopk.p==0) && (Q->dopk.k==0)) { + Q->no_rotation = 1; + } + + if( !read_convention(P) ) { + return nullptr; + } + + /* Let's help with debugging */ + if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { + proj_log_debug(P, "Helmert parameters:"); + proj_log_debug(P, "x= %8.5f y= %8.5f z= %8.5f", Q->xyz.x, Q->xyz.y, Q->xyz.z); + proj_log_debug(P, "rx= %8.5f ry= %8.5f rz= %8.5f", + Q->opk.o / ARCSEC_TO_RAD, Q->opk.p / ARCSEC_TO_RAD, Q->opk.k / ARCSEC_TO_RAD); + proj_log_debug(P, "s= %8.5f exact=%d%s", Q->scale, Q->exact, + Q->no_rotation ? "" : + Q->is_position_vector ? " convention=position_vector" : + " convention=coordinate_frame"); + proj_log_debug(P, "dx= %8.5f dy= %8.5f dz= %8.5f", Q->dxyz.x, Q->dxyz.y, Q->dxyz.z); + proj_log_debug(P, "drx=%8.5f dry=%8.5f drz=%8.5f", Q->dopk.o, Q->dopk.p, Q->dopk.k); + proj_log_debug(P, "ds= %8.5f t_epoch=%8.5f t_obs=%8.5f", Q->dscale, Q->t_epoch, Q->t_obs); + } + + if (Q->no_rotation) { + return P; + } + + update_parameters(P); + build_rot_matrix(P); + + return P; +} + + +/***********************************************************************/ +PJ *TRANSFORMATION(molobadekas, 0) { +/***********************************************************************/ + + struct pj_opaque_helmert *Q; + + if( !init_helmert_six_parameters(P) ) { + return nullptr; + } + + P->fwd3d = helmert_forward_3d; + P->inv3d = helmert_reverse_3d; + + Q = (struct pj_opaque_helmert *)P->opaque; + + /* Scale */ + if (pj_param (P->ctx, P->params, "ts").i) { + Q->scale_0 = pj_param (P->ctx, P->params, "ds").f; + } + + Q->opk = Q->opk_0; + Q->scale = Q->scale_0; + + if( !read_convention(P) ) { + return nullptr; + } + + /* Reference point */ + if (pj_param (P->ctx, P->params, "tpx").i) + Q->refp.x = pj_param (P->ctx, P->params, "dpx").f; + + if (pj_param (P->ctx, P->params, "tpy").i) + Q->refp.y = pj_param (P->ctx, P->params, "dpy").f; + + if (pj_param (P->ctx, P->params, "tpz").i) + Q->refp.z = pj_param (P->ctx, P->params, "dpz").f; + + + /* Let's help with debugging */ + if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { + proj_log_debug(P, "Molodensky-Badekas parameters:"); + proj_log_debug(P, "x= %8.5f y= %8.5f z= %8.5f", Q->xyz_0.x, Q->xyz_0.y, Q->xyz_0.z); + proj_log_debug(P, "rx= %8.5f ry= %8.5f rz= %8.5f", + Q->opk.o / ARCSEC_TO_RAD, Q->opk.p / ARCSEC_TO_RAD, Q->opk.k / ARCSEC_TO_RAD); + proj_log_debug(P, "s= %8.5f exact=%d%s", Q->scale, Q->exact, + Q->is_position_vector ? " convention=position_vector" : + " convention=coordinate_frame"); + proj_log_debug(P, "px= %8.5f py= %8.5f pz= %8.5f", Q->refp.x, Q->refp.y, Q->refp.z); + } + + /* as an optimization, we incorporate the refp in the translation terms */ + Q->xyz_0.x += Q->refp.x; + Q->xyz_0.y += Q->refp.y; + Q->xyz_0.z += Q->refp.z; + + Q->xyz = Q->xyz_0; + + build_rot_matrix(P); + + return P; +} |
