/src/PROJ/src/projections/aitoff.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * Project: PROJ.4 |
3 | | * Purpose: Implementation of the aitoff (Aitoff) and wintri (Winkel Tripel) |
4 | | * projections. |
5 | | * Author: Gerald Evenden (1995) |
6 | | * Drazen Tutic, Lovro Gradiser (2015) - add inverse |
7 | | * Thomas Knudsen (2016) - revise/add regression tests |
8 | | * |
9 | | ****************************************************************************** |
10 | | * Copyright (c) 1995, Gerald Evenden |
11 | | * |
12 | | * Permission is hereby granted, free of charge, to any person obtaining a |
13 | | * copy of this software and associated documentation files (the "Software"), |
14 | | * to deal in the Software without restriction, including without limitation |
15 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
16 | | * and/or sell copies of the Software, and to permit persons to whom the |
17 | | * Software is furnished to do so, subject to the following conditions: |
18 | | * |
19 | | * The above copyright notice and this permission notice shall be included |
20 | | * in all copies or substantial portions of the Software. |
21 | | * |
22 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
23 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
24 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
25 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
26 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
27 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
28 | | * DEALINGS IN THE SOFTWARE. |
29 | | *****************************************************************************/ |
30 | | |
31 | | #include <errno.h> |
32 | | #include <math.h> |
33 | | |
34 | | #include "proj.h" |
35 | | #include "proj_internal.h" |
36 | | |
37 | | namespace pj_aitoff_ns { |
38 | | enum Mode { AITOFF = 0, WINKEL_TRIPEL = 1 }; |
39 | | } |
40 | | |
41 | | namespace { // anonymous namespace |
42 | | struct pj_aitoff_data { |
43 | | double cosphi1; |
44 | | enum pj_aitoff_ns::Mode mode; |
45 | | }; |
46 | | } // anonymous namespace |
47 | | |
48 | | PROJ_HEAD(aitoff, "Aitoff") "\n\tMisc Sph"; |
49 | | PROJ_HEAD(wintri, "Winkel Tripel") "\n\tMisc Sph\n\tlat_1"; |
50 | | |
51 | | #if 0 |
52 | | FORWARD(aitoff_s_forward); /* spheroid */ |
53 | | #endif |
54 | | |
55 | 0 | static PJ_XY aitoff_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */ |
56 | 0 | PJ_XY xy = {0.0, 0.0}; |
57 | 0 | struct pj_aitoff_data *Q = static_cast<struct pj_aitoff_data *>(P->opaque); |
58 | 0 | double c, d; |
59 | |
|
60 | | #if 0 |
61 | | // Likely domain of validity for wintri in +over mode. Should be confirmed |
62 | | // Cf https://lists.osgeo.org/pipermail/gdal-dev/2023-April/057164.html |
63 | | if (Q->mode == WINKEL_TRIPEL && fabs(lp.lam) > 2 * M_PI) { |
64 | | proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); |
65 | | return xy; |
66 | | } |
67 | | #endif |
68 | 0 | c = 0.5 * lp.lam; |
69 | 0 | d = acos(cos(lp.phi) * cos(c)); |
70 | 0 | if (d != 0.0) { /* basic Aitoff */ |
71 | 0 | xy.x = 2. * d * cos(lp.phi) * sin(c) * (xy.y = 1. / sin(d)); |
72 | 0 | xy.y *= d * sin(lp.phi); |
73 | 0 | } else |
74 | 0 | xy.x = xy.y = 0.; |
75 | 0 | if (Q->mode == pj_aitoff_ns::WINKEL_TRIPEL) { |
76 | 0 | xy.x = (xy.x + lp.lam * Q->cosphi1) * 0.5; |
77 | 0 | xy.y = (xy.y + lp.phi) * 0.5; |
78 | 0 | } |
79 | 0 | return (xy); |
80 | 0 | } |
81 | | |
82 | | /*********************************************************************************** |
83 | | * |
84 | | * Inverse functions added by Drazen Tutic and Lovro Gradiser based on paper: |
85 | | * |
86 | | * I.Özbug Biklirici and Cengizhan Ipbüker. A General Algorithm for the Inverse |
87 | | * Transformation of Map Projections Using Jacobian Matrices. In Proceedings of |
88 | | *the Third International Symposium Mathematical & Computational Applications, |
89 | | * pages 175{182, Turkey, September 2002. |
90 | | * |
91 | | * Expected accuracy is defined by EPSILON = 1e-12. Should be appropriate for |
92 | | * most applications of Aitoff and Winkel Tripel projections. |
93 | | * |
94 | | * Longitudes of 180W and 180E can be mixed in solution obtained. |
95 | | * |
96 | | * Inverse for Aitoff projection in poles is undefined, longitude value of 0 is |
97 | | *assumed. |
98 | | * |
99 | | * Contact : dtutic at geof.hr |
100 | | * Date: 2015-02-16 |
101 | | * |
102 | | ************************************************************************************/ |
103 | | |
104 | 0 | static PJ_LP aitoff_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */ |
105 | 0 | PJ_LP lp = {0.0, 0.0}; |
106 | 0 | struct pj_aitoff_data *Q = static_cast<struct pj_aitoff_data *>(P->opaque); |
107 | 0 | int iter, MAXITER = 10, round = 0, MAXROUND = 20; |
108 | 0 | double EPSILON = 1e-12, D, C, f1, f2, f1p, f1l, f2p, f2l, dp, dl, sl, sp, |
109 | 0 | cp, cl, x, y; |
110 | |
|
111 | 0 | if ((fabs(xy.x) < EPSILON) && (fabs(xy.y) < EPSILON)) { |
112 | 0 | lp.phi = 0.; |
113 | 0 | lp.lam = 0.; |
114 | 0 | return lp; |
115 | 0 | } |
116 | | |
117 | | /* initial values for Newton-Raphson method */ |
118 | 0 | lp.phi = xy.y; |
119 | 0 | lp.lam = xy.x; |
120 | 0 | do { |
121 | 0 | iter = 0; |
122 | 0 | do { |
123 | 0 | sl = sin(lp.lam * 0.5); |
124 | 0 | cl = cos(lp.lam * 0.5); |
125 | 0 | sp = sin(lp.phi); |
126 | 0 | cp = cos(lp.phi); |
127 | 0 | D = cp * cl; |
128 | 0 | C = 1. - D * D; |
129 | 0 | const double denom = pow(C, 1.5); |
130 | 0 | if (denom == 0) { |
131 | 0 | proj_errno_set( |
132 | 0 | P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); |
133 | 0 | return lp; |
134 | 0 | } |
135 | 0 | D = acos(D) / denom; |
136 | 0 | f1 = 2. * D * C * cp * sl; |
137 | 0 | f2 = D * C * sp; |
138 | 0 | f1p = 2. * (sl * cl * sp * cp / C - D * sp * sl); |
139 | 0 | f1l = cp * cp * sl * sl / C + D * cp * cl * sp * sp; |
140 | 0 | f2p = sp * sp * cl / C + D * sl * sl * cp; |
141 | 0 | f2l = 0.5 * (sp * cp * sl / C - D * sp * cp * cp * sl * cl); |
142 | 0 | if (Q->mode == pj_aitoff_ns::WINKEL_TRIPEL) { |
143 | 0 | f1 = 0.5 * (f1 + lp.lam * Q->cosphi1); |
144 | 0 | f2 = 0.5 * (f2 + lp.phi); |
145 | 0 | f1p *= 0.5; |
146 | 0 | f1l = 0.5 * (f1l + Q->cosphi1); |
147 | 0 | f2p = 0.5 * (f2p + 1.); |
148 | 0 | f2l *= 0.5; |
149 | 0 | } |
150 | 0 | f1 -= xy.x; |
151 | 0 | f2 -= xy.y; |
152 | 0 | dp = f1p * f2l - f2p * f1l; |
153 | 0 | dl = (f2 * f1p - f1 * f2p) / dp; |
154 | 0 | dp = (f1 * f2l - f2 * f1l) / dp; |
155 | 0 | dl = fmod(dl, M_PI); /* set to interval [-M_PI, M_PI] */ |
156 | 0 | lp.phi -= dp; |
157 | 0 | lp.lam -= dl; |
158 | 0 | } while ((fabs(dp) > EPSILON || fabs(dl) > EPSILON) && |
159 | 0 | (iter++ < MAXITER)); |
160 | 0 | if (lp.phi > M_PI_2) |
161 | 0 | lp.phi -= |
162 | 0 | 2. * (lp.phi - |
163 | 0 | M_PI_2); /* correct if symmetrical solution for Aitoff */ |
164 | 0 | if (lp.phi < -M_PI_2) |
165 | 0 | lp.phi -= |
166 | 0 | 2. * (lp.phi + |
167 | 0 | M_PI_2); /* correct if symmetrical solution for Aitoff */ |
168 | 0 | if ((fabs(fabs(lp.phi) - M_PI_2) < EPSILON) && |
169 | 0 | (Q->mode == pj_aitoff_ns::AITOFF)) |
170 | 0 | lp.lam = 0.; /* if pole in Aitoff, return longitude of 0 */ |
171 | | |
172 | | /* calculate x,y coordinates with solution obtained */ |
173 | 0 | if ((D = acos(cos(lp.phi) * cos(C = 0.5 * lp.lam))) != |
174 | 0 | 0.0) { /* Aitoff */ |
175 | 0 | y = 1. / sin(D); |
176 | 0 | x = 2. * D * cos(lp.phi) * sin(C) * y; |
177 | 0 | y *= D * sin(lp.phi); |
178 | 0 | } else |
179 | 0 | x = y = 0.; |
180 | 0 | if (Q->mode == pj_aitoff_ns::WINKEL_TRIPEL) { |
181 | 0 | x = (x + lp.lam * Q->cosphi1) * 0.5; |
182 | 0 | y = (y + lp.phi) * 0.5; |
183 | 0 | } |
184 | | /* if too far from given values of x,y, repeat with better approximation |
185 | | * of phi,lam */ |
186 | 0 | } while (((fabs(xy.x - x) > EPSILON) || (fabs(xy.y - y) > EPSILON)) && |
187 | 0 | (round++ < MAXROUND)); |
188 | | |
189 | 0 | if (iter == MAXITER && round == MAXROUND) { |
190 | 0 | proj_context_errno_set( |
191 | 0 | P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); |
192 | | /* fprintf(stderr, "Warning: Accuracy of 1e-12 not reached. Last |
193 | | * increments: dlat=%e and dlon=%e\n", dp, dl); */ |
194 | 0 | } |
195 | |
|
196 | 0 | return lp; |
197 | 0 | } |
198 | | |
199 | 32 | static PJ *pj_aitoff_setup(PJ *P) { |
200 | 32 | P->inv = aitoff_s_inverse; |
201 | 32 | P->fwd = aitoff_s_forward; |
202 | 32 | P->es = 0.; |
203 | 32 | return P; |
204 | 32 | } |
205 | | |
206 | 21 | PJ *PJ_PROJECTION(aitoff) { |
207 | 21 | struct pj_aitoff_data *Q = static_cast<struct pj_aitoff_data *>( |
208 | 21 | calloc(1, sizeof(struct pj_aitoff_data))); |
209 | 21 | if (nullptr == Q) |
210 | 0 | return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); |
211 | 21 | P->opaque = Q; |
212 | | |
213 | 21 | Q->mode = pj_aitoff_ns::AITOFF; |
214 | 21 | return pj_aitoff_setup(P); |
215 | 21 | } |
216 | | |
217 | 11 | PJ *PJ_PROJECTION(wintri) { |
218 | 11 | struct pj_aitoff_data *Q = static_cast<struct pj_aitoff_data *>( |
219 | 11 | calloc(1, sizeof(struct pj_aitoff_data))); |
220 | 11 | if (nullptr == Q) |
221 | 0 | return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); |
222 | 11 | P->opaque = Q; |
223 | | |
224 | 11 | Q->mode = pj_aitoff_ns::WINKEL_TRIPEL; |
225 | 11 | if (pj_param(P->ctx, P->params, "tlat_1").i) { |
226 | 3 | if ((Q->cosphi1 = cos(pj_param(P->ctx, P->params, "rlat_1").f)) == 0.) { |
227 | 0 | proj_log_error( |
228 | 0 | P, _("Invalid value for lat_1: |lat_1| should be < 90°")); |
229 | 0 | return pj_default_destructor(P, |
230 | 0 | PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
231 | 0 | } |
232 | 3 | } else /* 50d28' or acos(2/pi) */ |
233 | 8 | Q->cosphi1 = 0.636619772367581343; |
234 | 11 | return pj_aitoff_setup(P); |
235 | 11 | } |