mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-01 08:12:32 +00:00
clean tool
This commit is contained in:
@@ -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" />
|
||||
|
@@ -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>
|
||||
|
177
website/src/lib/components/toolbar/tools/Clean.svelte
Normal file
177
website/src/lib/components/toolbar/tools/Clean.svelte
Normal 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>
|
@@ -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;
|
||||
|
@@ -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": {
|
||||
|
Reference in New Issue
Block a user