This is a copy of J. Lacko’s wonderful reproduction of parts of Minard’s map of Napoleon’s ill-fated invasion of Russia. See Geocoding the Minard.
First, we code the cities, army sizes, and campaign steps, all painstakingly specified by J. Lacko.
library(tidygeocoder)
library(ggplot2)
## Warning in register(): Can't find generic `scale_type` in package ggplot2 to
## register S3 method.
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
minard_raw <- data.frame(city = c("Kaunas", "Vilnius", "Глубокое", "Витебск",
"Смоленск", "Дорогобу́ж", "Гага́рин" ,
"Можа́йск", "Москва", "Москва",
"Малояросла́вец", "Можа́йск", "Вя́зьма",
"Дорогобу́ж", "Смоленск", "О́рша",
"Бобр", "Бори́сов", "Маладзечна",
"Сморгонь", "Vilnius", "Kaunas", # La Grande Armée
"Vilnius", "Daugavpils",
"По́лоцк", "Бобр", # reserve in Polotskt
"Kaunas", "Riga",
"Riga", "Kaunas"), # the siege of Riga
direction = c("outward", "outward", "outward", "outward",
"outward", "outward", "outward", "outward",
"outward", "homeward", "homeward", "homeward",
"homeward", "homeward", "homeward", "homeward",
"homeward", "homeward", "homeward", "homeward",
"homeward", "homeward", # Napoleon
"outward", "outward", "homeward",
"homeward", # Oudinot
"outward", "outward", "homeward",
"homeward"), # MacDonald
strength = c(422000, 340000, 280000, 210000, 145000,
140000, 127100, 100000, 100000, 100000,
98000, 87000, 55000, 37000, 24000, 20000,
50000, 28000, 12000, 14000, 8000,
4000, # Napoleon
60000, 40000, 33000, 28000, # Oudinot
22000, 22000, 6000, 6000), # MacDonald
leader = c(rep("Napoleon", 22),
rep("Oudinot", 4),
rep("MacDonald", 4)))
Lacko used this geocoding code, but some of the cities no longer return lat-long values.
minard_geocoded <- minard_raw %>%
tidygeocoder::geocode(city = city,
method = "osm")
So I was forced to (manually) scrape his website data table and load the values as specified on his website.
library(tidyverse)
## -- Attaching packages --------------------------------------- tidyverse 1.3.1 --
## v tibble 3.1.6 v purrr 0.3.4
## v tidyr 1.1.4 v stringr 1.4.0
## v readr 2.1.0 v forcats 0.5.1
## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag() masks stats::lag()
minard_geocoded <- read_csv("data/menard-scraped-data.csv")
## Rows: 30 Columns: 6
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr (3): city, direction, leader
## dbl (3): strength, lat, long
##
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
We then grab the city labels for the plot and hard-code some offsets.
# city labels
city_labels <- minard_geocoded %>%
select(city, lat, long) %>%
distinct()
# offsets - a kludge to make the labels legible
kludge <- data.frame(horizontal = c(0, -.25, -.2, 0, 0,
.25, -.5, 0, 0, 0,
.4, .25, .4, -.2, -.2,
.7, -.25, .4, 0),
vertical = c(-.2, -.2, .3, .3, -.3,
-.3, .2, .2, .2, -.2,
-.3, -.2, -.15, .2, -.15,
.1, .2, .2, .15))
We’re now ready to build the plot.
# and finally, the plot itself
ggplot() +
# paths taken by the armies
geom_path(data = minard_geocoded,
aes(x = long,
y = lat,
size = strength,
color = direction,
# make sure Napoleon gets drawn over Oudinot, not the other way round
group = factor(leader, levels = c("Oudinot",
"MacDonald",
"Napoleon")))) +
# labels for the cities
geom_text(data = city_labels,
aes(x = long,
y = lat,
label = city),
family = "Old Standard TT", # an old fashioned Cyrillic font
fontface = "italic",
# I wish that this was not necessary...
nudge_x = kludge$horizontal,
nudge_y = kludge$vertical) +
scale_colour_manual(values = c("gray10",
"#e8cbac")) + # the color Minard calls "rouge"
scale_size(range = c(1/10, 17)) + # a "slight" exaggeration
guides(color = F, size = F) + theme_void() + # just say "no" to distractions
labs(title = "1812, or There and Back Again",
subtitle = "geocoding the Minard's map") +
theme(plot.title = element_text(hjust = 1/2,
face = "bold"),
plot.subtitle = element_text(hjust = 1/2,
face = "italic"))
## Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> =
## "none")` instead.
## Warning in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, :
## font family not found in Windows font database
We can now layer the data on a modern map.
library(ggmap)
## Google's Terms of Service: https://cloud.google.com/maps-platform/terms/.
## Please cite ggmap if you use it! See citation("ggmap") for details.
##
## Attaching package: 'ggmap'
## The following object is masked from 'package:tidygeocoder':
##
## geocode
basemap <- get_stamenmap(bbox = c(left = 23, bottom = 53.75, right = 39.25, top = 57.15),
maptype = "toner", zoom = 7)
## Source : http://tile.stamen.com/toner/7/72/39.png
## Source : http://tile.stamen.com/toner/7/73/39.png
## Source : http://tile.stamen.com/toner/7/74/39.png
## Source : http://tile.stamen.com/toner/7/75/39.png
## Source : http://tile.stamen.com/toner/7/76/39.png
## Source : http://tile.stamen.com/toner/7/77/39.png
## Source : http://tile.stamen.com/toner/7/72/40.png
## Source : http://tile.stamen.com/toner/7/73/40.png
## Source : http://tile.stamen.com/toner/7/74/40.png
## Source : http://tile.stamen.com/toner/7/75/40.png
## Source : http://tile.stamen.com/toner/7/76/40.png
## Source : http://tile.stamen.com/toner/7/77/40.png
## Source : http://tile.stamen.com/toner/7/72/41.png
## Source : http://tile.stamen.com/toner/7/73/41.png
## Source : http://tile.stamen.com/toner/7/74/41.png
## Source : http://tile.stamen.com/toner/7/75/41.png
## Source : http://tile.stamen.com/toner/7/76/41.png
## Source : http://tile.stamen.com/toner/7/77/41.png
ggmap(basemap) +
geom_path(data = minard_geocoded,
aes(x = long, y = lat, size = strength, color = direction,
group = factor(leader, levels = c("Oudinot",
"MacDonald",
"Napoleon"))),
alpha = 2/3) +
scale_colour_manual(values = c("gray10", "#e8cbac")) +
scale_size(range = c(1/4, 17)) +
guides(color = F, size = F) +
theme_void() +
labs(title = "1812, or There and Back Again",
subtitle = "geocoding the Minard's map") +
theme(plot.title = element_text(hjust = 1/2, face = "bold"),
plot.subtitle = element_text(hjust = 1/2, face = "italic"))
## Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> =
## "none")` instead.
Finally, we go where Minard could not go by making the map interactive.
library(sf)
## Linking to GEOS 3.9.1, GDAL 3.2.1, PROJ 7.2.1; sf_use_s2() is TRUE
# first make the data frame spatial object
sf_path <- minard_geocoded %>%
st_as_sf(coords = c("long", "lat"), crs = 4326) %>%
mutate(direction = as.factor(direction)) # factor for color palette assignment
minard_lines <- NULL # initiate an empty object
for (captain in unique(sf_path$leader)) { # i.e. Napoleon, Oudinot, MacDonald
wrk_army <- subset(sf_path, leader == captain)
# cycle over all segments, except the last one
for (i in 1:(nrow(wrk_army)-1)) {
# line object from two points
wrk_line <- wrk_army[c(i, i+1), ] %>% # current & next point coords
st_coordinates() %>%
st_linestring() %>%
st_sfc()
# a single row of results dataframe
line_data <- data.frame(
direction = wrk_army$direction[i],
strength = wrk_army$strength[i],
leader = wrk_army$leader[i],
label = paste0(wrk_army$city[i], " to ",
wrk_army$city[i+1], "<br>army strength ",
format(wrk_army$strength[i],
big.mark = ",",
scientific = F), " men."),
geometry = wrk_line
)
# bind the single row to the output object
if (is.null(minard_lines)) {
minard_lines <- line_data
} else {
minard_lines <- dplyr::bind_rows(minard_lines,
line_data)
} # /if - binding results
} # /for segment
} # /for leader
# finalize the linear object
minard_lines <- st_as_sf(minard_lines, crs = 4326)
library(leaflet)
# palette for direction
pal <- colorFactor(c("gray10", "#e8cbac"),
minard_lines$direction)
# the map itself
leaflet(data = minard_lines) %>%
addProviderTiles("Stamen.Toner") %>%
addPolylines(weight = ~strength/15000,
color = ~pal(direction),
opacity = 1,
popup = ~label)