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