Mapbox GL snap GPS coordinates do road - javascript

I use Mapbox to show on map the path that the user traveled (with my app on phone). My application saves the GPS position regularly, which I then show on the map with the help of Mapbox GL.
<script>
mapboxgl.accessToken = 'pk.XXX';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [aa.AAA, bb.BBB],
zoom: 18
});
var popup = new mapboxgl.Popup()
.setText("XXX YYY")
.addTo(map);
var marker = new mapboxgl.Marker()
.setLngLat([aa.AAA, bb.BBB])
.addTo(map)
.setPopup(popup);
map.on('load', function () {
map.addSource('route', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
[xx.XXX,yy.YYY],
[xx.XXX,yy.YYY],
[xx.XXX,yy.YYY]
}
}
});
map.addLayer({
'id': 'route',
'type': 'line',
'source': 'route',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#888',
'line-width': 8
}
});
});
</script>
I would like Mapbox to snap the position to the road on the map. Something like "snap-to-road" in Google Maps.
I know that Mapbox has something called "map matching". Only I want to list many intermediate points (not just the beginning and the end), and I would like everyone with them to be snaped to the road on the map.
Is something like this possible? Maybe there are some other mapping solutions that can do that?

Take a look at my solution below. It uses Mapbox map matching, then extracts the tracepoints from the response object and draws them on the map.
To make it work on your end, just replace <ACCESS_TOKEN> with your Mapbox access token.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Map Matched Coordinates</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" rel="stylesheet" />
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
mapboxgl.accessToken = '<ACCESS_TOKEN>';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [-117.17292,32.71256],
zoom: 15
});
map.on('load', function () {
// map matching request
var matched_coordinates = [];
// Create
let xhr = new XMLHttpRequest();
// GET REQUEST
xhr.open('GET', 'https://api.mapbox.com/matching/v5/mapbox/driving/' +
'-117.17282,32.71204;-117.17288,32.71225;-117.17293,32.71244;-117.17292,32.71256;-117.17298,32.712603;-117.17314,32.71259;-117.17334,32.71254' +
'?access_token=<ACCESS_TOKEN>' +
'&geometries=geojson');
// After Response is received
xhr.onload = function() {
if (xhr.status != 200) {
// NOT SUCCESSFUL
} else {
// SUCCESSFUL
var response = JSON.parse(xhr.response);
var tracepoints = response["tracepoints"];
for(var i = 0; i < tracepoints.length; i++){
matched_coordinates.push(tracepoints[i]["location"]);
}
map.addSource('route', {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': matched_coordinates
}
}
});
map.addLayer({
'id': 'route',
'type': 'line',
'source': 'route',
'layout': {
'line-join': 'round',
'line-cap': 'round'
},
'paint': {
'line-color': '#888',
'line-width': 8
}
});
}
}
xhr.send();
});
</script>
</body>
</html>

Related

How can I make custom waypoints on water with a click in mapbox

