OSRM (Open Source Routing Machine) is a C++ implementation of a high performance route search engine to obtain the shortest paths in a road network. Available under a simplified BSD license, OSRM is an open source service. See http://project-osrm.org/docs/v5.24.0/api/.

OSRM has a demo server to test the software, but it is limited. It is therefore not recommended to use it to launch a large number of requests. Fortunately, it is possible to install an instance of OSRM locally with a docker container.

More explanations here and there in french (thanks to Timothée).

1. Set up an instance of OSRM

1.1 Install Docker
sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
sudo apt update
sudo apt install docker-ce
1.2 Increase swap memory (if needed)
See how to do here : https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-20-04-fr
1.3 Downloading OSM data
mkdir france
cd france
wget http://download.geofabrik.de/europe/germany/berlin-latest.osm.pbf
1.4 Preparing data for OSRM
docker run -t -v "${PWD}:/data" osrm/osrm-backend osrm-extract -p /opt/car.lua /data/france-latest.osm.pbf
docker run -t -v "${PWD}:/data" osrm/osrm-backend osrm-partition /data/france-latest.osrm
docker run -t -v "${PWD}:/data" osrm/osrm-backend osrm-customize /data/france-latest.osrm
1.5 Launch OSRM
docker run --rm -t -i -p 5000:5000 -v "${PWD}:/data" osrm/osrm-backend:latest osrm-routed --algorithm mld --max-table-size 50000000 /data/france-latest.osrm

The OSRM instance will now be accessible at the server address 0.0.0.0:5000. Yolo!

2. Creation of a matrix using R language

2.1 Isère (n -> n)

In this first example, we work on the municipalities of Isère (France). The data come from the IGN Admin Express API and are available here : ftp://Admin_Express_ext:Dahnoh0eigheeFok@ftp3.ign.fr/ADMIN-EXPRESS-COG-CARTO_3-0__SHP__FRA_WM_2021-05-19.7z The R script below allows us to handle it

# Data import
library(sf)
com = st_read("COMMUNE.shp") # use the good path

# Data handling
isere = com[com$INSEE_DEP == "38",]
cheflieu =  st_read("CHFLIEU_COMMUNE.shp") # use the good path
lonlat = st_coordinates(cheflieu)
cheflieu =  cheflieu %>% st_drop_geometry()
cheflieu = cbind(cheflieu, lonlat)
isere = merge(isere, cheflieu, by.x = "ID", by.y = "ID_COM")
isere = isere[,c("INSEE_COM","")]
isere <- isere[,c("INSEE_COM","NOM.x","POPULATION","X","Y","geometry")]
colnames(isere) = c("id","name","population","lon","lat","geometry")

# Simplification
library(rmapshaper)
isere = ms_simplify(isere, keep = 0.05)

# Export
st_write(isere,"data/isere.geojson")

Of course you can use de osrm package developped by Timothée Giraud. But here, I prefer to call directly the api without wrapper. Like this!

library(sf)
isere = st_read("data/isere.geojson")
isere = isere[,c("id","name","lon", "lat")] %>% st_drop_geometry()

# Coordinates and ids
coords = paste(paste(isere$lon,isere$lat,sep=","), collapse = ";")
ids = isere$id

# Get a matrix
router = "http://0.0.0.0:5000"
query = paste0(router,"/table/v1/driving/",coords,"?annotations=duration")
response = RCurl::getURL(query)

# Data Handling
output = rjson::fromJSON(response)
output = data.frame(output$duration)
colnames(output) = ids
output = cbind(id = ids, output)

# Export data
write.csv(output,"data/TimeDistCarIsere.csv", row.names = FALSE )

The output matrix is available here.

Find there a visualisation of this data through Observable.

2.2 Distance to Paris by road (n -> 1)

Here we work on the LAU2 units provided by Eurostat. Then, we select only the French communes

# Data Import
library(sf)
lau2 = st_read("https://gisco-services.ec.europa.eu/distribution/v2/lau/geojson/LAU_RG_01M_2020_4326.geojson")

# Data handling
dots = st_centroid(lau2[lau2$CNTR_CODE == "FR",])
dots = cbind(dots,st_coordinates(dots))
dots = dots[,c("id","POP_2020","AREA_KM2","X","Y")] %>% st_drop_geometry()
colnames(dots) = c("id","pop","area","lon","lat")
dots = dots[dots$lat > 40.93,]

# Coordinates and ids
coords = paste(paste(dots$lon,dots$lat,sep=","), collapse = ";")
ids = dots$id

Then, we select the code corresponding to Paris. The objective is to calculate with OSRM all the times distance by car to Paris.

# destination
target = "FR_75056"
destination <- match(target, dots$id) - 1

In order not to overload the computer’s memory, we create a function that will perform the search piece by piece and store the result in temporary files.

getdist = function(start){

# source
end = start + 999
if(end > dim(dots)[1]){end = dim(dots)[1]-1}
source = paste(rep(start:end), collapse = ";")

# Get a matrix
router = "http://0.0.0.0:5000"
query = paste0(router,"/table/v1/driving/",coords,"?destinations=",destination,"&sources=",source,"&annotations=duration")
start_time <- Sys.time()
response = RCurl::getURL(query)
end_time <- Sys.time()
end_time - start_time

# Data Handling
output = rjson::fromJSON(response)
output = data.frame(output$duration)


output = cbind(id = ids[(start+1):(end+1)], output)
colnames(output) = c("id","duration")

# Export data
file = paste0("tmp/TimeDistToParis_",start,"_",end,".csv")
write.csv(output,file, row.names = FALSE )
}

The we call the function…

for(i in seq(from=0, to=34000, by=1000)){
getdist(i)
}

…and we put the pieces together.

# Merge
df <- data.frame(id=character(),duration =  integer()) 
files = list.files("tmp/")
for(i in 1:length(files)){
  file = read.csv(paste0("tmp/",files[i]))
  df = rbind(df,file)
  }
output = merge(dots, df, by.x = "id", by.y = "id")
write.csv(output, "data/TimeDistToParis.csv", row.names = FALSE)cd 

# Intermediate files are deleted
unlink("tmp", recursive = TRUE)

The result file is available here