#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![deny(missing_copy_implementations)]
#![deny(trivial_casts)]
#![deny(trivial_numeric_casts)]
#![deny(unused_import_braces)]
#![deny(unused_qualifications)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(rustdoc::private_intra_doc_links)]
#![cfg_attr(not(feature = "std"), no_std)]
pub mod maidenhead;
mod wgs;
mod wgs72;
mod wgs84;
pub use wgs72::Wgs72;
pub use wgs84::Wgs84;
pub(crate) mod std {
pub use core::*;
}
pub(crate) mod math {
#[cfg(feature = "libm")]
pub use libm::{atan2, cos, pow, round, sin, sqrt};
#[cfg(all(not(feature = "libm"), feature = "std"))]
mod _std_math {
pub fn cos(x: f64) -> f64 {
x.cos()
}
pub fn sin(x: f64) -> f64 {
x.sin()
}
pub fn sqrt(x: f64) -> f64 {
x.sqrt()
}
pub fn atan2(x: f64, y: f64) -> f64 {
x.atan2(y)
}
pub fn pow(x: f64, y: f64) -> f64 {
x.powf(y)
}
pub fn round(x: f64) -> f64 {
x.round()
}
}
#[cfg(all(not(feature = "libm"), feature = "std"))]
pub use _std_math::*;
#[cfg(not(any(feature = "std", feature = "libm")))]
compile_error!("need one of libm or std for a math backend");
}
use math::{atan2, cos, pow, sin, sqrt};
use std::marker::PhantomData;
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Meters(f64);
impl Meters {
pub const fn new(meters: f64) -> Meters {
Meters(meters)
}
pub const fn as_float(self) -> f64 {
self.0
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Degrees(f64);
impl Degrees {
pub const fn new(degrees: f64) -> Degrees {
Degrees(degrees)
}
pub const fn as_float(self) -> f64 {
self.0
}
pub fn as_dms(&self) -> (i64, i64, f64) {
let v = self.0;
let degrees = v;
let minutes = (degrees - math::round(v)) * 60.0;
let seconds = (minutes - math::round(minutes)) * 60.0;
(degrees as i64, minutes as i64, seconds)
}
}
impl From<Degrees> for Radians {
fn from(deg: Degrees) -> Self {
Radians::new(std::f64::consts::PI / 180.0 * deg.as_float())
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Radians(f64);
impl Radians {
pub const fn new(radians: f64) -> Radians {
Radians(radians)
}
pub const fn as_float(self) -> f64 {
self.0
}
}
impl From<Radians> for Degrees {
fn from(rad: Radians) -> Self {
Degrees::new(180.0 / std::f64::consts::PI * rad.as_float())
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Lle<CoordinateSystem, AngularMeasure = Degrees>
where
Radians: From<AngularMeasure> + Copy,
AngularMeasure: From<Radians> + Copy,
CoordinateSystem: crate::CoordinateSystem<AngularMeasure>,
{
_unused: PhantomData<CoordinateSystem>,
pub latitude: AngularMeasure,
pub longitude: AngularMeasure,
pub elevation: Meters,
}
#[deprecated]
pub type LLE<CoordinateSystem> = Lle<CoordinateSystem>;
impl<CoordinateSystem, AngularMeasure> Lle<CoordinateSystem, AngularMeasure>
where
Radians: From<AngularMeasure> + Copy,
AngularMeasure: From<Radians> + Copy,
CoordinateSystem: crate::CoordinateSystem<AngularMeasure>,
{
pub const fn new(
latitude: AngularMeasure,
longitude: AngularMeasure,
elevation: Meters,
) -> Lle<CoordinateSystem, AngularMeasure> {
Lle {
latitude,
longitude,
elevation,
_unused: PhantomData,
}
}
pub fn enu_to(&self, other: &Lle<CoordinateSystem, AngularMeasure>) -> Enu {
CoordinateSystem::lle_to_enu(self, other)
}
pub fn aer_to<AerAngularMeasure>(
&self,
other: &Lle<CoordinateSystem, AngularMeasure>,
) -> Aer<AerAngularMeasure>
where
Aer<AerAngularMeasure>: From<Enu>,
{
self.enu_to(other).into()
}
}
impl<CoordinateSystem> Lle<CoordinateSystem>
where
CoordinateSystem: crate::CoordinateSystem<Degrees>,
{
pub fn as_dms(&self) -> [(i64, i64, f64); 2] {
[self.latitude.as_dms(), self.longitude.as_dms()]
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Xyz {
pub x: Meters,
pub y: Meters,
pub z: Meters,
}
#[deprecated]
pub type XYZ = Xyz;
impl<CoordinateSystem, AngularMeasure> From<Lle<CoordinateSystem, AngularMeasure>> for Xyz
where
Radians: From<AngularMeasure> + Copy,
AngularMeasure: From<Radians> + Copy,
CoordinateSystem: crate::CoordinateSystem<AngularMeasure>,
{
fn from(lle: Lle<CoordinateSystem, AngularMeasure>) -> Self {
CoordinateSystem::lle_to_xyz(&lle)
}
}
impl<CoordinateSystem, AngularMeasure> From<Xyz> for Lle<CoordinateSystem, AngularMeasure>
where
Radians: From<AngularMeasure> + Copy,
AngularMeasure: From<Radians> + Copy,
CoordinateSystem: crate::CoordinateSystem<AngularMeasure>,
{
fn from(xyz: Xyz) -> Self {
CoordinateSystem::xyz_to_lle(&xyz)
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Aer<AngularMeasure = Degrees> {
pub azimuth: AngularMeasure,
pub elevation: AngularMeasure,
pub range: Meters,
}
#[deprecated]
pub type AER = Aer;
impl<AngularMeasure> Aer<AngularMeasure>
where
Enu: From<Aer<AngularMeasure>>,
Self: Copy,
{
pub fn to_lle<CoordinateSystem, LleAngularMeasure>(
&self,
obs: &Lle<CoordinateSystem, LleAngularMeasure>,
) -> Lle<CoordinateSystem, LleAngularMeasure>
where
Radians: From<LleAngularMeasure> + Copy,
LleAngularMeasure: From<Radians> + Copy,
CoordinateSystem: crate::CoordinateSystem<LleAngularMeasure>,
{
let enu: Enu = (*self).into();
enu.to_lle(obs)
}
}
impl From<Aer<Degrees>> for Aer<Radians> {
fn from(aer: Aer<Degrees>) -> Self {
Self {
azimuth: aer.azimuth.into(),
elevation: aer.elevation.into(),
range: aer.range,
}
}
}
impl From<Aer<Radians>> for Aer<Degrees> {
fn from(aer: Aer<Radians>) -> Self {
Self {
azimuth: aer.azimuth.into(),
elevation: aer.elevation.into(),
range: aer.range,
}
}
}
impl From<Aer<Radians>> for Enu {
fn from(aer: Aer<Radians>) -> Self {
let az_rad: Radians = aer.azimuth;
let el_rad: Radians = aer.elevation;
let r = Meters::new(aer.range.as_float() * cos(el_rad.as_float()));
Enu {
east: Meters::new(r.as_float() * sin(az_rad.as_float())),
north: Meters::new(r.as_float() * cos(az_rad.as_float())),
up: Meters::new(aer.range.as_float() * sin(el_rad.as_float())),
}
}
}
impl From<Aer<Degrees>> for Enu {
fn from(aer: Aer<Degrees>) -> Self {
let aer: Aer<Radians> = aer.into();
aer.into()
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Enu {
pub east: Meters,
pub north: Meters,
pub up: Meters,
}
#[deprecated]
pub type ENU = Enu;
impl Enu {
pub fn to_lle<CoordinateSystem, LleAngularMeasure>(
&self,
obs: &Lle<CoordinateSystem, LleAngularMeasure>,
) -> Lle<CoordinateSystem, LleAngularMeasure>
where
Radians: From<LleAngularMeasure> + Copy,
LleAngularMeasure: From<Radians> + Copy,
CoordinateSystem: crate::CoordinateSystem<LleAngularMeasure>,
{
CoordinateSystem::enu_to_lle(obs, self)
}
}
impl From<Enu> for Aer<Radians> {
fn from(enu: Enu) -> Self {
let r = sqrt(
enu.east.as_float() * enu.east.as_float() + enu.north.as_float() * enu.north.as_float(),
);
let tau = std::f64::consts::TAU;
Self {
azimuth: Radians::new(atan2(enu.east.as_float(), enu.north.as_float()) % tau),
elevation: Radians::new(atan2(enu.up.as_float(), r)),
range: Meters::new(sqrt(r * r + enu.up.as_float() * enu.up.as_float())),
}
}
}
impl From<Enu> for Aer<Degrees> {
fn from(enu: Enu) -> Self {
let aer: Aer<Radians> = enu.into();
aer.into()
}
}
pub trait CoordinateSystem<AngularMeasure>
where
Radians: From<AngularMeasure> + Copy,
AngularMeasure: From<Radians> + Copy,
Self: Sized,
{
fn xyz_to_lle(c: &Xyz) -> Lle<Self, AngularMeasure>;
fn xyz_to_enu(refr: &Lle<Self, AngularMeasure>, c: &Xyz) -> Enu;
fn enu_to_xyz(refr: &Lle<Self, AngularMeasure>, c: &Enu) -> Xyz;
fn lle_to_xyz(geo: &Lle<Self, AngularMeasure>) -> Xyz;
fn enu_to_lle(refr: &Lle<Self, AngularMeasure>, c: &Enu) -> Lle<Self, AngularMeasure> {
let xyz = Self::enu_to_xyz(refr, c);
Self::xyz_to_lle(&xyz)
}
fn lle_to_enu(r: &Lle<Self, AngularMeasure>, geo: &Lle<Self, AngularMeasure>) -> Enu {
let xyz = Self::lle_to_xyz(geo);
Self::xyz_to_enu(r, &xyz)
}
}
impl<FromCoordinateSystem, FromAngularMeasure> Lle<FromCoordinateSystem, FromAngularMeasure>
where
Radians: From<FromAngularMeasure> + Copy,
FromAngularMeasure: From<Radians> + Copy,
FromCoordinateSystem: CoordinateSystem<FromAngularMeasure>,
Self: Copy,
{
pub fn translate<ToCoordinateSystem, ToAngularMeasure>(
&self,
) -> Lle<ToCoordinateSystem, ToAngularMeasure>
where
Radians: From<ToAngularMeasure> + Copy,
ToAngularMeasure: From<Radians> + Copy,
ToCoordinateSystem: CoordinateSystem<ToAngularMeasure>,
{
let xyz = FromCoordinateSystem::lle_to_xyz(self);
ToCoordinateSystem::xyz_to_lle(&xyz)
}
}
fn angular_measure_to_radian_delta<AngularMeasure>(
self_lat_lon: (AngularMeasure, AngularMeasure),
other_lat_lon: (AngularMeasure, AngularMeasure),
) -> ((f64, f64), (f64, f64), (f64, f64))
where
Radians: From<AngularMeasure> + Copy,
{
let (self_lat, self_lon) = self_lat_lon;
let (self_lat, self_lon): (Radians, Radians) = (self_lat.into(), self_lon.into());
let (self_lat, self_lon) = (self_lat.as_float(), self_lon.as_float());
let (other_lat, other_lon) = other_lat_lon;
let (other_lat, other_lon): (Radians, Radians) = (other_lat.into(), other_lon.into());
let (other_lat, other_lon) = (other_lat.as_float(), other_lon.as_float());
let delta_lat = other_lat - self_lat;
let delta_lon = other_lon - self_lon;
(
(delta_lat, delta_lon),
(self_lat, self_lon),
(other_lat, other_lon),
)
}
pub fn haversine_distance<AngularMeasure>(
self_lat_lon: (AngularMeasure, AngularMeasure),
other_lat_lon: (AngularMeasure, AngularMeasure),
) -> Meters
where
Radians: From<AngularMeasure> + Copy,
{
const EARTH_RADIUS: Meters = Meters(6371000.0);
let (
(delta_lat, delta_lon),
(self_lat, _self_lon),
(other_lat, _other_lon),
) = angular_measure_to_radian_delta(self_lat_lon, other_lat_lon);
let a = pow(sin(delta_lat / 2.0), 2.0)
+ cos(self_lat) * cos(other_lat) * pow(sin(delta_lon / 2.0), 2.0);
let c = 2.0 * atan2(sqrt(a), sqrt(1.0 - a));
Meters::new(EARTH_RADIUS.as_float() * c)
}
pub fn bearing<AngularMeasure>(
self_lat_lon: (AngularMeasure, AngularMeasure),
other_lat_lon: (AngularMeasure, AngularMeasure),
) -> AngularMeasure
where
Radians: From<AngularMeasure> + Copy,
AngularMeasure: From<Radians> + Copy,
{
let (
(_delta_lat, delta_lon),
(self_lat, _self_lon),
(other_lat, _other_lon),
) = angular_measure_to_radian_delta(self_lat_lon, other_lat_lon);
let x = cos(self_lat) * sin(other_lat) - sin(self_lat) * cos(other_lat) * cos(delta_lon);
let y = sin(delta_lon) * cos(other_lat);
Radians::new((atan2(y, x) + std::f64::consts::TAU) % std::f64::consts::TAU).into()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn degrees_as_radians() {
let result: Radians = Degrees::new(180.0).into();
assert_eq!(result.as_float(), std::f64::consts::PI);
}
#[test]
fn radians_as_degrees() {
let result: Degrees = Radians::new(std::f64::consts::PI).into();
assert_eq!(result.as_float(), 180.0);
}
macro_rules! assert_in_eps {
($x:expr, $y:expr, $d:expr) => {
if !($x - $y < $d && $y - $x < $d) {
panic!("{} is not ~= to {}", $x, $y);
}
};
}
#[test]
fn degrees_as_dms() {
let (dec, min, sec) = Degrees::new(42.15188).as_dms();
assert_eq!((42, 9), (dec, min));
assert_in_eps!(6.768, sec, 1e-3);
}
#[test]
fn haversine_known() {
let result = haversine_distance(
(Degrees::new(22.55), Degrees::new(43.12)),
(Degrees::new(13.45), Degrees::new(100.28)),
);
assert_in_eps!(6094544.408786774, result.as_float(), 1e-6);
let result = haversine_distance(
(Degrees::new(51.510357), Degrees::new(-0.116773)),
(Degrees::new(38.889931), Degrees::new(-77.009003)),
);
assert_in_eps!(5897658.288856054, result.as_float(), 1e-6);
}
#[test]
fn bearing_known() {
let result = bearing(
(Degrees::new(0.0), Degrees::new(-88.0)),
(Degrees::new(0.0), Degrees::new(0.0)),
);
assert_in_eps!(90.0, result.as_float(), 1e-6);
let result = bearing(
(Degrees::new(0.0), Degrees::new(0.0)),
(Degrees::new(0.0), Degrees::new(-88.0)),
);
assert_in_eps!(270.0, result.as_float(), 1e-6);
let result = bearing(
(Degrees::new(38.890591), Degrees::new(-77.004745)),
(Degrees::new(38.897957), Degrees::new(-77.036560)),
);
assert_in_eps!(286.576, result.as_float(), 1e-3);
}
#[test]
fn enu_aer_round_trip() {
let enu = Enu {
east: Meters(1.0),
north: Meters(0.0),
up: Meters(0.0),
};
let aer: Aer = enu.into();
assert_in_eps!(1.0, aer.range.as_float(), 1e-6);
assert_in_eps!(90.0, aer.azimuth.as_float(), 1e-6);
assert_in_eps!(0.0, aer.elevation.as_float(), 1e-6);
let enu = Enu {
east: Meters(0.0),
north: Meters(1.0),
up: Meters(0.0),
};
let aer: Aer = enu.into();
assert_in_eps!(1.0, aer.range.as_float(), 1e-6);
assert_in_eps!(0.0, aer.azimuth.as_float(), 1e-6);
assert_in_eps!(0.0, aer.elevation.as_float(), 1e-6);
let enu = Enu {
east: Meters(0.0),
north: Meters(0.0),
up: Meters(1.0),
};
let aer: Aer = enu.into();
assert_in_eps!(1.0, aer.range.as_float(), 1e-6);
assert_in_eps!(0.0, aer.azimuth.as_float(), 1e-6);
assert_in_eps!(90.0, aer.elevation.as_float(), 1e-6);
let enu = Enu {
east: Meters(0.0),
north: Meters(0.0),
up: Meters(-1.0),
};
let aer: Aer = enu.into();
assert_in_eps!(1.0, aer.range.as_float(), 1e-6);
assert_in_eps!(0.0, aer.azimuth.as_float(), 1e-6);
assert_in_eps!(-90.0, aer.elevation.as_float(), 1e-6);
}
}