/src/PROJ/src/projections/aeqd.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * Project: PROJ.4 |
3 | | * Purpose: Implementation of the aeqd (Azimuthal Equidistant) projection. |
4 | | * Author: Gerald Evenden |
5 | | * |
6 | | ****************************************************************************** |
7 | | * Copyright (c) 1995, Gerald Evenden |
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 "geodesic.h" |
29 | | #include "proj.h" |
30 | | #include "proj_internal.h" |
31 | | #include <errno.h> |
32 | | #include <math.h> |
33 | | |
34 | | namespace pj_aeqd_ns { |
35 | | enum Mode { N_POLE = 0, S_POLE = 1, EQUIT = 2, OBLIQ = 3 }; |
36 | | } |
37 | | |
38 | | namespace { // anonymous namespace |
39 | | struct pj_aeqd_data { |
40 | | double sinph0; |
41 | | double cosph0; |
42 | | double *en; |
43 | | double M1; |
44 | | double N1; |
45 | | double Mp; |
46 | | double He; |
47 | | double G; |
48 | | enum ::pj_aeqd_ns::Mode mode; |
49 | | struct geod_geodesic g; |
50 | | }; |
51 | | } // anonymous namespace |
52 | | |
53 | | PROJ_HEAD(aeqd, "Azimuthal Equidistant") "\n\tAzi, Sph&Ell\n\tlat_0 guam"; |
54 | | |
55 | 10.8k | #define EPS10 1.e-10 |
56 | 0 | #define TOL 1.e-14 |
57 | | |
58 | 310 | static PJ *destructor(PJ *P, int errlev) { /* Destructor */ |
59 | 310 | if (nullptr == P) |
60 | 0 | return nullptr; |
61 | | |
62 | 310 | if (nullptr == P->opaque) |
63 | 0 | return pj_default_destructor(P, errlev); |
64 | | |
65 | 310 | free(static_cast<struct pj_aeqd_data *>(P->opaque)->en); |
66 | 310 | return pj_default_destructor(P, errlev); |
67 | 310 | } |
68 | | |
69 | 5.20k | static PJ_XY e_guam_fwd(PJ_LP lp, PJ *P) { /* Guam elliptical */ |
70 | 5.20k | PJ_XY xy = {0.0, 0.0}; |
71 | 5.20k | struct pj_aeqd_data *Q = static_cast<struct pj_aeqd_data *>(P->opaque); |
72 | 5.20k | double cosphi, sinphi, t; |
73 | | |
74 | 5.20k | cosphi = cos(lp.phi); |
75 | 5.20k | sinphi = sin(lp.phi); |
76 | 5.20k | t = 1. / sqrt(1. - P->es * sinphi * sinphi); |
77 | 5.20k | xy.x = lp.lam * cosphi * t; |
78 | 5.20k | xy.y = pj_mlfn(lp.phi, sinphi, cosphi, Q->en) - Q->M1 + |
79 | 5.20k | .5 * lp.lam * lp.lam * cosphi * sinphi * t; |
80 | | |
81 | 5.20k | return xy; |
82 | 5.20k | } |
83 | | |
84 | 5.12k | static PJ_XY aeqd_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */ |
85 | 5.12k | PJ_XY xy = {0.0, 0.0}; |
86 | 5.12k | struct pj_aeqd_data *Q = static_cast<struct pj_aeqd_data *>(P->opaque); |
87 | 5.12k | double coslam, cosphi, sinphi, rho; |
88 | 5.12k | double azi1, azi2, s12; |
89 | 5.12k | double lat1, lon1, lat2, lon2; |
90 | | |
91 | 5.12k | coslam = cos(lp.lam); |
92 | 5.12k | switch (Q->mode) { |
93 | 0 | case pj_aeqd_ns::N_POLE: |
94 | 0 | coslam = -coslam; |
95 | 0 | PROJ_FALLTHROUGH; |
96 | 0 | case pj_aeqd_ns::S_POLE: |
97 | 0 | cosphi = cos(lp.phi); |
98 | 0 | sinphi = sin(lp.phi); |
99 | 0 | rho = fabs(Q->Mp - pj_mlfn(lp.phi, sinphi, cosphi, Q->en)); |
100 | 0 | xy.x = rho * sin(lp.lam); |
101 | 0 | xy.y = rho * coslam; |
102 | 0 | break; |
103 | 5.12k | case pj_aeqd_ns::EQUIT: |
104 | 5.12k | case pj_aeqd_ns::OBLIQ: |
105 | 5.12k | if (fabs(lp.lam) < EPS10 && fabs(lp.phi - P->phi0) < EPS10) { |
106 | 0 | xy.x = xy.y = 0.; |
107 | 0 | break; |
108 | 0 | } |
109 | | |
110 | 5.12k | lat1 = P->phi0 / DEG_TO_RAD; |
111 | 5.12k | lon1 = 0; |
112 | 5.12k | lat2 = lp.phi / DEG_TO_RAD; |
113 | 5.12k | lon2 = lp.lam / DEG_TO_RAD; |
114 | | |
115 | 5.12k | geod_inverse(&Q->g, lat1, lon1, lat2, lon2, &s12, &azi1, &azi2); |
116 | 5.12k | azi1 *= DEG_TO_RAD; |
117 | 5.12k | xy.x = s12 * sin(azi1); |
118 | 5.12k | xy.y = s12 * cos(azi1); |
119 | 5.12k | break; |
120 | 5.12k | } |
121 | 5.12k | return xy; |
122 | 5.12k | } |
123 | | |
124 | 0 | static PJ_XY aeqd_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ |
125 | 0 | PJ_XY xy = {0.0, 0.0}; |
126 | 0 | struct pj_aeqd_data *Q = static_cast<struct pj_aeqd_data *>(P->opaque); |
127 | |
|
128 | 0 | if (Q->mode == pj_aeqd_ns::EQUIT) { |
129 | 0 | const double cosphi = cos(lp.phi); |
130 | 0 | const double sinphi = sin(lp.phi); |
131 | 0 | const double coslam = cos(lp.lam); |
132 | 0 | const double sinlam = sin(lp.lam); |
133 | |
|
134 | 0 | xy.y = cosphi * coslam; |
135 | |
|
136 | 0 | if (fabs(fabs(xy.y) - 1.) < TOL) { |
137 | 0 | if (xy.y < 0.) { |
138 | 0 | proj_errno_set( |
139 | 0 | P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); |
140 | 0 | return xy; |
141 | 0 | } else |
142 | 0 | return aeqd_e_forward(lp, P); |
143 | 0 | } else { |
144 | 0 | xy.y = acos(xy.y); |
145 | 0 | xy.y /= sin(xy.y); |
146 | 0 | xy.x = xy.y * cosphi * sinlam; |
147 | 0 | xy.y *= sinphi; |
148 | 0 | } |
149 | 0 | } else if (Q->mode == pj_aeqd_ns::OBLIQ) { |
150 | 0 | const double cosphi = cos(lp.phi); |
151 | 0 | const double sinphi = sin(lp.phi); |
152 | 0 | const double coslam = cos(lp.lam); |
153 | 0 | const double sinlam = sin(lp.lam); |
154 | 0 | const double cosphi_x_coslam = cosphi * coslam; |
155 | |
|
156 | 0 | xy.y = Q->sinph0 * sinphi + Q->cosph0 * cosphi_x_coslam; |
157 | |
|
158 | 0 | if (fabs(fabs(xy.y) - 1.) < TOL) { |
159 | 0 | if (xy.y < 0.) { |
160 | 0 | proj_errno_set( |
161 | 0 | P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); |
162 | 0 | return xy; |
163 | 0 | } else |
164 | 0 | return aeqd_e_forward(lp, P); |
165 | 0 | } else { |
166 | 0 | xy.y = acos(xy.y); |
167 | 0 | xy.y /= sin(xy.y); |
168 | 0 | xy.x = xy.y * cosphi * sinlam; |
169 | 0 | xy.y *= Q->cosph0 * sinphi - Q->sinph0 * cosphi_x_coslam; |
170 | 0 | } |
171 | 0 | } else { |
172 | 0 | double coslam = cos(lp.lam); |
173 | 0 | double sinlam = sin(lp.lam); |
174 | 0 | if (Q->mode == pj_aeqd_ns::N_POLE) { |
175 | 0 | lp.phi = -lp.phi; |
176 | 0 | coslam = -coslam; |
177 | 0 | } |
178 | 0 | if (fabs(lp.phi - M_HALFPI) < EPS10) { |
179 | 0 | proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); |
180 | 0 | return xy; |
181 | 0 | } |
182 | 0 | xy.y = (M_HALFPI + lp.phi); |
183 | 0 | xy.x = xy.y * sinlam; |
184 | 0 | xy.y *= coslam; |
185 | 0 | } |
186 | 0 | return xy; |
187 | 0 | } |
188 | | |
189 | 0 | static PJ_LP e_guam_inv(PJ_XY xy, PJ *P) { /* Guam elliptical */ |
190 | 0 | PJ_LP lp = {0.0, 0.0}; |
191 | 0 | struct pj_aeqd_data *Q = static_cast<struct pj_aeqd_data *>(P->opaque); |
192 | 0 | double x2, t = 0.0; |
193 | 0 | int i; |
194 | |
|
195 | 0 | x2 = 0.5 * xy.x * xy.x; |
196 | 0 | lp.phi = P->phi0; |
197 | 0 | for (i = 0; i < 3; ++i) { |
198 | 0 | t = P->e * sin(lp.phi); |
199 | 0 | t = sqrt(1. - t * t); |
200 | 0 | lp.phi = pj_inv_mlfn(Q->M1 + xy.y - x2 * tan(lp.phi) * t, Q->en); |
201 | 0 | } |
202 | 0 | lp.lam = xy.x * t / cos(lp.phi); |
203 | 0 | return lp; |
204 | 0 | } |
205 | | |
206 | 0 | static PJ_LP aeqd_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */ |
207 | 0 | PJ_LP lp = {0.0, 0.0}; |
208 | 0 | struct pj_aeqd_data *Q = static_cast<struct pj_aeqd_data *>(P->opaque); |
209 | 0 | double azi1, azi2, s12, lat1, lon1, lat2, lon2; |
210 | |
|
211 | 0 | if ((s12 = hypot(xy.x, xy.y)) < EPS10) { |
212 | 0 | lp.phi = P->phi0; |
213 | 0 | lp.lam = 0.; |
214 | 0 | return (lp); |
215 | 0 | } |
216 | 0 | if (Q->mode == pj_aeqd_ns::OBLIQ || Q->mode == pj_aeqd_ns::EQUIT) { |
217 | 0 | lat1 = P->phi0 / DEG_TO_RAD; |
218 | 0 | lon1 = 0; |
219 | 0 | azi1 = atan2(xy.x, xy.y) / DEG_TO_RAD; // Clockwise from north |
220 | 0 | geod_direct(&Q->g, lat1, lon1, azi1, s12, &lat2, &lon2, &azi2); |
221 | 0 | lp.phi = lat2 * DEG_TO_RAD; |
222 | 0 | lp.lam = lon2 * DEG_TO_RAD; |
223 | 0 | } else { /* Polar */ |
224 | 0 | lp.phi = pj_inv_mlfn( |
225 | 0 | Q->mode == pj_aeqd_ns::N_POLE ? Q->Mp - s12 : Q->Mp + s12, Q->en); |
226 | 0 | lp.lam = atan2(xy.x, Q->mode == pj_aeqd_ns::N_POLE ? -xy.y : xy.y); |
227 | 0 | } |
228 | 0 | return lp; |
229 | 0 | } |
230 | | |
231 | 0 | static PJ_LP aeqd_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ |
232 | 0 | PJ_LP lp = {0.0, 0.0}; |
233 | 0 | struct pj_aeqd_data *Q = static_cast<struct pj_aeqd_data *>(P->opaque); |
234 | 0 | double cosc, c_rh, sinc; |
235 | |
|
236 | 0 | c_rh = hypot(xy.x, xy.y); |
237 | 0 | if (c_rh > M_PI) { |
238 | 0 | if (c_rh - EPS10 > M_PI) { |
239 | 0 | proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); |
240 | 0 | return lp; |
241 | 0 | } |
242 | 0 | c_rh = M_PI; |
243 | 0 | } else if (c_rh < EPS10) { |
244 | 0 | lp.phi = P->phi0; |
245 | 0 | lp.lam = 0.; |
246 | 0 | return (lp); |
247 | 0 | } |
248 | 0 | if (Q->mode == pj_aeqd_ns::OBLIQ || Q->mode == pj_aeqd_ns::EQUIT) { |
249 | 0 | sinc = sin(c_rh); |
250 | 0 | cosc = cos(c_rh); |
251 | 0 | if (Q->mode == pj_aeqd_ns::EQUIT) { |
252 | 0 | lp.phi = aasin(P->ctx, xy.y * sinc / c_rh); |
253 | 0 | xy.x *= sinc; |
254 | 0 | xy.y = cosc * c_rh; |
255 | 0 | } else { |
256 | 0 | lp.phi = aasin(P->ctx, |
257 | 0 | cosc * Q->sinph0 + xy.y * sinc * Q->cosph0 / c_rh); |
258 | 0 | xy.y = (cosc - Q->sinph0 * sin(lp.phi)) * c_rh; |
259 | 0 | xy.x *= sinc * Q->cosph0; |
260 | 0 | } |
261 | 0 | lp.lam = xy.y == 0. ? 0. : atan2(xy.x, xy.y); |
262 | 0 | } else if (Q->mode == pj_aeqd_ns::N_POLE) { |
263 | 0 | lp.phi = M_HALFPI - c_rh; |
264 | 0 | lp.lam = atan2(xy.x, -xy.y); |
265 | 0 | } else { |
266 | 0 | lp.phi = c_rh - M_HALFPI; |
267 | 0 | lp.lam = atan2(xy.x, xy.y); |
268 | 0 | } |
269 | 0 | return lp; |
270 | 0 | } |
271 | | |
272 | 310 | PJ *PJ_PROJECTION(aeqd) { |
273 | 310 | struct pj_aeqd_data *Q = static_cast<struct pj_aeqd_data *>( |
274 | 310 | calloc(1, sizeof(struct pj_aeqd_data))); |
275 | 310 | if (nullptr == Q) |
276 | 0 | return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); |
277 | 310 | P->opaque = Q; |
278 | 310 | P->destructor = destructor; |
279 | | |
280 | 310 | geod_init(&Q->g, 1, P->f); |
281 | | |
282 | 310 | if (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) { |
283 | 13 | Q->mode = P->phi0 < 0. ? pj_aeqd_ns::S_POLE : pj_aeqd_ns::N_POLE; |
284 | 13 | Q->sinph0 = P->phi0 < 0. ? -1. : 1.; |
285 | 13 | Q->cosph0 = 0.; |
286 | 297 | } else if (fabs(P->phi0) < EPS10) { |
287 | 250 | Q->mode = pj_aeqd_ns::EQUIT; |
288 | 250 | Q->sinph0 = 0.; |
289 | 250 | Q->cosph0 = 1.; |
290 | 250 | } else { |
291 | 47 | Q->mode = pj_aeqd_ns::OBLIQ; |
292 | 47 | Q->sinph0 = sin(P->phi0); |
293 | 47 | Q->cosph0 = cos(P->phi0); |
294 | 47 | } |
295 | 310 | if (P->es == 0.0) { |
296 | 7 | P->inv = aeqd_s_inverse; |
297 | 7 | P->fwd = aeqd_s_forward; |
298 | 303 | } else { |
299 | 303 | if (!(Q->en = pj_enfn(P->n))) |
300 | 0 | return pj_default_destructor(P, 0); |
301 | 303 | if (pj_param(P->ctx, P->params, "bguam").i) { |
302 | 56 | Q->M1 = pj_mlfn(P->phi0, Q->sinph0, Q->cosph0, Q->en); |
303 | 56 | P->inv = e_guam_inv; |
304 | 56 | P->fwd = e_guam_fwd; |
305 | 247 | } else { |
306 | 247 | switch (Q->mode) { |
307 | 2 | case pj_aeqd_ns::N_POLE: |
308 | 2 | Q->Mp = pj_mlfn(M_HALFPI, 1., 0., Q->en); |
309 | 2 | break; |
310 | 11 | case pj_aeqd_ns::S_POLE: |
311 | 11 | Q->Mp = pj_mlfn(-M_HALFPI, -1., 0., Q->en); |
312 | 11 | break; |
313 | 194 | case pj_aeqd_ns::EQUIT: |
314 | 234 | case pj_aeqd_ns::OBLIQ: |
315 | 234 | Q->N1 = 1. / sqrt(1. - P->es * Q->sinph0 * Q->sinph0); |
316 | 234 | Q->He = P->e / sqrt(P->one_es); |
317 | 234 | Q->G = Q->sinph0 * Q->He; |
318 | 234 | Q->He *= Q->cosph0; |
319 | 234 | break; |
320 | 247 | } |
321 | 247 | P->inv = aeqd_e_inverse; |
322 | 247 | P->fwd = aeqd_e_forward; |
323 | 247 | } |
324 | 303 | } |
325 | | |
326 | 310 | return P; |
327 | 310 | } |
328 | | |
329 | | #undef EPS10 |
330 | | #undef TOL |