use crate::geo_utils::haversine_distance;
use crate::{GpsPoint, MatchConfig, MatchResult, RouteSignature};
pub fn compare_routes(
sig1: &RouteSignature,
sig2: &RouteSignature,
config: &MatchConfig,
) -> Option<MatchResult> {
let distance_ratio = if sig1.total_distance > sig2.total_distance {
sig2.total_distance / sig1.total_distance
} else {
sig1.total_distance / sig2.total_distance
};
if distance_ratio < 0.5 {
return None;
}
let resampled1 = resample_for_comparison(&sig1.points, sig1.total_distance, config);
let resampled2 = resample_for_comparison(&sig2.points, sig2.total_distance, config);
let amd_1_to_2 = average_min_distance(&resampled1, &resampled2);
let amd_2_to_1 = average_min_distance(&resampled2, &resampled1);
let avg_amd = (amd_1_to_2 + amd_2_to_1) / 2.0;
let match_percentage =
amd_to_percentage(avg_amd, config.perfect_threshold, config.zero_threshold);
if match_percentage < config.min_match_percentage {
return None;
}
let direction = determine_direction_by_endpoints(sig1, sig2, config.endpoint_threshold);
let direction_str = if match_percentage >= 70.0 {
direction
} else {
"partial".to_string()
};
Some(MatchResult {
activity_id_1: sig1.activity_id.clone(),
activity_id_2: sig2.activity_id.clone(),
match_percentage,
direction: direction_str,
amd: avg_amd,
})
}
pub fn resample_for_comparison(
points: &[GpsPoint],
total_distance: f64,
config: &MatchConfig,
) -> Vec<GpsPoint> {
let target_count = if config.resample_spacing_meters > 0.0 {
let count_from_distance = (total_distance / config.resample_spacing_meters).ceil() as u32;
count_from_distance.clamp(config.min_resample_points, config.max_resample_points) as usize
} else {
config.resample_count as usize
};
resample_route(points, target_count)
}
pub fn average_min_distance(route1: &[GpsPoint], route2: &[GpsPoint]) -> f64 {
if route1.is_empty() || route2.is_empty() {
return f64::INFINITY;
}
let total_min_dist: f64 = route1
.iter()
.map(|p1| {
route2
.iter()
.map(|p2| haversine_distance(p1, p2))
.fold(f64::INFINITY, f64::min)
})
.sum();
total_min_dist / route1.len() as f64
}
pub fn amd_to_percentage(amd: f64, perfect_threshold: f64, zero_threshold: f64) -> f64 {
if amd <= perfect_threshold {
return 100.0;
}
if amd >= zero_threshold {
return 0.0;
}
100.0 * (1.0 - (amd - perfect_threshold) / (zero_threshold - perfect_threshold))
}
pub fn resample_route(points: &[GpsPoint], target_count: usize) -> Vec<GpsPoint> {
if points.len() < 2 {
return points.to_vec();
}
if points.len() == target_count {
return points.to_vec();
}
let total_dist = calculate_route_distance(points);
if total_dist == 0.0 {
return points[..target_count.min(points.len())].to_vec();
}
let step_dist = total_dist / (target_count - 1) as f64;
let mut resampled: Vec<GpsPoint> = vec![points[0]];
let mut accumulated = 0.0;
let mut next_threshold = step_dist;
let mut prev_point = &points[0];
for curr in points.iter().skip(1) {
let seg_dist = haversine_distance(prev_point, curr);
while accumulated + seg_dist >= next_threshold && resampled.len() < target_count - 1 {
let ratio = (next_threshold - accumulated) / seg_dist;
let new_lat = prev_point.latitude + ratio * (curr.latitude - prev_point.latitude);
let new_lng = prev_point.longitude + ratio * (curr.longitude - prev_point.longitude);
resampled.push(GpsPoint::new(new_lat, new_lng));
next_threshold += step_dist;
}
accumulated += seg_dist;
prev_point = curr;
}
if resampled.len() < target_count {
resampled.push(*points.last().unwrap());
}
resampled
}
pub fn calculate_route_distance(points: &[GpsPoint]) -> f64 {
points
.windows(2)
.map(|w| haversine_distance(&w[0], &w[1]))
.sum()
}
pub fn determine_direction_by_endpoints(
sig1: &RouteSignature,
sig2: &RouteSignature,
loop_threshold: f64,
) -> String {
let start1 = &sig1.start_point;
let end1 = &sig1.end_point;
let start2 = &sig2.start_point;
let end2 = &sig2.end_point;
let sig1_is_loop = haversine_distance(start1, end1) < loop_threshold;
let sig2_is_loop = haversine_distance(start2, end2) < loop_threshold;
if sig1_is_loop && sig2_is_loop {
return "same".to_string();
}
let same_score = haversine_distance(start2, start1) + haversine_distance(end2, end1);
let reverse_score = haversine_distance(start2, end1) + haversine_distance(end2, start1);
let min_direction_diff = 100.0;
if reverse_score < same_score - min_direction_diff {
"reverse".to_string()
} else {
"same".to_string()
}
}