I have a map on my website that just shows the map and the location of me(later this is going to change to the location of an little boat). I want to mark a path in the water so the boat can travel in that exact path, so for example from point A to B to C etc. but I couldn't figure out how to do that with dropbox. So basically an delivery route but then on water. can anyone help me! this is what I have so far:
this is my map.html
<meta charset="UTF-8">
<title>Title</title>
<meta name='viewport' content='width=device-width, initial-scale=1' />
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link href="https://api.mapbox.com/mapbox-gl-js/v2.7.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v2.7.0/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute ; top: 56px; bottom: 0; width: 100%; }
#buttons { position: absolute; bottom: 0; margin: 0 0 10px 25px}
</style>
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
<body >
<div id="map"></div>
<div >
<button type="button" class="btn btn-primary" id="buttons">asdasd</button>
</div>
</body>
<script src="../js/map.js">
</script>
</html>
and this is my map.js:
mapboxgl.accessToken = 'my_token';
const map = new mapboxgl.Map({
container: 'map', // container ID
style: 'mapbox://styles/mapbox/streets-v11', // style URL
center: [-96, 37.8], // starting position
zoom: 3 // starting zoom
});
const geolocate = new mapboxgl.GeolocateControl()
map.addControl(geolocate)
map.on('load', function()
{
geolocate.trigger();
});
you just have to store at regular intervals the position of the boat (store it in a simple variable, write it in a file, use an API, use localStorage, everything depends on the use). Then you just have to add a layer of type "line" which will use the different points recorded to draw lines between each point.
Here is a working example https://codepen.io/cladjidane/pen/MWrJjjw
mapboxgl.accessToken =
'pk.eyJ1IjoiY2xhZGppZGFuZSIsImEiOiJjbDA4NmVra3gwMG1jM2dvM3V1djh2NnFwIn0.m8Swm9zWKqf0ozaNcZtISQ'
// Init map
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [-122.02991,37.33265],
zoom: 12,
})
// Definition of the configuration for GeolocateControl
let geolocate = new mapboxgl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 0,
},
trackUserLocation: true,
});
map.addControl(geolocate)
// Once the map is loaded
map.on('load', function () {
geolocate.trigger();
// We create a data source - GeoJSON format
const position_boat = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
coordinates: [],
},
},
],
}
// We add to the map this source
map.addSource('route', {
type: 'geojson',
data: position_boat,
})
// All the magic happens here:
// We push in our source the table of coordinates updated with the method "geolocate".
geolocate.on('geolocate', function (position) {
let newCoordinates = [position.coords.longitude, position.coords.latitude]
position_boat.features[0].geometry.coordinates.push(newCoordinates)
map.getSource('route').setData(position_boat)
})
// We add the layer that displays the source on the map
map.addLayer({
id: 'route',
type: 'line',
source: 'route',
layout: {
'line-join': 'round',
'line-cap': 'round',
},
paint: {
'line-color': 'red',
'line-width': 8,
},
})
})

mapbox: How to get light default map?

Currently, I have developing angular app which has map box integrated, I have followed the mapbox instructions but unable to get the desired output.
Can someone guide me that how to achieve following map?
<div class="row">
<mgl-map
renderWorldCopies="false"
[style]="'mapbox://styles/mapbox/light-v10'"
[zoom]="[0]"
[center]="center"
>
<mgl-image
id="blue"
url="../../../../../assets/images/blue.png"
(loaded)="imageLoaded = 'true'"
>
</mgl-image>
<mgl-image
id="red"
url="../../../../../assets/images/red.png"
(loaded)="imageLoaded = 'true'"
>
</mgl-image>
</mgl-image>
<mgl-geojson-source
id="points"
[data]="countryCoordinatesList"
></mgl-geojson-source>
<ng-container *ngIf="imageLoaded">
<mgl-layer
(click)="onClick(row)"
*ngFor="let row of countryList"
[id]="toStringNum(row?.properties?.id)"
type="symbol"
[source]="{
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [
row.geometry.coordinates[0],
row.geometry.coordinates[1]
]
}
}
}"
[layout]="{
'icon-image': row.properties.icon,
'icon-size': 0.4,
'icon-allow-overlap': true
}"
></mgl-layer>
</ng-container>
<mgl-layer
id="points"
source="points"
type="symbol"
[layout]=""
></mgl-layer>
</mgl-map>
Thanks in advance
To make your map look exactly like yours, you would have to modify the style a bit, however using this style
mapbox://styles/mapbox/light-v10
the below code should give you a map somewhat similar to what you showed. You would have to change the position of the markers and the marker image to match yours.
Also. please make sure to replace <YOUR_ACCESS_TOKEN> by your token.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Draw GeoJSON points</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" rel="stylesheet" />
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
mapboxgl.accessToken = '<YOUR_ACCESS_TOKEN>';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
center: [-96, 37.8],
zoom: 3
});
map.on('load', function () {
// Add an image to use as a custom marker
map.loadImage(
'https://docs.mapbox.com/mapbox-gl-js/assets/custom_marker.png',
function (error, image) {
if (error) throw error;
map.addImage('custom-marker', image);
// Add a GeoJSON source with 2 points
map.addSource('points', {
'type': 'geojson',
'data': {
'type': 'FeatureCollection',
'features': [
{
// feature for Mapbox DC
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [
-77.03238901390978,
38.913188059745586
]
},
'properties': {
'title': 'Mapbox DC'
}
},
{
// feature for Mapbox SF
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-122.414, 37.776]
},
'properties': {
'title': 'Mapbox SF'
}
}
]
}
});
// Add a symbol layer
map.addLayer({
'id': 'points',
'type': 'symbol',
'source': 'points',
'layout': {
'icon-image': 'custom-marker',
// get the title name from the source's "title" property
'text-field': ['get', 'title'],
'text-font': [
'Open Sans Semibold',
'Arial Unicode MS Bold'
],
'text-offset': [0, 1.25],
'text-anchor': 'top'
}
});
}
);
});
</script>
</body>
</html>

