Coverage Report

Created: 2025-11-09 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/projections/igh.cpp
Line
Count
Source
1
2
3
#include <errno.h>
4
#include <math.h>
5
6
#include "proj.h"
7
#include "proj_internal.h"
8
9
PROJ_HEAD(igh, "Interrupted Goode Homolosine") "\n\tPCyl, Sph";
10
11
/*
12
This projection is a compilation of 12 separate sub-projections.
13
Sinusoidal projections are found near the equator and Mollweide
14
projections are found at higher latitudes. The transition between
15
the two occurs at 40 degrees latitude and is represented by the
16
constant `igh_phi_boundary`.
17
18
Each sub-projection is assigned an integer label
19
numbered 1 through 12. Most of this code contains logic to assign
20
the labels based on latitude (phi) and longitude (lam) regions.
21
22
Original Reference:
23
J. Paul Goode (1925) THE HOMOLOSINE PROJECTION: A NEW DEVICE FOR
24
    PORTRAYING THE EARTH'S SURFACE ENTIRE, Annals of the Association of
25
    American Geographers, 15:3, 119-125, DOI: 10.1080/00045602509356949
26
*/
27
28
C_NAMESPACE PJ *pj_sinu(PJ *), *pj_moll(PJ *);
29
30
/*
31
Transition from sinusoidal to Mollweide projection
32
Latitude (phi): 40deg 44' 11.8"
33
*/
34
35
constexpr double igh_phi_boundary = (40 + 44 / 60. + 11.8 / 3600.) * DEG_TO_RAD;
36
37
namespace pj_igh_ns {
38
struct pj_igh_data {
39
    struct PJconsts *pj[12];
40
    double dy0;
41
};
42
43
constexpr double d10 = 10 * DEG_TO_RAD;
44
constexpr double d20 = 20 * DEG_TO_RAD;
45
constexpr double d30 = 30 * DEG_TO_RAD;
46
constexpr double d40 = 40 * DEG_TO_RAD;
47
constexpr double d50 = 50 * DEG_TO_RAD;
48
constexpr double d60 = 60 * DEG_TO_RAD;
49
constexpr double d80 = 80 * DEG_TO_RAD;
50
constexpr double d90 = 90 * DEG_TO_RAD;
51
constexpr double d100 = 100 * DEG_TO_RAD;
52
constexpr double d140 = 140 * DEG_TO_RAD;
53
constexpr double d160 = 160 * DEG_TO_RAD;
54
constexpr double d180 = 180 * DEG_TO_RAD;
55
56
constexpr double EPSLN =
57
    1.e-10; /* allow a little 'slack' on zone edge positions */
58
59
} // namespace pj_igh_ns
60
61
2.52k
static PJ_XY igh_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */
62
2.52k
    using namespace pj_igh_ns;
63
2.52k
    PJ_XY xy;
64
2.52k
    struct pj_igh_data *Q = static_cast<struct pj_igh_data *>(P->opaque);
65
2.52k
    int z;
66
67
2.52k
    if (lp.phi >= igh_phi_boundary) { /* 1|2 */
68
924
        z = (lp.lam <= -d40 ? 1 : 2);
69
1.59k
    } else if (lp.phi >= 0) { /* 3|4 */
70
1.59k
        z = (lp.lam <= -d40 ? 3 : 4);
71
1.59k
    } else if (lp.phi >= -igh_phi_boundary) { /* 5|6|7|8 */
72
0
        if (lp.lam <= -d100)
73
0
            z = 5; /* 5 */
74
0
        else if (lp.lam <= -d20)
75
0
            z = 6; /* 6 */
76
0
        else if (lp.lam <= d80)
77
0
            z = 7; /* 7 */
78
0
        else
79
0
            z = 8; /* 8 */
80
0
    } else {       /* 9|10|11|12 */
81
0
        if (lp.lam <= -d100)
82
0
            z = 9; /* 9 */
83
0
        else if (lp.lam <= -d20)
84
0
            z = 10; /* 10 */
85
0
        else if (lp.lam <= d80)
86
0
            z = 11; /* 11 */
87
0
        else
88
0
            z = 12; /* 12 */
89
0
    }
90
91
2.52k
    lp.lam -= Q->pj[z - 1]->lam0;
92
2.52k
    xy = Q->pj[z - 1]->fwd(lp, Q->pj[z - 1]);
93
2.52k
    xy.x += Q->pj[z - 1]->x0;
94
2.52k
    xy.y += Q->pj[z - 1]->y0;
95
96
2.52k
    return xy;
