use crate::geom::Point;
use std::f64::consts::PI;
use super::PointType;
const EARTH_RADIUS_METERS: f64 = 6371000.0;
const EQUATOR_HALF: f64 = 20037508.34;
pub fn get_bearing(pt1: &PointType, pt2: &PointType) -> f64 {
let lon1 = pt1.x();
let lat1 = pt1.y();
let lon2 = pt2.x();
let lat2 = pt2.y();
let λ1 = lon1 * PI / 180.0;
let φ1 = lat1 * PI / 180.0;
let λ2 = lon2 * PI / 180.0;
let φ2 = lat2 * PI / 180.0;
let y = (λ2 - λ1).sin() * φ2.cos();
let x = φ1.cos() * φ2.sin() - φ1.sin() * φ2.cos() * (λ2 - λ1).cos();
let θ = y.atan2(x);
θ * 180.0 / PI
}
pub fn convert_epsg4326_to_3857(lon: f64, lat: f64) -> (f64, f64) {
let x = lon * EQUATOR_HALF / 180.0;
let y = ((90.0+lat)*PI/360.0).tan().ln() / (PI / 180.0);
let y = y * EQUATOR_HALF / 180.0;
(x, y)
}
pub fn convert_epsg3857_to_4326(lat: f64, lng: f64) -> (f64, f64) {
let new_lat = lat * 180.0 / EQUATOR_HALF;
let new_lng = (lng * PI / EQUATOR_HALF).exp().atan() * 360.0 / PI - 90.0;
(new_lat, new_lng)
}
pub fn gc_distance_pt(src: &PointType, dst: &PointType) -> f64 {
gc_distance(src.x(), src.y(), dst.x(), dst.y())
}
pub fn gc_distance(src_lon: f64, src_lat: f64, dst_lon: f64, dst_lat: f64) -> f64 {
let lat1: f64 = src_lat.to_radians();
let lat2 = dst_lat.to_radians();
let diff_lat = (dst_lat - src_lat).to_radians();
let diff_lon = (dst_lon - src_lon).to_radians();
let a = f64::powi(f64::sin(diff_lat / 2.0), 2) + f64::cos(lat1)*f64::cos(lat2)*f64::powi(f64::sin(diff_lon/2.0), 2);
let c = 2.0 * f64::atan2(f64::sqrt(a), f64::sqrt(1.0 - a));
c * EARTH_RADIUS_METERS
}
#[cfg(test)]
mod tests {
use crate::geom::{new_point, SRID};
use super::*;
#[test]
fn test_get_bearing() {
let pt_from = new_point(35.90434, 56.89028, Some(SRID::WGS84));
let pt_to = new_point(35.90430, 56.89033, Some(SRID::WGS84));
let bearing = get_bearing(&pt_from, &pt_to);
let correct_bearing = -23.605068777443574;
assert!(
(bearing - correct_bearing).abs() < 0.001,
"Bearing should be {}, but got {}",
correct_bearing,
bearing
);
}
#[test]
fn test_epsg_converter() {
let precision = 10e-5;
let given_point_4326 = new_point(37.61655751319856, 55.75163877328629, Some(SRID::WGS84));
let expected_point_3857 = new_point(4187456.027182254, 7509131.996742569, None);
let ans_point_3857 = convert_epsg4326_to_3857(given_point_4326.x(), given_point_4326.y());
assert!((expected_point_3857.x() - ans_point_3857.0).abs() < precision, "Wrong X (longitude) in EPSG:3857. Should: {}. Got: {}", expected_point_3857.x(), ans_point_3857.0);
assert!((expected_point_3857.y() - ans_point_3857.1).abs() < precision, "Wrong Y (latitude) in EPSG:3857. Should: {}. Got: {}", expected_point_3857.y(), ans_point_3857.1);
let ans_reversed_point_4326 = convert_epsg3857_to_4326(ans_point_3857.0, ans_point_3857.1);
assert!((given_point_4326.x() - ans_reversed_point_4326.0).abs() < precision, "Wrong X (longitude) in EPSG:3857. Should: {}. Got: {}", given_point_4326.x(), ans_reversed_point_4326.0);
assert!((given_point_4326.y() - ans_reversed_point_4326.1).abs() < precision, "Wrong Y (latitude) in EPSG:3857. Should: {}. Got: {}", given_point_4326.y(), ans_reversed_point_4326.1);
let given_line_4326 = vec![
new_point(37.61655751319856, 55.75163877328629, Some(SRID::WGS84)),
new_point(37.61617406590727, 55.751456041561624, Some(SRID::WGS84)),
];
let expected_line_3857 = vec![
new_point(4187456.027182254, 7509131.996742569, None),
new_point(4187413.342025048, 7509095.852052931, None),
];
let ans_line_3857: Vec<PointType> = given_line_4326.iter().map(|pt| {
let (x, y) = convert_epsg4326_to_3857(pt.x(), pt.y());
new_point(x, y, None)
}).collect();
for (i, pt) in ans_line_3857.iter().enumerate() {
assert!((expected_line_3857[i].x() - pt.x()).abs() < precision,
"Wrong X (longitude) in EPSG:3857 at pos #{}. Should: {}. Got: {}", i, expected_line_3857[i].x(), pt.x());
assert!((expected_line_3857[i].y() - pt.y()).abs() < precision,
"Wrong Y (latitude) in EPSG:3857 at pos #{}. Should: {}. Got: {}", i, expected_line_3857[i].y(), pt.y());
}
let ans_reversed_line_4326: Vec<PointType> = ans_line_3857.iter().map(|pt| {
let (x, y) = convert_epsg3857_to_4326(pt.x(), pt.y());
new_point(x, y, None)
}).collect();
for (i, pt) in ans_reversed_line_4326.iter().enumerate() {
assert!((given_line_4326[i].x() - pt.x()).abs() < precision,
"Wrong X (longitude) in EPSG:3857 at pos #{}. Should: {}. Got: {}", i, given_line_4326[i].x(), pt.x());
assert!((given_line_4326[i].y() - pt.y()).abs() < precision,
"Wrong Y (latitude) in EPSG:3857 at pos #{}. Should: {}. Got: {}", i, given_line_4326[i].y(), pt.y());
}
}
#[test]
fn test_gc_distance() {
let correct_distance = 634430.92026;
let distance = gc_distance(37.61556, 55.75222, 30.31413, 59.93863);
assert!(
(distance - correct_distance).abs() < 0.001,
"Distance should be {}, but got {}",
correct_distance,
distance
);
let distance = gc_distance_pt(&new_point(37.61556, 55.75222, Some(SRID::WGS84)), &new_point(30.31413, 59.93863, Some(SRID::WGS84)));
assert!(
(distance - correct_distance).abs() < 0.001,
"Distance should be {}, but got {}",
correct_distance,
distance
);
}
}