Coverage Report

Created: 2025-10-30 06:17

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