gistools/proj/project/
eqearth.rs1use crate::proj::{
2 CoordinateStep, EQUAL_EARTH, Proj, ProjectCoordinates, TransformCoordinates,
3 authalic_lat_compute_coeffs, authalic_lat_inverse, authalic_lat_q,
4};
5use alloc::{rc::Rc, vec::Vec};
6use core::cell::RefCell;
7use libm::{asin, cos, fabs, sin, sqrt};
8
9const A1: f64 = 1.340264;
23const A2: f64 = -0.081106;
24const A3: f64 = 0.000893;
25const A4: f64 = 0.003796;
26const M: f64 = 0.8660254037844386; const MAX_Y: f64 = 1.3173627591574;
30const EPS: f64 = 1e-11;
31const MAX_ITER: usize = 12;
32
33#[derive(Debug, Default, Clone, PartialEq)]
35pub struct EqEarth {
36 qp: f64,
37 rqda: f64,
38 apa: Vec<f64>,
39}
40
41#[derive(Debug, Clone, PartialEq)]
88pub struct EqualEarthProjection {
89 proj: Rc<RefCell<Proj>>,
90 store: RefCell<EqEarth>,
91}
92impl ProjectCoordinates for EqualEarthProjection {
93 fn code(&self) -> i64 {
94 EQUAL_EARTH
95 }
96 fn name(&self) -> &'static str {
97 "Equal Earth"
98 }
99 fn names() -> &'static [&'static str] {
100 &["Equal Earth", "EqualEarth", "eqearth"]
101 }
102}
103impl CoordinateStep for EqualEarthProjection {
104 fn new(proj: Rc<RefCell<Proj>>) -> Self {
105 let mut store = EqEarth { rqda: 1.0, ..Default::default() };
106 {
107 let proj = &mut proj.borrow_mut();
108 if proj.es != 0.0 {
110 store.apa = authalic_lat_compute_coeffs(proj.n); store.qp = authalic_lat_q(1.0, proj); store.rqda = sqrt(0.5 * store.qp); }
114 }
115 EqualEarthProjection { proj, store: store.into() }
116 }
117 fn forward<P: TransformCoordinates>(&self, p: &mut P) {
118 eqearth_e_forward(&mut self.store.borrow_mut(), &self.proj.borrow(), p);
119 }
120 fn inverse<P: TransformCoordinates>(&self, p: &mut P) {
121 eqearth_e_inverse(&mut self.store.borrow_mut(), &self.proj.borrow(), p);
122 }
123}
124
125pub fn eqearth_e_forward<P: TransformCoordinates>(eq_earth: &mut EqEarth, proj: &Proj, p: &mut P) {
127 let mut sbeta = sin(p.phi());
129
130 if proj.es != 0.0 {
132 sbeta = authalic_lat_q(sbeta, proj) / eq_earth.qp;
133
134 if fabs(sbeta) > 1. {
136 sbeta = if sbeta > 0. { 1. } else { -1. };
137 }
138 }
139
140 let psi = asin(M * sbeta);
142 let psi2 = psi * psi;
143 let psi6 = psi2 * psi2 * psi2;
144
145 let mut x =
146 p.lam() * cos(psi) / (M * (A1 + 3. * A2 * psi2 + psi6 * (7. * A3 + 9. * A4 * psi2)));
147 let mut y = psi * (A1 + A2 * psi2 + psi6 * (A3 + A4 * psi2));
148
149 x *= eq_earth.rqda;
151 y *= eq_earth.rqda;
152
153 p.set_x(x);
154 p.set_y(y);
155}
156
157pub fn eqearth_e_inverse<P: TransformCoordinates>(eq_earth: &mut EqEarth, proj: &Proj, p: &mut P) {
159 let x = p.x() / eq_earth.rqda;
161 let mut y = p.y() / eq_earth.rqda;
162
163 y = y.clamp(-MAX_Y, MAX_Y);
165
166 let mut yc = y;
167
168 let mut i = MAX_ITER;
170 while i > 0 {
171 let y2 = yc * yc;
172 let y6 = y2 * y2 * y2;
173
174 let f = yc * (A1 + A2 * y2 + y6 * (A3 + A4 * y2)) - y;
175 let fder = A1 + 3. * A2 * y2 + y6 * (7. * A3 + 9. * A4 * y2);
176
177 let tol = f / fder;
178 yc -= tol;
179
180 if fabs(tol) < EPS {
181 break;
182 }
183 i -= 1;
184 }
185
186 if i == 0 {
187 panic!("Coordinate outside projection domain");
188 }
189
190 let y2 = yc * yc;
192 let y6 = y2 * y2 * y2;
193
194 p.set_lam(M * x * (A1 + 3. * A2 * y2 + y6 * (7. * A3 + 9. * A4 * y2)) / cos(yc));
195
196 p.set_phi(asin(sin(yc) / M));
198
199 if proj.es != 0.0 {
201 p.set_phi(authalic_lat_inverse(p.phi(), &eq_earth.apa, proj, eq_earth.qp));
202 }
203}