/src/proj/src/projections/ob_tran.cpp
Line | Count | Source (jump to first uncovered line) |
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 | 0 | #define TOL 1e-10 |
26 | | |
27 | 0 | static PJ_XY o_forward(PJ_LP lp, PJ *P) { /* spheroid */ |
28 | 0 | struct pj_ob_tran_data *Q = |
29 | 0 | static_cast<struct pj_ob_tran_data *>(P->opaque); |
30 | 0 | double coslam, sinphi, cosphi; |
31 | |
|
32 | 0 | coslam = cos(lp.lam); |
33 | 0 | sinphi = sin(lp.phi); |
34 | 0 | cosphi = cos(lp.phi); |
35 | | /* Formula (5-8b) of Snyder's "Map projections: a working manual" */ |
36 | 0 | lp.lam = adjlon(aatan2(cosphi * sin(lp.lam), |
37 | 0 | Q->sphip * cosphi * coslam + Q->cphip * sinphi) + |
38 | 0 | Q->lamp); |
39 | | /* Formula (5-7) */ |
40 | 0 | lp.phi = aasin(P->ctx, Q->sphip * sinphi - Q->cphip * cosphi * coslam); |
41 | |
|
42 | 0 | return Q->link->fwd(lp, Q->link); |
43 | 0 | } |
44 | | |
45 | 0 | static PJ_XY t_forward(PJ_LP lp, PJ *P) { /* spheroid */ |
46 | 0 | struct pj_ob_tran_data *Q = |
47 | 0 | static_cast<struct pj_ob_tran_data *>(P->opaque); |
48 | 0 | double cosphi, coslam; |
49 | |
|
50 | 0 | cosphi = cos(lp.phi); |
51 | 0 | coslam = cos(lp.lam); |
52 | 0 | lp.lam = adjlon(aatan2(cosphi * sin(lp.lam), sin(lp.phi)) + Q->lamp); |
53 | 0 | lp.phi = aasin(P->ctx, -cosphi * coslam); |
54 | |
|
55 | 0 | return Q->link->fwd(lp, Q->link); |
56 | 0 | } |
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 | 0 | static PJ_LP t_inverse(PJ_XY xy, PJ *P) { /* spheroid */ |
80 | |
|
81 | 0 | struct pj_ob_tran_data *Q = |
82 | 0 | static_cast<struct pj_ob_tran_data *>(P->opaque); |
83 | 0 | double cosphi, t; |
84 | |
|
85 | 0 | PJ_LP lp = Q->link->inv(xy, Q->link); |
86 | 0 | if (lp.lam != HUGE_VAL) { |
87 | 0 | cosphi = cos(lp.phi); |
88 | 0 | t = lp.lam - Q->lamp; |
89 | 0 | lp.lam = aatan2(cosphi * sin(t), -sin(lp.phi)); |
90 | 0 | lp.phi = aasin(P->ctx, cosphi * cos(t)); |
91 | 0 | } |
92 | 0 | return lp; |
93 | 0 | } |
94 | | |
95 | 0 | static PJ *destructor(PJ *P, int errlev) { |
96 | 0 | if (nullptr == P) |
97 | 0 | return nullptr; |
98 | 0 | if (nullptr == P->opaque) |
99 | 0 | return pj_default_destructor(P, errlev); |
100 | | |
101 | 0 | if (static_cast<struct pj_ob_tran_data *>(P->opaque)->link) |
102 | 0 | static_cast<struct pj_ob_tran_data *>(P->opaque)->link->destructor( |
103 | 0 | static_cast<struct pj_ob_tran_data *>(P->opaque)->link, errlev); |
104 | |
|
105 | 0 | return pj_default_destructor(P, errlev); |
106 | 0 | } |
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 | 0 | static size_t paralist_params_argc(paralist *params) { |
131 | 0 | size_t argc = 0; |
132 | 0 | for (; params != nullptr; params = params->next) |
133 | 0 | argc++; |
134 | 0 | return argc; |
135 | 0 | } |
136 | | |
137 | | /* turn paralist into argc/argv style argument list */ |
138 | 0 | static ARGS ob_tran_target_params(paralist *params) { |
139 | 0 | int i = 0; |
140 | 0 | ARGS args = {0, nullptr}; |
141 | 0 | size_t argc = paralist_params_argc(params); |
142 | 0 | if (argc < 2) |
143 | 0 | return args; |
144 | | |
145 | | /* all args except the proj=ob_tran */ |
146 | 0 | args.argv = static_cast<char **>(calloc(argc - 1, sizeof(char *))); |
147 | 0 | 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 | 0 | for (i = 0; params != nullptr; params = params->next) { |
152 | 0 | if (0 == strcmp(params->param, "proj=ob_tran") || |
153 | 0 | 0 == strcmp(params->param, "inv")) |
154 | 0 | continue; |
155 | 0 | args.argv[i++] = params->param; |
156 | 0 | } |
157 | 0 | args.argc = i; |
158 | | |
159 | | /* Then convert the o_proj=xxx element to proj=xxx */ |
160 | 0 | for (i = 0; i < args.argc; i++) { |
161 | 0 | if (0 != strncmp(args.argv[i], "o_proj=", 7)) |
162 | 0 | continue; |
163 | 0 | args.argv[i] += 2; |
164 | 0 | 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 | 0 | break; |
170 | 0 | } |
171 | |
|
172 | 0 | return args; |
173 | 0 | } |
174 | | |
175 | 0 | PJ *PJ_PROJECTION(ob_tran) { |
176 | 0 | double phip; |
177 | 0 | ARGS args; |
178 | 0 | PJ *R; /* projection to rotate */ |
179 | |
|
180 | 0 | struct pj_ob_tran_data *Q = static_cast<struct pj_ob_tran_data *>( |
181 | 0 | calloc(1, sizeof(struct pj_ob_tran_data))); |
182 | 0 | if (nullptr == Q) |
183 | 0 | return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); |
184 | | |
185 | 0 | P->opaque = Q; |
186 | 0 | P->destructor = destructor; |
187 | | |
188 | | /* get name of projection to be translated */ |
189 | 0 | if (pj_param(P->ctx, P->params, "so_proj").s == nullptr) { |
190 | 0 | proj_log_error(P, _("Missing parameter: o_proj")); |
191 | 0 | return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
192 | 0 | } |
193 | | |
194 | | /* Create the target projection object to rotate */ |
195 | 0 | args = ob_tran_target_params(P->params); |
196 | | /* avoid endless recursion */ |
197 | 0 | 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 | 0 | R = pj_create_argv_internal(P->ctx, args.argc, args.argv); |
202 | 0 | free(args.argv); |
203 | |
|
204 | 0 | if (nullptr == R) { |
205 | 0 | proj_log_error(P, _("Projection to be rotated is unknown")); |
206 | 0 | return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
207 | 0 | } |
208 | | |
209 | | // Transfer the used flag from the R object to the P object |
210 | 0 | for (auto p = P->params; p; p = p->next) { |
211 | 0 | if (!p->used) { |
212 | 0 | for (auto r = R->params; r; r = r->next) { |
213 | 0 | if (r->used && strcmp(r->param, p->param) == 0) { |
214 | 0 | p->used = 1; |
215 | 0 | break; |
216 | 0 | } |
217 | 0 | } |
218 | 0 | } |
219 | 0 | } |
220 | |
|
221 | 0 | Q->link = R; |
222 | |
|
223 | 0 | if (pj_param(P->ctx, P->params, "to_alpha").i) { |
224 | 0 | double lamc, phic, alpha; |
225 | |
|
226 | 0 | lamc = pj_param(P->ctx, P->params, "ro_lon_c").f; |
227 | 0 | phic = pj_param(P->ctx, P->params, "ro_lat_c").f; |
228 | 0 | alpha = pj_param(P->ctx, P->params, "ro_alpha").f; |
229 | |
|
230 | 0 | 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 | 0 | Q->lamp = lamc + aatan2(-cos(alpha), -sin(alpha) * sin(phic)); |
237 | 0 | phip = aasin(P->ctx, cos(phic) * sin(alpha)); |
238 | 0 | } else if (pj_param(P->ctx, P->params, "to_lat_p") |
239 | 0 | .i) { /* specified new pole */ |
240 | 0 | Q->lamp = pj_param(P->ctx, P->params, "ro_lon_p").f; |
241 | 0 | phip = pj_param(P->ctx, P->params, "ro_lat_p").f; |
242 | 0 | } else { /* specified new "equator" points */ |
243 | 0 | double lam1, lam2, phi1, phi2, con; |
244 | |
|
245 | 0 | lam1 = pj_param(P->ctx, P->params, "ro_lon_1").f; |
246 | 0 | phi1 = pj_param(P->ctx, P->params, "ro_lat_1").f; |
247 | 0 | lam2 = pj_param(P->ctx, P->params, "ro_lon_2").f; |
248 | 0 | phi2 = pj_param(P->ctx, P->params, "ro_lat_2").f; |
249 | 0 | con = fabs(phi1); |
250 | |
|
251 | 0 | 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 | 0 | if (fabs(phi2) > M_HALFPI - TOL) { |
257 | 0 | proj_log_error( |
258 | 0 | P, _("Invalid value for lat_2: |lat_2| should be < 90°")); |
259 | 0 | return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
260 | 0 | } |
261 | 0 | if (fabs(phi1 - phi2) < TOL) { |
262 | 0 | proj_log_error( |
263 | 0 | P, _("Invalid value for lat_1 and lat_2: lat_1 should be " |
264 | 0 | "different from lat_2")); |
265 | 0 | return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
266 | 0 | } |
267 | 0 | if (con < TOL) { |
268 | 0 | proj_log_error(P, _("Invalid value for lat_1: lat_1 should be " |
269 | 0 | "different from zero")); |
270 | 0 | return destructor(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
271 | 0 | } |
272 | | |
273 | 0 | Q->lamp = atan2(cos(phi1) * sin(phi2) * cos(lam1) - |
274 | 0 | sin(phi1) * cos(phi2) * cos(lam2), |
275 | 0 | sin(phi1) * cos(phi2) * sin(lam2) - |
276 | 0 | cos(phi1) * sin(phi2) * sin(lam1)); |
277 | 0 | phip = atan(-cos(Q->lamp - lam1) / tan(phi1)); |
278 | 0 | } |
279 | | |
280 | 0 | if (fabs(phip) > TOL) { /* oblique */ |
281 | 0 | Q->cphip = cos(phip); |
282 | 0 | Q->sphip = sin(phip); |
283 | 0 | P->fwd = Q->link->fwd ? o_forward : nullptr; |
284 | 0 | P->inv = Q->link->inv ? o_inverse : nullptr; |
285 | 0 | } else { /* transverse */ |
286 | 0 | P->fwd = Q->link->fwd ? t_forward : nullptr; |
287 | 0 | P->inv = Q->link->inv ? t_inverse : nullptr; |
288 | 0 | } |
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 | 0 | if (Q->link->right == PJ_IO_UNITS_RADIANS) |
294 | 0 | P->right = PJ_IO_UNITS_WHATEVER; |
295 | |
|
296 | 0 | return P; |
297 | 0 | } |
298 | | |
299 | | #undef TOL |