gistools/proj/project/
cea.rs1use crate::proj::{
2 CoordinateStep, LATITUDE_STD_PARALLEL, Proj, ProjMethod, ProjectCoordinates,
3 TransformCoordinates, authalic_lat_compute_coeffs, authalic_lat_inverse, authalic_lat_q,
4};
5use alloc::{rc::Rc, vec::Vec};
6use core::{cell::RefCell, f64::consts::FRAC_PI_2};
7use libm::{asin, cos, fabs, sin, sqrt};
8
9#[derive(Debug, Default, Clone, PartialEq)]
11pub struct CeaData {
12 qp: f64,
13 apa: Vec<f64>,
14}
15
16const EPS: f64 = 1e-10;
17
18#[derive(Debug, Clone, PartialEq)]
67pub struct EqualAreaCylindricalProjection {
68 proj: Rc<RefCell<Proj>>,
69 store: RefCell<CeaData>,
70 method: ProjMethod,
71}
72impl ProjectCoordinates for EqualAreaCylindricalProjection {
73 fn code(&self) -> i64 {
74 -1
75 }
76 fn name(&self) -> &'static str {
77 "Equal Area Cylindrical"
78 }
79 fn names() -> &'static [&'static str] {
80 &["Equal Area Cylindrical", "cea"]
81 }
82}
83impl CoordinateStep for EqualAreaCylindricalProjection {
84 fn new(proj: Rc<RefCell<Proj>>) -> Self {
85 let mut store = CeaData::default();
86 let method: ProjMethod;
87 {
88 let proj = &mut proj.borrow_mut();
89 let mut t = 0.0;
90
91 if let Some(lat_ts) = proj.params.get(&LATITUDE_STD_PARALLEL) {
92 t = lat_ts.f64();
93 proj.k0 = cos(t);
94 if proj.k0 < 0.0 {
95 panic!("Invalid value for lat_ts: |lat_ts| should be <= 90°");
96 }
97 }
98 method = if proj.es != 0.0 {
99 t = sin(t);
100 proj.k0 /= sqrt(1. - proj.es * t * t);
101 proj.e = sqrt(proj.es);
102 store.apa = authalic_lat_compute_coeffs(proj.n);
103
104 store.qp = authalic_lat_q(1.0, proj);
105 ProjMethod::Ellipsoidal
106 } else {
107 ProjMethod::Spheroidal
108 };
109 }
110 EqualAreaCylindricalProjection { proj, store: store.into(), method }
111 }
112 fn forward<P: TransformCoordinates>(&self, p: &mut P) {
113 if self.method == ProjMethod::Spheroidal {
114 cea_s_forward(&self.proj.borrow(), p);
115 } else {
116 cea_e_forward(&self.proj.borrow(), p);
117 }
118 }
119 fn inverse<P: TransformCoordinates>(&self, p: &mut P) {
120 if self.method == ProjMethod::Spheroidal {
121 cea_s_inverse(&self.proj.borrow(), p);
122 } else {
123 cea_e_inverse(&self.store.borrow(), &self.proj.borrow(), p);
124 }
125 }
126}
127
128pub fn cea_e_forward<P: TransformCoordinates>(proj: &Proj, p: &mut P) {
130 p.set_x(proj.k0 * p.lam());
131 p.set_y(0.5 * authalic_lat_q(sin(p.phi()), proj) / proj.k0);
132}
133
134pub fn cea_s_forward<P: TransformCoordinates>(proj: &Proj, p: &mut P) {
136 p.set_x(proj.k0 * p.lam());
137 p.set_y(sin(p.phi()) / proj.k0);
138}
139
140pub fn cea_e_inverse<P: TransformCoordinates>(cea: &CeaData, proj: &Proj, p: &mut P) {
142 p.set_phi(authalic_lat_inverse(asin(2. * p.y() * proj.k0 / cea.qp), &cea.apa, proj, cea.qp));
143 p.set_lam(p.x() / proj.k0);
144}
145
146pub fn cea_s_inverse<P: TransformCoordinates>(proj: &Proj, p: &mut P) {
148 let y = p.y() * proj.k0;
149 let x = p.x();
150 let t = fabs(y);
151 if t - EPS <= 1. {
152 if t >= 1. {
153 p.set_phi(if y < 0. { -FRAC_PI_2 } else { FRAC_PI_2 });
154 } else {
155 p.set_phi(asin(y));
156 }
157 p.set_lam(x / proj.k0);
158 } else {
159 panic!("Coordinate outside projection domain");
160 }
161}