use crate::{Area, Elevation};
#[derive(PartialEq, Debug, Copy, Clone)]
#[cfg_attr(feature = "wasm", derive(serde::Serialize))]
pub struct Location {
pub longitude: f64,
pub latitude: f64,
pub altitude: f64,
}
impl Location {
pub fn calculate_distance_to(&self, to: &Location) -> f64 {
let earth_radius_kilometer = 6371.0;
let origin_latitude = self.latitude.to_radians();
let destination_latitude = to.latitude.to_radians();
let delta_latitude = (self.latitude - to.latitude).to_radians();
let delta_longitude = (self.longitude - to.longitude).to_radians();
let central_angle_inner = (delta_latitude / 2.0).sin().powi(2)
+ origin_latitude.cos()
* destination_latitude.cos()
* (delta_longitude / 2.0).sin().powi(2);
let central_angle = 2.0 * central_angle_inner.sqrt().asin();
earth_radius_kilometer * central_angle
}
pub fn calculate_bearing_to(&self, to: &Location) -> f64 {
let origin_latitude = self.latitude.to_radians();
let destination_latitude = to.latitude.to_radians();
let origin_longitude = self.longitude.to_radians();
let destination_longitude = to.longitude.to_radians();
let delta_longitude = (to.longitude - self.longitude).to_radians();
let y = delta_longitude.sin() * destination_latitude.cos();
let x = origin_latitude.cos() * destination_latitude.sin()
- origin_latitude.sin()
* destination_latitude.cos()
* (destination_longitude - origin_longitude).cos();
let teta = y.atan2(x);
((teta * 180.0) / std::f64::consts::PI + 360.0) % 360.0
}
pub fn calculate_elevation_to(&self, to: &Location) -> Elevation {
let delta = to.altitude - self.altitude;
match delta > 0.0 {
true => Elevation {
positive: delta,
negative: 0.0,
},
false => Elevation {
negative: delta.abs(),
positive: 0.0,
},
}
}
pub fn is_in_area(&self, area: &Area) -> bool {
self.longitude > area.min_longitude
&& self.longitude < area.max_longitude
&& self.latitude > area.min_latitude
&& self.latitude < area.max_latitude
}
pub fn is_in_radius(&self, location: &Location, radius: &f64) -> bool {
let distance = self.calculate_distance_to(location);
distance - radius < 0.0
}
}
#[cfg(test)]
mod tests {
use super::{Area, Location};
pub fn approx_equal(a: f64, b: f64, epsilon: f64) -> bool {
(a - b).abs() < epsilon
}
#[test]
fn calculate_distance_to_location() {
let paris = Location {
longitude: 2.350987,
latitude: 48.856667,
altitude: 0.0,
};
let moscow = Location {
longitude: 37.617634,
latitude: 55.755787,
altitude: 0.0,
};
let distance = paris.calculate_distance_to(&moscow);
assert!(approx_equal(distance, 2486.340992526076, 1e-10));
}
#[test]
fn calculate_bearing_to_location() {
let paris = Location {
longitude: 2.350987,
latitude: 48.856667,
altitude: 0.0,
};
let moscow = Location {
longitude: 37.617634,
latitude: 55.755787,
altitude: 0.0,
};
let bearing = paris.calculate_bearing_to(&moscow);
assert_eq!(bearing, 58.65519236183434);
}
#[test]
fn location_should_be_in_area() {
let paris = Location {
longitude: 2.350987,
latitude: 48.856667,
altitude: 0.0,
};
let area = Area {
max_latitude: 56.755787,
min_latitude: 54.755787,
min_longitude: 36.617634,
max_longitude: 38.617634,
};
let is_in = paris.is_in_area(&area);
assert!(!is_in);
}
#[test]
fn location_should_be_inside_area() {
let moscow = Location {
longitude: 37.617634,
latitude: 55.755787,
altitude: 0.0,
};
let area = Area {
max_latitude: 56.755787,
min_latitude: 54.755787,
min_longitude: 36.617634,
max_longitude: 38.617634,
};
let is_in = moscow.is_in_area(&area);
assert!(is_in);
}
#[test]
fn location_should_be_in_radius() {
let center = Location {
longitude: 6.23828,
latitude: 45.50127,
altitude: 0.0,
};
let location = Location {
longitude: 5.77367,
latitude: 45.07122,
altitude: 0.0,
};
let radius = 70000.00;
let is_in = location.is_in_radius(¢er, &radius);
assert!(is_in);
}
#[test]
fn calculate_elevation_to() {
let paris = Location {
longitude: 2.350987,
latitude: 48.856667,
altitude: 0.0,
};
let moscow = Location {
longitude: 37.617634,
latitude: 55.755787,
altitude: 200.0,
};
let elevation = paris.calculate_elevation_to(&moscow);
assert_eq!(200.0, elevation.positive);
assert_eq!(0.0, elevation.negative);
}
#[test]
fn calculate_elevation_to_descending() {
let moscow = Location {
longitude: 37.617634,
latitude: 55.755787,
altitude: 200.0,
};
let paris = Location {
longitude: 2.350987,
latitude: 48.856667,
altitude: 0.0,
};
let elevation = moscow.calculate_elevation_to(&paris);
assert_eq!(0.0, elevation.positive);
assert_eq!(200.0, elevation.negative);
}
}