diff --git a/gpx/src/gpx.ts b/gpx/src/gpx.ts index 39662e5c..cc32e928 100644 --- a/gpx/src/gpx.ts +++ b/gpx/src/gpx.ts @@ -274,6 +274,42 @@ export class GPXFile extends GPXTreeNode{ draft.trk = freeze(trk); // Pre-freeze the array, faster as well }); } + + clean(bounds: [Coordinates, Coordinates], inside: boolean, deleteTrackPoints: boolean, deleteWaypoints: boolean, trackIndices?: number[], segmentIndices?: number[], waypointIndices?: number[]) { + return produce(this, (draft) => { + let og = getOriginal(draft); // Read as much as possible from the original object because it is faster + if (deleteTrackPoints) { + let trk = og.trk.slice(); + let i = 0; + let trackIndex = 0; + while (i < trk.length) { + if (trackIndices === undefined || trackIndices.includes(trackIndex)) { + trk[i] = trk[i].clean(bounds, inside, segmentIndices); + if (trk[i].getNumberOfTrackPoints() === 0) { + trk.splice(i, 1); + } else { + i++; + } + } else { + i++; + } + trackIndex++; + } + draft.trk = freeze(trk); // Pre-freeze the array, faster as well + } + if (deleteWaypoints) { + let wpt = og.wpt.filter((point, waypointIndex) => { + if (waypointIndices === undefined || waypointIndices.includes(waypointIndex)) { + let inBounds = point.attributes.lat >= bounds[0].lat && point.attributes.lat <= bounds[1].lat && point.attributes.lon >= bounds[0].lon && point.attributes.lon <= bounds[1].lon; + return inBounds !== inside; + } else { + return true; + } + }); + draft.wpt = freeze(wpt); // Pre-freeze the array, faster as well + } + }); + } }; // A class that represents a Track in a GPX file @@ -414,6 +450,29 @@ export class Track extends GPXTreeNode { draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well }); } + + clean(bounds: [Coordinates, Coordinates], inside: boolean, segmentIndices?: number[]) { + return produce(this, (draft) => { + let og = getOriginal(draft); // Read as much as possible from the original object because it is faster + let trkseg = og.trkseg.slice(); + let i = 0; + let segmentIndex = 0; + while (i < trkseg.length) { + if (segmentIndices === undefined || segmentIndices.includes(segmentIndex)) { + trkseg[i] = trkseg[i].clean(bounds, inside); + if (trkseg[i].getNumberOfTrackPoints() === 0) { + trkseg.splice(i, 1); + } else { + i++; + } + } else { + i++; + } + segmentIndex++; + } + draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well + }); + } }; // A class that represents a TrackSegment in a GPX file @@ -616,6 +675,17 @@ export class TrackSegment extends GPXTreeLeaf { draft.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well }); } + + clean(bounds: [Coordinates, Coordinates], inside: boolean) { + return produce(this, (draft) => { + let og = getOriginal(draft); // Read as much as possible from the original object because it is faster + let trkpt = og.trkpt.filter((point) => { + let inBounds = point.attributes.lat >= bounds[0].lat && point.attributes.lat <= bounds[1].lat && point.attributes.lon >= bounds[0].lon && point.attributes.lon <= bounds[1].lon; + return inBounds !== inside; + }); + draft.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well + }); + } }; export class TrackPoint { diff --git a/website/src/lib/components/toolbar/Toolbar.svelte b/website/src/lib/components/toolbar/Toolbar.svelte index 49584eb5..51bf8512 100644 --- a/website/src/lib/components/toolbar/Toolbar.svelte +++ b/website/src/lib/components/toolbar/Toolbar.svelte @@ -52,7 +52,7 @@ - {$_('toolbar.clean_tooltip')} + {$_('toolbar.clean.tooltip')} diff --git a/website/src/lib/components/toolbar/ToolbarItemMenu.svelte b/website/src/lib/components/toolbar/ToolbarItemMenu.svelte index d3b8a8c1..4dd34230 100644 --- a/website/src/lib/components/toolbar/ToolbarItemMenu.svelte +++ b/website/src/lib/components/toolbar/ToolbarItemMenu.svelte @@ -6,6 +6,7 @@ import Scissors from '$lib/components/toolbar/tools/Scissors.svelte'; import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte'; import Merge from '$lib/components/toolbar/tools/Merge.svelte'; + import Clean from '$lib/components/toolbar/tools/Clean.svelte'; import RoutingControlPopup from '$lib/components/toolbar/tools/routing/RoutingControlPopup.svelte'; import { onMount } from 'svelte'; import mapboxgl from 'mapbox-gl'; @@ -39,6 +40,8 @@ {:else if $currentTool === Tool.MERGE} + {:else if $currentTool === Tool.CLEAN} + {/if} diff --git a/website/src/lib/components/toolbar/tools/Clean.svelte b/website/src/lib/components/toolbar/tools/Clean.svelte new file mode 100644 index 00000000..a3c0f6a7 --- /dev/null +++ b/website/src/lib/components/toolbar/tools/Clean.svelte @@ -0,0 +1,177 @@ + + + + +
+
+ + + + + + +
+ + + {#if validSelection} + {$_('toolbar.clean.help')} + {:else} + {$_('toolbar.clean.help_no_selection')} + {/if} + +
diff --git a/website/src/lib/db.ts b/website/src/lib/db.ts index cbc6db33..83c82fc9 100644 --- a/website/src/lib/db.ts +++ b/website/src/lib/db.ts @@ -647,6 +647,35 @@ export const dbUtils = { } }); }, + cleanSelection: (bounds: [Coordinates, Coordinates], inside: boolean, deleteTrackPoints: boolean, deleteWaypoints: boolean) => { + if (get(selection).size === 0) { + return; + } + applyGlobal((draft) => { + applyToOrderedSelectedItemsFromFile((fileId, level, items) => { + let file = original(draft)?.get(fileId); + if (file) { + let newFile = file; + if (level === ListLevel.FILE) { + newFile = file.clean(bounds, inside, deleteTrackPoints, deleteWaypoints); + } else if (level === ListLevel.TRACK) { + let trackIndices = items.map((item) => (item as ListTrackItem).getTrackIndex()); + newFile = newFile.clean(bounds, inside, deleteTrackPoints, false, trackIndices); + } else if (level === ListLevel.SEGMENT) { + let trackIndices = [(items[0] as ListTrackSegmentItem).getTrackIndex()]; + let segmentIndices = items.map((item) => (item as ListTrackSegmentItem).getSegmentIndex()); + newFile = newFile.clean(bounds, inside, deleteTrackPoints, false, trackIndices, segmentIndices); + } else if (level === ListLevel.WAYPOINTS) { + newFile = newFile.clean(bounds, inside, false, deleteWaypoints); + } else if (level === ListLevel.WAYPOINT) { + let waypointIndices = items.map((item) => (item as ListWaypointItem).getWaypointIndex()); + newFile = newFile.clean(bounds, inside, false, deleteWaypoints, [], [], waypointIndices); + } + draft.set(newFile._data.id, freeze(newFile)); + } + }); + }); + }, deleteSelection: () => { if (get(selection).size === 0) { return; diff --git a/website/src/locales/en.json b/website/src/locales/en.json index 5e07aaeb..b58387ec 100644 --- a/website/src/locales/en.json +++ b/website/src/locales/en.json @@ -131,7 +131,16 @@ "extract_tooltip": "Extract inner tracks or segments", "waypoint_tooltip": "Create and edit points of interest", "reduce_tooltip": "Reduce the number of GPS points", - "clean_tooltip": "Clean GPS points and points of interest with a rectangle selection", + "clean": { + "tooltip": "Clean GPS points and points of interest with a rectangle selection", + "delete_trackpoints": "Delete GPS points", + "delete_waypoints": "Delete points of interest", + "delete_inside": "Delete inside selection", + "delete_outside": "Delete outside selection", + "button": "Delete", + "help": "Select a rectangle area on the map to remove GPS points and points of interest", + "help_no_selection": "Select a file element to use the tool" + }, "style_tooltip": "Change the style of the trace" }, "layers": {