mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-09-24 08:27:37 +00:00
progress
This commit is contained in:
Generated
+29
@@ -88,12 +88,24 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpx-rs"
|
name = "gpx-rs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
|
"uuid",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -198,6 +210,12 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.23"
|
version = "1.0.23"
|
||||||
@@ -233,6 +251,17 @@ version = "1.0.24"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.26.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.126"
|
version = "0.2.126"
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ crate-type = ["cdylib"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.45"
|
chrono = "0.4.45"
|
||||||
quick-xml = "0.42.0"
|
quick-xml = "0.42.0"
|
||||||
|
uuid = {version = "1.26.1", features = ["v4", "js"]}
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
|
|||||||
+39
-69
@@ -1,7 +1,7 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::gpx::{
|
use crate::gpx::{
|
||||||
Author, GPXFile, Link, LngLat, Track, TrackPoint, TrackPointChunk, TrackSegment, Waypoint,
|
Author, GPXFile, Link, LngLat, Track, TrackSegment, Trackpoint, TrackpointChunk, Waypoint,
|
||||||
WaypointChunk,
|
WaypointChunk,
|
||||||
};
|
};
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
@@ -21,7 +21,7 @@ enum GPXElement {
|
|||||||
TEXT,
|
TEXT,
|
||||||
TRACK(Track),
|
TRACK(Track),
|
||||||
SEGMENT(TrackSegment),
|
SEGMENT(TrackSegment),
|
||||||
TRACKPOINT(TrackPoint),
|
TRACKPOINT(Trackpoint),
|
||||||
WAYPOINT(Waypoint),
|
WAYPOINT(Waypoint),
|
||||||
ELEVATION,
|
ELEVATION,
|
||||||
TIME,
|
TIME,
|
||||||
@@ -55,7 +55,7 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
|
|||||||
let mut buf = vec![];
|
let mut buf = vec![];
|
||||||
let mut gpx = GPXFile::default();
|
let mut gpx = GPXFile::default();
|
||||||
let mut stack: Vec<GPXElement> = vec![];
|
let mut stack: Vec<GPXElement> = vec![];
|
||||||
let mut trkpt_chunk = TrackPointChunk::default();
|
let mut trkpt_chunk = TrackpointChunk::default();
|
||||||
let mut wpt_chunk = WaypointChunk::default();
|
let mut wpt_chunk = WaypointChunk::default();
|
||||||
loop {
|
loop {
|
||||||
match reader.read_event_into(&mut buf) {
|
match reader.read_event_into(&mut buf) {
|
||||||
@@ -83,7 +83,7 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
|
|||||||
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));
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
|
|||||||
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),
|
||||||
_ => println!("{:?}", e),
|
_ => (),
|
||||||
},
|
},
|
||||||
Ok(Event::End(e)) => match e.name().as_ref() {
|
Ok(Event::End(e)) => match e.name().as_ref() {
|
||||||
"gpx" => {
|
"gpx" => {
|
||||||
@@ -146,8 +146,8 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
|
|||||||
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.chunks.push(Rc::new(trkpt_chunk));
|
trkseg.push(trkpt_chunk);
|
||||||
trkpt_chunk = TrackPointChunk::default();
|
trkpt_chunk = TrackpointChunk::default();
|
||||||
}
|
}
|
||||||
trk.trkseg.push(trkseg);
|
trk.trkseg.push(trkseg);
|
||||||
}
|
}
|
||||||
@@ -158,8 +158,8 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
|
|||||||
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.chunks.push(Rc::new(trkpt_chunk));
|
trkseg.push(trkpt_chunk);
|
||||||
trkpt_chunk = TrackPointChunk::default();
|
trkpt_chunk = TrackpointChunk::default();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -383,10 +383,8 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(trk.trkseg.len(), 1);
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
let trkseg = &trk.trkseg[0];
|
let trkseg = &trk.trkseg[0];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert_eq!(trkseg.len(), 80);
|
||||||
let chunk = &trkseg.chunks[0];
|
let trkpt = &trkseg[0];
|
||||||
assert_eq!(chunk.trkpt.len(), 80);
|
|
||||||
let trkpt = &chunk.trkpt[0];
|
|
||||||
assert_eq!(trkpt.coordinates.lat, 50.790867);
|
assert_eq!(trkpt.coordinates.lat, 50.790867);
|
||||||
assert_eq!(trkpt.coordinates.lng, 4.404968);
|
assert_eq!(trkpt.coordinates.lng, 4.404968);
|
||||||
assert_eq!(trkpt.ele, 109.0);
|
assert_eq!(trkpt.ele, 109.0);
|
||||||
@@ -406,10 +404,8 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(trk.trkseg.len(), 1);
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
let trkseg = &trk.trkseg[0];
|
let trkseg = &trk.trkseg[0];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert_eq!(trkseg.len(), 49);
|
||||||
let chunk = &trkseg.chunks[0];
|
let trkpt = &trkseg[0];
|
||||||
assert_eq!(chunk.trkpt.len(), 49);
|
|
||||||
let trkpt = &chunk.trkpt[0];
|
|
||||||
assert_eq!(trkpt.coordinates.lat, 50.790867);
|
assert_eq!(trkpt.coordinates.lat, 50.790867);
|
||||||
assert_eq!(trkpt.coordinates.lng, 4.404968);
|
assert_eq!(trkpt.coordinates.lng, 4.404968);
|
||||||
assert_eq!(trkpt.ele, 109.0);
|
assert_eq!(trkpt.ele, 109.0);
|
||||||
@@ -420,10 +416,8 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(trk.trkseg.len(), 1);
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
let trkseg = &trk.trkseg[0];
|
let trkseg = &trk.trkseg[0];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert_eq!(trkseg.len(), 28);
|
||||||
let chunk = &trkseg.chunks[0];
|
let trkpt = &trkseg[0];
|
||||||
assert_eq!(chunk.trkpt.len(), 28);
|
|
||||||
let trkpt = &chunk.trkpt[0];
|
|
||||||
assert_eq!(trkpt.coordinates.lat, 50.782212);
|
assert_eq!(trkpt.coordinates.lat, 50.782212);
|
||||||
assert_eq!(trkpt.coordinates.lng, 4.406377);
|
assert_eq!(trkpt.coordinates.lng, 4.406377);
|
||||||
assert_eq!(trkpt.ele, 115.5);
|
assert_eq!(trkpt.ele, 115.5);
|
||||||
@@ -441,19 +435,15 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(trk.trkseg.len(), 2);
|
assert_eq!(trk.trkseg.len(), 2);
|
||||||
let trkseg = &trk.trkseg[0];
|
let trkseg = &trk.trkseg[0];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert_eq!(trkseg.len(), 49);
|
||||||
let chunk = &trkseg.chunks[0];
|
let trkpt = &trkseg[0];
|
||||||
assert_eq!(chunk.trkpt.len(), 49);
|
|
||||||
let trkpt = &chunk.trkpt[0];
|
|
||||||
assert_eq!(trkpt.coordinates.lat, 50.790867);
|
assert_eq!(trkpt.coordinates.lat, 50.790867);
|
||||||
assert_eq!(trkpt.coordinates.lng, 4.404968);
|
assert_eq!(trkpt.coordinates.lng, 4.404968);
|
||||||
assert_eq!(trkpt.ele, 109.0);
|
assert_eq!(trkpt.ele, 109.0);
|
||||||
|
|
||||||
let trkseg = &trk.trkseg[1];
|
let trkseg = &trk.trkseg[1];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert_eq!(trkseg.len(), 28);
|
||||||
let chunk = &trkseg.chunks[0];
|
let trkpt = &trkseg[0];
|
||||||
assert_eq!(chunk.trkpt.len(), 28);
|
|
||||||
let trkpt = &chunk.trkpt[0];
|
|
||||||
assert_eq!(trkpt.coordinates.lat, 50.782212);
|
assert_eq!(trkpt.coordinates.lat, 50.782212);
|
||||||
assert_eq!(trkpt.coordinates.lng, 4.406377);
|
assert_eq!(trkpt.coordinates.lng, 4.406377);
|
||||||
assert_eq!(trkpt.ele, 115.5);
|
assert_eq!(trkpt.ele, 115.5);
|
||||||
@@ -471,19 +461,15 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(trk.trkseg.len(), 2);
|
assert_eq!(trk.trkseg.len(), 2);
|
||||||
let trkseg = &trk.trkseg[0];
|
let trkseg = &trk.trkseg[0];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert_eq!(trkseg.len(), 16);
|
||||||
let chunk = &trkseg.chunks[0];
|
let trkpt = &trkseg[0];
|
||||||
assert_eq!(chunk.trkpt.len(), 16);
|
|
||||||
let trkpt = &chunk.trkpt[0];
|
|
||||||
assert_eq!(trkpt.coordinates.lat, 50.790867);
|
assert_eq!(trkpt.coordinates.lat, 50.790867);
|
||||||
assert_eq!(trkpt.coordinates.lng, 4.404968);
|
assert_eq!(trkpt.coordinates.lng, 4.404968);
|
||||||
assert_eq!(trkpt.ele, 109.0);
|
assert_eq!(trkpt.ele, 109.0);
|
||||||
|
|
||||||
let trkseg = &trk.trkseg[1];
|
let trkseg = &trk.trkseg[1];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert_eq!(trkseg.len(), 34);
|
||||||
let chunk = &trkseg.chunks[0];
|
let trkpt = &trkseg[0];
|
||||||
assert_eq!(chunk.trkpt.len(), 34);
|
|
||||||
let trkpt = &chunk.trkpt[0];
|
|
||||||
assert_eq!(trkpt.coordinates.lat, 50.78727108169855);
|
assert_eq!(trkpt.coordinates.lat, 50.78727108169855);
|
||||||
assert_eq!(trkpt.coordinates.lng, 4.406133681127736);
|
assert_eq!(trkpt.coordinates.lng, 4.406133681127736);
|
||||||
assert_eq!(trkpt.ele, 115.0);
|
assert_eq!(trkpt.ele, 115.0);
|
||||||
@@ -492,19 +478,15 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(trk.trkseg.len(), 2);
|
assert_eq!(trk.trkseg.len(), 2);
|
||||||
let trkseg = &trk.trkseg[0];
|
let trkseg = &trk.trkseg[0];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert_eq!(trkseg.len(), 19);
|
||||||
let chunk = &trkseg.chunks[0];
|
let trkpt = &trkseg[0];
|
||||||
assert_eq!(chunk.trkpt.len(), 19);
|
|
||||||
let trkpt = &chunk.trkpt[0];
|
|
||||||
assert_eq!(trkpt.coordinates.lat, 50.782212);
|
assert_eq!(trkpt.coordinates.lat, 50.782212);
|
||||||
assert_eq!(trkpt.coordinates.lng, 4.406377);
|
assert_eq!(trkpt.coordinates.lng, 4.406377);
|
||||||
assert_eq!(trkpt.ele, 115.5);
|
assert_eq!(trkpt.ele, 115.5);
|
||||||
|
|
||||||
let trkseg = &trk.trkseg[1];
|
let trkseg = &trk.trkseg[1];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert_eq!(trkseg.len(), 10);
|
||||||
let chunk = &trkseg.chunks[0];
|
let trkpt = &trkseg[0];
|
||||||
assert_eq!(chunk.trkpt.len(), 10);
|
|
||||||
let trkpt = &chunk.trkpt[0];
|
|
||||||
assert_eq!(trkpt.coordinates.lat, 50.77906316558724);
|
assert_eq!(trkpt.coordinates.lat, 50.77906316558724);
|
||||||
assert_eq!(trkpt.coordinates.lng, 4.412547477922485);
|
assert_eq!(trkpt.coordinates.lng, 4.412547477922485);
|
||||||
assert_eq!(trkpt.ele, 133.3);
|
assert_eq!(trkpt.ele, 133.3);
|
||||||
@@ -553,10 +535,8 @@ mod tests {
|
|||||||
let trk = &gpx.trk[0];
|
let trk = &gpx.trk[0];
|
||||||
assert_eq!(trk.trkseg.len(), 1);
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
let trkseg = &trk.trkseg[0];
|
let trkseg = &trk.trkseg[0];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert!(trkseg.len() > 0);
|
||||||
let chunk = &trkseg.chunks[0];
|
let trkpt = &trkseg[0];
|
||||||
assert!(!chunk.trkpt.is_empty());
|
|
||||||
let trkpt = &chunk.trkpt[0];
|
|
||||||
assert!(trkpt.time.is_some_and(|t| t == 1704063600000));
|
assert!(trkpt.time.is_some_and(|t| t == 1704063600000));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,10 +551,8 @@ mod tests {
|
|||||||
let trk = &gpx.trk[0];
|
let trk = &gpx.trk[0];
|
||||||
assert_eq!(trk.trkseg.len(), 1);
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
let trkseg = &trk.trkseg[0];
|
let trkseg = &trk.trkseg[0];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert!(trkseg.len() > 0);
|
||||||
let chunk = &trkseg.chunks[0];
|
let trkpt = &trkseg[0];
|
||||||
assert!(!chunk.trkpt.is_empty());
|
|
||||||
let trkpt = &chunk.trkpt[0];
|
|
||||||
assert!(trkpt.hr.is_some_and(|h| h == 150));
|
assert!(trkpt.hr.is_some_and(|h| h == 150));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,10 +567,8 @@ mod tests {
|
|||||||
let trk = &gpx.trk[0];
|
let trk = &gpx.trk[0];
|
||||||
assert_eq!(trk.trkseg.len(), 1);
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
let trkseg = &trk.trkseg[0];
|
let trkseg = &trk.trkseg[0];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert!(trkseg.len() > 0);
|
||||||
let chunk = &trkseg.chunks[0];
|
let trkpt = &trkseg[0];
|
||||||
assert!(!chunk.trkpt.is_empty());
|
|
||||||
let trkpt = &chunk.trkpt[0];
|
|
||||||
assert!(trkpt.cad.is_some_and(|c| c == 80));
|
assert!(trkpt.cad.is_some_and(|c| c == 80));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -607,10 +583,8 @@ mod tests {
|
|||||||
let trk = &gpx.trk[0];
|
let trk = &gpx.trk[0];
|
||||||
assert_eq!(trk.trkseg.len(), 1);
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
let trkseg = &trk.trkseg[0];
|
let trkseg = &trk.trkseg[0];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert!(trkseg.len() > 0);
|
||||||
let chunk = &trkseg.chunks[0];
|
let trkpt = &trkseg[0];
|
||||||
assert!(!chunk.trkpt.is_empty());
|
|
||||||
let trkpt = &chunk.trkpt[0];
|
|
||||||
assert!(trkpt.power.is_some_and(|p| p == 200));
|
assert!(trkpt.power.is_some_and(|p| p == 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,10 +599,8 @@ mod tests {
|
|||||||
let trk = &gpx.trk[0];
|
let trk = &gpx.trk[0];
|
||||||
assert_eq!(trk.trkseg.len(), 1);
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
let trkseg = &trk.trkseg[0];
|
let trkseg = &trk.trkseg[0];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert!(trkseg.len() > 0);
|
||||||
let chunk = &trkseg.chunks[0];
|
let trkpt = &trkseg[0];
|
||||||
assert!(!chunk.trkpt.is_empty());
|
|
||||||
let trkpt = &chunk.trkpt[0];
|
|
||||||
assert!(trkpt.power.is_some_and(|p| p == 200));
|
assert!(trkpt.power.is_some_and(|p| p == 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -643,10 +615,8 @@ mod tests {
|
|||||||
let trk = &gpx.trk[0];
|
let trk = &gpx.trk[0];
|
||||||
assert_eq!(trk.trkseg.len(), 1);
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
let trkseg = &trk.trkseg[0];
|
let trkseg = &trk.trkseg[0];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert!(trkseg.len() > 0);
|
||||||
let chunk = &trkseg.chunks[0];
|
let trkpt = &trkseg[0];
|
||||||
assert!(!chunk.trkpt.is_empty());
|
|
||||||
let trkpt = &chunk.trkpt[0];
|
|
||||||
assert!(trkpt.atemp.is_some_and(|t| t == 21));
|
assert!(trkpt.atemp.is_some_and(|t| t == 21));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +1,65 @@
|
|||||||
use crate::gpx::TrackPoint;
|
use crate::gpx::{TrackSegment, TrackSegmentIndex};
|
||||||
|
|
||||||
pub fn ramer_douglas_peucker<F>(n: usize, distance: &F, epsilon: f64) -> Vec<usize>
|
pub fn ramer_douglas_peucker<F, T>(
|
||||||
|
trkseg: &TrackSegment,
|
||||||
|
distance: &F,
|
||||||
|
epsilon: T,
|
||||||
|
) -> Vec<TrackSegmentIndex>
|
||||||
where
|
where
|
||||||
F: Fn(usize, usize, usize) -> f64,
|
F: Fn(TrackSegmentIndex, TrackSegmentIndex, TrackSegmentIndex) -> T,
|
||||||
|
T: Default + PartialOrd + Copy,
|
||||||
{
|
{
|
||||||
if n <= 2 {
|
match trkseg.len() {
|
||||||
(0..n).collect()
|
0 => vec![],
|
||||||
} else {
|
1 => vec![trkseg.first_index().unwrap()],
|
||||||
let mut indices = vec![0];
|
2 => vec![trkseg.last_index().unwrap()],
|
||||||
ramer_douglas_peucker_helper(0, n - 1, distance, epsilon, &mut indices);
|
_ => {
|
||||||
indices.push(n - 1);
|
let first = trkseg.first_index().unwrap();
|
||||||
|
let last = trkseg.last_index().unwrap();
|
||||||
|
|
||||||
|
let mut indices = vec![first];
|
||||||
|
ramer_douglas_peucker_helper(trkseg, first, last, distance, epsilon, &mut indices);
|
||||||
|
indices.push(last);
|
||||||
indices
|
indices
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn ramer_douglas_peucker_helper<F>(
|
fn ramer_douglas_peucker_helper<F, T>(
|
||||||
start: usize,
|
trkseg: &TrackSegment,
|
||||||
end: usize,
|
start: TrackSegmentIndex,
|
||||||
|
end: TrackSegmentIndex,
|
||||||
distance: &F,
|
distance: &F,
|
||||||
epsilon: f64,
|
epsilon: T,
|
||||||
indices: &mut Vec<usize>,
|
indices: &mut Vec<TrackSegmentIndex>,
|
||||||
) where
|
) where
|
||||||
F: Fn(usize, usize, usize) -> f64,
|
F: Fn(TrackSegmentIndex, TrackSegmentIndex, TrackSegmentIndex) -> T,
|
||||||
|
T: Default + PartialOrd + Copy,
|
||||||
{
|
{
|
||||||
let mut idx = 0;
|
let mut max_idx = None;
|
||||||
let mut max_dist = 0.0;
|
let mut max_dist = T::default();
|
||||||
|
|
||||||
for i in (start + 1)..end {
|
let mut cur = trkseg.next_index(Some(start));
|
||||||
let dist = distance(start, end, i);
|
|
||||||
|
while let Some(idx) = cur {
|
||||||
|
if idx == end {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dist = distance(start, end, idx);
|
||||||
if dist > max_dist {
|
if dist > max_dist {
|
||||||
idx = i;
|
max_idx = Some(idx);
|
||||||
max_dist = dist;
|
max_dist = dist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cur = trkseg.next_index(cur);
|
||||||
}
|
}
|
||||||
|
|
||||||
if max_dist > epsilon && idx != 0 {
|
if let Some(idx) = max_idx {
|
||||||
ramer_douglas_peucker_helper(start, idx, distance, epsilon, indices);
|
if max_dist > epsilon {
|
||||||
|
ramer_douglas_peucker_helper(trkseg, start, idx, distance, epsilon, indices);
|
||||||
indices.push(idx);
|
indices.push(idx);
|
||||||
ramer_douglas_peucker_helper(idx, end, distance, epsilon, indices);
|
ramer_douglas_peucker_helper(trkseg, idx, end, distance, epsilon, indices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,50 @@
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! for_each_window {
|
macro_rules! for_each_window {
|
||||||
(
|
(
|
||||||
|
$trkseg:expr,
|
||||||
$left:expr,
|
$left:expr,
|
||||||
$right:expr,
|
$right:expr,
|
||||||
$window:expr,
|
$window:expr,
|
||||||
|$a:ident, $b:ident| $distance:expr,
|
|$a:ident, $b:ident| $distance:expr,
|
||||||
|$i:ident, $l:ident, $r:ident| $body:block,
|
|$i:ident, $l:ident, $r:ident| $body:block,
|
||||||
) => {{
|
) => {{
|
||||||
let mut start = $left;
|
let mut start = $left.unwrap_or_default();
|
||||||
|
let mut cur = $left;
|
||||||
for $i in $left..$right {
|
while let Some(idx) = cur {
|
||||||
while start + 1 < $i && {
|
// advance left if needed
|
||||||
|
while start != idx && {
|
||||||
let $a = start;
|
let $a = start;
|
||||||
let $b = $i;
|
let $b = idx;
|
||||||
$distance
|
$distance
|
||||||
} > $window
|
} > $window {
|
||||||
{
|
if let Some(next) = $trkseg.next_index(Some(start)) {
|
||||||
start += 1;
|
if next == idx {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
start = next;
|
||||||
let mut end = $right.min($i + 2);
|
} else {
|
||||||
|
break;
|
||||||
while end < $right && {
|
}
|
||||||
let $a = $i;
|
}
|
||||||
|
// advance right if needed
|
||||||
|
let mut end = idx;
|
||||||
|
while let Some(next) = $trkseg.next_index(Some(end)) {
|
||||||
|
if {
|
||||||
|
let $a = idx;
|
||||||
let $b = end;
|
let $b = end;
|
||||||
$distance
|
$distance
|
||||||
} <= $window
|
} > $window {
|
||||||
{
|
break;
|
||||||
end += 1;
|
|
||||||
}
|
}
|
||||||
|
end = next;
|
||||||
|
}
|
||||||
|
// apply body
|
||||||
|
let $i = idx;
|
||||||
let $l = start;
|
let $l = start;
|
||||||
let $r = end - 1;
|
let $r = end;
|
||||||
|
|
||||||
$body
|
$body
|
||||||
|
// go next
|
||||||
|
cur = $trkseg.next_index(cur);
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,21 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use crate::stack::Stack;
|
use crate::{
|
||||||
|
actions::parse,
|
||||||
|
gpx::GPXFile,
|
||||||
|
stack::{Stack, StackEntry},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Control flow
|
||||||
|
// - get lock to cancel any other updates
|
||||||
|
// - update files
|
||||||
|
// - update separate statistics
|
||||||
|
// - update selection
|
||||||
|
// - update selection statistics
|
||||||
|
// - notify UI (changed files, updated selection, new stats)
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct Controller {
|
pub struct Controller {
|
||||||
@@ -18,10 +31,31 @@ impl Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
// pub fn create_file(&mut self, name: &str) {
|
pub fn create_file(&mut self, name: &str) {
|
||||||
// let id = self.stack.get_new_file_id();
|
let mut file = GPXFile::default();
|
||||||
// let file = Rc::new(GPXFile::new(id, name));
|
file.info.name = name.to_string();
|
||||||
// self.stack.update(&[file]);
|
self.stack.create_and_push_next(|entry: &mut StackEntry| {
|
||||||
// }
|
entry.insert(file.id, Rc::new(file));
|
||||||
|
true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn load_file(&mut self, data: &[u8]) {
|
||||||
|
if let Ok(file) = parse(data) {
|
||||||
|
self.stack.create_and_push_next(|entry: &mut StackEntry| {
|
||||||
|
entry.insert(file.id, Rc::new(file));
|
||||||
|
true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn delete_file(&mut self, id: &[u8]) {
|
||||||
|
if let Ok(id) = Uuid::from_slice(id) {
|
||||||
|
self.stack
|
||||||
|
.create_and_push_next(|entry: &mut StackEntry| entry.remove(&id).is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
use crate::gpx::{Trackpoint, Waypoint};
|
||||||
|
|
||||||
|
static MAX_TRKPT_CHUNK_SIZE: usize = 4096;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct TrackpointChunk {
|
||||||
|
pub trkpt: Vec<Trackpoint>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrackpointChunk {
|
||||||
|
pub fn is_full(&self) -> bool {
|
||||||
|
self.trkpt.len() == MAX_TRKPT_CHUNK_SIZE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static MAX_WPT_CHUNK_SIZE: usize = 128;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct WaypointChunk {
|
||||||
|
pub wpt: Vec<Waypoint>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WaypointChunk {
|
||||||
|
pub fn is_full(&self) -> bool {
|
||||||
|
self.wpt.len() == MAX_WPT_CHUNK_SIZE
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
-1
@@ -1,15 +1,29 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::gpx::{Link, Track, WaypointChunk};
|
use crate::gpx::{Link, Track, WaypointChunk};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
pub struct GPXFile {
|
pub struct GPXFile {
|
||||||
|
pub id: Uuid,
|
||||||
pub info: GPXFileInfo,
|
pub info: GPXFileInfo,
|
||||||
pub trk: Vec<Track>,
|
pub trk: Vec<Track>,
|
||||||
pub wpt: Vec<Rc<WaypointChunk>>,
|
pub wpt: Vec<Rc<WaypointChunk>>,
|
||||||
// TODO routes
|
// TODO routes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for GPXFile {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
id: Uuid::new_v4(),
|
||||||
|
info: Default::default(),
|
||||||
|
trk: Default::default(),
|
||||||
|
wpt: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct GPXFileInfo {
|
pub struct GPXFileInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
mod chunk;
|
||||||
mod common;
|
mod common;
|
||||||
mod file;
|
mod file;
|
||||||
mod segment;
|
mod segment;
|
||||||
@@ -5,6 +6,7 @@ mod track;
|
|||||||
mod trackpoint;
|
mod trackpoint;
|
||||||
mod waypoint;
|
mod waypoint;
|
||||||
|
|
||||||
|
pub use chunk::*;
|
||||||
pub use common::*;
|
pub use common::*;
|
||||||
pub use file::*;
|
pub use file::*;
|
||||||
pub use segment::*;
|
pub use segment::*;
|
||||||
|
|||||||
+218
-25
@@ -1,60 +1,253 @@
|
|||||||
use std::rc::Rc;
|
use std::{ops::Index, rc::Rc};
|
||||||
|
|
||||||
use crate::gpx::TrackPoint;
|
use crate::gpx::{Trackpoint, TrackpointChunk};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct TrackSegment {
|
pub struct TrackSegment {
|
||||||
pub chunks: Vec<Rc<TrackPointChunk>>,
|
chunks: Vec<Rc<TrackpointChunk>>,
|
||||||
|
cumul_length: Vec<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TrackSegment {
|
impl TrackSegment {
|
||||||
pub fn iter(&self) -> TrackSegmentIterator {
|
pub fn push(&mut self, chunk: TrackpointChunk) {
|
||||||
|
if chunk.trkpt.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.cumul_length
|
||||||
|
.push(self.cumul_length.last().copied().unwrap_or_default() + chunk.trkpt.len());
|
||||||
|
self.chunks.push(Rc::new(chunk));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.cumul_length.last().copied().unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> TrackSegmentIterator<'_> {
|
||||||
TrackSegmentIterator::new(self)
|
TrackSegmentIterator::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn first_index(&self) -> Option<TrackSegmentIndex> {
|
||||||
|
self.next_index(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn last_index(&self) -> Option<TrackSegmentIndex> {
|
||||||
|
self.prev_index(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_index(&self, cur: Option<TrackSegmentIndex>) -> Option<TrackSegmentIndex> {
|
||||||
|
let mut next = cur.map_or_default(|idx| TrackSegmentIndex {
|
||||||
|
chunk: idx.chunk,
|
||||||
|
pos: idx.pos + 1,
|
||||||
|
});
|
||||||
|
loop {
|
||||||
|
if next.chunk >= self.chunks.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if next.pos == self.chunks[next.chunk].trkpt.len() {
|
||||||
|
next.chunk += 1;
|
||||||
|
next.pos = 0;
|
||||||
|
} else {
|
||||||
|
return Some(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prev_index(&self, cur: Option<TrackSegmentIndex>) -> Option<TrackSegmentIndex> {
|
||||||
|
let mut prev = cur.unwrap_or(TrackSegmentIndex {
|
||||||
|
chunk: self.chunks.len(),
|
||||||
|
pos: 0,
|
||||||
|
});
|
||||||
|
loop {
|
||||||
|
if prev.pos == 0 {
|
||||||
|
if prev.chunk == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
prev.chunk -= 1;
|
||||||
|
prev.pos = self.chunks[prev.chunk].trkpt.len();
|
||||||
|
} else {
|
||||||
|
prev.pos -= 1;
|
||||||
|
return Some(prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_flat_index(&self, idx: TrackSegmentIndex) -> usize {
|
||||||
|
idx.pos
|
||||||
|
+ if idx.chunk > 0 {
|
||||||
|
self.cumul_length[idx.chunk - 1]
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn locate(&self, idx: usize) -> Option<TrackSegmentIndex> {
|
||||||
|
let chunk = self.cumul_length.partition_point(|l| idx >= *l);
|
||||||
|
if chunk >= self.chunks.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let pos = if chunk > 0 {
|
||||||
|
idx - self.cumul_length[chunk - 1]
|
||||||
|
} else {
|
||||||
|
idx
|
||||||
|
};
|
||||||
|
if pos >= self.chunks[chunk].trkpt.len() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(TrackSegmentIndex { chunk, pos })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<TrackSegmentIndex> for TrackSegment {
|
||||||
|
type Output = Trackpoint;
|
||||||
|
|
||||||
|
fn index(&self, idx: TrackSegmentIndex) -> &Self::Output {
|
||||||
|
&self.chunks[idx.chunk].trkpt[idx.pos]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<usize> for TrackSegment {
|
||||||
|
type Output = Trackpoint;
|
||||||
|
|
||||||
|
fn index(&self, idx: usize) -> &Self::Output {
|
||||||
|
&self[self.locate(idx).unwrap()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord)]
|
||||||
|
pub struct TrackSegmentIndex {
|
||||||
|
pub chunk: usize,
|
||||||
|
pub pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct TrackSegmentIterator<'a> {
|
pub struct TrackSegmentIterator<'a> {
|
||||||
trkseg: &'a TrackSegment,
|
trkseg: &'a TrackSegment,
|
||||||
chunk_idx: usize,
|
idx: Option<TrackSegmentIndex>,
|
||||||
trkpt_idx: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TrackSegmentIterator<'a> {
|
impl<'a> TrackSegmentIterator<'a> {
|
||||||
pub fn new(trkseg: &'a TrackSegment) -> Self {
|
pub fn new(trkseg: &'a TrackSegment) -> Self {
|
||||||
Self {
|
Self {
|
||||||
trkseg,
|
trkseg,
|
||||||
chunk_idx: 0,
|
idx: Default::default(),
|
||||||
trkpt_idx: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for TrackSegmentIterator<'a> {
|
impl<'a> Iterator for TrackSegmentIterator<'a> {
|
||||||
type Item = &'a TrackPoint;
|
type Item = &'a Trackpoint;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if self.chunk_idx >= self.trkseg.chunks.len() {
|
self.idx = self.trkseg.next_index(self.idx);
|
||||||
None
|
self.idx.map(|idx| &self.trkseg[idx])
|
||||||
} else if self.trkpt_idx >= self.trkseg.chunks[self.chunk_idx].trkpt.len() {
|
|
||||||
self.chunk_idx += 1;
|
|
||||||
self.trkpt_idx = 0;
|
|
||||||
self.next()
|
|
||||||
} else {
|
|
||||||
self.trkpt_idx += 1;
|
|
||||||
Some(&self.trkseg.chunks[self.chunk_idx].trkpt[self.trkpt_idx - 1])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
||||||
|
let idx = self
|
||||||
|
.idx
|
||||||
|
.map_or_default(|idx| self.trkseg.to_flat_index(idx))
|
||||||
|
+ n;
|
||||||
|
self.idx = self.trkseg.locate(idx);
|
||||||
|
self.idx.map(|idx| &self.trkseg[idx])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static MAX_CHUNK_SIZE: usize = 4096;
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::gpx::TrackpointChunk;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
use super::*;
|
||||||
pub struct TrackPointChunk {
|
|
||||||
pub trkpt: Vec<TrackPoint>,
|
fn create_track_segment(nb_chunks: usize) -> TrackSegment {
|
||||||
|
let mut trkseg = TrackSegment::default();
|
||||||
|
let mut count = 0;
|
||||||
|
for n in 1..=nb_chunks {
|
||||||
|
let mut chunk = TrackpointChunk::default();
|
||||||
|
for _ in 0..n {
|
||||||
|
let mut trkpt = Trackpoint::default();
|
||||||
|
trkpt.ele = count as f64;
|
||||||
|
chunk.trkpt.push(trkpt);
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
trkseg.push(chunk);
|
||||||
|
}
|
||||||
|
trkseg
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TrackPointChunk {
|
#[test]
|
||||||
pub fn is_full(&self) -> bool {
|
fn test_len() {
|
||||||
self.trkpt.len() == MAX_CHUNK_SIZE
|
let nb_chunks = 10;
|
||||||
|
let trkseg = create_track_segment(nb_chunks);
|
||||||
|
assert_eq!(trkseg.len(), nb_chunks * (nb_chunks + 1) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_locate() {
|
||||||
|
let nb_chunks = 10;
|
||||||
|
let trkseg = create_track_segment(nb_chunks);
|
||||||
|
for i in 0..trkseg.len() {
|
||||||
|
let idx = trkseg.locate(i);
|
||||||
|
assert!(idx.is_some());
|
||||||
|
assert_eq!(trkseg[idx.unwrap()].ele, i as f64);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(trkseg.locate(trkseg.len()).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_index() {
|
||||||
|
let nb_chunks = 10;
|
||||||
|
let trkseg = create_track_segment(nb_chunks);
|
||||||
|
for i in 0..trkseg.len() {
|
||||||
|
assert_eq!(trkseg[i].ele, i as f64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_index_out_of_bounds_1() {
|
||||||
|
let nb_chunks = 10;
|
||||||
|
let trkseg = create_track_segment(nb_chunks);
|
||||||
|
trkseg[trkseg.len()].ele;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_index_out_of_bounds_2() {
|
||||||
|
let nb_chunks = 10;
|
||||||
|
let trkseg = create_track_segment(nb_chunks);
|
||||||
|
trkseg[TrackSegmentIndex {
|
||||||
|
chunk: trkseg.chunks.len(),
|
||||||
|
pos: 0,
|
||||||
|
}]
|
||||||
|
.ele;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_iter() {
|
||||||
|
let nb_chunks = 10;
|
||||||
|
let trkseg = create_track_segment(nb_chunks);
|
||||||
|
for (i, trkpt) in trkseg.iter().enumerate() {
|
||||||
|
assert!(std::ptr::eq(&trkseg[i], trkpt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_iter_nth() {
|
||||||
|
let nb_chunks = 10;
|
||||||
|
let trkseg = create_track_segment(nb_chunks);
|
||||||
|
let tenth = trkseg.iter().nth(10);
|
||||||
|
assert!(tenth.is_some());
|
||||||
|
assert!(std::ptr::eq(&trkseg[10], tenth.unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_iter_skip() {
|
||||||
|
let nb_chunks = 10;
|
||||||
|
let trkseg = create_track_segment(nb_chunks);
|
||||||
|
for (i, trkpt) in trkseg.iter().enumerate().skip(5) {
|
||||||
|
assert!(std::ptr::eq(&trkseg[i], trkpt));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::gpx::LngLat;
|
use crate::gpx::LngLat;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct TrackPoint {
|
pub struct Trackpoint {
|
||||||
pub coordinates: LngLat,
|
pub coordinates: LngLat,
|
||||||
pub ele: f64,
|
pub ele: f64,
|
||||||
pub time: Option<i64>,
|
pub time: Option<i64>,
|
||||||
@@ -11,3 +11,9 @@ pub struct TrackPoint {
|
|||||||
pub power: Option<u16>,
|
pub power: Option<u16>,
|
||||||
// TODO OSM data? or store intervals at a higher level?
|
// TODO OSM data? or store intervals at a higher level?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Trackpoint {
|
||||||
|
pub fn time_diff(&self, other: &Trackpoint) -> Option<i64> {
|
||||||
|
self.time.zip(other.time).map(|(t1, t2)| t1 - t2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,16 +12,3 @@ pub struct Waypoint {
|
|||||||
pub sym: Option<String>,
|
pub sym: Option<String>,
|
||||||
pub type_: Option<String>,
|
pub type_: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
static MAX_CHUNK_SIZE: usize = 128;
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct WaypointChunk {
|
|
||||||
pub wpt: Vec<Waypoint>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WaypointChunk {
|
|
||||||
pub fn is_full(&self) -> bool {
|
|
||||||
self.wpt.len() == MAX_CHUNK_SIZE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+30
-33
@@ -1,8 +1,8 @@
|
|||||||
use std::{collections::HashMap, rc::Rc};
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
use crate::gpx::GPXFile;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub type GPXFileId = usize;
|
use crate::gpx::GPXFile;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Stack {
|
pub struct Stack {
|
||||||
@@ -18,40 +18,37 @@ impl Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_new_file_id(&self) -> GPXFileId {
|
pub fn create_and_push_next<F>(&mut self, f: F)
|
||||||
if let Some(current) = self.current() {
|
where
|
||||||
for id in 0..current.len() {
|
F: FnOnce(&mut StackEntry) -> bool,
|
||||||
if !current.contains_key(&id) {
|
{
|
||||||
return id;
|
let mut next = self.current().map_or_default(|c| c.clone());
|
||||||
}
|
if f(&mut next) {
|
||||||
}
|
|
||||||
current.len() as GPXFileId
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self, files: &[Rc<GPXFile>], ids: &[GPXFileId]) {
|
|
||||||
let mut next = match self.current() {
|
|
||||||
Some(current) => current.clone(),
|
|
||||||
None => StackEntry::default(),
|
|
||||||
};
|
|
||||||
for (file, id) in files.iter().zip(ids) {
|
|
||||||
next.insert(*id, file.clone());
|
|
||||||
}
|
|
||||||
self.push(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete(&mut self, files: &[GPXFileId]) {
|
|
||||||
if let Some(current) = self.current() {
|
|
||||||
let mut next = current.clone();
|
|
||||||
for file in files {
|
|
||||||
next.remove(file);
|
|
||||||
}
|
|
||||||
self.push(next);
|
self.push(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pub fn update(&mut self, files: &[Rc<GPXFile>], ids: &[GPXFileId]) {
|
||||||
|
// let mut next = match self.current() {
|
||||||
|
// Some(current) => current.clone(),
|
||||||
|
// None => StackEntry::default(),
|
||||||
|
// };
|
||||||
|
// for (file, id) in files.iter().zip(ids) {
|
||||||
|
// next.insert(*id, file.clone());
|
||||||
|
// }
|
||||||
|
// self.push(next);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn delete(&mut self, files: &[GPXFileId]) {
|
||||||
|
// if let Some(current) = self.current() {
|
||||||
|
// let mut next = current.clone();
|
||||||
|
// for file in files {
|
||||||
|
// next.remove(file);
|
||||||
|
// }
|
||||||
|
// self.push(next);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
pub fn can_undo(&self) -> bool {
|
pub fn can_undo(&self) -> bool {
|
||||||
self.index.is_some()
|
self.index.is_some()
|
||||||
}
|
}
|
||||||
@@ -100,4 +97,4 @@ impl Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type StackEntry = HashMap<GPXFileId, Rc<GPXFile>>;
|
pub type StackEntry = HashMap<Uuid, Rc<GPXFile>>;
|
||||||
|
|||||||
+87
-63
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
algorithms::ramer_douglas_peucker,
|
algorithms::ramer_douglas_peucker,
|
||||||
for_each_window,
|
for_each_window,
|
||||||
gpx::{LngLat, LngLatBounds, TrackPoint, TrackPointChunk},
|
gpx::{LngLat, LngLatBounds, TrackSegment, Trackpoint},
|
||||||
utils::{distance, slope, speed},
|
utils::{distance, slope, speed},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -33,26 +33,25 @@ impl GPXStatistics {
|
|||||||
.map(|(d, t)| speed(d, t))
|
.map(|(d, t)| speed(d, t))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compute(chunk: &TrackPointChunk) -> Self {
|
pub fn compute(trkseg: &TrackSegment) -> Self {
|
||||||
let mut stats = Self::default();
|
let mut stats = Self::default();
|
||||||
if chunk.trkpt.is_empty() {
|
if trkseg.len() == 0 {
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut prev = &chunk.trkpt[0];
|
let mut prev = &trkseg[0];
|
||||||
for i in 0..chunk.trkpt.len() {
|
for cur in trkseg.iter() {
|
||||||
let cur = &chunk.trkpt[i];
|
|
||||||
stats.accumulate(prev, cur);
|
stats.accumulate(prev, cur);
|
||||||
prev = cur;
|
prev = cur;
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.compute_smoothed_speed(chunk);
|
stats.compute_smoothed_speed(trkseg);
|
||||||
stats.compute_smoothed_elevation_gain(chunk);
|
stats.compute_smoothed_elevation_gain(trkseg);
|
||||||
|
|
||||||
stats
|
stats
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -60,9 +59,9 @@ impl GPXStatistics {
|
|||||||
.push(TrackpointStatistics::from_partial_stats(&self));
|
.push(TrackpointStatistics::from_partial_stats(&self));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accumulate_distance_and_time(&mut self, prev: &TrackPoint, cur: &TrackPoint) {
|
fn accumulate_distance_and_time(&mut self, prev: &Trackpoint, cur: &Trackpoint) {
|
||||||
let dist = distance(prev.coordinates, cur.coordinates);
|
let dist = distance(prev.coordinates, cur.coordinates);
|
||||||
let time = prev.time.zip(cur.time).map(|(t1, t2)| t2 - t1);
|
let time = cur.time_diff(prev);
|
||||||
|
|
||||||
self.total_distance += dist;
|
self.total_distance += dist;
|
||||||
|
|
||||||
@@ -91,42 +90,37 @@ impl GPXStatistics {
|
|||||||
self.bounds.ne.lat = self.bounds.ne.lat.min(coordinates.lat);
|
self.bounds.ne.lat = self.bounds.ne.lat.min(coordinates.lat);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_smoothed_speed(&mut self, chunk: &TrackPointChunk) {
|
fn compute_smoothed_speed(&mut self, trkseg: &TrackSegment) {
|
||||||
for_each_window!(
|
for_each_window!(
|
||||||
0,
|
trkseg,
|
||||||
chunk.trkpt.len(),
|
trkseg.first_index(),
|
||||||
|
trkseg.last_index(),
|
||||||
Some(10000),
|
Some(10000),
|
||||||
|i, j| {
|
|i, j| trkseg[i].time_diff(&trkseg[j]),
|
||||||
chunk.trkpt[i]
|
|
||||||
.time
|
|
||||||
.zip(chunk.trkpt[j].time)
|
|
||||||
.map(|(t1, t2)| t2 - t1)
|
|
||||||
},
|
|
||||||
|i, left, right| {
|
|i, left, right| {
|
||||||
self.local[i].speed =
|
let i = trkseg.to_flat_index(i);
|
||||||
chunk.trkpt[left]
|
let left = trkseg.to_flat_index(left);
|
||||||
.time
|
let right = trkseg.to_flat_index(right);
|
||||||
.zip(chunk.trkpt[right].time)
|
self.local[i].speed = trkseg[right].time_diff(&trkseg[left]).map(|t| {
|
||||||
.map(|(t1, t2)| {
|
|
||||||
speed(
|
speed(
|
||||||
self.local[right].total_distance - self.local[left].total_distance,
|
self.local[right].total_distance - self.local[left].total_distance,
|
||||||
t2 - t1,
|
t,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_smoothed_elevation_gain(&mut self, chunk: &TrackPointChunk) {
|
fn compute_smoothed_elevation_gain(&mut self, trkseg: &TrackSegment) {
|
||||||
let simplified = ramer_douglas_peucker(
|
let simplified = ramer_douglas_peucker(
|
||||||
chunk.trkpt.len(),
|
trkseg,
|
||||||
&|i, j, k| {
|
&|i, j, k| {
|
||||||
let x1 = self.local[i].total_distance * 1000.0;
|
let x1 = self.local[trkseg.to_flat_index(i)].total_distance * 1000.0;
|
||||||
let x2 = self.local[j].total_distance * 1000.0;
|
let x2 = self.local[trkseg.to_flat_index(j)].total_distance * 1000.0;
|
||||||
let x3 = self.local[k].total_distance * 1000.0;
|
let x3 = self.local[trkseg.to_flat_index(k)].total_distance * 1000.0;
|
||||||
let y1 = chunk.trkpt[i].ele;
|
let y1 = trkseg[i].ele;
|
||||||
let y2 = chunk.trkpt[j].ele;
|
let y2 = trkseg[j].ele;
|
||||||
let y3 = chunk.trkpt[k].ele;
|
let y3 = trkseg[k].ele;
|
||||||
|
|
||||||
let dist = ((y2 - y1).powi(2) + (x2 - x1).powi(2)).sqrt();
|
let dist = ((y2 - y1).powi(2) + (x2 - x1).powi(2)).sqrt();
|
||||||
if dist == 0.0 {
|
if dist == 0.0 {
|
||||||
@@ -145,28 +139,40 @@ impl GPXStatistics {
|
|||||||
|
|
||||||
let mut cumul_ele = 0.0;
|
let mut cumul_ele = 0.0;
|
||||||
let mut current_left = start;
|
let mut current_left = start;
|
||||||
let mut current_right = start;
|
let mut current_right = Some(start);
|
||||||
let mut prev_smoothed_ele = chunk.trkpt[start].ele;
|
let mut prev_smoothed_ele = trkseg[start].ele;
|
||||||
|
|
||||||
for_each_window!(
|
for_each_window!(
|
||||||
start,
|
trkseg,
|
||||||
end,
|
Some(start),
|
||||||
|
Some(end),
|
||||||
0.1,
|
0.1,
|
||||||
|i, j| self.local[j].total_distance - self.local[i].total_distance,
|
|i, j| {
|
||||||
|
let i = trkseg.to_flat_index(i);
|
||||||
|
let j = trkseg.to_flat_index(j);
|
||||||
|
self.local[j].total_distance - self.local[i].total_distance
|
||||||
|
},
|
||||||
|i, left, right| {
|
|i, left, right| {
|
||||||
for i in current_left..left {
|
while current_left != left {
|
||||||
cumul_ele -= chunk.trkpt[i].ele;
|
cumul_ele -= trkseg[current_left].ele;
|
||||||
|
current_left = trkseg.next_index(Some(current_left)).unwrap();
|
||||||
}
|
}
|
||||||
for i in current_right..=right {
|
while let Some(current) = current_right {
|
||||||
cumul_ele += chunk.trkpt[i].ele;
|
if current > right {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
current_left = left;
|
cumul_ele += trkseg[current].ele;
|
||||||
current_right = right + 1;
|
current_right = trkseg.next_index(current_right);
|
||||||
|
}
|
||||||
|
|
||||||
|
let flat_i = trkseg.to_flat_index(i);
|
||||||
|
let flat_left = trkseg.to_flat_index(left);
|
||||||
|
let flat_right = trkseg.to_flat_index(right);
|
||||||
|
|
||||||
let smoothed_ele: f64 = if i == start || i == end {
|
let smoothed_ele: f64 = if i == start || i == end {
|
||||||
chunk.trkpt[i].ele
|
trkseg[i].ele
|
||||||
} else {
|
} else {
|
||||||
cumul_ele / (right - left + 1) as f64
|
cumul_ele / (flat_right - flat_left + 1) as f64
|
||||||
};
|
};
|
||||||
|
|
||||||
let delta = smoothed_ele - prev_smoothed_ele;
|
let delta = smoothed_ele - prev_smoothed_ele;
|
||||||
@@ -177,18 +183,22 @@ impl GPXStatistics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if i < end || last {
|
if i < end || last {
|
||||||
self.local[i].elevation_gain = self.elevation_gain;
|
self.local[flat_i].elevation_gain = self.elevation_gain;
|
||||||
self.local[i].elevation_loss = self.elevation_loss;
|
self.local[flat_i].elevation_loss = self.elevation_loss;
|
||||||
}
|
}
|
||||||
|
|
||||||
prev_smoothed_ele = smoothed_ele;
|
prev_smoothed_ele = smoothed_ele;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let segment_dist = self.local[end].total_distance - self.local[start].total_distance;
|
let flat_start = trkseg.to_flat_index(start);
|
||||||
let segment_ele = chunk.trkpt[end].ele - chunk.trkpt[start].ele;
|
let flat_end = trkseg.to_flat_index(end);
|
||||||
|
|
||||||
|
let segment_dist =
|
||||||
|
self.local[flat_end].total_distance - self.local[flat_start].total_distance;
|
||||||
|
let segment_ele = trkseg[end].ele - trkseg[start].ele;
|
||||||
let segment_slope = slope(segment_ele, segment_dist);
|
let segment_slope = slope(segment_ele, segment_dist);
|
||||||
for k in start..(end + last as usize) {
|
for k in flat_start..(flat_end + last as usize) {
|
||||||
self.local[k].slope_segment = SlopeSegment {
|
self.local[k].slope_segment = SlopeSegment {
|
||||||
slope: segment_slope,
|
slope: segment_slope,
|
||||||
distance: segment_dist,
|
distance: segment_dist,
|
||||||
@@ -197,14 +207,23 @@ impl GPXStatistics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for_each_window!(
|
for_each_window!(
|
||||||
0,
|
trkseg,
|
||||||
chunk.trkpt.len(),
|
trkseg.first_index(),
|
||||||
|
trkseg.last_index(),
|
||||||
0.05,
|
0.05,
|
||||||
|i, j| self.local[j].total_distance - self.local[i].total_distance,
|
|i, j| {
|
||||||
|
let i = trkseg.to_flat_index(i);
|
||||||
|
let j = trkseg.to_flat_index(j);
|
||||||
|
self.local[j].total_distance - self.local[i].total_distance
|
||||||
|
},
|
||||||
|i, left, right| {
|
|i, left, right| {
|
||||||
let dist = self.local[right].total_distance - self.local[left].total_distance;
|
let flat_i = trkseg.to_flat_index(i);
|
||||||
let ele = chunk.trkpt[right].ele - chunk.trkpt[left].ele;
|
let flat_left = trkseg.to_flat_index(left);
|
||||||
self.local[i].slope = slope(ele, dist);
|
let flat_right = trkseg.to_flat_index(right);
|
||||||
|
let dist =
|
||||||
|
self.local[flat_right].total_distance - self.local[flat_left].total_distance;
|
||||||
|
let ele = trkseg[right].ele - trkseg[left].ele;
|
||||||
|
self.local[flat_i].slope = slope(ele, dist);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -254,15 +273,20 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_simple() {
|
fn test_compute_smoothed_speed() {
|
||||||
let mut f = File::open("data/with_time.gpx").unwrap();
|
let mut f = File::open("data/with_time.gpx").unwrap();
|
||||||
let mut data = String::new();
|
let mut data = String::new();
|
||||||
let _ = f.read_to_string(&mut data);
|
let _ = f.read_to_string(&mut data);
|
||||||
let gpx = parse(data.as_bytes()).unwrap();
|
let gpx = parse(data.as_bytes()).unwrap();
|
||||||
|
|
||||||
println!(
|
let trkseg = &gpx.trk[0].trkseg[0];
|
||||||
"{:?}",
|
let stats = GPXStatistics::compute(trkseg);
|
||||||
GPXStatistics::compute(&gpx.trk[0].trkseg[0].chunks[0])
|
assert_eq!(stats.local.len(), trkseg.len());
|
||||||
);
|
for trkpt_stats in stats.local.iter() {
|
||||||
|
assert!(trkpt_stats.speed.is_some());
|
||||||
|
let speed = trkpt_stats.speed.unwrap();
|
||||||
|
assert_ne!(speed, f64::NAN);
|
||||||
|
assert!((speed - 20.0).abs() < 0.1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
class Engine {
|
||||||
|
_engine: Writable<any>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._engine = null;
|
||||||
|
import('gpx-rs').then((wasm) => {
|
||||||
|
this._engine = new wasm.Controller();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,14 +37,6 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (browser) {
|
|
||||||
const wasm = await import('gpx-rs');
|
|
||||||
const controller = new wasm.Controller();
|
|
||||||
|
|
||||||
console.log(controller);
|
|
||||||
|
|
||||||
controller.create_file();
|
|
||||||
}
|
|
||||||
settings.connectToDatabase(db);
|
settings.connectToDatabase(db);
|
||||||
fileStateCollection.connectToDatabase(db).then(() => {
|
fileStateCollection.connectToDatabase(db).then(() => {
|
||||||
let files: string[] = JSON.parse(page.url.searchParams.get('files') || '[]');
|
let files: string[] = JSON.parse(page.url.searchParams.get('files') || '[]');
|
||||||
|
|||||||
Reference in New Issue
Block a user