I really liked Ramiro Gómez’ rendering of the density of pub locations in the UK and Ireland at http://ramiro.org/notebook/mapping-pubs/, and just thought I would replicate his code from Matplotlib to R – also to point that very few lines of code are needed in R to produce a decent print quality map. Moreover the good folks at RStudio are also making considerable progress developing bindings between R and Leaflet Javascript library, so here is an example of how to turn Ramiro’s map into a fully interactive version.

First, we need to load a couple of workhorse R libraries.

library(data.table)
library(raster)
## Loading required package: sp
library(tmap)
library(leaflet)

I always use data.table for data wrangling operation (it’s incredibly fast with very concise syntax). data.table will load osm-x-tractor 800MB CSV file in under a second! raster is not strictly needed here (but useful to get access to GADM admnistrative boundary maps). raster will also load all necessary libraries to create and manipuate spatial features.

tmap is Martijn Tennekes’ excellent convenience methods to create quick and beautiful print maps. I highly recommend his introduction tmap in a Nutshell. Yes, you can use R base plot() and spplot() methods to print nice choropleth maps as well, but tmap goes a step further in simplicity.

We start by loading a complete CSV file in our R workspace. Note that osm-x-tractor requires that you download the file by hand (there’s no permanent URL). No need to use csvgrep though. I saved the archive in ./temp/POIEurope_2015.06.20.7z and then had to uncompress it.

# Uncompress 7z archive
system("7za e ./temp/POIEurope_2015.06.20.7z")  

# Read it in, note fread() would be much faster, but these CSV files have skipped 
# field delimiters, so we need to use fill=T
# poi <- fread("./temp/POIEurope.csv")
poi <- read.csv("./temp/POIEurope.csv", fill=T)
poi <- data.table(poi)
# Explore
kable(data.frame(colName=names(poi), colClass=sapply(poi, class)), row.names=F)
colName colClass
osmid numeric
name factor
amenity factor
emergency factor
geological factor
historic factor
leisure factor
man_made factor
office factor
shop factor
sport factor
tourism factor
craft factor
Longitude numeric
Latitude numeric
# Sumarize the number of locations across amenity classes (show only top 20 rows)
kable(poi[, .N, keyby=amenity][order(-N)][1:20], row.names=F)
amenity N
2824010
bench 442592
restaurant 234048
parking 205450
post_box 156604
recycling 129263
waste_basket 104173
cafe 103737
place_of_worship 101117
fuel 95373
bank 86800
pharmacy 84147
bicycle_parking 83492
pub 75770
fast_food 74551
drinking_water 71761
telephone 70070
school 66235
hunting_stand 65506
atm 55929
# Keep only pubs, and only the columns we need
poi <- poi[amenity=="pub", list(name, amenity, Longitude, Latitude)]

# Also remove pubs with no coordinate
poi <- poi[!is.na(Longitude) & !is.na(Latitude)]

There are 75770 pubs to be mapped across Europe. Next we load province boundaries for the UK and Ireland, and convert the list of pubs to a list of spatial points.

# Use raster::getData() to conveniently return admin boundaries
gbr <- getData("GADM", country="GBR", level=1)
irl <- getData("GADM", country="IRL", level=1)

# We can merge these 2 SpatialPolygonsDataframe objects into 1 for convenience
# Good practice is to ensure they have similar projection before merging
# and then generate unique polygon IDs with spChFIDs()
gbr <- spTransform(gbr, CRS("+init=epsg:4326"))
irl <- spTransform(irl, proj4string(gbr))

gbr <- spChFIDs(gbr, paste0("gbr", row.names(gbr)))
irl <- spChFIDs(irl, paste0("irl", row.names(irl)))

# Merge features
adm1 <- rbind(gbr, irl)

# Let's map it
tm_shape(adm1) +
  tm_grid(n.x=5, n.y=5, col="grey90", labels.col="grey50", on.top=F) +
  tm_polygons(col="NAME_1", border.col="white", palette="Paired",
    title="Provinces", labels=sort(unique(adm1$NAME_1)), max.categories=30) +
    tm_layout(draw.frame=F,
      legend.position=c(-0.1,.05), legend.bg.color="white")

We’re ready to convert the list of pubs to a SpatialPointsDataframe, and then limit its extent to Ireland and UK.

poi.sp <- SpatialPointsDataFrame(poi[, list(Longitude, Latitude)], 
  data.frame(poi), proj4string=CRS("+init=epsg:4326"))

# Simply crop the points to the map
poi.sp <- crop(poi.sp, extent(adm1))

Now using tmap package we can generate a bubble map for print.

# Let's overlay the pub locations
tm_shape(adm1, bbox=bbox(adm1)-c(-1,1)) +
  tm_polygons(col="#f8f5de", border.col=NA) +
  tm_shape(poi.sp) + tm_bubbles(col="#e5662b", alpha=.8, size=0.004) +
  tm_credits("Author: Melanie Bacou. Data at OpenStreetMap - openstreetmap.org", size=.8) +
  tm_layout(title="United Kingdom \nand Ireland... \nPub-wise", 
    bg.color="#73B6E6", draw.frame=F)

Else we can also draw an interactive map using leaflet.

  # Create the map
  m <- leaflet(data=adm1) %>%
  setView(0, 52, 7) %>%
  # Add basemap
  addTiles(
    urlTemplate="http://{s}.tiles.mapbox.com/v3/jcheng.map-5ebohr46/{z}/{x}/{y}.png",
    attribution="Mapbox", group="Basemap") %>%
  # Add pub locations clustered
  addCircleMarkers(data=poi.sp, layerId=~name, group="Pub location",
    radius=10, fillColor="steelblue", stroke=F,
    clusterOptions=markerClusterOptions(showCoverageOnHover=T)) %>%
  # Add layer control
  addLayersControl(
    overlayGroups="Pub location",
    options=layersControlOptions(collapsed=F))  

# Show
m