Coverage Report

Created: 2025-07-23 06:58

/src/PROJ/src/projections/gnom.cpp
Line
Count
Source (jump to first uncovered line)
1
2
3
#include <errno.h>
4
#include <float.h>
5
#include <math.h>
6
7
#include "geodesic.h"
8
#include "proj.h"
9
#include "proj_internal.h"
10
11
PROJ_HEAD(gnom, "Gnomonic") "\n\tAzi, Sph";
12
13
33
#define EPS10 1.e-10
14
15
namespace pj_gnom_ns {
16
enum Mode { N_POLE = 0, S_POLE = 1, EQUIT = 2, OBLIQ = 3 };
17
}
18
19
namespace { // anonymous namespace
20
struct pj_gnom_data {
21
    double sinph0;
22
    double cosph0;
23
    enum pj_gnom_ns::Mode mode;
24
    struct geod_geodesic g;
25
};
26
} // anonymous namespace
27
28
0
static PJ_XY gnom_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */
29
0
    PJ_XY xy = {0.0, 0.0};
30
0
    struct pj_gnom_data *Q = static_cast<struct pj_gnom_data *>(P->opaque);
31
0
    double coslam, cosphi, sinphi;
32
33
0
    sinphi = sin(lp.phi);
34
0
    cosphi = cos(lp.phi);
35
0
    coslam = cos(lp.lam);
36
37
0
    switch (Q->mode) {
38
0
    case pj_gnom_ns::EQUIT:
39
0
        xy.y = cosphi * coslam;
40
0
        break;
41
0
    case pj_gnom_ns::OBLIQ:
42
0
        xy.y = Q->sinph0 * sinphi + Q->cosph0 * cosphi * coslam;
43
0
        break;
44
0
    case pj_gnom_ns::S_POLE:
45
0
        xy.y = -sinphi;
46
0
        break;
47
0
    case pj_gnom_ns::N_POLE:
48
0
        xy.y = sinphi;
49
0
        break;
50
0
    }
51
52
0
    if (xy.y <= EPS10) {
53
0
        proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
54
0
        return xy;
55
0
    }
56
57
0
    xy.x = (xy.y = 1. / xy.y) * cosphi * sin(lp.lam);
58
0
    switch (Q->mode) {
59
0
    case pj_gnom_ns::EQUIT:
60
0
        xy.y *= sinphi;
61
0
        break;
62
0
    case pj_gnom_ns::OBLIQ:
63
0
        xy.y *= Q->cosph0 * sinphi - Q->sinph0 * cosphi * coslam;
64
0
        break;
65
0
    case pj_gnom_ns::N_POLE:
66
0
        coslam = -coslam;
67
0
        PROJ_FALLTHROUGH;
68
0
    case pj_gnom_ns::S_POLE:
69
0
        xy.y *= cosphi * coslam;
70
0
        break;
71
0
    }
72
0
    return xy;
73
0
}
74
75
0
static PJ_LP gnom_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */
76
0
    PJ_LP lp = {0.0, 0.0};
77
0
    struct pj_gnom_data *Q = static_cast<struct pj_gnom_data *>(P->opaque);
78
0
    double rh, cosz, sinz;
79
80
0
    rh = hypot(xy.x, xy.y);
81
0
    sinz = sin(lp.phi = atan(rh));
82
0
    cosz = sqrt(1. - sinz * sinz);
83
84
0
    if (fabs(rh) <= EPS10) {
85
0
        lp.phi = P->phi0;
86
0
        lp.lam = 0.;
87
0
    } else {
88
0
        switch (Q->mode) {
89
0
        case pj_gnom_ns::OBLIQ:
90
0
            lp.phi = cosz * Q->sinph0 + xy.y * sinz * Q->cosph0 / rh;
91
0
            if (fabs(lp.phi) >= 1.)
92
0
                lp.phi = lp.phi > 0. ? M_HALFPI : -M_HALFPI;
93
0
            else
94
0
                lp.phi = asin(lp.phi);
95
0
            xy.y = (cosz - Q->sinph0 * sin(lp.phi)) * rh;
96
0
            xy.x *= sinz * Q->cosph0;
97
0
            break;
98
0
        case pj_gnom_ns::EQUIT:
99
0
            lp.phi = xy.y * sinz / rh;
100
0
            if (fabs(lp.phi) >= 1.)
101
0
                lp.phi = lp.phi > 0. ? M_HALFPI : -M_HALFPI;
102
0
            else
103
0
                lp.phi = asin(lp.phi);
104
0
            xy.y = cosz * rh;
105
0
            xy.x *= sinz;
106
0
            break;
107
0
        case pj_gnom_ns::S_POLE:
108
0
            lp.phi -= M_HALFPI;
109
0
            break;
110
0
        case pj_gnom_ns::N_POLE:
111
0
            lp.phi = M_HALFPI - lp.phi;
112
0
            xy.y = -xy.y;
113
0
            break;
114
0
        }
115
0
        lp.lam = atan2(xy.x, xy.y);
116
0
    }
