diff --git a/site/src/components/SegmentCreate.svelte b/site/src/components/SegmentCreate.svelte index f2040b3..48128a6 100644 --- a/site/src/components/SegmentCreate.svelte +++ b/site/src/components/SegmentCreate.svelte @@ -36,10 +36,13 @@ let mapEl: HTMLDivElement; let map: any; 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 DIM_COLOR = '#71717a'; const SEL_COLOR = '#3b82f6'; + const REF_GRADIENT = ['interpolate', ['linear'], ['line-progress'], 0, '#22c55e', 1, '#ef4444']; // ── Derived ─────────────────────────────────────────────────────────────── $: filteredActivities = activities.filter(a => @@ -108,6 +111,18 @@ startIdx = 0; endIdx = pts.length - 1; 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) setTimeout(initMap, 0); } 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() { if (!mapEl || !gpsPoints.length) return; const lats = gpsPoints.map(p => p[0]); @@ -158,6 +184,37 @@ map.addControl(new maplibregl.AttributionControl({ compact: true }), 'bottom-right'); 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('selected', { type: 'geojson', data: toLine(selectedPolyline) }); @@ -177,6 +234,11 @@ 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 ───────────────────────────────────────────────────────── function onStartInput() { if (startIdx >= endIdx) startIdx = Math.max(0, endIdx - 1); } function onEndInput() { if (endIdx <= startIdx) endIdx = Math.min(maxIdx, startIdx + 1); } @@ -351,7 +413,7 @@