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.

Hard-Coding the Data

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

Geocoding the Data

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

Building the Plot

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

Adding a Map Background

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.

Building an Interactive Map

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)