mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-27 10:37:17 +00:00
Compare commits
3
Commits
27c0a57cc1
...
5868595a6c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5868595a6c | ||
|
|
eb8e09b835 | ||
|
|
41dc853f65 |
No files matched your search
Generated
+25
-13
@@ -104,6 +104,7 @@ name = "gpx-rs"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"js-sys",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"uuid",
|
"uuid",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -135,9 +136,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.103"
|
version = "0.3.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
|
checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@@ -245,6 +246,17 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.24"
|
version = "1.0.24"
|
||||||
@@ -264,9 +276,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.126"
|
version = "0.2.129"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
|
checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -277,9 +289,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.126"
|
version = "0.2.129"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
|
checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -287,22 +299,22 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.126"
|
version = "0.2.129"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
|
checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 3.0.6",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.126"
|
version = "0.2.129"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
|
checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -328,7 +340,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.118",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -339,7 +351,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.118",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ crate-type = ["cdylib"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.45"
|
chrono = "0.4.45"
|
||||||
|
js-sys = "0.3.106"
|
||||||
quick-xml = "0.42.0"
|
quick-xml = "0.42.0"
|
||||||
uuid = {version = "1.26.1", features = ["v4", "js"]}
|
uuid = {version = "1.26.1", features = ["v4", "js"]}
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
```
|
||||||
|
wasm-pack build --target bundler
|
||||||
|
```
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
use crate::state::StackEntry;
|
||||||
|
|
||||||
|
pub trait Action {
|
||||||
|
fn apply(entry: &mut StackEntry) -> Result<(), &'static str>;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
use crate::action::Action;
|
||||||
@@ -1 +1,5 @@
|
|||||||
|
mod action;
|
||||||
|
mod file;
|
||||||
|
|
||||||
|
pub use action::*;
|
||||||
|
pub use file::*;
|
||||||
+24
-12
@@ -1,5 +1,8 @@
|
|||||||
|
// TODO license in every file
|
||||||
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use js_sys::Function;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
@@ -7,30 +10,27 @@ use crate::{
|
|||||||
gpx::GPXFile,
|
gpx::GPXFile,
|
||||||
io::parse,
|
io::parse,
|
||||||
selection::Selection,
|
selection::Selection,
|
||||||
stack::{Stack, StackEntry},
|
state::{Stack, StackEntry},
|
||||||
|
statistics::StatisticsBuffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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 Engine {
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
selection: Selection,
|
selection: Selection,
|
||||||
|
statistics: StatisticsBuffer,
|
||||||
|
callback: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl Controller {
|
impl Engine {
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new() -> Self {
|
pub fn new(callback: Function) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stack: Default::default(),
|
stack: Default::default(),
|
||||||
selection: Default::default(),
|
selection: Default::default(),
|
||||||
|
statistics: Default::default(),
|
||||||
|
callback,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +52,7 @@ impl Controller {
|
|||||||
true
|
true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let _ = self.callback.call1(&JsValue::NULL, &JsValue::from("done!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@@ -61,4 +62,15 @@ impl Controller {
|
|||||||
.create_and_push_next(|entry: &mut StackEntry| entry.remove(&id).is_some());
|
.create_and_push_next(|entry: &mut StackEntry| entry.remove(&id).is_some());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn event_loop(&mut self) {
|
||||||
|
// Control flow
|
||||||
|
// - get lock to cancel any other updates
|
||||||
|
// - update files
|
||||||
|
// - update separate statistics
|
||||||
|
// - update selection
|
||||||
|
// - update selection statistics
|
||||||
|
// self.statistics.update(stats);
|
||||||
|
// - notify UI through callback (changed files, updated selection, new stats)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+36
-2
@@ -1,12 +1,30 @@
|
|||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::gpx::{Trackpoint, Waypoint};
|
use crate::gpx::{Trackpoint, Waypoint};
|
||||||
|
|
||||||
static MAX_TRKPT_CHUNK_SIZE: usize = 4096;
|
static MAX_TRKPT_CHUNK_SIZE: usize = 4096;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
pub struct TrackpointChunk {
|
pub struct TrackpointChunk {
|
||||||
|
pub id: Uuid,
|
||||||
pub trkpt: Vec<Trackpoint>,
|
pub trkpt: Vec<Trackpoint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for TrackpointChunk {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
id: Uuid::new_v4(),
|
||||||
|
trkpt: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for TrackpointChunk {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id == other.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TrackpointChunk {
|
impl TrackpointChunk {
|
||||||
pub fn is_full(&self) -> bool {
|
pub fn is_full(&self) -> bool {
|
||||||
self.trkpt.len() == MAX_TRKPT_CHUNK_SIZE
|
self.trkpt.len() == MAX_TRKPT_CHUNK_SIZE
|
||||||
@@ -15,11 +33,27 @@ impl TrackpointChunk {
|
|||||||
|
|
||||||
static MAX_WPT_CHUNK_SIZE: usize = 128;
|
static MAX_WPT_CHUNK_SIZE: usize = 128;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
pub struct WaypointChunk {
|
pub struct WaypointChunk {
|
||||||
|
pub id: Uuid,
|
||||||
pub wpt: Vec<Waypoint>,
|
pub wpt: Vec<Waypoint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for WaypointChunk {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
id: Uuid::new_v4(),
|
||||||
|
wpt: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for WaypointChunk {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id == other.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl WaypointChunk {
|
impl WaypointChunk {
|
||||||
pub fn is_full(&self) -> bool {
|
pub fn is_full(&self) -> bool {
|
||||||
self.wpt.len() == MAX_WPT_CHUNK_SIZE
|
self.wpt.len() == MAX_WPT_CHUNK_SIZE
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, PartialEq, Eq)]
|
||||||
pub struct Link {
|
pub struct Link {
|
||||||
pub href: String,
|
pub href: String,
|
||||||
pub text: Option<String>,
|
pub text: Option<String>,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use crate::gpx::{Link, Track, WaypointChunk};
|
use crate::gpx::{Link, Track, WaypointChunk};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct GPXFile {
|
pub struct GPXFile {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub info: GPXFileInfo,
|
pub info: GPXFileInfo,
|
||||||
@@ -24,7 +24,7 @@ impl Default for GPXFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub struct GPXFileInfo {
|
pub struct GPXFileInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub desc: Option<String>,
|
pub desc: Option<String>,
|
||||||
@@ -33,7 +33,7 @@ pub struct GPXFileInfo {
|
|||||||
pub time: Option<i64>,
|
pub time: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub struct Author {
|
pub struct Author {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::{ops::Index, rc::Rc};
|
|||||||
|
|
||||||
use crate::gpx::{Trackpoint, TrackpointChunk};
|
use crate::gpx::{Trackpoint, TrackpointChunk};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub struct TrackSegment {
|
pub struct TrackSegment {
|
||||||
chunks: Vec<Rc<TrackpointChunk>>,
|
chunks: Vec<Rc<TrackpointChunk>>,
|
||||||
cumul_length: Vec<usize>,
|
cumul_length: Vec<usize>,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use crate::gpx::{Link, TrackSegment};
|
use crate::gpx::{Link, TrackSegment};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub struct Track {
|
pub struct Track {
|
||||||
pub info: TrackInfo,
|
pub info: TrackInfo,
|
||||||
pub trkseg: Vec<TrackSegment>,
|
pub trkseg: Vec<TrackSegment>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub struct TrackInfo {
|
pub struct TrackInfo {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub cmt: Option<String>,
|
pub cmt: Option<String>,
|
||||||
|
|||||||
+1
-1
@@ -4,6 +4,6 @@ mod engine;
|
|||||||
mod gpx;
|
mod gpx;
|
||||||
mod io;
|
mod io;
|
||||||
mod selection;
|
mod selection;
|
||||||
mod stack;
|
mod state;
|
||||||
mod statistics;
|
mod statistics;
|
||||||
mod utils;
|
mod utils;
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
use std::{collections::HashMap, rc::Rc};
|
|
||||||
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::gpx::GPXFile;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Stack {
|
|
||||||
entries: Vec<StackEntry>,
|
|
||||||
index: Option<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Stack {
|
|
||||||
pub fn current(&self) -> Option<&StackEntry> {
|
|
||||||
match self.index {
|
|
||||||
Some(i) => Some(&self.entries[i]),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_and_push_next<F>(&mut self, f: F)
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut StackEntry) -> bool,
|
|
||||||
{
|
|
||||||
let mut next = self.current().map_or_default(|c| c.clone());
|
|
||||||
if f(&mut 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 {
|
|
||||||
self.index.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn can_redo(&self) -> bool {
|
|
||||||
match self.index {
|
|
||||||
Some(i) => i + 1 < self.entries.len(),
|
|
||||||
None => !self.entries.is_empty(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn undo(&mut self) {
|
|
||||||
if let Some(i) = self.index {
|
|
||||||
if i == 0 {
|
|
||||||
self.index = None;
|
|
||||||
} else {
|
|
||||||
self.index = Some(i - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn redo(&mut self) {
|
|
||||||
match self.index {
|
|
||||||
Some(i) => {
|
|
||||||
if i + 1 < self.entries.len() {
|
|
||||||
self.index = Some(i + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
if !self.entries.is_empty() {
|
|
||||||
self.index = Some(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(&mut self, entry: StackEntry) {
|
|
||||||
if let Some(i) = self.index {
|
|
||||||
if i + 1 < self.entries.len() {
|
|
||||||
self.entries.truncate(i + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.entries.push(entry);
|
|
||||||
self.index = Some(self.entries.len() - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type StackEntry = HashMap<Uuid, Rc<GPXFile>>;
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mod stack;
|
||||||
|
|
||||||
|
pub use stack::*;
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::gpx::GPXFile;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Stack {
|
||||||
|
entries: Vec<StackEntry>,
|
||||||
|
index: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stack {
|
||||||
|
pub fn current(&self) -> Option<&StackEntry> {
|
||||||
|
self.index.map_or_default(|i| self.get(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_and_push_next<F>(&mut self, f: F) -> Option<Diff>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut StackEntry) -> bool,
|
||||||
|
{
|
||||||
|
self.record_diff(|stack| {
|
||||||
|
let mut next = stack.current().map_or_default(|c| c.clone());
|
||||||
|
if f(&mut next) {
|
||||||
|
stack.push(next);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn undo(&mut self) -> Option<Diff> {
|
||||||
|
self.record_diff(|stack| {
|
||||||
|
if let Some(i) = stack.index {
|
||||||
|
if i == 0 {
|
||||||
|
stack.index = None;
|
||||||
|
} else {
|
||||||
|
stack.index = Some(i - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn redo(&mut self) -> Option<Diff> {
|
||||||
|
self.record_diff(|stack| match stack.index {
|
||||||
|
Some(i) => {
|
||||||
|
if i + 1 < stack.entries.len() {
|
||||||
|
stack.index = Some(i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if !stack.entries.is_empty() {
|
||||||
|
stack.index = Some(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn can_undo(&self) -> bool {
|
||||||
|
self.index.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn can_redo(&self) -> bool {
|
||||||
|
match self.index {
|
||||||
|
Some(i) => i + 1 < self.entries.len(),
|
||||||
|
None => !self.entries.is_empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, index: usize) -> Option<&StackEntry> {
|
||||||
|
if index < self.entries.len() {
|
||||||
|
Some(&self.entries[index])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, entry: StackEntry) {
|
||||||
|
if let Some(i) = self.index {
|
||||||
|
if i + 1 < self.entries.len() {
|
||||||
|
self.entries.truncate(i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.entries.push(entry);
|
||||||
|
self.index = Some(self.entries.len() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record_diff<F>(&mut self, f: F) -> Option<Diff>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Self),
|
||||||
|
{
|
||||||
|
let prev = self.index;
|
||||||
|
f(self);
|
||||||
|
let cur = self.index;
|
||||||
|
if prev == cur {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let prev = prev.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 cur_ids: HashSet<Uuid> = cur.map_or_default(|e| e.keys().copied().collect());
|
||||||
|
let mut modified = vec![];
|
||||||
|
for id in prev_ids.intersection(&cur_ids) {
|
||||||
|
let before = prev.map_or_default(|e| e.get(id));
|
||||||
|
let after = cur.map_or_default(|e| e.get(id));
|
||||||
|
if before != after {
|
||||||
|
modified.push(*id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Diff {
|
||||||
|
added: cur_ids.difference(&prev_ids).copied().collect(),
|
||||||
|
removed: prev_ids.difference(&cur_ids).copied().collect(),
|
||||||
|
modified,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type StackEntry = HashMap<Uuid, Rc<GPXFile>>;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Diff {
|
||||||
|
pub added: Vec<Uuid>,
|
||||||
|
pub removed: Vec<Uuid>,
|
||||||
|
pub modified: Vec<Uuid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_new_file() {
|
||||||
|
let mut stack = Stack::default();
|
||||||
|
|
||||||
|
assert!(!stack.can_undo());
|
||||||
|
assert!(!stack.can_redo());
|
||||||
|
assert!(stack.current().is_none());
|
||||||
|
|
||||||
|
let diff = stack.create_and_push_next(|e| {
|
||||||
|
let file = Rc::new(GPXFile::default());
|
||||||
|
e.insert(file.id, file);
|
||||||
|
true
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(stack.can_undo());
|
||||||
|
assert!(!stack.can_redo());
|
||||||
|
assert!(stack.current().is_some());
|
||||||
|
|
||||||
|
assert!(diff.is_some());
|
||||||
|
|
||||||
|
let diff = diff.unwrap();
|
||||||
|
assert_eq!(diff.added.len(), 1);
|
||||||
|
assert!(diff.removed.is_empty());
|
||||||
|
assert!(diff.modified.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO more tests
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
use crate::statistics::{GPXStatistics, GlobalStatistics};
|
use crate::statistics::{GPXStatistics, GlobalStatistics, sum_options};
|
||||||
|
|
||||||
pub struct Buffer {
|
#[derive(Debug, Default)]
|
||||||
|
pub struct StatisticsBuffer {
|
||||||
pub total_distance: Vec<f64>,
|
pub total_distance: Vec<f64>,
|
||||||
|
pub moving_distance: Vec<f64>,
|
||||||
pub total_time: Vec<i64>,
|
pub total_time: Vec<i64>,
|
||||||
pub moving_time: Vec<i64>,
|
pub moving_time: Vec<i64>,
|
||||||
pub speed: Vec<f64>,
|
pub speed: Vec<f64>,
|
||||||
@@ -12,9 +14,10 @@ pub struct Buffer {
|
|||||||
pub slope_segment_distance: Vec<f64>,
|
pub slope_segment_distance: Vec<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Buffer {
|
impl StatisticsBuffer {
|
||||||
pub fn update(&mut self, stats: &[&GPXStatistics]) {
|
pub fn update(&mut self, stats: &[&GPXStatistics]) {
|
||||||
self.total_distance.clear();
|
self.total_distance.clear();
|
||||||
|
self.moving_distance.clear();
|
||||||
self.total_time.clear();
|
self.total_time.clear();
|
||||||
self.moving_time.clear();
|
self.moving_time.clear();
|
||||||
self.speed.clear();
|
self.speed.clear();
|
||||||
@@ -26,8 +29,32 @@ impl Buffer {
|
|||||||
|
|
||||||
let mut cumul_stats = GlobalStatistics::default();
|
let mut cumul_stats = GlobalStatistics::default();
|
||||||
for stats in stats {
|
for stats in stats {
|
||||||
// add stats
|
for trkpt_stats in stats.local.iter() {
|
||||||
// merge global ones into cumul
|
self.total_distance
|
||||||
|
.push(cumul_stats.total_distance + trkpt_stats.total_distance);
|
||||||
|
self.moving_distance.push(
|
||||||
|
sum_options(cumul_stats.moving_distance, trkpt_stats.moving_distance)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
self.total_time.push(
|
||||||
|
sum_options(cumul_stats.total_time(), trkpt_stats.total_time)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
self.moving_time.push(
|
||||||
|
sum_options(cumul_stats.moving_time, trkpt_stats.moving_time)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
self.speed.push(trkpt_stats.speed.unwrap_or_default());
|
||||||
|
self.elevation_gain
|
||||||
|
.push(cumul_stats.elevation_gain + trkpt_stats.elevation_gain);
|
||||||
|
self.elevation_loss
|
||||||
|
.push(cumul_stats.elevation_loss + trkpt_stats.elevation_loss);
|
||||||
|
self.slope.push(trkpt_stats.slope);
|
||||||
|
self.slope_segment_slope
|
||||||
|
.push(trkpt_stats.slope_segment.slope);
|
||||||
|
self.slope_segment_distance
|
||||||
|
.push(trkpt_stats.slope_segment.distance);
|
||||||
|
}
|
||||||
cumul_stats.merge(&stats.global);
|
cumul_stats.merge(&stats.global);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
mod buffer;
|
mod buffer;
|
||||||
mod statistics;
|
mod statistics;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
pub use buffer::*;
|
pub use buffer::*;
|
||||||
pub use statistics::*;
|
pub use statistics::*;
|
||||||
|
pub use utils::*;
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
use std::ops::Add;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
algorithm::ramer_douglas_peucker,
|
algorithm::ramer_douglas_peucker,
|
||||||
for_each_window,
|
for_each_window,
|
||||||
gpx::{LngLat, LngLatBounds, TrackSegment, Trackpoint},
|
gpx::{LngLat, LngLatBounds, TrackSegment, Trackpoint},
|
||||||
|
statistics::sum_options,
|
||||||
utils::{distance, slope, speed},
|
utils::{distance, slope, speed},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -15,22 +14,15 @@ pub struct GPXStatistics {
|
|||||||
|
|
||||||
impl GPXStatistics {
|
impl GPXStatistics {
|
||||||
pub fn total_time(&self) -> Option<i64> {
|
pub fn total_time(&self) -> Option<i64> {
|
||||||
self.global
|
self.global.total_time()
|
||||||
.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()
|
self.global.total_speed()
|
||||||
.map(|t| speed(self.global.total_distance, t))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn moving_speed(&self) -> Option<f64> {
|
pub fn moving_speed(&self) -> Option<f64> {
|
||||||
self.global
|
self.global.moving_speed()
|
||||||
.moving_distance
|
|
||||||
.zip(self.global.moving_time)
|
|
||||||
.map(|(d, t)| speed(d, t))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compute(trkseg: &TrackSegment) -> Self {
|
pub fn compute(trkseg: &TrackSegment) -> Self {
|
||||||
@@ -244,19 +236,21 @@ pub struct GlobalStatistics {
|
|||||||
pub bounds: LngLatBounds,
|
pub bounds: LngLatBounds,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sum_options<T>(a: Option<T>, b: Option<T>) -> Option<T>
|
impl GlobalStatistics {
|
||||||
where
|
pub fn total_time(&self) -> Option<i64> {
|
||||||
T: Add<Output = T>,
|
self.start_time.zip(self.end_time).map(|(t1, t2)| t2 - t1)
|
||||||
{
|
|
||||||
match (a, b) {
|
|
||||||
(Some(a), Some(b)) => Some(a + b),
|
|
||||||
(Some(a), None) => Some(a),
|
|
||||||
(None, Some(b)) => Some(b),
|
|
||||||
(None, None) => None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn total_speed(&self) -> Option<f64> {
|
||||||
|
self.total_time().map(|t| speed(self.total_distance, t))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn moving_speed(&self) -> Option<f64> {
|
||||||
|
self.moving_distance
|
||||||
|
.zip(self.moving_time)
|
||||||
|
.map(|(d, t)| speed(d, t))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalStatistics {
|
|
||||||
pub fn merge(&mut self, other: &GlobalStatistics) {
|
pub fn merge(&mut self, other: &GlobalStatistics) {
|
||||||
self.total_distance += other.total_distance;
|
self.total_distance += other.total_distance;
|
||||||
self.moving_distance = sum_options(self.moving_distance, other.moving_distance);
|
self.moving_distance = sum_options(self.moving_distance, other.moving_distance);
|
||||||
@@ -328,4 +322,6 @@ mod tests {
|
|||||||
assert!((speed - 20.0).abs() < 0.1);
|
assert!((speed - 20.0).abs() < 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO more tests
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
use std::ops::Add;
|
||||||
|
|
||||||
|
pub 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
class Engine {
|
|
||||||
_engine: Writable<any>;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this._engine = null;
|
|
||||||
import('gpx-rs').then((wasm) => {
|
|
||||||
this._engine = new wasm.Controller();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { writable, type Subscriber, type Writable } from 'svelte/store';
|
||||||
|
|
||||||
|
class Engine {
|
||||||
|
_engine: Writable<any>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._engine = writable(null);
|
||||||
|
import('gpx-rs').then((wasm) => {
|
||||||
|
this._engine.set(new wasm.Engine(this.onUpdate));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(run: Subscriber<any>, invalidate?: () => void) {
|
||||||
|
return this._engine.subscribe(run, invalidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate(message: String) {
|
||||||
|
console.log('message from WASM: ' + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const engine = new Engine();
|
||||||
Reference in new issue
Block a user