1use 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
18super::projection! { aea, leac }
20
21const PHI_NITER: usize = 15;
22
23#[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 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.), params.try_angular_value("lat_2")?.unwrap_or(0.), )
208 }
209
210 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.), 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}