European map of cycle paths using OSM and ggplot2

Which countries have better cycle infrastructure?

Roman Kyrychenko https://www.linkedin.com/in/kirichenko17roman/
10-29-2018

Table of Contents


There is a new trend to have a good cycle infrastructure. But not all countries can boast about it. For example, all know about the Netherlands, which population is less than the number of bicycles. I decided to analyze the cycle infrastructure of European countries. For this, I took spatial data from Open Street Map and visualized it.

Downloading data from OpenStreetMap

OpenStreetMap, also called as OSM, has many spatial data, which gather thousands of users from around the world. We can find here lots of interesting information.

In addition to this, this data is open to downloading and we can use OSM API for receiving spatial features.

I’ve used osmdata package for this. But I had to take into account the limits of api, so I modified the function to download data.

I loaded the data one by one in latitude, so as not to exceed the download limits of the data. My function os divide data into parts, which then, after transforming into a data table (I used for this fortify function from ggplot2 package), were merged into one large table. Fortify-function makes the data suitable for visualization in ggplot2.

It took me half a day.


require(osmdata)
require(ggplot2)
require(dplyr)

os <- function() {
  b <- combn(-10:50,2) %>% t() 
  b <- b[!duplicated(b[,1]),]
  cw <- opq(bbox = c(b[1,1], 35, b[1,2], 70), 
            timeout = 125, memsize = 100000000) %>%
    add_osm_feature(key = 'highway', value = 'cycleway')
  cwmap <- osmdata_sp(cw)
  
  xdf <- fortify(cwmap$osm_lines)
  cat(paste0(round(i/nrow(b),2)*100,"% \n"))
  for(i in 2:nrow(b)) {
    cw <- opq(bbox = c(b[i,1], 35, b[i,2], 70),
              timeout = 125, memsize = 100000000) %>%
      add_osm_feature(key = 'highway', value = 'cycleway')
    cwmap <- osmdata_sp(cw)
    xdf <- rbind(xdf, fortify(cwmap$osm_lines))
    cat(paste0(round(i/nrow(b),2)*100,"% \n"))
  }
  xdf
} 

cyclemap <- os()

I got the following dataset:


head(cyclemap) %>% gt::gt()
.id long lat order piece id group
24783150 -9.450528 53.44152 1 1 24783150 24783150.1
24783150 -9.450343 53.44161 2 1 24783150 24783150.1
24783150 -9.450218 53.44182 3 1 24783150 24783150.1
24783150 -9.450209 53.44187 4 1 24783150 24783150.1
24783150 -9.450190 53.44196 5 1 24783150 24783150.1
24783150 -9.450343 53.44213 6 1 24783150 24783150.1

It had 7421025 observations and 7 variables.


dplyr::glimpse(cyclemap, width = 60)

Observations: 7,421,025
Variables: 7
$ .id   <chr> "24783150", "24783150", "24783150", "247831…
$ long  <dbl> -9.450528, -9.450343, -9.450218, -9.450209,…
$ lat   <dbl> 53.44152, 53.44161, 53.44182, 53.44187, 53.…
$ order <int> 1, 2, 3, 4, 5, 6, 1, 2, 1, 2, 3, 4, 5, 6, 7…
$ piece <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ id    <dbl> 24783150, 24783150, 24783150, 24783150, 247…
$ group <fct> 24783150.1, 24783150.1, 24783150.1, 2478315…

Plot map in ggplot2

Now that I’ve got the data, I can make visualization.

There is no alternative to this - ggplot2.

I wanted to achieve the effect of the glow from space, so used dark background tones (I made a black fill for the countries and dark gray fill for seas and oceans).

Also, I used gilbert’s projection for the edge because this projection does not stretch the poles so much as the Mercator-projection.


ggplot() + 
  borders("world", xlim = c(-10, 50), 
          ylim = c(20, 70), size= 0.05, 
          fill = "black") + #add coutries borders
  geom_path(data = cyclemap, 
            aes(long, lat, group = group), 
            size = 0.01, color = "white") +
  coord_map("gilbert", 
            xlim = c(-10, 50), ylim = c(33, 71)) +
  geom_text(aes(x=-20, y = 70, 
                label = "European map of cycle paths", 
                hjust = 0, vjust=1), 
            family = "PT Sans", color = "white", size = 4)+
  geom_text(aes(x=-20, y = 68.6, 
                label = "  Based on OSM data", 
                hjust = 0, vjust = 1), 
            family = "PT Sans", color = "white", size = 3)+
  theme_void() +
  theme(
    plot.background = element_rect(fill = "#252525", 
                                   colour = "#252525")
  )