11 min read

ggplot2 Map of Amelia Earhart's Last Flight


Bismark Tribune, North Dakota, July 2, 1937. Source Library of Congress.

View raw source for this post

Summary

Amelia Earhart’s attempt to circumnavigate the world by airplane ended in tragedy. This post uses ggplot2 to map her last flight. There were 33 stops in the journey with the most dangerous being the three spanning the Pacific Ocean. From Lae, New Guinea, she and her navigator Fred Noonan departed and were to land at Howland Island. They never arrived. A map was generated with ggplot2 and shows the trip.

Table of Contents

Overview

Amelia Earhart’s attempt to circumnavigate the world by airplane ended in tragedy on July 2, 1937. There were 33 stops in the journey with the most dangerous being the three spanning the Pacific Ocean. From Lae, she and her navigator Fred Noonan were to land at Howland Island. Howland Island is approximately one kilometer wide by two kilometers long. They never arrived. A map generated by ggplot2 shows the trip.

In her book Last Flight, Amelia Earhart noted that the trip to Howland Island was the longest hop of 2,556 miles. The plane was stripped of as much weight as possible to maximize fuel efficiency. Since there are no landmarks on the ocean, signals by radio wave were crucial to finding the island. Two Coast Guard cutters were stationed along the route to aid in naviation: the Itasca and the Ontario. The Itasca was just off the beach on the “leeward” side of the island. Most investigators ascribe poor radio communication as the leading cause of the disappearance. In a passage that foreshadows the tragedy, Earhart writes to her husband from Lae:

“Fred Noonan has been unable, because of radio difficulties, to set his chronometers. Any lack of knowledge of their fastness and slowness would defeat the accuracy of celestial navigation. Howland is such a small spot in the Pacific that every aid to locating it must be available.” [1]

Step 1 - Build Earhart’s Flight Path

The data for the project was scraped from Wikipedia; the cities/airports were updated and geocoded; and the data was saved as a rds file.

library(rvest)
library(dplyr)
library(tidyr)
library(tidygeocoder)
# get stops
read_html("https://en.wikipedia.org/wiki/Amelia_Earhart") %>% 
html_elements("table") %>% html_table() %>% .[[2]] %>% 
  rename_with(~janitor::make_clean_names(.)) %>% 
  rename(departure_city = departure_city_140,
         notes = notes_141)-> stops
# pull cities
c(stops$departure_city, stops$arrival_city) %>% 
  unique() %>% 
  as_tibble() %>% 
  rename(old_address = value)  -> locations_old
# geocode
tribble(
  ~old_address,                             ~new_address,
  "Gao, French Sudan",                  "Gao, Mali",
  "Fort-Lamy, F.E. Africa",             "N'Djamena, Chad",
  "El Fasher, Anglo-Egyptian Sudan",    "El Fasher, Sudan",
  "Khartoum, Anglo-Egyptian Sudan",     "Khartoum, Sudan",
  "Massawa, Italian East Africa",       "Massawa, Eritrea",
  "Assab, Italian East Africa",         "Assab, Eritrea",
  "Karachi, British India",             "Karachi, Pakistan",
  "Calcutta, British India",            "Kolkata, India",
  "Singapore, Straits Settlements",     "Singapore",
  "Bandoeng, Dutch East Indies",        "Bandung, Indonesia",
  "Soerabaia, Dutch East Indies",       "Surabaya, Indonesia",
  "Koepang, Dutch East Indies",         "Kupang, Indonesia"
) -> locations_new

# merge
left_join(locations_old, locations_new, by = "old_address") %>% 
mutate(address = if_else(is.na(new_address), old_address, new_address)) %>% 
select(old_address, address) -> locations

# geocode
locations %>% 
  geocode(address, method = 'osm') -> locations
# merge back to stops
left_join(stops, locations, by = c("departure_city" = "old_address")) %>%
  left_join(locations, by = c("arrival_city" = "old_address")) %>% 
  select(!c(departure_city, arrival_city)) %>%
  rename(departure_city = address.x,
         arrival_city = address.y,
         begin_lat = lat.x,
         begin_lon = long.x,
         end_lat = lat.y,
         end_lon = long.y,
         nautical_miles = nauticalmiles) %>% 
  select(date, departure_city, begin_lat, begin_lon,
         arrival_city, end_lat, end_lon, nautical_miles,
         notes) %>% 
  mutate(date = as.Date(date, format = c("%b %d, %Y"))) %>% 
  mutate(nautical_miles = gsub("\\[144\\]", "", nautical_miles),
         nautical_miles = as.integer(nautical_miles)) %>% 
  rename(status = notes) %>% 
  arrange(date) -> earhart 
