use super::overlap::OverlapCluster;
use super::rtree::{build_rtree, IndexedPoint};
use super::{SectionConfig, SectionPortion};
use crate::geo_utils::polyline_length;
use crate::GpsPoint;
use rstar::{PointDistance, RTree};
use std::collections::HashMap;
pub fn compute_activity_portions(
cluster: &OverlapCluster,
representative_polyline: &[GpsPoint],
all_tracks: &HashMap<String, Vec<GpsPoint>>,
config: &SectionConfig,
) -> Vec<SectionPortion> {
let mut portions = Vec::new();
for activity_id in &cluster.activity_ids {
if let Some(track) = all_tracks.get(activity_id) {
if let Some((start_idx, end_idx, direction)) =
find_track_portion(track, representative_polyline, config.proximity_threshold)
{
let distance = polyline_length(&track[start_idx..end_idx]);
portions.push(SectionPortion {
activity_id: activity_id.clone(),
start_index: start_idx as u32,
end_index: end_idx as u32,
distance_meters: distance,
direction,
});
}
}
}
portions
}
struct OverlapSegment {
start_idx: usize,
end_idx: usize,
distance: f64,
}
fn find_track_portion(
track: &[GpsPoint],
reference: &[GpsPoint],
threshold: f64,
) -> Option<(usize, usize, String)> {
if track.is_empty() || reference.is_empty() {
return None;
}
let ref_tree = build_rtree(reference);
let threshold_deg = threshold / 111_000.0;
let threshold_deg_sq = threshold_deg * threshold_deg;
let ref_length = polyline_length(reference);
let mut segments: Vec<OverlapSegment> = Vec::new();
let mut current_start: Option<usize> = None;
let mut gap_count = 0;
const MAX_GAP: usize = 3;
for (i, point) in track.iter().enumerate() {
let query = [point.latitude, point.longitude];
let is_near = ref_tree
.nearest_neighbor(&query)
.map(|nearest| nearest.distance_2(&query) <= threshold_deg_sq)
.unwrap_or(false);
if is_near {
if current_start.is_none() {
current_start = Some(i);
}
gap_count = 0;
} else if current_start.is_some() {
gap_count += 1;
if gap_count > MAX_GAP {
let start = current_start.unwrap();
let end = i - gap_count;
if end > start {
let distance = polyline_length(&track[start..end]);
segments.push(OverlapSegment {
start_idx: start,
end_idx: end,
distance,
});
}
current_start = None;
gap_count = 0;
}
}
}
if let Some(start) = current_start {
let end = track.len() - gap_count.min(track.len() - start - 1);
if end > start {
let distance = polyline_length(&track[start..end]);
segments.push(OverlapSegment {
start_idx: start,
end_idx: end,
distance,
});
}
}
if segments.is_empty() {
return None;
}
let tolerance = 0.5; let min_dist = ref_length * (1.0 - tolerance);
let max_dist = ref_length * (1.0 + tolerance);
let mut best_segment: Option<&OverlapSegment> = None;
let mut best_diff = f64::MAX;
for segment in &segments {
if segment.distance >= min_dist && segment.distance <= max_dist {
let diff = (segment.distance - ref_length).abs();
if diff < best_diff {
best_diff = diff;
best_segment = Some(segment);
}
}
}
if best_segment.is_none() {
for segment in &segments {
if segment.distance >= ref_length * 0.5 {
let diff = (segment.distance - ref_length).abs();
if diff < best_diff {
best_diff = diff;
best_segment = Some(segment);
}
}
}
}
best_segment.map(|seg| {
let direction =
detect_direction_robust(&track[seg.start_idx..seg.end_idx], reference, &ref_tree);
(seg.start_idx, seg.end_idx, direction)
})
}
fn detect_direction_robust(
track_portion: &[GpsPoint],
reference: &[GpsPoint],
ref_tree: &RTree<IndexedPoint>,
) -> String {
if track_portion.len() < 3 || reference.len() < 3 {
return "same".to_string();
}
let sample_count = 5.min(track_portion.len());
let step = track_portion.len() / sample_count;
let mut ref_indices: Vec<usize> = Vec::with_capacity(sample_count);
for i in 0..sample_count {
let track_idx = (i * step).min(track_portion.len() - 1);
let point = &track_portion[track_idx];
let query = [point.latitude, point.longitude];
if let Some(nearest) = ref_tree.nearest_neighbor(&query) {
ref_indices.push(nearest.idx);
}
}
if ref_indices.len() < 2 {
return "same".to_string();
}
let mut forward_count = 0;
let mut backward_count = 0;
for i in 1..ref_indices.len() {
let prev_idx = ref_indices[i - 1];
let curr_idx = ref_indices[i];
if curr_idx > prev_idx {
forward_count += 1;
} else if curr_idx < prev_idx {
backward_count += 1;
}
}
if backward_count > forward_count {
"reverse".to_string()
} else {
"same".to_string()
}
}