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