I am trying to use osrmTable
to calculate driving distances, but I am encountering incorrect results under certain circumstances. It took me a lot of trial and error to (I think) identify when the error happens, but I think I have figured it out (though not the solution) - see below. First, say we have the following data and run osrmTable
(using the demo server):
library(osrm)
library(sf)
a <- data.frame(
lon=c(-98,-92),
lat=c(38,38))
b <- data.frame(
lon=c(-68,-109),
lat=c(38,34))
a <- st_as_sf(a, coords = c("lon", "lat"), crs = "WGS84")
b <- st_as_sf(b, coords = c("lon", "lat"), crs = "WGS84")
osrmTable(src = a, dst = b, measure=c("duration", "distance"))
This produces:
$durations
1 2
1 0 0
2 0 0
$distances
1 2
1 0 0
2 0 0
$sources
lon lat
1 -9.497727 38.78069
2 -9.497727 38.78069
$destinations
lon lat
1 -9.497727 38.78069
2 -9.497727 38.78069
This is nonsensical - the durations and distances are all 0, and the sources and destinations are nowhere remotely close to the actual ones.
But if I then make only one very small change - instead of the lat
being both 38, I change the second to be 38.01 - I get correct (and dramatically different) results.
c <- data.frame(
lon=c(-98,-92),
lat=c(38,38.01))
d <- data.frame(
lon=c(-68,-109),
lat=c(38,34))
c <- st_as_sf(c, coords = c("lon", "lat"), crs = "WGS84")
d <- st_as_sf(d, coords = c("lon", "lat"), crs = "WGS84")
osrmTable(src = c, dst = d, measure=c("duration", "distance"))
Results:
$durations
1 2
1 2078.6 971.8
2 1732.5 1319.3
$distances
1 2
1 2769379 1236686
2 2250736 1829854
$sources
lon lat
1 -98.00000 37.99923
2 -92.00317 38.01000
$destinations
lon lat
1 -69.97288 41.24970
2 -108.99029 33.99603
These results make sense. Again, the only thing that changed was that I made the latitudes slightly different in the a
data frame.
What is causing this odd behavior, and is there a way to achieve correct results besides changing the coordinates to be slightly different?
Your latitude and longitude are converted to an incorrect polyline representation. This seems to be caused by an older version of the googlePolylines
package, which is imported by osrm
. This can be fixed by downloading the development version of osrm
and updating all dependencies.
This osrmTable()
function builds a url that it sends to a server running the osrm backend, a high-performance routing engine written in C++, and parses the json response for you.
My initial question was: is your problem caused by sending the server the wrong request, or by the server itself?
To find out, let's try your osrmTable()
call using one pair of coordinates, the first row of a
and the first row of b
:
res_table <- osrmTable(src = a[1, ], dst = b[1, ], measure = c("duration", "distance"))
The magic happens on line 107 of the osrmTable()
source:
url <- paste0(url, encode_coords(x = loc, osrm.server = osrm.server), "?")
In your case, the url generated is https://routing.openstreetmap.de/routed-car/table/v1/driving/polyline(_%7B%7CfF~rcuQ?_kbvD)?sources=0&destinations=1
. We can decode that polyline in R:
googlePolylines::decode("_%7B%7CfF~rcuQ?_kbvD")
# lat lon
# 1 0.00096 0.00012
# 2 0.00094 0.00015
# 3 0.00106 0.00017
# 4 -0.00010 -97.99982
# 5 -0.00010 -67.99982
These are not close to the coordinates that you entered. The problem is on our end - we are sending the server the incorrect coordinates.
Install the development version of osrm
:
remotes::install_github("riatelab/osrm")
If prompted, update other packaging dependencies, most importantly googlePolylines
. Your versions should be at least:
packageVersion("osrm") # 4.0.1
packageVersion("googlePolylines") # 0.8.2
We can then simulate what happens in the osrm::enable_coords()
function. Note that in the updated version, it no longer generates a polyline if the osrm server is the default (https://routing.openstreetmap.de/
), so I have set it to something else for testing:
# Create your previous coordinates
loc <- data.frame(id = 1, lon = c(-98, -68), lat = c(38, 38))
osrm:::encode_coords(x = loc, osrm.server = "some_random_server")
# "polyline(_{|fF~rcuQ?_kbvD)"
The polyline is different. If we convert it back we get:
googlePolylines::decode("_{|fF~rcuQ?_kbvD")
# lat lon
# 1 38 -98
# 2 38 -68
This means your original request should now work as intended:
osrmTable(src = a, dst = b, measure=c("duration", "distance"))
# $durations
# 1 2
# 1 2078.6 971.8
# 2 1735.8 1322.7
# $distances
# 1 2
# 1 2769379 1236686
# 2 2252086 1831203
# $sources
# lon lat
# 1 -98.0000 37.99923
# 2 -91.9997 38.00023
# $destinations
# lon lat
# 1 -69.97288 41.24970
# 2 -108.99029 33.99603
We can compare this output to your second case, where you manually changed the input by 0.01. It is, as expected, almost identical.