mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-09-25 17:07:37 +00:00
progress
This commit is contained in:
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<GPXFile, Error> {
|
||||
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<GPXFile, Error> {
|
||||
}
|
||||
}
|
||||
}
|
||||
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<GPXFile, Error> {
|
||||
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<GPXFile, Error> {
|
||||
}
|
||||
}
|
||||
"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<GPXFile, Error> {
|
||||
}
|
||||
}
|
||||
"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<GPXFile, Error> {
|
||||
}
|
||||
}
|
||||
"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<GPXFile, Error> {
|
||||
_ => (),
|
||||
},
|
||||
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<GPXFile, Error> {
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
_ => (),
|
||||
+5
-3
@@ -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;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileSelection {
|
||||
files: HashSet<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TrackSelection {
|
||||
file: Uuid,
|
||||
trk: HashSet<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TrackSegmentSelection {
|
||||
file: Uuid,
|
||||
trk: Uuid,
|
||||
trkseg: HashSet<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WaypointSelection {
|
||||
file: Uuid,
|
||||
wpt: HashSet<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub enum Selection {
|
||||
#[default]
|
||||
Empty,
|
||||
File(FileSelection),
|
||||
Track(TrackSelection),
|
||||
TrackSegment(TrackSegmentSelection),
|
||||
Waypoints,
|
||||
Waypoint(WaypointSelection),
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
use crate::statistics::{GPXStatistics, GlobalStatistics};
|
||||
|
||||
pub struct Buffer {
|
||||
pub total_distance: Vec<f64>,
|
||||
pub total_time: Vec<i64>,
|
||||
pub moving_time: Vec<i64>,
|
||||
pub speed: Vec<f64>,
|
||||
pub elevation_gain: Vec<f64>,
|
||||
pub elevation_loss: Vec<f64>,
|
||||
pub slope: Vec<f64>,
|
||||
pub slope_segment_slope: Vec<f64>,
|
||||
pub slope_segment_distance: Vec<f64>,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
mod buffer;
|
||||
mod statistics;
|
||||
|
||||
pub use buffer::*;
|
||||
pub use statistics::*;
|
||||
@@ -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<f64>,
|
||||
pub moving_time: Option<i64>,
|
||||
pub elevation_gain: f64,
|
||||
pub elevation_loss: f64,
|
||||
pub start_time: Option<i64>,
|
||||
pub end_time: Option<i64>,
|
||||
pub bounds: LngLatBounds,
|
||||
pub global: GlobalStatistics,
|
||||
pub local: Vec<TrackpointStatistics>,
|
||||
}
|
||||
|
||||
impl GPXStatistics {
|
||||
pub fn total_time(&self) -> Option<i64> {
|
||||
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<f64> {
|
||||
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<f64> {
|
||||
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<i64>) {
|
||||
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<f64>,
|
||||
pub moving_time: Option<i64>,
|
||||
pub elevation_gain: f64,
|
||||
pub elevation_loss: f64,
|
||||
pub start_time: Option<i64>,
|
||||
pub end_time: Option<i64>,
|
||||
pub bounds: LngLatBounds,
|
||||
}
|
||||
|
||||
fn sum_options<T>(a: Option<T>, b: Option<T>) -> Option<T>
|
||||
where
|
||||
T: Add<Output = T>,
|
||||
{
|
||||
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::*;
|
||||
|
||||
Reference in New Issue
Block a user