Coverage Report

Created: 2025-10-13 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/projections/som.cpp
Line
Count
Source
1
/******************************************************************************
2
 * This implements the Space Oblique Mercator (SOM) projection, used by the
3
 * Multi-angle Imaging SpectroRadiometer (MISR) products, from the NASA EOS
4
 *Terra platform among others (e.g. ASTER).
5
 *
6
 * This code was originally developed for the Landsat SOM projection with the
7
 * following parameters set for Landsat satellites 1, 2, and 3:
8
 *
9
 *   inclination angle = 99.092 degrees
10
 *   period of revolution = 103.2669323 minutes
11
 *   ascending longitude = 128.87 degrees - (360 / 251) * path_number
12
 *
13
 * or for Landsat satellites greater than 3:
14
 *
15
 *   inclination angle = 98.2 degrees
16
 *   period of revolution = 98.8841202 minutes
17
 *   ascending longitude = 129.3 degrees - (360 / 233) * path_number
18
 *
19
 * For the MISR path based SOM projection, the code is identical to that of
20
 *Landsat SOM with the following parameter changes:
21
 *
22
 *   inclination angle = 98.30382 degrees
23
 *   period of revolution = 98.88 minutes
24
 *   ascending longitude = 129.3056 degrees - (360 / 233) * path_number
25
 *
26
 * and the following code used for Landsat:
27
 *
28
 *   Q->rlm = PI * (1. / 248. + .5161290322580645);
29
 *
30
 * changed to:
31
 *
32
 *   Q->rlm = 0
33
 *
34
 * For the generic SOM projection, the code is identical to the above for MISR
35
 * except that the following parameters are now taken as input rather than
36
 *derived from path number:
37
 *
38
 *   inclination angle
39
 *   period of revolution
40
 *   ascending longitude
41
 *
42
 * The change of Q->rlm = 0 is kept.
43
 *
44
 *****************************************************************************/
