6

Given my location (in Bochum city, Germany), I am trying to identify the nearest highway way id by using the following Overpass API query:

[out:json][timeout:180];
area[name="Deutschland"];
way(around:50.0, 51.4661, 7.2491)[highway~"[a-z]"][name~"[a-z]"](area);
out ids;

And it returns me a list of way ids:

overpass turbo screenshot

The returned ids are sorted in numeric ascending order...

Is there please a way to find the way id nearest to my location?

If that is not possible, then please advise me how to select a random single id.

The background of my question is that my application reports car positions, but I only would like to report a limited number of cars per way id - to get better "area coverage". (That is why I suppose that selecting a random way id would possibly be an okay workaround...)

Note, that I have added [name~"[a-z]"], because I would only like to see highways with names (as a novice I am not sure, if there is a better way to express that).

2 Answers 2

6

Overpass API doesn't support a "nearest neighbor"-like query at the moment. While I don't really understand your workaround, you can certainly limit the output to a max of 1 entry:

[out:json][timeout:180];
way(around:50.0, 51.4661, 7.2491)[highway~"[a-z]"][name~"[a-z]"];
out ids 1;

(I removed all the "area" stuff in your example, as it's not strictly needed for (around:50.0, ...) to work.)

2

Even though I have received a great reply by @mmd, I would like to submit my own answer to help the future users trying to find the nearest way to a location.

For that I have prepared a jsFiddle demo using the turf.js library, where a marker can be dragged around and the closest way is drawn in red color:

animated map

Below is a copy of my javascript code:

'use strict';

function distancePointNodes(lat, lng, list) {
    var pt = turf.point([lng, lat]);
    var line = turf.lineString(list);
    var snapped = turf.nearestPointOnLine(line, pt);
    
    return snapped.properties.dist;
}

function findNearestWayId(ways, nodes) {
  var markerPosition = draggableMarker.getLatLng();
  var shortestDistance = Number.MAX_VALUE;
  var nearestWayId;
  for (var wayId in ways) {
    var way = ways[wayId];
    // iterate all nodes of a way object and save their positions
    // as a list of Leaflet latLng objects in the way object
    way.latLngs = way.nodes.map(nodeId => nodes[nodeId].latLng);
    // also save the minimal distance from the way object to car position
    way.minDistance = distancePointNodes(
      markerPosition.lat, 
      markerPosition.lng, 
      way.latLngs.map(pos => [pos.lng, pos.lat])
    );

    // find the overall shortest distance among all ways
    if (way.minDistance < shortestDistance) {
      shortestDistance = way.minDistance;
      nearestWayId = wayId;
    }
  }

  return nearestWayId;
}

// draw ways as polylines with popups
function drawPolylines(ways, nearestWayId) {
  var markerPosition = draggableMarker.getLatLng();
  for (var wayId in ways) {
    var way = ways[wayId];
    var line = L.polyline(way.latLngs, { color: (wayId === nearestWayId ? 'red' : 'blue') });

    var markerLatStr = parseFloat(markerPosition.lat).toFixed(6);
    var markerLngStr = parseFloat(markerPosition.lng).toFixed(6);
    var minDistanceStr = parseFloat(1000 * way.minDistance).toFixed(2);

    var popupMsg = '<p>Way: <a href="https://www.openstreetmap.org/way/' + wayId + '" target="_blank">' + wayId + '</a></p>' + 
        '<p>Distance to the draggable marker at<br>' + markerLatStr + ',' + markerLngStr + ':</p><p>' + minDistanceStr + ' meter</p>';
    line.bindPopup(popupMsg);

    line.addTo(lineGroup);
  }
}

function processOverpassReply(data) {
    var ways = { };
    var nodes = { };

    console.log('Received Overpass API reply with ' + data.elements.length + ' elements');
    if (data.elements.length < 3) {
        // for drawing the received data must contain at least 1 way and 2 nodes
        return;
    }

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

        if (type === 'way') {
            var wayId = element.id;
            ways[wayId] = element;
        } 
        else if (type === 'node') {
            var nodeId = element.id;
            nodes[nodeId] = element;
            // create a Leaflet LatLng object for this node, for drawing polylines later
            element.latLng = L.latLng(element.lat, element.lon);
        }
    }

    var nearestWayId = findNearestWayId(ways, nodes);
    console.log('The nearest way id ' + nearestWayId + ' with the shortest distance of ' + ways[nearestWayId].minDistance + ' km will be drawn in red color');
    //console.log(ways);
    //console.log(nodes);
    drawPolylines(ways, nearestWayId);
}

function sendOverpassRequest() {
  // remove all lines
  lineGroup.clearLayers();
  
  var markerPosition = draggableMarker.getLatLng();
  // return ways and nodes with non-empty "highway" tag around the draggable marker position
  var query = 'way(around:50,' + markerPosition.lat + ',' + markerPosition.lng + ')[highway~"."][name~"."];(._;>;);out;';
  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);
    }
  };
  request.send();
}

var startPosition = [51.4661, 7.2491];
var zoomOptions = {
  minZoom: 9,
  maxZoom: 18
};

var map = L.map('map', zoomOptions).setView(startPosition, 16);
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);

var draggableMarker = L.marker(startPosition, {
  draggable: true
})
.addTo(map)
.on('dragend', sendOverpassRequest);

var lineGroup = L.layerGroup();
map.addLayer(lineGroup);

sendOverpassRequest();
html,
body {
  padding: 0;
  margin: 0;
  height: 100%;
}

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

#map {
  flex-grow: 1;
}
<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>
<script src="https://cdn.jsdelivr.net/npm/leaflet-overpass-layer@2/dist/OverPassLayer.bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.js"></script>

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

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.