pub fn calculate_distance_probability(distance_meters: f64, distance_scale: f64) -> f64 {
if distance_scale <= 0.0 {
if distance_meters <= 0.0 {
1.0
} else {
0.0
}
} else {
(-distance_meters / distance_scale).exp()
}
}
pub fn calculate_heading_probability(
heading_difference_degrees: f64,
heading_scale: f64,
heading_cutoff_degrees: f64,
) -> f64 {
if heading_difference_degrees > heading_cutoff_degrees {
0.0
} else if heading_scale <= 0.0 {
if heading_difference_degrees <= 0.0 {
1.0
} else {
0.0
}
} else {
(-heading_difference_degrees / heading_scale).exp()
}
}
pub fn calculate_combined_probability(distance_probability: f64, heading_probability: f64) -> f64 {
distance_probability * heading_probability
}
pub fn calculate_transition_probability(
route_distance: f64,
great_circle_distance: f64,
beta: f64,
) -> f64 {
if beta <= 0.0 {
if (route_distance - great_circle_distance).abs() < 1e-9 {
1.0
} else {
0.0
}
} else {
(-(route_distance - great_circle_distance).abs() / beta).exp()
}
}
pub fn is_near_netelement_edge(
projected_point: &geo::Point<f64>,
netelement_geometry: &geo::LineString<f64>,
edge_zone_distance: f64,
) -> bool {
use geo::HaversineDistance;
let coords = netelement_geometry.coords().collect::<Vec<_>>();
if coords.is_empty() {
return true; }
let start = geo::Point::new(coords[0].x, coords[0].y);
let end = geo::Point::new(coords[coords.len() - 1].x, coords[coords.len() - 1].y);
let dist_to_start = projected_point.haversine_distance(&start);
let dist_to_end = projected_point.haversine_distance(&end);
dist_to_start.min(dist_to_end) <= edge_zone_distance
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_distance_probability_at_scale() {
let p = calculate_distance_probability(100.0, 100.0);
assert!((p - 0.36787944).abs() < 0.0001);
}
#[test]
fn test_distance_probability_zero_distance() {
let p = calculate_distance_probability(0.0, 100.0);
assert_eq!(p, 1.0);
}
#[test]
fn test_distance_probability_large_distance() {
let p = calculate_distance_probability(1000.0, 100.0);
assert!(p < 0.001);
}
#[test]
fn test_heading_probability_at_scale() {
let p = calculate_heading_probability(45.0, 45.0, 90.0);
assert!((p - 0.36787944).abs() < 0.0001);
}
#[test]
fn test_heading_probability_zero_difference() {
let p = calculate_heading_probability(0.0, 45.0, 90.0);
assert_eq!(p, 1.0);
}
#[test]
fn test_heading_probability_exceeds_cutoff() {
let p = calculate_heading_probability(100.0, 45.0, 90.0);
assert_eq!(p, 0.0);
}
#[test]
fn test_heading_probability_at_cutoff_boundary() {
let p = calculate_heading_probability(90.0, 45.0, 90.0);
assert!(p > 0.0); let p_exact = calculate_heading_probability(90.0, 45.0, 90.0);
let expected = (-90.0_f64 / 45.0_f64).exp();
assert!((p_exact - expected).abs() < 0.0001);
}
#[test]
fn test_combined_probability() {
let combined = calculate_combined_probability(0.8, 0.75);
assert!((combined - 0.6).abs() < 0.0001); }
#[test]
fn test_combined_probability_zero_component() {
let combined = calculate_combined_probability(0.8, 0.0);
assert_eq!(combined, 0.0);
}
#[test]
fn test_transition_probability_perfect_match() {
let p = calculate_transition_probability(100.0, 100.0, 50.0);
assert!((p - 1.0).abs() < 1e-9);
}
#[test]
fn test_transition_probability_at_beta() {
let p = calculate_transition_probability(150.0, 100.0, 50.0);
assert!((p - 0.36787944).abs() < 0.0001);
}
#[test]
fn test_transition_probability_large_mismatch() {
let p = calculate_transition_probability(500.0, 100.0, 50.0);
assert!(p < 0.001);
}
#[test]
fn test_transition_probability_symmetric() {
let p1 = calculate_transition_probability(80.0, 100.0, 50.0);
let p2 = calculate_transition_probability(120.0, 100.0, 50.0);
assert!((p1 - p2).abs() < 1e-9);
}
#[test]
fn test_near_edge_at_start() {
let line = geo::LineString::from(vec![(3.0, 50.0), (3.001, 50.0), (3.002, 50.0)]);
let point = geo::Point::new(3.0001, 50.0);
assert!(is_near_netelement_edge(&point, &line, 50.0));
}
#[test]
fn test_near_edge_at_end() {
let line = geo::LineString::from(vec![(3.0, 50.0), (3.001, 50.0), (3.002, 50.0)]);
let point = geo::Point::new(3.0019, 50.0);
assert!(is_near_netelement_edge(&point, &line, 50.0));
}
#[test]
fn test_not_near_edge_interior() {
let line = geo::LineString::from(vec![(3.0, 50.0), (3.005, 50.0), (3.01, 50.0)]);
let point = geo::Point::new(3.005, 50.0);
assert!(!is_near_netelement_edge(&point, &line, 50.0));
}
#[test]
fn test_near_edge_empty_geometry() {
let line = geo::LineString::from(Vec::<(f64, f64)>::new());
let point = geo::Point::new(3.0, 50.0);
assert!(is_near_netelement_edge(&point, &line, 50.0));
}
}