clean tool

This commit is contained in:
vcoppe
2024-06-11 16:33:06 +02:00
parent 14a81a530c
commit c4cc4b179b
6 changed files with 290 additions and 2 deletions

View File

@@ -52,7 +52,7 @@
</ToolbarItem>
<ToolbarItem tool={Tool.CLEAN}>
<SquareDashedMousePointer slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.clean_tooltip')}</span>
<span slot="tooltip">{$_('toolbar.clean.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.STYLE}>
<Palette slot="icon" size="18" />

View File

@@ -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 @@
<Waypoint />
{:else if $currentTool === Tool.MERGE}
<Merge />
{:else if $currentTool === Tool.CLEAN}
<Clean />
{/if}
</Card.Content>
</Card.Root>

View File

@@ -0,0 +1,177 @@
<script lang="ts" context="module">
enum CleanType {
INSIDE = 'inside',
OUTSIDE = 'outside'
}
</script>
<script lang="ts">
import { Label } from '$lib/components/ui/label/index.js';
import { Checkbox } from '$lib/components/ui/checkbox';
import * as RadioGroup from '$lib/components/ui/radio-group';
import { Button } from '$lib/components/ui/button';
import Help from '$lib/components/Help.svelte';
import { _ } from 'svelte-i18n';
import { onDestroy, onMount } from 'svelte';
import { resetCursor, setCrosshairCursor } from '$lib/utils';
import { Trash2 } from 'lucide-svelte';
import { map } from '$lib/stores';
import { selection } from '$lib/components/file-list/Selection';
import { dbUtils } from '$lib/db';
let cleanType = CleanType.INSIDE;
let deleteTrackpoints = true;
let deleteWaypoints = true;
let rectangleCoordinates: mapboxgl.LngLat[] = [];
function updateRectangle() {
if ($map) {
if (rectangleCoordinates.length != 2) {
if ($map.getLayer('rectangle')) {
$map.removeLayer('rectangle');
}
} else {
let data = {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [
[
[rectangleCoordinates[0].lng, rectangleCoordinates[0].lat],
[rectangleCoordinates[1].lng, rectangleCoordinates[0].lat],
[rectangleCoordinates[1].lng, rectangleCoordinates[1].lat],
[rectangleCoordinates[0].lng, rectangleCoordinates[1].lat],
[rectangleCoordinates[0].lng, rectangleCoordinates[0].lat]
]
]
}
};
let source = $map.getSource('rectangle');
if (source) {
source.setData(data);
} else {
$map.addSource('rectangle', {
type: 'geojson',
data: data
});
}
if (!$map.getLayer('rectangle')) {
$map.addLayer({
id: 'rectangle',
type: 'fill',
source: 'rectangle',
paint: {
'fill-color': 'SteelBlue',
'fill-opacity': 0.5
}
});
}
}
}
}
$: if (rectangleCoordinates) {
updateRectangle();
}
let mousedown = false;
function onMouseDown(e: any) {
mousedown = true;
rectangleCoordinates = [e.lngLat, e.lngLat];
}
function onMouseMove(e: any) {
if (mousedown) {
rectangleCoordinates[1] = e.lngLat;
}
}
function onMouseUp(e: any) {
mousedown = false;
}
onMount(() => {
setCrosshairCursor();
});
$: if ($map) {
$map.on('mousedown', onMouseDown);
$map.on('mousemove', onMouseMove);
$map.on('mouseup', onMouseUp);
$map.dragPan.disable();
}
onDestroy(() => {
resetCursor();
if ($map) {
$map.off('mousedown', onMouseDown);
$map.off('mousemove', onMouseMove);
$map.off('mouseup', onMouseUp);
$map.dragPan.enable();
if ($map.getLayer('rectangle')) {
$map.removeLayer('rectangle');
}
if ($map.getSource('rectangle')) {
$map.removeSource('rectangle');
}
}
});
$: validSelection = $selection.size > 0;
</script>
<div class="flex flex-col gap-3 max-w-80">
<fieldset class="flex flex-col gap-3">
<Label class="flex flex-row items-center gap-[6.4px] h-3">
<Checkbox bind:checked={deleteTrackpoints} class="scale-90" />
{$_('toolbar.clean.delete_trackpoints')}
</Label>
<Label class="flex flex-row items-center gap-[6.4px] h-3">
<Checkbox bind:checked={deleteWaypoints} class="scale-90" />
{$_('toolbar.clean.delete_waypoints')}
</Label>
<RadioGroup.Root bind:value={cleanType}>
<Label class="flex flex-row items-center gap-2">
<RadioGroup.Item value={CleanType.INSIDE} />
{$_('toolbar.clean.delete_inside')}
</Label>
<Label class="flex flex-row items-center gap-2">
<RadioGroup.Item value={CleanType.OUTSIDE} />
{$_('toolbar.clean.delete_outside')}
</Label>
</RadioGroup.Root>
</fieldset>
<Button
variant="outline"
disabled={!validSelection || rectangleCoordinates.length != 2}
on:click={() => {
dbUtils.cleanSelection(
[
{
lat: Math.min(rectangleCoordinates[0].lat, rectangleCoordinates[1].lat),
lon: Math.min(rectangleCoordinates[0].lng, rectangleCoordinates[1].lng)
},
{
lat: Math.max(rectangleCoordinates[0].lat, rectangleCoordinates[1].lat),
lon: Math.max(rectangleCoordinates[0].lng, rectangleCoordinates[1].lng)
}
],
cleanType === CleanType.INSIDE,
deleteTrackpoints,
deleteWaypoints
);
rectangleCoordinates = [];
}}
>
<Trash2 size="16" class="mr-1" />
{$_('toolbar.clean.button')}
</Button>
<Help>
{#if validSelection}
{$_('toolbar.clean.help')}
{:else}
{$_('toolbar.clean.help_no_selection')}
{/if}
</Help>
</div>

View File

@@ -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;

View File

@@ -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": {