Segment create: load existing segments in activity bbox as reference layer
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user