earhart[["status"]][1:30] <- "arrived"
earhart[["status"]][31:33] <- "missing"
saveRDS(earhart, "./earhart.rds")

Step 2 - Create sf objects

The next step is to create the sf objects for the map. The basemap is from a GitHub repository that holds historical maps. This particular map is from 1938, the year after Amelia Earhart’s trip and shows national boundaries as they existed then.

library(dplyr)
library(ggplot2)
library(rnaturalearth)
library(sf)
library(tidyr)
library(geojsonsf)
# basemap
create_1938_basemap <- function(){
  lon_0 <- -120
  geojson_sf("./world_1938.geojson") %>% 
  rmapshaper::ms_simplify(method = "vis", keep = .25) %>%
  rename_with(~tolower(.)) %>% 
  st_break_antimeridian(., lon_0 = lon_0) %>% 
  st_transform(., paste0(st_crs(.)$proj4string, " +lon_0=", lon_0)) %>% 
  dplyr::select(name)
}
world <- create_1938_basemap()
# cities
create_cities <- function(){
  eh <- readRDS("./earhart.rds")
  eh %>% 
    select(departure_city, begin_lon, begin_lat) %>% 
    rename(city = departure_city, lon = begin_lon, lat = begin_lat) -> departures
  eh %>%
    select(arrival_city, end_lon, end_lat) %>% 
    rename(city = arrival_city, lon = end_lon, lat = end_lat) -> arrivals
  
  bind_rows(departures, arrivals) %>% 
  distinct() %>% 
  separate(city, into = c("city", "country"), sep = ", ") %>%
  st_as_sf(coords = c("lon", "lat"), crs = 4326) %>% 
  mutate(city = gsub("Howland Island", "", city)) %>% print(n = Inf)
}
cities <- create_cities() 
# create flight path
create_flight_path <- function(){
  eh <- readRDS("./earhart.rds") 
  lon_0 <- -120
  #departures
  eh %>% 
  filter(status == "arrived") %>% 
  arrange(date) %>% 
  select(date, departure_city, begin_lat, begin_lon) %>% 
  mutate(flow = "departure") %>% 
  rename(city = departure_city, lat = begin_lat, lon = begin_lon) -> departures
  # arrivals
  eh %>% 
  filter(status == "arrived") %>% 
  arrange(date) %>% 
  select(date, arrival_city, end_lat, end_lon) %>%
  mutate(flow = "arrival") %>% 
  rename(city = arrival_city, lat = end_lat, lon = end_lon) -> arrivals
  bind_rows(departures, arrivals) %>%
  arrange(date) %>% 
  select(lat, lon) %>% 
  st_as_sf(coords = c("lon", "lat"), crs = 4326) %>%
  st_combine() %>% 
  sf::st_cast("LINESTRING") %>% 
  st_break_antimeridian(., lon_0 = lon_0) %>% 
  st_transform(., paste0(st_crs(.)$proj4string, " +lon_0=", lon_0)) 
}
flight_path <- create_flight_path()
# create missing path
create_missing_path <- function(){
  eh <- readRDS("./earhart.rds") 
  lon_0 <- -120
  eh %>% 
    filter(status == "missing" & date == "1937-07-02") %>% 
    arrange(date) %>% 
    select(date, departure_city, begin_lat, begin_lon) %>% 
    mutate(flow = "departure") %>% 
    rename(city = departure_city, lat = begin_lat, lon = begin_lon) -> departures
  eh %>% 
    filter(status == "missing" & date == "1937-07-02") %>% 
    arrange(date) %>% 
    select(date, arrival_city, end_lat, end_lon) %>%
    mutate(flow = "arrival") %>% 
    rename(city = arrival_city, lat = end_lat, lon = end_lon) -> arrivals
  bind_rows(departures, arrivals) %>%
    arrange(date) %>% 
    select(lat, lon) %>% 
    st_as_sf(coords = c("lon", "lat"), crs = 4326) %>%
    st_combine() %>% 
    sf::st_cast("LINESTRING") 
} 
missing_path <- create_missing_path()
# import howland island
create_howland <- function(){
  sf::st_read(dsn = "./N00W180", layer = "N00W180") %>% 
  rename_with(~tolower(.)) %>% 
  filter(source_id == "GC10609") %>% 
  select(source_id,geometry) %>% 
  st_transform(., 4326) %>% 
  st_cast("POLYGON") %>% 
  mutate(name = "Howland Island", .before = geometry) %>% 
  select(name, geometry)
}
hi <- create_howland()
# create itasca
create_itasca <- function(){
tibble(name = "Itasca", lat = .815, lon = -176.55) %>%
  sf::st_as_sf(coords = c("lon", "lat"), crs = 4326)
}
itasca <- create_itasca()
# bounding box
create_bbox <- function(){
tribble(
  ~lat,         ~lon,        ~id,
   2.5,        -178.5,       1,
   2.5,        -173.5,       1,
  -2.5,        -173.5,       1,
  -2.5,        -178.5,       1
) %>% 
  st_as_sf(coords = c("lon", "lat"), crs = 4326) %>% 
  group_by(id) %>% 
  summarise(geometry = st_combine(geometry)) %>%
  st_cast("POLYGON")
}
bbox <- create_bbox()

