Blog - BS Data, hackery, stories

Pen plotter maps in R

I made a sweet Christmas present for my friend Clément, who cycles far too much. Last year he got himself a nice custom bicycle made and started documenting his travels.

So I grabbed the lot and started playing, and I pen-plotted his journeys on postcards.

This blog was contributed to R Bloggers


Base map

First order of business is getting and representing borders of the countries travelled. Arcane base R beware.

###################
# Get the world map
worldMap <- rworldmap::getMap(resolution = "high")

# Europe
europe <- c('France', 'United Kingdom')
# Select only the index of states member of the E.U.
country_filtered <- which(worldMap$NAME%in%europe)

# Extract longitude and latitude border's coordinates of countries
# Rebuild a dataframe with just the stuff we want
europeCoords <- lapply(country_filtered, function(i){
  df <- data.frame(worldMap@polygons[[i]]@Polygons[[1]]@coords)
  df$region = as.character(worldMap$NAME[i])
  colnames(df) <- list("long", "lat", "region")
  return(df)
})

europeCoords <- do.call("rbind", europeCoords)

Parsing GPX files and tracks

Since Clément has done some good work compiling this dataset, we only need to loop over all GPX files we can find in the directories. We've kept data@time to have a look at when the journeys happened.

Oh, and we need to assign a group variable to each unique path in order to be able to separate them when visualising. Otherwise, we'll end up with one single uninterrupted line.

########################
# build riders' paths in df
paths <- data.frame()
for (file in list.files("data/", pattern = "gpx", recursive = TRUE)) {
  print(file)
  track_points <- rgdal::readOGR(paste("data/", file, sep = ""),
                           layer = "track_points")
  track_points@data$time_clean <- ymd_hms(track_points@data$time)

  df1 <- data.frame(track_points@data)
  df2 <- data.frame(track_points@coords)
  df <- bind_cols(df1, df2)

  df.filter <- df %>%
    select(time, coords.x1, coords.x2) %>%
    mutate(group = file)
  colnames(df.filter) <- c('time', 'lon', 'lat', 'group')

  paths <- rbind(paths, df.filter)
}

> paths %>% head()
                    time       lon      lat                                  group
1 2017/04/08 01:00:45+00 -1.983406 50.71916 17-04-08-hard-boiled-300/gpx/trace.gpx
2 2017/04/08 01:00:57+00 -1.983241 50.71897 17-04-08-hard-boiled-300/gpx/trace.gpx
3 2017/04/08 01:01:03+00 -1.982949 50.71899 17-04-08-hard-boiled-300/gpx/trace.gpx
4 2017/04/08 01:01:15+00 -1.982324 50.71934 17-04-08-hard-boiled-300/gpx/trace.gpx
5 2017/04/08 01:01:27+00 -1.981452 50.71970 17-04-08-hard-boiled-300/gpx/trace.gpx
6 2017/04/08 01:01:32+00 -1.981107 50.71955 17-04-08-hard-boiled-300/gpx/trace.gpx

Visualising and plotting

Easy as pie: our country boundaries are polygons, while the GPS tracks are paths, in ggplot world.

Once happy with a general layout, exporting an SVG is trivial.

ggplot() +
  geom_polygon(data = europeCoords,
    aes(x = long, y = lat, group = region),
      colour = "#DDDDDD",
      fill = "#FFFFFF") +
  geom_path(data = paths,
    aes(lon, lat, group = group),
      colour="steelblue",
      alpha = 0.5) +
  theme_opts  + 
  coord_map(projection = "mercator")

Pen plotting

In the output SVG file, the country boundaries and the tracks are in separate <g> groups, and very easy to isolate. This makes plotting the result one group after the other, with two different pens, very easy. There's a video of the plotting below.