diff --git a/gpx-rs/src/action/mod.rs b/gpx-rs/src/action/mod.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/gpx-rs/src/action/mod.rs @@ -0,0 +1 @@ + diff --git a/gpx-rs/src/algorithms/mod.rs b/gpx-rs/src/algorithm/mod.rs similarity index 100% rename from gpx-rs/src/algorithms/mod.rs rename to gpx-rs/src/algorithm/mod.rs diff --git a/gpx-rs/src/algorithms/simplify.rs b/gpx-rs/src/algorithm/simplify.rs similarity index 100% rename from gpx-rs/src/algorithms/simplify.rs rename to gpx-rs/src/algorithm/simplify.rs diff --git a/gpx-rs/src/algorithms/smooth.rs b/gpx-rs/src/algorithm/smooth.rs similarity index 100% rename from gpx-rs/src/algorithms/smooth.rs rename to gpx-rs/src/algorithm/smooth.rs diff --git a/gpx-rs/src/controller.rs b/gpx-rs/src/engine.rs similarity index 90% rename from gpx-rs/src/controller.rs rename to gpx-rs/src/engine.rs index e0e6279f3..bdee4babc 100644 --- a/gpx-rs/src/controller.rs +++ b/gpx-rs/src/engine.rs @@ -4,8 +4,9 @@ use uuid::Uuid; use wasm_bindgen::prelude::*; use crate::{ - actions::parse, gpx::GPXFile, + io::parse, + selection::Selection, stack::{Stack, StackEntry}, }; @@ -20,6 +21,7 @@ use crate::{ #[wasm_bindgen] pub struct Controller { stack: Stack, + selection: Selection, } #[wasm_bindgen] @@ -27,7 +29,8 @@ impl Controller { #[wasm_bindgen(constructor)] pub fn new() -> Self { Self { - stack: Stack::default(), + stack: Default::default(), + selection: Default::default(), } } diff --git a/gpx-rs/src/gpx/common.rs b/gpx-rs/src/gpx/common.rs index ff2593021..c074e3e69 100644 --- a/gpx-rs/src/gpx/common.rs +++ b/gpx-rs/src/gpx/common.rs @@ -10,8 +10,39 @@ pub struct LngLat { pub lat: f64, } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct LngLatBounds { pub sw: LngLat, pub ne: LngLat, } + +impl Default for LngLatBounds { + fn default() -> Self { + Self { + sw: LngLat { + lng: 180.0, + lat: 90.0, + }, + ne: LngLat { + lng: -180.0, + lat: -90.0, + }, + } + } +} + +impl LngLatBounds { + pub fn extend(&mut self, coordinates: LngLat) { + self.sw.lng = self.sw.lng.min(coordinates.lng); + self.sw.lat = self.sw.lat.min(coordinates.lat); + self.ne.lng = self.ne.lng.min(coordinates.lng); + self.ne.lat = self.ne.lat.min(coordinates.lat); + } + + pub fn merge(&mut self, other: &LngLatBounds) { + self.sw.lng = self.sw.lng.min(other.sw.lng); + self.sw.lat = self.sw.lat.min(other.sw.lat); + self.ne.lng = self.ne.lng.min(other.ne.lng); + self.ne.lat = self.ne.lat.min(other.ne.lat); + } +} diff --git a/gpx-rs/src/actions/mod.rs b/gpx-rs/src/io/mod.rs similarity index 100% rename from gpx-rs/src/actions/mod.rs rename to gpx-rs/src/io/mod.rs diff --git a/gpx-rs/src/actions/parse.rs b/gpx-rs/src/io/parse.rs similarity index 81% rename from gpx-rs/src/actions/parse.rs rename to gpx-rs/src/io/parse.rs index ebbb40a71..3f8bf285d 100644 --- a/gpx-rs/src/actions/parse.rs +++ b/gpx-rs/src/io/parse.rs @@ -11,29 +11,29 @@ use quick_xml::events::attributes::Attributes; use quick_xml::reader::Reader; enum GPXElement { - METADATA, - NAME, - COMMENT, - DESCRIPTION, - SOURCE, - AUTHOR(Author), - LINK(Link), - TEXT, - TRACK(Track), - SEGMENT(TrackSegment), - TRACKPOINT(Trackpoint), - WAYPOINT(Waypoint), - ELEVATION, - TIME, - TEMPERATURE, - HEARTRATE, - CADENCE, - POWER, - SYMBOL, - TYPE, - COLOR, - OPACITY, - WIDTH, + Metadata, + Name, + Comment, + Description, + Source, + Author(Author), + Link(Link), + Text, + Track(Track), + Segment(TrackSegment), + Trackpoint(Trackpoint), + Waypoint(Waypoint), + Elevation, + Time, + Temperature, + Heartrate, + Cadence, + Power, + Symbol, + Type, + Color, + Opacity, + Width, } fn parse_coordinates(attributes: Attributes<'_>) -> LngLat { @@ -60,12 +60,12 @@ pub fn parse(data: &[u8]) -> Result { loop { match reader.read_event_into(&mut buf) { Ok(Event::Start(e)) => match e.name().as_ref() { - "metadata" => stack.push(GPXElement::METADATA), - "name" => stack.push(GPXElement::NAME), - "cmt" => stack.push(GPXElement::COMMENT), - "desc" => stack.push(GPXElement::DESCRIPTION), - "src" => stack.push(GPXElement::SOURCE), - "author" => stack.push(GPXElement::AUTHOR(Author::default())), + "metadata" => stack.push(GPXElement::Metadata), + "name" => stack.push(GPXElement::Name), + "cmt" => stack.push(GPXElement::Comment), + "desc" => stack.push(GPXElement::Description), + "src" => stack.push(GPXElement::Source), + "author" => stack.push(GPXElement::Author(Author::default())), "link" => { let mut link = Link::default(); for attr in e.attributes() { @@ -75,35 +75,35 @@ pub fn parse(data: &[u8]) -> Result { } } } - stack.push(GPXElement::LINK(link)); + stack.push(GPXElement::Link(link)); } - "text" => stack.push(GPXElement::TEXT), - "trk" => stack.push(GPXElement::TRACK(Track::default())), + "text" => stack.push(GPXElement::Text), + "trk" => stack.push(GPXElement::Track(Track::default())), "trkseg" => { - stack.push(GPXElement::SEGMENT(TrackSegment::default())); + stack.push(GPXElement::Segment(TrackSegment::default())); } "trkpt" => { let mut trkpt = Trackpoint::default(); trkpt.coordinates = parse_coordinates(e.attributes()); - stack.push(GPXElement::TRACKPOINT(trkpt)); + stack.push(GPXElement::Trackpoint(trkpt)); } "wpt" => { let mut wpt = Waypoint::default(); wpt.coordinates = parse_coordinates(e.attributes()); - stack.push(GPXElement::WAYPOINT(wpt)); + stack.push(GPXElement::Waypoint(wpt)); } - "ele" => stack.push(GPXElement::ELEVATION), - "time" => stack.push(GPXElement::TIME), - e if e.ends_with("atemp") => stack.push(GPXElement::TEMPERATURE), - e if e.ends_with("hr") => stack.push(GPXElement::HEARTRATE), - e if e.ends_with("cad") => stack.push(GPXElement::CADENCE), - "power" => stack.push(GPXElement::POWER), - e if e.ends_with("PowerInWatts") => stack.push(GPXElement::POWER), - "sym" => stack.push(GPXElement::SYMBOL), - "type" => stack.push(GPXElement::TYPE), - e if e.ends_with("color") => stack.push(GPXElement::COLOR), - e if e.ends_with("opacity") => stack.push(GPXElement::OPACITY), - e if e.ends_with("width") => stack.push(GPXElement::WIDTH), + "ele" => stack.push(GPXElement::Elevation), + "time" => stack.push(GPXElement::Time), + e if e.ends_with("atemp") => stack.push(GPXElement::Temperature), + e if e.ends_with("hr") => stack.push(GPXElement::Heartrate), + e if e.ends_with("cad") => stack.push(GPXElement::Cadence), + "power" => stack.push(GPXElement::Power), + e if e.ends_with("PowerInWatts") => stack.push(GPXElement::Power), + "sym" => stack.push(GPXElement::Symbol), + "type" => stack.push(GPXElement::Type), + e if e.ends_with("color") => stack.push(GPXElement::Color), + e if e.ends_with("opacity") => stack.push(GPXElement::Opacity), + e if e.ends_with("width") => stack.push(GPXElement::Width), _ => (), }, Ok(Event::End(e)) => match e.name().as_ref() { @@ -117,20 +117,20 @@ pub fn parse(data: &[u8]) -> Result { stack.pop(); } "author" => { - if let Some(GPXElement::AUTHOR(author)) = stack.pop() { + if let Some(GPXElement::Author(author)) = stack.pop() { gpx.info.author = Some(author); } } "link" => { - if let Some(GPXElement::LINK(link)) = stack.pop() { + if let Some(GPXElement::Link(link)) = stack.pop() { match stack.last_mut() { - Some(GPXElement::AUTHOR(author)) => { + Some(GPXElement::Author(author)) => { author.link = Some(link); } - Some(GPXElement::TRACK(trk)) => { + Some(GPXElement::Track(trk)) => { trk.info.link = Some(link); } - Some(GPXElement::WAYPOINT(wpt)) => { + Some(GPXElement::Waypoint(wpt)) => { wpt.link = Some(link); } _ => (), @@ -138,13 +138,13 @@ pub fn parse(data: &[u8]) -> Result { } } "trk" => { - if let Some(GPXElement::TRACK(trk)) = stack.pop() { + if let Some(GPXElement::Track(trk)) = stack.pop() { gpx.trk.push(trk); } } "trkseg" => { - if let Some(GPXElement::SEGMENT(mut trkseg)) = stack.pop() { - if let Some(GPXElement::TRACK(trk)) = stack.last_mut() { + if let Some(GPXElement::Segment(mut trkseg)) = stack.pop() { + if let Some(GPXElement::Track(trk)) = stack.last_mut() { if !trkpt_chunk.trkpt.is_empty() { trkseg.push(trkpt_chunk); trkpt_chunk = TrackpointChunk::default(); @@ -154,8 +154,8 @@ pub fn parse(data: &[u8]) -> Result { } } "trkpt" => { - if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.pop() { - if let Some(GPXElement::SEGMENT(trkseg)) = stack.last_mut() { + if let Some(GPXElement::Trackpoint(trkpt)) = stack.pop() { + if let Some(GPXElement::Segment(trkseg)) = stack.last_mut() { trkpt_chunk.trkpt.push(trkpt); if trkpt_chunk.is_full() { trkseg.push(trkpt_chunk); @@ -165,7 +165,7 @@ pub fn parse(data: &[u8]) -> Result { } } "wpt" => { - if let Some(GPXElement::WAYPOINT(wpt)) = stack.pop() { + if let Some(GPXElement::Waypoint(wpt)) = stack.pop() { wpt_chunk.wpt.push(wpt); if wpt_chunk.is_full() { gpx.wpt.push(Rc::new(wpt_chunk)); @@ -176,135 +176,135 @@ pub fn parse(data: &[u8]) -> Result { _ => (), }, Ok(Event::Text(e)) => match stack.last_mut() { - Some(GPXElement::NAME) => { + Some(GPXElement::Name) => { stack.pop(); match stack.last_mut() { - Some(GPXElement::METADATA) => { + Some(GPXElement::Metadata) => { gpx.info.name = e.to_string(); } - Some(GPXElement::AUTHOR(author)) => { + Some(GPXElement::Author(author)) => { author.name = Some(e.to_string()); } - Some(GPXElement::TRACK(trk)) => { + Some(GPXElement::Track(trk)) => { trk.info.name = Some(e.to_string()); } - Some(GPXElement::WAYPOINT(wpt)) => { + Some(GPXElement::Waypoint(wpt)) => { wpt.name = Some(e.to_string()); } _ => (), } } - Some(GPXElement::COMMENT) => { + Some(GPXElement::Comment) => { stack.pop(); match stack.last_mut() { - Some(GPXElement::TRACK(trk)) => { + Some(GPXElement::Track(trk)) => { trk.info.cmt = Some(e.to_string()); } - Some(GPXElement::WAYPOINT(wpt)) => { + Some(GPXElement::Waypoint(wpt)) => { wpt.cmt = Some(e.to_string()); } _ => (), } } - Some(GPXElement::DESCRIPTION) => { + Some(GPXElement::Description) => { stack.pop(); match stack.last_mut() { - Some(GPXElement::METADATA) => { + Some(GPXElement::Metadata) => { gpx.info.desc = Some(e.to_string()); } - Some(GPXElement::TRACK(trk)) => { + Some(GPXElement::Track(trk)) => { trk.info.desc = Some(e.to_string()); } - Some(GPXElement::WAYPOINT(wpt)) => { + Some(GPXElement::Waypoint(wpt)) => { wpt.desc = Some(e.to_string()); } _ => (), } } - Some(GPXElement::SOURCE) => { + Some(GPXElement::Source) => { stack.pop(); match stack.last_mut() { - Some(GPXElement::TRACK(trk)) => { + Some(GPXElement::Track(trk)) => { trk.info.src = Some(e.to_string()); } _ => (), } } - Some(GPXElement::TEXT) => { + Some(GPXElement::Text) => { stack.pop(); - if let Some(GPXElement::LINK(link)) = stack.last_mut() { + if let Some(GPXElement::Link(link)) = stack.last_mut() { link.text = Some(e.to_string()); } } - Some(GPXElement::ELEVATION) => { + Some(GPXElement::Elevation) => { stack.pop(); match stack.last_mut() { - Some(GPXElement::TRACKPOINT(trkpt)) => { + Some(GPXElement::Trackpoint(trkpt)) => { trkpt.ele = e.parse().unwrap_or_default(); } - Some(GPXElement::WAYPOINT(wpt)) => { + Some(GPXElement::Waypoint(wpt)) => { wpt.ele = e.parse().unwrap_or_default(); } _ => (), } } - Some(GPXElement::TIME) => { + Some(GPXElement::Time) => { stack.pop(); - if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.last_mut() { + if let Some(GPXElement::Trackpoint(trkpt)) = stack.last_mut() { if let Ok(time) = DateTime::parse_from_rfc3339(e.as_ref()) { trkpt.time = Some(time.timestamp_millis()); } } } - Some(GPXElement::TEMPERATURE) => { + Some(GPXElement::Temperature) => { stack.pop(); - if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.last_mut() { + if let Some(GPXElement::Trackpoint(trkpt)) = stack.last_mut() { trkpt.atemp = e.parse().ok(); } } - Some(GPXElement::HEARTRATE) => { + Some(GPXElement::Heartrate) => { stack.pop(); - if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.last_mut() { + if let Some(GPXElement::Trackpoint(trkpt)) = stack.last_mut() { trkpt.hr = e.parse().ok(); } } - Some(GPXElement::CADENCE) => { + Some(GPXElement::Cadence) => { stack.pop(); - if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.last_mut() { + if let Some(GPXElement::Trackpoint(trkpt)) = stack.last_mut() { trkpt.cad = e.parse().ok(); } } - Some(GPXElement::POWER) => { + Some(GPXElement::Power) => { stack.pop(); - if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.last_mut() { + if let Some(GPXElement::Trackpoint(trkpt)) = stack.last_mut() { trkpt.power = e.parse().ok(); } } - Some(GPXElement::SYMBOL) => { + Some(GPXElement::Symbol) => { stack.pop(); match stack.last_mut() { - Some(GPXElement::WAYPOINT(wpt)) => { + Some(GPXElement::Waypoint(wpt)) => { wpt.sym = Some(e.to_string()); } _ => (), } } - Some(GPXElement::TYPE) => { + Some(GPXElement::Type) => { stack.pop(); match stack.last_mut() { - Some(GPXElement::TRACK(trk)) => { + Some(GPXElement::Track(trk)) => { trk.info.type_ = Some(e.to_string()); } - Some(GPXElement::WAYPOINT(wpt)) => { + Some(GPXElement::Waypoint(wpt)) => { wpt.type_ = Some(e.to_string()); } _ => (), } } - Some(GPXElement::COLOR) => { + Some(GPXElement::Color) => { stack.pop(); match stack.last_mut() { - Some(GPXElement::TRACK(trk)) => { + Some(GPXElement::Track(trk)) => { let mut color = "#".to_string(); color.push_str(&e); trk.info.color = Some(color); @@ -312,19 +312,19 @@ pub fn parse(data: &[u8]) -> Result { _ => (), } } - Some(GPXElement::OPACITY) => { + Some(GPXElement::Opacity) => { stack.pop(); match stack.last_mut() { - Some(GPXElement::TRACK(trk)) => { + Some(GPXElement::Track(trk)) => { trk.info.opacity = e.parse().ok(); } _ => (), } } - Some(GPXElement::WIDTH) => { + Some(GPXElement::Width) => { stack.pop(); match stack.last_mut() { - Some(GPXElement::TRACK(trk)) => { + Some(GPXElement::Track(trk)) => { trk.info.width = e.parse().ok(); } _ => (), diff --git a/gpx-rs/src/lib.rs b/gpx-rs/src/lib.rs index 1f901dc6f..b35d0c364 100644 --- a/gpx-rs/src/lib.rs +++ b/gpx-rs/src/lib.rs @@ -1,7 +1,9 @@ -mod actions; -mod controller; +mod action; +mod algorithm; +mod engine; mod gpx; +mod io; +mod selection; mod stack; mod statistics; mod utils; -mod algorithms; diff --git a/gpx-rs/src/selection.rs b/gpx-rs/src/selection.rs new file mode 100644 index 000000000..d37a56592 --- /dev/null +++ b/gpx-rs/src/selection.rs @@ -0,0 +1,38 @@ +use std::collections::HashSet; + +use uuid::Uuid; + +#[derive(Debug)] +pub struct FileSelection { + files: HashSet, +} + +#[derive(Debug)] +pub struct TrackSelection { + file: Uuid, + trk: HashSet, +} + +#[derive(Debug)] +pub struct TrackSegmentSelection { + file: Uuid, + trk: Uuid, + trkseg: HashSet, +} + +#[derive(Debug)] +pub struct WaypointSelection { + file: Uuid, + wpt: HashSet, +} + +#[derive(Debug, Default)] +pub enum Selection { + #[default] + Empty, + File(FileSelection), + Track(TrackSelection), + TrackSegment(TrackSegmentSelection), + Waypoints, + Waypoint(WaypointSelection), +} diff --git a/gpx-rs/src/statistics/buffer.rs b/gpx-rs/src/statistics/buffer.rs new file mode 100644 index 000000000..bbd4049b1 --- /dev/null +++ b/gpx-rs/src/statistics/buffer.rs @@ -0,0 +1,34 @@ +use crate::statistics::{GPXStatistics, GlobalStatistics}; + +pub struct Buffer { + pub total_distance: Vec, + pub total_time: Vec, + pub moving_time: Vec, + pub speed: Vec, + pub elevation_gain: Vec, + pub elevation_loss: Vec, + pub slope: Vec, + pub slope_segment_slope: Vec, + pub slope_segment_distance: Vec, +} + +impl Buffer { + pub fn update(&mut self, stats: &[&GPXStatistics]) { + self.total_distance.clear(); + self.total_time.clear(); + self.moving_time.clear(); + self.speed.clear(); + self.elevation_gain.clear(); + self.elevation_loss.clear(); + self.slope.clear(); + self.slope_segment_slope.clear(); + self.slope_segment_distance.clear(); + + let mut cumul_stats = GlobalStatistics::default(); + for stats in stats { + // add stats + // merge global ones into cumul + cumul_stats.merge(&stats.global); + } + } +} diff --git a/gpx-rs/src/statistics/mod.rs b/gpx-rs/src/statistics/mod.rs new file mode 100644 index 000000000..5bd15f937 --- /dev/null +++ b/gpx-rs/src/statistics/mod.rs @@ -0,0 +1,5 @@ +mod buffer; +mod statistics; + +pub use buffer::*; +pub use statistics::*; diff --git a/gpx-rs/src/statistics.rs b/gpx-rs/src/statistics/statistics.rs similarity index 78% rename from gpx-rs/src/statistics.rs rename to gpx-rs/src/statistics/statistics.rs index a63050ffa..3aa3f120d 100644 --- a/gpx-rs/src/statistics.rs +++ b/gpx-rs/src/statistics/statistics.rs @@ -1,5 +1,7 @@ +use std::ops::Add; + use crate::{ - algorithms::ramer_douglas_peucker, + algorithm::ramer_douglas_peucker, for_each_window, gpx::{LngLat, LngLatBounds, TrackSegment, Trackpoint}, utils::{distance, slope, speed}, @@ -7,29 +9,27 @@ use crate::{ #[derive(Default, Debug)] pub struct GPXStatistics { - pub total_distance: f64, - pub moving_distance: Option, - pub moving_time: Option, - pub elevation_gain: f64, - pub elevation_loss: f64, - pub start_time: Option, - pub end_time: Option, - pub bounds: LngLatBounds, + pub global: GlobalStatistics, pub local: Vec, } impl GPXStatistics { pub fn total_time(&self) -> Option { - self.start_time.zip(self.end_time).map(|(t1, t2)| t2 - t1) + self.global + .start_time + .zip(self.global.end_time) + .map(|(t1, t2)| t2 - t1) } pub fn total_speed(&self) -> Option { - self.total_time().map(|t| speed(self.total_distance, t)) + self.total_time() + .map(|t| speed(self.global.total_distance, t)) } pub fn moving_speed(&self) -> Option { - self.moving_distance - .zip(self.moving_time) + self.global + .moving_distance + .zip(self.global.moving_time) .map(|(d, t)| speed(d, t)) } @@ -54,7 +54,7 @@ impl GPXStatistics { fn accumulate(&mut self, prev: &Trackpoint, cur: &Trackpoint) { self.accumulate_distance_and_time(prev, cur); self.update_time_bounds(cur.time); - self.update_bounds(&cur.coordinates); + self.update_bounds(cur.coordinates); self.local .push(TrackpointStatistics::from_partial_stats(&self)); } @@ -63,31 +63,34 @@ impl GPXStatistics { let dist = distance(prev.coordinates, cur.coordinates); let time = cur.time_diff(prev); - self.total_distance += dist; + self.global.total_distance += dist; if let Some(time) = time { let speed = speed(dist, time); if speed >= 0.5 && speed <= 1500.0 { - self.moving_distance = self.moving_distance.map_or(Some(dist), |d| Some(d + dist)); - self.moving_time = self.moving_time.map_or(Some(time), |t| Some(t + time)); + self.global.moving_distance = self + .global + .moving_distance + .map_or(Some(dist), |d| Some(d + dist)); + self.global.moving_time = self + .global + .moving_time + .map_or(Some(time), |t| Some(t + time)); } } } fn update_time_bounds(&mut self, time: Option) { if let Some(time) = time { - if self.start_time.is_none() { - self.start_time = Some(time); + if self.global.start_time.is_none() { + self.global.start_time = Some(time); } - self.end_time = Some(time); + self.global.end_time = Some(time); } } - fn update_bounds(&mut self, coordinates: &LngLat) { - self.bounds.sw.lng = self.bounds.sw.lng.min(coordinates.lng); - self.bounds.sw.lat = self.bounds.sw.lat.min(coordinates.lat); - self.bounds.ne.lng = self.bounds.ne.lng.min(coordinates.lng); - self.bounds.ne.lat = self.bounds.ne.lat.min(coordinates.lat); + fn update_bounds(&mut self, coordinates: LngLat) { + self.global.bounds.extend(coordinates); } fn compute_smoothed_speed(&mut self, trkseg: &TrackSegment) { @@ -177,14 +180,14 @@ impl GPXStatistics { let delta = smoothed_ele - prev_smoothed_ele; if delta > 0.0 { - self.elevation_gain += delta; + self.global.elevation_gain += delta; } else if delta < 0.0 { - self.elevation_loss -= delta; + self.global.elevation_loss -= delta; } if i < end || last { - self.local[flat_i].elevation_gain = self.elevation_gain; - self.local[flat_i].elevation_loss = self.elevation_loss; + self.local[flat_i].elevation_gain = self.global.elevation_gain; + self.local[flat_i].elevation_loss = self.global.elevation_loss; } prev_smoothed_ele = smoothed_ele; @@ -230,9 +233,38 @@ impl GPXStatistics { } #[derive(Default, Debug)] -pub struct SlopeSegment { - pub slope: f64, - pub distance: f64, +pub struct GlobalStatistics { + pub total_distance: f64, + pub moving_distance: Option, + pub moving_time: Option, + pub elevation_gain: f64, + pub elevation_loss: f64, + pub start_time: Option, + pub end_time: Option, + pub bounds: LngLatBounds, +} + +fn sum_options(a: Option, b: Option) -> Option +where + T: Add, +{ + match (a, b) { + (Some(a), Some(b)) => Some(a + b), + (Some(a), None) => Some(a), + (None, Some(b)) => Some(b), + (None, None) => None, + } +} + +impl GlobalStatistics { + pub fn merge(&mut self, other: &GlobalStatistics) { + self.total_distance += other.total_distance; + self.moving_distance = sum_options(self.moving_distance, other.moving_distance); + self.moving_time = sum_options(self.moving_time, other.moving_time); + self.elevation_gain += other.elevation_gain; + self.elevation_loss += other.elevation_loss; + self.bounds.merge(&other.bounds); + } } #[derive(Default, Debug)] @@ -251,24 +283,31 @@ pub struct TrackpointStatistics { impl TrackpointStatistics { fn from_partial_stats(stats: &GPXStatistics) -> Self { Self { - total_distance: stats.total_distance, - moving_distance: stats.moving_distance, - total_time: stats.start_time.zip(stats.end_time).map(|(t1, t2)| t2 - t1), - moving_time: stats.moving_time, + total_distance: stats.global.total_distance, + moving_distance: stats.global.moving_distance, + total_time: stats.total_time(), + moving_time: stats.global.moving_time, + // stats below are computed later speed: None, - elevation_gain: stats.elevation_gain, - elevation_loss: stats.elevation_loss, + elevation_gain: Default::default(), + elevation_loss: Default::default(), slope: Default::default(), slope_segment: Default::default(), } } } +#[derive(Default, Debug)] +pub struct SlopeSegment { + pub slope: f64, + pub distance: f64, +} + #[cfg(test)] mod tests { use std::{fs::File, io::Read}; - use crate::actions::parse; + use crate::io::parse; use super::*;