Step 3 - Plot the Main Map

ggplot() +
  geom_sf(data = world) +
  geom_sf(data = bbox, col = "black", alpha = 0, lwd = .35) +
  geom_sf(data = cities, col = "steelblue", size = .75) +
  geom_sf_text(data = cities, aes(label = city),
               col = "black",
               nudge_y = 3.5,
               nudge_x = 3,
               size = 1.75) +
  geom_sf(data = flight_path, col = "steelblue") +
  geom_sf(data = missing_path, col = "steelblue", linetype = "dotted") +
  coord_sf(xlim = c(-180, 180), 
           ylim = c(-50, 51),
           expand = F,
           label_graticule = "SE") +
  scale_x_continuous(name = "", breaks = seq(-180, 180, 60)) +
  scale_y_continuous(name = "", breaks = c(-40, -20, 0, 20, 40)) +
  theme_bw() -> main

Step 4 - Plot Howland Island

ggplot() +
  geom_sf(data = hi, col = "steelblue", fill = "steelblue") +
  geom_sf(data = itasca, col = "black", fill = "black", size = .5) +
  coord_sf(xlim = c(-176.7, -176.5), 
           ylim = c(.7, .9),
           expand = F) +
  geom_sf_text(data = hi, mapping = aes(label = name), 
                nudge_y = -.04,
                nudge_x = .02,
                size = 1.2) +
  geom_sf_text(data = itasca, mapping = aes(label = name), 
                nudge_y = .02,
                nudge_x = 0,
                size = 1.2) +
theme_void() +
theme(panel.grid.major = element_blank(), 
      panel.grid.minor = element_blank(),
      panel.background = element_rect(colour = "black", linewidth = 1)) -> howland

Step 5 - Combine the Maps

main +
  annotation_custom(
    grob = ggplotGrob(howland),
    xmin = -160,
    xmax = 100,
    ymin = -30,
    ymax = 0
  ) 

Final Map

Ultimately, the labels were difficult to place. There were a number of packages that were attempted like ggrepel but the easiest was to use ggplot::geom_sf_text and then export as a pdf. The labels were then moved in Inkscape. The final map is shown below.

Conclusion

This project proved to be a challenging effort, largely because of the number of iterations required for the map to look presentable.

References

