4

For my question I have prepared a jsFiddle demo requiring you to click at the map 5 times:

animated Open Street Map

If I understand this correctly, the OSRM does not care about ways.

It only cares about nodes and their probabilities as shown in A Practical Guide to an Open-Source Map-Matching Approach for Big GPS Data:

fig 1

fig 2

However for my app the map-matched ways are needed, to achieve better road coverage:

'use strict';

function processOsrmReply(data) {

    console.log('Received OSRM map matching reply:');
    console.log(data);

    if (data.code !== 'Ok') {
        clearMap('OSRM error code: ' + data.code);
        return;
    }

    for (var m = 0; m < data.matchings.length; m++) {
        var matching = data.matchings[m];
        var coordinates = matching.geometry.coordinates;
        // alternate line colors to see the different matchings
        var lineColor = (m % 2 === 0 ? 'red' : 'darkred');
        var latLngs = coordinates.map(coordinate => [coordinate[1], coordinate[0]]);
        L.polyline(latLngs, { color: lineColor }).addTo(osrmNodesGroup);

        for (var l = 0; l < matching.legs.length; l++) {
            var leg = matching.legs[l];
            var nodes = leg.annotation.nodes;
            console.log('matching ' + m + ', leg ' + l + ' contains the nodes:');
            console.log(nodes);
            sendOverpassRequest(nodes);
        }
    }
}

function sendOsrmRequest() {
    // get the currently displayed markers
    var markers = markersGroup.getLayers();

    // create an array of string: "lng,lat" with 6 digits after comma
    var lngLats = markers.map(marker => 
        parseFloat(marker.getLatLng().lng).toFixed(6) + ',' + 
        parseFloat(marker.getLatLng().lat).toFixed(6)
    );

    // create an array of radiuses, same length as lngLats array
    var radiuses = lngLats.map(lngLat => 50);

    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';

    console.log('Sending OSRM map matching query to the URL ' + url);

    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('OSRM error status: ' + this.status);
        }
    };
    request.send();
}

function processOverpassReply(data) {
    console.log('Received Overpass API reply with ' + data.elements.length + ' elements');
    console.log(data.elements);

    for (var w = 0; w < data.elements.length; w++) {
        var element = data.elements[w];

        if (element.type === 'way') {
            var wayId = element.id;
            // alternate line colors to see the different ways
            var lineColor = (w % 2 === 0 ? 'blue' : 'navy');
            var latLngs = element.geometry.map(geom => [geom.lat, geom.lon]);
            L.polyline(latLngs, { color: lineColor }).addTo(overpassWaysGroup);
        } 
    }
}

function sendOverpassRequest(nodes) {
    var query = 'node(id:' + nodes.join(',') + '); way[highway](bn); out geom;'
    console.log('Sending Overpass API query: ' + query);

    var url =  'https://overpass-api.de/api/interpreter?data=[out:json];' + query;
    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);
            processOverpassReply(data);
        } else {
            clearMap('Overpass error status: ' + this.status);
        }
    };
    request.send();
}

function processMapClick(ev) {
    // get the currently displayed markers
    var markers = markersGroup.getLayers();

    if (markers.length < MARKERS_MAX) {
        L.marker(ev.latlng).addTo(markersGroup);
        return;
    }

    // get the count of currently displayed polylines
    var linesCount = osrmNodesGroup.getLayers().length;
    if (linesCount > 1) {
        clearMap();
        return;
    }

    sendOsrmRequest();
}

function clearMap(str = '') {
    var hint = document.getElementById('hint');
    hint.textContent = str;

    // remove polylines and markers
    osrmNodesGroup.clearLayers();
    overpassWaysGroup.clearLayers();
    markersGroup.clearLayers();
}

var MARKERS_MAX = 4;
var map = L.map('map').setView([51.4661, 7.2491], 14).on('click', processMapClick);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

// layer groups for markers and polylines
var markersGroup = L.layerGroup();
map.addLayer(markersGroup);
var osrmNodesGroup = L.layerGroup();
map.addLayer(osrmNodesGroup);
var overpassWaysGroup = L.layerGroup();
map.addLayer(overpassWaysGroup);

var overlays = {
    'Show <b>markers</b> placed by user': markersGroup,
    'Show <b>nodes</b> returned by OSRM': osrmNodesGroup,
    'Show <b>ways</b> returned by Overpass': overpassWaysGroup
};

L.control.layers(null, overlays, { collapsed: false }).addTo(map);
html,
body {
  padding: 0;
  margin: 0;
  height: 100%;
}

body {
  display: flex;
  flex-direction: column;
}

#map {
  flex-grow: 1;
}

#hint {
  text-align: center;
}
<link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1/dist/leaflet.css">
<script src="https://cdn.jsdelivr.net/npm/leaflet@1/dist/leaflet-src.js"></script>

<p id="hint">
Click the map 5x to draw the map matched ways!
</p>

<div id="map"></div>

If you set the "Show ways returned by Overpass" checkbox, you will see that the demo is able to retrieve the ways.

However it retrieves too many ways, probably also the ways, which only have one node from the OSRM-returned list of nodes.

Please help me, how to filter out only the map matched ways?

The reason, why I need ways is that my app will collect vehicle data and I want to cover all ways as good as possible, by setting a limit on reports (for example max 10 per day) coming from a way id. In other words: I do not want 10 reports from a busy highway and 0 reports from a nearby forest way. I need 10 reports from the both.

1 Answer 1

2
+50

tl;dr: You can't, feasibly, and you shouldn't, really.

But:

If all you want is OSM way ids of ways that have been traversed at least partially, cross reference your list of input nodes (those sent to Overpass) against each node id in elements.nodes - if there are more than one the given way has been traversed. There will be edge cases where this fails, though.


OSM ways are descriptive relations - i.e. they describe a strip of road infrastructure by common naming or purpose (OSM tags) - and are defined by a sequence of nodes.

OSM ways do not contain any topological structure¹ - except when they themselves define aerial relations as part of a referential hierarchy; this reference pattern is the core concept of the OSM database, with nodes being the logical (and spatial) core entity defining all descriptive relations.

OSRM constructs a network topology for its routing graph by de-referencing node sequences into edges - tag enhanced, extracted from the corresponding ways, but the referential definition of the ways themselves have no value for the graph.

And conversely, or consecutively, they have no value for routing results. You will have to (and want to, really) make use of OSRMs rich return object information and geometry conpositions².

If you still want ways, your test result is what you will get, always.


¹ Most crucially they have no notion of intersections - the same way, describing a road by name, can cross many other roads and consists of several branches, multiple lanes, roundabouts etc. - rendering them useless for routing applications.

² All the above being said, if you are comfortable with diving into the OSRM codebase you can exctract and deliver the way id during entity parsing in the respective function called in the LUA profiles - ultimately changing nothing about the effect you see in your tests.

2
  • Thanks for your reply (upvoted)! However the reason, why I need ways is, that my app will collect vehicle data and I want to cover all ways as good as possible, by setting a limit on reports (for example max 10 per day) coming from a way id. (Updated my question). My first impression is, that you are talking about routing (which is what I do not need), but I will re-read you reply few times to understand. Also, I think by checking, that a way has a min number (two?) of map matched nodes, I could filter them out. Commented Apr 23, 2023 at 7:09
  • @AlexanderFarber my explanation is unrelated to the actual service (matching, routing, isochrones, ...) - and to the emission probablities of Viterbi's backtracing. But see my update for a possible solution.
    – geozelot
    Commented Apr 23, 2023 at 9:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.