Run the City Challenge with R

[This article was first published on R, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

My lovely wife wanted to start a new running challenge: Run all the streets in a city in one year. Interesting of course, but how do you track the streets that you already visited? You could of course use the heatmapping functions on various websites like Strava, Citystrides or Veloviewer for this.

I tried to create something myself using R. First you will of course need to have the files of the runs. I use GPX files for that. So from each run a GPX is exported and placed in a folder[1].


files <- dir(pattern = "\\.gpx")

# Consolidate routes in one data frame
index <- c()
lat <- c()
lon <- c()
for (i in 1:length(files)) {
   route <- readGPX(files[i])
   location <- route$tracks[[1]][[1]]
   index <- c(index, rep(i, dim(location)[1]))
   lat <- c(lat, location$lat)
   lon <- c(lon, location$lon)
routes <- data.frame(cbind(index, lat, lon))

Now you have a large data frame with three variables: index, lat(itude) and lon(gitude). Index is related to the GPX files, latitude and longitude are the logged positions in the GPX files. Other information like time, altitude and heart rate is omitted. head(routes) gives this:

index      lat      lon
  1      51.40871 5.354247
  1      51.40740 5.335107
  1      51.40630 5.331588
  1      51.40619 5.323691
  1      51.41471 5.317168
  1      51.41687 5.316653

Let’s plot the GPX files on a map with ggplot. First we need to retrieve the map from Stamen. With a boundig box we determine the map that we need to download. get_stamenmap downloads the required map parts with the map type (I chose “toner-lite” but many others are available, see for example here for a complete list) and zoom-factor. I also set force download to False as it is not needed to download the maps every time. R ensures that it is updated when needed (e.g. when the cache is emptied after a restart).

If you want a fixed box you should set it manually, for example with bbox <- c("left" = 5.310091, "bottom" = 51.373372, "right" = 5.435567, "top" = 51.442094) for Veldhoven in The Neterlands.


bbox <- make_bbox(lon,lat)
b1<- get_stamenmap(bbox, maptype="toner-lite", zoom = 14, force = F)         

The GPX files are processed alphabetically so if you want to display city limits or that one special route in a different colour you can use the index for this. I added an underscore before the filename of the city limits file, this will add it to the top of a sorted list resulting in index 1. I later use this to plot the city limits in a different colour and line type than the other files.

I created two data frames, one for the city limits and one for the rest. This allows using different colours. I also added a theme te remove the legend, axis labels and such. An overview of named colours can be found here. Plot both the city border and the routes with ggmap.

city_border <-, index == 1)  # City Limits
routes <-, index != 1)       # The rest
vc <- ggmap(b1) +  
   # plot activites
   geom_path(data=routes, aes(x=lon, y=lat, group=index), colour="deepskyblue3",
             size=1, alpha = 1) +
   # plot city limits
   geom_path(data=city_border, aes(x=lon, y=lat), colour="navyblue",
             size=2.5, linetype = "dotdash", alpha = .75)

The resulting image with city borders and the covered streets:

Next step is to add a title (you could do that in ggplot as well) but I wanted some overlap with the map and a dfferent font, and I want to add a logo (Homer Simpson as all time favourite). I do both with the magick package. For the title I used Extrafont to be able to use different fonts. Offset and scaling depends on the dimensions of the logo and the inital map.


city <- image_read("filename.png")
logo <- image_read("homer.png") %>% image_scale("x150")
cc_logo <- image_composite(city, logo, offset = "-20-930")

# Adding the title with shadows
font_col <- "grey20"
font_type <- "Multicolore Pro" 

# title shadow, shifted a bit to the left
cc <- image_annotate(cc_logo, "City Challenge 2021", gravity = "north", size = 45,
                     color = "deepskyblue2", font = font_type, location = "-5+10")
# title in black, printed over it
cc <- image_annotate(vc, "City Challenge 2021", gravity = "north", size = 45,
                     color = font_col, font = font_type, location = "+0+10")

This is the final image with map, city limits, title, logo and the covered streets:

[1] Inital part based for about 100% on the work of Ben Unsworth: PlotGPX

To leave a comment for the author, please follow the link and comment on their blog: R. offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)