[1]
A. Earhart, Last flight: The world’s foremost woman aviator recounts, in her own words, her last, fateful flight. Crown, 2009 [Online]. Available: https://books.google.com/books?id=iYCCg6I1vBwC
[2]
R Core Team, R: A language and environment for statistical computing. Vienna, Austria: R Foundation for Statistical Computing, 2024 [Online]. Available: https://www.R-project.org/
[3]
Y. Xie, C. Dervieux, and A. Presmanes Hill, Blogdown: Create blogs and websites with r markdown. 2024 [Online]. Available: https://github.com/rstudio/blogdown
[4]
H. Wickham, J. Hester, W. Chang, and J. Bryan, Devtools: Tools to make developing r packages easier. 2022 [Online]. Available: https://devtools.r-lib.org/
[5]
H. Wickham, R. François, L. Henry, K. Müller, and D. Vaughan, Dplyr: A grammar of data manipulation. 2023 [Online]. Available: https://dplyr.tidyverse.org
[6]
D. Cooley, Geojsonsf: GeoJSON to simple feature converter. 2022 [Online]. Available: https://github.com/SymbolixAU/geojsonsf
[7]
H. Wickham et al., ggplot2: Create elegant data visualisations using the grammar of graphics. 2024 [Online]. Available: https://ggplot2.tidyverse.org
[8]
Y. Xie, Knitr: A general-purpose package for dynamic report generation in r. 2024 [Online]. Available: https://yihui.org/knitr/
[9]
P. Massicotte and A. South, Rnaturalearth: World map data from natural earth. 2023 [Online]. Available: https://docs.ropensci.org/rnaturalearth/
[10]
H. Wickham, Rvest: Easily harvest (scrape) web pages. 2024 [Online]. Available: https://rvest.tidyverse.org/
[11]
E. Pebesma, Sf: Simple features for r. 2024 [Online]. Available: https://r-spatial.github.io/sf/
[12]
K. Müller and H. Wickham, Tibble: Simple data frames. 2023 [Online]. Available: https://tibble.tidyverse.org/
[13]
J. Cambon, D. Hernangómez, C. Belanger, and D. Possenriede, Tidygeocoder: Geocoding made easy. 2021 [Online]. Available: https://jessecambon.github.io/tidygeocoder/
[14]
H. Wickham, D. Vaughan, and M. Girlich, Tidyr: Tidy messy data. 2024 [Online]. Available: https://tidyr.tidyverse.org

Disclaimer

The views, analysis and conclusions presented within this paper represent the author’s alone and not of any other person, organization or government entity. While I have made every reasonable effort to ensure that the information in this article was correct, it will nonetheless contain errors, inaccuracies and inconsistencies. It is a working paper subject to revision without notice as additional information becomes available. Any liability is disclaimed as to any party for any loss, damage, or disruption caused by errors or omissions, whether such errors or omissions result from negligence, accident, or any other cause. The author(s) received no financial support for the research, authorship, and/or publication of this article.

Reproducibility

─ Session info ───────────────────────────────────────────────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.4.1 (2024-06-14)
 os       macOS Sonoma 14.4
 system   aarch64, darwin20
 ui       X11
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       America/New_York
 date     2024-11-06
 pandoc   3.2 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/aarch64/ (via rmarkdown)

