2025-02-02 11:17:22 +01:00
|
|
|
import { TrackPoint, TrackSegment } from 'gpx';
|
|
|
|
import { get } from 'svelte/store';
|
|
|
|
import mapboxgl from 'mapbox-gl';
|
|
|
|
import { dbUtils, getFile } from '$lib/db';
|
|
|
|
import {
|
|
|
|
applyToOrderedSelectedItemsFromFile,
|
|
|
|
selection,
|
|
|
|
} from '$lib/components/file-list/Selection';
|
|
|
|
import { ListTrackSegmentItem } from '$lib/components/file-list/FileList';
|
|
|
|
import { currentTool, gpxStatistics, Tool } from '$lib/stores';
|
|
|
|
import { _ } from 'svelte-i18n';
|
|
|
|
import { Scissors } from 'lucide-static';
|
2024-07-25 16:15:44 +02:00
|
|
|
|
|
|
|
export class SplitControls {
|
|
|
|
active: boolean = false;
|
|
|
|
map: mapboxgl.Map;
|
|
|
|
controls: ControlWithMarker[] = [];
|
|
|
|
shownControls: ControlWithMarker[] = [];
|
|
|
|
unsubscribes: Function[] = [];
|
|
|
|
|
2025-02-02 11:17:22 +01:00
|
|
|
toggleControlsForZoomLevelAndBoundsBinded: () => void =
|
|
|
|
this.toggleControlsForZoomLevelAndBounds.bind(this);
|
2024-07-25 16:15:44 +02:00
|
|
|
|
|
|
|
constructor(map: mapboxgl.Map) {
|
|
|
|
this.map = map;
|
|
|
|
|
|
|
|
this.unsubscribes.push(selection.subscribe(this.addIfNeeded.bind(this)));
|
|
|
|
this.unsubscribes.push(gpxStatistics.subscribe(this.addIfNeeded.bind(this)));
|
|
|
|
this.unsubscribes.push(currentTool.subscribe(this.addIfNeeded.bind(this)));
|
|
|
|
}
|
|
|
|
|
|
|
|
addIfNeeded() {
|
|
|
|
let scissors = get(currentTool) === Tool.SCISSORS;
|
|
|
|
if (!scissors) {
|
|
|
|
if (this.active) {
|
|
|
|
this.remove();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.active) {
|
|
|
|
this.updateControls();
|
|
|
|
} else {
|
|
|
|
this.add();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
add() {
|
|
|
|
this.active = true;
|
|
|
|
|
|
|
|
this.map.on('zoom', this.toggleControlsForZoomLevelAndBoundsBinded);
|
|
|
|
this.map.on('move', this.toggleControlsForZoomLevelAndBoundsBinded);
|
|
|
|
}
|
|
|
|
|
2025-02-02 11:17:22 +01:00
|
|
|
updateControls() {
|
|
|
|
// Update the markers when the files change
|
2024-07-25 16:15:44 +02:00
|
|
|
let controlIndex = 0;
|
|
|
|
applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
|
|
|
|
let file = getFile(fileId);
|
|
|
|
|
|
|
|
if (file) {
|
|
|
|
file.forEachSegment((segment, trackIndex, segmentIndex) => {
|
2025-02-02 11:17:22 +01:00
|
|
|
if (
|
|
|
|
get(selection).hasAnyParent(
|
|
|
|
new ListTrackSegmentItem(fileId, trackIndex, segmentIndex)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
for (let point of segment.trkpt.slice(1, -1)) {
|
|
|
|
// Update the existing controls (could be improved by matching the existing controls with the new ones?)
|
2024-07-25 16:15:44 +02:00
|
|
|
if (point._data.anchor) {
|
|
|
|
if (controlIndex < this.controls.length) {
|
2024-09-05 09:51:43 +02:00
|
|
|
this.controls[controlIndex].fileId = fileId;
|
2024-07-25 16:15:44 +02:00
|
|
|
this.controls[controlIndex].point = point;
|
|
|
|
this.controls[controlIndex].segment = segment;
|
|
|
|
this.controls[controlIndex].trackIndex = trackIndex;
|
|
|
|
this.controls[controlIndex].segmentIndex = segmentIndex;
|
2025-02-02 11:17:22 +01:00
|
|
|
this.controls[controlIndex].marker.setLngLat(
|
|
|
|
point.getCoordinates()
|
|
|
|
);
|
2024-07-25 16:15:44 +02:00
|
|
|
} else {
|
2025-02-02 11:17:22 +01:00
|
|
|
this.controls.push(
|
|
|
|
this.createControl(
|
|
|
|
point,
|
|
|
|
segment,
|
|
|
|
fileId,
|
|
|
|
trackIndex,
|
|
|
|
segmentIndex
|
|
|
|
)
|
|
|
|
);
|
2024-07-25 16:15:44 +02:00
|
|
|
}
|
|
|
|
controlIndex++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}, false);
|
|
|
|
|
2025-02-02 11:17:22 +01:00
|
|
|
while (controlIndex < this.controls.length) {
|
|
|
|
// Remove the extra controls
|
2024-07-25 16:15:44 +02:00
|
|
|
this.controls.pop()?.marker.remove();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.toggleControlsForZoomLevelAndBounds();
|
|
|
|
}
|
|
|
|
|
|
|
|
remove() {
|
|
|
|
this.active = false;
|
|
|
|
|
|
|
|
for (let control of this.controls) {
|
|
|
|
control.marker.remove();
|
|
|
|
}
|
|
|
|
this.map.off('zoom', this.toggleControlsForZoomLevelAndBoundsBinded);
|
|
|
|
this.map.off('move', this.toggleControlsForZoomLevelAndBoundsBinded);
|
|
|
|
}
|
|
|
|
|
2025-02-02 11:17:22 +01:00
|
|
|
toggleControlsForZoomLevelAndBounds() {
|
|
|
|
// Show markers only if they are in the current zoom level and bounds
|
2024-07-25 16:15:44 +02:00
|
|
|
this.shownControls.splice(0, this.shownControls.length);
|
|
|
|
|
|
|
|
let southWest = this.map.unproject([0, this.map.getCanvas().height]);
|
|
|
|
let northEast = this.map.unproject([this.map.getCanvas().width, 0]);
|
|
|
|
let bounds = new mapboxgl.LngLatBounds(southWest, northEast);
|
|
|
|
|
|
|
|
let zoom = this.map.getZoom();
|
|
|
|
this.controls.forEach((control) => {
|
|
|
|
control.inZoom = control.point._data.zoom <= zoom;
|
|
|
|
if (control.inZoom && bounds.contains(control.marker.getLngLat())) {
|
|
|
|
control.marker.addTo(this.map);
|
|
|
|
this.shownControls.push(control);
|
|
|
|
} else {
|
|
|
|
control.marker.remove();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-02-02 11:17:22 +01:00
|
|
|
createControl(
|
|
|
|
point: TrackPoint,
|
|
|
|
segment: TrackSegment,
|
|
|
|
fileId: string,
|
|
|
|
trackIndex: number,
|
|
|
|
segmentIndex: number
|
|
|
|
): ControlWithMarker {
|
2024-07-25 16:15:44 +02:00
|
|
|
let element = document.createElement('div');
|
|
|
|
element.className = `h-6 w-6 p-0.5 rounded-full bg-white border-2 border-black cursor-pointer`;
|
2025-02-02 11:17:22 +01:00
|
|
|
element.innerHTML = Scissors.replace('width="24"', '')
|
|
|
|
.replace('height="24"', '')
|
|
|
|
.replace('stroke="currentColor"', 'stroke="black"');
|
2024-07-25 16:15:44 +02:00
|
|
|
|
|
|
|
let marker = new mapboxgl.Marker({
|
|
|
|
draggable: true,
|
|
|
|
className: 'z-10',
|
2025-02-02 11:17:22 +01:00
|
|
|
element,
|
2024-07-25 16:15:44 +02:00
|
|
|
}).setLngLat(point.getCoordinates());
|
|
|
|
|
|
|
|
let control = {
|
|
|
|
point,
|
|
|
|
segment,
|
|
|
|
fileId,
|
|
|
|
trackIndex,
|
|
|
|
segmentIndex,
|
|
|
|
marker,
|
2025-02-02 11:17:22 +01:00
|
|
|
inZoom: false,
|
2024-07-25 16:15:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
marker.getElement().addEventListener('click', (e) => {
|
|
|
|
e.stopPropagation();
|
2025-02-02 11:17:22 +01:00
|
|
|
dbUtils.split(
|
|
|
|
control.fileId,
|
|
|
|
control.trackIndex,
|
|
|
|
control.segmentIndex,
|
|
|
|
control.point.getCoordinates(),
|
|
|
|
control.point._data.index
|
|
|
|
);
|
2024-07-25 16:15:44 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
return control;
|
|
|
|
}
|
|
|
|
|
|
|
|
destroy() {
|
|
|
|
this.remove();
|
|
|
|
this.unsubscribes.forEach((unsubscribe) => unsubscribe());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type Control = {
|
|
|
|
segment: TrackSegment;
|
|
|
|
fileId: string;
|
|
|
|
trackIndex: number;
|
|
|
|
segmentIndex: number;
|
|
|
|
point: TrackPoint;
|
|
|
|
};
|
|
|
|
|
|
|
|
type ControlWithMarker = Control & {
|
|
|
|
marker: mapboxgl.Marker;
|
|
|
|
inZoom: boolean;
|
|
|
|
};
|