use crate::tackler;
use rust_decimal::Decimal;
use serde::Serialize;
use std::fmt::{Display, Formatter};
#[derive(Serialize, Debug, Clone)]
pub struct GeoPoint {
pub lat: Decimal,
pub lon: Decimal,
#[serde(skip_serializing_if = "Option::is_none")]
pub alt: Option<Decimal>,
}
impl Display for GeoPoint {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let alt = match &self.alt {
Some(a) => format!(",{a}"),
None => String::new(),
};
write!(f, "geo:{},{}{}", self.lat, self.lon, alt)
}
}
pub const MAX_LAT: Decimal = Decimal::from_parts(90, 0, 0, false, 0);
pub const MIN_LAT: Decimal = Decimal::from_parts(90, 0, 0, true, 0);
pub const MAX_LON: Decimal = Decimal::from_parts(180, 0, 0, false, 0);
pub const MIN_LON: Decimal = Decimal::from_parts(180, 0, 0, true, 0);
pub const MIN_ALTITUDE: Decimal = Decimal::from_parts(6_378_137, 0, 0, true, 0);
#[allow(clippy::manual_range_contains)]
impl GeoPoint {
pub fn from(
lat: Decimal,
lon: Decimal,
alt: Option<Decimal>,
) -> Result<GeoPoint, tackler::Error> {
if lat < MIN_LAT || MAX_LAT < lat {
let msg = format!("Value out of specification for Latitude: {lat}");
return Err(msg.into());
}
if lon < MIN_LON || MAX_LON < lon {
let msg = format!("Value out of specification for Longitude: {lon}");
return Err(msg.into());
}
if let Some(z) = alt {
if z < Decimal::from(-6_378_137) {
let msg = format!("Value Out of specification for Altitude: {z}");
return Err(msg.into());
}
}
Ok(GeoPoint { lat, lon, alt })
}
}
#[cfg(test)]
mod tests {
use crate::location::GeoPoint;
use rust_decimal_macros::dec;
#[test]
fn geo_display() {
let tests: Vec<(GeoPoint, String)> = vec![
(
GeoPoint::from(dec!(60), dec!(24), None).unwrap(),
"geo:60,24".to_string(),
),
(
GeoPoint::from(dec!(60), dec!(24), Some(dec!(5))).unwrap(),
"geo:60,24,5".to_string(),
),
(
GeoPoint::from(dec!(60.167), dec!(24.955), Some(dec!(5.0))).unwrap(),
"geo:60.167,24.955,5.0".to_string(),
),
(
GeoPoint::from(dec!(60.167000), dec!(24.955000), Some(dec!(5.000))).unwrap(),
"geo:60.167000,24.955000,5.000".to_string(),
),
(
GeoPoint::from(dec!(-60), dec!(-24), Some(dec!(-5))).unwrap(),
"geo:-60,-24,-5".to_string(),
),
(
GeoPoint::from(dec!(-60.167), dec!(-24.955), Some(dec!(-5.0))).unwrap(),
"geo:-60.167,-24.955,-5.0".to_string(),
),
(
GeoPoint::from(dec!(-60.167000), dec!(-24.955000), Some(dec!(-5.000))).unwrap(),
"geo:-60.167000,-24.955000,-5.000".to_string(),
),
];
let mut count = 0;
let should_be_count = tests.len();
for t in tests {
let geo_str = format!("{}", t.0);
assert_eq!(geo_str, t.1);
count += 1;
}
assert_eq!(count, should_be_count);
}
}