Mapbox - Trying to add a route between several markers

I am trying to learn JS and creating maps with the Mapbox GL JS and Directions. I managed to add markers to a map, and now I would like to add a route between the markers (1 -> 2 -> 3 -> 4).
After playing around for several time now, I got stuck. The markers are added, but no route is shown.
Would be create if you could help me :)
Thanks, Ben
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title></title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.11.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.11.0/mapbox-gl.css' rel='stylesheet' />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<style>
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
.marker {
background-image: url('mapbox-icon.png');
background-size: cover;
width: 50px;
height: 50px;
border-radius: 50%;
cursor: pointer;
}
</style>
</head>
<body>
<div id='map'></div>
<script>
mapboxgl.accessToken = 'xxx';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
center: [-122.662323, 45.523751],
zoom: 11
});
// code from the next step will go here!
var geojson = {
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [-122.662323, 45.523751]
},
properties: {
title: 'Mapbox',
description: 'Washington, D.C.'
}
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [-122.692323, 45.523751]
},
properties: {
title: 'Mapbox',
description: 'San Francisco, California'
}
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [-122.612323, 45.523751]
},
properties: {
title: 'Mapbox',
description: 'San Francisco, California'
}
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [-122.682323, 45.523751]
},
properties: {
title: 'Mapbox',
description: 'San Francisco, California'
}
}]
};
// add markers to map
geojson.features.forEach(function(marker) {
// create a HTML element for each feature
var el = document.createElement('div');
el.className = 'marker';
// make a marker for each feature and add to the map
new mapboxgl.Marker(el)
.setLngLat(marker.geometry.coordinates)
.addTo(map);
});
map.on('load', () => {
for (i = 0; i < geojson.length; i++) {
for(j = i+1; j < geojson.length; j++) {
var geomFrom = geojson[i];
var geomTo = geojson[j];
$.get(`https://api.mapbox.com/directions/v5/mapbox/driving/` + geomFrom[0] + ',' + geoFrom[1] + ';' + geomTo[0] + ',' + geomTo[1] + `?access_token=${mapboxgl.accessToken}&geometries=geojson`, data => {
map.addLayer({
id: 'route',
type: 'line',
source: {
type: 'geojson',
data: {
type: 'Feature',
properties: {},
geometry: data.routes[0].geometry,
},
},
layout: {
'line-join': 'round',
'line-cap': 'round',
},
paint: {
'line-color': '#ff7e5f',
'line-width': 8,
},
})
})
};
};
});
</script>
</body>
</html>
There are many issues I have found in the code, but long story short, the source you are creating is wrongly created. To simplify the explanation I had to remove the double for loop and the call to the service to show you how you should create the source for the path.
I have shaped this fiddle creating a path in Portland.
Here is the relevant js code for the layer you have to create:
map.addLayer({
id: 'route',
type: 'line',
source: {
'type': 'geojson',
'data': {
'type': 'Feature',
'properties': {},
'geometry': {
'type': 'LineString',
'coordinates': [
[
-122.68035572839027,
45.52729517240144
],
[
-122.67657260381876,
45.527330174436116
],
[
-122.67657129671815,
45.52666556739695
],
[
-122.67085005715236,
45.52677044480427
],
[
-122.66645605237485,
45.52862702776275
],
[
-122.66560830926798,
45.52866212597536
],
[
-122.66532421497476,
45.52712020010674
],
[
-122.6654770006126,
45.52158881104725
],
[
-122.66684678096325,
45.51749007039993
]
],
},
}
},
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': '#888',
'line-width': 8
}
})
Now knowing how this layer needs to be created, you can surely bring back your loops and the call to the service, but as long you are double looping you'll be creating a layer per loop, which is definitely NOT desirable nor recommendable.
I also noted the coords of the your geojson source are repeated and the coords do not correspond to the places indicated, but I ignored them for the sample in the fiddle.
If this answer solves your question about how to add a route between several markers, please mark it as answer accepted, in this way it will also help other users to know it was the right solution

