mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-11-15 07:07:07 +00:00
Migration to new Rust-based engine (#401)
* wasm first steps * parsing start * parsing continued * progress * progress * progress * progress * progress * progress * progress * progress * fixes * reorganize * change type * fix * progress * refactoring * progress * progress * progress * keep style info * progress * progress * remove (copy) in name * progress * merge methods into 1 * splice * progress * progress * progress * cleaning * progress * progress * progress * visibility * progress * progress * progress * progress * progress * fixes * progress * fix markers * fix * progress * progress * progress * progress * progress * progress * small fix * use engine info * fix popups * small fix * progress * progress * progress * clean tool * handle self closing elements * reduce data exchanges across boundary * copy on wasm side to avoid any data corruption * speed fix * routing * refactoring * reverse * reverse * crop * split * merge * progress * time tool * fix bounds for waypoints * reduce * fix reactivity * handle corrupt time data * export * progress * remove gpx js package * remove todo * update readme * cleaning * progress * fix link fields * remove unused fields * refactoring and tests * import and export of routes, ref #192 * per-chunk categories storage * cross-tab sync * outdated state * refactoring * reorder methods * refactoring * fix * improve flow * renaming * progress * progress * fix * progress * per file history * keep original flag * use history variant * fix * progress * file explorer * progress * fix * sticky folders * improved sticky folders * fix sortable * fix sortable * fix * fix tab size * no file delete on desktop * fix routing on other file * fix sorting * remember language * open links in browser * commit missing changes * avoid selecting on resize * clarify filename * explorer actions * fix folder styling * drop anywhere in folder * cleaning * fixes * remove focus-ring * full screen * small fix * consistent highlight color * improved preview * performance improvements * sparser data transfers * sparser data transfers * show popup once data is loaded * fix routing control size * fix * sparse desktop build * remove custom ign styles * use new endpoint for openhikingmap * strip native menu * reorder context menu items * route full support * route - track conversion * create route file action * route export, filter out anchors at high zoom level * clarify name handling * move hide & center to view * routes continued * update docs * fix power format, closes #378 * lint fixes * fix display of additional data with gaps, closes #361 * fix linting issues * fix import modules * fix multidrag * change base to resolve * fix type errors * fix layer typings * fix last type error * each keys * improve typings * deploy to cloudflare --------- Co-authored-by: vcoppe <vcoppe@hexaly.com>
This commit is contained in:
1 parent
6a0d434371
commit
8fe7d8d64d
484 files changed
+53949
-41152
No files matched your search
@@ -0,0 +1,232 @@
|
||||
use crate::{
|
||||
core::gpx::{TrackSegment, TrackSegmentIndex},
|
||||
core::utils::{crossarc, crossarc_lnglat},
|
||||
};
|
||||
|
||||
pub fn ramer_douglas_peucker<F>(
|
||||
trkseg: &TrackSegment,
|
||||
mapping: &F,
|
||||
epsilon: f64,
|
||||
) -> Vec<TrackSegmentIndex>
|
||||
where
|
||||
F: Fn(TrackSegmentIndex) -> (f64, f64),
|
||||
{
|
||||
let measure = |start, end, idx| {
|
||||
let (start, end, pt) = (mapping(start), mapping(end), mapping(idx));
|
||||
crossarc(start.0, start.1, end.0, end.1, pt.0, pt.1)
|
||||
};
|
||||
ramer_douglas_peucker_by(trkseg, &measure, epsilon)
|
||||
.into_iter()
|
||||
.map(|(idx, _)| idx)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Ramer-Douglas-Peucker with any distance from a point (the last argument) to the line made of
|
||||
/// two others (the first ones). Gives the kept points, with the distance at which each of them
|
||||
/// was selected (`None` for the ends of the segment, which are always kept).
|
||||
pub fn ramer_douglas_peucker_by<M>(
|
||||
trkseg: &TrackSegment,
|
||||
measure: &M,
|
||||
epsilon: f64,
|
||||
) -> Vec<(TrackSegmentIndex, Option<f64>)>
|
||||
where
|
||||
M: Fn(TrackSegmentIndex, TrackSegmentIndex, TrackSegmentIndex) -> f64,
|
||||
{
|
||||
match trkseg.len() {
|
||||
0 => vec![],
|
||||
1 => vec![(trkseg.first_index().unwrap(), None)],
|
||||
2 => vec![
|
||||
(trkseg.first_index().unwrap(), None),
|
||||
(trkseg.last_index().unwrap(), None),
|
||||
],
|
||||
_ => {
|
||||
let first = trkseg.first_index().unwrap();
|
||||
let last = trkseg.last_index().unwrap();
|
||||
|
||||
let mut kept = vec![(first, None)];
|
||||
ramer_douglas_peucker_helper(trkseg, first, last, measure, epsilon, &mut kept);
|
||||
kept.push((last, None));
|
||||
kept
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ramer_douglas_peucker_helper<M>(
|
||||
trkseg: &TrackSegment,
|
||||
start: TrackSegmentIndex,
|
||||
end: TrackSegmentIndex,
|
||||
measure: &M,
|
||||
epsilon: f64,
|
||||
kept: &mut Vec<(TrackSegmentIndex, Option<f64>)>,
|
||||
) where
|
||||
M: Fn(TrackSegmentIndex, TrackSegmentIndex, TrackSegmentIndex) -> f64,
|
||||
{
|
||||
let mut max_idx = None;
|
||||
let mut max_dist = 0.0;
|
||||
|
||||
let mut cur = trkseg.next_index(Some(start));
|
||||
while let Some(idx) = cur {
|
||||
if idx == end {
|
||||
break;
|
||||
}
|
||||
|
||||
let dist = measure(start, end, idx);
|
||||
if dist > max_dist {
|
||||
max_idx = Some(idx);
|
||||
max_dist = dist;
|
||||
}
|
||||
|
||||
cur = trkseg.next_index(cur);
|
||||
}
|
||||
|
||||
if let Some(idx) = max_idx.filter(|_| max_dist > epsilon) {
|
||||
ramer_douglas_peucker_helper(trkseg, start, idx, measure, epsilon, kept);
|
||||
kept.push((idx, Some(max_dist)));
|
||||
ramer_douglas_peucker_helper(trkseg, idx, end, measure, epsilon, kept);
|
||||
}
|
||||
}
|
||||
|
||||
/// Lowest tolerance (in meters) of the reduction of a segment, see [`reduction_distances`].
|
||||
pub const MIN_REDUCE_TOLERANCE: f64 = 0.1;
|
||||
|
||||
/// The distance (in meters) from a trackpoint to the line between two others of a segment.
|
||||
fn lnglat_measure(
|
||||
trkseg: &TrackSegment,
|
||||
) -> impl Fn(TrackSegmentIndex, TrackSegmentIndex, TrackSegmentIndex) -> f64 + '_ {
|
||||
move |start, end, idx| {
|
||||
crossarc_lnglat(
|
||||
trkseg[start].coordinates,
|
||||
trkseg[end].coordinates,
|
||||
trkseg[idx].coordinates,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// The indices of the trackpoints of a segment that are kept when it is reduced with `tolerance`
|
||||
/// meters (Ramer-Douglas-Peucker), in order. The ends are always kept.
|
||||
pub fn reduce_indices(trkseg: &TrackSegment, tolerance: f64) -> Vec<usize> {
|
||||
ramer_douglas_peucker_by(trkseg, &lnglat_measure(trkseg), tolerance)
|
||||
.into_iter()
|
||||
.map(|(idx, _)| idx.flat)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// For each trackpoint of a segment, the tolerance (in meters) up to which it is kept by
|
||||
/// [`reduce_indices`]: infinite for the ends, which are always kept, zero for the trackpoints that
|
||||
/// are not selected even at [`MIN_REDUCE_TOLERANCE`]. A trackpoint is kept with a tolerance if its
|
||||
/// value is greater than it.
|
||||
///
|
||||
/// A trackpoint is only selected if the ones that split the path before it are, so its value is
|
||||
/// the smallest distance at which it or one of them was selected.
|
||||
pub fn reduction_distances(trkseg: &TrackSegment) -> Vec<f64> {
|
||||
let mut distances = vec![0.0; trkseg.len()];
|
||||
let (Some(first), Some(last)) = (trkseg.first_index(), trkseg.last_index()) else {
|
||||
return distances;
|
||||
};
|
||||
distances[first.flat] = f64::INFINITY;
|
||||
distances[last.flat] = f64::INFINITY;
|
||||
if trkseg.len() > 2 {
|
||||
let measure = lnglat_measure(trkseg);
|
||||
reduction_distances_helper(trkseg, first, last, &measure, f64::INFINITY, &mut distances);
|
||||
}
|
||||
distances
|
||||
}
|
||||
|
||||
fn reduction_distances_helper<M>(
|
||||
trkseg: &TrackSegment,
|
||||
start: TrackSegmentIndex,
|
||||
end: TrackSegmentIndex,
|
||||
measure: &M,
|
||||
bound: f64,
|
||||
distances: &mut [f64],
|
||||
) where
|
||||
M: Fn(TrackSegmentIndex, TrackSegmentIndex, TrackSegmentIndex) -> f64,
|
||||
{
|
||||
let mut max_idx = None;
|
||||
let mut max_dist = 0.0;
|
||||
let mut cur = trkseg.next_index(Some(start));
|
||||
while let Some(idx) = cur {
|
||||
if idx == end {
|
||||
break;
|
||||
}
|
||||
let dist = measure(start, end, idx);
|
||||
if dist > max_dist {
|
||||
max_idx = Some(idx);
|
||||
max_dist = dist;
|
||||
}
|
||||
cur = trkseg.next_index(cur);
|
||||
}
|
||||
|
||||
if let Some(idx) = max_idx.filter(|_| max_dist > MIN_REDUCE_TOLERANCE) {
|
||||
let distance = max_dist.min(bound);
|
||||
distances[idx.flat] = distance;
|
||||
reduction_distances_helper(trkseg, start, idx, measure, distance, distances);
|
||||
reduction_distances_helper(trkseg, idx, end, measure, distance, distances);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{Trackpoint, TrackpointChunk};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn segment(eles: &[f64]) -> TrackSegment {
|
||||
let mut trkseg = TrackSegment::default();
|
||||
// several small chunks, to also exercise the chunk boundaries
|
||||
for part in eles.chunks(3) {
|
||||
let mut chunk = TrackpointChunk::default();
|
||||
for ele in part {
|
||||
chunk.trkpt.push(Trackpoint {
|
||||
ele: *ele,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
trkseg.push(chunk);
|
||||
}
|
||||
trkseg
|
||||
}
|
||||
|
||||
fn simplify(eles: &[f64], epsilon: f64) -> Vec<usize> {
|
||||
let trkseg = segment(eles);
|
||||
ramer_douglas_peucker(&trkseg, &|idx| (idx.flat as f64, trkseg[idx].ele), epsilon)
|
||||
.iter()
|
||||
.map(|idx| idx.flat)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_and_single_point() {
|
||||
assert!(simplify(&[], 1.0).is_empty());
|
||||
assert_eq!(simplify(&[5.0], 1.0), vec![0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collinear_points_are_removed() {
|
||||
let eles: Vec<f64> = (0..20).map(|i| i as f64 * 2.0).collect();
|
||||
assert_eq!(simplify(&eles, 0.1), vec![0, 19]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peak_is_kept_when_above_epsilon() {
|
||||
let eles = [0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0];
|
||||
assert_eq!(simplify(&eles, 5.0), vec![0, 3, 6]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peak_is_dropped_when_below_epsilon() {
|
||||
let eles = [0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0];
|
||||
assert_eq!(simplify(&eles, 1.0), vec![0, 6]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_indices_are_sorted_and_keep_extremities() {
|
||||
let eles = [0.0, 5.0, -3.0, 8.0, 1.0, 9.0, -4.0, 2.0, 7.0, 0.0];
|
||||
let kept = simplify(&eles, 0.5);
|
||||
assert_eq!(kept.first(), Some(&0));
|
||||
assert_eq!(kept.last(), Some(&9));
|
||||
assert!(kept.windows(2).all(|w| w[0] < w[1]));
|
||||
// a larger epsilon never keeps more points
|
||||
assert!(simplify(&eles, 5.0).len() <= kept.len());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user