rmpca 0.1.1

Enterprise-grade route optimization engine — Chinese Postman Problem solver with Eulerian circuit detection, Lean 4 FFI boundary, and property-based testing
Documentation
//! Spatial calculation utilities.
//!
//! Provides Haversine distance and bearing calculations for geographic coordinates.
//!
//! // Aligns with `Lean4`: theorem `haversine_distance_nonneg`, theorem `haversine_symmetric`

use crate::geo::types::Coordinate;

/// Calculate the Haversine distance between two coordinates in meters.
///
/// // Aligns with `Lean4`: theorem `haversine_distance_nonneg`
/// // Aligns with `Lean4`: theorem `haversine_distance_symmetric`
#[must_use]
pub fn coord_distance(a: &Coordinate, b: &Coordinate) -> f64 {
    const R: f64 = 6_371_000.0; // Earth radius in meters

    let lat1 = a.lat.to_radians();
    let lat2 = b.lat.to_radians();
    let dlat = lat2 - lat1;
    let dlon = (b.lon - a.lon).to_radians();

    let sin_dlat = (dlat / 2.0).sin();
    let sin_dlon = (dlon / 2.0).sin();

    let a_val = sin_dlat * sin_dlat + lat1.cos() * lat2.cos() * sin_dlon * sin_dlon;
    let c = 2.0 * a_val.sqrt().atan2((1.0 - a_val).sqrt());

    R * c
}

/// Calculate the bearing from coordinate a to coordinate b in degrees [0, 360).
#[allow(dead_code)]
#[must_use]
pub fn bearing(a: &Coordinate, b: &Coordinate) -> f64 {
    let lat1 = a.lat.to_radians();
    let lat2 = b.lat.to_radians();
    let lon_diff = (b.lon - a.lon).to_radians();

    let x = lon_diff.sin() * lat2.cos();
    let y = lat1.cos() * lat2.sin() - lat1.sin() * lat2.cos() * lon_diff.cos();

    (x.atan2(y).to_degrees() + 360.0) % 360.0
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_coord_distance_symmetry() {
        let a = Coordinate::new(45.5, -73.6);
        let b = Coordinate::new(45.6, -73.7);
        let d1 = coord_distance(&a, &b);
        let d2 = coord_distance(&b, &a);
        assert!((d1 - d2).abs() < 1e-6, "distance should be symmetric");
    }

    #[test]
    fn test_coord_distance_to_self() {
        let a = Coordinate::new(45.5, -73.6);
        let d = coord_distance(&a, &a);
        assert!(d.abs() < 1e-6, "distance to self should be ~0");
    }

    #[test]
    fn test_known_distance() {
        // NYC to LA ~3944 km
        let nyc = Coordinate::new(40.7128, -74.0060);
        let la = Coordinate::new(34.0522, -118.2437);
        let d = coord_distance(&nyc, &la);
        assert!((d - 3944000.0).abs() < 50000.0, "NYC-LA should be ~3944km: {d}m");
    }
}