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 wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actions::parse,
|
|
||||||
gpx::GPXFile,
|
gpx::GPXFile,
|
||||||
|
io::parse,
|
||||||
|
selection::Selection,
|
||||||
stack::{Stack, StackEntry},
|
stack::{Stack, StackEntry},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ use crate::{
|
|||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct Controller {
|
pub struct Controller {
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
|
selection: Selection,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@@ -27,7 +29,8 @@ impl Controller {
|
|||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
stack: Stack::default(),
|
stack: Default::default(),
|
||||||
|
selection: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10,8 +10,39 @@ pub struct LngLat {
|
|||||||
pub lat: f64,
|
pub lat: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
pub struct LngLatBounds {
|
pub struct LngLatBounds {
|
||||||
pub sw: LngLat,
|
pub sw: LngLat,
|
||||||
pub ne: 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;
|
use quick_xml::reader::Reader;
|
||||||
|
|
||||||
enum GPXElement {
|
enum GPXElement {
|
||||||
METADATA,
|
Metadata,
|
||||||
NAME,
|
Name,
|
||||||
COMMENT,
|
Comment,
|
||||||
DESCRIPTION,
|
Description,
|
||||||
SOURCE,
|
Source,
|
||||||
AUTHOR(Author),
|
Author(Author),
|
||||||
LINK(Link),
|
Link(Link),
|
||||||
TEXT,
|
Text,
|
||||||
TRACK(Track),
|
Track(Track),
|
||||||
SEGMENT(TrackSegment),
|
Segment(TrackSegment),
|
||||||
TRACKPOINT(Trackpoint),
|
Trackpoint(Trackpoint),
|
||||||
WAYPOINT(Waypoint),
|
Waypoint(Waypoint),
|
||||||
ELEVATION,
|
Elevation,
|
||||||
TIME,
|
Time,
|
||||||
TEMPERATURE,
|
Temperature,
|
||||||
HEARTRATE,
|
Heartrate,
|
||||||
CADENCE,
|
Cadence,
|
||||||
POWER,
|
Power,
|
||||||
SYMBOL,
|
Symbol,
|
||||||
TYPE,
|
Type,
|
||||||
COLOR,
|
Color,
|
||||||
OPACITY,
|
Opacity,
|
||||||
WIDTH,
|
Width,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_coordinates(attributes: Attributes<'_>) -> LngLat {
|
fn parse_coordinates(attributes: Attributes<'_>) -> LngLat {
|
||||||
@@ -60,12 +60,12 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
|
|||||||
loop {
|
loop {
|
||||||
match reader.read_event_into(&mut buf) {
|
match reader.read_event_into(&mut buf) {
|
||||||
Ok(Event::Start(e)) => match e.name().as_ref() {
|
Ok(Event::Start(e)) => match e.name().as_ref() {
|
||||||
"metadata" => stack.push(GPXElement::METADATA),
|
"metadata" => stack.push(GPXElement::Metadata),
|
||||||
"name" => stack.push(GPXElement::NAME),
|
"name" => stack.push(GPXElement::Name),
|
||||||
"cmt" => stack.push(GPXElement::COMMENT),
|
"cmt" => stack.push(GPXElement::Comment),
|
||||||
"desc" => stack.push(GPXElement::DESCRIPTION),
|
"desc" => stack.push(GPXElement::Description),
|
||||||
"src" => stack.push(GPXElement::SOURCE),
|
"src" => stack.push(GPXElement::Source),
|
||||||
"author" => stack.push(GPXElement::AUTHOR(Author::default())),
|
"author" => stack.push(GPXElement::Author(Author::default())),
|
||||||
"link" => {
|
"link" => {
|
||||||
let mut link = Link::default();
|
let mut link = Link::default();
|
||||||
for attr in e.attributes() {
|
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),
|
"text" => stack.push(GPXElement::Text),
|
||||||
"trk" => stack.push(GPXElement::TRACK(Track::default())),
|
"trk" => stack.push(GPXElement::Track(Track::default())),
|
||||||
"trkseg" => {
|
"trkseg" => {
|
||||||
stack.push(GPXElement::SEGMENT(TrackSegment::default()));
|
stack.push(GPXElement::Segment(TrackSegment::default()));
|
||||||
}
|
}
|
||||||
"trkpt" => {
|
"trkpt" => {
|
||||||
let mut trkpt = Trackpoint::default();
|
let mut trkpt = Trackpoint::default();
|
||||||
trkpt.coordinates = parse_coordinates(e.attributes());
|
trkpt.coordinates = parse_coordinates(e.attributes());
|
||||||
stack.push(GPXElement::TRACKPOINT(trkpt));
|
stack.push(GPXElement::Trackpoint(trkpt));
|
||||||
}
|
}
|
||||||
"wpt" => {
|
"wpt" => {
|
||||||
let mut wpt = Waypoint::default();
|
let mut wpt = Waypoint::default();
|
||||||
wpt.coordinates = parse_coordinates(e.attributes());
|
wpt.coordinates = parse_coordinates(e.attributes());
|
||||||
stack.push(GPXElement::WAYPOINT(wpt));
|
stack.push(GPXElement::Waypoint(wpt));
|
||||||
}
|
}
|
||||||
"ele" => stack.push(GPXElement::ELEVATION),
|
"ele" => stack.push(GPXElement::Elevation),
|
||||||
"time" => stack.push(GPXElement::TIME),
|
"time" => stack.push(GPXElement::Time),
|
||||||
e if e.ends_with("atemp") => stack.push(GPXElement::TEMPERATURE),
|
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("hr") => stack.push(GPXElement::Heartrate),
|
||||||
e if e.ends_with("cad") => stack.push(GPXElement::CADENCE),
|
e if e.ends_with("cad") => stack.push(GPXElement::Cadence),
|
||||||
"power" => stack.push(GPXElement::POWER),
|
"power" => stack.push(GPXElement::Power),
|
||||||
e if e.ends_with("PowerInWatts") => stack.push(GPXElement::POWER),
|
e if e.ends_with("PowerInWatts") => stack.push(GPXElement::Power),
|
||||||
"sym" => stack.push(GPXElement::SYMBOL),
|
"sym" => stack.push(GPXElement::Symbol),
|
||||||
"type" => stack.push(GPXElement::TYPE),
|
"type" => stack.push(GPXElement::Type),
|
||||||
e if e.ends_with("color") => stack.push(GPXElement::COLOR),
|
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("opacity") => stack.push(GPXElement::Opacity),
|
||||||
e if e.ends_with("width") => stack.push(GPXElement::WIDTH),
|
e if e.ends_with("width") => stack.push(GPXElement::Width),
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
Ok(Event::End(e)) => match e.name().as_ref() {
|
Ok(Event::End(e)) => match e.name().as_ref() {
|
||||||
@@ -117,20 +117,20 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
|
|||||||
stack.pop();
|
stack.pop();
|
||||||
}
|
}
|
||||||
"author" => {
|
"author" => {
|
||||||
if let Some(GPXElement::AUTHOR(author)) = stack.pop() {
|
if let Some(GPXElement::Author(author)) = stack.pop() {
|
||||||
gpx.info.author = Some(author);
|
gpx.info.author = Some(author);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"link" => {
|
"link" => {
|
||||||
if let Some(GPXElement::LINK(link)) = stack.pop() {
|
if let Some(GPXElement::Link(link)) = stack.pop() {
|
||||||
match stack.last_mut() {
|
match stack.last_mut() {
|
||||||
Some(GPXElement::AUTHOR(author)) => {
|
Some(GPXElement::Author(author)) => {
|
||||||
author.link = Some(link);
|
author.link = Some(link);
|
||||||
}
|
}
|
||||||
Some(GPXElement::TRACK(trk)) => {
|
Some(GPXElement::Track(trk)) => {
|
||||||
trk.info.link = Some(link);
|
trk.info.link = Some(link);
|
||||||
}
|
}
|
||||||
Some(GPXElement::WAYPOINT(wpt)) => {
|
Some(GPXElement::Waypoint(wpt)) => {
|
||||||
wpt.link = Some(link);
|
wpt.link = Some(link);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
@@ -138,13 +138,13 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"trk" => {
|
"trk" => {
|
||||||
if let Some(GPXElement::TRACK(trk)) = stack.pop() {
|
if let Some(GPXElement::Track(trk)) = stack.pop() {
|
||||||
gpx.trk.push(trk);
|
gpx.trk.push(trk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"trkseg" => {
|
"trkseg" => {
|
||||||
if let Some(GPXElement::SEGMENT(mut trkseg)) = stack.pop() {
|
if let Some(GPXElement::Segment(mut trkseg)) = stack.pop() {
|
||||||
if let Some(GPXElement::TRACK(trk)) = stack.last_mut() {
|
if let Some(GPXElement::Track(trk)) = stack.last_mut() {
|
||||||
if !trkpt_chunk.trkpt.is_empty() {
|
if !trkpt_chunk.trkpt.is_empty() {
|
||||||
trkseg.push(trkpt_chunk);
|
trkseg.push(trkpt_chunk);
|
||||||
trkpt_chunk = TrackpointChunk::default();
|
trkpt_chunk = TrackpointChunk::default();
|
||||||
@@ -154,8 +154,8 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"trkpt" => {
|
"trkpt" => {
|
||||||
if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.pop() {
|
if let Some(GPXElement::Trackpoint(trkpt)) = stack.pop() {
|
||||||
if let Some(GPXElement::SEGMENT(trkseg)) = stack.last_mut() {
|
if let Some(GPXElement::Segment(trkseg)) = stack.last_mut() {
|
||||||
trkpt_chunk.trkpt.push(trkpt);
|
trkpt_chunk.trkpt.push(trkpt);
|
||||||
if trkpt_chunk.is_full() {
|
if trkpt_chunk.is_full() {
|
||||||
trkseg.push(trkpt_chunk);
|
trkseg.push(trkpt_chunk);
|
||||||
@@ -165,7 +165,7 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"wpt" => {
|
"wpt" => {
|
||||||
if let Some(GPXElement::WAYPOINT(wpt)) = stack.pop() {
|
if let Some(GPXElement::Waypoint(wpt)) = stack.pop() {
|
||||||
wpt_chunk.wpt.push(wpt);
|
wpt_chunk.wpt.push(wpt);
|
||||||
if wpt_chunk.is_full() {
|
if wpt_chunk.is_full() {
|
||||||
gpx.wpt.push(Rc::new(wpt_chunk));
|
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() {
|
Ok(Event::Text(e)) => match stack.last_mut() {
|
||||||
Some(GPXElement::NAME) => {
|
Some(GPXElement::Name) => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
match stack.last_mut() {
|
match stack.last_mut() {
|
||||||
Some(GPXElement::METADATA) => {
|
Some(GPXElement::Metadata) => {
|
||||||
gpx.info.name = e.to_string();
|
gpx.info.name = e.to_string();
|
||||||
}
|
}
|
||||||
Some(GPXElement::AUTHOR(author)) => {
|
Some(GPXElement::Author(author)) => {
|
||||||
author.name = Some(e.to_string());
|
author.name = Some(e.to_string());
|
||||||
}
|
}
|
||||||
Some(GPXElement::TRACK(trk)) => {
|
Some(GPXElement::Track(trk)) => {
|
||||||
trk.info.name = Some(e.to_string());
|
trk.info.name = Some(e.to_string());
|
||||||
}
|
}
|
||||||
Some(GPXElement::WAYPOINT(wpt)) => {
|
Some(GPXElement::Waypoint(wpt)) => {
|
||||||
wpt.name = Some(e.to_string());
|
wpt.name = Some(e.to_string());
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(GPXElement::COMMENT) => {
|
Some(GPXElement::Comment) => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
match stack.last_mut() {
|
match stack.last_mut() {
|
||||||
Some(GPXElement::TRACK(trk)) => {
|
Some(GPXElement::Track(trk)) => {
|
||||||
trk.info.cmt = Some(e.to_string());
|
trk.info.cmt = Some(e.to_string());
|
||||||
}
|
}
|
||||||
Some(GPXElement::WAYPOINT(wpt)) => {
|
Some(GPXElement::Waypoint(wpt)) => {
|
||||||
wpt.cmt = Some(e.to_string());
|
wpt.cmt = Some(e.to_string());
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(GPXElement::DESCRIPTION) => {
|
Some(GPXElement::Description) => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
match stack.last_mut() {
|
match stack.last_mut() {
|
||||||
Some(GPXElement::METADATA) => {
|
Some(GPXElement::Metadata) => {
|
||||||
gpx.info.desc = Some(e.to_string());
|
gpx.info.desc = Some(e.to_string());
|
||||||
}
|
}
|
||||||
Some(GPXElement::TRACK(trk)) => {
|
Some(GPXElement::Track(trk)) => {
|
||||||
trk.info.desc = Some(e.to_string());
|
trk.info.desc = Some(e.to_string());
|
||||||
}
|
}
|
||||||
Some(GPXElement::WAYPOINT(wpt)) => {
|
Some(GPXElement::Waypoint(wpt)) => {
|
||||||
wpt.desc = Some(e.to_string());
|
wpt.desc = Some(e.to_string());
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(GPXElement::SOURCE) => {
|
Some(GPXElement::Source) => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
match stack.last_mut() {
|
match stack.last_mut() {
|
||||||
Some(GPXElement::TRACK(trk)) => {
|
Some(GPXElement::Track(trk)) => {
|
||||||
trk.info.src = Some(e.to_string());
|
trk.info.src = Some(e.to_string());
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(GPXElement::TEXT) => {
|
Some(GPXElement::Text) => {
|
||||||
stack.pop();
|
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());
|
link.text = Some(e.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(GPXElement::ELEVATION) => {
|
Some(GPXElement::Elevation) => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
match stack.last_mut() {
|
match stack.last_mut() {
|
||||||
Some(GPXElement::TRACKPOINT(trkpt)) => {
|
Some(GPXElement::Trackpoint(trkpt)) => {
|
||||||
trkpt.ele = e.parse().unwrap_or_default();
|
trkpt.ele = e.parse().unwrap_or_default();
|
||||||
}
|
}
|
||||||
Some(GPXElement::WAYPOINT(wpt)) => {
|
Some(GPXElement::Waypoint(wpt)) => {
|
||||||
wpt.ele = e.parse().unwrap_or_default();
|
wpt.ele = e.parse().unwrap_or_default();
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(GPXElement::TIME) => {
|
Some(GPXElement::Time) => {
|
||||||
stack.pop();
|
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()) {
|
if let Ok(time) = DateTime::parse_from_rfc3339(e.as_ref()) {
|
||||||
trkpt.time = Some(time.timestamp_millis());
|
trkpt.time = Some(time.timestamp_millis());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(GPXElement::TEMPERATURE) => {
|
Some(GPXElement::Temperature) => {
|
||||||
stack.pop();
|
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();
|
trkpt.atemp = e.parse().ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(GPXElement::HEARTRATE) => {
|
Some(GPXElement::Heartrate) => {
|
||||||
stack.pop();
|
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();
|
trkpt.hr = e.parse().ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(GPXElement::CADENCE) => {
|
Some(GPXElement::Cadence) => {
|
||||||
stack.pop();
|
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();
|
trkpt.cad = e.parse().ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(GPXElement::POWER) => {
|
Some(GPXElement::Power) => {
|
||||||
stack.pop();
|
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();
|
trkpt.power = e.parse().ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(GPXElement::SYMBOL) => {
|
Some(GPXElement::Symbol) => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
match stack.last_mut() {
|
match stack.last_mut() {
|
||||||
Some(GPXElement::WAYPOINT(wpt)) => {
|
Some(GPXElement::Waypoint(wpt)) => {
|
||||||
wpt.sym = Some(e.to_string());
|
wpt.sym = Some(e.to_string());
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(GPXElement::TYPE) => {
|
Some(GPXElement::Type) => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
match stack.last_mut() {
|
match stack.last_mut() {
|
||||||
Some(GPXElement::TRACK(trk)) => {
|
Some(GPXElement::Track(trk)) => {
|
||||||
trk.info.type_ = Some(e.to_string());
|
trk.info.type_ = Some(e.to_string());
|
||||||
}
|
}
|
||||||
Some(GPXElement::WAYPOINT(wpt)) => {
|
Some(GPXElement::Waypoint(wpt)) => {
|
||||||
wpt.type_ = Some(e.to_string());
|
wpt.type_ = Some(e.to_string());
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(GPXElement::COLOR) => {
|
Some(GPXElement::Color) => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
match stack.last_mut() {
|
match stack.last_mut() {
|
||||||
Some(GPXElement::TRACK(trk)) => {
|
Some(GPXElement::Track(trk)) => {
|
||||||
let mut color = "#".to_string();
|
let mut color = "#".to_string();
|
||||||
color.push_str(&e);
|
color.push_str(&e);
|
||||||
trk.info.color = Some(color);
|
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();
|
stack.pop();
|
||||||
match stack.last_mut() {
|
match stack.last_mut() {
|
||||||
Some(GPXElement::TRACK(trk)) => {
|
Some(GPXElement::Track(trk)) => {
|
||||||
trk.info.opacity = e.parse().ok();
|
trk.info.opacity = e.parse().ok();
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(GPXElement::WIDTH) => {
|
Some(GPXElement::Width) => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
match stack.last_mut() {
|
match stack.last_mut() {
|
||||||
Some(GPXElement::TRACK(trk)) => {
|
Some(GPXElement::Track(trk)) => {
|
||||||
trk.info.width = e.parse().ok();
|
trk.info.width = e.parse().ok();
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
+5
-3
@@ -1,7 +1,9 @@
|
|||||||
mod actions;
|
mod action;
|
||||||
mod controller;
|
mod algorithm;
|
||||||
|
mod engine;
|
||||||
mod gpx;
|
mod gpx;
|
||||||
|
mod io;
|
||||||
|
mod selection;
|
||||||
mod stack;
|
mod stack;
|
||||||
mod statistics;
|
mod statistics;
|
||||||
mod utils;
|
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::{
|
use crate::{
|
||||||
algorithms::ramer_douglas_peucker,
|
algorithm::ramer_douglas_peucker,
|
||||||
for_each_window,
|
for_each_window,
|
||||||
gpx::{LngLat, LngLatBounds, TrackSegment, Trackpoint},
|
gpx::{LngLat, LngLatBounds, TrackSegment, Trackpoint},
|
||||||
utils::{distance, slope, speed},
|
utils::{distance, slope, speed},
|
||||||
@@ -7,29 +9,27 @@ use crate::{
|
|||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct GPXStatistics {
|
pub struct GPXStatistics {
|
||||||
pub total_distance: f64,
|
pub global: GlobalStatistics,
|
||||||
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 local: Vec<TrackpointStatistics>,
|
pub local: Vec<TrackpointStatistics>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GPXStatistics {
|
impl GPXStatistics {
|
||||||
pub fn total_time(&self) -> Option<i64> {
|
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> {
|
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> {
|
pub fn moving_speed(&self) -> Option<f64> {
|
||||||
self.moving_distance
|
self.global
|
||||||
.zip(self.moving_time)
|
.moving_distance
|
||||||
|
.zip(self.global.moving_time)
|
||||||
.map(|(d, t)| speed(d, t))
|
.map(|(d, t)| speed(d, t))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ impl GPXStatistics {
|
|||||||
fn accumulate(&mut self, prev: &Trackpoint, cur: &Trackpoint) {
|
fn accumulate(&mut self, prev: &Trackpoint, cur: &Trackpoint) {
|
||||||
self.accumulate_distance_and_time(prev, cur);
|
self.accumulate_distance_and_time(prev, cur);
|
||||||
self.update_time_bounds(cur.time);
|
self.update_time_bounds(cur.time);
|
||||||
self.update_bounds(&cur.coordinates);
|
self.update_bounds(cur.coordinates);
|
||||||
self.local
|
self.local
|
||||||
.push(TrackpointStatistics::from_partial_stats(&self));
|
.push(TrackpointStatistics::from_partial_stats(&self));
|
||||||
}
|
}
|
||||||
@@ -63,31 +63,34 @@ impl GPXStatistics {
|
|||||||
let dist = distance(prev.coordinates, cur.coordinates);
|
let dist = distance(prev.coordinates, cur.coordinates);
|
||||||
let time = cur.time_diff(prev);
|
let time = cur.time_diff(prev);
|
||||||
|
|
||||||
self.total_distance += dist;
|
self.global.total_distance += dist;
|
||||||
|
|
||||||
if let Some(time) = time {
|
if let Some(time) = time {
|
||||||
let speed = speed(dist, time);
|
let speed = speed(dist, time);
|
||||||
if speed >= 0.5 && speed <= 1500.0 {
|
if speed >= 0.5 && speed <= 1500.0 {
|
||||||
self.moving_distance = self.moving_distance.map_or(Some(dist), |d| Some(d + dist));
|
self.global.moving_distance = self
|
||||||
self.moving_time = self.moving_time.map_or(Some(time), |t| Some(t + time));
|
.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>) {
|
fn update_time_bounds(&mut self, time: Option<i64>) {
|
||||||
if let Some(time) = time {
|
if let Some(time) = time {
|
||||||
if self.start_time.is_none() {
|
if self.global.start_time.is_none() {
|
||||||
self.start_time = Some(time);
|
self.global.start_time = Some(time);
|
||||||
}
|
}
|
||||||
self.end_time = Some(time);
|
self.global.end_time = Some(time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_bounds(&mut self, coordinates: &LngLat) {
|
fn update_bounds(&mut self, coordinates: LngLat) {
|
||||||
self.bounds.sw.lng = self.bounds.sw.lng.min(coordinates.lng);
|
self.global.bounds.extend(coordinates);
|
||||||
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 compute_smoothed_speed(&mut self, trkseg: &TrackSegment) {
|
fn compute_smoothed_speed(&mut self, trkseg: &TrackSegment) {
|
||||||
@@ -177,14 +180,14 @@ impl GPXStatistics {
|
|||||||
|
|
||||||
let delta = smoothed_ele - prev_smoothed_ele;
|
let delta = smoothed_ele - prev_smoothed_ele;
|
||||||
if delta > 0.0 {
|
if delta > 0.0 {
|
||||||
self.elevation_gain += delta;
|
self.global.elevation_gain += delta;
|
||||||
} else if delta < 0.0 {
|
} else if delta < 0.0 {
|
||||||
self.elevation_loss -= delta;
|
self.global.elevation_loss -= delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
if i < end || last {
|
if i < end || last {
|
||||||
self.local[flat_i].elevation_gain = self.elevation_gain;
|
self.local[flat_i].elevation_gain = self.global.elevation_gain;
|
||||||
self.local[flat_i].elevation_loss = self.elevation_loss;
|
self.local[flat_i].elevation_loss = self.global.elevation_loss;
|
||||||
}
|
}
|
||||||
|
|
||||||
prev_smoothed_ele = smoothed_ele;
|
prev_smoothed_ele = smoothed_ele;
|
||||||
@@ -230,9 +233,38 @@ impl GPXStatistics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct SlopeSegment {
|
pub struct GlobalStatistics {
|
||||||
pub slope: f64,
|
pub total_distance: f64,
|
||||||
pub 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)]
|
#[derive(Default, Debug)]
|
||||||
@@ -251,24 +283,31 @@ pub struct TrackpointStatistics {
|
|||||||
impl TrackpointStatistics {
|
impl TrackpointStatistics {
|
||||||
fn from_partial_stats(stats: &GPXStatistics) -> Self {
|
fn from_partial_stats(stats: &GPXStatistics) -> Self {
|
||||||
Self {
|
Self {
|
||||||
total_distance: stats.total_distance,
|
total_distance: stats.global.total_distance,
|
||||||
moving_distance: stats.moving_distance,
|
moving_distance: stats.global.moving_distance,
|
||||||
total_time: stats.start_time.zip(stats.end_time).map(|(t1, t2)| t2 - t1),
|
total_time: stats.total_time(),
|
||||||
moving_time: stats.moving_time,
|
moving_time: stats.global.moving_time,
|
||||||
|
// stats below are computed later
|
||||||
speed: None,
|
speed: None,
|
||||||
elevation_gain: stats.elevation_gain,
|
elevation_gain: Default::default(),
|
||||||
elevation_loss: stats.elevation_loss,
|
elevation_loss: Default::default(),
|
||||||
slope: Default::default(),
|
slope: Default::default(),
|
||||||
slope_segment: Default::default(),
|
slope_segment: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct SlopeSegment {
|
||||||
|
pub slope: f64,
|
||||||
|
pub distance: f64,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{fs::File, io::Read};
|
use std::{fs::File, io::Read};
|
||||||
|
|
||||||
use crate::actions::parse;
|
use crate::io::parse;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
Reference in New Issue
Block a user