Coverage Report

Created: 2025-06-09 08:44

/src/gdal/proj/src/projections/laea.cpp
Line
Count
Source (jump to first uncovered line)
1
#include "proj.h"
2
#include "proj_internal.h"
3
#include <errno.h>
4
#include <math.h>
5
6
PROJ_HEAD(laea, "Lambert Azimuthal Equal Area") "\n\tAzi, Sph&Ell";
7
8
namespace pj_laea_ns {
9
enum Mode { N_POLE = 0, S_POLE = 1, EQUIT = 2, OBLIQ = 3 };
10
}
11
12
namespace { // anonymous namespace
13
struct pj_laea_data {
14
    double sinb1;
15
    double cosb1;
16
    double xmf;
17
    double ymf;
18
    double mmf;
19
    double qp;
20
    double dd;
21
    double rq;
22
    double *apa;
23
    enum pj_laea_ns::Mode mode;
24
};
25
} // anonymous namespace
26
27
109k
#define EPS10 1.e-10
28
29
0
static PJ_XY laea_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */
30
0
    PJ_XY xy = {0.0, 0.0};
31
0
    struct pj_laea_data *Q = static_cast<struct pj_laea_data *>(P->opaque);
32
0
    double coslam, sinlam, sinphi, cosphi, q, sinb = 0.0, cosb = 0.0, b = 0.0;
33
34
0
    coslam = cos(lp.lam);
35
0
    sinlam = sin(lp.lam);
36
0
    sinphi = sin(lp.phi);
37
0
    cosphi = cos(lp.phi);
38
0
    const double xi = pj_authalic_lat(lp.phi, sinphi, cosphi,
39
0
                                      Q->apa, P, Q->qp);
40
0
    q = sin(xi) * Q->qp;
41
42
0
    if (Q->mode == pj_laea_ns::OBLIQ || Q->mode == pj_laea_ns::EQUIT) {
43
0
        sinb = sin(xi); cosb = cos(xi);
44
0
    }
45
46
0
    switch (Q->mode) {
47
0
    case pj_laea_ns::OBLIQ:
48
0
        b = 1. + Q->sinb1 * sinb + Q->cosb1 * cosb * coslam;
49
0
        break;
50
0
    case pj_laea_ns::EQUIT:
51
0
        b = 1. + cosb * coslam;
52
0
        break;
53
0
    case pj_laea_ns::N_POLE:
54
0
        b = M_HALFPI + lp.phi;
55
0
        q = Q->qp - q;
56
0
        break;
57
0
    case pj_laea_ns::S_POLE:
58
0
        b = lp.phi - M_HALFPI;
59
0
        q = Q->qp + q;
60
0
        break;
61
0
    }
62
0
    if (fabs(b) < EPS10) {
63
0
        proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
64
0
        return xy;
65
0
    }
66
67
0
    switch (Q->mode) {
68
0
    case pj_laea_ns::OBLIQ:
69
0
        b = sqrt(2. / b);
70
0
        xy.y = Q->ymf * b * (Q->cosb1 * sinb - Q->sinb1 * cosb * coslam);
71
0
        goto eqcon;
72
0
        break;
73
0
    case pj_laea_ns::EQUIT:
74
0
        b = sqrt(2. / (1. + cosb * coslam));
75
0
        xy.y = b * sinb * Q->ymf;
76
0
    eqcon:
77
0
        xy.x = Q->xmf * b * cosb * sinlam;
78
0
        break;
79
0
    case pj_laea_ns::N_POLE:
80
0
    case pj_laea_ns::S_POLE:
81
0
        if (q >= 1e-15) {
82
0
            b = sqrt(q);
83
0
            xy.x = b * sinlam;
84
0
            xy.y = coslam * (Q->mode == pj_laea_ns::S_POLE ? b : -b);
85
0
        } else
86
0
            xy.x = xy.y = 0.;
87
0
        break;
88
0
    }
89
0
    return xy;
90
0
}
91
92
0
static PJ_XY laea_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */
93
0
    PJ_XY xy = {0.0, 0.0};
94
0
    struct pj_laea_data *Q = static_cast<struct pj_laea_data *>(P->opaque);
95
0
    double coslam, cosphi, sinphi;
96
97
0
    sinphi = sin(lp.phi);
98
0
    cosphi = cos(lp.phi);
99
0
    coslam = cos(lp.lam);
