Files
gpx.studio/website/src/lib/components/gpx-layer/GPXLayer.ts

266 lines
8.6 KiB
TypeScript
Raw Normal View History

2024-05-23 11:21:57 +02:00
import { map, currentTool, Tool } from "$lib/stores";
2024-05-08 21:31:54 +02:00
import { settings, type GPXFileWithStatistics } from "$lib/db";
2024-05-03 22:15:47 +02:00
import { get, type Readable } from "svelte/store";
import mapboxgl from "mapbox-gl";
2024-05-13 19:43:10 +02:00
import { currentWaypoint, waypointPopup } from "./WaypointPopup";
2024-05-23 14:44:07 +02:00
import { addSelect, selectFile, selection } from "$lib/components/file-list/Selection";
import { ListTrackSegmentItem, type ListItem, ListFileItem, ListTrackItem } from "$lib/components/file-list/FileList";
2024-04-25 13:48:31 +02:00
2024-05-23 11:21:57 +02:00
let defaultWeight = 5;
2024-04-25 13:48:31 +02:00
let defaultOpacity = 1;
const colors = [
'#ff0000',
'#0000ff',
'#46e646',
'#00ccff',
'#ff9900',
'#ff00ff',
'#ffff32',
'#288228',
'#9933ff',
'#50f0be',
'#8c645a'
];
const colorCount: { [key: string]: number } = {};
for (let color of colors) {
colorCount[color] = 0;
}
// Get the color with the least amount of uses
function getColor() {
let color = colors.reduce((a, b) => (colorCount[a] <= colorCount[b] ? a : b));
colorCount[color]++;
return color;
}
function decrementColor(color: string) {
colorCount[color]--;
}
2024-05-23 11:21:57 +02:00
const { directionMarkers } = settings;
2024-05-08 14:53:28 +02:00
2024-04-25 14:55:35 +02:00
export class GPXLayer {
2024-04-25 13:48:31 +02:00
map: mapboxgl.Map;
2024-04-30 20:55:47 +02:00
fileId: string;
2024-05-08 21:31:54 +02:00
file: Readable<GPXFileWithStatistics | undefined>;
2024-04-25 13:48:31 +02:00
layerColor: string;
markers: mapboxgl.Marker[] = [];
2024-05-23 11:21:57 +02:00
selected: ListItem[] = [];
2024-05-08 14:53:28 +02:00
unsubscribe: Function[] = [];
2024-04-25 13:48:31 +02:00
2024-05-03 15:59:34 +02:00
updateBinded: () => void = this.update.bind(this);
2024-04-25 16:41:06 +02:00
selectOnClickBinded: (e: any) => void = this.selectOnClick.bind(this);
2024-05-13 19:43:10 +02:00
constructor(map: mapboxgl.Map, fileId: string, file: Readable<GPXFileWithStatistics | undefined>) {
2024-04-25 13:48:31 +02:00
this.map = map;
2024-05-03 15:59:34 +02:00
this.fileId = fileId;
2024-05-08 21:31:54 +02:00
this.file = file;
2024-04-25 13:48:31 +02:00
this.layerColor = getColor();
2024-05-08 14:53:28 +02:00
this.unsubscribe.push(file.subscribe(this.updateBinded));
2024-05-23 11:21:57 +02:00
this.unsubscribe.push(selection.subscribe($selection => {
let selected = $selection.getChild(fileId)?.getSelected() || [];
if (selected.length !== this.selected.length || selected.some((item, index) => item !== this.selected[index])) {
this.selected = selected;
2024-05-08 21:31:54 +02:00
this.update();
2024-05-23 11:21:57 +02:00
if (this.selected.length > 0) {
this.moveToFront();
}
2024-05-08 21:31:54 +02:00
}
}));
2024-05-23 11:21:57 +02:00
this.unsubscribe.push(directionMarkers.subscribe(this.updateBinded));
2024-04-25 13:48:31 +02:00
2024-05-03 15:59:34 +02:00
this.map.on('style.load', this.updateBinded);
2024-04-25 13:48:31 +02:00
}
2024-05-03 15:59:34 +02:00
update() {
2024-05-08 21:31:54 +02:00
let file = get(this.file)?.file;
2024-05-03 15:59:34 +02:00
if (!file) {
return;
}
2024-05-04 23:50:27 +02:00
try {
2024-05-08 21:31:54 +02:00
let source = this.map.getSource(this.fileId);
if (source) {
source.setData(this.getGeoJSON());
} else {
2024-05-04 23:50:27 +02:00
this.map.addSource(this.fileId, {
type: 'geojson',
2024-05-08 21:31:54 +02:00
data: this.getGeoJSON()
2024-05-04 23:50:27 +02:00
});
}
if (!this.map.getLayer(this.fileId)) {
this.map.addLayer({
id: this.fileId,
type: 'line',
source: this.fileId,
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': ['get', 'color'],
'line-width': ['get', 'weight'],
'line-opacity': ['get', 'opacity']
}
});
2024-04-25 13:48:31 +02:00
2024-05-04 23:50:27 +02:00
this.map.on('click', this.fileId, this.selectOnClickBinded);
this.map.on('mouseenter', this.fileId, toPointerCursor);
this.map.on('mouseleave', this.fileId, toDefaultCursor);
}
2024-05-08 14:53:28 +02:00
if (get(directionMarkers)) {
if (!this.map.getLayer(this.fileId + '-direction')) {
this.map.addLayer({
id: this.fileId + '-direction',
type: 'symbol',
source: this.fileId,
layout: {
'text-field': '>',
'text-keep-upright': false,
'text-max-angle': 361,
2024-05-08 21:31:54 +02:00
'text-allow-overlap': true,
2024-05-08 14:53:28 +02:00
'symbol-placement': 'line',
'symbol-spacing': 25,
},
paint: {
'text-color': 'white',
'text-halo-width': 0.5,
'text-halo-color': 'white'
}
});
}
} else {
if (this.map.getLayer(this.fileId + '-direction')) {
this.map.removeLayer(this.fileId + '-direction');
}
}
2024-05-08 21:31:54 +02:00
} catch (e) { // No reliable way to check if the map is ready to add sources and layers
return;
2024-04-30 15:57:47 +02:00
}
let markerIndex = 0;
2024-05-03 15:59:34 +02:00
file.wpt.forEach((waypoint) => { // Update markers
2024-04-30 15:57:47 +02:00
if (markerIndex < this.markers.length) {
this.markers[markerIndex].setLngLat(waypoint.getCoordinates());
} else {
let marker = new mapboxgl.Marker().setLngLat(waypoint.getCoordinates());
2024-05-13 19:43:10 +02:00
marker.getElement().addEventListener('mouseover', (e) => {
currentWaypoint.set(waypoint);
marker.setPopup(waypointPopup);
marker.togglePopup();
e.stopPropagation();
});
2024-05-13 19:43:10 +02:00
marker.getElement().addEventListener('mouseout', () => {
marker.togglePopup();
});
this.markers.push(marker);
2024-04-30 15:57:47 +02:00
}
markerIndex++;
});
while (markerIndex < this.markers.length) { // Remove extra markers
this.markers.pop()?.remove();
}
this.markers.forEach((marker) => {
marker.addTo(this.map);
});
2024-04-25 13:48:31 +02:00
}
remove() {
2024-04-30 20:55:47 +02:00
this.map.off('click', this.fileId, this.selectOnClickBinded);
this.map.off('mouseenter', this.fileId, toPointerCursor);
this.map.off('mouseleave', this.fileId, toDefaultCursor);
2024-05-03 15:59:34 +02:00
this.map.off('style.load', this.updateBinded);
2024-04-25 13:48:31 +02:00
2024-05-08 14:53:28 +02:00
if (this.map.getLayer(this.fileId + '-direction')) {
this.map.removeLayer(this.fileId + '-direction');
}
2024-05-04 23:50:27 +02:00
if (this.map.getLayer(this.fileId)) {
this.map.removeLayer(this.fileId);
}
if (this.map.getSource(this.fileId)) {
this.map.removeSource(this.fileId);
}
2024-04-25 13:48:31 +02:00
this.markers.forEach((marker) => {
marker.remove();
});
2024-05-08 14:53:28 +02:00
this.unsubscribe.forEach((unsubscribe) => unsubscribe());
2024-04-25 13:48:31 +02:00
decrementColor(this.layerColor);
}
moveToFront() {
2024-05-03 15:59:34 +02:00
if (this.map.getLayer(this.fileId)) {
this.map.moveLayer(this.fileId);
}
2024-05-08 14:53:28 +02:00
if (this.map.getLayer(this.fileId + '-direction')) {
this.map.moveLayer(this.fileId + '-direction');
}
2024-04-25 13:48:31 +02:00
}
selectOnClick(e: any) {
if (get(currentTool) === Tool.ROUTING) {
return;
}
2024-04-25 13:48:31 +02:00
if (e.originalEvent.shiftKey) {
2024-05-23 11:21:57 +02:00
addSelect(this.fileId);
2024-04-25 13:48:31 +02:00
} else {
2024-05-23 14:44:07 +02:00
selectFile(this.fileId);
2024-04-25 13:48:31 +02:00
}
}
getGeoJSON(): GeoJSON.FeatureCollection {
2024-05-08 21:31:54 +02:00
let file = get(this.file)?.file;
2024-05-03 15:59:34 +02:00
if (!file) {
return {
type: 'FeatureCollection',
features: []
};
}
let data = file.toGeoJSON();
2024-05-23 11:21:57 +02:00
let trackIndex = 0, segmentIndex = 0;
2024-04-25 13:48:31 +02:00
for (let feature of data.features) {
if (!feature.properties) {
feature.properties = {};
}
if (!feature.properties.color) {
feature.properties.color = this.layerColor;
}
if (!feature.properties.weight) {
feature.properties.weight = defaultWeight;
}
if (!feature.properties.opacity) {
feature.properties.opacity = defaultOpacity;
}
2024-05-23 14:44:07 +02:00
if (get(selection).has(new ListFileItem(this.fileId)) || get(selection).has(new ListTrackItem(this.fileId, trackIndex)) || get(selection).has(new ListTrackSegmentItem(this.fileId, trackIndex, segmentIndex))) {
2024-05-23 11:21:57 +02:00
feature.properties.weight = feature.properties.weight + 2;
}
2024-05-08 21:31:54 +02:00
2024-05-23 11:21:57 +02:00
segmentIndex++;
if (segmentIndex >= file.trk[trackIndex].trkseg.length) {
segmentIndex = 0;
trackIndex++;
2024-05-08 21:31:54 +02:00
}
}
2024-05-23 11:21:57 +02:00
return data;
2024-05-08 21:31:54 +02:00
}
2024-04-25 13:48:31 +02:00
}
function toPointerCursor() {
get(map).getCanvas().style.cursor = 'pointer';
}
function toDefaultCursor() {
get(map).getCanvas().style.cursor = '';
}