Skip to content

Instantly share code, notes, and snippets.

@mastersigat
Created May 21, 2019 08:17
Show Gist options
  • Save mastersigat/2c0514819a1e57040ae7cf7e35c66f05 to your computer and use it in GitHub Desktop.
Save mastersigat/2c0514819a1e57040ae7cf7e35c66f05 to your computer and use it in GitHub Desktop.
#MapboxGL / Carte itinéraire
license: mit
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>Animate a point along a route</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.54.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.54.0/mapbox-gl.css' rel='stylesheet' />
<style>
body { margin:0; padding:0; }
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
</head>
<body>
<style>
.overlay {
position: absolute;
top: 10px;
left: 10px;
}
.overlay button {
font:600 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
background-color: #3386c0;
color: #fff;
display: inline-block;
margin: 0;
padding: 10px 20px;
border: none;
cursor: pointer;
border-radius: 3px;
}
.overlay button:hover {
background-color:#4ea0da;
}
</style>
<script src='https://api.tiles.mapbox.com/mapbox.js/plugins/turf/v2.0.0/turf.min.js' charset='utf-8'></script>
<div id='map'></div>
<div class='overlay'>
<button id='replay'>Relancer le trajet</button>
</div>
<script>
// Appel de la carte
mapboxgl.accessToken = 'pk.eyJ1IjoibmluYW5vdW4iLCJhIjoiY2pjdHBoZGlzMnV4dDJxcGc5azJkbWRiYSJ9.o4dZRrdHcgVEKCveOXG1YQ';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [-1.7031,48.1183],
zoom: 16.3,
pitch: 45,
bearing:30
});
// Itinéraire (Geojson)
var route = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[
-1.703691,
48.121242
],
[
-1.703718,
48.120525
],
[
-1.703235,
48.120515
],
[
-1.70324,
48.119881
],
[
-1.703342,
48.119881
],
[
-1.703358,
48.119404
],
[
-1.70301,
48.119236
],
[
-1.702275,
48.119215
],
[
-1.702248,
48.118867
],
[
-1.702253,
48.118506
],
[
-1.702312,
48.117578
],
[
-1.702334,
48.11742
],
[
-1.703407,
48.117456
],
[
-1.703407,
48.117009
]
]
}
}]
};
// A single point that animates along the route.
// Coordinates are initially set to origin.
var point = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": origin
}
}]
};
// Calculate the distance in kilometers between route start/end point.
var lineDistance = turf.lineDistance(route.features[0], 'kilometers');
var arc = [];
// Number of steps to use in the arc and animation, more steps means
// a smoother arc and animation, but too many steps will result in a
// low frame rate
var steps = 500;
// Draw an arc between the `origin` & `destination` of the two points
for (var i = 0; i < lineDistance; i += lineDistance / steps) {
var segment = turf.along(route.features[0], i, 'kilometers');
arc.push(segment.geometry.coordinates);
}
// Update the route with calculated arc coordinates
route.features[0].geometry.coordinates = arc;
// Used to increment the value of the point measurement against the route.
var counter = 0;
map.on('load', function () {
// Add a source and layer displaying a point which will be animated in a circle.
map.addSource('route', {
"type": "geojson",
"data": route
});
map.addSource('point', {
"type": "geojson",
"data": point
});
map.addLayer({
"id": "route",
"source": "route",
"type": "line",
"paint": {
"line-width": 4,
"line-color": "#007cbf"
}
});
map.addLayer({
"id": "point",
"source": "point",
"type": "circle",
"paint": {
"circle-radius": 9,
"circle-color": "#007cbf",
"circle-stroke-color": "#FFFFFF",
"circle-stroke-width": 2.5 }
});
function animate() {
// Update point geometry to a new position based on counter denoting
// the index to access the arc.
point.features[0].geometry.coordinates = route.features[0].geometry.coordinates[counter];
// Calculate the bearing to ensure the icon is rotated to match the route arc
// The bearing is calculate between the current point and the next point, except
// at the end of the arc use the previous point and the current point
point.features[0].properties.bearing = turf.bearing(
turf.point(route.features[0].geometry.coordinates[counter >= steps ? counter - 1 : counter]),
turf.point(route.features[0].geometry.coordinates[counter >= steps ? counter : counter + 1])
);
// Update the source with this new data.
map.getSource('point').setData(point);
// Request the next frame of animation so long the end has not been reached.
if (counter < steps) {
requestAnimationFrame(animate);
}
counter = counter + 1;
}
document.getElementById('replay').addEventListener('click', function() {
// Set the coordinates of the original point back to origin
point.features[0].geometry.coordinates = origin;
// Update the source layer
map.getSource('point').setData(point);
// Reset the counter
counter = 0;
// Restart the animation.
animate(counter);
});
// Start the animation.
animate(counter);
var layers = map.getStyle().layers;
var labelLayerId;
for (var i = 0; i < layers.length; i++) {
if (layers[i].type === 'symbol' && layers[i].layout['text-field']) {
labelLayerId = layers[i].id;
break;
}
}
map.addLayer({
'id': '3d-buildings',
'source': 'composite',
'source-layer': 'building',
'filter': ['==', 'extrude', 'true'],
'type': 'fill-extrusion',
'minzoom': 15,
'paint': {
'fill-extrusion-color': '#aaa',
// use an 'interpolate' expression to add a smooth transition effect to the
// buildings as the user zooms in
'fill-extrusion-height': [
"interpolate", ["linear"], ["zoom"],
15, 0,
15.05, ["get", "height"]
],
'fill-extrusion-base': [
"interpolate", ["linear"], ["zoom"],
15, 0,
15.05, ["get", "min_height"]
],
'fill-extrusion-opacity': .6
}
}, labelLayerId);
map.loadImage("https://i.imgur.com/MK4NUzI.png", function(error, image) {
if (error) throw error;
map.addImage("custom-marker", image);
/* Style layer: A style layer ties together the source and image and specifies how they are displayed on the map. */
map.addLayer({
id: "markers",
type: "symbol",
/* Source: A data source specifies the geographic coordinate where the image marker gets placed. */
source: {
type: "geojson",
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {},
geometry: {
type: "Point",
coordinates: [-1.703407, 48.117009]
},
properties: {
"title": "MSHB",
"description": "Salle 108 / 1er étage"
}
}
]
}
},
layout: {
"icon-image": "custom-marker",
}
});
});
});
var popup = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false });
map.on('mousemove', function(e) {
var features = map.queryRenderedFeatures(e.point, { layers: ['markers'] });
// Change the cursor style as a UI indicator.
map.getCanvas().style.cursor = (features.length) ? 'pointer' : '';
if (!features.length) {
popup.remove();
return; }
var feature = features[0];
popup.setLngLat(feature.geometry.coordinates)
.setHTML('<h2>' + feature.properties.title + '</h2>'
+ feature.properties.description ) .addTo(map);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment