use std::fmt::Display;
use crate::{Error, utm::UtmUps, mgrs::Mgrs};
const EARTH_MEAN_RADIUS_M: f64 = 6371.0088 * 1000.0;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LatLon {
#[cfg_attr(feature = "serde", serde(alias = "lat"))]
pub(crate) latitude: f64,
#[cfg_attr(feature = "serde", serde(alias = "lon"))]
pub(crate) longitude: f64,
}
impl LatLon {
pub(crate) fn new(lat: f64, lon: f64) -> LatLon {
Self {
latitude: lat,
longitude: lon,
}
}
pub fn create(lat: f64, lon: f64) -> Result<LatLon, Error> {
if !(-90_f64..=90_f64).contains(&lat) {
Err(Error::InvalidCoord(format!("Latitude {lat} outside of valid range [-90, 90].")))
} else if !(-180_f64..180_f64).contains(&lon) {
Err(Error::InvalidCoord(format!("Longitude {lon} outside of valid range [-180, 180].")))
} else {
Ok(LatLon::new(lat, lon))
}
}
#[inline]
pub fn latitude(&self) -> f64 {
self.latitude
}
#[inline]
pub fn longitude(&self) -> f64 {
self.longitude
}
pub fn is_north(&self) -> bool {
self.latitude.is_sign_positive()
}
pub fn haversine(&self, other: &LatLon) -> f64 {
let lat1_r = self.latitude.to_radians();
let lat2_r = other.latitude.to_radians();
2.0 * EARTH_MEAN_RADIUS_M * (
((other.latitude - self.latitude).to_radians() / 2.0).sin().powi(2) +
lat1_r.cos() * lat2_r.cos() *
((other.longitude - self.longitude).to_radians() / 2.0).sin().powi(2)
).sqrt().asin()
}
pub fn from_utmups(value: &UtmUps) -> LatLon {
value.to_latlon()
}
pub fn to_utmups(&self) -> UtmUps {
UtmUps::from_latlon(self)
}
pub fn from_mgrs(value: &Mgrs) -> LatLon {
value.to_latlon()
}
pub fn to_mgrs(&self, precision: i32) -> Mgrs {
Mgrs::from_latlon(self, precision)
}
}
impl Display for LatLon {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut buf = ryu::Buffer::new();
let lat = buf.format(self.latitude);
let mut buf = ryu::Buffer::new();
let lon = buf.format(self.longitude);
write!(
f,
"{lat} {lon}",
)
}
}