mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-06 22:04:06 +00:00
clean tool
This commit is contained in:
@@ -1,4 +1,10 @@
|
|||||||
use crate::{Apply, CommandError, LngLatBounds, State};
|
use std::{collections::HashSet, rc::Rc};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Apply, CommandError, Edit, Editor, File, FileId, LngLat, LngLatBounds, Selection, State,
|
||||||
|
TrackSegment, TrackSegmentId, Trackpoint, Waypoint, delete_waypoints, edit_waypoint_chunks,
|
||||||
|
update_selected,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum CleanType {
|
pub enum CleanType {
|
||||||
@@ -6,6 +12,9 @@ pub enum CleanType {
|
|||||||
Outside,
|
Outside,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the trackpoints and/or waypoints of the selection lying inside (or outside) a
|
||||||
|
/// rectangle. A selected file cleans its tracks and its waypoints, a selected track or segment
|
||||||
|
/// only trackpoints, selected waypoints only waypoints.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Clean {
|
pub struct Clean {
|
||||||
pub bounds: LngLatBounds,
|
pub bounds: LngLatBounds,
|
||||||
@@ -14,9 +23,385 @@ pub struct Clean {
|
|||||||
pub wpt: bool,
|
pub wpt: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Clean {
|
||||||
|
fn contains(&self, c: LngLat) -> bool {
|
||||||
|
(self.bounds.sw.lng..=self.bounds.ne.lng).contains(&c.lng)
|
||||||
|
&& (self.bounds.sw.lat..=self.bounds.ne.lat).contains(&c.lat)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn removes(&self, c: LngLat) -> bool {
|
||||||
|
self.contains(c) == (self.type_ == CleanType::Inside)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Apply for Clean {
|
impl Apply for Clean {
|
||||||
fn apply(self, _state: &mut State) -> Result<(), CommandError> {
|
fn apply(self, state: &mut State) -> Result<(), CommandError> {
|
||||||
// TODO
|
match &*state.selection {
|
||||||
Err(CommandError::NotImplemented("clean"))
|
Selection::Waypoint { file_id, .. } | Selection::Waypoints { file_id } => {
|
||||||
|
let file_id = *file_id;
|
||||||
|
if !self.wpt {
|
||||||
|
return Err(CommandError::NothingToDo);
|
||||||
|
}
|
||||||
|
let wpt_ids = match &*state.selection {
|
||||||
|
Selection::Waypoint { wpt_ids, .. } => Some(wpt_ids.clone()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let selected =
|
||||||
|
|wpt: &Waypoint| wpt_ids.as_ref().is_none_or(|ids| ids.contains(&wpt.id));
|
||||||
|
*state.selection = delete_waypoints(state.files, file_id, |wpt| {
|
||||||
|
selected(wpt) && self.removes(wpt.coordinates)
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Selection::Empty => Err(CommandError::NothingToDo),
|
||||||
|
_ => {
|
||||||
|
let mut cleaner = Cleaner {
|
||||||
|
clean: &self,
|
||||||
|
changed: false,
|
||||||
|
emptied: HashSet::new(),
|
||||||
|
wpt_files: HashSet::new(),
|
||||||
|
};
|
||||||
|
update_selected(state, &mut cleaner);
|
||||||
|
if cleaner.changed {
|
||||||
|
prune(state, &cleaner.emptied, &cleaner.wpt_files);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(CommandError::NothingToDo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Cleaner<'a> {
|
||||||
|
clean: &'a Clean,
|
||||||
|
changed: bool,
|
||||||
|
/// Segments left without trackpoints by the clean.
|
||||||
|
emptied: HashSet<TrackSegmentId>,
|
||||||
|
/// Files that lost waypoints.
|
||||||
|
wpt_files: HashSet<FileId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Editor for Cleaner<'_> {
|
||||||
|
fn file(&mut self, file: &mut File) -> Edit {
|
||||||
|
let mut edit = Edit::Unchanged;
|
||||||
|
for trk in &mut file.trk {
|
||||||
|
if self.track(trk) == Edit::Changed {
|
||||||
|
edit = Edit::Changed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.clean.wpt
|
||||||
|
&& edit_waypoint_chunks(
|
||||||
|
file,
|
||||||
|
|wpt| self.clean.removes(wpt.coordinates),
|
||||||
|
|wpts| {
|
||||||
|
wpts.retain(|wpt| !self.clean.removes(wpt.coordinates));
|
||||||
|
true
|
||||||
|
},
|
||||||
|
)
|
||||||
|
{
|
||||||
|
edit = Edit::Changed;
|
||||||
|
self.wpt_files.insert(file.id);
|
||||||
|
}
|
||||||
|
self.changed |= edit == Edit::Changed;
|
||||||
|
edit
|
||||||
|
}
|
||||||
|
|
||||||
|
fn segment(&mut self, segment: &mut TrackSegment) -> Edit {
|
||||||
|
if !self.clean.trkpt || !segment.iter().any(|pt| self.clean.removes(pt.coordinates)) {
|
||||||
|
return Edit::Unchanged;
|
||||||
|
}
|
||||||
|
let kept: Vec<Trackpoint> = segment
|
||||||
|
.iter()
|
||||||
|
.filter(|pt| !self.clean.removes(pt.coordinates))
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
if kept.is_empty() {
|
||||||
|
self.emptied.insert(segment.id);
|
||||||
|
}
|
||||||
|
segment.splice(0, segment.len(), kept);
|
||||||
|
self.changed = true;
|
||||||
|
Edit::Changed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes what the clean left empty: the emptied segments, the tracks that lost all their
|
||||||
|
/// segments and the files that lost something and have nothing left. The selection falls back
|
||||||
|
/// to the closest element still existing.
|
||||||
|
fn prune(state: &mut State, emptied: &HashSet<TrackSegmentId>, wpt_files: &HashSet<FileId>) {
|
||||||
|
let ids: Vec<FileId> = state
|
||||||
|
.files
|
||||||
|
.iter()
|
||||||
|
.filter(|(id, file)| {
|
||||||
|
wpt_files.contains(id)
|
||||||
|
|| file
|
||||||
|
.trk
|
||||||
|
.iter()
|
||||||
|
.any(|trk| trk.trkseg.iter().any(|seg| emptied.contains(&seg.id)))
|
||||||
|
})
|
||||||
|
.map(|(id, _)| *id)
|
||||||
|
.collect();
|
||||||
|
for id in ids {
|
||||||
|
let mut file = (*state.files[&id]).clone();
|
||||||
|
let lost_something = !emptied.is_empty() || wpt_files.contains(&id);
|
||||||
|
file.trk.retain_mut(|trk| {
|
||||||
|
let len = trk.trkseg.len();
|
||||||
|
trk.trkseg.retain(|seg| !emptied.contains(&seg.id));
|
||||||
|
trk.trkseg.len() == len || !trk.trkseg.is_empty()
|
||||||
|
});
|
||||||
|
if lost_something && file.trk.is_empty() && file.wpt.is_empty() {
|
||||||
|
state.files.remove(&id);
|
||||||
|
state.order.0.retain(|f| *f != id);
|
||||||
|
} else {
|
||||||
|
state.files.insert(id, Rc::new(file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*state.selection = fixed_selection(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fixed_selection(state: &State) -> Selection {
|
||||||
|
let files = &*state.files;
|
||||||
|
match &*state.selection {
|
||||||
|
Selection::File { file_ids } => {
|
||||||
|
let file_ids: HashSet<_> = file_ids
|
||||||
|
.iter()
|
||||||
|
.filter(|id| files.contains_key(id))
|
||||||
|
.copied()
|
||||||
|
.collect();
|
||||||
|
if file_ids.is_empty() {
|
||||||
|
Selection::Empty
|
||||||
|
} else {
|
||||||
|
Selection::File { file_ids }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Selection::Track { file_id, trk_ids } => {
|
||||||
|
let Some(file) = files.get(file_id) else {
|
||||||
|
return Selection::Empty;
|
||||||
|
};
|
||||||
|
let trk_ids: HashSet<_> = file
|
||||||
|
.trk
|
||||||
|
.iter()
|
||||||
|
.map(|trk| trk.id)
|
||||||
|
.filter(|id| trk_ids.contains(id))
|
||||||
|
.collect();
|
||||||
|
if trk_ids.is_empty() {
|
||||||
|
Selection::File {
|
||||||
|
file_ids: [*file_id].into(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Selection::Track {
|
||||||
|
file_id: *file_id,
|
||||||
|
trk_ids,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Selection::TrackSegment {
|
||||||
|
file_id,
|
||||||
|
trk_id,
|
||||||
|
trkseg_ids,
|
||||||
|
} => {
|
||||||
|
let Some(file) = files.get(file_id) else {
|
||||||
|
return Selection::Empty;
|
||||||
|
};
|
||||||
|
let Some(trk) = file.trk.iter().find(|trk| trk.id == *trk_id) else {
|
||||||
|
return Selection::File {
|
||||||
|
file_ids: [*file_id].into(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let trkseg_ids: HashSet<_> = trk
|
||||||
|
.trkseg
|
||||||
|
.iter()
|
||||||
|
.map(|seg| seg.id)
|
||||||
|
.filter(|id| trkseg_ids.contains(id))
|
||||||
|
.collect();
|
||||||
|
if trkseg_ids.is_empty() {
|
||||||
|
Selection::Track {
|
||||||
|
file_id: *file_id,
|
||||||
|
trk_ids: [*trk_id].into(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Selection::TrackSegment {
|
||||||
|
file_id: *file_id,
|
||||||
|
trk_id: *trk_id,
|
||||||
|
trkseg_ids,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Selection::Waypoints { file_id } | Selection::Waypoint { file_id, .. } => {
|
||||||
|
if files.contains_key(file_id) {
|
||||||
|
Selection::File {
|
||||||
|
file_ids: [*file_id].into(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Selection::Empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Selection::Empty => Selection::Empty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use crate::{FileId, Load, engine::command::fixture::Fixture};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn loaded() -> (Fixture, FileId) {
|
||||||
|
let mut fx = Fixture::default();
|
||||||
|
let data = std::fs::read("data/with_tracks_and_segments.gpx").unwrap();
|
||||||
|
Load {
|
||||||
|
data: &data,
|
||||||
|
name: "file",
|
||||||
|
}
|
||||||
|
.apply(&mut fx.state())
|
||||||
|
.unwrap();
|
||||||
|
let id = fx.order.0[0];
|
||||||
|
fx.selection = Selection::File {
|
||||||
|
file_ids: HashSet::from([id]),
|
||||||
|
};
|
||||||
|
(fx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(w: f64, s: f64, e: f64, n: f64) -> LngLatBounds {
|
||||||
|
LngLatBounds {
|
||||||
|
sw: LngLat { lng: w, lat: s },
|
||||||
|
ne: LngLat { lng: e, lat: n },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clean(bounds: LngLatBounds, type_: CleanType) -> Clean {
|
||||||
|
Clean {
|
||||||
|
bounds,
|
||||||
|
type_,
|
||||||
|
trkpt: true,
|
||||||
|
wpt: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count(fx: &Fixture, id: FileId) -> usize {
|
||||||
|
fx.files
|
||||||
|
.get(&id)
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|file| &file.trk)
|
||||||
|
.flat_map(|trk| &trk.trkseg)
|
||||||
|
.map(|seg| seg.len())
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_inside_everything_removes_all_trackpoints() {
|
||||||
|
let (mut fx, id) = loaded();
|
||||||
|
assert!(count(&fx, id) > 0);
|
||||||
|
clean(bounds(-180.0, -90.0, 180.0, 90.0), CleanType::Inside)
|
||||||
|
.apply(&mut fx.state())
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(count(&fx, id), 0);
|
||||||
|
// nothing is left of the file
|
||||||
|
assert!(!fx.files.contains_key(&id));
|
||||||
|
assert!(fx.order.0.is_empty());
|
||||||
|
assert_eq!(fx.selection, Selection::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_emptied_segments_and_tracks_are_removed() {
|
||||||
|
let (mut fx, id) = loaded();
|
||||||
|
let before = fx.files[&id].clone();
|
||||||
|
let seg = &before.trk[0].trkseg[0];
|
||||||
|
let pts: Vec<_> = seg.iter().map(|p| p.coordinates).collect();
|
||||||
|
let mut b = LngLatBounds::default();
|
||||||
|
pts.iter().for_each(|c| b.extend(*c));
|
||||||
|
clean(b, CleanType::Inside).apply(&mut fx.state()).unwrap();
|
||||||
|
let after = &fx.files[&id];
|
||||||
|
assert!(after.trk.iter().all(|t| !t.trkseg.is_empty()));
|
||||||
|
assert!(
|
||||||
|
after
|
||||||
|
.trk
|
||||||
|
.iter()
|
||||||
|
.flat_map(|t| &t.trkseg)
|
||||||
|
.all(|s| !s.is_empty())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_outside_everything_changes_nothing() {
|
||||||
|
let (mut fx, id) = loaded();
|
||||||
|
let before = fx.files[&id].clone();
|
||||||
|
let result =
|
||||||
|
clean(bounds(-180.0, -90.0, 180.0, 90.0), CleanType::Outside).apply(&mut fx.state());
|
||||||
|
assert!(matches!(result, Err(CommandError::NothingToDo)));
|
||||||
|
assert_eq!(fx.files[&id], before);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_partial_clean_only_removes_points_inside_and_bumps_revision() {
|
||||||
|
let (mut fx, id) = loaded();
|
||||||
|
let before = fx.files[&id].clone();
|
||||||
|
let first = before.trk[0].trkseg[0].iter().next().unwrap().coordinates;
|
||||||
|
let total = count(&fx, id);
|
||||||
|
// tiny rectangle around the first point
|
||||||
|
let b = bounds(
|
||||||
|
first.lng - 1e-9,
|
||||||
|
first.lat - 1e-9,
|
||||||
|
first.lng + 1e-9,
|
||||||
|
first.lat + 1e-9,
|
||||||
|
);
|
||||||
|
clean(b, CleanType::Inside).apply(&mut fx.state()).unwrap();
|
||||||
|
let removed = total - count(&fx, id);
|
||||||
|
assert!(removed >= 1);
|
||||||
|
let seg = &fx.files[&id].trk[0].trkseg[0];
|
||||||
|
assert_ne!(seg.rev_id, before.trk[0].trkseg[0].rev_id);
|
||||||
|
assert!(seg.iter().all(|pt| !b_contains(&b, pt.coordinates)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn b_contains(b: &LngLatBounds, c: LngLat) -> bool {
|
||||||
|
c.lng >= b.sw.lng && c.lng <= b.ne.lng && c.lat >= b.sw.lat && c.lat <= b.ne.lat
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_waypoints_flag_and_selection() {
|
||||||
|
let (mut fx, id) = loaded();
|
||||||
|
let mut file = (*fx.files[&id]).clone();
|
||||||
|
file.wpt = vec![std::rc::Rc::new(crate::WaypointChunk {
|
||||||
|
wpt: vec![Waypoint::default(), Waypoint::default()],
|
||||||
|
..Default::default()
|
||||||
|
})];
|
||||||
|
let ids: Vec<_> = file.wpt[0].wpt.iter().map(|w| w.id).collect();
|
||||||
|
fx.files.insert(id, std::rc::Rc::new(file));
|
||||||
|
let everywhere = bounds(-180.0, -90.0, 180.0, 90.0);
|
||||||
|
let wpt_count = |fx: &Fixture| {
|
||||||
|
fx.files
|
||||||
|
.get(&id)
|
||||||
|
.map_or(0, |f| f.wpt.iter().map(|c| c.wpt.len()).sum::<usize>())
|
||||||
|
};
|
||||||
|
|
||||||
|
// trackpoints only: waypoints are kept
|
||||||
|
Clean {
|
||||||
|
wpt: false,
|
||||||
|
..clean(everywhere, CleanType::Inside)
|
||||||
|
}
|
||||||
|
.apply(&mut fx.state())
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(wpt_count(&fx), 2);
|
||||||
|
|
||||||
|
// one selected waypoint
|
||||||
|
fx.selection = Selection::Waypoint {
|
||||||
|
file_id: id,
|
||||||
|
wpt_ids: HashSet::from([ids[0]]),
|
||||||
|
};
|
||||||
|
clean(everywhere, CleanType::Inside)
|
||||||
|
.apply(&mut fx.state())
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(wpt_count(&fx), 1);
|
||||||
|
|
||||||
|
// whole file
|
||||||
|
fx.selection = Selection::File {
|
||||||
|
file_ids: HashSet::from([id]),
|
||||||
|
};
|
||||||
|
clean(everywhere, CleanType::Inside)
|
||||||
|
.apply(&mut fx.state())
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(wpt_count(&fx), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,7 @@
|
|||||||
import { Trash2 } from '@lucide/svelte';
|
import { Trash2 } from '@lucide/svelte';
|
||||||
import { map } from '$lib/components/map/map';
|
import { map } from '$lib/components/map/map';
|
||||||
import type { GeoJSONSource } from 'maplibre-gl';
|
import type { GeoJSONSource } from 'maplibre-gl';
|
||||||
import { selection } from '$lib/logic/selection';
|
import { engine } from '$lib/engine';
|
||||||
import { fileActions } from '$lib/logic/file-actions';
|
|
||||||
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
|
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
|
||||||
import { ANCHOR_LAYER_KEY } from '$lib/components/map/style';
|
import { ANCHOR_LAYER_KEY } from '$lib/components/map/style';
|
||||||
|
|
||||||
@@ -130,7 +129,8 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let validSelection = $derived($selection.size > 0);
|
const selection = engine.selection;
|
||||||
|
let validSelection = $derived($selection.type !== 'empty');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-3 w-full max-w-80 items-center {props.class ?? ''}">
|
<div class="flex flex-col gap-3 w-full max-w-80 items-center {props.class ?? ''}">
|
||||||
@@ -163,17 +163,13 @@
|
|||||||
class="w-full"
|
class="w-full"
|
||||||
disabled={!validSelection || rectangleCoordinates.length != 2}
|
disabled={!validSelection || rectangleCoordinates.length != 2}
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
fileActions.cleanSelection(
|
engine.clean(
|
||||||
[
|
|
||||||
{
|
{
|
||||||
lat: Math.min(rectangleCoordinates[0].lat, rectangleCoordinates[1].lat),
|
west: Math.min(rectangleCoordinates[0].lng, rectangleCoordinates[1].lng),
|
||||||
lon: Math.min(rectangleCoordinates[0].lng, rectangleCoordinates[1].lng),
|
south: Math.min(rectangleCoordinates[0].lat, rectangleCoordinates[1].lat),
|
||||||
|
east: Math.max(rectangleCoordinates[0].lng, rectangleCoordinates[1].lng),
|
||||||
|
north: Math.max(rectangleCoordinates[0].lat, rectangleCoordinates[1].lat),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
lat: Math.max(rectangleCoordinates[0].lat, rectangleCoordinates[1].lat),
|
|
||||||
lon: Math.max(rectangleCoordinates[0].lng, rectangleCoordinates[1].lng),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
cleanType === CleanType.INSIDE,
|
cleanType === CleanType.INSIDE,
|
||||||
deleteTrackpoints,
|
deleteTrackpoints,
|
||||||
deleteWaypoints
|
deleteWaypoints
|
||||||
|
|||||||
@@ -494,6 +494,29 @@ class Engine {
|
|||||||
return this.run((w) => w.delete_waypoint(fileId, waypointId));
|
return this.run((w) => w.delete_waypoint(fileId, waypointId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the trackpoints and/or waypoints of the selection inside (or outside) a rectangle.
|
||||||
|
* Resolves to `false` when nothing was removed.
|
||||||
|
*/
|
||||||
|
clean(
|
||||||
|
bounds: { west: number; south: number; east: number; north: number },
|
||||||
|
inside: boolean,
|
||||||
|
trackpoints: boolean,
|
||||||
|
waypoints: boolean
|
||||||
|
) {
|
||||||
|
return this.run((w) =>
|
||||||
|
w.clean(
|
||||||
|
bounds.west,
|
||||||
|
bounds.south,
|
||||||
|
bounds.east,
|
||||||
|
bounds.north,
|
||||||
|
inside ? w.CleanType.Inside : w.CleanType.Outside,
|
||||||
|
trackpoints,
|
||||||
|
waypoints
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** A trackpoint of a segment, `undefined` if it does not exist. */
|
/** A trackpoint of a segment, `undefined` if it does not exist. */
|
||||||
trackpoint(fileId: string, segmentId: string, index: number): TrackpointDetails | undefined {
|
trackpoint(fileId: string, segmentId: string, index: number): TrackpointDetails | undefined {
|
||||||
return this.wasm?.trackpoint(fileId, segmentId, index);
|
return this.wasm?.trackpoint(fileId, segmentId, index);
|
||||||
|
|||||||
@@ -570,57 +570,6 @@ export const fileActions = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
cleanSelection: (
|
|
||||||
bounds: [Coordinates, Coordinates],
|
|
||||||
inside: boolean,
|
|
||||||
deleteTrackPoints: boolean,
|
|
||||||
deleteWaypoints: boolean
|
|
||||||
) => {
|
|
||||||
if (get(selection).size === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fileActionManager.applyGlobal((draft) => {
|
|
||||||
selection.applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
|
|
||||||
let file = draft.get(fileId);
|
|
||||||
if (file) {
|
|
||||||
if (level === ListLevel.FILE) {
|
|
||||||
file.clean(bounds, inside, deleteTrackPoints, deleteWaypoints);
|
|
||||||
} else if (level === ListLevel.TRACK) {
|
|
||||||
let trackIndices = items.map((item) =>
|
|
||||||
(item as ListTrackItem).getTrackIndex()
|
|
||||||
);
|
|
||||||
file.clean(
|
|
||||||
bounds,
|
|
||||||
inside,
|
|
||||||
deleteTrackPoints,
|
|
||||||
deleteWaypoints,
|
|
||||||
trackIndices
|
|
||||||
);
|
|
||||||
} else if (level === ListLevel.SEGMENT) {
|
|
||||||
let trackIndices = [(items[0] as ListTrackSegmentItem).getTrackIndex()];
|
|
||||||
let segmentIndices = items.map((item) =>
|
|
||||||
(item as ListTrackSegmentItem).getSegmentIndex()
|
|
||||||
);
|
|
||||||
file.clean(
|
|
||||||
bounds,
|
|
||||||
inside,
|
|
||||||
deleteTrackPoints,
|
|
||||||
deleteWaypoints,
|
|
||||||
trackIndices,
|
|
||||||
segmentIndices
|
|
||||||
);
|
|
||||||
} else if (level === ListLevel.WAYPOINTS) {
|
|
||||||
file.clean(bounds, inside, false, deleteWaypoints);
|
|
||||||
} else if (level === ListLevel.WAYPOINT) {
|
|
||||||
let waypointIndices = items.map((item) =>
|
|
||||||
(item as ListWaypointItem).getWaypointIndex()
|
|
||||||
);
|
|
||||||
file.clean(bounds, inside, false, deleteWaypoints, [], [], waypointIndices);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
reduce: (itemsAndPoints: Map<ListItem, TrackPoint[]>) => {
|
reduce: (itemsAndPoints: Map<ListItem, TrackPoint[]>) => {
|
||||||
if (itemsAndPoints.size === 0) {
|
if (itemsAndPoints.size === 0) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user