Coverage Report

Created: 2026-04-01 06:20

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
9
static PJ *pj_igh_o_destructor(PJ *P, int errlev) {
221
9
    using namespace pj_igh_o_ns;
222
223
9
    int i;
224
9
    if (nullptr == P)
225
0
        return nullptr;
226
227
9
    if (nullptr == P->opaque)
228
0
        return pj_default_destructor(P, errlev);
229
230
9
    struct pj_igh_o_data *Q = static_cast<struct pj_igh_o_data *>(P->opaque);
231
232
117
    for (i = 0; i < 12; ++i) {
233
108
        if (Q->pj[i])
234
108
            Q->pj[i]->destructor(Q->pj[i], errlev);
235
108
    }
236
237
9
    return pj_default_destructor(P, errlev);
238
9
}
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
108
                                double y_0, double lon_0) {
263
108
    if (!(Q->pj[n - 1] = proj_ptr(nullptr)))
264
0
        return false;
265
108
    if (!(Q->pj[n - 1] = proj_ptr(Q->pj[n - 1])))
266
0
        return false;
267
108
    Q->pj[n - 1]->ctx = P->ctx;
268
108
    Q->pj[n - 1]->x0 = x_0;
269
108
    Q->pj[n - 1]->y0 = y_0;
270
108
    Q->pj[n - 1]->lam0 = lon_0;
271
108
    return true;
272
108
}
273
274
9
PJ *PJ_PROJECTION(igh_o) {
275
9
    using namespace pj_igh_o_ns;
276
277
9
    PJ_XY xy1, xy4;
278
9
    PJ_LP lp = {0, igh_o_phi_boundary};
279
9
    struct pj_igh_o_data *Q = static_cast<struct pj_igh_o_data *>(
280
9
        calloc(1, sizeof(struct pj_igh_o_data)));
281
9
    if (nullptr == Q)
282
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
283
9
    P->opaque = Q;
284
285
    /* sinusoidal zones */
286
9
    if (!pj_igh_o_setup_zone(P, Q, 4, pj_sinu, -d140, 0, -d140) ||
287
9
        !pj_igh_o_setup_zone(P, Q, 5, pj_sinu, -d10, 0, -d10) ||
288
9
        !pj_igh_o_setup_zone(P, Q, 6, pj_sinu, d130, 0, d130) ||
289
9
        !pj_igh_o_setup_zone(P, Q, 7, pj_sinu, -d110, 0, -d110) ||
290
9
        !pj_igh_o_setup_zone(P, Q, 8, pj_sinu, d20, 0, d20) ||
291
9
        !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
9
    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
9
    xy1 = Q->pj[0]->fwd(lp, Q->pj[0]); /* zone 1 */
302
9
    xy4 = Q->pj[3]->fwd(lp, Q->pj[3]); /* zone 4 */
303
    /* y0 + xy1.y = xy4.y for lt = 40d44'11.8" */
304
9
    Q->dy0 = xy4.y - xy1.y;
305
306
9
    Q->pj[0]->y0 = Q->dy0;
307
308
    /* mollweide zones (cont'd) */
309
9
    if (!pj_igh_o_setup_zone(P, Q, 2, pj_moll, -d10, Q->dy0, -d10) ||
310
9
        !pj_igh_o_setup_zone(P, Q, 3, pj_moll, d130, Q->dy0, d130) ||
311
9
        !pj_igh_o_setup_zone(P, Q, 10, pj_moll, -d110, -Q->dy0, -d110) ||
312
9
        !pj_igh_o_setup_zone(P, Q, 11, pj_moll, d20, -Q->dy0, d20) ||
313
9
        !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
9
    P->inv = igh_o_s_inverse;
318
9
    P->fwd = igh_o_s_forward;
319
9
    P->destructor = pj_igh_o_destructor;
320
9
    P->es = 0.;
321
322
9
    return P;
323
9
}