117
0
    return lp;
118
0
}
119
120
15.3k
static PJ_XY gnom_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */
121
15.3k
    PJ_XY xy = {0.0, 0.0};
122
15.3k
    struct pj_gnom_data *Q = static_cast<struct pj_gnom_data *>(P->opaque);
123
124
15.3k
    double lat0 = P->phi0 / DEG_TO_RAD, lon0 = 0, lat1 = lp.phi / DEG_TO_RAD,
125
15.3k
           lon1 = lp.lam / DEG_TO_RAD, azi0, m, M;
126
127
15.3k
    geod_geninverse(&Q->g, lat0, lon0, lat1, lon1, nullptr, &azi0, nullptr, &m,
128
15.3k
                    &M, nullptr, nullptr);
129
15.3k
    if (M <= 0) {
130
6.78k
        proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
131
6.78k
        xy.x = xy.y = HUGE_VAL;
132
8.58k
    } else {
133
8.58k
        double rho = m / M;
134
8.58k
        azi0 *= DEG_TO_RAD;
135
8.58k
        xy.x = rho * sin(azi0);
136
8.58k
        xy.y = rho * cos(azi0);
137
8.58k
    }
138
15.3k
    return xy;
139
15.3k
}
140
141
0
static PJ_LP gnom_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */
142
0
    constexpr int numit_ = 10;
143
0
    static const double eps_ = 0.01 * sqrt(DBL_EPSILON);
144
145
0
    PJ_LP lp = {0.0, 0.0};
146
0
    struct pj_gnom_data *Q = static_cast<struct pj_gnom_data *>(P->opaque);
147
148
0
    double lat0 = P->phi0 / DEG_TO_RAD, lon0 = 0,
149
0
           azi0 = atan2(xy.x, xy.y) / DEG_TO_RAD, // Clockwise from north
150
0
        rho = hypot(xy.x, xy.y), s = atan(rho);
151
0
    bool little = rho <= 1;
152
0
    if (!little)
153
0
        rho = 1 / rho;
154
0
    struct geod_geodesicline l;
155
0
    geod_lineinit(&l, &Q->g, lat0, lon0, azi0,
156
0
                  GEOD_LATITUDE | GEOD_LONGITUDE | GEOD_DISTANCE_IN |
157
0
                      GEOD_REDUCEDLENGTH | GEOD_GEODESICSCALE);
158
159
0
    int count = numit_, trip = 0;
160
0
    double lat1 = 0, lon1 = 0;
161
0
    while (count--) {
162
0
        double m, M;
163
0
        geod_genposition(&l, 0, s, &lat1, &lon1, nullptr, &s, &m, &M, nullptr,
164
0
                         nullptr);
165
0
        if (trip)
166
0
            break;
167
        // If little, solve rho(s) = rho with drho(s)/ds = 1/M^2
168
        // else solve 1/rho(s) = 1/rho with d(1/rho(s))/ds = -1/m^2
169
0
        double ds = little ? (m - rho * M) * M : (rho * m - M) * m;
170
0
        s -= ds;
171
        // Reversed test to allow escape with NaNs
172
0
        if (!(fabs(ds) >= eps_))
173
0
            ++trip;
174
0
    }
175
0
    if (trip) {
176
0
        lp.phi = lat1 * DEG_TO_RAD;
177
0
        lp.lam = lon1 * DEG_TO_RAD;
178
0
    } else {
179
0
        proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
180
0
        lp.phi = lp.lam = HUGE_VAL;
181
0
    }
182
0
    return lp;
183
0
}
184
185
265
PJ *PJ_PROJECTION(gnom) {
186
265
    struct pj_gnom_data *Q = static_cast<struct pj_gnom_data *>(
187
265
        calloc(1, sizeof(struct pj_gnom_data)));
188
265
    if (nullptr == Q)
189
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
190
265
    P->opaque = Q;
191
192
265
    if (P->es == 0) {
193
18
        if (fabs(fabs(P->phi0) - M_HALFPI) < EPS10) {
194
3
            Q->mode = P->phi0 < 0. ? pj_gnom_ns::S_POLE : pj_gnom_ns::N_POLE;
195
15
        } else if (fabs(P->phi0) < EPS10) {
196
9
            Q->mode = pj_gnom_ns::EQUIT;
197
9
        } else {
198
6
            Q->mode = pj_gnom_ns::OBLIQ;
199
6
            Q->sinph0 = sin(P->phi0);
200
6
            Q->cosph0 = cos(P->phi0);
201
6
        }
202
203
18
        P->inv = gnom_s_inverse;
204
18
        P->fwd = gnom_s_forward;
205
247
    } else {
206
247
        geod_init(&Q->g, 1, P->f);
207
208
247
        P->inv = gnom_e_inverse;
209
247
        P->fwd = gnom_e_forward;
210
247
    }
211
265
    P->es = 0.;
212
213
265
    return P;
214
265
}