Edit marker size and popup box size

I managed to get the marker and the popup box to appear in the Mapbox API
However, I don't seem to be able to edit their size and popup size.
for (let i = 0; i < locations.length; i++) {
let location = locations[i];
let marker = new mapboxgl.Marker({ color: '#FF8C00' });
marker.setLngLat(location.coordinates);
let popup = new mapboxgl.Popup({ offset: 40 });
popup.setHTML(location.description);
marker.setPopup(popup);
// Display the marker.
marker.addTo(map);
// Display the popup.
popup.addTo(map);
}
Mapbox ususally recommends to use the Annotation Plugin to create markers and interact with these.
However, if you would like to stick to the example you have presented, you could follow this Mapbox example in which custom icons are used for the marker symbol. The size of these icons can be adjusted with the iconSize property:
<script>
mapboxgl.accessToken = 'access_token';
var geojson = {
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'properties': {
'message': 'Foo',
'iconSize': [60, 60]
},
'geometry': {
'type': 'Point',
'coordinates': [-66.324462890625, -16.024695711685304]
}
},
{
'type': 'Feature',
'properties': {
'message': 'Bar',
'iconSize': [50, 50]
},
'geometry': {
'type': 'Point',
'coordinates': [-61.2158203125, -15.97189158092897]
}
},
{
'type': 'Feature',
'properties': {
'message': 'Baz',
'iconSize': [40, 40]
},
'geometry': {
'type': 'Point',
'coordinates': [-63.29223632812499, -18.28151823530889]
}
}
]
};
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [-65.017, -16.457],
zoom: 5
});
// add markers to map
geojson.features.forEach(function(marker) {
// create a DOM element for the marker
var el = document.createElement('div');
el.className = 'marker';
el.style.backgroundImage =
'url(https://placekitten.com/g/' +
marker.properties.iconSize.join('/') +
'/)';
el.style.width = marker.properties.iconSize[0] + 'px';
el.style.height = marker.properties.iconSize[1] + 'px';
el.addEventListener('click', function() {
window.alert(marker.properties.message);
});
// add marker to map
new mapboxgl.Marker(el)
.setLngLat(marker.geometry.coordinates)
.addTo(map);
});
</script>

Mapbox GL JS get features within geoson boundary

