Coverage Report

Created: 2026-04-09 06:50

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
4.11k
static PJ_XY igh_s_forward(PJ_LP lp, PJ *P) { /* Spheroidal, forward */
62
4.11k
    using namespace pj_igh_ns;
63
4.11k
    PJ_XY xy;
64
4.11k
    struct pj_igh_data *Q = static_cast<struct pj_igh_data *>(P->opaque);
65
4.11k
    int z;
66
67
4.11k
    if (lp.phi >= igh_phi_boundary) { /* 1|2 */
68
1.91k
        z = (lp.lam <= -d40 ? 1 : 2);
69
2.19k
    } else if (lp.phi >= 0) { /* 3|4 */
70
2.19k
        z = (lp.lam <= -d40 ? 3 : 4);
71
2.19k
    } 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
4.11k
    lp.lam -= Q->pj[z - 1]->lam0;
92
4.11k
    xy = Q->pj[z - 1]->fwd(lp, Q->pj[z - 1]);
93
4.11k
    xy.x += Q->pj[z - 1]->x0;
94
4.11k
    xy.y += Q->pj[z - 1]->y0;
95
96
4.11k
    return xy;
97
4.11k
}
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
108
static PJ *pj_igh_data_destructor(PJ *P, int errlev) {
197
108
    using namespace pj_igh_ns;
198
108
    int i;
199
108
    if (nullptr == P)
200
0
        return nullptr;
201
202
108
    if (nullptr == P->opaque)
203
0
        return pj_default_destructor(P, errlev);
204
205
108
    struct pj_igh_data *Q = static_cast<struct pj_igh_data *>(P->opaque);
206
207
1.40k
    for (i = 0; i < 12; ++i) {
208
1.29k
        if (Q->pj[i])
209
1.29k
            Q->pj[i]->destructor(Q->pj[i], errlev);
210
1.29k
    }
211
212
108
    return pj_default_destructor(P, errlev);
213
108
}
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
1.29k
                              double lon_0) {
238
1.29k
    if (!(Q->pj[n - 1] = proj_ptr(nullptr)))
239
0
        return false;
240
1.29k
    if (!(Q->pj[n - 1] = proj_ptr(Q->pj[n - 1])))
241
0
        return false;
242
1.29k
    Q->pj[n - 1]->ctx = P->ctx;
243
1.29k
    Q->pj[n - 1]->x0 = x_0;
244
1.29k
    Q->pj[n - 1]->y0 = y_0;
245
1.29k
    Q->pj[n - 1]->lam0 = lon_0;
246
1.29k
    return true;
247
1.29k
}
248
249
108
PJ *PJ_PROJECTION(igh) {
250
108
    using namespace pj_igh_ns;
251
252
108
    PJ_XY xy1, xy3;
253
108
    PJ_LP lp = {0, igh_phi_boundary};
254
108
    struct pj_igh_data *Q = static_cast<struct pj_igh_data *>(
255
108
        calloc(1, sizeof(struct pj_igh_data)));
256
108
    if (nullptr == Q)
257
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
258
108
    P->opaque = Q;
259
260
    /* sinusoidal zones */
261
108
    if (!pj_igh_setup_zone(P, Q, 3, pj_sinu, -d100, 0, -d100) ||
262
108
        !pj_igh_setup_zone(P, Q, 4, pj_sinu, d30, 0, d30) ||
263
108
        !pj_igh_setup_zone(P, Q, 5, pj_sinu, -d160, 0, -d160) ||
264
108
        !pj_igh_setup_zone(P, Q, 6, pj_sinu, -d60, 0, -d60) ||
265
108
        !pj_igh_setup_zone(P, Q, 7, pj_sinu, d20, 0, d20) ||
266
108
        !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
108
    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
108
    xy1 = Q->pj[0]->fwd(lp, Q->pj[0]); /* zone 1 */
277
108
    xy3 = Q->pj[2]->fwd(lp, Q->pj[2]); /* zone 3 */
278
    /* y0 + xy1.y = xy3.y for lt = 40d44'11.8" */
279
108
    Q->dy0 = xy3.y - xy1.y;
280
281
108
    Q->pj[0]->y0 = Q->dy0;
282
283
    /* mollweide zones (cont'd) */
284
108
    if (!pj_igh_setup_zone(P, Q, 2, pj_moll, d30, Q->dy0, d30) ||
285
108
        !pj_igh_setup_zone(P, Q, 9, pj_moll, -d160, -Q->dy0, -d160) ||
286
108
        !pj_igh_setup_zone(P, Q, 10, pj_moll, -d60, -Q->dy0, -d60) ||
287
108
        !pj_igh_setup_zone(P, Q, 11, pj_moll, d20, -Q->dy0, d20) ||
288
108
        !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
108
    P->inv = igh_s_inverse;
293
108
    P->fwd = igh_s_forward;
294
108
    P->destructor = pj_igh_data_destructor;
295
108
    P->es = 0.;
296
297
108
    return P;
298
108
}