/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 |