mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-03 12:28:22 +00:00
progress
This commit is contained in:
@@ -8,43 +8,44 @@ macro_rules! for_each_window {
|
|||||||
|$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.unwrap_or_default();
|
let mut window_start = $left.unwrap_or_default();
|
||||||
let mut cur = $left;
|
let mut window_center = $left;
|
||||||
while let Some(idx) = cur {
|
let mut window_end = $left.unwrap_or_default();
|
||||||
|
while let Some(idx) = window_center {
|
||||||
// advance left if needed
|
// advance left if needed
|
||||||
while start != idx && {
|
while window_start != idx && {
|
||||||
let $a = start;
|
let $a = window_start;
|
||||||
let $b = idx;
|
let $b = idx;
|
||||||
$distance
|
$distance
|
||||||
} > $window {
|
} > $window {
|
||||||
if let Some(next) = $trkseg.next_index(Some(start)) {
|
if let Some(next) = $trkseg.next_index(Some(window_start)) {
|
||||||
if next == idx {
|
if next == idx {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
start = next;
|
window_start = next;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// advance right if needed
|
// advance right if needed
|
||||||
let mut end = idx;
|
window_end = window_end.max(idx);
|
||||||
while let Some(next) = $trkseg.next_index(Some(end)) {
|
while let Some(next) = $trkseg.next_index(Some(window_end)) {
|
||||||
if {
|
if window_end != idx && {
|
||||||
let $a = idx;
|
let $a = idx;
|
||||||
let $b = end;
|
let $b = next;
|
||||||
$distance
|
$distance
|
||||||
} > $window {
|
} > $window {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
end = next;
|
window_end = next;
|
||||||
}
|
}
|
||||||
// apply body
|
// apply body
|
||||||
let $i = idx;
|
let $i = idx;
|
||||||
let $l = start;
|
let $l = window_start;
|
||||||
let $r = end;
|
let $r = window_end;
|
||||||
$body
|
$body
|
||||||
// go next
|
// go next
|
||||||
cur = $trkseg.next_index(cur);
|
window_center = $trkseg.next_index(window_center);
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
pub struct CoordinatesBuffer {
|
||||||
|
pub lng: Vec<f64>,
|
||||||
|
pub lat: Vec<f64>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
mod coordinates;
|
||||||
|
mod statistics;
|
||||||
|
|
||||||
|
pub use coordinates::*;
|
||||||
|
pub use statistics::*;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
use crate::statistics::{GPXStatistics, GlobalStatistics, sum_options};
|
use crate::statistics::{GlobalStatistics, Statistics, sum_options};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct StatisticsBuffer {
|
pub struct StatisticsBuffer {
|
||||||
pub total_distance: Vec<f64>,
|
pub total_distance: Vec<f64>,
|
||||||
pub moving_distance: Vec<f64>,
|
pub moving_distance: Vec<f64>,
|
||||||
pub total_time: Vec<i64>,
|
pub total_time: Vec<i32>,
|
||||||
pub moving_time: Vec<i64>,
|
pub moving_time: Vec<i32>,
|
||||||
pub speed: Vec<f64>,
|
pub speed: Vec<f64>,
|
||||||
pub elevation_gain: Vec<f64>,
|
pub elevation_gain: Vec<f64>,
|
||||||
pub elevation_loss: Vec<f64>,
|
pub elevation_loss: Vec<f64>,
|
||||||
@@ -15,7 +15,7 @@ pub struct StatisticsBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StatisticsBuffer {
|
impl StatisticsBuffer {
|
||||||
pub fn update(&mut self, stats: &[&GPXStatistics]) {
|
pub fn update(&mut self, stats: &[&Statistics]) {
|
||||||
self.total_distance.clear();
|
self.total_distance.clear();
|
||||||
self.moving_distance.clear();
|
self.moving_distance.clear();
|
||||||
self.total_time.clear();
|
self.total_time.clear();
|
||||||
+107
-31
@@ -2,75 +2,151 @@
|
|||||||
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use js_sys::Function;
|
use js_sys::{Float64Array, Function, Int32Array};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
gpx::GPXFile,
|
buffer::StatisticsBuffer,
|
||||||
|
gpx::{File, FileId},
|
||||||
io::parse,
|
io::parse,
|
||||||
selection::Selection,
|
selection::Selection,
|
||||||
state::{Stack, StackEntry},
|
state::{Stack, StackEntry},
|
||||||
statistics::StatisticsBuffer,
|
statistics::StatisticsCache,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern crate console_error_panic_hook;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct Engine {
|
pub struct Engine {
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
selection: Selection,
|
selection: Selection,
|
||||||
statistics: StatisticsBuffer,
|
statistics_cache: StatisticsCache,
|
||||||
callback: Function,
|
statistics_buffer: StatisticsBuffer,
|
||||||
|
callback: Option<Function>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl Engine {
|
impl Engine {
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new(callback: Function) -> Self {
|
pub fn new(callback: Function) -> Self {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
Self {
|
Self {
|
||||||
stack: Default::default(),
|
stack: Default::default(),
|
||||||
selection: Default::default(),
|
selection: Default::default(),
|
||||||
statistics: Default::default(),
|
statistics_cache: Default::default(),
|
||||||
callback,
|
statistics_buffer: Default::default(),
|
||||||
|
callback: Some(callback),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
pub fn total_distance(&self) -> Float64Array {
|
||||||
|
unsafe { Float64Array::view(&self.statistics_buffer.total_distance) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn moving_distance(&self) -> Float64Array {
|
||||||
|
unsafe { Float64Array::view(&self.statistics_buffer.moving_distance) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn total_time(&self) -> Int32Array {
|
||||||
|
unsafe { Int32Array::view(&self.statistics_buffer.total_time) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn moving_time(&self) -> Int32Array {
|
||||||
|
unsafe { Int32Array::view(&self.statistics_buffer.moving_time) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn speed(&self) -> Float64Array {
|
||||||
|
unsafe { Float64Array::view(&self.statistics_buffer.speed) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn elevation_gain(&self) -> Float64Array {
|
||||||
|
unsafe { Float64Array::view(&self.statistics_buffer.elevation_gain) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn elevation_loss(&self) -> Float64Array {
|
||||||
|
unsafe { Float64Array::view(&self.statistics_buffer.elevation_loss) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slope(&self) -> Float64Array {
|
||||||
|
unsafe { Float64Array::view(&self.statistics_buffer.slope) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slope_segment_slope(&self) -> Float64Array {
|
||||||
|
unsafe { Float64Array::view(&self.statistics_buffer.slope_segment_slope) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slope_segment_distance(&self) -> Float64Array {
|
||||||
|
unsafe { Float64Array::view(&self.statistics_buffer.slope_segment_distance) }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_file(&mut self, name: &str) {
|
pub fn create_file(&mut self, name: &str) {
|
||||||
let mut file = GPXFile::default();
|
self.event_loop(|entry| {
|
||||||
file.info.name = name.to_string();
|
let mut file = File::default();
|
||||||
self.stack.create_and_push_next(|entry: &mut StackEntry| {
|
file.info.name = name.to_string();
|
||||||
entry.insert(file.id, Rc::new(file));
|
entry.insert(file.id, Rc::new(file));
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn load_file(&mut self, data: &[u8]) {
|
pub fn load_file(&mut self, data: &[u8]) {
|
||||||
if let Ok(file) = parse(data) {
|
self.event_loop(|entry| {
|
||||||
self.stack.create_and_push_next(|entry: &mut StackEntry| {
|
if let Ok(file) = parse(data) {
|
||||||
entry.insert(file.id, Rc::new(file));
|
entry.insert(file.id, Rc::new(file));
|
||||||
true
|
true
|
||||||
});
|
} else {
|
||||||
}
|
false
|
||||||
let _ = self.callback.call1(&JsValue::NULL, &JsValue::from("done!"));
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn delete_file(&mut self, id: &[u8]) {
|
pub fn delete_file(&mut self, id: &[u8]) {
|
||||||
if let Ok(id) = Uuid::from_slice(id) {
|
self.event_loop(|entry| {
|
||||||
self.stack
|
if let Ok(id) = Uuid::from_slice(id) {
|
||||||
.create_and_push_next(|entry: &mut StackEntry| entry.remove(&id).is_some());
|
let id = FileId(id);
|
||||||
}
|
entry.remove(&id).is_some()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event_loop(&mut self) {
|
fn event_loop<F>(&mut self, f: F)
|
||||||
// Control flow
|
where
|
||||||
// - get lock to cancel any other updates
|
F: FnOnce(&mut StackEntry) -> bool,
|
||||||
// - update files
|
{
|
||||||
// - update separate statistics
|
if let Some(diff) = self.stack.create_and_push_next(f) {
|
||||||
// - update selection
|
self.selection.select(diff.added[0]);
|
||||||
// - update selection statistics
|
self.statistics_cache.update(self.stack.current());
|
||||||
// self.statistics.update(stats);
|
self.statistics_buffer.update(
|
||||||
// - notify UI through callback (changed files, updated selection, new stats)
|
&self
|
||||||
|
.statistics_cache
|
||||||
|
.get(self.stack.current(), &self.selection),
|
||||||
|
);
|
||||||
|
if let Some(c) = &self.callback {
|
||||||
|
let _ = c.call0(&JsValue::NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{fs::File, io::Read};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_load_file() {
|
||||||
|
let mut engine = Engine::default();
|
||||||
|
|
||||||
|
let mut f = File::open("data/simple.gpx").unwrap();
|
||||||
|
let mut data = String::new();
|
||||||
|
let _ = f.read_to_string(&mut data);
|
||||||
|
|
||||||
|
engine.load_file(data.as_bytes());
|
||||||
|
|
||||||
|
assert_eq!(engine.statistics_buffer.total_distance.len(), 80);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-23
@@ -4,19 +4,19 @@ use crate::gpx::{Trackpoint, Waypoint};
|
|||||||
|
|
||||||
static MAX_TRKPT_CHUNK_SIZE: usize = 4096;
|
static MAX_TRKPT_CHUNK_SIZE: usize = 4096;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct TrackpointChunk {
|
pub struct TrackpointChunkId(Uuid);
|
||||||
pub id: Uuid,
|
|
||||||
pub trkpt: Vec<Trackpoint>,
|
impl Default for TrackpointChunkId {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Uuid::new_v4())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TrackpointChunk {
|
#[derive(Debug, Default)]
|
||||||
fn default() -> Self {
|
pub struct TrackpointChunk {
|
||||||
Self {
|
pub id: TrackpointChunkId,
|
||||||
id: Uuid::new_v4(),
|
pub trkpt: Vec<Trackpoint>,
|
||||||
trkpt: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for TrackpointChunk {
|
impl PartialEq for TrackpointChunk {
|
||||||
@@ -31,21 +31,21 @@ impl TrackpointChunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static MAX_WPT_CHUNK_SIZE: usize = 128;
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct WaypointChunkId(Uuid);
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl Default for WaypointChunkId {
|
||||||
pub struct WaypointChunk {
|
fn default() -> Self {
|
||||||
pub id: Uuid,
|
Self(Uuid::new_v4())
|
||||||
pub wpt: Vec<Waypoint>,
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WaypointChunk {
|
static MAX_WPT_CHUNK_SIZE: usize = 128;
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
#[derive(Debug, Default)]
|
||||||
id: Uuid::new_v4(),
|
pub struct WaypointChunk {
|
||||||
wpt: Default::default(),
|
pub id: WaypointChunkId,
|
||||||
}
|
pub wpt: Vec<Waypoint>,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for WaypointChunk {
|
impl PartialEq for WaypointChunk {
|
||||||
|
|||||||
+14
-16
@@ -4,28 +4,26 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use crate::gpx::{Link, Track, WaypointChunk};
|
use crate::gpx::{Link, Track, WaypointChunk};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct GPXFile {
|
pub struct FileId(pub Uuid);
|
||||||
pub id: Uuid,
|
|
||||||
pub info: GPXFileInfo,
|
impl Default for FileId {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Uuid::new_v4())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq)]
|
||||||
|
pub struct File {
|
||||||
|
pub id: FileId,
|
||||||
|
pub info: FileInfo,
|
||||||
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, PartialEq)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub struct GPXFileInfo {
|
pub struct FileInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub desc: Option<String>,
|
pub desc: Option<String>,
|
||||||
pub author: Option<Author>,
|
pub author: Option<Author>,
|
||||||
|
|||||||
@@ -1,9 +1,31 @@
|
|||||||
use std::{ops::Index, rc::Rc};
|
use std::{ops::Index, rc::Rc};
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::gpx::{Trackpoint, TrackpointChunk};
|
use crate::gpx::{Trackpoint, TrackpointChunk};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct TrackSegmentId(Uuid);
|
||||||
|
|
||||||
|
impl Default for TrackSegmentId {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Uuid::new_v4())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct TrackSegmentRevisionId(Uuid);
|
||||||
|
|
||||||
|
impl Default for TrackSegmentRevisionId {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Uuid::new_v4())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub struct TrackSegment {
|
pub struct TrackSegment {
|
||||||
|
pub id: TrackSegmentId,
|
||||||
|
pub rev_id: TrackSegmentRevisionId,
|
||||||
chunks: Vec<Rc<TrackpointChunk>>,
|
chunks: Vec<Rc<TrackpointChunk>>,
|
||||||
cumul_length: Vec<usize>,
|
cumul_length: Vec<usize>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::gpx::{Link, TrackSegment};
|
use crate::gpx::{Link, TrackSegment};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct TrackId(Uuid);
|
||||||
|
|
||||||
|
impl Default for TrackId {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Uuid::new_v4())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub struct Track {
|
pub struct Track {
|
||||||
|
pub id: TrackId,
|
||||||
pub info: TrackInfo,
|
pub info: TrackInfo,
|
||||||
pub trkseg: Vec<TrackSegment>,
|
pub trkseg: Vec<TrackSegment>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,3 @@ 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::gpx::{Link, LngLat};
|
use crate::gpx::{Link, LngLat};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct WaypointId(Uuid);
|
||||||
|
|
||||||
|
impl Default for WaypointId {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Uuid::new_v4())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Waypoint {
|
pub struct Waypoint {
|
||||||
|
pub id: WaypointId,
|
||||||
pub coordinates: LngLat,
|
pub coordinates: LngLat,
|
||||||
pub ele: f64,
|
pub ele: f64,
|
||||||
pub time: Option<i64>,
|
pub time: Option<i64>,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::gpx::{
|
use crate::gpx::{
|
||||||
Author, GPXFile, Link, LngLat, Track, TrackSegment, Trackpoint, TrackpointChunk, Waypoint,
|
Author, File, Link, LngLat, Track, TrackSegment, Trackpoint, TrackpointChunk, Waypoint,
|
||||||
WaypointChunk,
|
WaypointChunk,
|
||||||
};
|
};
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
@@ -50,10 +50,10 @@ fn parse_coordinates(attributes: Attributes<'_>) -> LngLat {
|
|||||||
coordinates
|
coordinates
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
|
pub fn parse(data: &[u8]) -> Result<File, Error> {
|
||||||
let mut reader = Reader::from_reader(data);
|
let mut reader = Reader::from_reader(data);
|
||||||
let mut buf = vec![];
|
let mut buf = vec![];
|
||||||
let mut gpx = GPXFile::default();
|
let mut gpx = File::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();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
mod action;
|
mod action;
|
||||||
mod algorithm;
|
mod algorithm;
|
||||||
|
mod buffer;
|
||||||
mod engine;
|
mod engine;
|
||||||
mod gpx;
|
mod gpx;
|
||||||
mod io;
|
mod io;
|
||||||
@@ -7,3 +8,5 @@ mod selection;
|
|||||||
mod state;
|
mod state;
|
||||||
mod statistics;
|
mod statistics;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
|
pub use engine::*;
|
||||||
|
|||||||
+23
-10
@@ -1,29 +1,34 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use crate::gpx::{FileId, TrackId, TrackSegmentId, WaypointId};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FileSelection {
|
pub struct FileSelection {
|
||||||
files: HashSet<Uuid>,
|
pub files: HashSet<FileId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TrackSelection {
|
pub struct TrackSelection {
|
||||||
file: Uuid,
|
pub file: FileId,
|
||||||
trk: HashSet<Uuid>,
|
pub trk: HashSet<TrackId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TrackSegmentSelection {
|
pub struct TrackSegmentSelection {
|
||||||
file: Uuid,
|
pub file: FileId,
|
||||||
trk: Uuid,
|
pub trk: TrackId,
|
||||||
trkseg: HashSet<Uuid>,
|
pub trkseg: HashSet<TrackSegmentId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WaypointsSelection {
|
||||||
|
pub file: FileId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct WaypointSelection {
|
pub struct WaypointSelection {
|
||||||
file: Uuid,
|
pub file: FileId,
|
||||||
wpt: HashSet<Uuid>,
|
pub wpt: HashSet<WaypointId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
@@ -33,6 +38,14 @@ pub enum Selection {
|
|||||||
File(FileSelection),
|
File(FileSelection),
|
||||||
Track(TrackSelection),
|
Track(TrackSelection),
|
||||||
TrackSegment(TrackSegmentSelection),
|
TrackSegment(TrackSegmentSelection),
|
||||||
Waypoints,
|
Waypoints(WaypointsSelection),
|
||||||
Waypoint(WaypointSelection),
|
Waypoint(WaypointSelection),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Selection {
|
||||||
|
pub fn select(&mut self, id: FileId) {
|
||||||
|
let mut files = HashSet::default();
|
||||||
|
files.insert(id);
|
||||||
|
*self = Selection::File(FileSelection { files });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ use std::{
|
|||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use uuid::Uuid;
|
use crate::gpx::{File, FileId};
|
||||||
|
|
||||||
use crate::gpx::GPXFile;
|
#[derive(Debug, Default)]
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Stack {
|
pub struct Stack {
|
||||||
entries: Vec<StackEntry>,
|
entries: Vec<StackEntry>,
|
||||||
index: Option<usize>,
|
index: Option<usize>,
|
||||||
@@ -99,8 +97,8 @@ impl Stack {
|
|||||||
}
|
}
|
||||||
let prev = prev.map_or_default(|i| self.get(i));
|
let prev = prev.map_or_default(|i| self.get(i));
|
||||||
let cur = cur.map_or_default(|i| self.get(i));
|
let cur = cur.map_or_default(|i| self.get(i));
|
||||||
let prev_ids: HashSet<Uuid> = prev.map_or_default(|e| e.keys().copied().collect());
|
let prev_ids: HashSet<FileId> = prev.map_or_default(|e| e.keys().copied().collect());
|
||||||
let cur_ids: HashSet<Uuid> = cur.map_or_default(|e| e.keys().copied().collect());
|
let cur_ids: HashSet<FileId> = cur.map_or_default(|e| e.keys().copied().collect());
|
||||||
let mut modified = vec![];
|
let mut modified = vec![];
|
||||||
for id in prev_ids.intersection(&cur_ids) {
|
for id in prev_ids.intersection(&cur_ids) {
|
||||||
let before = prev.map_or_default(|e| e.get(id));
|
let before = prev.map_or_default(|e| e.get(id));
|
||||||
@@ -117,13 +115,13 @@ impl Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type StackEntry = HashMap<Uuid, Rc<GPXFile>>;
|
pub type StackEntry = HashMap<FileId, Rc<File>>;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Diff {
|
pub struct Diff {
|
||||||
pub added: Vec<Uuid>,
|
pub added: Vec<FileId>,
|
||||||
pub removed: Vec<Uuid>,
|
pub removed: Vec<FileId>,
|
||||||
pub modified: Vec<Uuid>,
|
pub modified: Vec<FileId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -139,7 +137,7 @@ mod tests {
|
|||||||
assert!(stack.current().is_none());
|
assert!(stack.current().is_none());
|
||||||
|
|
||||||
let diff = stack.create_and_push_next(|e| {
|
let diff = stack.create_and_push_next(|e| {
|
||||||
let file = Rc::new(GPXFile::default());
|
let file = Rc::new(File::default());
|
||||||
e.insert(file.id, file);
|
e.insert(file.id, file);
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
use std::{
|
||||||
|
collections::{HashMap, HashSet, hash_map::Entry},
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
gpx::{File, Track, TrackSegment, TrackSegmentRevisionId},
|
||||||
|
selection::Selection,
|
||||||
|
state::StackEntry,
|
||||||
|
statistics::Statistics,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct StatisticsCache {
|
||||||
|
map: HashMap<TrackSegmentRevisionId, Statistics>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatisticsCache {
|
||||||
|
pub fn update(&mut self, state: Option<&StackEntry>) {
|
||||||
|
let mut stale: HashSet<TrackSegmentRevisionId> = self.map.keys().copied().collect();
|
||||||
|
if let Some(state) = state {
|
||||||
|
for file in state.values() {
|
||||||
|
for trk in file.trk.iter() {
|
||||||
|
for trkseg in trk.trkseg.iter() {
|
||||||
|
match self.map.entry(trkseg.rev_id) {
|
||||||
|
Entry::Occupied(_) => {
|
||||||
|
stale.remove(&trkseg.rev_id);
|
||||||
|
}
|
||||||
|
Entry::Vacant(e) => {
|
||||||
|
e.insert(Statistics::compute(trkseg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id in stale.iter() {
|
||||||
|
self.map.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get<'a>(
|
||||||
|
&'a self,
|
||||||
|
state: Option<&StackEntry>,
|
||||||
|
selection: &Selection,
|
||||||
|
) -> Vec<&'a Statistics> {
|
||||||
|
let mut stats = vec![];
|
||||||
|
if let Some(state) = state {
|
||||||
|
match selection {
|
||||||
|
Selection::Empty => (),
|
||||||
|
Selection::File(s) => {
|
||||||
|
for id in s.files.iter() {
|
||||||
|
if let Some(file) = state.get(id) {
|
||||||
|
self.add_file_stats(file, &mut stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Selection::Track(s) => {
|
||||||
|
if let Some(file) = state.get(&s.file) {
|
||||||
|
for trk in file.trk.iter() {
|
||||||
|
if s.trk.contains(&trk.id) {
|
||||||
|
self.add_track_stats(trk, &mut stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Selection::TrackSegment(s) => {
|
||||||
|
if let Some(file) = state.get(&s.file) {
|
||||||
|
for trk in file.trk.iter() {
|
||||||
|
if s.trk == trk.id {
|
||||||
|
for trkseg in trk.trkseg.iter() {
|
||||||
|
if s.trkseg.contains(&trkseg.id) {
|
||||||
|
self.add_segment_stats(trkseg, &mut stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Selection::Waypoints(s) => {
|
||||||
|
if let Some(file) = state.get(&s.file) {
|
||||||
|
self.add_file_stats(file, &mut stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Selection::Waypoint(s) => {
|
||||||
|
if let Some(file) = state.get(&s.file) {
|
||||||
|
self.add_file_stats(file, &mut stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stats
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_file_stats<'a>(&'a self, file: &Rc<File>, stats: &mut Vec<&'a Statistics>) {
|
||||||
|
for trk in file.trk.iter() {
|
||||||
|
self.add_track_stats(trk, stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_track_stats<'a>(&'a self, trk: &Track, stats: &mut Vec<&'a Statistics>) {
|
||||||
|
for trkseg in trk.trkseg.iter() {
|
||||||
|
self.add_segment_stats(trkseg, stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_segment_stats<'a>(&'a self, trkseg: &TrackSegment, stats: &mut Vec<&'a Statistics>) {
|
||||||
|
if let Some(s) = self.map.get(&trkseg.rev_id) {
|
||||||
|
stats.push(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
mod buffer;
|
mod cache;
|
||||||
mod statistics;
|
mod statistics;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
pub use buffer::*;
|
pub use cache::*;
|
||||||
pub use statistics::*;
|
pub use statistics::*;
|
||||||
pub use utils::*;
|
pub use utils::*;
|
||||||
|
|||||||
+22
-12
@@ -18,8 +18,15 @@ pub fn distance(p1: LngLat, p2: LngLat) -> f64 {
|
|||||||
EARTH_RADIUS * c
|
EARTH_RADIUS * c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn time_diff(a: &Option<i64>, b: &Option<i64>) -> Option<i32> {
|
||||||
|
match (a, b) {
|
||||||
|
(Some(t1), Some(t2)) => Some((t1 - t2) as i32),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Computes the speed for a given distance in kilometers and a time in milliseconds
|
/// Computes the speed for a given distance in kilometers and a time in milliseconds
|
||||||
pub fn speed(distance: f64, time: i64) -> f64 {
|
pub fn speed(distance: f64, time: i32) -> f64 {
|
||||||
distance / (time as f64 / 3600_000.0)
|
distance / (time as f64 / 3600_000.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +47,7 @@ fn get_meters_per_longitude_degree(latitude: f64) -> f64 {
|
|||||||
// Calculates the point on the line segment defined by p1 and p2
|
// Calculates the point on the line segment defined by p1 and p2
|
||||||
// that is closest to the third point, p3.
|
// that is closest to the third point, p3.
|
||||||
// Uses simple planar geometry (ignores earth curvature).
|
// Uses simple planar geometry (ignores earth curvature).
|
||||||
fn projected(p1: LngLat, p2: LngLat, coord3: LngLat) -> LngLat {
|
fn projected(p1: LngLat, p2: LngLat, p3: LngLat) -> LngLat {
|
||||||
// Convert to meters using approximate scaling
|
// Convert to meters using approximate scaling
|
||||||
let meters_per_longitude_degree = get_meters_per_longitude_degree(p1.lat);
|
let meters_per_longitude_degree = get_meters_per_longitude_degree(p1.lat);
|
||||||
|
|
||||||
@@ -48,8 +55,8 @@ fn projected(p1: LngLat, p2: LngLat, coord3: LngLat) -> LngLat {
|
|||||||
let y1 = p1.lat * METERS_PER_LATITUDE_DEGREE;
|
let y1 = p1.lat * METERS_PER_LATITUDE_DEGREE;
|
||||||
let x2 = p2.lng * meters_per_longitude_degree;
|
let x2 = p2.lng * meters_per_longitude_degree;
|
||||||
let y2 = p2.lat * METERS_PER_LATITUDE_DEGREE;
|
let y2 = p2.lat * METERS_PER_LATITUDE_DEGREE;
|
||||||
let x3 = coord3.lng * meters_per_longitude_degree;
|
let x3 = p3.lng * meters_per_longitude_degree;
|
||||||
let y3 = coord3.lat * METERS_PER_LATITUDE_DEGREE;
|
let y3 = p3.lat * METERS_PER_LATITUDE_DEGREE;
|
||||||
|
|
||||||
let dx = x2 - x1;
|
let dx = x2 - x1;
|
||||||
let dy = y2 - y1;
|
let dy = y2 - y1;
|
||||||
@@ -78,17 +85,20 @@ fn projected(p1: LngLat, p2: LngLat, coord3: LngLat) -> LngLat {
|
|||||||
/// Calculates the perpendicular distance in meters
|
/// Calculates the perpendicular distance in meters
|
||||||
/// between a line segment (defined by p1 and p2) and a third point, p3.
|
/// between a line segment (defined by p1 and p2) and a third point, p3.
|
||||||
/// Uses simple planar geometry (ignores earth curvature).
|
/// Uses simple planar geometry (ignores earth curvature).
|
||||||
fn crossarc(p1: LngLat, p2: LngLat, p3: LngLat) -> f64 {
|
fn crossarc_lnglat(p1: LngLat, p2: LngLat, p3: LngLat) -> f64 {
|
||||||
// Convert to meters using approximate scaling
|
// Convert to meters using approximate scaling
|
||||||
let meters_per_longitude_degree = get_meters_per_longitude_degree(p1.lat);
|
let meters_per_longitude_degree = get_meters_per_longitude_degree(p1.lat);
|
||||||
|
crossarc(
|
||||||
|
p1.lng * meters_per_longitude_degree,
|
||||||
|
p1.lat * METERS_PER_LATITUDE_DEGREE,
|
||||||
|
p2.lng * meters_per_longitude_degree,
|
||||||
|
p2.lat * METERS_PER_LATITUDE_DEGREE,
|
||||||
|
p3.lng * meters_per_longitude_degree,
|
||||||
|
p3.lat * METERS_PER_LATITUDE_DEGREE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let x1 = p1.lng * meters_per_longitude_degree;
|
pub fn crossarc(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> f64 {
|
||||||
let y1 = p1.lat * METERS_PER_LATITUDE_DEGREE;
|
|
||||||
let x2 = p2.lng * meters_per_longitude_degree;
|
|
||||||
let y2 = p2.lat * METERS_PER_LATITUDE_DEGREE;
|
|
||||||
let x3 = p3.lng * meters_per_longitude_degree;
|
|
||||||
let y3 = p3.lat * METERS_PER_LATITUDE_DEGREE;
|
|
||||||
|
|
||||||
let dx = x2 - x1;
|
let dx = x2 - x1;
|
||||||
let dy = y2 - y1;
|
let dy = y2 - y1;
|
||||||
let segment_length_squared = dx * dx + dy * dy;
|
let segment_length_squared = dx * dx + dy * dy;
|
||||||
|
|||||||
Reference in New Issue
Block a user