Coverage Report

Created: 2026-06-09 07:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/projections/ob_tran.cpp
Line
Count
Source
1
2
#include <errno.h>
3
#include <math.h>
4
#include <set>
5
#include <stddef.h>
6
#include <string.h>
7
8
#include "proj.h"
9
#include "proj_internal.h"
10
11
namespace { // anonymous namespace
12
struct pj_ob_tran_data {
13
    struct PJconsts *link;
14
    double lamp;
15
    double cphip, sphip;
16
};
17
} // anonymous namespace
18
19
PROJ_HEAD(ob_tran, "General Oblique Transformation")
20
"\n\tMisc Sph"
21
    "\n\to_proj= plus parameters for projection"
22
    "\n\to_lat_p= o_lon_p= (new pole) or"
23
    "\n\to_alpha= o_lon_c= o_lat_c= or"
24
    "\n\to_lon_1= o_lat_1= o_lon_2= o_lat_2=";
25
26
10.5k
#define TOL 1e-10
27
28
9.32k
static PJ_XY o_forward(PJ_LP lp, PJ *P) { /* spheroid */
29
9.32k
    struct pj_ob_tran_data *Q =
30
9.32k
        static_cast<struct pj_ob_tran_data *>(P->opaque);
31
9.32k
    double coslam, sinphi, cosphi;
32
33
9.32k
    coslam = cos(lp.lam);
34
9.32k
    sinphi = sin(lp.phi);
35
9.32k
    cosphi = cos(lp.phi);
36
    /* Formula (5-8b) of Snyder's "Map projections: a working manual" */
37
9.32k
    lp.lam = adjlon(aatan2(cosphi * sin(lp.lam),
38
9.32k
                           Q->sphip * cosphi * coslam + Q->cphip * sinphi) +
39
9.32k
                    Q->lamp);
40
    /* Formula (5-7) */
41
9.32k
    lp.phi = aasin(P->ctx, Q->sphip * sinphi - Q->cphip * cosphi * coslam);
42
43
9.32k
    return Q->link->fwd(lp, Q->link);
44
9.32k
}
45
46
340k
static PJ_XY t_forward(PJ_LP lp, PJ *P) { /* spheroid */
47
340k
    struct pj_ob_tran_data *Q =
48
340k
        static_cast<struct pj_ob_tran_data *>(P->opaque);
49
340k
    double cosphi, coslam;
50
51
340k
    cosphi = cos(lp.phi);
52
340k
    coslam = cos(lp.lam);
53
340k
    lp.lam = adjlon(aatan2(cosphi * sin(lp.lam), sin(lp.phi)) + Q->lamp);
54
340k
    lp.phi = aasin(P->ctx, -cosphi * coslam);
55
56
340k
    return Q->link->fwd(lp, Q->link);
57
340k
}
58
59
0
static PJ_LP o_inverse(PJ_XY xy, PJ *P) { /* spheroid */
60
61
0
    struct pj_ob_tran_data *Q =
62
0
        static_cast<struct pj_ob_tran_data *>(P->opaque);
63
0
    double coslam, sinphi, cosphi;
64
65
0
    PJ_LP lp = Q->link->inv(xy, Q->link);
66
0
    if (lp.lam != HUGE_VAL) {
67
0
        lp.lam -= Q->lamp;
68
0
        coslam = cos(lp.lam);
69
0
        sinphi = sin(lp.phi);
70
0
        cosphi = cos(lp.phi);
71
        /* Formula (5-9) */
72
0
        lp.phi = aasin(P->ctx, Q->sphip * sinphi + Q->cphip * cosphi * coslam);
73
        /* Formula (5-10b) */
74
0
        lp.lam = aatan2(cosphi * sin(lp.lam),
75
0
                        Q->sphip * cosphi * coslam - Q->cphip * sinphi);
76
0
    }
77
0
    return lp;
78
0
}
79
80
101k
static PJ_LP t_inverse(PJ_XY xy, PJ *P) { /* spheroid */
81
82
101k
    struct pj_ob_tran_data *Q =
83
101k
        static_cast<struct pj_ob_tran_data *>(P->opaque);
84
101k
    double cosphi, t;
85
86
101k
    PJ_LP lp = Q->link->inv(xy, Q->link);
87
101k
    if (lp.lam != HUGE_VAL) {
88
101k
        cosphi = cos(lp.phi);
89
101k
        t = lp.lam - Q->lamp;
90
101k
        lp.lam = aatan2(cosphi * sin(t), -sin(lp.phi));
91
101k
        lp.phi = aasin(P->ctx, cosphi * cos(t));
92
101k
    }
93
101k
    return lp;
94
101k
}
95
96
10.4k
static PJ *destructor(PJ *P, int errlev) {
97
10.4k
    if (nullptr == P)
98
0
        return nullptr;
99
10.4k
    if (nullptr == P->opaque)
100
0
        return pj_default_destructor(P, errlev);
101
102
10.4k
    if (static_cast<struct pj_ob_tran_data *>(P->opaque)->link)
103
10.0k
        static_cast<struct pj_ob_tran_data *>(P->opaque)->link->destructor(
104
10.0k
            static_cast<struct pj_ob_tran_data *>(P->opaque)->link, errlev);
105
106
10.4k
    return pj_default_destructor(P, errlev);
107
10.4k
}
108
109
/***********************************************************************
110
111
These functions are modified versions of the functions "argc_params"
112
and "argv_params" from PJ_pipeline.c
113
114
Basically, they do the somewhat backwards stunt of turning the paralist
115
representation of the +args back into the original +argv, +argc
116
representation accepted by pj_init_ctx().
117
118
This, however, also begs the question of whether we really need the
119
paralist linked list representation, or if we could do with a simpler
120
null-terminated argv style array? This would simplify some code, and
121
keep memory allocations more localized.
122
123
***********************************************************************/
124
125
typedef struct {
126
    int argc;
127
    char **argv;
128
} ARGS;
129
130
/* count the number of args in the linked list <params> */
131
10.3k
static size_t paralist_params_argc(paralist *params) {
132
10.3k
    size_t argc = 0;
133
269k
    for (; params != nullptr; params = params->next)
134
258k
        argc++;
135
10.3k
    return argc;
136
10.3k
}
137
138
/* turn paralist into argc/argv style argument list */
139
10.3k
static ARGS ob_tran_target_params(paralist *params) {
140
10.3k
    int i = 0;
141
10.3k
    ARGS args = {0, nullptr};
142
10.3k
    size_t argc = paralist_params_argc(params);
143
10.3k
    if (argc < 2)
144
0
        return args;
145
146
    /* all args except the proj=ob_tran */
147
10.3k
    args.argv = static_cast<char **>(calloc(argc - 1, sizeof(char *)));
148
10.3k
    if (nullptr == args.argv)
149
0
        return args;
150
151
    /* Copy all args *except* the proj=ob_tran or inv arg to the argv array */
152
269k
    for (i = 0; params != nullptr; params = params->next) {
153
258k
        if (0 == strcmp(params->param, "proj=ob_tran") ||
154
248k
            0 == strcmp(params->param, "inv"))
155
14.4k
            continue;
156
244k
        args.argv[i++] = params->param;
157
244k
    }
158
10.3k
    args.argc = i;
159
160
    /* Then convert the o_proj=xxx element to proj=xxx */
161
11.7k
    for (i = 0; i < args.argc; i++) {
162
11.7k
        if (0 != strncmp(args.argv[i], "o_proj=", 7))
163
1.43k
            continue;
164
10.2k
        args.argv[i] += 2;
165
10.2k
        if (strcmp(args.argv[i], "proj=ob_tran") == 0) {
166
3
            free(args.argv);
167
3
            args.argc = 0;
168
3
            args.argv = nullptr;
169
3
        }
170
10.2k
        break;
171
11.7k
    }
172
173
10.3k
    return args;
174
10.3k
}
175
176
10.4k
PJ *PJ_PROJECTION(ob_tran) {
177
10.4k
    double phip;
178
10.4k
    ARGS args;
179
10.4k
    PJ *R; /* projection to rotate */
180
181
10.4k
    struct pj_ob_tran_data *Q = static_cast<struct pj_ob_tran_data *>(
182
10.4k
        calloc(1, sizeof(struct pj_ob_tran_data)));
183
10.4k
    if (nullptr == Q)
184
0
        return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
185
186
10.4k
    P->opaque = Q;
187
10.4k
    P->destructor = destructor;
188
189
    /* get name of projection to be translated */
190
10.4k
    if (pj_param(P->ctx, P->params, "so_proj").s == nullptr) {
191
120
        proj_log_error(P, _("Missing parameter: o_proj"));
192
120
        return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
193
120
    }
194
195
    /* Create the target projection object to rotate */
196
10.3k
    args = ob_tran_target_params(P->params);
197
    /* avoid endless recursion */
198
10.3k
    if (args.argv == nullptr) {
199
3
        proj_log_error(P, _("Failed to find projection to be rotated"));
200
3
        return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG);
201
3
    }
