Summary
Amelia Earhart’s attempt to circumnavigate the world by airplane ended in tragedy. This post usesggplot2
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.
Acknowledgements
This blog post was made possible thanks to:
References
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
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────