Coverage Report

Created: 2025-07-23 06:58

/src/PROJ/src/projections/stere.cpp
Line
Count
Source (jump to first uncovered line)
1
2
#include "proj.h"
3
#include "proj_internal.h"
4
#include <errno.h>
5
#include <math.h>
6
7
PROJ_HEAD(stere, "Stereographic") "\n\tAzi, Sph&Ell\n\tlat_ts=";
8
PROJ_HEAD(ups, "Universal Polar Stereographic") "\n\tAzi, Ell\n\tsouth";
9
10
namespace { // anonymous namespace
11
enum Mode { S_POLE = 0, N_POLE = 1, OBLIQ = 2, EQUIT = 3 };
12
} // anonymous namespace
13
14
namespace { // anonymous namespace
15
struct pj_stere {
16
    double phits;
17
    double sinX1;
18
    double cosX1;
19
    double akm1;
20
    enum Mode mode;
21
};
22
} // anonymous namespace
23
24
4
#define sinph0 static_cast<struct pj_stere *>(P->opaque)->sinX1
25
4
#define cosph0 static_cast<struct pj_stere *>(P->opaque)->cosX1
26
2.24k
#define EPS10 1.e-10
27
0
#define TOL 1.e-8
28
0
#define NITER 8
29
0
#define CONV 1.e-10
30
31
17.1k
static double ssfn_(double phit, double sinphi, double eccen) {
32
17.1k
    sinphi *= eccen;
33
17.1k
    return (tan(.5 * (M_HALFPI + phit)) *
34
17.1k
            pow((1. - sinphi) / (1. + sinphi), .5 * eccen));
35
17.1k
}
36
37
29.7k
static PJ_XY stere_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */
38
29.7k
    PJ_XY xy = {0.0, 0.0};
39
29.7k
    struct pj_stere *Q = static_cast<struct pj_stere *>(P->opaque);
40
29.7k
    double coslam, sinlam, sinX = 0.0, cosX = 0.0, A = 0.0, sinphi;
41
42
29.7k
    coslam = cos(lp.lam);
43
29.7k
    sinlam = sin(lp.lam);
44
29.7k
    sinphi = sin(lp.phi);
45
29.7k
    if (Q->mode == OBLIQ || Q->mode == EQUIT) {
46
16.5k
        const double X = 2. * atan(ssfn_(lp.phi, sinphi, P->e)) - M_HALFPI;
47
16.5k
        sinX = sin(X);
48
16.5k
        cosX = cos(X);
49
16.5k
    }
50
51
29.7k
    switch (Q->mode) {
52
7.56k
    case OBLIQ: {
53
7.56k
        const double denom =
54
7.56k
            Q->cosX1 * (1. + Q->sinX1 * sinX + Q->cosX1 * cosX * coslam);
55
7.56k
        if (denom == 0) {
56
0
            proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
57
0
            return proj_coord_error().xy;
58
0
        }
59
7.56k
        A = Q->akm1 / denom;
60
7.56k
        xy.y = A * (Q->cosX1 * sinX - Q->sinX1 * cosX * coslam);
61
7.56k
        xy.x = A * cosX;
62
7.56k
        break;
63
7.56k
    }
64
65
8.98k
    case EQUIT:
66
        /* avoid zero division */
67
8.98k
        if (1. + cosX * coslam == 0.0) {
68
0
            xy.y = HUGE_VAL;
69
8.98k
        } else {
70
8.98k
            A = Q->akm1 / (1. + cosX * coslam);
71
8.98k
            xy.y = A * sinX;
72
8.98k
        }
73
8.98k
        xy.x = A * cosX;
74
8.98k
        break;
75
76
2.60k
    case S_POLE:
77
2.60k
        lp.phi = -lp.phi;
78
2.60k
        coslam = -coslam;
79
2.60k
        sinphi = -sinphi;
80
2.60k
        PROJ_FALLTHROUGH;
81
13.1k
    case N_POLE:
82
13.1k
        if (fabs(lp.phi - M_HALFPI) < 1e-15)
83
0
            xy.x = 0;
84
13.1k
        else
85
13.1k
            xy.x = Q->akm1 * pj_tsfn(lp.phi, sinphi, P->e);
86
13.1k
        xy.y = -xy.x * coslam;
87
13.1k
        break;
88
29.7k
    }
89
90
29.7k
    xy.x = xy.x * sinlam;
91
29.7k
    return xy;
92
29.7k
}
93
94
0
static PJ_XY stere_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */
95
0
    PJ_XY xy = {0.0, 0.0};
96
0
    struct pj_stere *Q = static_cast<struct pj_stere *>(P->opaque);
97
0
    double sinphi, cosphi, coslam, sinlam;
98
99
0
    sinphi = sin(lp.phi);
100
0
    cosphi = cos(lp.phi);
101
0
    coslam = cos(lp.lam);
102
0
    sinlam = sin(lp.lam);
