/src/gdal/proj/src/conversions/topocentric.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * Project: PROJ |
3 | | * Purpose: Convert between geocentric coordinates and topocentric (ENU) |
4 | | *coordinates |
5 | | * |
6 | | ****************************************************************************** |
7 | | * Copyright (c) 2020, Even Rouault <even.rouault at spatialys.com> |
8 | | * |
9 | | * Permission is hereby granted, free of charge, to any person obtaining a |
10 | | * copy of this software and associated documentation files (the "Software"), |
11 | | * to deal in the Software without restriction, including without limitation |
12 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
13 | | * and/or sell copies of the Software, and to permit persons to whom the |
14 | | * Software is furnished to do so, subject to the following conditions: |
15 | | * |
16 | | * The above copyright notice and this permission notice shall be included |
17 | | * in all copies or substantial portions of the Software. |
18 | | * |
19 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
20 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
21 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
22 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
23 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
24 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
25 | | * DEALINGS IN THE SOFTWARE. |
26 | | *****************************************************************************/ |
27 | | |
28 | | #include "proj_internal.h" |
29 | | #include <errno.h> |
30 | | #include <math.h> |
31 | | |
32 | | PROJ_HEAD(topocentric, "Geocentric/Topocentric conversion"); |
33 | | |
34 | | // Notations and formulas taken from IOGP Publication 373-7-2 - |
35 | | // Geomatics Guidance Note number 7, part 2 - October 2020 |
36 | | |
37 | | namespace { // anonymous namespace |
38 | | struct pj_opaque { |
39 | | double X0; |
40 | | double Y0; |
41 | | double Z0; |
42 | | double sinphi0; |
43 | | double cosphi0; |
44 | | double sinlam0; |
45 | | double coslam0; |
46 | | }; |
47 | | } // anonymous namespace |
48 | | |
49 | | // Convert from geocentric to topocentric |
50 | 0 | static void topocentric_fwd(PJ_COORD &coo, PJ *P) { |
51 | 0 | struct pj_opaque *Q = static_cast<struct pj_opaque *>(P->opaque); |
52 | 0 | const double dX = coo.xyz.x - Q->X0; |
53 | 0 | const double dY = coo.xyz.y - Q->Y0; |
54 | 0 | const double dZ = coo.xyz.z - Q->Z0; |
55 | 0 | coo.xyz.x = -dX * Q->sinlam0 + dY * Q->coslam0; |
56 | 0 | coo.xyz.y = -dX * Q->sinphi0 * Q->coslam0 - dY * Q->sinphi0 * Q->sinlam0 + |
57 | 0 | dZ * Q->cosphi0; |
58 | 0 | coo.xyz.z = dX * Q->cosphi0 * Q->coslam0 + dY * Q->cosphi0 * Q->sinlam0 + |
59 | 0 | dZ * Q->sinphi0; |
60 | 0 | } |
61 | | |
62 | | // Convert from topocentric to geocentric |
63 | 0 | static void topocentric_inv(PJ_COORD &coo, PJ *P) { |
64 | 0 | struct pj_opaque *Q = static_cast<struct pj_opaque *>(P->opaque); |
65 | 0 | const double x = coo.xyz.x; |
66 | 0 | const double y = coo.xyz.y; |
67 | 0 | const double z = coo.xyz.z; |
68 | 0 | coo.xyz.x = Q->X0 - x * Q->sinlam0 - y * Q->sinphi0 * Q->coslam0 + |
69 | 0 | z * Q->cosphi0 * Q->coslam0; |
70 | 0 | coo.xyz.y = Q->Y0 + x * Q->coslam0 - y * Q->sinphi0 * Q->sinlam0 + |
71 | 0 | z * Q->cosphi0 * Q->sinlam0; |
72 | 0 | coo.xyz.z = Q->Z0 + y * Q->cosphi0 + z * Q->sinphi0; |
73 | 0 | } |
74 | | |
75 | | /*********************************************************************/ |
76 | 17.5k | PJ *PJ_CONVERSION(topocentric, 1) { |
77 | | /*********************************************************************/ |
78 | 17.5k | struct pj_opaque *Q = |
79 | 17.5k | static_cast<struct pj_opaque *>(calloc(1, sizeof(struct pj_opaque))); |
80 | 17.5k | if (nullptr == Q) |
81 | 0 | return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); |
82 | 17.5k | P->opaque = static_cast<void *>(Q); |
83 | | |
84 | | // The topocentric origin can be specified either in geocentric coordinates |
85 | | // (X_0,Y_0,Z_0) or as geographic coordinates (lon_0,lat_0,h_0) |
86 | | // Checks: |
87 | | // - X_0 or lon_0 must be specified |
88 | | // - If X_0 is specified, the Y_0 and Z_0 must also be |
89 | | // - If lon_0 is specified, then lat_0 must also be |
90 | | // - If any of X_0, Y_0, Z_0 is specified, then any of lon_0,lat_0,h_0 must |
91 | | // not be, and vice versa. |
92 | 17.5k | const auto hasX0 = pj_param_exists(P->params, "X_0"); |
93 | 17.5k | const auto hasY0 = pj_param_exists(P->params, "Y_0"); |
94 | 17.5k | const auto hasZ0 = pj_param_exists(P->params, "Z_0"); |
95 | 17.5k | const auto hasLon0 = pj_param_exists(P->params, "lon_0"); |
96 | 17.5k | const auto hasLat0 = pj_param_exists(P->params, "lat_0"); |
97 | 17.5k | const auto hash0 = pj_param_exists(P->params, "h_0"); |
98 | 17.5k | if (!hasX0 && !hasLon0) { |
99 | 988 | proj_log_error(P, _("missing X_0 or lon_0")); |
100 | 988 | return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
101 | 988 | } |
102 | 16.5k | if ((hasX0 || hasY0 || hasZ0) && (hasLon0 || hasLat0 || hash0)) { |
103 | 755 | proj_log_error( |
104 | 755 | P, _("(X_0,Y_0,Z_0) and (lon_0,lat_0,h_0) are mutually exclusive")); |
105 | 755 | return pj_default_destructor( |
106 | 755 | P, PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS); |
107 | 755 | } |
108 | 15.8k | if (hasX0 && (!hasY0 || !hasZ0)) { |
109 | 657 | proj_log_error(P, _("missing Y_0 and/or Z_0")); |
110 | 657 | return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
111 | 657 | } |
112 | 15.1k | if (hasLon0 && !hasLat0) // allow missing h_0 |
113 | 493 | { |
114 | 493 | proj_log_error(P, _("missing lat_0")); |
115 | 493 | return pj_default_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
116 | 493 | } |
117 | | |
118 | | // Pass a dummy ellipsoid definition that will be overridden just afterwards |
119 | 14.6k | PJ *cart = proj_create(P->ctx, "+proj=cart +a=1"); |
120 | 14.6k | if (cart == nullptr) |
121 | 0 | return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); |
122 | | /* inherit ellipsoid definition from P to cart */ |
123 | 14.6k | pj_inherit_ellipsoid_def(P, cart); |
124 | | |
125 | 14.6k | if (hasX0) { |
126 | 486 | Q->X0 = pj_param(P->ctx, P->params, "dX_0").f; |
127 | 486 | Q->Y0 = pj_param(P->ctx, P->params, "dY_0").f; |
128 | 486 | Q->Z0 = pj_param(P->ctx, P->params, "dZ_0").f; |
129 | | |
130 | | // Compute lam0, phi0 from X0,Y0,Z0 |
131 | 486 | PJ_XYZ xyz; |
132 | 486 | xyz.x = Q->X0; |
133 | 486 | xyz.y = Q->Y0; |
134 | 486 | xyz.z = Q->Z0; |
135 | 486 | const auto lpz = pj_inv3d(xyz, cart); |
136 | 486 | Q->sinphi0 = sin(lpz.phi); |
137 | 486 | Q->cosphi0 = cos(lpz.phi); |
138 | 486 | Q->sinlam0 = sin(lpz.lam); |
139 | 486 | Q->coslam0 = cos(lpz.lam); |
140 | 14.2k | } else { |
141 | | // Compute X0,Y0,Z0 from lam0, phi0, h0 |
142 | 14.2k | PJ_LPZ lpz; |
143 | 14.2k | lpz.lam = P->lam0; |
144 | 14.2k | lpz.phi = P->phi0; |
145 | 14.2k | lpz.z = pj_param(P->ctx, P->params, "dh_0").f; |
146 | 14.2k | const auto xyz = pj_fwd3d(lpz, cart); |
147 | 14.2k | Q->X0 = xyz.x; |
148 | 14.2k | Q->Y0 = xyz.y; |
149 | 14.2k | Q->Z0 = xyz.z; |
150 | | |
151 | 14.2k | Q->sinphi0 = sin(P->phi0); |
152 | 14.2k | Q->cosphi0 = cos(P->phi0); |
153 | 14.2k | Q->sinlam0 = sin(P->lam0); |
154 | 14.2k | Q->coslam0 = cos(P->lam0); |
155 | 14.2k | } |
156 | | |
157 | 14.6k | proj_destroy(cart); |
158 | | |
159 | 14.6k | P->fwd4d = topocentric_fwd; |
160 | 14.6k | P->inv4d = topocentric_inv; |
161 | 14.6k | P->left = PJ_IO_UNITS_CARTESIAN; |
162 | 14.6k | P->right = PJ_IO_UNITS_CARTESIAN; |
163 | 14.6k | return P; |
164 | 14.6k | } |