Segment create: load existing segments in activity bbox as reference layer

This commit is contained in:
Davide Scaini
2026-05-17 09:40:41 +02:00
parent 6bc77486f1
commit 979a6c527f
+63 -1
View File
@@ -36,10 +36,13 @@
let mapEl: HTMLDivElement; let mapEl: HTMLDivElement;
let map: any; let map: any;
let mapReady = false; let mapReady = false;
let refSegments: { id: string; name: string; polyline: [number, number][] }[] = [];
let refPopup: any = null;
const TILE_STYLE = 'https://tiles.openfreemap.org/styles/positron'; const TILE_STYLE = 'https://tiles.openfreemap.org/styles/positron';
const DIM_COLOR = '#71717a'; const DIM_COLOR = '#71717a';
const SEL_COLOR = '#3b82f6'; const SEL_COLOR = '#3b82f6';
const REF_GRADIENT = ['interpolate', ['linear'], ['line-progress'], 0, '#22c55e', 1, '#ef4444'];
// ── Derived ─────────────────────────────────────────────────────────────── // ── Derived ───────────────────────────────────────────────────────────────
$: filteredActivities = activities.filter(a => $: filteredActivities = activities.filter(a =>
@@ -108,6 +111,18 @@
startIdx = 0; startIdx = 0;
endIdx = pts.length - 1; endIdx = pts.length - 1;
loadingTrack = false; loadingTrack = false;
// Fetch existing segments in this bbox as reference (fire-and-forget)
refSegments = [];
const lonMin = Math.min(...pts.map(p => p[1]));
const latMin = Math.min(...pts.map(p => p[0]));
const lonMax = Math.max(...pts.map(p => p[1]));
const latMax = Math.max(...pts.map(p => p[0]));
fetch(`/api/segments?bbox=${lonMin.toFixed(5)},${latMin.toFixed(5)},${lonMax.toFixed(5)},${latMax.toFixed(5)}`, { credentials: 'include' })
.then(r => r.ok ? r.json() : [])
.then(segs => { refSegments = segs; })
.catch(() => {});
// Map init happens after the DOM updates (bind:this needs the element) // Map init happens after the DOM updates (bind:this needs the element)
setTimeout(initMap, 0); setTimeout(initMap, 0);
} catch { } catch {
@@ -143,6 +158,17 @@
}; };
} }
function toRefCollection(segs: typeof refSegments) {
return {
type: 'FeatureCollection' as const,
features: segs.map(s => ({
type: 'Feature' as const,
geometry: { type: 'LineString' as const, coordinates: s.polyline.map(([lat, lon]: [number, number]) => [lon, lat]) },
properties: { id: s.id, name: s.name },
})),
};
}
function initMap() { function initMap() {
if (!mapEl || !gpsPoints.length) return; if (!mapEl || !gpsPoints.length) return;
const lats = gpsPoints.map(p => p[0]); const lats = gpsPoints.map(p => p[0]);
@@ -158,6 +184,37 @@
map.addControl(new maplibregl.AttributionControl({ compact: true }), 'bottom-right'); map.addControl(new maplibregl.AttributionControl({ compact: true }), 'bottom-right');
map.on('load', () => { map.on('load', () => {
// Reference segments — rendered below the activity track
map.addSource('refs', {
type: 'geojson',
lineMetrics: true,
data: toRefCollection(refSegments),
});
map.addLayer({ id: 'ref-line', type: 'line', source: 'refs',
layout: { 'line-cap': 'round', 'line-join': 'round' },
paint: {
'line-gradient': REF_GRADIENT,
'line-width': 3,
'line-opacity': 0.75,
},
});
map.on('mouseenter', 'ref-line', (e: any) => {
map.getCanvas().style.cursor = 'pointer';
const name = e.features?.[0]?.properties?.name;
if (name) {
refPopup = new maplibregl.Popup({ closeButton: false, closeOnClick: false, offset: 8 })
.setLngLat(e.lngLat)
.setText(name)
.addTo(map);
}
});
map.on('mouseleave', 'ref-line', () => {
map.getCanvas().style.cursor = '';
refPopup?.remove();
refPopup = null;
});
map.addSource('full', { type: 'geojson', data: toLine(gpsPoints) }); map.addSource('full', { type: 'geojson', data: toLine(gpsPoints) });
map.addSource('selected', { type: 'geojson', data: toLine(selectedPolyline) }); map.addSource('selected', { type: 'geojson', data: toLine(selectedPolyline) });
@@ -177,6 +234,11 @@
map.getSource('selected').setData(toLine(selectedPolyline)); map.getSource('selected').setData(toLine(selectedPolyline));
} }
// Update ref layer when segments load after the map is already ready
$: if (mapReady && map?.getSource('refs')) {
map.getSource('refs').setData(toRefCollection(refSegments));
}
// ── Slider guards ───────────────────────────────────────────────────────── // ── Slider guards ─────────────────────────────────────────────────────────
function onStartInput() { if (startIdx >= endIdx) startIdx = Math.max(0, endIdx - 1); } function onStartInput() { if (startIdx >= endIdx) startIdx = Math.max(0, endIdx - 1); }
function onEndInput() { if (endIdx <= startIdx) endIdx = Math.min(maxIdx, startIdx + 1); } function onEndInput() { if (endIdx <= startIdx) endIdx = Math.min(maxIdx, startIdx + 1); }
@@ -351,7 +413,7 @@
</div> </div>
<button <button
class="text-xs text-zinc-400 hover:text-white transition-colors shrink-0" class="text-xs text-zinc-400 hover:text-white transition-colors shrink-0"
on:click={() => { selectedActivity = null; gpsPoints = []; elevations = []; map?.remove(); mapReady = false; }} on:click={() => { selectedActivity = null; gpsPoints = []; elevations = []; refSegments = []; map?.remove(); mapReady = false; }}
>Change</button> >Change</button>
</div> </div>