103
104
0
    switch (Q->mode) {
105
0
    case EQUIT:
106
0
        xy.y = 1. + cosphi * coslam;
107
0
        goto oblcon;
108
0
    case OBLIQ:
109
0
        xy.y = 1. + sinph0 * sinphi + cosph0 * cosphi * coslam;
110
0
    oblcon:
111
0
        if (xy.y <= EPS10) {
112
0
            proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
113
0
            return xy;
114
0
        }
115
0
        xy.y = Q->akm1 / xy.y;
116
0
        xy.x = xy.y * cosphi * sinlam;
117
0
        xy.y *= (Q->mode == EQUIT) ? sinphi
118
0
                                   : cosph0 * sinphi - sinph0 * cosphi * coslam;
119
0
        break;
120
0
    case N_POLE:
121
0
        coslam = -coslam;
122
0
        lp.phi = -lp.phi;
123
0
        PROJ_FALLTHROUGH;
124
0
    case S_POLE:
125
0
        if (fabs(lp.phi - M_HALFPI) < TOL) {
126
0
            proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
127
0
            return xy;
128
0
        }
129
0
        xy.y = Q->akm1 * tan(M_FORTPI + .5 * lp.phi);
130
0
        xy.x = sinlam * xy.y;
131
0
        xy.y *= coslam;
132
0
        break;
133
0
    }
134
0
    return xy;
135
0
}
136
137
0
static PJ_LP stere_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */
138
0
    PJ_LP lp = {0.0, 0.0};
139
0
    struct pj_stere *Q = static_cast<struct pj_stere *>(P->opaque);
140
0
    double cosphi, sinphi, tp = 0.0, phi_l = 0.0, rho, halfe = 0.0,
141
0
                           halfpi = 0.0;
142
143
0
    rho = hypot(xy.x, xy.y);
144
145
0
    switch (Q->mode) {
146
0
    case OBLIQ:
147
0
    case EQUIT:
148
0
        tp = 2. * atan2(rho * Q->cosX1, Q->akm1);
149
0
        cosphi = cos(tp);
150
0
        sinphi = sin(tp);
151
0
        if (rho == 0.0)
152
0
            phi_l = asin(cosphi * Q->sinX1);
153
0
        else
154
0
            phi_l = asin(cosphi * Q->sinX1 + (xy.y * sinphi * Q->cosX1 / rho));
155
156
0
        tp = tan(.5 * (M_HALFPI + phi_l));
157
0
        xy.x *= sinphi;
158
0
        xy.y = rho * Q->cosX1 * cosphi - xy.y * Q->sinX1 * sinphi;
159
0
        halfpi = M_HALFPI;
160
0
        halfe = .5 * P->e;
161
0
        break;
162
0
    case N_POLE:
163
0
        xy.y = -xy.y;
164
0
        PROJ_FALLTHROUGH;
165
0
    case S_POLE:
166
0
        tp = -rho / Q->akm1;
167
0
        phi_l = M_HALFPI - 2. * atan(tp);
168
0
        halfpi = -M_HALFPI;
169
0
        halfe = -.5 * P->e;
170
0
        break;
171
0
    }
172
173
0
    for (int i = NITER; i > 0; --i) {
174
0
        sinphi = P->e * sin(phi_l);
175
0
        lp.phi =
176
0
            2. * atan(tp * pow((1. + sinphi) / (1. - sinphi), halfe)) - halfpi;
177
0
        if (fabs(phi_l - lp.phi) < CONV) {
178
0
            if (Q->mode == S_POLE)
179
0
                lp.phi = -lp.phi;
180
0
            lp.lam = (xy.x == 0. && xy.y == 0.) ? 0. : atan2(xy.x, xy.y);
181
0
            return lp;
182
0
        }
183
0
        phi_l = lp.phi;
184
0
    }
185
186
0
    proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
187
0
    return lp;
188
0
}
189
190
0
static PJ_LP stere_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */
191
0
    PJ_LP lp = {0.0, 0.0};
192
0
    struct pj_stere *Q = static_cast<struct pj_stere *>(P->opaque);
193
0
    double c, sinc, cosc;
194
195
0
    const double rh = hypot(xy.x, xy.y);
196
0
    c = 2. * atan(rh / Q->akm1);
197
0
    sinc = sin(c);
198
0
    cosc = cos(c);
199
0
    lp.lam = 0.;
200
201
0
    switch (Q->mode) {
202
0
    case EQUIT:
203
0
        if (fabs(rh) <= EPS10)
204
0
            lp.phi = 0.;
205
0
        else
206
0
            lp.phi = asin(xy.y * sinc / rh);
207
0
        if (cosc != 0. || xy.x != 0.)
208
0
            lp.lam = atan2(xy.x * sinc, cosc * rh);
209
0
        break;
210
0
    case OBLIQ:
211
0
        if (fabs(rh) <= EPS10)
212
0
            lp.phi = P->phi0;
213
0
        else
214
0
            lp.phi = asin(cosc * sinph0 + xy.y * sinc * cosph0 / rh);
215
0
        c = cosc - sinph0 * sin(lp.phi);
216
0
        if (c != 0. || xy.x != 0.)
217
0
            lp.lam = atan2(xy.x * sinc * cosph0, c * rh);
218
0
        break;
219
0
    case N_POLE:
220
0
        xy.y = -xy.y;
221
0
        PROJ_FALLTHROUGH;
222
0
    case S_POLE:
223
0
        if (fabs(rh) <= EPS10)
224
0
            lp.phi = P->phi0;
225
0
        else
226
0
            lp.phi = asin(Q->mode == S_POLE ? -cosc : cosc);
227
0
        lp.lam = (xy.x == 0. && xy.y == 0.) ? 0. : atan2(xy.x, xy.y);
228
0
        break;
229
0
    }
