use std::{
fmt,
ops::{Add, Div, Mul, Sub},
};
pub const DEGREES: f64 = 1.;
pub const MINUTES: f64 = DEGREES * 60.;
pub const SECS: f64 = MINUTES * 60.;
pub const MILLI_SECS: f64 = SECS * 1_000.;
pub const MICRO_SECS: f64 = MILLI_SECS * 1_000.;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct LatLon<T = f64>(
pub T,
pub T,
);
impl<T> LatLon<T> {
pub fn lat(&self) -> &T {
&self.0
}
pub fn lon(&self) -> &T {
&self.1
}
pub fn map<U>(self, f: impl Fn(T) -> U) -> LatLon<U> {
let LatLon(lat, lon) = self;
LatLon(f(lat), f(lon))
}
}
impl LatLon<f64> {
pub fn to_dms(self) -> LatLon<Dms> {
self.map(Dms::from_degrees)
}
pub(crate) fn validate_degrees(self) -> Result<(), DegreesError> {
fn is_in_degrees_range(lat: f64, lon: f64) -> bool {
lat.abs() <= 90. && lon.abs() <= 180.
}
let LatLon(lat, lon) = self;
if is_in_degrees_range(lat, lon) {
return Ok(());
}
let possibly_reversed = is_in_degrees_range(lon, lat);
Err(DegreesError { possibly_reversed })
}
}
impl LatLon<Dms> {
pub fn to_degrees(self) -> LatLon<f64> {
self.map(Dms::to_degrees)
}
}
impl Add<LatLon> for LatLon {
type Output = Self;
fn add(mut self, rhs: LatLon) -> Self::Output {
self.0 += rhs.0;
self.1 += rhs.1;
self
}
}
impl Sub<LatLon> for LatLon {
type Output = Self;
fn sub(mut self, rhs: LatLon) -> Self::Output {
self.0 -= rhs.0;
self.1 -= rhs.1;
self
}
}
impl Mul<f64> for LatLon {
type Output = Self;
fn mul(self, rhs: f64) -> Self::Output {
self.map(|x| x * rhs)
}
}
impl Div<f64> for LatLon {
type Output = Self;
fn div(self, rhs: f64) -> Self::Output {
self.map(|x| x / rhs)
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct Dms(
pub i32,
pub i32,
pub f64,
);
impl Dms {
pub fn d(self) -> i32 {
self.0
}
pub fn m(self) -> i32 {
self.1
}
pub fn s(self) -> f64 {
self.2
}
fn from_degrees(deg: f64) -> Self {
let d = deg as i32;
let m = (deg * 60. % 60.) as i32;
let s = (deg * 3600.) % 60.;
Dms(d, m, s)
}
fn to_degrees(self) -> f64 {
let Dms(d, m, s) = self;
f64::from(d) + f64::from(m) / 60. + s / 3_600.
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct DegreesError {
possibly_reversed: bool,
}
impl fmt::Display for DegreesError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "degrees out of range")?;
if self.possibly_reversed {
write!(f, "; may be lat and lon reversed?")?;
}
Ok(())
}
}
impl std::error::Error for DegreesError {}