202
10.3k
    R = pj_create_argv_internal(P->ctx, args.argc, args.argv);
203
10.3k
    free(args.argv);
204
205
10.3k
    if (nullptr == R) {
206
255
        proj_log_error(P, _("Projection to be rotated is unknown"));
207
255
        return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
208
255
    }
209
210
    // Colect used parameters from the R object
211
10.0k
    std::set<std::string> setUsedRParams;
212
252k
    for (auto r = R->params; r; r = r->next) {
213
241k
        if (r->used) {
214
33.0k
            setUsedRParams.insert(r->param);
215
33.0k
        }
216
241k
    }
217
218
    // Transfer the used flag from the R object to the P object
219
265k
    for (auto p = P->params; p; p = p->next) {
220
254k
        if (!p->used && setUsedRParams.find(p->param) != setUsedRParams.end()) {
221
20.7k
            p->used = 1;
222
20.7k
        }
223
254k
    }
224
225
10.0k
    Q->link = R;
226
227
10.0k
    if (pj_param(P->ctx, P->params, "to_alpha").i) {
228
11
        double lamc, phic, alpha;
229
230
11
        lamc = pj_param(P->ctx, P->params, "ro_lon_c").f;
231
11
        phic = pj_param(P->ctx, P->params, "ro_lat_c").f;
232
11
        alpha = pj_param(P->ctx, P->params, "ro_alpha").f;
233
234
11
        if (fabs(fabs(phic) - M_HALFPI) <= TOL) {
235
0
            proj_log_error(
236
0
                P, _("Invalid value for lat_c: |lat_c| should be < 90°"));
237
0
            return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
238
0
        }
239
240
11
        Q->lamp = lamc + aatan2(-cos(alpha), -sin(alpha) * sin(phic));
241
11
        phip = aasin(P->ctx, cos(phic) * sin(alpha));
242
10.0k
    } else if (pj_param(P->ctx, P->params, "to_lat_p")
243
10.0k
                   .i) { /* specified new pole */
244
9.86k
        Q->lamp = pj_param(P->ctx, P->params, "ro_lon_p").f;
245
9.86k
        phip = pj_param(P->ctx, P->params, "ro_lat_p").f;
246
9.86k
    } else { /* specified new "equator" points */
247
203
        double lam1, lam2, phi1, phi2, con;
248
249
203
        lam1 = pj_param(P->ctx, P->params, "ro_lon_1").f;
250
203
        phi1 = pj_param(P->ctx, P->params, "ro_lat_1").f;
251
203
        lam2 = pj_param(P->ctx, P->params, "ro_lon_2").f;
252
203
        phi2 = pj_param(P->ctx, P->params, "ro_lat_2").f;
253
203
        con = fabs(phi1);
254
255
203
        if (fabs(phi1) > M_HALFPI - TOL) {
256
3
            proj_log_error(
257
3
                P, _("Invalid value for lat_1: |lat_1| should be < 90°"));
258
3
            return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
259
3
        }
260
200
        if (fabs(phi2) > M_HALFPI - TOL) {
261
11
            proj_log_error(
262
11
                P, _("Invalid value for lat_2: |lat_2| should be < 90°"));
263
11
            return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
264
11
        }
265
189
        if (fabs(phi1 - phi2) < TOL) {
266
166
            proj_log_error(
267
166
                P, _("Invalid value for lat_1 and lat_2: lat_1 should be "
268
166
                     "different from lat_2"));
269
166
            return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
270
166
        }
271
23
        if (con < TOL) {
272
5
            proj_log_error(P, _("Invalid value for lat_1: lat_1 should be "
273
5
                                "different from zero"));
274
5
            return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
275
5
        }
276
277
18
        Q->lamp = atan2(cos(phi1) * sin(phi2) * cos(lam1) -
278
18
                            sin(phi1) * cos(phi2) * cos(lam2),
279
18
                        sin(phi1) * cos(phi2) * sin(lam2) -
280
18
                            cos(phi1) * sin(phi2) * sin(lam1));
281
18
        phip = atan(-cos(Q->lamp - lam1) / tan(phi1));
282
18
    }
283
284
9.89k
    if (fabs(phip) > TOL) { /* oblique */
285
125
        Q->cphip = cos(phip);
286
125
        Q->sphip = sin(phip);
287
125
        P->fwd = Q->link->fwd ? o_forward : nullptr;
288
125
        P->inv = Q->link->inv ? o_inverse : nullptr;
289
9.76k
    } else { /* transverse */
290
9.76k
        P->fwd = Q->link->fwd ? t_forward : nullptr;
291
9.76k
        P->inv = Q->link->inv ? t_inverse : nullptr;
292
9.76k
    }
293
294
    /* Support some rather speculative test cases, where the rotated projection
295
     */
296
    /* is actually latlong. We do not want scaling in that case... */
297
9.89k
    if (Q->link->right == PJ_IO_UNITS_RADIANS)
298
9.72k
        P->right = PJ_IO_UNITS_WHATEVER;
299
300
9.89k
    return P;
301
10.0k
}
302
303
#undef TOL