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:

- Calculate the angle at each point in time by looking at the change in position 5 seconds into the future
- 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.

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

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()
```

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: