use crate::ecef::ECEF;
use crate::nvector::NVector;
use na::RealField;
use std::convert::From;
#[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;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(PartialEq, Clone, Copy, Debug)]
pub struct WGS84<N> {
#[cfg_attr(feature = "serde", serde(rename = "latitude"))]
lat: N,
#[cfg_attr(feature = "serde", serde(rename = "longitude"))]
lon: N,
#[cfg_attr(feature = "serde", serde(rename = "altitude"))]
alt: N,
}
impl<N: RealField> WGS84<N>
where
f64: From<N>,
{
pub fn from_degrees_and_meters(latitude: N, longitude: N, altitude: N) -> WGS84<N> {
assert!(
latitude.abs() <= N::from_f64(90.0).unwrap(),
"Latitude must be in the range [-90, 90]"
);
assert!(
longitude.abs() <= N::from_f64(180.0).unwrap(),
"Longitude must be in the range [-180, 180]"
);
WGS84 {
lat: N::from_f64(f64::from(latitude).to_radians()).unwrap(),
lon: N::from_f64(f64::from(longitude).to_radians()).unwrap(),
alt: altitude,
}
}
pub fn try_from_degrees_and_meters(latitude: N, longitude: N, altitude: N) -> Option<WGS84<N>> {
if latitude.abs() <= N::from_f64(90.0).unwrap()
&& longitude.abs() <= N::from_f64(180.0).unwrap()
{
Some(WGS84::from_degrees_and_meters(
latitude, longitude, altitude,
))
} else {
None
}
}
pub fn from_radians_and_meters(latitude: N, longitude: N, altitude: N) -> WGS84<N> {
assert!(
latitude.abs() <= N::from_f64(std::f64::consts::FRAC_PI_2).unwrap(),
"Latitude must be in the range [-π/2, π/2]"
);
assert!(
longitude.abs() <= N::from_f64(std::f64::consts::PI).unwrap(),
"Longitude must be in the range [-π, π]"
);
WGS84 {
lat: latitude,
lon: longitude,
alt: altitude,
}
}
pub fn try_from_radians_and_meters(latitude: N, longitude: N, altitude: N) -> Option<WGS84<N>> {
if latitude.abs() <= N::from_f64(std::f64::consts::FRAC_PI_2).unwrap()
&& longitude.abs() <= N::from_f64(std::f64::consts::PI).unwrap()
{
Some(WGS84 {
lat: latitude,
lon: longitude,
alt: altitude,
})
} else {
None
}
}
pub fn latitude_degrees(&self) -> N {
N::from_f64(f64::from(self.lat).to_degrees()).unwrap()
}
pub fn longitude_degrees(&self) -> N {
N::from_f64(f64::from(self.lon).to_degrees()).unwrap()
}
pub fn distance(&self, other: &WGS84<N>) -> N {
let delta_lat = other.latitude_radians() - self.latitude_radians();
let delta_lon = other.longitude_radians() - self.longitude_radians();
let a = (delta_lat / N::from_f64(2.0).unwrap()).sin().powi(2)
+ self.latitude_radians().cos()
* other.latitude_radians().cos()
* (delta_lon / N::from_f64(2.0).unwrap()).sin().powi(2);
let c = N::from_f64(2.0).unwrap() * a.sqrt().asin();
N::from_f64(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_radians(&self) -> N {
self.lat
}
pub fn longitude_radians(&self) -> N {
self.lon
}
}
impl<N: RealField> 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: RealField> From<ECEF<N>> for WGS84<N> {
#![allow(clippy::many_single_char_names)]
fn from(ecef: ECEF<N>) -> WGS84<N> {
let a = N::from_f64(SEMI_MAJOR_AXIS).unwrap();
let b = N::from_f64(SEMI_MINOR_AXIS).unwrap();
let r_squared = (ecef.x() * ecef.x()) + (ecef.y() * ecef.y());
let r = r_squared.sqrt();
let z_squared = ecef.z() * ecef.z();
let z = z_squared.sqrt();
let a_squared = a * a;
let b_squared = b * b;
let e_squared = N::one() - (b_squared / a_squared);
let e_dot_squared = (a_squared - b_squared) / b_squared;
let f = N::from_f64(54.0).unwrap() * b_squared * z_squared;
let g = r_squared + ((N::one() - e_squared) * z_squared)
- (e_squared * (a_squared - b_squared));
let g_squared = g * g;
let c = (e_squared * e_squared * f * r_squared) / (g * g_squared);
let s = (N::one() + c + ((c * c) + c + c).sqrt()).cbrt();
let s_plus_one_over_s_plus_one = s + (N::one() / s) + N::one();
let p = f
/ (N::from_f64(3.0).unwrap()
* s_plus_one_over_s_plus_one
* s_plus_one_over_s_plus_one
* g_squared);
let q = (N::one() + (N::from_f64(2.0).unwrap() * e_squared * e_squared * p)).sqrt();
let r_0 = (-(p * e_squared * r) / (N::one() + q))
+ (a_squared / N::from_f64(2.0).unwrap() * (N::one() + (N::one() / q))
- ((p * (N::one() - e_squared) * z_squared) / (q * (N::one() + q)))
- (p * r_squared / N::from_f64(2.0).unwrap()))
.sqrt();
let r_minus_e_squared_r0 = r - (e_squared * r_0);
let r_minus_e_squared_r0_squared = r_minus_e_squared_r0 * r_minus_e_squared_r0;
let u = (r_minus_e_squared_r0_squared + z_squared).sqrt();
let v = (r_minus_e_squared_r0_squared + ((N::one() - e_squared) * z_squared)).sqrt();
let z_0 = (b_squared * z) / (a * v);
let h = u * (N::one() - (b_squared / (a * v)));
let phi = ecef.z().signum() * ((z + (e_dot_squared * z_0)) / r).atan().abs();
let lambda = ecef.y().atan2(ecef.x());
WGS84 {
lat: phi,
lon: lambda,
alt: h,
}
}
}
#[cfg(test)]
impl Arbitrary for WGS84<f64> {
fn arbitrary<G: Gen>(g: &mut G) -> WGS84<f64> {
use rand::Rng;
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::from_degrees_and_meters(lat, lon, alt)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::enu::ENU;
use assert::close;
use quickcheck::{quickcheck, TestResult};
#[test]
#[cfg_attr(not(feature = "serde"), ignore)]
fn test_ser_de() {
#[cfg(feature = "serde")]
{
use serde_test::{assert_tokens, Token};
let oslo: WGS84<f64> = WGS84 {
lat: 1.0463,
lon: 0.1876,
alt: 0.0,
};
assert_tokens(
&oslo,
&[
Token::Struct {
name: "WGS84",
len: 3,
},
Token::Str("latitude"),
Token::F64(1.0463),
Token::Str("longitude"),
Token::F64(0.1876),
Token::Str("altitude"),
Token::F64(0.0),
Token::StructEnd,
],
);
}
#[cfg(not(feature = "serde"))]
{
panic!("This test requires the serde feature to be enabled");
}
}
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::from_degrees_and_meters(latitude, longitude, altitude);
})
}
}
#[test]
fn test_create_wgs84() {
quickcheck(create_wgs84 as fn(f32, f32, f32) -> TestResult);
}
#[test]
fn dateline() {
let a = WGS84::from_degrees_and_meters(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);
}
#[test]
fn conversion_inversion_ecef() {
let oslo: WGS84<f64> = WGS84::from_degrees_and_meters(59.95, 10.75, 0.0);
let stockholm: WGS84<f64> = WGS84::from_degrees_and_meters(59.329444, 18.068611, 0.0);
for &place in [oslo, stockholm].iter() {
let distance = place.distance(&WGS84::from(ECEF::from(place)));
close(distance, 0.0, 0.00000001);
}
}
#[test]
fn conversion_ecef() {
let oslo_wgs84: WGS84<f64> = WGS84::from_degrees_and_meters(59.95, 10.75, 0.0);
let oslo_ecef: ECEF<f64> = ECEF::new(3145735.0, 597236.0, 5497690.0);
for &(place_wgs84, place_ecef) in [(oslo_wgs84, oslo_ecef)].iter() {
let distance = place_wgs84.distance(&WGS84::from(place_ecef));
close(distance, 0.0, 1.0);
}
}
#[test]
fn add_enu() {
let oslo: WGS84<f64> = WGS84::from_degrees_and_meters(59.95, 10.75, 0.0);
let oslo_high: WGS84<f64> = WGS84::from_degrees_and_meters(59.95, 10.75, 100.0);
let stockholm: WGS84<f64> = WGS84::from_degrees_and_meters(59.329444, 18.068611, 0.0);
let stockholm_high: WGS84<f64> =
WGS84::from_degrees_and_meters(59.329444, 18.068611, 100.0);
for &(place, place_high) in [(oslo, oslo_high), (stockholm, stockholm_high)].iter() {
let distance =
ECEF::from(place_high).distance(&(ECEF::from(place) + ENU::new(0.0, 0.0, 100.0)));
close(distance, 0.0, 0.00001);
}
}
quickcheck! {
fn distance_haversin(a: WGS84<f64>, b: WGS84<f64>) -> () {
close(a.distance(&b), b.distance(&a), 0.0000001);
}
}
}