feat: add shared MapLibre GL utilities (journey line, markers)

This commit is contained in:
2026-06-19 21:43:45 +02:00
parent 0d1688c6c4
commit 12c5b2c4a1
+109
View File
@@ -0,0 +1,109 @@
/* Shared MapLibre GL utilities — loaded by map.html.twig, dailies.html.twig, home.html.twig */
(function (global) {
var ACCENT = '#2A8C73';
var ACCENT_DIM = '#155244';
var MAP_STYLE = 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json';
/* Build a GeoJSON LineString feature */
function lineFeature(coords) {
return { type: 'Feature', properties: {}, geometry: { type: 'LineString', coordinates: coords } };
}
/*
* Progressively draw the journey line using a requestAnimationFrame loop.
* coords: [[lng, lat], ...] in chronological order.
* sourceId: the MapLibre source id to update each frame.
*/
function animateJourneyLine(map, coords, sourceId) {
if (coords.length < 2) return;
/* Cumulative Euclidean distance between waypoints */
var segDist = [0];
for (var i = 1; i < coords.length; i++) {
var dx = coords[i][0] - coords[i - 1][0];
var dy = coords[i][1] - coords[i - 1][1];
segDist.push(segDist[i - 1] + Math.sqrt(dx * dx + dy * dy));
}
var totalDist = segDist[segDist.length - 1];
var DURATION = 5000;
var startTime = performance.now();
function frame(now) {
if (!map.getSource(sourceId)) return; /* map was removed */
var t = Math.min((now - startTime) / DURATION, 1);
var eased = 1 - Math.pow(1 - t, 3); /* ease-out cubic */
var target = eased * totalDist;
var animCoords = [coords[0]];
for (var j = 1; j < coords.length; j++) {
if (segDist[j] <= target) {
animCoords.push(coords[j]);
} else {
var frac = (target - segDist[j - 1]) / (segDist[j] - segDist[j - 1]);
animCoords.push([
coords[j - 1][0] + (coords[j][0] - coords[j - 1][0]) * frac,
coords[j - 1][1] + (coords[j][1] - coords[j - 1][1]) * frac
]);
break;
}
}
map.getSource(sourceId).setData(lineFeature(animCoords));
if (t < 1) requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}
/*
* Add a journey line source + two layers (glow + main) to a loaded map,
* then animate or draw instantly based on prefers-reduced-motion.
*/
function addJourneyLine(map, coords, sourceId) {
if (coords.length < 2) return;
map.addSource(sourceId, { type: 'geojson', data: lineFeature([coords[0]]) });
map.addLayer({
id: sourceId + '-glow', type: 'line', source: sourceId,
layout: { 'line-join': 'round', 'line-cap': 'round' },
paint: { 'line-color': ACCENT, 'line-width': 6, 'line-opacity': 0.18 }
});
map.addLayer({
id: sourceId + '-line', type: 'line', source: sourceId,
layout: { 'line-join': 'round', 'line-cap': 'round' },
paint: { 'line-color': ACCENT, 'line-width': 2.5, 'line-opacity': 0.85 }
});
var reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reducedMotion) {
map.getSource(sourceId).setData(lineFeature(coords));
} else {
animateJourneyLine(map, coords, sourceId);
}
}
/*
* Return a styled <div> element for a map marker dot.
* isLatest: make it larger with a teal ring.
*/
function createDotMarker(isLatest) {
var el = document.createElement('div');
var size = isLatest ? 18 : 12;
var bg = isLatest ? ACCENT_DIM : ACCENT;
var ring = isLatest ? ',0 0 0 4px rgba(42,140,115,0.25)' : '';
el.style.cssText = [
'width:' + size + 'px',
'height:' + size + 'px',
'background:' + bg,
'border:2px solid #fff',
'border-radius:50%',
'box-shadow:0 1px 4px rgba(0,0,0,0.4)' + ring,
'cursor:pointer'
].join(';');
return el;
}
global.MapUtils = { MAP_STYLE: MAP_STYLE, ACCENT: ACCENT, addJourneyLine: addJourneyLine, createDotMarker: createDotMarker };
})(window);