100
0
    switch (Q->mode) {
101
0
    case pj_laea_ns::EQUIT:
102
0
        xy.y = 1. + cosphi * coslam;
103
0
        goto oblcon;
104
0
    case pj_laea_ns::OBLIQ:
105
0
        xy.y = 1. + Q->sinb1 * sinphi + Q->cosb1 * cosphi * coslam;
106
0
    oblcon:
107
0
        if (xy.y <= EPS10) {
108
0
            proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
109
0
            return xy;
110
0
        }
111
0
        xy.y = sqrt(2. / xy.y);
112
0
        xy.x = xy.y * cosphi * sin(lp.lam);
113
0
        xy.y *= Q->mode == pj_laea_ns::EQUIT
114
0
                    ? sinphi
115
0
                    : Q->cosb1 * sinphi - Q->sinb1 * cosphi * coslam;
116
0
        break;
117
0
    case pj_laea_ns::N_POLE:
118
0
        coslam = -coslam;
119
0
        PROJ_FALLTHROUGH;
120
0
    case pj_laea_ns::S_POLE:
121
0
        if (fabs(lp.phi + P->phi0) < EPS10) {
122
0
            proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
123
0
            return xy;
124
0
        }
125
0
        xy.y = M_FORTPI - lp.phi * .5;
126
0
        xy.y = 2. * (Q->mode == pj_laea_ns::S_POLE ? cos(xy.y) : sin(xy.y));
127
0
        xy.x = xy.y * sin(lp.lam);
128
0
        xy.y *= coslam;
129
0
        break;
130
0
    }
131
0
    return xy;
132
0
}
133
134
0
static PJ_LP laea_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */
135
0
    PJ_LP lp = {0.0, 0.0};
136
0
    struct pj_laea_data *Q = static_cast<struct pj_laea_data *>(P->opaque);
137
0
    double cCe, sCe, q, rho, ab = 0.0;
138
139
0
    switch (Q->mode) {
140
0
    case pj_laea_ns::EQUIT:
141
0
    case pj_laea_ns::OBLIQ: {
142
0
        xy.x /= Q->dd;
143
0
        xy.y *= Q->dd;
144
0
        rho = hypot(xy.x, xy.y);
145
0
        if (rho < EPS10) {
146
0
            lp.lam = 0.;
147
0
            lp.phi = P->phi0;
148
0
            return lp;
149
0
        }
150
0
        const double asin_argument = .5 * rho / Q->rq;
151
0
        if (asin_argument > 1) {
152
0
            proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
153
0
            return lp;
154
0
        }
155
0
        sCe = 2. * asin(asin_argument);
156
0
        cCe = cos(sCe);
157
0
        sCe = sin(sCe);
158
0
        xy.x *= sCe;
159
0
        if (Q->mode == pj_laea_ns::OBLIQ) {
160
0
            ab = cCe * Q->sinb1 + xy.y * sCe * Q->cosb1 / rho;
161
0
            xy.y = rho * Q->cosb1 * cCe - xy.y * Q->sinb1 * sCe;
162
0
        } else {
163
0
            ab = xy.y * sCe / rho;
164
0
            xy.y = rho * cCe;
165
0
        }
166
0
        break;
167
0
    }
168
0
    case pj_laea_ns::N_POLE:
169
0
        xy.y = -xy.y;
170
0
        PROJ_FALLTHROUGH;
171
0
    case pj_laea_ns::S_POLE:
172
0
        q = (xy.x * xy.x + xy.y * xy.y);
173
0
        if (q == 0.0) {
174
0
            lp.lam = 0.;
175
0
            lp.phi = P->phi0;
176
0
            return (lp);
177
0
        }
178
0
        ab = 1. - q / Q->qp;
179
0
        if (Q->mode == pj_laea_ns::S_POLE)
180
0
            ab = -ab;
181
0
        break;
182
0
    }
183
0
    lp.lam = atan2(xy.x, xy.y);
184
0
    lp.phi = pj_authalic_lat_inverse(asin(ab), Q->apa, P, Q->qp);
185
0
    return lp;
186
0
}
187
188
0
static PJ_LP laea_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */
189
0
    PJ_LP lp = {0.0, 0.0};
190
0
    struct pj_laea_data *Q = static_cast<struct pj_laea_data *>(P->opaque);
191
0
    double cosz = 0.0, rh, sinz = 0.0;
192
193
0
    rh = hypot(xy.x, xy.y);
194
0
    if ((lp.phi = rh * .5) > 1.) {
195
0
        proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
196
0
        return lp;
197
0
    }
198
0
    lp.phi = 2. * asin(lp.phi);
199
0
    if (Q->mode == pj_laea_ns::OBLIQ || Q->mode == pj_laea_ns::EQUIT) {
200
0
        sinz = sin(lp.phi);
201
0
        cosz = cos(lp.phi);
202
0
    }
203
0
    switch (Q->mode) {
204
0
    case pj_laea_ns::EQUIT:
205
0
        lp.phi = fabs(rh) <= EPS10 ? 0. : asin(xy.y * sinz / rh);
206
0
        xy.x *= sinz;
207
0
        xy.y = cosz * rh;
208
0
        break;
209
0
    case pj_laea_ns::OBLIQ:
210
0
        lp.phi = fabs(rh) <= EPS10
211
0
                     ? P->phi0
212
0
                     : asin(cosz * Q->sinb1 + xy.y * sinz * Q->cosb1 / rh);
213
0
        xy.x *= sinz * Q->cosb1;
214
0
        xy.y = (cosz - sin(lp.phi) * Q->sinb1) * rh;
215
0
        break;
216
0
    case pj_laea_ns::N_POLE:
217
0
        xy.y = -xy.y;
218
0
        lp.phi = M_HALFPI - lp.phi;
219
0
        break;
220
0
    case pj_laea_ns::S_POLE:
221
0
        lp.phi -= M_HALFPI;
222
0
        break;
223
0
    }
