#![allow(non_upper_case_globals)]
use crate::ellipsoids::{EllipsoidDefn, FlatteningParam};
use crate::errors::{Error, Result};
use crate::math::consts::EPS_10;
use crate::parameters::ParamList;
use std::ops::ControlFlow;
const SIXTH: f64 = 1. / 6.;
const RA4: f64 = 17. / 360.;
const RA6: f64 = 67. / 3024.;
const RV4: f64 = 5. / 72.;
const RV6: f64 = 55. / 1296.;
const TOK_rf: &str = "rf";
const TOK_f: &str = "f";
const TOK_es: &str = "es";
const TOK_e: &str = "e";
const TOK_b: &str = "b";
const TOK_R_A: &str = "R_A";
const TOK_R_V: &str = "R_V";
const TOK_R_a: &str = "R_a";
const TOK_R_g: &str = "R_g";
const TOK_R_h: &str = "R_h";
#[allow(non_camel_case_types)]
pub enum Shape {
SP_rf(f64),
SP_f(f64),
SP_es(f64),
SP_e(f64),
SP_b(f64),
}
#[derive(Clone, Debug)]
pub struct Ellipsoid {
pub a: f64, pub b: f64,
pub ra: f64, #[allow(dead_code)]
pub rb: f64,
pub e: f64, pub es: f64, pub one_es: f64, pub rone_es: f64,
pub f: f64, #[allow(dead_code)]
pub rf: f64,
}
use Shape::*;
impl Ellipsoid {
#[inline]
pub fn is_sphere(&self) -> bool {
self.es == 0.
}
#[inline]
pub fn is_ellipsoid(&self) -> bool {
self.es != 0.
}
pub fn sphere(radius: f64) -> Result<Self> {
if !(radius.is_normal() && radius > 0.) {
return Err(Error::InvalidParameterValue("Invalid radius"));
}
Ok(Self {
a: radius,
b: radius,
ra: 1. / radius,
rb: 1. / radius,
e: 0.,
es: 0.,
f: 0.,
rf: f64::INFINITY,
one_es: 1.,
rone_es: 1.,
})
}
#[allow(dead_code)]
pub fn try_from_ellipsoid(defn: &EllipsoidDefn) -> Result<Self> {
Self::calc_ellipsoid_params(
defn.a,
match defn.rf_or_b {
FlatteningParam::MinorAxis(b) => SP_b(b),
FlatteningParam::InvFlat(rf) => SP_rf(rf),
},
)
}
pub fn try_from_ellipsoid_with_params(
defn: &EllipsoidDefn,
params: &ParamList,
) -> Result<Self> {
let a = if let Some(p) = params.get("a") {
p.try_into()?
} else {
defn.a
};
let sp = Self::find_shape_parameter(params).unwrap_or(Ok(match defn.rf_or_b {
FlatteningParam::MinorAxis(b) => SP_b(b),
FlatteningParam::InvFlat(rf) => SP_rf(rf),
}))?;
Self::calc_ellipsoid_params(a, sp).and_then(|ellps| ellps.spherification(params))
}
pub fn try_from_semi_major_axis(a: f64, params: &ParamList) -> Result<Self> {
let sp = Self::find_shape_parameter(params).unwrap_or(Ok(SP_es(0.)))?;
Self::calc_ellipsoid_params(a, sp).and_then(|ellps| ellps.spherification(params))
}
fn find_shape_parameter(params: &ParamList) -> Option<Result<Shape>> {
const SHAPE_TOKENS: &[&str] = &[TOK_rf, TOK_f, TOK_es, TOK_e, TOK_b];
SHAPE_TOKENS.iter().find_map(|tok| {
params.get(tok).map(|p| {
p.try_into().map(|v| match *tok {
TOK_rf => SP_rf(v),
TOK_f => SP_f(v),
TOK_es => SP_es(v),
TOK_e => SP_e(v),
TOK_b => SP_b(v),
_ => unreachable!(),
})
})
})
}
pub fn calc_ellipsoid_params(a: f64, sp: Shape) -> Result<Self> {
if a <= 0. {
return Err(Error::InvalidParameterValue("Invalid major axis"));
}
let (mut f, mut rf, mut es, mut e, mut b);
match sp {
SP_rf(p_rf) => {
if p_rf <= 1. {
return Err(Error::InvalidParameterValue(
"Inverse flattening lower than 1.",
));
}
rf = p_rf;
f = 1. / rf;
es = 2. * f - f * f;
e = es.sqrt();
b = (1.0 - f) * a;
}
SP_f(p_f) => {
if !(0. ..1.).contains(&p_f) {
return Err(Error::InvalidParameterValue("Flattening not in [0..1["));
}
f = p_f;
es = 2. * f - f * f;
e = es.sqrt();
b = (1.0 - f) * a;
rf = if f > 0. { 1. / f } else { f64::INFINITY }
}
SP_es(p_es) => {
if !(0. ..1.).contains(&p_es) {
return Err(Error::InvalidParameterValue(
"Square eccentricity not in [0..1[",
));
}
es = p_es;
e = es.sqrt();
f = 1. - e.asin().cos();
b = (1.0 - f) * a;
rf = if f > 0. { 1. / f } else { f64::INFINITY }
}
SP_e(p_e) => {
if !(0. ..1.).contains(&p_e) {
return Err(Error::InvalidParameterValue("Eccentricity not in [0..1["));
}
e = p_e;
es = e * e;
f = 1. - e.asin().cos();
b = (1.0 - f) * a;
rf = if f > 0. { 1. / f } else { f64::INFINITY }
}
SP_b(p_b) => {
if !(p_b > 0. && p_b <= a) {
return Err(Error::InvalidParameterValue("Invalid minor axis"));
}
b = p_b;
let a2 = a * a;
let b2 = b * b;
es = (a2 - b2) / a2;
e = es.sqrt();
f = (a - b) / b;
rf = if f > 0. { 1. / f } else { f64::INFINITY }
}
}
if (a - b).abs() < EPS_10 {
b = a;
es = 0.;
e = 0.;
f = 0.;
rf = f64::INFINITY;
}
let one_es = 1. - es;
Ok(Self {
a,
b,
ra: 1. / a,
rb: 1. / b,
e,
es,
f,
rf,
one_es,
rone_es: 1. / one_es,
})
}
fn spherification(self, params: &ParamList) -> Result<Self> {
const SPHERE_TOKENS: &[&str] = &[TOK_R_A, TOK_R_V, TOK_R_a, TOK_R_g, TOK_R_h];
match SPHERE_TOKENS.iter().try_for_each(|tok| {
if params.get(tok).is_some() {
let es = self.es;
let a = match *tok {
TOK_R_A => 1. - es * (SIXTH + es * (RA4 + es * RA6)),
TOK_R_V => 1. - es * (SIXTH + es * (RV4 + es * RV6)),
TOK_R_a => (self.a + self.b) / 2.,
TOK_R_g => (self.a + self.b).sqrt(),
TOK_R_h => (2. * self.a * self.b) / (self.a + self.b),
_ => unreachable!(),
};
ControlFlow::Break(Self::calc_ellipsoid_params(a, SP_es(0.)))
} else {
ControlFlow::Continue(())
}
}) {
ControlFlow::Break(rv) => rv,
_ => Ok(self),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ellipsoids::constants::*;
use crate::projstring;
#[test]
fn ellps_from_defn() {
let ellps = Ellipsoid::try_from_ellipsoid(&WGS84).unwrap();
assert_eq!(ellps.a, 6_378_137.);
assert_eq!(ellps.rf, 298.257_223_563);
}
#[test]
fn ellps_from_defn_and_params() {
let ellps = Ellipsoid::try_from_ellipsoid_with_params(
&WGS84,
&projstring::parse("+a=6370997.").unwrap(),
)
.unwrap();
assert_eq!(ellps.a, 6_370_997.);
assert_eq!(ellps.rf, 298.257_223_563);
}
fn assert_sphere(ellps: Ellipsoid) {
assert_eq!(ellps.a, ellps.b);
assert_eq!(ellps.f, 0.);
assert_eq!(ellps.rf, f64::INFINITY);
assert_eq!(ellps.e, 0.);
assert_eq!(ellps.es, 0.);
}
#[test]
fn ellps_sphere() {
let ellps = Ellipsoid::sphere(6_378_388.).unwrap();
assert_eq!(ellps.a, 6_378_388.);
assert_sphere(ellps);
}
#[test]
fn ellps_from_defn_and_es_zero() {
let ellps = Ellipsoid::try_from_ellipsoid_with_params(
&WGS84,
&projstring::parse("+es=0.").unwrap(),
)
.unwrap();
assert_sphere(ellps);
}
#[test]
fn ellps_spherification() {
let ellps =
Ellipsoid::try_from_ellipsoid_with_params(&WGS84, &projstring::parse("+R_A").unwrap())
.unwrap();
assert_sphere(ellps);
}
#[test]
fn ellps_invalid_params() {
fn from_projstring(s: &str) -> Result<Ellipsoid> {
Ellipsoid::try_from_ellipsoid_with_params(&WGS84, &projstring::parse(s).unwrap())
}
assert!(from_projstring("+a=-0.").is_err());
assert!(from_projstring("+a=-2.").is_err());
assert!(from_projstring("+es=-1.").is_err());
assert!(from_projstring("+f=20.").is_err());
}
}