─ Packages ───────────────────────────────────────────────────────────────────────────────────────────────────────────
 package     * version  date (UTC) lib source
 blogdown    * 1.19     2024-02-01 [1] CRAN (R 4.4.0)
 bookdown      0.41     2024-10-16 [1] CRAN (R 4.4.1)
 bslib         0.8.0    2024-07-29 [1] CRAN (R 4.4.0)
 cachem        1.1.0    2024-05-16 [1] CRAN (R 4.4.0)
 cli           3.6.3    2024-06-21 [1] CRAN (R 4.4.0)
 codetools     0.2-20   2024-03-31 [1] CRAN (R 4.4.1)
 colorspace    2.1-1    2024-07-26 [1] CRAN (R 4.4.0)
 devtools    * 2.4.5    2022-10-11 [1] CRAN (R 4.4.0)
 digest        0.6.37   2024-08-19 [1] CRAN (R 4.4.1)
 dplyr         1.1.4    2023-11-17 [1] CRAN (R 4.4.0)
 ellipsis      0.3.2    2021-04-29 [1] CRAN (R 4.4.0)
 evaluate      1.0.1    2024-10-10 [1] CRAN (R 4.4.1)
 fansi         1.0.6    2023-12-08 [1] CRAN (R 4.4.0)
 fastmap       1.2.0    2024-05-15 [1] CRAN (R 4.4.0)
 fs            1.6.4    2024-04-25 [1] CRAN (R 4.4.0)
 generics      0.1.3    2022-07-05 [1] CRAN (R 4.4.0)
 ggplot2       3.5.1    2024-04-23 [1] CRAN (R 4.4.0)
 ggthemes    * 5.1.0    2024-02-10 [1] CRAN (R 4.4.0)
 glue          1.8.0    2024-09-30 [1] CRAN (R 4.4.1)
 gtable        0.3.6    2024-10-25 [1] CRAN (R 4.4.1)
 htmltools     0.5.8.1  2024-04-04 [1] CRAN (R 4.4.0)
 htmlwidgets   1.6.4    2023-12-06 [1] CRAN (R 4.4.0)
 httpuv        1.6.15   2024-03-26 [1] CRAN (R 4.4.0)
 jquerylib     0.1.4    2021-04-26 [1] CRAN (R 4.4.0)
 jsonlite      1.8.9    2024-09-20 [1] CRAN (R 4.4.1)
 knitr         1.48     2024-07-07 [1] CRAN (R 4.4.0)
 later         1.3.2    2023-12-06 [1] CRAN (R 4.4.0)
 lifecycle     1.0.4    2023-11-07 [1] CRAN (R 4.4.0)
 magrittr      2.0.3    2022-03-30 [1] CRAN (R 4.4.0)
 memoise       2.0.1    2021-11-26 [1] CRAN (R 4.4.0)
 mime          0.12     2021-09-28 [1] CRAN (R 4.4.0)
 miniUI        0.1.1.1  2018-05-18 [1] CRAN (R 4.4.0)
 munsell       0.5.1    2024-04-01 [1] CRAN (R 4.4.0)
 pillar        1.9.0    2023-03-22 [1] CRAN (R 4.4.0)
 pkgbuild      1.4.4    2024-03-17 [1] CRAN (R 4.4.0)
 pkgconfig     2.0.3    2019-09-22 [1] CRAN (R 4.4.0)
 pkgload       1.4.0    2024-06-28 [1] CRAN (R 4.4.0)
 profvis       0.4.0    2024-09-20 [1] CRAN (R 4.4.1)
 promises      1.3.0    2024-04-05 [1] CRAN (R 4.4.0)
 purrr         1.0.2    2023-08-10 [1] CRAN (R 4.4.0)
 R6            2.5.1    2021-08-19 [1] CRAN (R 4.4.0)
 Rcpp          1.0.13-1 2024-11-02 [1] CRAN (R 4.4.1)
 remotes       2.5.0    2024-03-17 [1] CRAN (R 4.4.0)
 rlang         1.1.4    2024-06-04 [1] CRAN (R 4.4.0)
 rmarkdown     2.28     2024-08-17 [1] CRAN (R 4.4.0)
 rstudioapi    0.17.1   2024-10-22 [1] CRAN (R 4.4.1)
 sass          0.4.9    2024-03-15 [1] CRAN (R 4.4.0)
 scales        1.3.0    2023-11-28 [1] CRAN (R 4.4.0)
 sessioninfo   1.2.2    2021-12-06 [1] CRAN (R 4.4.0)
 shiny         1.9.1    2024-08-01 [1] CRAN (R 4.4.0)
 stringi       1.8.4    2024-05-06 [1] CRAN (R 4.4.0)
 stringr       1.5.1    2023-11-14 [1] CRAN (R 4.4.0)
 tibble        3.2.1    2023-03-20 [1] CRAN (R 4.4.0)
 tidyselect    1.2.1    2024-03-11 [1] CRAN (R 4.4.0)
 urlchecker    1.0.1    2021-11-30 [1] CRAN (R 4.4.0)
 usethis     * 3.0.0    2024-07-29 [1] CRAN (R 4.4.0)
 utf8          1.2.4    2023-10-22 [1] CRAN (R 4.4.0)
 vctrs         0.6.5    2023-12-01 [1] CRAN (R 4.4.0)
 xfun          0.48     2024-10-03 [1] CRAN (R 4.4.1)
 xtable        1.8-4    2019-04-21 [1] CRAN (R 4.4.0)
 yaml          2.3.10   2024-07-26 [1] CRAN (R 4.4.0)

 [1] /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/library

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────