Routing Singapore’s Street

Using OpenStreetMaps and R’s igraph package to create a routing engine for Singapore’s streets.

Imaduddin Haetami
05-03-2020

Table of Contents


Preamble

This article is about my experience in building a routing engine for streets in Singapore. Starting from downloading line data from the openstreetmap(OSM), constructing the graph structure from the lines, ended with sampling on the shortest path output of the graph. In the process, I use R programming language and packages developed by its community.

I will explain in detail how each package contributes for this project. But for now, let’s load them first.


library(osmdata)
library(sf)
library(dplyr)
library(lwgeom)
library(igraph)
library(mapview)

The end product will be the shortest path computation. I am pretty familiar with R’s interface to igraph package. I have used that before for routing Singapore’s bus and train network. If you deal with a graph problems, I really feel that igraph is a complete package. It has Python and C interface too.

I have used igraph in the past. But in the past, the network lines were given by my supervisor. The current problem is where do you get the graph?

Data Abstraction From OSM

Fortunately, I came across osmdata package, which offer a convenient way to download OSM geometries to R. So, we download the data using osmdata package:


if(file.exists("sg highway 20200119b.rds")==F){
  sg=opq(bbox=c(103.614311874285,1.23047931395298,104.031394289329,1.46968609740717)) %>%
    add_osm_feature(key='highway')%>%
    osmdata_sf()
  saveRDS(sg,"sg highway 20200119b.rds") 
  #saving the data and not making multiple attempts on the same query.
}else{
  #load the data if it already exist
  sg=readRDS("sg highway 20200119b.rds") 
}

Let’s see what we get from the download:


summary(sg)

                  Length Class  Mode     
bbox                1    -none- character
overpass_call       1    -none- character
meta                3    -none- list     
osm_points        345    sf     list     
osm_lines         271    sf     list     
osm_polygons      271    sf     list     
osm_multilines     30    sf     list     
osm_multipolygons  29    sf     list     

Great, it’s an sf object from sf packaga. Looking at 190262 linestrings, it seems that this data will be the robust one. So, I will explore this dataset first, and I choose dplyr package for this task.

Lines Exploration and Cleaning


lineSG=sg$osm_lines

One of the step for exploration:


lineSG %>% pull(highway) %>% unique()

 [1] "primary"        "residential"    "tertiary"      
 [4] "footway"        "service"        "secondary"     
 [7] "motorway"       "motorway_link"  "trunk"         
[10] "trunk_link"     "primary_link"   "secondary_link"
[13] "living_street"  "construction"   "pedestrian"    
[16] "unclassified"   "tertiary_link"  "proposed"      
[19] "steps"          "track"          "cycleway"      
[22] "path"           "bridleway"      "raceway"       
[25] "corridor"       NA               "footpath"      

Apparrently, there is footpath too. Great data, but not what I need at the moment.

After several other steps of exploration, I decided that the following features of the dataset are the lines and attributes that we need:


lineSG=lineSG %>% 
  filter(highway%in%c("motorway","motorway_link",
                      "primary","primary_link",
                      "secondary","secondary_link",
                      "tertiary","tertiary_link",
                      "residential",
                      #"service",
                      "unclassified",
                      "trunk","trunk_link")) %>% 
  select(osm_id,name,highway,lanes,maxspeed,oneway,geometry)

Let’s inpect graphically whether the chosen dataset is promising:


mapview(lineSG)