gistools/proj/project/
laea.rs1use crate::proj::{
2 CoordinateStep, EPS10, LAMBERT_AZIMUTHAL_EQUAL_AREA, LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL,
3 Proj, ProjMethod, ProjMode, ProjectCoordinates, TransformCoordinates, authalic_lat,
4 authalic_lat_compute_coeffs, authalic_lat_inverse, authalic_lat_q,
5};
6use alloc::{rc::Rc, vec::Vec};
7use core::{
8 cell::RefCell,
9 f64::consts::{FRAC_PI_2, FRAC_PI_4},
10};
11use libm::{asin, atan2, cos, fabs, hypot, sin, sqrt};
12
13#[derive(Debug, Default, Clone, PartialEq)]
15pub struct LaeaData {
16 sinb1: f64,
17 cosb1: f64,
18 xmf: f64,
19 ymf: f64,
20 mmf: f64,
21 qp: f64,
22 dd: f64,
23 rq: f64,
24 apa: Vec<f64>,
25 mode: ProjMode,
26}
27
28pub type LambertAzimuthalEqualAreaProjection =
32 LambertAzimuthalEqualAreaBase<LAMBERT_AZIMUTHAL_EQUAL_AREA>;
33pub type LambertAzimuthalEqualAreaSphericalProjection =
37 LambertAzimuthalEqualAreaBase<LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL>;
38
39#[derive(Debug, Clone, PartialEq)]
77pub struct LambertAzimuthalEqualAreaBase<const C: i64> {
78 proj: Rc<RefCell<Proj>>,
79 store: RefCell<LaeaData>,
80 method: ProjMethod,
81}
82impl<const C: i64> ProjectCoordinates for LambertAzimuthalEqualAreaBase<C> {
83 fn code(&self) -> i64 {
84 C
85 }
86 fn name(&self) -> &'static str {
87 "Lambert Azimuthal Equal Area"
88 }
89 fn names() -> &'static [&'static str] {
90 &[
91 "Lambert Azimuthal Equal Area",
92 "Lambert_Azimuthal_Equal_Area",
93 "Lambert Azimuthal Equal Area (Spherical)",
94 "laea",
95 ]
96 }
97}
98impl<const C: i64> CoordinateStep for LambertAzimuthalEqualAreaBase<C> {
99 fn new(proj: Rc<RefCell<Proj>>) -> Self {
100 let mut store = LaeaData::default();
101 let method: ProjMethod;
102 {
103 let proj = &mut proj.borrow_mut();
104
105 let t = fabs(proj.phi0);
106 if t > FRAC_PI_2 + EPS10 {
107 panic!("Invalid value for lat_0: |lat_0| should be <= 90°");
108 }
109 if fabs(t - FRAC_PI_2) < EPS10 {
110 store.mode = if proj.phi0 < 0. { ProjMode::SPole } else { ProjMode::NPole };
111 } else if fabs(t) < EPS10 {
112 store.mode = ProjMode::Equit;
113 } else {
114 store.mode = ProjMode::Obliq;
115 }
116 method = if proj.es != 0.0 {
117 proj.e = sqrt(proj.es);
118 store.qp = authalic_lat_q(1.0, proj);
119 store.mmf = 0.5 / (1. - proj.es);
120 store.apa = authalic_lat_compute_coeffs(proj.n);
121 match store.mode {
122 ProjMode::NPole | ProjMode::SPole => {
123 store.dd = 1.;
124 }
125 ProjMode::Equit => {
126 store.rq = sqrt(0.5 * store.qp);
127 store.dd = 1. / store.rq;
128 store.xmf = 1.;
129 store.ymf = 0.5 * store.qp;
130 }
131 ProjMode::Obliq => {
132 store.rq = sqrt(0.5 * store.qp);
133 let sinphi = sin(proj.phi0);
134 let cosphi = cos(proj.phi0);
135 let b1 =
136 authalic_lat(proj.phi0, sinphi, cosphi, &store.apa, proj, store.qp);
137 store.sinb1 = sin(b1);
138 store.cosb1 = cos(b1);
139 store.dd = cos(proj.phi0)
140 / (sqrt(1. - proj.es * sinphi * sinphi) * store.rq * store.cosb1);
141 store.xmf = store.rq;
142 store.ymf = store.xmf / store.dd;
143 store.xmf *= store.dd;
144 }
145 }
146 ProjMethod::Ellipsoidal
147 } else {
148 if store.mode == ProjMode::Obliq {
149 store.sinb1 = sin(proj.phi0);
150 store.cosb1 = cos(proj.phi0);
151 }
152 ProjMethod::Spheroidal
153 }
154 }
155
156 LambertAzimuthalEqualAreaBase { proj, store: store.into(), method }
157 }
158 fn forward<P: TransformCoordinates>(&self, p: &mut P) {
159 if self.method == ProjMethod::Spheroidal {
160 laea_s_forward(&self.store.borrow(), &self.proj.borrow(), p);
161 } else {
162 laea_e_forward(&self.store.borrow(), &self.proj.borrow(), p);
163 }
164 }
165 fn inverse<P: TransformCoordinates>(&self, p: &mut P) {
166 if self.method == ProjMethod::Spheroidal {
167 laea_s_inverse(&self.store.borrow(), &self.proj.borrow(), p);
168 } else {
169 laea_e_inverse(&self.store.borrow(), &self.proj.borrow(), p);
170 }
171 }
172}
173
174pub fn laea_e_forward<P: TransformCoordinates>(laea: &LaeaData, proj: &Proj, p: &mut P) {
176 let coslam = cos(p.lam());
177 let sinlam = sin(p.lam());
178 let sinphi = sin(p.phi());
179 let cosphi = cos(p.phi());
180 let mut sinb = 0.0;
181 let mut cosb = 0.0;
182 let xi = authalic_lat(p.phi(), sinphi, cosphi, &laea.apa, proj, laea.qp);
183 let mut q = sin(xi) * laea.qp;
184 let mut b;
185
186 if laea.mode == ProjMode::Obliq || laea.mode == ProjMode::Equit {
187 sinb = sin(xi);
188 cosb = cos(xi);
189 }
190
191 match laea.mode {
192 ProjMode::Obliq => {
193 b = 1. + laea.sinb1 * sinb + laea.cosb1 * cosb * coslam;
194 }
195 ProjMode::Equit => {
196 b = 1. + cosb * coslam;
197 }
198 ProjMode::NPole => {
199 b = FRAC_PI_2 + p.phi();
200 q = laea.qp - q;
201 }
202 ProjMode::SPole => {
203 b = p.phi() - FRAC_PI_2;
204 q += laea.qp;
205 }
206 }
207 if fabs(b) < EPS10 {
208 panic!("Coordinate outside projection domain");
209 }
210
211 match laea.mode {
212 ProjMode::Obliq => {
213 b = sqrt(2. / b);
214 p.set_y(laea.ymf * b * (laea.cosb1 * sinb - laea.sinb1 * cosb * coslam));
215 p.set_x(laea.xmf * b * cosb * sinlam);
216 }
217 ProjMode::Equit => {
218 b = sqrt(2. / (1. + cosb * coslam));
219 p.set_y(b * sinb * laea.ymf);
220 p.set_x(laea.xmf * b * cosb * sinlam);
221 }
222 ProjMode::NPole | ProjMode::SPole => {
223 if q >= 1e-15 {
224 b = sqrt(q);
225 p.set_x(b * sinlam);
226 p.set_y(coslam * (if laea.mode == ProjMode::SPole { b } else { -b }));
227 } else {
228 p.set_x(0.);
229 p.set_y(0.);
230 }
231 }
232 }
233}
234
235pub fn laea_s_forward<P: TransformCoordinates>(laea: &LaeaData, proj: &Proj, p: &mut P) {
237 let sinphi = sin(p.phi());
238 let cosphi = cos(p.phi());
239 let mut coslam = cos(p.lam());
240 let x;
241 let mut y;
242 match laea.mode {
243 ProjMode::Equit => {
244 y = 1. + cosphi * coslam;
245 if y <= EPS10 {
246 panic!("Coordinate outside projection domain");
247 }
248 y = sqrt(2. / y);
249 x = y * cosphi * sin(p.lam());
250 y *= if laea.mode == ProjMode::Equit {
251 sinphi
252 } else {
253 laea.cosb1 * sinphi - laea.sinb1 * cosphi * coslam
254 };
255 }
256 ProjMode::Obliq => {
257 y = 1. + laea.sinb1 * sinphi + laea.cosb1 * cosphi * coslam;
258 if y <= EPS10 {
259 panic!("Coordinate outside projection domain");
260 }
261 y = sqrt(2. / y);
262 x = y * cosphi * sin(p.lam());
263 y *= if laea.mode == ProjMode::Equit {
264 sinphi
265 } else {
266 laea.cosb1 * sinphi - laea.sinb1 * cosphi * coslam
267 };
268 }
269 ProjMode::NPole => {
270 coslam = -coslam;
271 if fabs(p.phi() + proj.phi0) < EPS10 {
272 panic!("Coordinate outside projection domain");
273 }
274 y = FRAC_PI_4 - p.phi() * 0.5;
275 y = 2. * (if laea.mode == ProjMode::SPole { cos(y) } else { sin(y) });
276 x = y * sin(p.lam());
277 y *= coslam;
278 }
279 ProjMode::SPole => {
280 if fabs(p.phi() + proj.phi0) < EPS10 {
281 panic!("Coordinate outside projection domain");
282 }
283 y = FRAC_PI_4 - p.phi() * 0.5;
284 y = 2. * (if laea.mode == ProjMode::SPole { cos(y) } else { sin(y) });
285 x = y * sin(p.lam());
286 y *= coslam;
287 }
288 }
289 p.set_x(x);
290 p.set_y(y);
291}
292
293pub fn laea_e_inverse<P: TransformCoordinates>(laea: &LaeaData, proj: &Proj, p: &mut P) {
295 let mut x = p.x();
300 let mut y = p.y();
301 let mut ab;
302
303 match laea.mode {
304 ProjMode::Equit | ProjMode::Obliq => {
305 x /= laea.dd;
306 y *= laea.dd;
307 let rho = hypot(x, y);
308 if rho < EPS10 {
309 p.set_lam(0.);
310 p.set_phi(proj.phi0);
311 return;
312 }
313 let asin_argument = 0.5 * rho / laea.rq;
314 if asin_argument > 1. {
315 panic!("Coordinate outside projection domain");
318 }
319 let s_ce = 2. * asin(asin_argument);
320 let c_ce = cos(s_ce);
321 let s_ce = sin(s_ce);
322 x *= s_ce;
323 if laea.mode == ProjMode::Obliq {
324 ab = c_ce * laea.sinb1 + y * s_ce * laea.cosb1 / rho;
325 y = rho * laea.cosb1 * c_ce - y * laea.sinb1 * s_ce;
326 } else {
327 ab = y * s_ce / rho;
328 y = rho * c_ce;
329 }
330 }
331 ProjMode::NPole => {
332 y = -y;
333 let q = x * x + y * y;
334 if q == 0.0 {
335 p.set_lam(0.);
336 p.set_phi(proj.phi0);
337 return;
338 }
339 ab = 1. - q / laea.qp;
340 if laea.mode == ProjMode::SPole {
341 ab = -ab;
342 }
343 }
344 ProjMode::SPole => {
345 let q = x * x + y * y;
346 if q == 0.0 {
347 p.set_lam(0.);
348 p.set_phi(proj.phi0);
349 return;
350 }
351 ab = 1. - q / laea.qp;
352 if laea.mode == ProjMode::SPole {
353 ab = -ab;
354 }
355 }
356 }
357 p.set_lam(atan2(x, y));
358 p.set_phi(authalic_lat_inverse(asin(ab), &laea.apa, proj, laea.qp));
359}
360
361pub fn laea_s_inverse<P: TransformCoordinates>(laea: &LaeaData, proj: &Proj, p: &mut P) {
363 let mut cosz = 0.0;
369 let mut sinz = 0.0;
370 let mut x = p.x();
371 let mut y = p.y();
372 let rh = hypot(x, y);
373 let mut phi = rh * 0.5;
374 if phi > 1. {
375 panic!("Coordinate outside projection domain");
378 }
379 phi = 2. * asin(phi);
380 if laea.mode == ProjMode::Obliq || laea.mode == ProjMode::Equit {
381 sinz = sin(phi);
382 cosz = cos(phi);
383 }
384 match laea.mode {
385 ProjMode::Equit => {
386 phi = if fabs(rh) <= EPS10 { 0. } else { asin(y * sinz / rh) };
387 x *= sinz;
388 y = cosz * rh;
389 }
390 ProjMode::Obliq => {
391 phi = if fabs(rh) <= EPS10 {
392 proj.phi0
393 } else {
394 asin(cosz * laea.sinb1 + y * sinz * laea.cosb1 / rh)
395 };
396 x *= sinz * laea.cosb1;
397 y = (cosz - sin(phi) * laea.sinb1) * rh;
398 }
399 ProjMode::NPole => {
400 y = -y;
401 phi = FRAC_PI_2 - phi;
402 }
403 ProjMode::SPole => {
404 phi -= FRAC_PI_2;
405 }
406 }
407 let lam = if y == 0. && (laea.mode == ProjMode::Equit || laea.mode == ProjMode::Obliq) {
408 0.
409 } else {
410 atan2(x, y)
411 };
412
413 p.set_lam(lam);
414 p.set_phi(phi);
415}