230
0
    return lp;
231
0
}
232
233
1.12k
static PJ *stere_setup(PJ *P) { /* general initialization */
234
1.12k
    double t;
235
1.12k
    struct pj_stere *Q = static_cast<struct pj_stere *>(P->opaque);
236
237
1.12k
    if (fabs((t = fabs(P->phi0)) - M_HALFPI) < EPS10)
238
447
        Q->mode = P->phi0 < 0. ? S_POLE : N_POLE;
239
673
    else
240
673
        Q->mode = t > EPS10 ? OBLIQ : EQUIT;
241
1.12k
    Q->phits = fabs(Q->phits);
242
243
1.12k
    if (P->es != 0.0) {
244
1.06k
        double X;
245
246
1.06k
        switch (Q->mode) {
247
380
        case N_POLE:
248
433
        case S_POLE:
249
433
            if (fabs(Q->phits - M_HALFPI) < EPS10)
250
417
                Q->akm1 =
251
417
                    2. * P->k0 /
252
417
                    sqrt(pow(1 + P->e, 1 + P->e) * pow(1 - P->e, 1 - P->e));
253
16
            else {
254
16
                t = sin(Q->phits);
255
16
                Q->akm1 = cos(Q->phits) / pj_tsfn(Q->phits, t, P->e);
256
16
                t *= P->e;
257
16
                Q->akm1 /= sqrt(1. - t * t);
258
16
            }
259
433
            break;
260
518
        case EQUIT:
261
632
        case OBLIQ:
262
632
            t = sin(P->phi0);
263
632
            X = 2. * atan(ssfn_(P->phi0, t, P->e)) - M_HALFPI;
264
632
            t *= P->e;
265
632
            Q->akm1 = 2. * P->k0 * cos(P->phi0) / sqrt(1. - t * t);
266
632
            Q->sinX1 = sin(X);
267
632
            Q->cosX1 = cos(X);
268
632
            break;
269
1.06k
        }
270
1.06k
        P->inv = stere_e_inverse;
271
1.06k
        P->fwd = stere_e_forward;
272
1.06k
    } else {
273
55
        switch (Q->mode) {
274
4
        case OBLIQ:
275
4
            sinph0 = sin(P->phi0);
276
4
            cosph0 = cos(P->phi0);
277
4
            PROJ_FALLTHROUGH;
278
41
        case EQUIT:
279
41
            Q->akm1 = 2. * P->k0;
280
41
            break;
281
0
        case S_POLE:
282
14
        case N_POLE:
283
14
            Q->akm1 = fabs(Q->phits - M_HALFPI) >= EPS10
284
14
                          ? cos(Q->phits) / tan(M_FORTPI - .5 * Q->phits)
285
14
                          : 2. * P->k0;
286
14
            break;
287
55
        }
288
289
55
        P->inv = stere_s_inverse;
290
55
        P->fwd = stere_s_forward;
291
55
    }
292
1.12k
    return P;
293
1.12k
}
294
295
825
PJ *PJ_PROJECTION(stere) {
296
825
    struct pj_stere *Q =
297
825
        static_cast<struct pj_stere *>(calloc(1, sizeof(struct pj_stere)));
298
825
    if (nullptr == Q)
299
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
300
825
    P->opaque = Q;
301
302
825
    Q->phits = pj_param(P->ctx, P->params, "tlat_ts").i
303
825
                   ? pj_param(P->ctx, P->params, "rlat_ts").f
304
825
                   : M_HALFPI;
305
306
825
    return stere_setup(P);
307
825
}
308
309
298
PJ *PJ_PROJECTION(ups) {
310
298
    struct pj_stere *Q =
311
298
        static_cast<struct pj_stere *>(calloc(1, sizeof(struct pj_stere)));
312
298
    if (nullptr == Q)
313
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
314
298
    P->opaque = Q;
315
316
    /* International Ellipsoid */
317
298
    P->phi0 = pj_param(P->ctx, P->params, "bsouth").i ? -M_HALFPI : M_HALFPI;
318
298
    if (P->es == 0.0) {
319
3
        proj_log_error(
320
3
            P,
321
3
            _("Invalid value for es: only ellipsoidal formulation supported"));
322
3
        return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
323
3
    }
324
295
    P->k0 = .994;
325
295
    P->x0 = 2000000.;
326
295
    P->y0 = 2000000.;
327
295
    Q->phits = M_HALFPI;
328
295
    P->lam0 = 0.;
329
330
295
    return stere_setup(P);
331
298
}
332
333
#undef sinph0
334
#undef cosph0
335
#undef EPS10
336
#undef TOL
337
#undef NITER
338
#undef CONV