mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-08-31 23:53:25 +00:00
163 lines
5.9 KiB
TypeScript
163 lines
5.9 KiB
TypeScript
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";
|
|
|
|
export class SplitControls {
|
|
active: boolean = false;
|
|
map: mapboxgl.Map;
|
|
controls: ControlWithMarker[] = [];
|
|
shownControls: ControlWithMarker[] = [];
|
|
unsubscribes: Function[] = [];
|
|
|
|
toggleControlsForZoomLevelAndBoundsBinded: () => void = this.toggleControlsForZoomLevelAndBounds.bind(this);
|
|
|
|
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);
|
|
}
|
|
|
|
updateControls() { // Update the markers when the files change
|
|
let controlIndex = 0;
|
|
applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
|
|
let file = getFile(fileId);
|
|
|
|
if (file) {
|
|
file.forEachSegment((segment, trackIndex, segmentIndex) => {
|
|
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?)
|
|
if (point._data.anchor) {
|
|
if (controlIndex < this.controls.length) {
|
|
this.controls[controlIndex].fileId = fileId;
|
|
this.controls[controlIndex].point = point;
|
|
this.controls[controlIndex].segment = segment;
|
|
this.controls[controlIndex].trackIndex = trackIndex;
|
|
this.controls[controlIndex].segmentIndex = segmentIndex;
|
|
this.controls[controlIndex].marker.setLngLat(point.getCoordinates());
|
|
} else {
|
|
this.controls.push(this.createControl(point, segment, fileId, trackIndex, segmentIndex));
|
|
}
|
|
controlIndex++;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
}
|
|
}, false);
|
|
|
|
while (controlIndex < this.controls.length) { // Remove the extra controls
|
|
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);
|
|
}
|
|
|
|
toggleControlsForZoomLevelAndBounds() { // Show markers only if they are in the current zoom level and bounds
|
|
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();
|
|
}
|
|
});
|
|
}
|
|
|
|
createControl(point: TrackPoint, segment: TrackSegment, fileId: string, trackIndex: number, segmentIndex: number): ControlWithMarker {
|
|
let element = document.createElement('div');
|
|
element.className = `h-6 w-6 p-0.5 rounded-full bg-white border-2 border-black cursor-pointer`;
|
|
element.innerHTML = Scissors.replace('width="24"', "").replace('height="24"', "").replace('stroke="currentColor"', 'stroke="black"');
|
|
|
|
let marker = new mapboxgl.Marker({
|
|
draggable: true,
|
|
className: 'z-10',
|
|
element
|
|
}).setLngLat(point.getCoordinates());
|
|
|
|
let control = {
|
|
point,
|
|
segment,
|
|
fileId,
|
|
trackIndex,
|
|
segmentIndex,
|
|
marker,
|
|
inZoom: false
|
|
};
|
|
|
|
marker.getElement().addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
dbUtils.split(control.fileId, control.trackIndex, control.segmentIndex, control.point.getCoordinates(), control.point._data.index);
|
|
});
|
|
|
|
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;
|
|
};
|