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