I have been lucky to fly to a few countries around the world. Like any other bored traveller, I thumb through the airline magazines and look at the air travel route maps. These maps are beautifully stylised depictions of the world with gently curved lines between the many destinations serviced by the airline. I always wanted such a map for my own travel adventures. In this article I explain how to create a map of your own travels in the style of the Emirates Airlines route map.

Create Air Travel Route Maps using ggplot2

The first step was to create a list of all the places I have flown between at least once. Paging through my travel photos and diaries, I managed to create a pretty complete list. The structure of this document is simply a list of all routes (From, To) and every flight only gets counted once. The next step finds the spatial coordinates for each airport by searching Google Maps using the geocode function from the ggmap package. In some instances, I had to add the country name to avoid confusion between places. To prevent errors from the Google maps API, I have added a while loop that runs until all destiationshave en geocoded.

We now we have a data frame of airports with their coordinates and can create air travel route maps. The data frames are merged so that we can create air travel route maps using the curve geom. The borders function of ggplot2 creates the map data. The ggrepel package helps to prevent overplotting of text. This code also removes any return flights and splilts flights that crossed the date line.

You can view the recent version of the code and associated files in GitHub. In another post I have used the same principle to create a route map of flights between islands in the Pacific Ocean using the schedules from several internatioal airlines.

Create Air Travel Route Maps

## Flightpath map
## https://r.prevos.net/create-air-travel-route-maps/

## Init
library(tidyverse)
library(ggmap)
library(ggrepel)

## Read flight and airports lists 
flights <- read_csv("Geography/flights.csv")
airports_file <- "Geography/airports.csv"
if (file.exists(airports_file)) {
  airports <- read_csv(airports_file)
  } else {
  airports <- data_frame(airport = NA, lon = NA, lat= NA)
}

## Lookup coordinates
## Some airports need counry names to ensure Google finds the correct location
## The geocoding keeps looping till all coordinates have been found
destinations <- unique(c(flights$From, flights$To))
new_destinations <- destinations[!destinations %in% airports$airport]
while (length(new_destinations) > 0) {
    new_airports <- geocode(new_destinations) %>%
      mutate(airport = new_destinations) %>%
      select(airport, lon, lat)
    airports <- rbind(airports, new_airports) %>%
      filter(!is.na(lon) | !is.na(lat))
    new_destinations <- destinations[!destinations %in% airports$airport]
}
write_csv(airports, "Geography/airports.csv")

## Remove return flights
d <- vector()
for (i in 1:nrow(flights)) {
    d <- which(paste(flights$From, flights$To) %in% paste(flights$To[i], flights$From[i]))
    flights$From[d] <- "R"
}
flights <- flights %>%
  filter(From != "R") %>%
  select(From, To)

## Add coordinates to flight list
flights <- merge(flights, airports, by.x="From", by.y="airport")
flights <- merge(flights, airports, by.x="To", by.y="airport")
flights <- flights %>% 
  select(From, To, lon.x, lat.x, lon.y, lat.y) %>% 
  as_data_frame()

## Split Circumnaviation Flights at -180/180 degrees
circ <- which(abs(flights$lon.y - flights$lon.x) > 180)
flights[circ,]
flights$lon.y[circ] <- ifelse(flights$lon.y[circ] < 0, 180, -180)
flights$lat.y[circ] <- rowSums(flights[circ, c("lat.x", "lat.y")]) / 2
leg2 <- airports %>%
  filter(airport %in% flights$To[circ]) %>%
  mutate(From = rep("", length(circ))) %>%
  mutate(lon.x = -flights$lon.y[circ], lat.x = flights$lat.y[circ]) %>%
  select(From, To = airport, lon.x, lat.x, lon.y = lon, lat.y = lat)
flights <- rbind(flights, leg2)

## Remove country names
airports$airport <- as.character(airports$airport)
comma <- regexpr(",", airports$airport)
airports$airport[which(comma > 0)] <- substr(airports$airport[which(comma > 0)], 1, comma[comma > 0] - 1)

## Plot flight routes
worldmap <- borders("world", colour="#efede1", fill="#efede1") 
ggplot() + worldmap + 
    geom_point(data=airports, aes(x = lon, y = lat), col = "#970027") + 
    geom_text_repel(data=airports, aes(x = lon, y = lat, label = airport), col = "black", size = 2, segment.color = NA) + 
    geom_curve(data = flights, aes(x = lon.x, y = lat.x, xend = lon.y, yend = lat.y), col = "#b29e7d", size = .4) + 
    theme_void()
ggsave("Geography/flights_map.png", dpi = 300)