1use crate::proj::{
2 CoordinateStep, EPS10, LATITUDE_STD_PARALLEL, POLAR_STEREOGRAPHIC_VARIANT_A,
3 POLAR_STEREOGRAPHIC_VARIANT_B, POLAR_STEREOGRAPHIC_VARIANT_C, Proj, ProjMethod, ProjMode,
4 ProjectCoordinates, SOUTH, TransformCoordinates, tsfn,
5};
6use alloc::rc::Rc;
7use core::{
8 cell::RefCell,
9 f64::consts::{FRAC_PI_2, FRAC_PI_4},
10};
11use libm::{asin, atan, atan2, cos, fabs, hypot, pow, sin, sqrt, tan};
12
13#[derive(Debug, Default, Clone, PartialEq)]
15pub struct StereData {
16 phits: f64,
17 sin_x1: f64,
18 cos_x1: f64,
19 akm1: f64,
20 mode: ProjMode,
21}
22
23const TOL: f64 = 1e-8;
24const NITER: usize = 8;
25const CONV: f64 = 1e-10;
26
27fn ssfn_(phit: f64, mut sinphi: f64, eccen: f64) -> f64 {
28 sinphi *= eccen;
29 tan(0.5 * (FRAC_PI_2 + phit)) * pow((1. - sinphi) / (1. + sinphi), 0.5 * eccen)
30}
31
32fn stere_setup(proj: &mut Proj, store: &mut StereData) -> ProjMethod {
34 let t = fabs(proj.phi0);
35 if fabs(t - FRAC_PI_2) < EPS10 {
36 store.mode = if proj.phi0 < 0. { ProjMode::SPole } else { ProjMode::NPole };
37 } else {
38 store.mode = if t > EPS10 { ProjMode::Obliq } else { ProjMode::Equit };
39 }
40 store.phits = fabs(store.phits);
41
42 if proj.es != 0.0 {
43 match store.mode {
44 ProjMode::NPole | ProjMode::SPole => {
45 if fabs(store.phits - FRAC_PI_2) < EPS10 {
46 store.akm1 = 2. * proj.k0
47 / sqrt(pow(1. + proj.e, 1. + proj.e) * pow(1. - proj.e, 1. - proj.e));
48 } else {
49 let mut t = sin(store.phits);
50 store.akm1 = cos(store.phits) / tsfn(store.phits, t, proj.e);
51 t *= proj.e;
52 store.akm1 /= sqrt(1. - t * t);
53 }
54 }
55 ProjMode::Equit | ProjMode::Obliq => {
56 let mut t = sin(proj.phi0);
57 let x = 2. * atan(ssfn_(proj.phi0, t, proj.e)) - FRAC_PI_2;
58 t *= proj.e;
59 store.akm1 = 2. * proj.k0 * cos(proj.phi0) / sqrt(1. - t * t);
60 store.sin_x1 = sin(x);
61 store.cos_x1 = cos(x);
62 }
63 }
64 ProjMethod::Ellipsoidal
65 } else {
66 match store.mode {
67 ProjMode::Obliq => {
68 store.sin_x1 = sin(proj.phi0);
69 store.cos_x1 = cos(proj.phi0);
70 store.akm1 = 2. * proj.k0;
71 }
72 ProjMode::Equit => {
73 store.akm1 = 2. * proj.k0;
74 }
75 ProjMode::SPole | ProjMode::NPole => {
76 store.akm1 = if fabs(store.phits - FRAC_PI_2) >= EPS10 {
77 cos(store.phits) / tan(FRAC_PI_4 - 0.5 * store.phits)
78 } else {
79 2. * proj.k0
80 };
81 }
82 }
83
84 ProjMethod::Spheroidal
85 }
86}
87
88#[derive(Debug, Clone, PartialEq)]
129pub struct StereographicProjection {
130 proj: Rc<RefCell<Proj>>,
131 store: RefCell<StereData>,
132 method: ProjMethod,
133}
134impl ProjectCoordinates for StereographicProjection {
135 fn code(&self) -> i64 {
136 -1
137 }
138 fn name(&self) -> &'static str {
139 "Stereographic"
140 }
141 fn names() -> &'static [&'static str] {
142 &[
143 "Stereographic",
144 "Polar_Stereographic",
145 "StereographicSouthPole",
146 "Stereographic_South_Pole",
147 "Stereographic South Pole",
148 "Polar Stereographic (variant B)",
149 "stere",
150 ]
151 }
152}
153impl CoordinateStep for StereographicProjection {
154 fn new(proj: Rc<RefCell<Proj>>) -> Self {
155 let mut store = StereData::default();
156 let method: ProjMethod;
157 {
158 let proj = &mut proj.borrow_mut();
159 store.phits = if let Some(lat_ts) = proj.params.get(&LATITUDE_STD_PARALLEL) {
160 lat_ts.f64()
161 } else {
162 FRAC_PI_2
163 };
164
165 method = stere_setup(proj, &mut store);
166 }
167 StereographicProjection { proj, store: store.into(), method }
168 }
169 fn forward<P: TransformCoordinates>(&self, p: &mut P) {
170 if self.method == ProjMethod::Spheroidal {
171 stere_s_forward(&mut self.store.borrow_mut(), p);
172 } else {
173 stere_e_forward(&mut self.store.borrow_mut(), &self.proj.borrow(), p);
174 }
175 }
176 fn inverse<P: TransformCoordinates>(&self, p: &mut P) {
177 if self.method == ProjMethod::Spheroidal {
178 stere_s_inverse(&mut self.store.borrow_mut(), &self.proj.borrow(), p);
179 } else {
180 stere_e_inverse(&mut self.store.borrow_mut(), &self.proj.borrow(), p);
181 }
182 }
183}
184
185pub type PolarStereographicVariantAProjection =
187 UniversalPolarStereographicProjection<POLAR_STEREOGRAPHIC_VARIANT_A>;
188pub type PolarStereographicVariantBProjection =
190 UniversalPolarStereographicProjection<POLAR_STEREOGRAPHIC_VARIANT_B>;
191pub type PolarStereographicVariantCProjection =
193 UniversalPolarStereographicProjection<POLAR_STEREOGRAPHIC_VARIANT_C>;
194
195#[derive(Debug, Clone, PartialEq)]
197pub struct UniversalPolarStereographicProjection<const C: i64> {
198 proj: Rc<RefCell<Proj>>,
199 store: RefCell<StereData>,
200 method: ProjMethod,
201}
202impl<const C: i64> ProjectCoordinates for UniversalPolarStereographicProjection<C> {
203 fn code(&self) -> i64 {
204 C
205 }
206 fn name(&self) -> &'static str {
207 "Universal Polar Stereographic"
208 }
209 fn names() -> &'static [&'static str] {
210 &[
211 "Polar Stereographic",
212 "Universal Polar Stereographic",
213 "Polar Stereographic (variant A)",
214 "Polar Stereographic (variant B)",
215 "Polar Stereographic (variant C)",
216 ]
217 }
218}
219impl<const C: i64> CoordinateStep for UniversalPolarStereographicProjection<C> {
220 fn new(proj: Rc<RefCell<Proj>>) -> Self {
221 let mut store = StereData::default();
222 let method: ProjMethod;
223 {
224 let proj = &mut proj.borrow_mut();
225 proj.phi0 = if proj.params.contains_key(&SOUTH) { -FRAC_PI_2 } else { FRAC_PI_2 };
226 if proj.es == 0.0 {
227 panic!("Invalid value for es: only ellipsoidal formulation supported");
228 }
229 proj.k0 = 0.994;
230 proj.x0 = 2000000.;
231 proj.y0 = 2000000.;
232 store.phits = FRAC_PI_2;
233 proj.lam0 = 0.;
234
235 method = stere_setup(proj, &mut store);
236 }
237 UniversalPolarStereographicProjection { proj, store: store.into(), method }
238 }
239 fn forward<P: TransformCoordinates>(&self, p: &mut P) {
240 if self.method == ProjMethod::Spheroidal {
241 stere_s_forward(&mut self.store.borrow_mut(), p);
242 } else {
243 stere_e_forward(&mut self.store.borrow_mut(), &self.proj.borrow(), p);
244 }
245 }
246 fn inverse<P: TransformCoordinates>(&self, p: &mut P) {
247 if self.method == ProjMethod::Spheroidal {
248 stere_s_inverse(&mut self.store.borrow_mut(), &self.proj.borrow(), p);
249 } else {
250 stere_e_inverse(&mut self.store.borrow_mut(), &self.proj.borrow(), p);
251 }
252 }
253}
254
255pub fn stere_e_forward<P: TransformCoordinates>(stere: &mut StereData, proj: &Proj, p: &mut P) {
257 let mut x;
258 let y;
259 let mut coslam = cos(p.lam());
260 let sinlam = sin(p.lam());
261 let mut sinphi = sin(p.phi());
262 let mut sin_x = 0.;
263 let mut cos_x = 0.;
264 if stere.mode == ProjMode::Obliq || stere.mode == ProjMode::Equit {
265 let x = 2. * atan(ssfn_(p.phi(), sinphi, proj.e)) - FRAC_PI_2;
266 sin_x = sin(x);
267 cos_x = cos(x);
268 }
269
270 match stere.mode {
271 ProjMode::Obliq => {
272 let denom = stere.cos_x1 * (1. + stere.sin_x1 * sin_x + stere.cos_x1 * cos_x * coslam);
273 if denom == 0. {
274 panic!("Coordinate outside projection domain");
275 }
276 let a = stere.akm1 / denom;
277 y = a * (stere.cos_x1 * sin_x - stere.sin_x1 * cos_x * coslam);
278 x = a * cos_x;
279 }
280 ProjMode::Equit => {
281 let mut a = 0.;
283 if 1. + cos_x * coslam == 0.0 {
284 y = f64::MAX;
285 } else {
286 a = stere.akm1 / (1. + cos_x * coslam);
287 y = a * sin_x;
288 }
289 x = a * cos_x;
290 }
291 ProjMode::SPole => {
292 p.set_phi(-p.phi());
293 coslam = -coslam;
294 sinphi = -sinphi;
295 if fabs(p.phi() - FRAC_PI_2) < 1e-15 {
296 x = 0.;
297 } else {
298 x = stere.akm1 * tsfn(p.phi(), sinphi, proj.e);
299 }
300 y = -x * coslam;
301 }
302 ProjMode::NPole => {
303 if fabs(p.phi() - FRAC_PI_2) < 1e-15 {
304 x = 0.;
305 } else {
306 x = stere.akm1 * tsfn(p.phi(), sinphi, proj.e);
307 }
308 y = -x * coslam;
309 }
310 }
311
312 x *= sinlam;
313 p.set_x(x);
314 p.set_y(y);
315}
316
317pub fn stere_s_forward<P: TransformCoordinates>(stere: &mut StereData, p: &mut P) {
319 let sinphi = sin(p.phi());
320 let cosphi = cos(p.phi());
321 let mut coslam = cos(p.lam());
322 let sinlam = sin(p.lam());
323 let x;
324 let mut y;
325
326 match stere.mode {
327 ProjMode::Equit => {
328 y = 1. + cosphi * coslam;
329 if y <= EPS10 {
330 panic!("Coordinate outside projection domain");
331 }
332 y = stere.akm1 / y;
333 x = y * cosphi * sinlam;
334 y *= if stere.mode == ProjMode::Equit {
335 sinphi
336 } else {
337 stere.cos_x1 * sinphi - stere.sin_x1 * cosphi * coslam
338 };
339 }
340 ProjMode::Obliq => {
341 y = 1. + stere.sin_x1 * sinphi + stere.cos_x1 * cosphi * coslam;
342 if y <= EPS10 {
343 panic!("Coordinate outside projection domain");
344 }
345 y = stere.akm1 / y;
346 x = y * cosphi * sinlam;
347 y *= if stere.mode == ProjMode::Equit {
348 sinphi
349 } else {
350 stere.cos_x1 * sinphi - stere.sin_x1 * cosphi * coslam
351 };
352 }
353 ProjMode::NPole => {
354 coslam = -coslam;
355 p.set_phi(-p.phi());
356 if fabs(p.phi() - FRAC_PI_2) < TOL {
357 panic!("Coordinate outside projection domain");
358 }
359 y = stere.akm1 * tan(FRAC_PI_4 + 0.5 * p.phi());
360 x = sinlam * y;
361 y *= coslam;
362 }
363 ProjMode::SPole => {
364 if fabs(p.phi() - FRAC_PI_2) < TOL {
365 panic!("Coordinate outside projection domain");
366 }
367 y = stere.akm1 * tan(FRAC_PI_4 + 0.5 * p.phi());
368 x = sinlam * y;
369 y *= coslam;
370 }
371 }
372
373 p.set_x(x);
374 p.set_y(y);
375}
376
377pub fn stere_e_inverse<P: TransformCoordinates>(stere: &mut StereData, proj: &Proj, p: &mut P) {
379 let mut x = p.x();
380 let mut y = p.y();
381 let rho = hypot(p.x(), p.y());
382 let mut phi_l;
383 let mut tp;
384 let halfpi;
385 let halfe;
386
387 match stere.mode {
388 ProjMode::Obliq | ProjMode::Equit => {
389 tp = 2. * atan2(rho * stere.cos_x1, stere.akm1);
390 let cosphi = cos(tp);
391 let sinphi = sin(tp);
392 if rho == 0.0 {
393 phi_l = asin(cosphi * stere.sin_x1);
394 } else {
395 phi_l = asin(cosphi * stere.sin_x1 + (y * sinphi * stere.cos_x1 / rho));
396 }
397
398 tp = tan(0.5 * (FRAC_PI_2 + phi_l));
399 x *= sinphi;
400 y = rho * stere.cos_x1 * cosphi - y * stere.sin_x1 * sinphi;
401 halfpi = FRAC_PI_2;
402 halfe = 0.5 * proj.e;
403 }
404 ProjMode::NPole => {
405 y = -y;
406 tp = -rho / stere.akm1;
407 phi_l = FRAC_PI_2 - 2. * atan(tp);
408 halfpi = -FRAC_PI_2;
409 halfe = -0.5 * proj.e;
410 }
411 ProjMode::SPole => {
412 tp = -rho / stere.akm1;
413 phi_l = FRAC_PI_2 - 2. * atan(tp);
414 halfpi = -FRAC_PI_2;
415 halfe = -0.5 * proj.e;
416 }
417 }
418
419 let mut i = NITER;
420 while i > 0 {
421 let sinphi = proj.e * sin(phi_l);
422 p.set_phi(2. * atan(tp * pow((1. + sinphi) / (1. - sinphi), halfe)) - halfpi);
423 if fabs(phi_l - p.phi()) < CONV {
424 if stere.mode == ProjMode::SPole {
425 p.set_phi(-p.phi());
426 }
427 p.set_lam(if x == 0. && y == 0. { 0. } else { atan2(x, y) });
428 return;
429 }
430 phi_l = p.phi();
431 i -= 1;
432 }
433
434 panic!("Coordinate outside projection domain");
435}
436
437pub fn stere_s_inverse<P: TransformCoordinates>(stere: &mut StereData, proj: &Proj, p: &mut P) {
439 let rh = hypot(p.x(), p.y());
440 let mut c = 2. * atan(rh / stere.akm1);
441 let sinc = sin(c);
442 let cosc = cos(c);
443 let mut lam = 0.;
444 let phi;
445
446 match stere.mode {
447 ProjMode::Equit => {
448 if fabs(rh) <= EPS10 {
449 phi = 0.;
450 } else {
451 phi = asin(p.y() * sinc / rh);
452 }
453 if cosc != 0. || p.x() != 0. {
454 lam = atan2(p.x() * sinc, cosc * rh);
455 }
456 }
457 ProjMode::Obliq => {
458 if fabs(rh) <= EPS10 {
459 phi = proj.phi0;
460 } else {
461 phi = asin(cosc * stere.sin_x1 + p.y() * sinc * stere.cos_x1 / rh);
462 }
463 c = cosc - stere.sin_x1 * sin(phi);
464 if c != 0. || p.x() != 0. {
465 lam = atan2(p.x() * sinc * stere.cos_x1, c * rh);
466 }
467 }
468 ProjMode::NPole => {
469 p.set_y(-p.y());
470 if fabs(rh) <= EPS10 {
471 phi = proj.phi0;
472 } else {
473 phi = asin(if stere.mode == ProjMode::SPole { -cosc } else { cosc });
474 }
475 lam = if p.x() == 0. && p.y() == 0. { 0. } else { atan2(p.x(), p.y()) };
476 }
477 ProjMode::SPole => {
478 if fabs(rh) <= EPS10 {
479 phi = proj.phi0;
480 } else {
481 phi = asin(if stere.mode == ProjMode::SPole { -cosc } else { cosc });
482 }
483 lam = if p.x() == 0. && p.y() == 0. { 0. } else { atan2(p.x(), p.y()) };
484 }
485 }
486
487 p.set_lam(lam);
488 p.set_phi(phi);
489}