Coverage Report

Created: 2026-09-28 07:23

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