From 0f00ed23fec3d925e1b46322fc1e01050e754e1f Mon Sep 17 00:00:00 2001 From: omi Date: Sun, 26 Oct 2025 22:11:20 +0530 Subject: [PATCH 1/3] added rhumb line distance algorithm to navigation module --- DIRECTORY.md | 1 + src/navigation/mod.rs | 5 ++- src/navigation/rhumbline.rs | 85 +++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/navigation/rhumbline.rs diff --git a/DIRECTORY.md b/DIRECTORY.md index 7a800e41379..4024c1c8eb0 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -262,6 +262,7 @@ * Navigation * [Bearing](https://github.com/TheAlgorithms/Rust/blob/master/src/navigation/bearing.rs) * [Haversine](https://github.com/TheAlgorithms/Rust/blob/master/src/navigation/haversine.rs) + * [Rhumbline](https://github.com/TheAlgorithms/Rust/blob/master/src/navigation/rhumbline.rs) * Number Theory * [Compute Totient](https://github.com/TheAlgorithms/Rust/blob/master/src/number_theory/compute_totient.rs) * [Euler Totient](https://github.com/TheAlgorithms/Rust/blob/master/src/number_theory/euler_totient.rs) diff --git a/src/navigation/mod.rs b/src/navigation/mod.rs index e62be90acbc..515396899c8 100644 --- a/src/navigation/mod.rs +++ b/src/navigation/mod.rs @@ -1,5 +1,8 @@ mod bearing; mod haversine; - +mod rhumbline; pub use self::bearing::bearing; pub use self::haversine::haversine; +pub use self::rhumbline::rhumb_bearing; +pub use self::rhumbline::rhumb_destination; +pub use self::rhumbline::rhumb_dist; diff --git a/src/navigation/rhumbline.rs b/src/navigation/rhumbline.rs new file mode 100644 index 00000000000..3127b3d7dc7 --- /dev/null +++ b/src/navigation/rhumbline.rs @@ -0,0 +1,85 @@ +use std::f64::consts::PI; + +const EARTH_RADIUS: f64 = 6371000.0; + +pub fn rhumb_dist(lat1: f64, long1: f64, lat2: f64, long2: f64) -> f64 { + let phi1 = lat1 * PI / 180.00; + let phi2 = lat2 * PI / 180.00; + let del_phi = phi2 - phi1; + let mut del_lambda = (long2 - long1) * PI / 180.00; + + if del_lambda > PI { + del_lambda -= 2.00 * PI; + } else if del_lambda < -PI { + del_lambda += 2.00 * PI; + } + + let del_psi = ((phi2 / 2.00 + PI / 4.00).tan() / (phi1 / 2.00 + PI / 4.00).tan()).ln(); + let q = match del_psi.abs() > 1e-12 { + true => del_phi / del_psi, + false => phi1.cos(), + }; + + (del_phi.powf(2.00) + (q * del_lambda).powf(2.00)).sqrt() * EARTH_RADIUS +} + +pub fn rhumb_bearing(lat1: f64, long1: f64, lat2: f64, long2: f64) -> f64 { + let phi1 = lat1 * PI / 180.00; + let phi2 = lat2 * PI / 180.00; + let mut del_lambda = (long2 - long1) * PI / 180.00; + + if del_lambda > PI { + del_lambda -= 2.00 * PI; + } else if del_lambda < -PI { + del_lambda += 2.00 * PI; + } + + let del_psi = ((phi2 / 2.00 + PI / 4.00).tan() / (phi1 / 2.00 + PI / 4.00).tan()).ln(); + let bearing = del_lambda.atan2(del_psi) * 180.0 / PI; + (bearing + 360.00) % 360.00 +} +pub fn rhumb_destination(lat: f64, long: f64, distance: f64, bearing: f64) -> (f64, f64) { + let del = distance / EARTH_RADIUS; + let phi1 = lat * PI / 180.00; + let lambda1 = long * PI / 180.00; + let theta = bearing * PI / 180.00; + + let del_phi = del * theta.cos(); + let phi2 = (phi1 + del_phi).clamp(-PI / 2.0, PI / 2.0); + + let del_psi = ((phi2 / 2.00 + PI / 4.00).tan() / (phi1 / 2.0 + PI / 4.0).tan()).ln(); + let q = match del_psi.abs() > 1e-12 { + true => del_phi / del_psi, + false => phi1.cos(), + }; + + let del_lambda = del * theta.sin() / q; + let lambda2 = lambda1 + del_lambda; + + (phi2 * 180.00 / PI, lambda2 * 180.00 / PI) +} + +//TESTS +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rhumb_distance() { + let distance = rhumb_dist(28.5416, 77.2006, 28.5457, 77.1928); + assert!(distance > 700.00 && distance < 1000.0); + } + + #[test] + fn test_rhumb_bearing() { + let bearing = rhumb_bearing(28.5416, 77.2006, 28.5457, 77.1928); + assert!((bearing - 300.0).abs() < 5.0); + } + + #[test] + fn test_rhumb_destination_point() { + let (lat, lng) = rhumb_destination(28.5457, 77.1928, 1000.00, 305.0); + assert!((lat - 28.550).abs() < 0.010); + assert!((lng - 77.1851).abs() < 0.010); + } +} From bc8cf5128758e367446ad7b685c3ba66ee90e685 Mon Sep 17 00:00:00 2001 From: omi Date: Sun, 26 Oct 2025 22:24:42 +0530 Subject: [PATCH 2/3] fixing clippy warnings --- src/navigation/rhumbline.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/navigation/rhumbline.rs b/src/navigation/rhumbline.rs index 3127b3d7dc7..7524b15294f 100644 --- a/src/navigation/rhumbline.rs +++ b/src/navigation/rhumbline.rs @@ -15,9 +15,10 @@ pub fn rhumb_dist(lat1: f64, long1: f64, lat2: f64, long2: f64) -> f64 { } let del_psi = ((phi2 / 2.00 + PI / 4.00).tan() / (phi1 / 2.00 + PI / 4.00).tan()).ln(); - let q = match del_psi.abs() > 1e-12 { - true => del_phi / del_psi, - false => phi1.cos(), + let q = if del_psi.abs() > 1e-12 { + del_phi / del_psi + } else { + phi1.cos() }; (del_phi.powf(2.00) + (q * del_lambda).powf(2.00)).sqrt() * EARTH_RADIUS @@ -29,9 +30,9 @@ pub fn rhumb_bearing(lat1: f64, long1: f64, lat2: f64, long2: f64) -> f64 { let mut del_lambda = (long2 - long1) * PI / 180.00; if del_lambda > PI { - del_lambda -= 2.00 * PI; + del_lambda -= 2.0 * PI; } else if del_lambda < -PI { - del_lambda += 2.00 * PI; + del_lambda += 2.0 * PI; } let del_psi = ((phi2 / 2.00 + PI / 4.00).tan() / (phi1 / 2.00 + PI / 4.00).tan()).ln(); @@ -48,9 +49,10 @@ pub fn rhumb_destination(lat: f64, long: f64, distance: f64, bearing: f64) -> (f let phi2 = (phi1 + del_phi).clamp(-PI / 2.0, PI / 2.0); let del_psi = ((phi2 / 2.00 + PI / 4.00).tan() / (phi1 / 2.0 + PI / 4.0).tan()).ln(); - let q = match del_psi.abs() > 1e-12 { - true => del_phi / del_psi, - false => phi1.cos(), + let q = if del_psi.abs() > 1e-12 { + del_phi / del_psi + } else { + phi1.cos() }; let del_lambda = del * theta.sin() / q; From 357c4541b1c8e261cea68b8272643888113943de Mon Sep 17 00:00:00 2001 From: omi Date: Sun, 26 Oct 2025 22:34:45 +0530 Subject: [PATCH 3/3] added additional tests for more coverage of edge cases (antimeridian crossing, equator distance etc.) --- src/navigation/rhumbline.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/navigation/rhumbline.rs b/src/navigation/rhumbline.rs index 7524b15294f..7f5e14d7257 100644 --- a/src/navigation/rhumbline.rs +++ b/src/navigation/rhumbline.rs @@ -61,7 +61,7 @@ pub fn rhumb_destination(lat: f64, long: f64, distance: f64, bearing: f64) -> (f (phi2 * 180.00 / PI, lambda2 * 180.00 / PI) } -//TESTS +// TESTS #[cfg(test)] mod tests { use super::*; @@ -84,4 +84,26 @@ mod tests { assert!((lat - 28.550).abs() < 0.010); assert!((lng - 77.1851).abs() < 0.010); } + // edge cases + + #[test] + fn test_rhumb_distance_cross_antimeridian() { + // Test when del_lambda > PI (line 12) + let distance = rhumb_dist(0.0, 170.0, 0.0, -170.0); + assert!(distance > 0.0); + } + + #[test] + fn test_rhumb_distance_cross_antimeridian_negative() { + // Test when del_lambda < -PI (line 14) + let distance = rhumb_dist(0.0, -170.0, 0.0, 170.0); + assert!(distance > 0.0); + } + + #[test] + fn test_rhumb_distance_to_equator() { + // Test when del_psi is near zero (line 21 - the else branch) + let distance = rhumb_dist(0.0, 0.0, 0.0, 1.0); + assert!(distance > 0.0); + } }