Files
bincio-activity/site/src/components/ActivityMap.svelte
T
Davide Scaini 3441079913 map now working
2026-03-28 19:34:22 +01:00

125 lines
3.8 KiB
Svelte

<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import type { Timeseries } from '../lib/types';
export let trackUrl: string;
export let timeseries: Timeseries | null = null;
export let bbox: [number, number, number, number] | null = null;
export let accentColor: string = '#00c8ff';
export let hoveredIdx: number | null = null;
let mapEl: HTMLDivElement;
let map: any;
const MarkerClass = maplibregl.Marker;
let hoverMarker: any;
let markersAdded = false;
const TILE_STYLE = 'https://tiles.openfreemap.org/styles/positron';
onMount(() => {
map = new maplibregl.Map({
container: mapEl,
style: TILE_STYLE,
center: [0, 0],
zoom: 1,
attributionControl: false,
});
map.addControl(new maplibregl.AttributionControl({ compact: true }), 'bottom-right');
// Hover dot marker — must set lngLat before addTo in MapLibre v5
const el = document.createElement('div');
el.style.cssText = `
width:12px;height:12px;border-radius:50%;
background:white;border:2px solid ${accentColor};
box-shadow:0 0 6px ${accentColor};display:none;pointer-events:none;
`;
hoverMarker = new maplibregl.Marker({ element: el, anchor: 'center' })
.setLngLat([0, 0])
.addTo(map);
map.on('load', () => {
map.addSource('track', {
type: 'geojson',
data: trackUrl,
lineMetrics: true,
});
map.addLayer({
id: 'track-shadow',
type: 'line',
source: 'track',
paint: { 'line-color': 'rgba(0,0,0,0.3)', 'line-width': 5, 'line-blur': 2 },
});
map.addLayer({
id: 'track-line',
type: 'line',
source: 'track',
layout: { 'line-cap': 'round', 'line-join': 'round' },
paint: {
'line-width': 3,
'line-gradient': [
'interpolate', ['linear'], ['line-progress'],
0, accentColor,
0.5, '#ff6b35',
1, accentColor,
],
},
});
});
});
// Fit to bbox when detail JSON loads (bbox is null at map init)
$: if (map && bbox) {
const fit = () => map.fitBounds(
[[bbox![0], bbox![1]], [bbox![2], bbox![3]]],
{ padding: 40, animate: true },
);
map.loaded() ? fit() : map.once('load', fit);
}
// Add start/end markers when timeseries arrives
$: if (map && MarkerClass && timeseries && !markersAdded) {
markersAdded = true;
const add = () => {
const lats = (timeseries!.lat ?? []).filter(v => v != null) as number[];
const lons = (timeseries!.lon ?? []).filter(v => v != null) as number[];
if (!lats.length) return;
new MarkerClass({ element: makeDot('#4ade80'), anchor: 'center' })
.setLngLat([lons[0], lats[0]]).addTo(map);
new MarkerClass({ element: makeDot('#f87171'), anchor: 'center' })
.setLngLat([lons[lons.length - 1], lats[lats.length - 1]]).addTo(map);
};
map.loaded() ? add() : map.once('load', add);
}
// Hover dot linked to chart crosshair
$: if (hoverMarker && timeseries && hoveredIdx != null) {
const lat = timeseries.lat?.[hoveredIdx];
const lon = timeseries.lon?.[hoveredIdx];
if (lat != null && lon != null) {
hoverMarker.getElement().style.display = 'block';
hoverMarker.setLngLat([lon, lat]);
}
} else if (hoverMarker) {
hoverMarker.getElement().style.display = 'none';
}
function makeDot(color: string): HTMLDivElement {
const el = document.createElement('div');
el.style.cssText = `
width:10px;height:10px;border-radius:50%;
background:${color};border:2px solid white;
box-shadow:0 0 4px rgba(0,0,0,0.5);
`;
return el;
}
onDestroy(() => map?.remove());
</script>
<div bind:this={mapEl} class="w-full h-full" />