From 19d3fd9316c008dfd378a2cfa952ab4930a518b9 Mon Sep 17 00:00:00 2001 From: vcoppe Date: Fri, 24 May 2024 16:37:26 +0200 Subject: [PATCH] apply routing tool to selection --- gpx/src/gpx.ts | 87 ++++++++--- .../src/lib/components/file-list/FileList.ts | 33 +++-- .../src/lib/components/gpx-layer/GPXLayer.ts | 4 +- .../toolbar/tools/routing/Routing.svelte | 49 ++---- .../toolbar/tools/routing/RoutingControls.ts | 140 +++++++++++------- .../toolbar/tools/routing/Simplify.ts | 4 +- website/src/lib/db.ts | 55 ++++--- website/src/locales/en.json | 4 +- 8 files changed, 230 insertions(+), 146 deletions(-) diff --git a/gpx/src/gpx.ts b/gpx/src/gpx.ts index 781b42f3..b4c062e9 100644 --- a/gpx/src/gpx.ts +++ b/gpx/src/gpx.ts @@ -23,8 +23,7 @@ export abstract class GPXTreeElement> { abstract toGeoJSON(): GeoJSON.Feature | GeoJSON.Feature[] | GeoJSON.FeatureCollection | GeoJSON.FeatureCollection[]; // Producers - abstract replaceTrackPoints(segment: number, start: number, end: number, points: TrackPoint[]); - abstract reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date); + abstract _reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date); } export type AnyGPXTreeElement = GPXTreeElement>; @@ -56,22 +55,7 @@ abstract class GPXTreeNode> extends GPXTreeElement } // Producers - replaceTrackPoints(segment: number, start: number, end: number, points: TrackPoint[]) { - return produce(this, (draft: Draft>) => { - let og = getOriginal(draft); - let cumul = 0; - for (let i = 0; i < og.children.length; i++) { - let childSegments = og.children[i].getSegments(); - if (segment < cumul + childSegments.length) { - draft.children[i] = draft.children[i].replaceTrackPoints(segment - cumul, start, end, points); - break; - } - cumul += childSegments.length; - } - }); - } - - reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date) { + _reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date) { return produce(this, (draft: Draft>) => { let og = getOriginal(draft); if (!originalNextTimestamp && !newPreviousTimestamp) { @@ -84,7 +68,7 @@ abstract class GPXTreeNode> extends GPXTreeElement for (let i = 0; i < og.children.length; i++) { let originalStartTimestamp = og.children[og.children.length - i - 1].getStartTimestamp(); - draft.children[i] = draft.children[i].reverse(originalNextTimestamp, newPreviousTimestamp); + draft.children[i] = draft.children[i]._reverse(originalNextTimestamp, newPreviousTimestamp); originalNextTimestamp = originalStartTimestamp; newPreviousTimestamp = draft.children[i].getEndTimestamp(); @@ -153,6 +137,18 @@ export class GPXFile extends GPXTreeNode{ return this.trk; } + getSegment(trackIndex: number, segmentIndex: number): TrackSegment { + return this.trk[trackIndex].children[segmentIndex]; + } + + forEachSegment(callback: (segment: TrackSegment, trackIndex: number, segmentIndex: number) => void) { + this.trk.forEach((track, trackIndex) => { + track.children.forEach((segment, segmentIndex) => { + callback(segment, trackIndex, segmentIndex); + }); + }); + } + clone(): GPXFile { return new GPXFile({ attributes: cloneJSON(this.attributes), @@ -198,6 +194,15 @@ export class GPXFile extends GPXTreeNode{ }); } + replaceTrackPoints(trackIndex: number, segmentIndex: number, start: number, end: number, points: TrackPoint[]) { + return produce(this, (draft) => { + let og = getOriginal(draft); // Read as much as possible from the original object because it is faster + let trk = og.trk.slice(); + trk[trackIndex] = trk[trackIndex].replaceTrackPoints(segmentIndex, start, end, points); + draft.trk = freeze(trk); // Pre-freeze the array, faster as well + }); + } + replaceWaypoints(start: number, end: number, waypoints: Waypoint[]) { return produce(this, (draft) => { let og = getOriginal(draft); // Read as much as possible from the original object because it is faster @@ -206,6 +211,28 @@ export class GPXFile extends GPXTreeNode{ draft.wpt = freeze(wpt); // Pre-freeze the array, faster as well }); } + + reverse() { + return this._reverse(); + } + + reverseTrack(trackIndex: number) { + return produce(this, (draft) => { + let og = getOriginal(draft); // Read as much as possible from the original object because it is faster + let trk = og.trk.slice(); + trk[trackIndex] = trk[trackIndex]._reverse(); + draft.trk = freeze(trk); // Pre-freeze the array, faster as well + }); + } + + reverseTrackSegment(trackIndex: number, segmentIndex: number) { + return produce(this, (draft) => { + let og = getOriginal(draft); // Read as much as possible from the original object because it is faster + let trk = og.trk.slice(); + trk[trackIndex] = trk[trackIndex].reverseTrackSegment(segmentIndex); + draft.trk = freeze(trk); // Pre-freeze the array, faster as well + }); + } }; // A class that represents a Track in a GPX file @@ -298,6 +325,24 @@ export class Track extends GPXTreeNode { draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well }); } + + replaceTrackPoints(segmentIndex: number, start: number, end: number, points: TrackPoint[]) { + 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(); + trkseg[segmentIndex] = trkseg[segmentIndex].replaceTrackPoints(start, end, points); + draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well + }); + } + + reverseTrackSegment(segmentIndex: 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(); + trkseg[segmentIndex] = trkseg[segmentIndex]._reverse(); + draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well + }); + } }; // A class that represents a TrackSegment in a GPX file @@ -448,7 +493,7 @@ export class TrackSegment extends GPXTreeLeaf { } // Producers - replaceTrackPoints(segment: number, start: number, end: number, points: TrackPoint[]) { + replaceTrackPoints(start: number, end: number, points: TrackPoint[]) { 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.slice(); @@ -457,7 +502,7 @@ export class TrackSegment extends GPXTreeLeaf { }); } - reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date) { + _reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date) { return produce(this, (draft) => { if (originalNextTimestamp !== undefined && newPreviousTimestamp !== undefined) { let og = getOriginal(draft); // Read as much as possible from the original object because it is faster diff --git a/website/src/lib/components/file-list/FileList.ts b/website/src/lib/components/file-list/FileList.ts index 857316d5..b2cb9e51 100644 --- a/website/src/lib/components/file-list/FileList.ts +++ b/website/src/lib/components/file-list/FileList.ts @@ -4,6 +4,7 @@ export class SelectionTreeType { children: { [key: string | number]: SelectionTreeType }; + size: number = 0; constructor(item: ListItem) { this.item = item; @@ -16,18 +17,25 @@ export class SelectionTreeType { for (let key in this.children) { this.children[key].clear(); } + this.size = 0; } _setOrToggle(item: ListItem, value?: boolean) { if (item.level === this.item.level) { - this.selected = value === undefined ? !this.selected : value; + let newSelected = value === undefined ? !this.selected : value; + if (this.selected !== newSelected) { + this.selected = newSelected; + this.size += this.selected ? 1 : -1; + } } else { let id = item.getIdAtLevel(this.item.level); if (id !== undefined) { if (!this.children.hasOwnProperty(id)) { this.children[id] = new SelectionTreeType(this.item.extend(id)); } + this.size -= this.children[id].size; this.children[id]._setOrToggle(item, value); + this.size += this.children[id].size; } } } @@ -67,19 +75,23 @@ export class SelectionTreeType { return false; } - hasAnyChildren(item: ListItem, self: boolean = true): boolean { + hasAnyChildren(item: ListItem, self: boolean = true, ignoreIds?: (string | number)[]): boolean { if (this.selected && this.item.level >= item.level && (self || this.item.level > item.level)) { return this.selected; } let id = item.getIdAtLevel(this.item.level); if (id !== undefined) { - if (this.children.hasOwnProperty(id)) { - return this.children[id].hasAnyChildren(item, self); + if (ignoreIds === undefined || ignoreIds.indexOf(id) === -1) { + if (this.children.hasOwnProperty(id)) { + return this.children[id].hasAnyChildren(item, self, ignoreIds); + } } } else { for (let key in this.children) { - if (this.children[key].hasAnyChildren(item, self)) { - return true; + if (ignoreIds === undefined || ignoreIds.indexOf(key) === -1) { + if (this.children[key].hasAnyChildren(item, self, ignoreIds)) { + return true; + } } } } @@ -113,16 +125,9 @@ export class SelectionTreeType { } deleteChild(id: string | number) { + this.size -= this.children[id].size; delete this.children[id]; } - - get size(): number { - let size = this.selected ? 1 : 0; - for (let key in this.children) { - size += this.children[key].size; - } - return size; - } }; export enum ListLevel { diff --git a/website/src/lib/components/gpx-layer/GPXLayer.ts b/website/src/lib/components/gpx-layer/GPXLayer.ts index c487a31e..6111d71a 100644 --- a/website/src/lib/components/gpx-layer/GPXLayer.ts +++ b/website/src/lib/components/gpx-layer/GPXLayer.ts @@ -4,7 +4,7 @@ import { get, type Readable } from "svelte/store"; import mapboxgl from "mapbox-gl"; import { currentWaypoint, waypointPopup } from "./WaypointPopup"; import { addSelectItem, selectItem, selection } from "$lib/components/file-list/Selection"; -import { ListTrackSegmentItem, type ListItem, ListWaypointItem, ListWaypointsItem, ListTrackItem, ListFileItem } from "$lib/components/file-list/FileList"; +import { ListTrackSegmentItem, type ListItem, ListWaypointItem, ListWaypointsItem, ListTrackItem, ListFileItem, ListRootItem } from "$lib/components/file-list/FileList"; import type { Waypoint } from "gpx"; let defaultWeight = 5; @@ -213,7 +213,7 @@ export class GPXLayer { } selectOnClick(e: any) { - if (get(currentTool) === Tool.ROUTING) { + if (get(currentTool) === Tool.ROUTING && get(selection).hasAnyChildren(new ListRootItem(), true, ['waypoints'])) { return; } diff --git a/website/src/lib/components/toolbar/tools/routing/Routing.svelte b/website/src/lib/components/toolbar/tools/routing/Routing.svelte index ca245b72..5e8cba7f 100644 --- a/website/src/lib/components/toolbar/tools/routing/Routing.svelte +++ b/website/src/lib/components/toolbar/tools/routing/Routing.svelte @@ -31,7 +31,7 @@ import { fileObservers } from '$lib/db'; import { slide } from 'svelte/transition'; import { selection } from '$lib/components/file-list/Selection'; - import type { ListItem } from '$lib/components/file-list/FileList'; + import { ListRootItem, type ListItem } from '$lib/components/file-list/FileList'; let routingControls: Map = new Map(); let popupElement: HTMLElement; @@ -45,7 +45,7 @@ // remove controls for deleted files routingControls.forEach((controls, fileId) => { if (!$fileObservers.has(fileId)) { - controls.remove(); + controls.destroy(); routingControls.delete(fileId); if (selectedItem && selectedItem.getFileId() === fileId) { @@ -53,41 +53,19 @@ } } }); - } - - $: if ($map && $selection) { - // update selected file - if ($selection.size == 0 || $selection.size > 1 || !active) { - if (selectedItem) { - routingControls.get(selectedItem.getFileId())?.remove(); - } - selectedItem = null; - } else { - let newSelectedItem = get(selection).getSelected()[0]; - if (selectedItem !== newSelectedItem) { - if (selectedItem) { - routingControls.get(selectedItem.getFileId())?.remove(); - } - selectedItem = newSelectedItem; - } - } - } - - $: if ($map && selectedItem) { - let fileId = selectedItem.getFileId(); - if (!routingControls.has(fileId)) { - let selectedFileObserver = get(fileObservers).get(fileId); - if (selectedFileObserver) { + // add controls for new files + $fileObservers.forEach((file, fileId) => { + if (!routingControls.has(fileId)) { routingControls.set( fileId, - new RoutingControls(get(map), fileId, selectedFileObserver, popup, popupElement) + new RoutingControls(get(map), fileId, file, popup, popupElement) ); } - } else { - routingControls.get(fileId)?.add(); - } + }); } + $: validSelection = $selection.hasAnyChildren(new ListRootItem(), true, ['waypoints']); + onMount(() => { popup = new mapboxgl.Popup({ closeButton: false, @@ -154,7 +132,8 @@ @@ -164,7 +143,7 @@