>


Visualizing Direction in Running Routes

By Max Candocia

|

May 17, 2020

Since the Lakefront Trail in Chicago was closed by the mayor, I have been running a wide variety of different routes. Oftentimes I take the same streets in different runs, but going different directions. I wanted a good way to visualize the directions of the path while I ran.

Normally, arrows or tapering lines can be used to visualize direction with paths, but those do not work very well with a lot of overlap or particularly long or complex paths, so I came up with a simple method below:

  1. Calculate the angle at each point in time by looking at the change in position 5 seconds into the future
  2. Use a cyclical color scale that is easy to read and has almost the same color at 365 degerees as 0 degrees

I used the `ggmap` package in R to download Stamen maps (which are derived from OpenStreetMap maps), along with the `cetcolor` package, which contains a cyclical color map that I used. I used a sample of 26 runs as examples, as well as an almost-circle generated sample that anyone can recreate.

Examples

Use the below button to switch between a red-only map and the multicolored map.

Generated Sample

26-run Sample

Code

Below you can see the code I used to generate the graphs. The constants at the top can be changed according to your local directories. The cyclical color legends are created using `gridExtra` and a separate plot, as I do not know how to actually generate such a legend in R otherwise.

Note the 3 required columns for the CSV are position_lat, position_long, and eid, where eid is an integer representing event ID (so that multiple routes in the same file are not connected).

Also note that the last 5 points of each event are not plotted due to the 5-second lag calculation for the angles.

library(tidyverse)
library(ggthemes)
library(cetcolor)
library(ggmap)
library(gridExtra)

# constants
# from cetcolor::cet_pal(8, 'c2s')
colors=c("#2E22EA","#9E3DFB","#F86BE2","#FCCE7B",
  "#C4E416","#4BBA0F","#447D87","#2C24E9"
)

COORDINATE_FILENAME = '/your/directory/here/filename.csv'

USE_GENERATED_SAMPLE=FALSE

PREFIX = ifelse(
  USE_GENERATED_SAMPLE, 
  'sample',
  'data'
)

# get data, either by generating or loading
if (USE_GENERATED_SAMPLE){
  # create an almost-circle
  # note: eid = event ID, which normally separates different sets of paths
  tval = 0:1000
  df = data.frame(
    position_lat = 41.8781 + 0.1 * cos(tval * pi/450), 
    position_long = -87.8298 + 0.13 * sin(tval * pi/500), 
    eid=1
  )
} else {
  df = read_csv(
    COORDINATE_FILENAME
  ) 
}

bbox = c(
  bottom = min(df$position_lat) - 0.005, 
  top = max(df$position_lat) + 0.005,
  left = min(df$position_long) - 0.005, 
  right = max(df$position_long) + 0.005
)

# functions
direction_labeller <- function(x){
  ifelse(
    x %% 45 == 0, 
    c('E','NE','N','NW','W','SW','S','SE')[1+(as.integer(x/45) %% 8)], 
    ''
  )
}

angle <- function(x,y){
  (atan(y/(x)) + pi*(x<0)) %% (2*pi)
}

# attributes calculation
# longitude is scaled back by factor of cosine latitude
# so that the scaled unit is the same distance as a unit
# of latitude
df = df %>% 
  group_by(
    eid
  ) %>%
  mutate(
    dlon=(lead(position_long,5)-position_long)*cos(median(position_lat)*pi/180),
    dlat=lead(position_lat,5)-position_lat,
    angles = angle(dlat, dlon) * 180/pi,
    vd = direction_labeller(round(angles/45)*45) %>% 
      factor(levels=direction_labeller(seq(0,315, 45)))
  ) %>% 
  ungroup()

# create compasses
hues_df = data.frame(degree = 0:359) %>%
  mutate(
    label=direction_labeller((degree+90) %% 360),
    colors = colorRampPalette(cet_pal(8,'c2'))(360)
  )

color_compass = ggplot(hues_df) + 
  geom_rect(
    aes(ymin=3,ymax=4, xmin=degree-0.5,xmax=degree+0.5,color=colors,fill=colors)
  ) + coord_polar(direction=-1, start=0) +
  scale_color_identity() + 
  scale_fill_identity() +
  guides(fill=FALSE,color=FALSE) + 
  theme_void() + 
  ylim(c(1,4.5)) + 
  geom_text(
    aes(x=degree,y=4.5,label=label) 
  )


# load map
gmap=get_map(location=bbox,source='stamen', type='toner',force=TRUE,color='bw')

mymap = ggmap(gmap) +
  geom_path(data=df, aes(x=position_long, y=position_lat, 
   group=eid,color=vd), alpha=0.7,size=1) +
  scale_color_manual(values=cetcolor::cet_pal(8, 'c2')) + 
  guides(color=FALSE)


mymap_angle = ggmap(gmap) +
    geom_path(data=df, aes(x=position_long, y=position_lat, 
   group=eid,color=(-angles) %% 360), alpha=0.7,size=1) +
    scale_color_gradientn(
      colors=cetcolor::cet_pal(8, 'c2'),
      breaks=seq(0,315,45),
      limits=c(0,359)
    )  + guides(color=FALSE)


png(sprintf('%s_compass_v1.png', PREFIX), height=840,width=940)
grid.arrange(mymap, color_compass, widths=c(4,1)) 
dev.off()

png(sprintf('%s_compass_v2.png', PREFIX), height=840,width=940)
grid.arrange(mymap_angle, color_compass, widths=c(4,1))
dev.off()

Citations

    D. Kahle and H. Wickham. ggmap: Spatial Visualization with ggplot2. The R Journal, 5(1), 144-161. URL http://journal.r-project.org/archive/2013-1/kahle-wickham.pdf


Tags: 

Recommended Articles

How Likely Are You to be Banned From Reddit?

How Likely Are You to be Banned From Reddit? I got a bot for that.

Tips for Effectively Using Color in Visualizations

Color is an important aspect of any visualization. Often, readability is sacrificed for simplicity or aesthetics, even though it is not necessary. Here I demonstrate some examples of how to use and improve visualizations via choice of colors and color palettes.