45
/* based upon Snyder and Linck, USGS-NMD */
46
47
#include <errno.h>
48
#include <math.h>
49
50
#include "proj.h"
51
#include "proj_internal.h"
52
53
PROJ_HEAD(som, "Space Oblique Mercator")
54
"\n\tCyl, Sph&Ell\n\tinc_angle= ps_rev= asc_lon= ";
55
PROJ_HEAD(misrsom, "Space oblique for MISR")
56
"\n\tCyl, Sph&Ell\n\tpath=";
57
PROJ_HEAD(lsat, "Space oblique for LANDSAT")
58
"\n\tCyl, Sph&Ell\n\tlsat= path=";
59
60
93.9k
#define TOL 1e-7
61
62
namespace { // anonymous namespace
63
struct pj_som_data {
64
    double a2, a4, b, c1, c3;
65
    double q, t, u, w, p22, sa, ca, xj, rlm, rlm2;
66
    double alf;
67
};
68
} // anonymous namespace
69
70
2.34k
static void seraz0(double lam, double mult, PJ *P) {
71
2.34k
    struct pj_som_data *Q = static_cast<struct pj_som_data *>(P->opaque);
72
2.34k
    double sdsq, h, s, fc, sd, sq, d_1 = 0;
73
74
2.34k
    lam *= DEG_TO_RAD;
75
2.34k
    sd = sin(lam);
76
2.34k
    sdsq = sd * sd;
77
2.34k
    s = Q->p22 * Q->sa * cos(lam) *
78
2.34k
        sqrt((1. + Q->t * sdsq) / ((1. + Q->w * sdsq) * (1. + Q->q * sdsq)));
79
2.34k
    d_1 = 1. + Q->q * sdsq;
80
2.34k
    h = sqrt((1. + Q->q * sdsq) / (1. + Q->w * sdsq)) *
81
2.34k
        ((1. + Q->w * sdsq) / (d_1 * d_1) - Q->p22 * Q->ca);
82
2.34k
    sq = sqrt(Q->xj * Q->xj + s * s);
83
2.34k
    fc = mult * (h * Q->xj - s * s) / sq;
84
2.34k
    Q->b += fc;
85
2.34k
    Q->a2 += fc * cos(lam + lam);
86
2.34k
    Q->a4 += fc * cos(lam * 4.);
87
2.34k
    fc = mult * s * (h + Q->xj) / sq;
88
2.34k
    Q->c1 += fc * cos(lam);
89
2.34k
    Q->c3 += fc * cos(lam * 3.);
90
2.34k
}
91
92
15.2k
static PJ_XY som_e_forward(PJ_LP lp, PJ *P) { /* Ellipsoidal, forward */
93
15.2k
    PJ_XY xy = {0.0, 0.0};
94
15.2k
    struct pj_som_data *Q = static_cast<struct pj_som_data *>(P->opaque);
95
15.2k
    int l, nn;
96
15.2k
    double lamt = 0.0, xlam, sdsq, c, d, s, lamdp = 0.0, phidp, lampp, tanph;
97
15.2k
    double lamtp, cl, sd, sp, sav, tanphi;
98
99
15.2k
    if (lp.phi > M_HALFPI)
100
0
        lp.phi = M_HALFPI;
101
15.2k
    else if (lp.phi < -M_HALFPI)
102
0
        lp.phi = -M_HALFPI;
103
15.2k
    if (lp.phi >= 0.)
104
15.2k
        lampp = M_HALFPI;
105
0
    else
106
0
        lampp = M_PI_HALFPI;
107
15.2k
    tanphi = tan(lp.phi);
108
23.4k
    for (nn = 0;;) {
109
23.4k
        double fac;
110
23.4k
        sav = lampp;
111
23.4k
        lamtp = lp.lam + Q->p22 * lampp;
112
23.4k
        cl = cos(lamtp);
113
23.4k
        if (cl < 0)
114
6.86k
            fac = lampp + sin(lampp) * M_HALFPI;
115
16.6k
        else
116
16.6k
            fac = lampp - sin(lampp) * M_HALFPI;
117
46.9k
        for (l = 50; l >= 0; --l) {
118
46.9k
            lamt = lp.lam + Q->p22 * sav;
119
46.9k
            c = cos(lamt);
120
46.9k
            if (fabs(c) < TOL)
121
0
                lamt -= TOL;
122
46.9k
            xlam = (P->one_es * tanphi * Q->sa + sin(lamt) * Q->ca) / c;
123
46.9k
            lamdp = atan(xlam) + fac;
124
46.9k
            if (fabs(fabs(sav) - fabs(lamdp)) < TOL)
125
23.4k
                break;
126
23.4k
            sav = lamdp;
127
23.4k
        }
128
23.4k
        if (!l || ++nn >= 3 || (lamdp > Q->rlm && lamdp < Q->rlm2))
129
15.2k
            break;
130
8.29k
        if (lamdp <= Q->rlm)
131
8.29k
            lampp = M_TWOPI_HALFPI;
132
0
        else if (lamdp >= Q->rlm2)
133
0
            lampp = M_HALFPI;
134
8.29k
    }
135
15.2k
    if (l) {
136
15.2k
        sp = sin(lp.phi);
137
15.2k
        phidp = aasin(
138
15.2k
            P->ctx, (P->one_es * Q->ca * sp - Q->sa * cos(lp.phi) * sin(lamt)) /
139
15.2k
                        sqrt(1. - P->es * sp * sp));
140
15.2k
        tanph = log(tan(M_FORTPI + .5 * phidp));
141
15.2k
        sd = sin(lamdp);
142
15.2k
        sdsq = sd * sd;
143
15.2k
        s = Q->p22 * Q->sa * cos(lamdp) *
144
15.2k
            sqrt((1. + Q->t * sdsq) /
145
15.2k
                 ((1. + Q->w * sdsq) * (1. + Q->q * sdsq)));
146
15.2k
        d = sqrt(Q->xj * Q->xj + s * s);
147
15.2k
        xy.x = Q->b * lamdp + Q->a2 * sin(2. * lamdp) +
148
15.2k
               Q->a4 * sin(lamdp * 4.) - tanph * s / d;
149
15.2k
        xy.y = Q->c1 * sd + Q->c3 * sin(lamdp * 3.) + tanph * Q->xj / d;
150
15.2k
    } else
151
0
        xy.x = xy.y = HUGE_VAL;
152
15.2k
    return xy;
153
15.2k
}
154
155
0
static PJ_LP som_e_inverse(PJ_XY xy, PJ *P) { /* Ellipsoidal, inverse */
156
0
    PJ_LP lp = {0.0, 0.0};
157
0
    struct pj_som_data *Q = static_cast<struct pj_som_data *>(P->opaque);
158
0
    int nn;
159
0
    double lamt, sdsq, s, lamdp, phidp, sppsq, dd, sd, sl, fac, scl, sav, spp;
160
161
0
    lamdp = xy.x / Q->b;
162
0
    nn = 50;
163
0
    do {
164
0
        sav = lamdp;
165
0
        sd = sin(lamdp);
166
0
        sdsq = sd * sd;
167
0
        s = Q->p22 * Q->sa * cos(lamdp) *
168
0
            sqrt((1. + Q->t * sdsq) /
169
0
                 ((1. + Q->w * sdsq) * (1. + Q->q * sdsq)));
170
0
        lamdp = xy.x + xy.y * s / Q->xj - Q->a2 * sin(2. * lamdp) -
171
0
                Q->a4 * sin(lamdp * 4.) -
172
0
                s / Q->xj * (Q->c1 * sin(lamdp) + Q->c3 * sin(lamdp * 3.));
173
0
        lamdp /= Q->b;
174
0
    } while (fabs(lamdp - sav) >= TOL && --nn);
175
0
    sl = sin(lamdp);
176
0
    fac = exp(sqrt(1. + s * s / Q->xj / Q->xj) *
177
0
              (xy.y - Q->c1 * sl - Q->c3 * sin(lamdp * 3.)));
178
0
    phidp = 2. * (atan(fac) - M_FORTPI);
179
0
    dd = sl * sl;
180
0
    if (fabs(cos(lamdp)) < TOL)
181
0
        lamdp -= TOL;
182
0
    spp = sin(phidp);
183
0
    sppsq = spp * spp;
184
0
    const double denom = 1. - sppsq * (1. + Q->u);
185
0
    if (denom == 0.0) {
186
0
        proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
187
0
        return proj_coord_error().lp;
188
0
    }
189
0
    lamt = atan(
190
0
        ((1. - sppsq * P->rone_es) * tan(lamdp) * Q->ca -
191
0
         spp * Q->sa * sqrt((1. + Q->q * dd) * (1. - sppsq) - sppsq * Q->u) /
192
0
             cos(lamdp)) /
193
0
        denom);
194
0
    sl = lamt >= 0. ? 1. : -1.;
195
0
    scl = cos(lamdp) >= 0. ? 1. : -1;
196
0
    lamt -= M_HALFPI * (1. - scl) * sl;
197
0
    lp.lam = lamt - Q->p22 * lamdp;
198
0
    if (fabs(Q->sa) < TOL)
199
0
        lp.phi =
200
0
            aasin(P->ctx, spp / sqrt(P->one_es * P->one_es + P->es * sppsq));
201
0
    else
202
0
        lp.phi = atan((tan(lamdp) * cos(lamt) - Q->ca * sin(lamt)) /
203
0
                      (P->one_es * Q->sa));
204
0
    return lp;
205
0
}
206
207
213
static PJ *som_setup(PJ *P) {
208
213
    double esc, ess, lam;
209
213
    struct pj_som_data *Q = static_cast<struct pj_som_data *>(P->opaque);
210
213
    Q->sa = sin(Q->alf);
211
213
    Q->ca = cos(Q->alf);
212
213
    if (fabs(Q->ca) < 1e-9)
213
0
        Q->ca = 1e-9;
214
213
    esc = P->es * Q->ca * Q->ca;
215
213
    ess = P->es * Q->sa * Q->sa;
216
213
    Q->w = (1. - esc) * P->rone_es;
217
213
    Q->w = Q->w * Q->w - 1.;
218
213
    Q->q = ess * P->rone_es;
219
213
    Q->t = ess * (2. - P->es) * P->rone_es * P->rone_es;
220
213
    Q->u = esc * P->rone_es;
221
213
    Q->xj = P->one_es * P->one_es * P->one_es;
222
213
    Q->rlm2 = Q->rlm + M_TWOPI;
223
213
    Q->a2 = Q->a4 = Q->b = Q->c1 = Q->c3 = 0.;
224
213
    seraz0(0., 1., P);
225
1.27k
    for (lam = 9.; lam <= 81.0001; lam += 18.)
226
1.06k
        seraz0(lam, 4., P);
227
1.06k
    for (lam = 18; lam <= 72.0001; lam += 18.)
228
852
        seraz0(lam, 2., P);
229
213
    seraz0(90., 1., P);
230
213
    Q->a2 /= 30.;
231
213
    Q->a4 /= 60.;
232
213
    Q->b /= 30.;
233
213
    Q->c1 /= 15.;
234
213
    Q->c3 /= 45.;
235
236
213
    P->inv = som_e_inverse;
237
213
    P->fwd = som_e_forward;
238
239
213
    return P;
240
213
}
241
242
208
PJ *PJ_PROJECTION(som) {
243
208
    struct pj_som_data *Q = static_cast<struct pj_som_data *>(
244
208
        calloc(1, sizeof(struct pj_som_data)));
245
208
    if (nullptr == Q)
246
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
247
208
    P->opaque = Q;
248
249
    // ascending longitude (radians)
250
208
    P->lam0 = pj_param(P->ctx, P->params, "rasc_lon").f;
251
208
    if (P->lam0 < -M_TWOPI || P->lam0 > M_TWOPI) {
252
0
        proj_log_error(P,
253
0
                       _("Invalid value for ascending longitude: should be in "
254
0
                         "[-2pi, 2pi] range"));
255
0
        return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
256
0
    }
257
258
    // inclination angle (radians)
259
208
    Q->alf = pj_param(P->ctx, P->params, "rinc_angle").f;
260
208
    if (Q->alf < 0 || Q->alf > M_PI) {
261
0
        proj_log_error(P, _("Invalid value for inclination angle: should be in "
262
0
                            "[0, pi] range"));
263
0
        return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
264
0
    }
265
266
    // period of revolution (day / rev)
267
208
    Q->p22 = pj_param(P->ctx, P->params, "dps_rev").f;
268
208
    if (Q->p22 < 0) {
269
0
        proj_log_error(P, _("Number of days per rotation should be positive"));
270
0
        return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
271
0
    }
272
273
208
    Q->rlm = 0;
274
275
208
    return som_setup(P);
276
208
}
277
278
0
PJ *PJ_PROJECTION(misrsom) {
279
0
    int path;
280
281
0
    struct pj_som_data *Q = static_cast<struct pj_som_data *>(
282
0
        calloc(1, sizeof(struct pj_som_data)));
283
0
    if (nullptr == Q)
284
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
285
0
    P->opaque = Q;
286
287
0
    path = pj_param(P->ctx, P->params, "ipath").i;
288
0
    if (path <= 0 || path > 233) {
289
0
        proj_log_error(
290
0
            P, _("Invalid value for path: path should be in [1, 233] range"));
291
0
        return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
292
0
    }
293
294
0
    P->lam0 = DEG_TO_RAD * 129.3056 - M_TWOPI / 233. * path;
295
0
    Q->alf = 98.30382 * DEG_TO_RAD;
296
0
    Q->p22 = 98.88 / 1440.0;
297
298
0
    Q->rlm = 0;
299
300
0
    return som_setup(P);
301
0
}
302
303
18
PJ *PJ_PROJECTION(lsat) {
304
18
    int land, path;
305
18
    struct pj_som_data *Q = static_cast<struct pj_som_data *>(
306
18
        calloc(1, sizeof(struct pj_som_data)));
307
18
    if (nullptr == Q)
308
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
309
18
    P->opaque = Q;
310
311
18
    land = pj_param(P->ctx, P->params, "ilsat").i;
312
18
    if (land <= 0 || land > 5) {
313
6
        proj_log_error(
314
6
            P, _("Invalid value for lsat: lsat should be in [1, 5] range"));
315
6
        return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
316
6
    }
317
318
12
    path = pj_param(P->ctx, P->params, "ipath").i;
319
12
    const int maxPathVal = (land <= 3 ? 251 : 233);
320
12
    if (path <= 0 || path > maxPathVal) {
321
7
        proj_log_error(
322
7
            P, _("Invalid value for path: path should be in [1, %d] range"),
323
7
            maxPathVal);
324
7
        return pj_default_destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
325
7
    }
326
327
5
    if (land <= 3) {
328
2
        P->lam0 = DEG_TO_RAD * 128.87 - M_TWOPI / 251. * path;
329
2
        Q->p22 = 103.2669323;
330
2
        Q->alf = DEG_TO_RAD * 99.092;
331
3
    } else {
332
3
        P->lam0 = DEG_TO_RAD * 129.3 - M_TWOPI / 233. * path;
333
3
        Q->p22 = 98.8841202;
334
3
        Q->alf = DEG_TO_RAD * 98.2;
335
3
    }
336
5
    Q->p22 /= 1440.;
337
338
5
    Q->rlm = M_PI * (1. / 248. + .5161290322580645);
339
340
5
    return som_setup(P);
341
12
}
342
343
#undef TOL