/src/proj/src/projections/eqearth.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | Equal Earth is a projection inspired by the Robinson projection, but unlike |
3 | | the Robinson projection retains the relative size of areas. The projection |
4 | | was designed in 2018 by Bojan Savric, Tom Patterson and Bernhard Jenny. |
5 | | |
6 | | Publication: |
7 | | Bojan Savric, Tom Patterson & Bernhard Jenny (2018). The Equal Earth map |
8 | | projection, International Journal of Geographical Information Science, |
9 | | DOI: 10.1080/13658816.2018.1504949 |
10 | | |
11 | | Port to PROJ by Juernjakob Dugge, 16 August 2018 |
12 | | Added ellipsoidal equations by Bojan Savric, 22 August 2018 |
13 | | */ |
14 | | |
15 | | #include <errno.h> |
16 | | #include <math.h> |
17 | | |
18 | | #include "proj.h" |
19 | | #include "proj_internal.h" |
20 | | |
21 | | PROJ_HEAD(eqearth, "Equal Earth") "\n\tPCyl, Sph&Ell"; |
22 | | |
23 | | /* A1..A4, polynomial coefficients */ |
24 | 0 | #define A1 1.340264 |
25 | 0 | #define A2 -0.081106 |
26 | 0 | #define A3 0.000893 |
27 | 0 | #define A4 0.003796 |
28 | 0 | #define M (sqrt(3.0) / 2.0) |
29 | | |
30 | 0 | #define MAX_Y 1.3173627591574 /* 90° latitude on a sphere with radius 1 */ |
31 | 0 | #define EPS 1e-11 |
32 | 0 | #define MAX_ITER 12 |
33 | | |
34 | | namespace { // anonymous namespace |
35 | | struct pj_eqearth { |
36 | | double qp; |
37 | | double rqda; |
38 | | double *apa; |
39 | | }; |
40 | | } // anonymous namespace |
41 | | |
42 | | static PJ_XY eqearth_e_forward(PJ_LP lp, |
43 | 0 | PJ *P) { /* Ellipsoidal/spheroidal, forward */ |
44 | 0 | PJ_XY xy = {0.0, 0.0}; |
45 | 0 | struct pj_eqearth *Q = static_cast<struct pj_eqearth *>(P->opaque); |
46 | 0 | double sbeta; |
47 | 0 | double psi, psi2, psi6; |
48 | | |
49 | | /* Spheroidal case, using sine latitude */ |
50 | 0 | sbeta = sin(lp.phi); |
51 | | |
52 | | /* In the ellipsoidal case, we convert sbeta to sine of authalic latitude */ |
53 | 0 | if (P->es != 0.0) { |
54 | 0 | sbeta = pj_authalic_lat_q(sbeta, P) / Q->qp; |
55 | | |
56 | | /* Rounding error. */ |
57 | 0 | if (fabs(sbeta) > 1) |
58 | 0 | sbeta = sbeta > 0 ? 1 : -1; |
59 | 0 | } |
60 | | |
61 | | /* Equal Earth projection */ |
62 | 0 | psi = asin(M * sbeta); |
63 | 0 | psi2 = psi * psi; |
64 | 0 | psi6 = psi2 * psi2 * psi2; |
65 | |
|
66 | 0 | xy.x = lp.lam * cos(psi) / |
67 | 0 | (M * (A1 + 3 * A2 * psi2 + psi6 * (7 * A3 + 9 * A4 * psi2))); |
68 | 0 | xy.y = psi * (A1 + A2 * psi2 + psi6 * (A3 + A4 * psi2)); |
69 | | |
70 | | /* Adjusting x and y for authalic radius */ |
71 | 0 | xy.x *= Q->rqda; |
72 | 0 | xy.y *= Q->rqda; |
73 | |
|
74 | 0 | return xy; |
75 | 0 | } |
76 | | |
77 | | static PJ_LP eqearth_e_inverse(PJ_XY xy, |
78 | 0 | PJ *P) { /* Ellipsoidal/spheroidal, inverse */ |
79 | 0 | PJ_LP lp = {0.0, 0.0}; |
80 | 0 | struct pj_eqearth *Q = static_cast<struct pj_eqearth *>(P->opaque); |
81 | 0 | double yc, y2, y6; |
82 | 0 | int i; |
83 | | |
84 | | /* Adjusting x and y for authalic radius */ |
85 | 0 | xy.x /= Q->rqda; |
86 | 0 | xy.y /= Q->rqda; |
87 | | |
88 | | /* Make sure y is inside valid range */ |
89 | 0 | if (xy.y > MAX_Y) |
90 | 0 | xy.y = MAX_Y; |
91 | 0 | else if (xy.y < -MAX_Y) |
92 | 0 | xy.y = -MAX_Y; |
93 | |
|
94 | 0 | yc = xy.y; |
95 | | |
96 | | /* Newton-Raphson */ |
97 | 0 | for (i = MAX_ITER; i; --i) { |
98 | 0 | double f, fder, tol; |
99 | |
|
100 | 0 | y2 = yc * yc; |
101 | 0 | y6 = y2 * y2 * y2; |
102 | |
|
103 | 0 | f = yc * (A1 + A2 * y2 + y6 * (A3 + A4 * y2)) - xy.y; |
104 | 0 | fder = A1 + 3 * A2 * y2 + y6 * (7 * A3 + 9 * A4 * y2); |
105 | |
|
106 | 0 | tol = f / fder; |
107 | 0 | yc -= tol; |
108 | |
|
109 | 0 | if (fabs(tol) < EPS) |
110 | 0 | break; |
111 | 0 | } |
112 | |
|
113 | 0 | if (i == 0) { |
114 | 0 | proj_context_errno_set( |
115 | 0 | P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); |
116 | 0 | return lp; |
117 | 0 | } |
118 | | |
119 | | /* Longitude */ |
120 | 0 | y2 = yc * yc; |
121 | 0 | y6 = y2 * y2 * y2; |
122 | |
|
123 | 0 | lp.lam = |
124 | 0 | M * xy.x * (A1 + 3 * A2 * y2 + y6 * (7 * A3 + 9 * A4 * y2)) / cos(yc); |
125 | | |
126 | | /* Latitude (for spheroidal case, this is latitude */ |
127 | 0 | lp.phi = asin(sin(yc) / M); |
128 | | |
129 | | /* Ellipsoidal case, converting auth. latitude */ |
130 | 0 | if (P->es != 0.0) |
131 | 0 | lp.phi = pj_authalic_lat_inverse(lp.phi, Q->apa, P, Q->qp); |
132 | |
|
133 | 0 | return lp; |
134 | 0 | } |
135 | | |
136 | 121 | static PJ *destructor(PJ *P, int errlev) { /* Destructor */ |
137 | 121 | if (nullptr == P) |
138 | 0 | return nullptr; |
139 | | |
140 | 121 | if (nullptr == P->opaque) |
141 | 0 | return pj_default_destructor(P, errlev); |
142 | | |
143 | 121 | free(static_cast<struct pj_eqearth *>(P->opaque)->apa); |
144 | 121 | return pj_default_destructor(P, errlev); |
145 | 121 | } |
146 | | |
147 | 121 | PJ *PJ_PROJECTION(eqearth) { |
148 | 121 | struct pj_eqearth *Q = |
149 | 121 | static_cast<struct pj_eqearth *>(calloc(1, sizeof(struct pj_eqearth))); |
150 | 121 | if (nullptr == Q) |
151 | 0 | return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); |
152 | 121 | P->opaque = Q; |
153 | 121 | P->destructor = destructor; |
154 | 121 | P->fwd = eqearth_e_forward; |
155 | 121 | P->inv = eqearth_e_inverse; |
156 | 121 | Q->rqda = 1.0; |
157 | | |
158 | | /* Ellipsoidal case */ |
159 | 121 | if (P->es != 0.0) { |
160 | 72 | Q->apa = pj_authalic_lat_compute_coeffs(P->n); /* For auth_lat(). */ |
161 | 72 | if (nullptr == Q->apa) |
162 | 0 | return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); |
163 | 72 | Q->qp = |
164 | 72 | pj_authalic_lat_q(1.0, P); /* For auth_lat(). */ |
165 | 72 | Q->rqda = sqrt(0.5 * Q->qp); /* Authalic radius divided by major axis */ |
166 | 72 | } |
167 | | |
168 | 121 | return P; |
169 | 121 | } |
170 | | |
171 | | #undef A1 |
172 | | #undef A2 |
173 | | #undef A3 |
174 | | #undef A4 |
175 | | #undef M |
176 | | |
177 | | #undef MAX_Y |
178 | | #undef EPS |
179 | | #undef MAX_ITER |