use std::collections::HashMap;
use crate::{Bounds, GpsPoint};
#[derive(Debug, Clone)]
pub struct ActivityData {
pub id: String,
pub coords: Vec<GpsPoint>,
pub sport_type: String,
pub bounds: Option<Bounds>,
}
#[derive(Debug, Default)]
pub struct ActivityStore {
activities: HashMap<String, ActivityData>,
}
impl ActivityStore {
pub fn new() -> Self {
Self {
activities: HashMap::new(),
}
}
pub fn add(&mut self, id: String, coords: Vec<GpsPoint>, sport_type: String) -> Option<Bounds> {
let bounds = Bounds::from_points(&coords);
let activity = ActivityData {
id: id.clone(),
coords,
sport_type,
bounds,
};
self.activities.insert(id, activity);
bounds
}
pub fn add_flat(
&mut self,
id: String,
flat_coords: &[f64],
sport_type: String,
) -> Option<Bounds> {
let coords: Vec<GpsPoint> = flat_coords
.chunks_exact(2)
.map(|chunk| GpsPoint::new(chunk[0], chunk[1]))
.collect();
self.add(id, coords, sport_type)
}
pub fn add_many_flat(
&mut self,
activity_ids: &[String],
all_coords: &[f64],
offsets: &[u32],
sport_types: &[String],
) -> Vec<String> {
let mut added_ids = Vec::with_capacity(activity_ids.len());
for (i, id) in activity_ids.iter().enumerate() {
let start = offsets[i] as usize;
let end = offsets
.get(i + 1)
.map(|&o| o as usize)
.unwrap_or(all_coords.len() / 2);
let coords: Vec<GpsPoint> = (start..end)
.filter_map(|j| {
let idx = j * 2;
if idx + 1 < all_coords.len() {
Some(GpsPoint::new(all_coords[idx], all_coords[idx + 1]))
} else {
None
}
})
.collect();
let sport = sport_types.get(i).cloned().unwrap_or_default();
self.add(id.clone(), coords, sport);
added_ids.push(id.clone());
}
added_ids
}
pub fn remove(&mut self, id: &str) -> Option<ActivityData> {
self.activities.remove(id)
}
pub fn remove_many(&mut self, ids: &[String]) -> Vec<String> {
let mut removed = Vec::new();
for id in ids {
if self.activities.remove(id).is_some() {
removed.push(id.clone());
}
}
removed
}
pub fn clear(&mut self) {
self.activities.clear();
}
pub fn get(&self, id: &str) -> Option<&ActivityData> {
self.activities.get(id)
}
pub fn get_coords(&self, id: &str) -> Option<&[GpsPoint]> {
self.activities.get(id).map(|a| a.coords.as_slice())
}
pub fn get_sport_type(&self, id: &str) -> Option<&str> {
self.activities.get(id).map(|a| a.sport_type.as_str())
}
pub fn contains(&self, id: &str) -> bool {
self.activities.contains_key(id)
}
pub fn ids(&self) -> impl Iterator<Item = &String> {
self.activities.keys()
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &ActivityData)> {
self.activities.iter()
}
pub fn values(&self) -> impl Iterator<Item = &ActivityData> {
self.activities.values()
}
pub fn len(&self) -> usize {
self.activities.len()
}
pub fn is_empty(&self) -> bool {
self.activities.is_empty()
}
pub fn sport_type_map(&self) -> HashMap<String, String> {
self.activities
.iter()
.map(|(id, a)| (id.clone(), a.sport_type.clone()))
.collect()
}
pub fn as_tracks(&self) -> Vec<(String, Vec<GpsPoint>)> {
self.activities
.values()
.map(|a| (a.id.clone(), a.coords.clone()))
.collect()
}
pub fn compute_track_distance(coords: &[GpsPoint]) -> f64 {
if coords.len() < 2 {
return 0.0;
}
coords
.windows(2)
.map(|pair| crate::geo_utils::haversine_distance(&pair[0], &pair[1]))
.sum()
}
}