#[cfg(feature = "parallel")]
use rayon::prelude::*;
use super::rtree::{build_rtree, IndexedPoint};
use crate::GpsPoint;
use rstar::{PointDistance, RTree};
use std::collections::HashMap;
const TRACE_PROXIMITY_THRESHOLD: f64 = 50.0;
const MIN_TRACE_POINTS: usize = 3;
fn extract_activity_trace(
track: &[GpsPoint],
section_polyline: &[GpsPoint],
polyline_tree: &RTree<IndexedPoint>,
) -> Vec<GpsPoint> {
if track.len() < MIN_TRACE_POINTS || section_polyline.len() < 2 {
return Vec::new();
}
let threshold_deg = (TRACE_PROXIMITY_THRESHOLD * 1.2) / 111_000.0;
let threshold_deg_sq = threshold_deg * threshold_deg;
let mut sequences: Vec<Vec<GpsPoint>> = Vec::new();
let mut current_sequence: Vec<GpsPoint> = Vec::new();
let mut gap_count = 0;
const MAX_GAP: usize = 3;
for point in track {
let query = [point.latitude, point.longitude];
let is_near = if let Some(nearest) = polyline_tree.nearest_neighbor(&query) {
nearest.distance_2(&query) <= threshold_deg_sq
} else {
false
};
if is_near {
gap_count = 0;
current_sequence.push(*point);
} else {
gap_count += 1;
if gap_count <= MAX_GAP && !current_sequence.is_empty() {
current_sequence.push(*point);
} else if gap_count > MAX_GAP {
if current_sequence.len() >= MIN_TRACE_POINTS {
sequences.push(std::mem::take(&mut current_sequence));
} else {
current_sequence.clear();
}
gap_count = 0;
}
}
}
if current_sequence.len() >= MIN_TRACE_POINTS {
sequences.push(current_sequence);
}
if sequences.is_empty() {
return Vec::new();
}
if sequences.len() == 1 {
return sequences.into_iter().next().unwrap();
}
let section_tree = build_rtree(section_polyline);
let mut sequence_with_position: Vec<(usize, Vec<GpsPoint>)> = sequences
.into_iter()
.map(|seq| {
let start_pos = if let Some(first) = seq.first() {
let query = [first.latitude, first.longitude];
section_tree
.nearest_neighbor(&query)
.map(|n| n.idx)
.unwrap_or(0)
} else {
0
};
(start_pos, seq)
})
.collect();
sequence_with_position.sort_by_key(|(pos, _)| *pos);
let mut merged: Vec<GpsPoint> = Vec::new();
for (_, seq) in sequence_with_position {
merged.extend(seq);
}
merged
}
pub fn extract_all_activity_traces(
activity_ids: &[String],
section_polyline: &[GpsPoint],
track_map: &HashMap<String, Vec<GpsPoint>>,
) -> HashMap<String, Vec<GpsPoint>> {
let polyline_tree = build_rtree(section_polyline);
#[cfg(feature = "parallel")]
let traces: HashMap<String, Vec<GpsPoint>> = activity_ids
.par_iter()
.filter_map(|activity_id| {
track_map.get(activity_id).and_then(|track| {
let trace = extract_activity_trace(track, section_polyline, &polyline_tree);
if trace.is_empty() {
None
} else {
Some((activity_id.clone(), trace))
}
})
})
.collect();
#[cfg(not(feature = "parallel"))]
let traces: HashMap<String, Vec<GpsPoint>> = activity_ids
.iter()
.filter_map(|activity_id| {
track_map.get(activity_id).and_then(|track| {
let trace = extract_activity_trace(track, section_polyline, &polyline_tree);
if trace.is_empty() {
None
} else {
Some((activity_id.clone(), trace))
}
})
})
.collect();
traces
}