1use crate::tackler;
9use rust_decimal::Decimal;
10use serde::Serialize;
11use std::fmt::{Display, Formatter};
12
13#[derive(Serialize, Debug, Clone)]
16pub struct GeoPoint {
17 pub lat: Decimal,
19 pub lon: Decimal,
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub alt: Option<Decimal>,
24}
25
26impl Display for GeoPoint {
27 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
28 let alt = match &self.alt {
29 Some(a) => format!(",{a}"),
30 None => String::new(),
31 };
32 write!(f, "geo:{},{}{}", self.lat, self.lon, alt)
33 }
34}
35pub const MAX_LAT: Decimal = Decimal::from_parts(90, 0, 0, false, 0);
37pub const MIN_LAT: Decimal = Decimal::from_parts(90, 0, 0, true, 0);
39pub const MAX_LON: Decimal = Decimal::from_parts(180, 0, 0, false, 0);
41pub const MIN_LON: Decimal = Decimal::from_parts(180, 0, 0, true, 0);
43pub const MIN_ALTITUDE: Decimal = Decimal::from_parts(6_378_137, 0, 0, true, 0);
45
46#[allow(clippy::manual_range_contains)]
47impl GeoPoint {
48 pub fn from(
58 lat: Decimal,
59 lon: Decimal,
60 alt: Option<Decimal>,
61 ) -> Result<GeoPoint, tackler::Error> {
62 if lat < MIN_LAT || MAX_LAT < lat {
63 let msg = format!("Value out of specification for Latitude: {lat}");
64 return Err(msg.into());
65 }
66 if lon < MIN_LON || MAX_LON < lon {
67 let msg = format!("Value out of specification for Longitude: {lon}");
68 return Err(msg.into());
69 }
70 if let Some(z) = alt {
71 if z < Decimal::from(-6_378_137) {
72 let msg = format!("Value Out of specification for Altitude: {z}");
74 return Err(msg.into());
75 }
76 }
77 Ok(GeoPoint { lat, lon, alt })
78 }
79}
80#[cfg(test)]
81mod tests {
82 use crate::location::GeoPoint;
83 use rust_decimal_macros::dec;
84
85 #[test]
88 fn geo_display() {
89 let tests: Vec<(GeoPoint, String)> = vec![
90 (
91 GeoPoint::from(dec!(60), dec!(24), None).unwrap(),
92 "geo:60,24".to_string(),
93 ),
94 (
95 GeoPoint::from(dec!(60), dec!(24), Some(dec!(5))).unwrap(),
96 "geo:60,24,5".to_string(),
97 ),
98 (
99 GeoPoint::from(dec!(60.167), dec!(24.955), Some(dec!(5.0))).unwrap(),
100 "geo:60.167,24.955,5.0".to_string(),
101 ),
102 (
103 GeoPoint::from(dec!(60.167000), dec!(24.955000), Some(dec!(5.000))).unwrap(),
104 "geo:60.167000,24.955000,5.000".to_string(),
105 ),
106 (
107 GeoPoint::from(dec!(-60), dec!(-24), Some(dec!(-5))).unwrap(),
108 "geo:-60,-24,-5".to_string(),
109 ),
110 (
111 GeoPoint::from(dec!(-60.167), dec!(-24.955), Some(dec!(-5.0))).unwrap(),
112 "geo:-60.167,-24.955,-5.0".to_string(),
113 ),
114 (
115 GeoPoint::from(dec!(-60.167000), dec!(-24.955000), Some(dec!(-5.000))).unwrap(),
116 "geo:-60.167000,-24.955000,-5.000".to_string(),
117 ),
118 ];
119
120 let mut count = 0;
121 let should_be_count = tests.len();
122 for t in tests {
123 let geo_str = format!("{}", t.0);
124 assert_eq!(geo_str, t.1);
125 count += 1;
126 }
127 assert_eq!(count, should_be_count);
128 }
129}