Below is a complete minimal example of a dot layer on top of a choropleth layer. You simply need to add an access token and it will work. When a user clicks within one of the choropleth boundaries I would like to get a list of the points within that boundary. This is so that I can use this data to populate an element elsewhere on the page. I am aware that some boundaries overlap, but I am not worried about how this is handled.
<head>
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v1.5.0/mapbox-gl.js"></script>
<link
href="https://api.tiles.mapbox.com/mapbox-gl-js/v1.5.0/mapbox-gl.css"
rel="stylesheet"
/>
</head>
<body>
<div id="map" style="width: 800px; height: 800px;"></div>
<script>
mapboxgl.accessToken =
"your token here";
const geojson_url =
"https://opendata.arcgis.com/datasets/d4d519d1d1a1455a9b82331228f77489_4.geojson";
fetch(geojson_url)
.then(res => res.json())
.then(geojson => {
const data = [
{ lat: "52.04009", long: "-0.52926" },
{ lat: "51.45906", long: "-2.60329" },
{ lat: "51.24257", long: "-0.58795" },
{ lat: "51.51161", long: "-0.11625" },
{ lat: "51.38113", long: "-2.36011" },
{ lat: "51.449824", long: "-2.60034" },
{ lat: "52.04009", long: "-0.52926" },
{ lat: "51.06386", long: "-2.90667" },
{ lat: "50.72623", long: "-3.5222" }
];
const map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/mapbox/light-v10",
zoom: 5.5,
center: [-2.2, 53]
});
map.on("load", function() {
// choropleth
geojson.features = geojson.features.map(feature => {
feature["id"] = feature.properties.objectid;
return feature;
});
map.addSource("leps", {
type: "geojson",
data: geojson
});
map.addLayer({
id: "leps-fills",
type: "fill",
source: "leps",
paint: {
"fill-color": "green",
"fill-opacity": 0.5
}
});
map.on("click", "leps-fills", function(e) {
// using this event, I would like to find all the points within the clicked boundary
console.log("list of points here");
});
// point data layers
// create points geojson data
const pointMapGeojson = {
type: "FeatureCollection",
features: data.map((g, i) => {
return {
type: "Feature",
properties: g,
id: i,
geometry: {
type: "Point",
coordinates: [g.long, g.lat]
}
};
})
};
map.addSource("point_layer", {
type: "geojson",
data: pointMapGeojson
});
map.addLayer({
id: "point_layer",
type: "circle",
source: "point_layer",
layout: {
visibility: "visible"
}
});
});
});
</script>
</body>
You may look at Turf.js. It provides a function to find any points within the polygon. https://turfjs.org/docs/#pointsWithinPolygon
Here is the example:
mapboxgl.accessToken = 'pk.eyJ1IjoicGFybmRlcHUiLCJhIjoiY2l6dXZ5OXVkMDByZDMycXI2NGgyOGdyNiJ9.jyTchGQ8N1gjPdra98qRYg';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
center: [-46.6062, -23.5513],
zoom: 11
});
map.on('load', function () {
var points = turf.points([
[-46.6318, -23.5523],
[-46.6246, -23.5325],
[-46.6062, -23.5513],
[-46.663, -23.554],
[-46.643, -23.557]
]);
var searchWithin = turf.polygon([[
[-46.653,-23.543],
[-46.634,-23.5346],
[-46.613,-23.543],
[-46.614,-23.559],
[-46.631,-23.567],
[-46.653,-23.560],
[-46.653,-23.543]
]]);
// Find point within polygon
var ptsWithin = turf.pointsWithinPolygon(points, searchWithin);
// Draw polygon
map.addLayer({
'id': 'searchWithin',
'type': 'fill',
'source': {
'type': 'geojson',
'data': searchWithin
},
'layout': {},
'paint': {
'fill-color': '#525252',
'fill-opacity': 0.5
}
});
// Draw all points
map.addLayer({
'id': 'points',
'type': 'circle',
'source': {
'type': 'geojson',
'data': points
},
'layout': {},
'paint': {
'circle-radius': 5,
'circle-color': 'red',
'circle-opacity': 1
}
});
// Draw points within polygon feature
map.addLayer({
'id': 'ptsWithin',
'type': 'circle',
'source': {
'type': 'geojson',
'data': ptsWithin
},
'layout': {},
'paint': {
'circle-radius': 5,
'circle-color': 'blue',
'circle-opacity': 1
}
});
});
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; };
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.5.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.5.0/mapbox-gl.css' rel='stylesheet' />
<script src='https://npmcdn.com/#turf/turf/turf.min.js'></script>
<div id='map'></div>

Categories

Resources