Files
gpx.studio/website/src/lib/components/routing/RoutingControls.ts

220 lines
7.6 KiB
TypeScript
Raw Normal View History

2024-04-25 19:02:34 +02:00
import { distance, type Coordinates, type GPXFile, type TrackSegment } from "gpx";
2024-04-25 16:41:06 +02:00
import { get, type Writable } from "svelte/store";
2024-04-25 19:02:34 +02:00
import { computeAnchorPoints, type SimplifiedTrackPoint } from "./Simplify";
2024-04-25 16:41:06 +02:00
import mapboxgl from "mapbox-gl";
import { route } from "./Routing";
2024-04-25 19:02:34 +02:00
import { applyToFileElement, applyToFileStore } from "$lib/stores";
2024-04-25 16:41:06 +02:00
export class RoutingControls {
map: mapboxgl.Map;
file: Writable<GPXFile>;
markers: mapboxgl.Marker[] = [];
unsubscribe: () => void = () => { };
toggleMarkersForZoomLevelAndBoundsBinded: () => void = this.toggleMarkersForZoomLevelAndBounds.bind(this);
extendFileBinded: (e: mapboxgl.MapMouseEvent) => void = this.extendFile.bind(this);
constructor(map: mapboxgl.Map, file: Writable<GPXFile>) {
this.map = map;
this.file = file;
this.add();
}
add() {
this.map.on('zoom', this.toggleMarkersForZoomLevelAndBoundsBinded);
this.map.on('move', this.toggleMarkersForZoomLevelAndBoundsBinded);
this.map.on('click', this.extendFileBinded);
this.unsubscribe = this.file.subscribe(this.updateControls.bind(this));
}
updateControls() {
// Update controls
2024-04-25 19:02:34 +02:00
for (let segment of get(this.file).getSegments()) {
if (!segment._data.anchors) { // New segment
computeAnchorPoints(segment);
this.createMarkers(segment);
continue;
}
let anchors = segment._data.anchors;
for (let i = 0; i < anchors.length;) {
let anchor = anchors[i];
if (anchor.point._data.index >= segment.trkpt.length || anchor.point !== segment.trkpt[anchor.point._data.index]) { // Point removed
anchors.splice(i, 1);
let markerIndex = this.markers.findIndex(marker => marker._simplified === anchor);
this.markers[markerIndex].remove();
this.markers.splice(markerIndex, 1);
continue;
}
i++;
}
}
this.toggleMarkersForZoomLevelAndBounds();
2024-04-25 16:41:06 +02:00
}
remove() {
for (let marker of this.markers) {
marker.remove();
}
this.map.off('zoom', this.toggleMarkersForZoomLevelAndBoundsBinded);
this.map.off('move', this.toggleMarkersForZoomLevelAndBoundsBinded);
this.map.off('click', this.extendFileBinded);
this.unsubscribe();
}
2024-04-25 19:02:34 +02:00
createMarkers(segment: TrackSegment) {
for (let anchor of segment._data.anchors) {
this.createMarker(anchor);
2024-04-25 16:41:06 +02:00
}
}
2024-04-25 19:02:34 +02:00
createMarker(anchor: SimplifiedTrackPoint) {
let element = document.createElement('div');
element.className = `h-3 w-3 rounded-full bg-background border-2 border-black cursor-pointer`;
let marker = new mapboxgl.Marker({
draggable: true,
element
}).setLngLat(anchor.point.getCoordinates());
Object.defineProperty(marker, '_simplified', {
value: anchor
});
anchor.marker = marker;
marker.on('dragend', this.updateAnchor.bind(this));
this.markers.push(marker);
}
2024-04-25 16:41:06 +02:00
toggleMarkersForZoomLevelAndBounds() {
let zoom = this.map.getZoom();
this.markers.forEach((marker) => {
if (marker._simplified.zoom <= zoom && this.map.getBounds().contains(marker.getLngLat())) {
marker.addTo(this.map);
2024-04-25 19:02:34 +02:00
Object.defineProperty(marker, '_inZoom', {
value: true,
writable: true
});
2024-04-25 16:41:06 +02:00
} else {
marker.remove();
2024-04-25 19:02:34 +02:00
Object.defineProperty(marker, '_inZoom', {
value: false,
writable: true
});
2024-04-25 16:41:06 +02:00
}
});
}
2024-04-25 19:02:34 +02:00
updateAnchor(e: any) {
let marker = e.target;
let anchor = marker._simplified;
let latlng = marker.getLngLat();
let coordinates = {
lat: latlng.lat,
lon: latlng.lng
};
let segment = anchor.point._data.segment;
let anchors = segment._data.anchors;
let previousAnchor: SimplifiedTrackPoint | null = null;
let nextAnchor: SimplifiedTrackPoint | null = null;
for (let i = 0; i < anchors.length; i++) {
if (anchors[i].point._data.index < anchor.point._data.index && anchors[i].marker._inZoom) {
if (!previousAnchor || anchors[i].point._data.index > previousAnchor.point._data.index) {
previousAnchor = anchors[i];
}
} else if (anchors[i].point._data.index > anchor.point._data.index && anchors[i].marker._inZoom) {
if (!nextAnchor || anchors[i].point._data.index < nextAnchor.point._data.index) {
nextAnchor = anchors[i];
}
}
}
let routeCoordinates = [];
if (previousAnchor) {
routeCoordinates.push(previousAnchor.point.getCoordinates());
}
routeCoordinates.push(coordinates);
if (nextAnchor) {
routeCoordinates.push(nextAnchor.point.getCoordinates());
}
let start = previousAnchor ? previousAnchor.point._data.index + 1 : anchor.point._data.index;
let end = nextAnchor ? nextAnchor.point._data.index - 1 : anchor.point._data.index;
if (routeCoordinates.length === 1) {
return;
} else {
route(routeCoordinates).then((response) => {
if (previousAnchor) {
previousAnchor.zoom = 0;
} else {
anchor.zoom = 0;
anchor.point = response[0];
}
if (nextAnchor) {
nextAnchor.zoom = 0;
} else {
anchor.zoom = 0;
anchor.point = response[response.length - 1];
}
// find closest point to the dragged marker
// and transfer the marker to that point
if (previousAnchor && nextAnchor) {
let minDistance = Number.MAX_VALUE;
2024-04-25 19:13:53 +02:00
let minIndex = 0;
2024-04-25 19:02:34 +02:00
for (let i = 1; i < response.length - 1; i++) {
2024-04-25 19:13:53 +02:00
let dist = distance(response[i].getCoordinates(), coordinates);
2024-04-25 19:02:34 +02:00
if (dist < minDistance) {
minDistance = dist;
2024-04-25 19:13:53 +02:00
minIndex = i;
2024-04-25 19:02:34 +02:00
}
}
2024-04-25 19:13:53 +02:00
anchor.zoom = 0;
anchor.point = response[minIndex];
2024-04-25 19:02:34 +02:00
}
marker.setLngLat(anchor.point.getCoordinates());
applyToFileElement(this.file, segment, (segment) => {
segment.replace(start, end, response);
}, true);
});
}
}
2024-04-25 16:41:06 +02:00
async extendFile(e: mapboxgl.MapMouseEvent) {
let segments = get(this.file).getSegments();
if (segments.length === 0) {
return;
}
2024-04-25 19:02:34 +02:00
let segment = segments[segments.length - 1];
let anchors = segment._data.anchors;
2024-04-25 16:41:06 +02:00
let lastAnchor = anchors[anchors.length - 1];
let newPoint = {
lon: e.lngLat.lng,
lat: e.lngLat.lat
};
let response = await route([lastAnchor.point.getCoordinates(), newPoint]);
2024-04-25 19:02:34 +02:00
let anchor = {
point: response[response.length - 1],
zoom: 0
};
segment._data.anchors.push(anchor);
this.createMarker(anchor);
2024-04-25 16:41:06 +02:00
applyToFileStore(this.file, (f) => f.append(response), true);
}
}