Road highlighting on a map with Overpass


Example of a road selected on map For a recent project at one of my courses, we had a requirement to highlight a road segment on a map at any given point. In a search for a ready-to-use database or API with road coordinates, I’ve found Overpass. It’s a part of an OpenStreetMap project that provides OSM map data over API, and has a feature to get roads around given point, which is exactly what was needed. Try the demo right away or proceed reading the article on implementation details.

Overpass uses quite sophisticated query language, which comes in 2 formats: XML and Overpass QL. You probably couldn’t avoid reading the docs thoroughly to build a complex query, but luckily I’ve found a relevant example on stackoverflow. I converted that example from XML to QL format here and after a few modifications finally got a query I need.

Now to actually run it from our node/express application, we used a query-overpass library. There’re similar solutions for other languages, for example PHP. Query-overpass exports just one function and is quite simple by itself, but, like many of your node libraries, it brings a few dependencies for converting OSM data to GeoJSON. I’m not sure if it can be used in a browser. In our case, we have this functionality on a backend, which is probably a good place for it anyway, because in a real application you would want to query a local database or cache first before making a remote request.

The core function that performs an Overpass query for us looks like this:

const overpass = require('query-overpass');
const getNearestRoad = (lat, lon) => {
  const maxDist = 10; // maximum distance from the point in meters
  const query = `[out:json];
      way
      (around:${maxDist},${lat},${lon})
      ["highway"];
    (
      ._;
      >;
    );
    out;`;

  return new Promise((resolve, reject) => {
    overpass(query, (error, roads) => {
      if (error) {
        return reject(error);
      } else if (roads.features.length < 1) {
        return reject({statusCode: 404, message: "No roads found"});
      } else {
        // we are interested in a single nearest road, 
        // but an array of roads also can be returned
        return resolve(roads.features[0]);
      }
    });
  });
};

As a result, you will have an object in GeoJSON format with a structure like this:

{
  "type": "Feature",
  "id": "way\/423501050",
  "properties": {
    "type": "way",
    "id": 423501050,
    "tags": {
      "cycleway": "no",
      "highway": "secondary",
      "maxspeed": "25 mph",
      "name": "Middlefield Road",
      "tiger:cfcc": "A41",
      "tiger:county": "Santa Clara, CA",
      "tiger:name_base": "Middlefield",
      "tiger:name_type": "Rd",
      "tiger:zip_left": "94303",
      "tiger:zip_right": "94303"
    },
    "relations": [
      
    ],
    "meta": {
      
    }
  },
  "geometry": {
    "type": "LineString",
    "coordinates": [
      [
        -122.1593238,
        37.4523135
      ],
      ...
    ]
  }
}

Notice it contains not only polygon coordinates, but a whole bunch of other useful information, like street name and max speed.

For user interface, I used Leaflet library with a Polyline representing a road. You can see the demo below or try it fullscreen here. Click on any road and see what happens.

While this solution feels quick and easy, it’s far from ideal and definitely requires additional improvements. These are a few problems that I’ve faced and still didn’t solve:

A repository with both frontend and backend code for this example is on my github.

comments powered by Disqus