use ::ecef::ECEF;
use ::nvector::NVector;
use num_traits::Float;
use std::convert::From;
use std::f32::consts::FRAC_PI_2;
#[cfg(test)]
use quickcheck::{Arbitrary, Gen};
pub const INVERSE_FLATTENING: f64 = 298.257_223_563;
pub const FLATTENING: f64 = 1.0 / INVERSE_FLATTENING;
pub const SEMI_MAJOR_AXIS: f64 = 6_378_137.0;
pub const SEMI_MINOR_AXIS: f64 = SEMI_MAJOR_AXIS * (1.0 - FLATTENING);
pub const ECCENTRICITY_SQ: f64 = 2.0 * FLATTENING - FLATTENING * FLATTENING;
#[derive(PartialEq, Clone, Copy, Debug)]
pub struct WGS84<N> {
lat: N,
lon: N,
alt: N,
}
impl<N: Float> WGS84<N> {
pub fn new(latitude: N, longitude: N, altitude: N) -> WGS84<N> {
assert!(latitude.abs() <= N::from(90.0).unwrap(),
"Latitude must be in the range [-90, 90]");
assert!(longitude.abs() <= N::from(180.0).unwrap(),
"Longitude must be in the range [-180, 180]");
WGS84 {
lat: latitude.to_radians(),
lon: longitude.to_radians(),
alt: altitude,
}
}
pub fn try_new(latitude: N, longitude: N, altitude: N) -> Option<WGS84<N>> {
if latitude.abs() <= N::from(90.0).unwrap() && longitude.abs() <= N::from(180.0).unwrap() {
Some(WGS84::new(latitude, longitude, altitude))
} else {
None
}
}
pub fn latitude_degrees(&self) -> N {
self.lat.to_degrees()
}
pub fn longitude_degrees(&self) -> N {
self.lon.to_degrees()
}
pub fn distance(&self, other: &WGS84<N>) -> N {
let delta_lat = other.latitude() - self.latitude();
let delta_lon = other.longitude() - self.longitude();
let a = (delta_lat / N::from(2.0).unwrap()).sin().powi(2) +
self.latitude().cos() * other.latitude().cos() *
(delta_lon / N::from(2.0).unwrap()).sin().powi(2);
let c = N::from(2.0).unwrap() * a.sqrt().asin();
N::from(SEMI_MAJOR_AXIS).unwrap() * c + (self.altitude() - other.altitude()).abs()
}
}
impl<N: Copy> WGS84<N> {
pub fn altitude(&self) -> N {
self.alt
}
pub fn latitude(&self) -> N {
self.lat
}
pub fn longitude(&self) -> N {
self.lon
}
}
impl<N: Float> From<NVector<N>> for WGS84<N> {
fn from(f: NVector<N>) -> WGS84<N> {
let x = f.vector().z;
let y = f.vector().y;
let z = -f.vector().x;
let longitude = y.atan2(-z);
let equat = (y.powi(2) + z.powi(2)).sqrt();
let latitude = x.atan2(equat);
WGS84 {
lat: latitude,
lon: longitude,
alt: f.altitude(),
}
}
}
impl<N: Float> From<ECEF<N>> for WGS84<N> {
fn from(f: ECEF<N>) -> WGS84<N> {
let a_sq = N::from(SEMI_MAJOR_AXIS).unwrap().powi(2);
let e_2 = N::from(ECCENTRICITY_SQ).unwrap();
let e_4 = N::from(ECCENTRICITY_SQ).unwrap().powi(2);
let p = (f.x().powi(2) + f.y().powi(2)) / a_sq;
let q = ((N::one() - e_2) / a_sq) * f.z().powi(2);
let r = (p + q - e_4) / N::from(6.0).unwrap();
let s = e_4 * ((p * q) / (N::from(4.0).unwrap() * r.powi(3)));
let t = (N::one() + s + (s * (N::from(2.0).unwrap() + s)).sqrt()).cbrt();
let u = r * (N::one() + t + t.recip());
let v = (u.powi(2) + e_4 * q).sqrt();
let w = e_2 * ((u + v - q) / (N::from(2.0).unwrap() * v));
let k = (u + v + w.powi(2)).sqrt() - w;
let d = (k * (f.x().powi(2) + f.y().powi(2)).sqrt()) / (k + e_2);
let pi_half = N::from(FRAC_PI_2).unwrap();
let altitude = ((k + e_2 - N::one()) / k) * (d.powi(2) + f.z().powi(2)).sqrt();
let latitude = N::from(2.0).unwrap() * f.z().atan2(d + (d.powi(2) + f.z().powi(2)).sqrt());
let longitude = if f.y() >= N::zero() {
pi_half -
N::from(2.0).unwrap() * f.x().atan2((f.x().powi(2) + f.y().powi(2)).sqrt() + f.y())
} else {
-pi_half +
N::from(2.0).unwrap() * f.x().atan2((f.x().powi(2) + f.y().powi(2)).sqrt() - f.y())
};
WGS84 {
lat: latitude,
lon: longitude,
alt: altitude,
}
}
}
#[cfg(test)]
impl Arbitrary for WGS84<f64> {
fn arbitrary<G: Gen>(g: &mut G) -> WGS84<f64> {
let lat = g.gen_range(-90.0, 90.0);
let lon = g.gen_range(-180.0, 180.0);
let alt = g.gen_range(-6300000.0, 10000000.0);
WGS84::new(lat, lon, alt)
}
}
#[cfg(test)]
mod tests {
use ::enu::ENU;
use assert::close;
use quickcheck::{TestResult, quickcheck};
use super::*;
fn create_wgs84(latitude: f32, longitude: f32, altitude: f32) -> TestResult {
if latitude.abs() <= 90.0 && longitude.abs() <= 180.0 {
TestResult::discard()
} else {
TestResult::must_fail(move || {
WGS84::new(latitude, longitude, altitude);
})
}
}
#[test]
fn test_create_wgs84() {
quickcheck(create_wgs84 as fn(f32, f32, f32) -> TestResult);
}
#[test]
fn dateline() {
let a = WGS84::new(20.0, 180.0, 0.0);
let vec = ENU::new(-10.0, 0.0, 0.0);
let ans = a + vec;
close(ans.longitude_degrees(), -180.0, 0.001);
close(ans.latitude_degrees(), 20.0, 0.001);
}
quickcheck!{
fn distance_haversin(a: WGS84<f64>, b: WGS84<f64>) -> () {
close(a.distance(&b), b.distance(&a), 0.0000001);
}
}
}