How to get road surface information for my route

I am using ORS public API to get an optimal route between my home-base location to many destinations. I am getting both the optimal itinerary (which destination to visit first, then second, etc) and the actual geometry of the route.
The use case of my application entails knowing the road surface (i.e. distance on paved road, distance on dirt road, etc). However, the returned route geometry doesn’t have this info in its attribute table.
How can I get that info?
I looked into the OSM road data that this service uses and it contains a “road surface” tag. In QGIS, tried to overlay my returned route geometry to this OSM data so that I can select from this data the road segments that correspond to my route. This kind of works but it is approximate since this line-on-line overlay operation is not straightforward when the two layers don’t perfectly align.
My question is, is this something that can be done using the public API?

My next attempt is to try to run the service on my local machine using the OSM road data that I have downloaded. I installed the ORS backend using Docker but I realized I don’t have the “Optimization” endpoint which is what I need to get this optimal route. I am told to get that I need to use Vroom and the information to achieve that is sparse (especially for me who is using Windows).
So, my second question is, if I eventually manage to get this Vroom running on my local machine, will I get back a route geometry that contains the tags from my original input road data?

Hey,

when issuing a request to the /directions-endpoint, you can add the extra-info-parameter - we have a bunch of information on extra-info in our backend documentation.

This does not work with an /optimization-request, but if you have your optimized response, you can use that to query the /directions-endpoint.

Overlaying OSM data in QGIS should work, since the returned geometries should be directly derived from OSM geometries - if you’re using data from the same timestamp, this should be the same.

Best regards

This is interesting.

I was not made aware of this this extra_info parameter before.

I am using the openrouteservice package in R and when I run this code:

route ← ors_directions(coordinates=coords, profile = “driving-car”, extra_info = “surface”, output = “sf”)

I am getting an extra column called extras.

It is still one row (so, only one geometry for the whole road) and therefore I am not sure how that road surface info is encoded.
By subsetting that column:

surface ← route$extras$surface$values

I get a table apparently containing the surfaced IDs and the distance for each ID. If that’s correct, then this is already good data that I can use. Obviously, I would prefer having each segment geometry separately but, if that’s not possible, then I will settle with this data already.

Thanks.

By the way, this is the list of destinations’ coordinates I am using here:

dput(coords)
list(c(29.77555556, -2.372777778), c(29.748518, -1.507839), c(29.6710600022872,
-1.53936000898918), c(29.7257100023348, -1.44213000919053), c(29.6486700023398,
-1.44098000923422), c(29.6013600023359, -1.4535300092295), c(30.066304,
-1.944651))

1 Like

Hi,
the table you are referring to maps start/end point ids along the route to surface types. You can match these id pairs against the route’s geometry to extract the geometries of individual segments corresponding to different surface types. Please note that these point ids are 0-based so need to be shifted when used as an index to R data structures.

library(openrouteservice)

coords <- list(c(29.77555556, -2.372777778), c(29.748518, -1.507839), c(29.6710600022872, -1.53936000898918), c(29.7257100023348, -1.44213000919053), c(29.6486700023398, -1.44098000923422), c(29.6013600023359, -1.4535300092295), c(30.066304, -1.944651))
route <- ors_directions( coordinates = coords, profile = "driving-car", extra_info = "surface", output = "sf")

surface <- route$extras$surface$values
geometry <- route$geometry[[1]]

f <- function(start, end, value) list(geometry[start:end,], value)

segments <- mapply(f, surface[,1] + 1, surface[,2] + 1, surface[,3], SIMPLIFY = FALSE)

I can’t wrap my head around the structure of the result in this segments object.
It is a list of lists where each of the child lists is also a list of two elements. The first element is a matrix of longitude/latitude pairs and second element is the road surface ID.
I was expecting starts and ends to be pairs of lon/lat coordinates instead.

This is correct, segments is a list of complete segment geometries along with the corresponding surface type ids. If you are interested in the start and end coordinates of the segments, you can easily modify the processing function f to return just these.

library(openrouteservice)

coords <- list(c(29.77555556, -2.372777778), c(29.748518, -1.507839), c(29.6710600022872, -1.53936000898918), c(29.7257100023348, -1.44213000919053), c(29.6486700023398, -1.44098000923422), c(29.6013600023359, -1.4535300092295), c(30.066304, -1.944651))
route <- ors_directions( coordinates = coords, profile = "driving-car", extra_info = "surface", output = "sf")