224
0
    lp.lam = (xy.y == 0. &&
225
0
              (Q->mode == pj_laea_ns::EQUIT || Q->mode == pj_laea_ns::OBLIQ))
226
0
                 ? 0.
227
0
                 : atan2(xy.x, xy.y);
228
0
    return (lp);
229
0
}
230
231
40.3k
static PJ *pj_laea_destructor(PJ *P, int errlev) {
232
40.3k
    if (nullptr == P)
233
0
        return nullptr;
234
235
40.3k
    if (nullptr == P->opaque)
236
0
        return pj_default_destructor(P, errlev);
237
238
40.3k
    free(static_cast<struct pj_laea_data *>(P->opaque)->apa);
239
240
40.3k
    return pj_default_destructor(P, errlev);
241
40.3k
}
242
243
40.3k
PJ *PJ_PROJECTION(laea) {
244
40.3k
    double t;
245
40.3k
    struct pj_laea_data *Q = static_cast<struct pj_laea_data *>(
246
40.3k
        calloc(1, sizeof(struct pj_laea_data)));
247
40.3k
    if (nullptr == Q)
248
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
249
40.3k
    P->opaque = Q;
250
40.3k
    P->destructor = pj_laea_destructor;
251
252
40.3k
    t = fabs(P->phi0);
253
40.3k
    if (t > M_HALFPI + EPS10) {
254
0
        proj_log_error(P,
255
0
                       _("Invalid value for lat_0: |lat_0| should be <= 90°"));
256
0
        return pj_laea_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
257
0
    }
258
40.3k
    if (fabs(t - M_HALFPI) < EPS10)
259
11.3k
        Q->mode = P->phi0 < 0. ? pj_laea_ns::S_POLE : pj_laea_ns::N_POLE;
260
29.0k
    else if (fabs(t) < EPS10)
261
18.0k
        Q->mode = pj_laea_ns::EQUIT;
262
10.9k
    else
263
10.9k
        Q->mode = pj_laea_ns::OBLIQ;
264
40.3k
    if (P->es != 0.0) {
265
27.1k
        double sinphi, cosphi;
266
267
27.1k
        P->e = sqrt(P->es);
268
27.1k
        Q->qp = pj_authalic_lat_q(1.0, P);
269
27.1k
        Q->mmf = .5 / (1. - P->es);
270
27.1k
        Q->apa = pj_authalic_lat_compute_coeffs(P->n);
271
27.1k
        if (nullptr == Q->apa)
272
0
            return pj_laea_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
273
27.1k
        switch (Q->mode) {
274
7.11k
        case pj_laea_ns::N_POLE:
275
9.78k
        case pj_laea_ns::S_POLE:
276
9.78k
            Q->dd = 1.;
277
9.78k
            break;
278
11.4k
        case pj_laea_ns::EQUIT:
279
11.4k
            Q->dd = 1. / (Q->rq = sqrt(.5 * Q->qp));
280
11.4k
            Q->xmf = 1.;
281
11.4k
            Q->ymf = .5 * Q->qp;
282
11.4k
            break;
283
5.96k
        case pj_laea_ns::OBLIQ:
284
5.96k
            Q->rq = sqrt(.5 * Q->qp);
285
5.96k
            sinphi = sin(P->phi0); cosphi = cos(P->phi0);
286
5.96k
            const double b1 = pj_authalic_lat(P->phi0, sinphi, cosphi,
287
5.96k
                                              Q->apa, P, Q->qp);
288
5.96k
            Q->sinb1 = sin(b1);
289
5.96k
            Q->cosb1 = cos(b1);
290
5.96k
            Q->dd = cos(P->phi0) /
291
5.96k
                    (sqrt(1. - P->es * sinphi * sinphi) * Q->rq * Q->cosb1);
292
5.96k
            Q->ymf = (Q->xmf = Q->rq) / Q->dd;
293
5.96k
            Q->xmf *= Q->dd;
294
5.96k
            break;
295
27.1k
        }
296
27.1k
        P->inv = laea_e_inverse;
297
27.1k
        P->fwd = laea_e_forward;
298
27.1k
    } else {
299
13.1k
        if (Q->mode == pj_laea_ns::OBLIQ) {
300
5.01k
            Q->sinb1 = sin(P->phi0);
301
5.01k
            Q->cosb1 = cos(P->phi0);
302
5.01k
        }
303
13.1k
        P->inv = laea_s_inverse;
304
13.1k
        P->fwd = laea_s_forward;
305
13.1k
    }
306
307
40.3k
    return P;
308
40.3k
}