I have location values for every driving trip fetched from our servers. I need to generate a single route that is matched to roads, with given locations. I’m using Leaflet Javascript Map Library, OpenStreetMap Tile Map Service and OSRM API Match Service.
I send 100 (parameter limit) locations(latitudes and longitudes) per request, then I append a list with every geometry returned as a response from OSRM Match API. Then I decode every single geometry, result is a list of lats and longs. I combine the result then draw it on a map.
P.S. Time interval between consecutive locations is 1 second.
Some of the locations are well matched to roads as exactly I want.
But most of the trip is shown on the map with a lot of branching, unrelated lines;
There are even unmatched, recurring shapes;
Here is the explanation of Match Service which is found in official OSRM documentation;
Is there a way to generate a single, properly drawn line that’s matched to roads, using these services?
Here a jsFiddle demo, which draws map matched response by OSRM as a blue line:
My Javascript code is below and I have even tested it with about 200 locations recorded by a driving car, after increasing the osrm-routed
limit by adding the start parameter --max-matching-size 1000
:
'use strict';
function processOsrmReply(data) {
if (data.code !== 'Ok') {
clearMap('Error code: ' + data.code);
return;
}
data.matchings.forEach(function(matching) {
matchesGroup.addData(matching.geometry);
});
myMap.flyToBounds(matchesGroup.getBounds());
}
function sendOsrmRequest(lngLats) {
// create an array of radiuses, same length as lngLats array
var radiuses = lngLats.map(lngLat => 49);
var url = 'https://router.project-osrm.org/match/v1/driving/' +
lngLats.join(';') +
'?overview=simplified' +
'&radiuses=' +
radiuses.join(';') +
'&generate_hints=false' +
'&skip_waypoints=true' +
'&gaps=ignore' +
'&annotations=nodes' +
'&geometries=geojson';
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.onload = function() {
if (this.status >= 200 && this.status < 400) {
var data = JSON.parse(this.response);
processOsrmReply(data);
} else {
clearMap('Error status: ' + this.status);
}
};
request.send();
}
function processMapClick(ev) {
// get the count of currently displayed markers
var markersCount = markersGroup.getLayers().length;
if (markersCount < MARKERS_MAX) {
L.marker(ev.latlng).addTo(markersGroup);
matchesGroup.clearLayers();
return;
}
// get the count of currently displayed matches
var linesCount = matchesGroup.getLayers().length;
if (linesCount >= 1) {
clearMap();
return;
}
// create an array of string: "lng,lat" with 6 digits after comma
var lngLats = markersGroup.getLayers().map(marker =>
parseFloat(marker.getLatLng().lng).toFixed(6) + ',' +
parseFloat(marker.getLatLng().lat).toFixed(6)
);
sendOsrmRequest(lngLats);
}
function clearMap(str = '') {
var myStatus = document.getElementById('myStatus');
myStatus.textContent = str;
matchesGroup.clearLayers();
markersGroup.clearLayers();
}
var MARKERS_MAX = 4;
var startPosition = [40.0005, 32.8722];
var myMap = L.map('myMap').setView(startPosition, 14).on('click', processMapClick);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(myMap);
var markersGroup = L.layerGroup();
myMap.addLayer(markersGroup);
var matchesGroup = L.geoJSON();
myMap.addLayer(matchesGroup);
html, body {
margin: 0;
padding: 0;
}
#myMap {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
}
#myStatus {
text-align: center;
position: absolute;
z-index: 2;
width: 100%;
}
<link href="https://cdn.jsdelivr.net/npm/leaflet@1/dist/leaflet.min.css" type="text/css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/leaflet@1/dist/leaflet-src.min.js"></script>
<div id="myStatus">Click 5x at the map to send OSRM map matching request</div>
<div id="myMap"></div>