surface <- route$extras$surface$values
geometry <- route$geometry[[1]]

f <- function(start, end, value) {
  data.frame(start.lon = geometry[start,1], start.lat = geometry[start,2],
             end.lon = geometry[end,1], end.lat = geometry[end,2], value)
}

segments <- mapply(f, surface[,1] + 1, surface[,2] + 1, surface[,3], SIMPLIFY = FALSE)

do.call(rbind, segments)
#>    start.lon start.lat  end.lon   end.lat value
#> 1   29.77642 -2.372954 29.72763 -1.577149     1
#> 2   29.72763 -1.577149 29.72763 -1.577149     2
#> 3   29.72763 -1.577149 29.67197 -1.538699     1
#> 4   29.67197 -1.538699 29.67197 -1.538699     2
#> 5   29.67197 -1.538699 29.66224 -1.524383     1
#> 6   29.66224 -1.524383 29.63640 -1.498886     3
#> 7   29.63640 -1.498886 29.71002 -1.437556     1
#> 8   29.71002 -1.437556 29.71002 -1.437556     2
#> 9   29.71002 -1.437556 29.69543 -1.456110     1
#> 10  29.69543 -1.456110 29.65170 -1.451171     2
#> 11  29.65170 -1.451171 29.64644 -1.437220     3
#> 12  29.64644 -1.437220 29.60524 -1.451644     2
#> 13  29.60524 -1.451644 29.66224 -1.524383     3
#> 14  29.66224 -1.524383 30.04424 -1.942049     1
#> 15  30.04424 -1.942049 30.04433 -1.942011     3
#> 16  30.04433 -1.942011 30.04702 -1.940819     1
#> 17  30.04702 -1.940819 30.04923 -1.940531    11
#> 18  30.04923 -1.940531 30.05665 -1.939388     3
#> 19  30.05665 -1.939388 30.06189 -1.943530     1
#> 20  30.06189 -1.943530 30.06208 -1.944460     3
#> 21  30.06208 -1.944460 30.06244 -1.944630     0
#> 22  30.06244 -1.944630 30.06413 -1.944873     3
#> 23  30.06413 -1.944873 30.06555 -1.943896     2
#> 24  30.06555 -1.943896 30.06608 -1.944047     0

Created on 2024-07-12 with reprex v2.1.0

This is what made it click in my mind what you did here. Turns out, your previous solution was exactly what I wanted. I just had to convert that matrix into an actual linestring; that’s what I call a “geometry”. Working with sf for a long time, I don’t think of a string of coordinates pairs as a geometry…yet.

Thanks a lot for patience and helping me out; I now have everything I need.

Since my area of interest is located in a remote area, road surface tags from OSM data are not accurate enough. An intern has helped me cleaning the data for this area by eyeballing every road segment overlayed onto a google earth satellite basemap and editing the road surface tags manually.
Now to be able to use this cleaned data, I have to figure out how to get vroom running on my local machine. Right now, I have the ORS engine running fine and accepting API calls.
Any resources I can use?

I am working on Windows.

I’m glad I could help! :tada:

If your task involves editing road surface tags, you might consider contributing your improvements back directly to openstreetmap.org, unless you are doing so already. This is the preferred approach over local edits, as both you and the community would benefit from it: you would always have access to up-to-date data, while others would see your contributions.

It usually takes around 1-2 weeks for the updates to propagate from there to the ORS public API. The above approach might also save you from setting up a local instance, if only you are fine with the turnover time for updates and the API quotas.

@jschnell Please correct me if I’m wrong, but I’m afraid it’s indeed not particularly well documented how to set up a local vroom instance communicating with ORS. Any hints from you side?

Hey,

no, there’s no specific documentation, but the vroom-docker project has a bit of info.

In short, get both docker containers on the same network and adjust the config:

cliArgs:
  …
  router: 'ors'

routingServers:
 ors:
  driving-car:
    host: "yourIP"
    port: "yourPort"
  driving-hgv:
    host: "yourIP"
    port: "yourPort"
  …

You might have to play around with the host and the baseURL parameters a bit, if it doesn’t work out of the box.

Best regards

1 Like