97
2.52k
}
98
99
0
static PJ_LP igh_s_inverse(PJ_XY xy, PJ *P) { /* Spheroidal, inverse */
100
0
    using namespace pj_igh_ns;
101
0
    PJ_LP lp = {0.0, 0.0};
102
0
    struct pj_igh_data *Q = static_cast<struct pj_igh_data *>(P->opaque);
103
0
    const double y90 =
104
0
        Q->dy0 + sqrt(2.0); /* lt=90 corresponds to y=y0+sqrt(2) */
105
106
0
    int z = 0;
107
0
    if (xy.y > y90 + EPSLN || xy.y < -y90 + EPSLN) /* 0 */
108
0
        z = 0;
109
0
    else if (xy.y >= igh_phi_boundary) /* 1|2 */
110
0
        z = (xy.x <= -d40 ? 1 : 2);
111
0
    else if (xy.y >= 0) /* 3|4 */
112
0
        z = (xy.x <= -d40 ? 3 : 4);
113
0
    else if (xy.y >= -igh_phi_boundary) { /* 5|6|7|8 */
114
0
        if (xy.x <= -d100)
115
0
            z = 5; /* 5 */
116
0
        else if (xy.x <= -d20)
117
0
            z = 6; /* 6 */
118
0
        else if (xy.x <= d80)
119
0
            z = 7; /* 7 */
120
0
        else
121
0
            z = 8; /* 8 */
122
0
    } else {       /* 9|10|11|12 */
123
0
        if (xy.x <= -d100)
124
0
            z = 9; /* 9 */
125
0
        else if (xy.x <= -d20)
126
0
            z = 10; /* 10 */
127
0
        else if (xy.x <= d80)
128
0
            z = 11; /* 11 */
129
0
        else
130
0
            z = 12; /* 12 */
131
0
    }
132
133
0
    if (z) {
134
0
        bool ok = false;
135
136
0
        xy.x -= Q->pj[z - 1]->x0;
137
0
        xy.y -= Q->pj[z - 1]->y0;
138
0
        lp = Q->pj[z - 1]->inv(xy, Q->pj[z - 1]);
139
0
        lp.lam += Q->pj[z - 1]->lam0;
140
141
0
        switch (z) {
142
0
        case 1:
143
0
            ok = (lp.lam >= -d180 - EPSLN && lp.lam <= -d40 + EPSLN) ||
144
0
                 ((lp.lam >= -d40 - EPSLN && lp.lam <= -d10 + EPSLN) &&
145
0
                  (lp.phi >= d60 - EPSLN && lp.phi <= d90 + EPSLN));
146
0
            break;
147
0
        case 2:
148
0
            ok = (lp.lam >= -d40 - EPSLN && lp.lam <= d180 + EPSLN) ||
149
0
                 ((lp.lam >= -d180 - EPSLN && lp.lam <= -d160 + EPSLN) &&
150
0
                  (lp.phi >= d50 - EPSLN && lp.phi <= d90 + EPSLN)) ||
151
0
                 ((lp.lam >= -d50 - EPSLN && lp.lam <= -d40 + EPSLN) &&
152
0
                  (lp.phi >= d60 - EPSLN && lp.phi <= d90 + EPSLN));
153
0
            break;
154
0
        case 3:
155
0
            ok = (lp.lam >= -d180 - EPSLN && lp.lam <= -d40 + EPSLN);
156
0
            break;
157
0
        case 4:
158
0
            ok = (lp.lam >= -d40 - EPSLN && lp.lam <= d180 + EPSLN);
159
0
            break;
160
0
        case 5:
161
0
            ok = (lp.lam >= -d180 - EPSLN && lp.lam <= -d100 + EPSLN);
162
0
            break;
163
0
        case 6:
164
0
            ok = (lp.lam >= -d100 - EPSLN && lp.lam <= -d20 + EPSLN);
165
0
            break;
166
0
        case 7:
167
0
            ok = (lp.lam >= -d20 - EPSLN && lp.lam <= d80 + EPSLN);
168
0
            break;
169
0
        case 8:
170
0
            ok = (lp.lam >= d80 - EPSLN && lp.lam <= d180 + EPSLN);
171
0
            break;
172
0
        case 9:
173
0
            ok = (lp.lam >= -d180 - EPSLN && lp.lam <= -d100 + EPSLN);
174
0
            break;
175
0
        case 10:
176
0
            ok = (lp.lam >= -d100 - EPSLN && lp.lam <= -d20 + EPSLN);
177
0
            break;
178
0
        case 11:
179
0
            ok = (lp.lam >= -d20 - EPSLN && lp.lam <= d80 + EPSLN);
180
0
            break;
181
0
        case 12:
182
0
            ok = (lp.lam >= d80 - EPSLN && lp.lam <= d180 + EPSLN);
183
0
            break;
184
0
        }
185
0
        z = (!ok ? 0 : z); /* projectable? */
186
0
    }
187
188
0
    if (!z)
189
0
        lp.lam = HUGE_VAL;
190
0
    if (!z)
191
0
        lp.phi = HUGE_VAL;
192
193
0
    return lp;
194
0
}
195
196
68
static PJ *pj_igh_data_destructor(PJ *P, int errlev) {
197
68
    using namespace pj_igh_ns;
198
68
    int i;
199
68
    if (nullptr == P)
200
0
        return nullptr;
201
202
68
    if (nullptr == P->opaque)
203
0
        return pj_default_destructor(P, errlev);
204
205
68
    struct pj_igh_data *Q = static_cast<struct pj_igh_data *>(P->opaque);
206
207
884
    for (i = 0; i < 12; ++i) {
208
816
        if (Q->pj[i])
209
816
            Q->pj[i]->destructor(Q->pj[i], errlev);
210
816
    }
211
212
68
    return pj_default_destructor(P, errlev);
213
68
}
214
215
/*
216
  Zones:
217
218
    -180            -40                       180
219
      +--------------+-------------------------+    Zones 1,2,9,10,11 & 12:
220
      |1             |2                        |      Mollweide projection
221
      |              |                         |
222
      +--------------+-------------------------+    Zones 3,4,5,6,7 & 8:
223
      |3             |4                        |      Sinusoidal projection
224
      |              |                         |
225
    0 +-------+------+-+-----------+-----------+
226
      |5      |6       |7          |8          |
227
      |       |        |           |           |
228
      +-------+--------+-----------+-----------+
229
      |9      |10      |11         |12         |
230
      |       |        |           |           |
231
      +-------+--------+-----------+-----------+
232
    -180    -100      -20         80          180
233
*/
234
235
static bool pj_igh_setup_zone(PJ *P, struct pj_igh_ns::pj_igh_data *Q, int n,
236
                              PJ *(*proj_ptr)(PJ *), double x_0, double y_0,
237
816
                              double lon_0) {
238
816
    if (!(Q->pj[n - 1] = proj_ptr(nullptr)))
239
0
        return false;
240
816
    if (!(Q->pj[n - 1] = proj_ptr(Q->pj[n - 1])))
241
0
        return false;
242
816
    Q->pj[n - 1]->ctx = P->ctx;
243
816
    Q->pj[n - 1]->x0 = x_0;
244
816
    Q->pj[n - 1]->y0 = y_0;
245
816
    Q->pj[n - 1]->lam0 = lon_0;
246
816
    return true;
247
816
}
248
249
68
PJ *PJ_PROJECTION(igh) {
250
68
    using namespace pj_igh_ns;
251
252
68
    PJ_XY xy1, xy3;
253
68
    PJ_LP lp = {0, igh_phi_boundary};
254
68
    struct pj_igh_data *Q = static_cast<struct pj_igh_data *>(
255
68
        calloc(1, sizeof(struct pj_igh_data)));
256
68
    if (nullptr == Q)
257
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
258
68
    P->opaque = Q;
259
260
    /* sinusoidal zones */
261
68
    if (!pj_igh_setup_zone(P, Q, 3, pj_sinu, -d100, 0, -d100) ||
262
68
        !pj_igh_setup_zone(P, Q, 4, pj_sinu, d30, 0, d30) ||
263
68
        !pj_igh_setup_zone(P, Q, 5, pj_sinu, -d160, 0, -d160) ||
264
68
        !pj_igh_setup_zone(P, Q, 6, pj_sinu, -d60, 0, -d60) ||
265
68
        !pj_igh_setup_zone(P, Q, 7, pj_sinu, d20, 0, d20) ||
266
68
        !pj_igh_setup_zone(P, Q, 8, pj_sinu, d140, 0, d140)) {
267
0
        return pj_igh_data_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
268
0
    }
269
270
    /* mollweide zones */
271
68
    if (!pj_igh_setup_zone(P, Q, 1, pj_moll, -d100, 0, -d100)) {
272
0
        return pj_igh_data_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
273
0
    }
274
275
    /* y0 ? */
276
68
    xy1 = Q->pj[0]->fwd(lp, Q->pj[0]); /* zone 1 */
277
68
    xy3 = Q->pj[2]->fwd(lp, Q->pj[2]); /* zone 3 */
278
    /* y0 + xy1.y = xy3.y for lt = 40d44'11.8" */
279
68
    Q->dy0 = xy3.y - xy1.y;
280
281
68
    Q->pj[0]->y0 = Q->dy0;
282
283
    /* mollweide zones (cont'd) */
284
68
    if (!pj_igh_setup_zone(P, Q, 2, pj_moll, d30, Q->dy0, d30) ||
285
68
        !pj_igh_setup_zone(P, Q, 9, pj_moll, -d160, -Q->dy0, -d160) ||
286
68
        !pj_igh_setup_zone(P, Q, 10, pj_moll, -d60, -Q->dy0, -d60) ||
287
68
        !pj_igh_setup_zone(P, Q, 11, pj_moll, d20, -Q->dy0, d20) ||
288
68
        !pj_igh_setup_zone(P, Q, 12, pj_moll, d140, -Q->dy0, d140)) {
289
0
        return pj_igh_data_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
290
0
    }
291
292
68
    P->inv = igh_s_inverse;
293
68
    P->fwd = igh_s_forward;
294
68
    P->destructor = pj_igh_data_destructor;
295
68
    P->es = 0.;
296
297
68
    return P;
298
68
}