use crate::{
constants::EARTH_RADIUS, latlng::LatLng, point::Point, rectangle::Rectangle, utils::deg_to_rad,
};
pub fn point_to_point_distance_cosine(start_point: Point, end_point: Point) -> f64 {
(start_point.y.sin() * end_point.y.sin()
+ start_point.y.cos() * end_point.y.cos() * (start_point.x - end_point.x).cos())
.acos()
* EARTH_RADIUS
}
pub fn point_to_point_haversine(start: LatLng, end: LatLng) -> f64 {
let start_radian = start.convert_to_radian();
let end_radian = end.convert_to_radian();
let dif_lat = end_radian.lat - start_radian.lat;
let dif_lng = end_radian.lng - start_radian.lng;
let a = (dif_lat / 2.0).sin() * (dif_lat / 2.0).sin()
+ start_radian.lat.cos()
* end_radian.lat.cos()
* (dif_lng / 2.0).sin()
* (dif_lng / 2.0).sin();
let c = 2.0 * (a.sqrt().atan2((1.0 - a).sqrt()));
let d = EARTH_RADIUS * c;
return d;
}
pub fn point_to_point_cartesian_distance(source: Point, target: Point) -> f64 {
((target.x - source.x).powi(2) + (target.y - source.y).powi(2)).sqrt()
}
pub fn point_to_rectangle_distance(p: Point, r: Rectangle) -> f64 {
let pre_max_x = (r.min_point.x - p.x).max(0.0);
let dx = (pre_max_x).max(p.x - r.max_point.x);
let pre_max_y = (r.min_point.y - p.y).max(0.0);
let dy = (pre_max_y).max(p.y - r.max_point.y);
return (dx * dx + dy * dy).sqrt();
}
pub fn point_to_rectangle_distance_geographic(l: LatLng, b: Rectangle) -> f64 {
let converted = l.convert_to_radian();
let c_as_point = converted.convert_to_point();
if check_between_longs(&l, &b) {
if l.lat < b.min_point.y {
return point_to_point_distance_cosine(
c_as_point,
Point {
x: c_as_point.x,
y: deg_to_rad(b.min_point.y),
},
);
}
if l.lat > b.max_point.y {
return point_to_point_distance_cosine(
c_as_point,
Point {
x: c_as_point.x,
y: deg_to_rad(b.max_point.y),
},
);
}
return 0.0;
}
if l.lng < b.min_point.x {
if l.lat > b.max_point.y {
return point_to_point_distance_cosine(
c_as_point,
Point {
x: deg_to_rad(b.min_point.x),
y: deg_to_rad(b.max_point.y),
},
);
}
if l.lat < b.min_point.y {
return point_to_point_distance_cosine(
c_as_point,
Point {
x: deg_to_rad(b.min_point.x),
y: deg_to_rad(b.min_point.y),
},
);
}
return point_to_point_distance_cosine(
c_as_point,
Point {
x: deg_to_rad(b.min_point.x),
y: c_as_point.y,
},
);
}
if l.lat > b.max_point.y {
return point_to_point_distance_cosine(
c_as_point,
Point {
x: deg_to_rad(b.max_point.x),
y: deg_to_rad(b.max_point.y),
},
);
}
if l.lat < b.min_point.y {
return point_to_point_distance_cosine(
c_as_point,
Point {
x: deg_to_rad(b.max_point.x),
y: deg_to_rad(b.min_point.y),
},
);
}
return point_to_point_distance_cosine(
c_as_point,
Point {
x: deg_to_rad(b.max_point.x),
y: c_as_point.y,
},
);
}
fn check_between_longs(l: &LatLng, b: &Rectangle) -> bool {
if l.lng >= b.min_point.x && l.lng <= b.max_point.x {
return true;
}
return false;
}
pub fn mid_point_geographic_coordinates(a: LatLng, b: LatLng) -> LatLng {
let bx = b.lat.cos() * (b.lng - a.lng).cos();
let by = b.lat.cos() * (b.lng - a.lng).sin();
let lng_mid = a.lng + by.atan2(a.lat.cos() + bx); let lat_mid = (a.lat.sin() + b.lat.sin())
.atan2(((a.lat.cos() + bx) * (a.lat.cos() + bx) + by * by).sqrt());
LatLng {
lng: lng_mid,
lat: lat_mid,
}
}
pub fn find_bearing(start_point: Point, end_point: Point) -> f64 {
let y = (end_point.x - start_point.x).sin() * end_point.y.cos();
let x = start_point.y.cos() * end_point.y.sin()
- start_point.y.sin() * end_point.y.cos() * (end_point.x - start_point.x).cos();
y.atan2(x)
}
#[cfg(test)]
mod tests {
use crate::utils::deg_to_rad;
use super::*;
#[test]
fn test_point_to_point_distance_cosine() {
let start_lat = 51.27936;
let start_lng = 1.07263;
let end_lat = 51.27741;
let end_lng = 1.07281;
let start_point = Point {
x: deg_to_rad(start_lng),
y: deg_to_rad(start_lat),
};
let end_point = Point {
x: deg_to_rad(end_lng),
y: deg_to_rad(end_lat),
};
let distance = point_to_point_distance_cosine(start_point, end_point);
assert_eq!(distance.round(), 217.0);
}
#[test]
fn test_point_to_point_distance_haversine() {
let start_lat = 51.27936;
let start_lng = 1.07263;
let end_lat = 51.27741;
let end_lng = 1.07281;
let start_lat_lng = LatLng {
lat: start_lat,
lng: start_lng,
};
let end_lat_lng = LatLng {
lat: end_lat,
lng: end_lng,
};
let distance = point_to_point_haversine(start_lat_lng, end_lat_lng);
assert_eq!(distance.round(), 217.0);
}
#[test]
fn test_point_to_bounding_box_geographic() {
let test_bbox = Rectangle {
min_point: Point {
x: 1.070678,
y: 51.279336,
},
max_point: Point {
x: 1.071720,
y: 51.277559,
},
};
let start_point = LatLng {
lat: 51.27936,
lng: 1.07263,
};
let distance = point_to_rectangle_distance_geographic(start_point, test_bbox);
assert_eq!(distance.round(), 210.0);
}
}