proj4rs/projections/
aea.rs

1//!
2//! Implementation of the aea (Albers Equal Area) projection.
3//! and the leac (Lambert Equal Area Conic) projection
4//!
5
6// From proj4 PJ_aea.c
7//
8// Original author:   Gerald Evenden (1995)
9//
10use crate::errors::{Error, Result};
11use crate::math::{
12    consts::{EPS_10, EPS_7, FRAC_PI_2},
13    msfn, qsfn,
14};
15use crate::parameters::ParamList;
16use crate::proj::ProjData;
17
18// Projection stub
19super::projection! { aea, leac }
20
21const PHI_NITER: usize = 15;
22
23// determine latitude angle phi1
24#[inline]
25fn phi1_inv(qs: f64, e: f64, one_es: f64) -> Result<f64> {
26    let mut phi = (0.5 * qs).asin();
27    if e < EPS_7 {
28        Ok(phi)
29    } else {
30        let mut i = PHI_NITER;
31        let (mut sinphi, mut cosphi, mut con, mut com, mut dphi);
32        while i > 0 {
33            (sinphi, cosphi) = phi.sin_cos();
34            con = e * sinphi;
35            com = 1. - con * con;
36            dphi = 0.5 * com * com / cosphi
37                * (qs / one_es - sinphi / com + 0.5 / e * ((1. - con) / (1. + con)).ln());
38            phi += dphi;
39
40            if dphi.abs() <= EPS_10 {
41                break;
42            }
43
44            i -= 1;
45        }
46        if i == 0 {
47            Err(Error::ToleranceConditionError)
48        } else {
49            Ok(phi)
50        }
51    }
52}
53
54#[derive(Debug, Default, Clone)]
55pub(crate) struct Projection {
56    e: f64,
57    one_es: f64,
58    ec: f64,
59    n: f64,
60    n2: f64,
61    c: f64,
62    dd: f64,
63    rho0: f64,
64}
65
66impl Projection {
67    pub fn init(p: &ProjData, phi1: f64, phi2: f64) -> Result<Self> {
68        if (phi1 + phi2).abs() < EPS_10 {
69            return Err(Error::ProjErrConicLatEqual);
70        }
71
72        let el = &p.ellps;
73        let (sinphi, cosphi) = phi1.sin_cos();
74        let mut n = sinphi;
75        let secant = (phi1 - phi2).abs() >= EPS_10;
76
77        if el.is_ellipsoid() {
78            let m1 = msfn(sinphi, cosphi, el.es);
79            let ml1 = qsfn(sinphi, el.e, el.one_es);
80            if ml1.is_infinite() {
81                return Err(Error::ToleranceConditionError);
82            }
83
84            if secant {
85                let (sinphi2, cosphi2) = phi2.sin_cos();
86
87                let m2 = msfn(sinphi2, cosphi2, el.es);
88                let ml2 = qsfn(sinphi2, el.e, el.one_es);
89                if ml2.is_infinite() || ml1 == ml2 {
90                    return Err(Error::ToleranceConditionError);
91                }
92                n = (m1 * m1 - m2 * m2) / (ml2 - ml1);
93            }
94
95            let ec = 1. - 0.5 * el.one_es * ((1. - el.e) / (1. + el.e)).ln() / el.e;
96            let c = m1 * m1 + n * ml1;
97            let dd = 1. / n;
98            let n2 = n + n;
99            let rho0 = dd * (c - n * qsfn(p.phi0.sin(), el.e, el.one_es)).sqrt();
100
101            Ok(Self {
102                e: el.e,
103                one_es: el.one_es,
104                ec,
105                n,
106                n2,
107                c,
108                dd,
109                rho0,
110            })
111        } else {
112            if secant {
113                n = 0.5 * (n + phi2.sin());
114            }
115            let dd = 1. / n;
116            let n2 = n + n;
117            let c = cosphi * cosphi + n2 * sinphi;
118            let rho0 = dd * (c - n2 * p.phi0.sin()).sqrt();
119            Ok(Self {
120                e: el.e,
121                one_es: el.one_es,
122                ec: 1.,
123                n,
124                n2,
125                c,
126                dd,
127                rho0,
128            })
129        }
130    }
131
132    #[inline]
133    fn is_ellipse(&self) -> bool {
134        self.e != 0.
135    }
136
137    #[inline(always)]
138    pub fn forward(&self, lam: f64, phi: f64, z: f64) -> Result<(f64, f64, f64)> {
139        let rho = self.c
140            - if self.is_ellipse() {
141                self.n * qsfn(phi.sin(), self.e, self.one_es)
142            } else {
143                self.n2 * phi.sin()
144            };
145
146        if rho < 0. {
147            Err(Error::ToleranceConditionError)
148        } else {
149            let rho = self.dd * rho.sqrt();
150            let (sin_i, cos_i) = (lam * self.n).sin_cos();
151            Ok((rho * sin_i, self.rho0 - rho * cos_i, z))
152        }
153    }
154
155    #[inline(always)]
156    pub fn inverse(&self, x: f64, y: f64, z: f64) -> Result<(f64, f64, f64)> {
157        let (mut xx, mut yy) = (x, self.rho0 - y);
158        let mut rho = xx.hypot(yy);
159        if rho != 0. {
160            if self.n < 0. {
161                rho = -rho;
162                xx = -xx;
163                yy = -yy;
164            }
165            let mut phi = rho / self.dd;
166            if self.is_ellipse() {
167                phi = (self.c - phi * phi) / self.n;
168                phi = if (self.ec - phi.abs()).abs() > EPS_7 {
169                    phi1_inv(phi, self.e, self.one_es)?
170                } else if phi < 0. {
171                    -FRAC_PI_2
172                } else {
173                    FRAC_PI_2
174                }
175            } else {
176                phi = (self.c - phi * phi) / self.n2;
177                phi = if phi.abs() <= 1. {
178                    phi.asin()
179                } else if phi < 0. {
180                    -FRAC_PI_2
181                } else {
182                    FRAC_PI_2
183                }
184            }
185            Ok((xx.atan2(yy) / self.n, phi, z))
186        } else {
187            Ok((0., if self.n > 0. { FRAC_PI_2 } else { -FRAC_PI_2 }, z))
188        }
189    }
190
191    pub const fn has_inverse() -> bool {
192        true
193    }
194
195    pub const fn has_forward() -> bool {
196        true
197    }
198
199    // ------------
200    // aea
201    // -----------
202    pub fn aea(p: &mut ProjData, params: &ParamList) -> Result<Self> {
203        Self::init(
204            p,
205            params.try_angular_value("lat_1")?.unwrap_or(0.), // phi1
206            params.try_angular_value("lat_2")?.unwrap_or(0.), // phi2
207        )
208    }
209
210    // ----------
211    // leac
212    // ----------
213    pub fn leac(p: &mut ProjData, params: &ParamList) -> Result<Self> {
214        Self::init(
215            p,
216            params.try_angular_value("lat_1")?.unwrap_or(0.), // phi1
217            if params.check_option("south")? {
218                -FRAC_PI_2
219            } else {
220                FRAC_PI_2
221            },
222        )
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use crate::math::consts::EPS_10;
229    use crate::proj::Proj;
230    use crate::tests::utils::{test_proj_forward, test_proj_inverse};
231
232    #[test]
233    fn proj_aea_aea_ellipsoidal() {
234        let p = Proj::from_proj_string("+proj=aea +ellps=GRS80 +lat_1=0 +lat_2=2").unwrap();
235
236        println!("{:#?}", p.projection());
237
238        let inputs = [
239            ((2., 1., 0.), (222571.60875710563, 110653.32674302977, 0.)),
240            ((2., -1., 0.), (222706.30650839131, -110484.26714439997, 0.)),
241            ((-2., 1., 0.), (-222571.60875710563, 110653.32674302977, 0.)),
242            (
243                (-2., -1., 0.),
244                (-222706.30650839131, -110484.26714439997, 0.),
245            ),
246        ];
247
248        test_proj_forward(&p, &inputs, EPS_10);
249        test_proj_inverse(&p, &inputs, EPS_10);
250    }
251
252    #[test]
253    fn proj_aea_aea_spherical() {
254        let p = Proj::from_proj_string("+proj=aea +R=6400000 +lat_1=0 +lat_2=2").unwrap();
255
256        println!("{:#?}", p.projection());
257
258        let inputs = [
259            ((2., 1., 0.), (223334.08517088494, 111780.43188447191, 0.)),
260            ((2., -1., 0.), (223470.15499168713, -111610.33943099028, 0.)),
261            ((-2., 1., 0.), (-223334.08517088494, 111780.43188447191, 0.)),
262            (
263                (-2., -1., 0.),
264                (-223470.15499168713, -111610.33943099028, 0.),
265            ),
266        ];
267
268        test_proj_forward(&p, &inputs, EPS_10);
269        test_proj_inverse(&p, &inputs, EPS_10);
270    }
271
272    #[test]
273    fn proj_aea_leac_ellipsoidal() {
274        let p = Proj::from_proj_string("+proj=leac +ellps=GRS80").unwrap();
275
276        println!("{:#?}", p.projection());
277
278        let inputs = [
279            ((2., 1., 0.), (220685.14054297868, 112983.50088939646, 0.)),
280            ((2., -1., 0.), (224553.31227982609, -108128.63674487274, 0.)),
281            ((-2., 1., 0.), (-220685.14054297868, 112983.50088939646, 0.)),
282            (
283                (-2., -1., 0.),
284                (-224553.31227982609, -108128.63674487274, 0.),
285            ),
286        ];
287
288        test_proj_forward(&p, &inputs, EPS_10);
289        test_proj_inverse(&p, &inputs, EPS_10);
290    }
291
292    #[test]
293    fn proj_aea_leac_spherical() {
294        let p = Proj::from_proj_string("+proj=leac +R=6400000").unwrap();
295
296        println!("{:#?}", p.data());
297        println!("{:#?}", p.projection());
298
299        let inputs = [
300            ((2., 1., 0.), (221432.86859285168, 114119.45452653214, 0.)),
301            ((2., -1., 0.), (225331.72412711097, -109245.82943505641, 0.)),
302            ((-2., 1., 0.), (-221432.86859285168, 114119.45452653214, 0.)),
303            (
304                (-2., -1., 0.),
305                (-225331.72412711097, -109245.82943505641, 0.),
306            ),
307        ];
308
309        test_proj_forward(&p, &inputs, EPS_10);
310        test_proj_inverse(&p, &inputs, EPS_10);
311    }
312}