use super::F;
use crate::LngLat;
const EARTH_RADIUS_M: f64 = 6371008.8;
#[inline]
fn normalize_longitude_difference(dlng: f64) -> f64 {
let mut normalized_dlng = dlng;
if normalized_dlng > std::f64::consts::PI {
normalized_dlng -= 2.0 * std::f64::consts::PI;
} else if normalized_dlng < -std::f64::consts::PI {
normalized_dlng += 2.0 * std::f64::consts::PI;
}
normalized_dlng
}
#[inline]
fn compute_haversine_parameter(dlat: f64, dlng: f64, lat1_rad: f64, lat2_rad: f64) -> f64 {
let half_dlat = dlat * 0.5;
let half_dlng = dlng * 0.5;
let sin_half_dlat = half_dlat.sin();
let sin_half_dlng = half_dlng.sin();
sin_half_dlat * sin_half_dlat + lat1_rad.cos() * lat2_rad.cos() * sin_half_dlng * sin_half_dlng
}
#[inline]
fn compute_central_angle(h: f64) -> f64 {
2.0 * h.sqrt().atan2((1.0 - h).sqrt())
}
#[inline]
fn apply_ellipsoidal_correction(
spherical_distance: f64,
dlat: f64,
dlng: f64,
lat1_rad: f64,
lat2_rad: f64,
) -> f64 {
let avg_lat = (lat1_rad + lat2_rad) * 0.5;
let bearing_factor = dlng.abs() / (dlat.abs() + dlng.abs() + 1e-12);
let flattening_correction = 1.0 - F * (1.0 - bearing_factor) * (avg_lat.cos().powi(2));
spherical_distance * flattening_correction
}
pub fn haversine(a: LngLat, b: LngLat) -> f64 {
let (lng1_rad, lat1_rad) = a.to_radians();
let (lng2_rad, lat2_rad) = b.to_radians();
let dlat = lat2_rad - lat1_rad;
let dlng = normalize_longitude_difference(lng2_rad - lng1_rad);
let h = compute_haversine_parameter(dlat, dlng, lat1_rad, lat2_rad);
let central_angle = compute_central_angle(h);
let spherical_distance = EARTH_RADIUS_M * central_angle;
apply_ellipsoidal_correction(spherical_distance, dlat, dlng, lat1_rad, lat2_rad)
}
pub fn haversine_km(a: LngLat, b: LngLat) -> f64 {
haversine(a, b) / 1000.0
}
pub fn haversine_miles(a: LngLat, b: LngLat) -> f64 {
haversine(a, b) / 1609.344
}
pub fn haversine_nautical(a: LngLat, b: LngLat) -> f64 {
haversine(a, b) / 1852.0
}
pub fn bearing(from: LngLat, to: LngLat) -> f64 {
let (lng1_rad, lat1_rad) = from.to_radians();
let (lng2_rad, lat2_rad) = to.to_radians();
let dlng = normalize_longitude_difference(lng2_rad - lng1_rad);
let y = dlng.sin() * lat2_rad.cos();
let x = lat1_rad.cos() * lat2_rad.sin() - lat1_rad.sin() * lat2_rad.cos() * dlng.cos();
let bearing_rad = y.atan2(x);
let bearing_deg = bearing_rad.to_degrees();
normalize_bearing_to_360(bearing_deg)
}
pub fn destination(origin: LngLat, distance_m: f64, bearing_deg: f64) -> LngLat {
let (lng_rad, lat_rad) = origin.to_radians();
let bearing_rad = bearing_deg.to_radians();
let angular_distance = distance_m / EARTH_RADIUS_M;
let lat2_rad = compute_destination_latitude(lat_rad, angular_distance, bearing_rad);
let lng2_rad =
compute_destination_longitude(lng_rad, lat_rad, lat2_rad, angular_distance, bearing_rad);
let normalized_lng2 = normalize_longitude_rad(lng2_rad);
LngLat::new_rad(normalized_lng2, lat2_rad)
}
#[inline]
fn compute_destination_latitude(lat1_rad: f64, angular_distance: f64, bearing_rad: f64) -> f64 {
let sin_lat1 = lat1_rad.sin();
let cos_lat1 = lat1_rad.cos();
let cos_angular_distance = angular_distance.cos();
let sin_angular_distance = angular_distance.sin();
let cos_bearing = bearing_rad.cos();
(sin_lat1 * cos_angular_distance + cos_lat1 * sin_angular_distance * cos_bearing).asin()
}
#[inline]
fn compute_destination_longitude(
lng1_rad: f64,
lat1_rad: f64,
lat2_rad: f64,
angular_distance: f64,
bearing_rad: f64,
) -> f64 {
let sin_bearing = bearing_rad.sin();
let sin_angular_distance = angular_distance.sin();
let cos_lat1 = lat1_rad.cos();
let y = sin_bearing * sin_angular_distance * cos_lat1;
let x = angular_distance.cos() - lat1_rad.sin() * lat2_rad.sin();
lng1_rad + y.atan2(x)
}
#[inline]
fn normalize_longitude_rad(lng_rad: f64) -> f64 {
let two_pi = 2.0 * std::f64::consts::PI;
let pi = std::f64::consts::PI;
let mut result = lng_rad % two_pi;
if result >= pi {
result -= two_pi;
} else if result < -pi {
result += two_pi;
}
result
}
#[inline]
fn normalize_bearing_to_360(bearing_deg: f64) -> f64 {
((bearing_deg % 360.0) + 360.0) % 360.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize_longitude_difference() {
assert!((normalize_longitude_difference(1.0) - 1.0).abs() < 1e-10);
assert!((normalize_longitude_difference(-1.0) - (-1.0)).abs() < 1e-10);
let input = std::f64::consts::PI + 1.0;
let expected = input - 2.0 * std::f64::consts::PI;
assert!((normalize_longitude_difference(input) - expected).abs() < 1e-10);
let input = -std::f64::consts::PI - 1.0;
let expected = input + 2.0 * std::f64::consts::PI;
assert!((normalize_longitude_difference(input) - expected).abs() < 1e-10);
assert!(
(normalize_longitude_difference(std::f64::consts::PI) - std::f64::consts::PI).abs()
< 1e-10
);
assert!(
(normalize_longitude_difference(-std::f64::consts::PI) - (-std::f64::consts::PI)).abs()
< 1e-10
);
}
#[test]
fn test_compute_haversine_parameter() {
let h = compute_haversine_parameter(0.0, 0.0, 0.0, 0.0);
assert!(h.abs() < 1e-10);
let dlat = std::f64::consts::PI / 2.0;
let h = compute_haversine_parameter(dlat, 0.0, 0.0, dlat);
let expected = (dlat / 2.0).sin().powi(2);
assert!((h - expected).abs() < 1e-10);
let dlng = std::f64::consts::PI / 2.0;
let h = compute_haversine_parameter(0.0, dlng, 0.0, 0.0);
let expected = (dlng / 2.0).sin().powi(2);
assert!((h - expected).abs() < 1e-10);
}
#[test]
fn test_compute_central_angle() {
assert!((compute_central_angle(0.0) - 0.0).abs() < 1e-10);
let angle = compute_central_angle(1.0);
assert!((angle - std::f64::consts::PI).abs() < 1e-10);
let angle = compute_central_angle(0.5);
assert!((angle - std::f64::consts::PI / 2.0).abs() < 1e-10);
}
#[test]
fn test_apply_ellipsoidal_correction() {
let base_distance = 100000.0;
let corrected_meridional = apply_ellipsoidal_correction(
base_distance,
1.0, 0.0, 0.0, 1.0, );
let corrected_equatorial = apply_ellipsoidal_correction(
base_distance,
0.0, 1.0, 0.0, 0.0, );
assert!(corrected_meridional < corrected_equatorial);
assert!(corrected_meridional < base_distance);
assert!(corrected_equatorial <= base_distance); assert!(corrected_meridional > base_distance * 0.99); }
#[test]
fn test_apply_ellipsoidal_correction_at_poles() {
let base_distance = 100000.0;
let corrected_polar = apply_ellipsoidal_correction(
base_distance,
0.1, 0.1, 1.4, 1.5, );
assert!(corrected_polar > 0.0);
assert!(corrected_polar < base_distance * 1.01); }
#[test]
fn test_haversine_identical_points() {
let point = LngLat::new_deg(-122.4194, 37.7749);
assert_eq!(haversine(point, point), 0.0);
}
#[test]
fn test_haversine_symmetry() {
let sf = LngLat::new_deg(-122.4194, 37.7749);
let nyc = LngLat::new_deg(-74.0060, 40.7128);
let d1 = haversine(sf, nyc);
let d2 = haversine(nyc, sf);
assert!((d1 - d2).abs() < 1e-10);
}
#[test]
fn test_haversine_known_distances() {
let sf = LngLat::new_deg(-122.4194, 37.7749);
let nyc = LngLat::new_deg(-74.0060, 40.7128);
let distance = haversine(sf, nyc);
assert!((distance - 4135000.0).abs() < 10000.0);
let p1 = LngLat::new_deg(0.0, 0.0);
let p2 = LngLat::new_deg(1.0, 0.0);
let distance = haversine(p1, p2);
assert!((distance - 111320.0).abs() < 1000.0);
}
#[test]
fn test_haversine_antimeridian_crossing() {
let west = LngLat::new_deg(179.5, 0.0);
let east = LngLat::new_deg(-179.5, 0.0);
let distance = haversine(west, east);
assert!(distance < 200000.0); assert!(distance > 100000.0); }
#[test]
fn test_haversine_very_small_distances() {
let base = LngLat::new_deg(0.0, 0.0);
let one_meter_north = LngLat::new_deg(0.0, 0.0 + 1.0 / 111320.0);
let distance = haversine(base, one_meter_north);
assert!((distance - 1.0).abs() < 0.1);
let ten_cm_east = LngLat::new_deg(0.0 + 0.1 / 111320.0, 0.0);
let distance = haversine(base, ten_cm_east);
assert!((distance - 0.1).abs() < 0.01);
}
#[test]
fn test_haversine_polar_regions() {
let north_1 = LngLat::new_deg(0.0, 89.9);
let north_2 = LngLat::new_deg(180.0, 89.9);
let distance = haversine(north_1, north_2);
assert!(distance < 50000.0);
let south_1 = LngLat::new_deg(45.0, -89.9);
let south_2 = LngLat::new_deg(-135.0, -89.9);
let distance = haversine(south_1, south_2);
assert!(distance < 50000.0);
}
#[test]
fn test_haversine_meridional_vs_equatorial() {
let meridional = haversine(LngLat::new_deg(0.0, 0.0), LngLat::new_deg(0.0, 1.0));
let equatorial = haversine(LngLat::new_deg(0.0, 0.0), LngLat::new_deg(1.0, 0.0));
assert!(meridional < equatorial);
assert!((meridional - equatorial).abs() < 1000.0); }
#[test]
fn test_haversine_long_distances() {
let quarter_earth = haversine(LngLat::new_deg(0.0, 0.0), LngLat::new_deg(90.0, 0.0));
let expected = std::f64::consts::PI * EARTH_RADIUS_M / 2.0;
assert!((quarter_earth - expected).abs() < 50000.0);
let near_antipodal = haversine(LngLat::new_deg(0.0, 0.0), LngLat::new_deg(179.0, 0.0));
assert!(near_antipodal > 19_000_000.0);
assert!(near_antipodal < 21_000_000.0);
}
#[test]
fn test_haversine_triangle_inequality() {
let sf = LngLat::new_deg(-122.4194, 37.7749);
let chicago = LngLat::new_deg(-87.6298, 41.8781);
let nyc = LngLat::new_deg(-74.0060, 40.7128);
let sf_chi = haversine(sf, chicago);
let chi_nyc = haversine(chicago, nyc);
let sf_nyc = haversine(sf, nyc);
assert!(sf_nyc <= sf_chi + chi_nyc + 1000.0); assert!(sf_chi <= sf_nyc + chi_nyc + 1000.0);
assert!(chi_nyc <= sf_chi + sf_nyc + 1000.0);
}
#[test]
fn test_haversine_km_known_distances() {
let sf = LngLat::new_deg(-122.4194, 37.7749);
let nyc = LngLat::new_deg(-74.0060, 40.7128);
let distance_km = haversine_km(sf, nyc);
assert!((distance_km - 4135.0).abs() < 10.0);
let p1 = LngLat::new_deg(0.0, 0.0);
let p2 = LngLat::new_deg(1.0, 0.0);
let distance_km = haversine_km(p1, p2);
assert!((distance_km - 111.32).abs() < 1.0);
}
#[test]
fn test_haversine_miles_known_distances() {
let sf = LngLat::new_deg(-122.4194, 37.7749);
let nyc = LngLat::new_deg(-74.0060, 40.7128);
let distance_miles = haversine_miles(sf, nyc);
assert!((distance_miles - 2569.0).abs() < 10.0);
let p1 = LngLat::new_deg(0.0, 0.0);
let p2 = LngLat::new_deg(1.0, 0.0);
let distance_miles = haversine_miles(p1, p2);
assert!((distance_miles - 69.17).abs() < 1.0);
}
#[test]
fn test_haversine_nautical_known_distances() {
let sf = LngLat::new_deg(-122.4194, 37.7749);
let nyc = LngLat::new_deg(-74.0060, 40.7128);
let distance_nm = haversine_nautical(sf, nyc);
assert!((distance_nm - 2233.0).abs() < 10.0);
let p1 = LngLat::new_deg(0.0, 0.0);
let p2 = LngLat::new_deg(1.0, 0.0);
let distance_nm = haversine_nautical(p1, p2);
assert!((distance_nm - 60.11).abs() < 1.0);
}
#[test]
fn test_unit_conversion_accuracy() {
let sf = LngLat::new_deg(-122.4194, 37.7749);
let nyc = LngLat::new_deg(-74.0060, 40.7128);
let meters = haversine(sf, nyc);
let km = haversine_km(sf, nyc);
let miles = haversine_miles(sf, nyc);
let nautical = haversine_nautical(sf, nyc);
assert!((km - meters / 1000.0).abs() < 1e-10);
assert!((miles - meters / 1609.344).abs() < 1e-10);
assert!((nautical - meters / 1852.0).abs() < 1e-10);
}
#[test]
fn test_unit_conversions_identical_points() {
let point = LngLat::new_deg(-122.4194, 37.7749);
assert_eq!(haversine_km(point, point), 0.0);
assert_eq!(haversine_miles(point, point), 0.0);
assert_eq!(haversine_nautical(point, point), 0.0);
}
#[test]
fn test_unit_conversions_small_distances() {
let base = LngLat::new_deg(0.0, 0.0);
let nearby = LngLat::new_deg(0.001, 0.001);
let km = haversine_km(base, nearby);
let miles = haversine_miles(base, nearby);
let nautical = haversine_nautical(base, nearby);
assert!(km > 0.0 && km < 1.0);
assert!(miles > 0.0 && miles < 1.0);
assert!(nautical > 0.0 && nautical < 1.0);
}
#[test]
fn test_unit_conversions_large_distances() {
let p1 = LngLat::new_deg(0.0, 0.0);
let p2 = LngLat::new_deg(90.0, 0.0);
let km = haversine_km(p1, p2);
let miles = haversine_miles(p1, p2);
let nautical = haversine_nautical(p1, p2);
assert!(km > 9000.0 && km < 11000.0);
assert!(miles > 5500.0 && miles < 7000.0);
assert!(nautical > 4800.0 && nautical < 6000.0);
}
#[test]
fn test_normalize_bearing_to_360() {
assert!((normalize_bearing_to_360(45.0) - 45.0).abs() < 1e-10);
assert!((normalize_bearing_to_360(359.0) - 359.0).abs() < 1e-10);
assert!((normalize_bearing_to_360(0.0) - 0.0).abs() < 1e-10);
assert!((normalize_bearing_to_360(360.0) - 0.0).abs() < 1e-10);
assert!((normalize_bearing_to_360(-45.0) - 315.0).abs() < 1e-10);
assert!((normalize_bearing_to_360(-90.0) - 270.0).abs() < 1e-10);
assert!((normalize_bearing_to_360(-180.0) - 180.0).abs() < 1e-10);
assert!((normalize_bearing_to_360(-360.0) - 0.0).abs() < 1e-10);
assert!((normalize_bearing_to_360(405.0) - 45.0).abs() < 1e-10);
assert!((normalize_bearing_to_360(720.0) - 0.0).abs() < 1e-10);
assert!((normalize_bearing_to_360(815.0) - 95.0).abs() < 1e-10);
assert!((normalize_bearing_to_360(-405.0) - 315.0).abs() < 1e-10);
assert!((normalize_bearing_to_360(-720.0) - 0.0).abs() < 1e-10);
assert!((normalize_bearing_to_360(0.1) - 0.1).abs() < 1e-10);
assert!((normalize_bearing_to_360(-0.1) - 359.9).abs() < 1e-10);
}
#[test]
fn test_bearing_identical_points() {
let point = LngLat::new_deg(-122.4194, 37.7749);
let bearing_result = bearing(point, point);
assert!((bearing_result - 0.0).abs() < 1e-10);
}
#[test]
fn test_bearing_cardinal_directions() {
let origin = LngLat::new_deg(0.0, 0.0);
let north = LngLat::new_deg(0.0, 1.0);
let bearing_north = bearing(origin, north);
assert!((bearing_north - 0.0).abs() < 1e-6);
let east = LngLat::new_deg(1.0, 0.0);
let bearing_east = bearing(origin, east);
assert!((bearing_east - 90.0).abs() < 1e-6);
let south = LngLat::new_deg(0.0, -1.0);
let bearing_south = bearing(origin, south);
assert!((bearing_south - 180.0).abs() < 1e-6);
let west = LngLat::new_deg(-1.0, 0.0);
let bearing_west = bearing(origin, west);
assert!((bearing_west - 270.0).abs() < 1e-6);
}
#[test]
fn test_bearing_intercardinal_directions() {
let origin = LngLat::new_deg(0.0, 0.0);
let northeast = LngLat::new_deg(1.0, 1.0);
let bearing_ne = bearing(origin, northeast);
assert!(bearing_ne > 40.0 && bearing_ne < 50.0);
let southeast = LngLat::new_deg(1.0, -1.0);
let bearing_se = bearing(origin, southeast);
assert!(bearing_se > 130.0 && bearing_se < 140.0);
let southwest = LngLat::new_deg(-1.0, -1.0);
let bearing_sw = bearing(origin, southwest);
assert!(bearing_sw > 220.0 && bearing_sw < 230.0);
let northwest = LngLat::new_deg(-1.0, 1.0);
let bearing_nw = bearing(origin, northwest);
assert!(bearing_nw > 310.0 && bearing_nw < 320.0);
}
#[test]
fn test_bearing_known_city_pairs() {
let sf = LngLat::new_deg(-122.4194, 37.7749);
let nyc = LngLat::new_deg(-74.0060, 40.7128);
let sf_to_nyc = bearing(sf, nyc);
assert!(sf_to_nyc > 65.0 && sf_to_nyc < 85.0);
let nyc_to_sf = bearing(nyc, sf);
assert!(nyc_to_sf > 240.0 && nyc_to_sf < 290.0);
let london = LngLat::new_deg(-0.1276, 51.5074);
let paris = LngLat::new_deg(2.3522, 48.8566);
let london_to_paris = bearing(london, paris);
assert!((london_to_paris - 146.0).abs() < 10.0);
let sydney = LngLat::new_deg(151.2093, -33.8688);
let tokyo = LngLat::new_deg(139.6917, 35.6895);
let sydney_to_tokyo = bearing(sydney, tokyo);
assert!(!(60.0..=320.0).contains(&sydney_to_tokyo));
}
#[test]
fn test_bearing_antimeridian_crossing() {
let west = LngLat::new_deg(179.0, 0.0);
let east = LngLat::new_deg(-179.0, 0.0);
let bearing_we = bearing(west, east);
assert!((bearing_we - 90.0).abs() < 5.0);
let bearing_ew = bearing(east, west);
assert!((bearing_ew - 270.0).abs() < 5.0);
let west_north = LngLat::new_deg(179.0, 10.0);
let east_south = LngLat::new_deg(-179.0, -10.0);
let bearing_crossing = bearing(west_north, east_south);
assert!(bearing_crossing > 90.0 && bearing_crossing < 180.0);
}
#[test]
fn test_bearing_polar_regions() {
let north_pole_1 = LngLat::new_deg(0.0, 89.9);
let north_pole_2 = LngLat::new_deg(180.0, 89.9);
let polar_bearing = bearing(north_pole_1, north_pole_2);
assert!((0.0..360.0).contains(&polar_bearing));
let south_pole_1 = LngLat::new_deg(45.0, -89.9);
let south_pole_2 = LngLat::new_deg(-135.0, -89.9);
let south_bearing = bearing(south_pole_1, south_pole_2);
assert!((0.0..360.0).contains(&south_bearing));
let equator = LngLat::new_deg(0.0, 0.0);
let north = LngLat::new_deg(0.0, 89.0);
let to_pole = bearing(equator, north);
assert!((to_pole - 0.0).abs() < 1.0);
}
#[test]
fn test_bearing_precision_edge_cases() {
let base = LngLat::new_deg(0.0, 0.0);
let tiny_east = LngLat::new_deg(0.0001, 0.0);
let bearing_tiny = bearing(base, tiny_east);
assert!((bearing_tiny - 90.0).abs() < 1.0);
let tiny_west = LngLat::new_deg(-0.0001, 0.0);
let bearing_tiny_w = bearing(base, tiny_west);
assert!((bearing_tiny_w - 270.0).abs() < 1.0);
let tiny_north = LngLat::new_deg(0.0, 0.0001);
let bearing_tiny_n = bearing(base, tiny_north);
assert!((bearing_tiny_n - 0.0).abs() < 1.0);
let tiny_south = LngLat::new_deg(0.0, -0.0001);
let bearing_tiny_s = bearing(base, tiny_south);
assert!((bearing_tiny_s - 180.0).abs() < 1.0);
}
#[test]
fn test_bearing_consistency_with_distance() {
let origin = LngLat::new_deg(0.0, 0.0);
let target = LngLat::new_deg(1.0, 1.0);
let bearing_value = bearing(origin, target);
let distance_value = haversine(origin, target);
assert!((0.0..360.0).contains(&bearing_value));
assert!(distance_value > 0.0);
let reverse_bearing = bearing(target, origin);
let bearing_diff = (bearing_value - reverse_bearing + 180.0) % 360.0;
assert!((bearing_diff - 0.0).abs() < 5.0 || (bearing_diff - 360.0).abs() < 5.0);
}
#[test]
fn test_bearing_mathematical_properties() {
let sf = LngLat::new_deg(-122.4194, 37.7749);
let chicago = LngLat::new_deg(-87.6298, 41.8781);
let nyc = LngLat::new_deg(-74.0060, 40.7128);
let b1 = bearing(sf, chicago);
let b2 = bearing(chicago, nyc);
let b3 = bearing(nyc, sf);
assert!((0.0..360.0).contains(&b1));
assert!((0.0..360.0).contains(&b2));
assert!((0.0..360.0).contains(&b3));
assert!((b1 - b2).abs() > 1.0);
assert!((b2 - b3).abs() > 1.0);
assert!((b3 - b1).abs() > 1.0);
}
#[test]
fn test_bearing_longitudinal_extremes() {
let origin = LngLat::new_deg(0.0, 0.0);
let max_lng = LngLat::new_deg(180.0, 0.0);
let bearing_max = bearing(origin, max_lng);
assert!((bearing_max - 90.0).abs() < 5.0);
let min_lng = LngLat::new_deg(-180.0, 0.0);
let bearing_min = bearing(origin, min_lng);
assert!((bearing_min - 270.0).abs() < 5.0);
let cross_bearing = bearing(max_lng, min_lng);
assert!((0.0..360.0).contains(&cross_bearing));
}
#[test]
fn test_bearing_latitudinal_extremes() {
let origin = LngLat::new_deg(0.0, 0.0);
let max_lat = LngLat::new_deg(0.0, 89.0);
let bearing_max = bearing(origin, max_lat);
assert!((bearing_max - 0.0).abs() < 1.0);
let min_lat = LngLat::new_deg(0.0, -89.0);
let bearing_min = bearing(origin, min_lat);
assert!((bearing_min - 180.0).abs() < 1.0);
let ns_bearing = bearing(max_lat, min_lat);
assert!((ns_bearing - 180.0).abs() < 5.0); }
#[test]
fn test_normalize_longitude_rad() {
use std::f64::consts::PI;
assert!((normalize_longitude_rad(0.0) - 0.0).abs() < 1e-12);
assert!((normalize_longitude_rad(PI / 2.0) - PI / 2.0).abs() < 1e-12);
assert!((normalize_longitude_rad(-PI / 2.0) - (-PI / 2.0)).abs() < 1e-12);
let normalized_pi = normalize_longitude_rad(PI);
assert!((normalized_pi - (-PI)).abs() < 1e-12);
let just_under_neg_pi = -PI - 0.1;
let normalized = normalize_longitude_rad(just_under_neg_pi);
assert!((normalized - (just_under_neg_pi + 2.0 * PI)).abs() < 1e-12);
let multiple_wraps = 3.0 * PI; let normalized = normalize_longitude_rad(multiple_wraps);
assert!((-PI..=PI).contains(&normalized));
assert!((normalized - (-PI)).abs() < 1e-12);
let negative_multiple_wraps = -5.0 * PI; let normalized = normalize_longitude_rad(negative_multiple_wraps);
assert!((-PI..=PI).contains(&normalized));
assert!((normalized - (-PI)).abs() < 1e-12);
}
#[test]
fn test_compute_destination_latitude() {
use std::f64::consts::PI;
let original_lat = PI / 4.0; let result = compute_destination_latitude(original_lat, 0.0, 0.0);
assert!((result - original_lat).abs() < 1e-12);
let quarter_earth_angular = PI / 2.0;
let result = compute_destination_latitude(0.0, quarter_earth_angular, 0.0);
assert!((result - PI / 2.0).abs() < 1e-10);
let result = compute_destination_latitude(0.0, quarter_earth_angular, PI);
assert!((result - (-PI / 2.0)).abs() < 1e-10);
let result_east = compute_destination_latitude(0.0, PI / 4.0, PI / 2.0);
assert!(result_east.abs() < 1e-10);
let result_west = compute_destination_latitude(0.0, PI / 4.0, 3.0 * PI / 2.0);
assert!(result_west.abs() < 1e-10);
let small_angular = 0.001; let result = compute_destination_latitude(0.0, small_angular, PI / 4.0);
assert!(result > 0.0); assert!(result < small_angular); }
#[test]
fn test_compute_destination_longitude() {
use std::f64::consts::PI;
let original_lng = PI / 3.0; let original_lat = PI / 4.0; let result =
compute_destination_longitude(original_lng, original_lat, original_lat, 0.0, 0.0);
assert!((result - original_lng).abs() < 1e-12);
let result_north = compute_destination_longitude(0.0, 0.0, PI / 4.0, PI / 4.0, 0.0);
assert!(result_north.abs() < 1e-10);
let result_south = compute_destination_longitude(0.0, 0.0, -PI / 4.0, PI / 4.0, PI);
assert!(result_south.abs() < 1e-10);
let result_east = compute_destination_longitude(0.0, 0.0, 0.0, PI / 4.0, PI / 2.0);
assert!(result_east > 0.0);
let result_west = compute_destination_longitude(0.0, 0.0, 0.0, PI / 4.0, 3.0 * PI / 2.0);
assert!(result_west < 0.0); }
#[test]
fn test_destination_zero_distance() {
let origin = LngLat::new_deg(-122.4194, 37.7749);
let result_north = destination(origin, 0.0, 0.0);
assert!((result_north.lng_deg - origin.lng_deg).abs() < 1e-12);
assert!((result_north.lat_deg - origin.lat_deg).abs() < 1e-12);
let result_east = destination(origin, 0.0, 90.0);
assert!((result_east.lng_deg - origin.lng_deg).abs() < 1e-12);
assert!((result_east.lat_deg - origin.lat_deg).abs() < 1e-12);
let result_arbitrary = destination(origin, 0.0, 123.45);
assert!((result_arbitrary.lng_deg - origin.lng_deg).abs() < 1e-12);
assert!((result_arbitrary.lat_deg - origin.lat_deg).abs() < 1e-12);
}
#[test]
fn test_destination_cardinal_directions() {
let origin = LngLat::new_deg(0.0, 0.0); let distance_m = 111320.0;
let north = destination(origin, distance_m, 0.0);
assert!(north.lng_deg.abs() < 1e-6); assert!((north.lat_deg - 1.0).abs() < 0.01);
let east = destination(origin, distance_m, 90.0);
assert!(east.lat_deg.abs() < 1e-6); assert!((east.lng_deg - 1.0).abs() < 0.01);
let south = destination(origin, distance_m, 180.0);
assert!(south.lng_deg.abs() < 1e-6); assert!((south.lat_deg + 1.0).abs() < 0.01);
let west = destination(origin, distance_m, 270.0);
assert!(west.lat_deg.abs() < 1e-6); assert!((west.lng_deg + 1.0).abs() < 0.01); }
#[test]
fn test_destination_intercardinal_directions() {
let origin = LngLat::new_deg(0.0, 0.0);
let distance_m = 111320.0 * std::f64::consts::SQRT_2;
let northeast = destination(origin, distance_m, 45.0);
assert!(northeast.lng_deg > 0.9 && northeast.lng_deg < 1.1); assert!(northeast.lat_deg > 0.9 && northeast.lat_deg < 1.1);
let southeast = destination(origin, distance_m, 135.0);
assert!(southeast.lng_deg > 0.9 && southeast.lng_deg < 1.1); assert!(southeast.lat_deg < -0.9 && southeast.lat_deg > -1.1);
let southwest = destination(origin, distance_m, 225.0);
assert!(southwest.lng_deg < -0.9 && southwest.lng_deg > -1.1); assert!(southwest.lat_deg < -0.9 && southwest.lat_deg > -1.1);
let northwest = destination(origin, distance_m, 315.0);
assert!(northwest.lng_deg < -0.9 && northwest.lng_deg > -1.1); assert!(northwest.lat_deg > 0.9 && northwest.lat_deg < 1.1); }
#[test]
fn test_destination_known_coordinates() {
let london = LngLat::new_deg(0.0, 51.5);
let northeast_1000km = destination(london, 1000000.0, 45.0);
assert!(northeast_1000km.lng_deg > 0.0 && northeast_1000km.lng_deg < 25.0);
assert!(northeast_1000km.lat_deg > 50.0 && northeast_1000km.lat_deg < 65.0);
let sydney = LngLat::new_deg(151.2093, -33.8688);
let north_2000km = destination(sydney, 2000000.0, 0.0);
assert!((north_2000km.lng_deg - sydney.lng_deg).abs() < 2.0); assert!(north_2000km.lat_deg > -25.0 && north_2000km.lat_deg < -5.0);
let paris = LngLat::new_deg(2.3522, 48.8566);
let east_1km = destination(paris, 1000.0, 90.0);
assert!(east_1km.lng_deg > paris.lng_deg); assert!((east_1km.lat_deg - paris.lat_deg).abs() < 0.01); assert!(
(east_1km.lng_deg - paris.lng_deg) > 0.005
&& (east_1km.lng_deg - paris.lng_deg) < 0.020
);
}
#[test]
fn test_destination_antimeridian_crossing() {
let near_antimeridian = LngLat::new_deg(179.5, 0.0);
let eastward = destination(near_antimeridian, 111320.0, 90.0);
assert!(eastward.lng_deg < -179.0);
assert!(eastward.lat_deg.abs() < 0.1);
let near_antimeridian_west = LngLat::new_deg(-179.5, 0.0);
let westward = destination(near_antimeridian_west, 111320.0, 270.0);
assert!(westward.lng_deg > 179.0);
assert!(westward.lat_deg.abs() < 0.1);
let crossing_origin = LngLat::new_deg(175.0, 10.0);
let cross_pacific = destination(crossing_origin, 1000000.0, 90.0);
assert!(cross_pacific.lng_deg < 0.0);
assert!(cross_pacific.lat_deg > 5.0 && cross_pacific.lat_deg < 15.0);
}
#[test]
fn test_destination_polar_regions() {
let near_north_pole = LngLat::new_deg(0.0, 89.0);
let south_from_pole = destination(near_north_pole, 111320.0, 180.0);
assert!(south_from_pole.lat_deg < 88.0);
assert!(south_from_pole.lng_deg.abs() < 5.0);
let east_from_pole = destination(near_north_pole, 50000.0, 90.0);
assert!(east_from_pole.lat_deg > 88.5); assert!(east_from_pole.lng_deg > 0.0);
let near_south_pole = LngLat::new_deg(45.0, -89.0);
let north_from_south_pole = destination(near_south_pole, 111320.0, 0.0);
assert!(north_from_south_pole.lat_deg > -88.0);
let west_from_south_pole = destination(near_south_pole, 50000.0, 270.0);
assert!(west_from_south_pole.lat_deg < -88.5); assert!(west_from_south_pole.lng_deg < 45.0); }
#[test]
fn test_destination_very_small_distances() {
let origin = LngLat::new_deg(-74.0060, 40.7128);
let one_meter_north = destination(origin, 1.0, 0.0);
assert!((one_meter_north.lng_deg - origin.lng_deg).abs() < 1e-6); assert!(one_meter_north.lat_deg > origin.lat_deg); assert!((one_meter_north.lat_deg - origin.lat_deg) < 0.0001);
let ten_cm_east = destination(origin, 0.1, 90.0);
assert!((ten_cm_east.lat_deg - origin.lat_deg).abs() < 1e-6); assert!(ten_cm_east.lng_deg > origin.lng_deg); assert!((ten_cm_east.lng_deg - origin.lng_deg) < 0.00001);
let one_mm_south = destination(origin, 0.001, 180.0);
assert!((one_mm_south.lng_deg - origin.lng_deg).abs() < 1e-8);
assert!(one_mm_south.lat_deg < origin.lat_deg);
assert!((origin.lat_deg - one_mm_south.lat_deg) < 0.00001);
}
#[test]
fn test_destination_very_large_distances() {
let origin = LngLat::new_deg(0.0, 0.0);
let quarter_earth = std::f64::consts::PI * EARTH_RADIUS_M / 2.0;
let quarter_east = destination(origin, quarter_earth, 90.0);
assert!((quarter_east.lng_deg - 90.0).abs() < 5.0); assert!(quarter_east.lat_deg.abs() < 5.0);
let quarter_earth = std::f64::consts::PI * EARTH_RADIUS_M / 2.0;
let to_pole = destination(origin, quarter_earth, 0.0);
assert!(to_pole.lat_deg > 85.0);
let nearly_antipodal = std::f64::consts::PI * EARTH_RADIUS_M * 0.99;
let far_point = destination(origin, nearly_antipodal, 45.0);
let distance_back = haversine(origin, far_point);
assert!(distance_back > 19_000_000.0);
let long_journey = destination(origin, 10_000_000.0, 30.0); assert!(long_journey.lng_deg > 80.0 && long_journey.lng_deg < 100.0); assert!(long_journey.lat_deg > 59.0); }
#[test]
fn test_destination_roundtrip_consistency() {
let origin = LngLat::new_deg(-122.4194, 37.7749); let distance_m = 500000.0; let bearing_deg = 67.5;
let destination_point = destination(origin, distance_m, bearing_deg);
let back_bearing = bearing(destination_point, origin);
let back_distance = haversine(destination_point, origin);
assert!((back_distance - distance_m).abs() < 50000.0);
let expected_back_bearing = (bearing_deg + 180.0) % 360.0;
let bearing_diff = (back_bearing - expected_back_bearing).abs();
let bearing_diff_wrapped = (360.0 - bearing_diff).min(bearing_diff);
assert!(bearing_diff_wrapped < 10.0);
let return_point = destination(destination_point, back_distance, back_bearing);
let final_distance = haversine(origin, return_point);
assert!(final_distance < 5000.0); }
#[test]
fn test_destination_multiple_roundtrips() {
let start = LngLat::new_deg(2.3522, 48.8566); let mut current = start;
let step_distance = 100000.0;
for i in 0..8 {
let bearing = (i as f64) * 45.0; current = destination(current, step_distance, bearing);
}
let final_distance = haversine(start, current);
assert!(final_distance < 50000.0);
}
#[test]
fn test_destination_bearing_consistency() {
let tests = vec![
(LngLat::new_deg(0.0, 0.0), 100000.0, 0.0), (LngLat::new_deg(0.0, 0.0), 100000.0, 90.0), (LngLat::new_deg(-74.0, 40.7), 50000.0, 45.0), (LngLat::new_deg(151.2, -33.9), 200000.0, 225.0), (LngLat::new_deg(-0.1, 51.5), 75000.0, 135.0), ];
for (origin, distance, bearing_out) in tests {
let destination_point = destination(origin, distance, bearing_out);
let calculated_bearing = bearing(origin, destination_point);
let bearing_diff = (calculated_bearing - bearing_out).abs();
let bearing_diff_wrapped = (360.0 - bearing_diff).min(bearing_diff);
assert!(
bearing_diff_wrapped < 10.0,
"Bearing mismatch: expected {}, got {}, diff {}",
bearing_out,
calculated_bearing,
bearing_diff_wrapped
);
let calculated_distance = haversine(origin, destination_point);
assert!(
(calculated_distance - distance).abs() < 10000.0,
"Distance mismatch: expected {}, got {}",
distance,
calculated_distance
);
}
}
#[test]
fn test_destination_mathematical_properties() {
let origin = LngLat::new_deg(-87.6298, 41.8781); let distance = 1000000.0;
for bearing in [
0.0, 30.0, 60.0, 90.0, 120.0, 150.0, 180.0, 210.0, 240.0, 270.0, 300.0, 330.0,
] {
let dest = destination(origin, distance, bearing);
assert!(
dest.lng_deg >= -180.0 && dest.lng_deg <= 180.0,
"Invalid longitude: {} at bearing {}",
dest.lng_deg,
bearing
);
assert!(
dest.lat_deg >= -90.0 && dest.lat_deg <= 90.0,
"Invalid latitude: {} at bearing {}",
dest.lat_deg,
bearing
);
let actual_distance = haversine(origin, dest);
assert!(
(actual_distance - distance).abs() < 200000.0,
"Distance error: expected {}, got {} at bearing {}",
distance,
actual_distance,
bearing
);
}
}
#[test]
fn test_destination_boundary_conditions() {
let extreme_cases = vec![
(LngLat::new_deg(180.0, 0.0), 1000.0, 90.0), (LngLat::new_deg(-180.0, 0.0), 1000.0, 270.0), (LngLat::new_deg(0.0, 89.99), 1000.0, 0.0), (LngLat::new_deg(0.0, -89.99), 1000.0, 180.0), (LngLat::new_deg(179.99, 89.99), 1000.0, 225.0), (LngLat::new_deg(-179.99, -89.99), 1000.0, 45.0), ];
for (origin, distance, bearing) in extreme_cases {
let dest = destination(origin, distance, bearing);
assert!(dest.lng_deg >= -180.0 && dest.lng_deg <= 180.0);
assert!(dest.lat_deg >= -90.0 && dest.lat_deg <= 90.0);
if distance > 0.0 {
let moved_distance = haversine(origin, dest);
assert!(moved_distance > 0.0);
}
}
}
#[test]
fn test_destination_precision_consistency() {
let origin = LngLat::new_deg(0.0, 0.0);
let base_distance = 1000.0;
let bearing1 = 90.0;
let bearing2 = 90.001;
let dest1 = destination(origin, base_distance, bearing1);
let dest2 = destination(origin, base_distance, bearing2);
let position_difference = haversine(dest1, dest2);
assert!(position_difference < 1.0); assert!(position_difference > 0.0);
let distance1 = 1000.0;
let distance2 = 1001.0;
let dest_d1 = destination(origin, distance1, 45.0);
let dest_d2 = destination(origin, distance2, 45.0);
let distance_difference = haversine(dest_d1, dest_d2);
assert!((distance_difference - 1.0).abs() < 0.1); }
}