Coverage Report

Created: 2025-06-22 06:59

/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