A visuaisation of how big Middle Earth is compared with the size of Europe. Sam and Frodo walked a long way! Hobbiton is centered on Cardiff in Wales - just because
GIS
Data Visualization
R Programming
2025
Author
Peter Gray
Published
September 9, 2024
#### 1. R code
Show code
if(!require(yaml)){install.packages("yaml"); library(yaml)}if(!require(tidyverse)){install.packages("tidyverse"); library(tidyverse)}if(!require(ggplot2)){install.packages("ggplot2"); library(ggplot2)}# if needed and on Linux apt-get -y update && apt-get install -y libudunits2-dev libgdal-dev libgeos-dev libproj-devif(!require(sf)){install.packages("sf"); library(sf)}if(!require(patchwork)){install.packages("patchwork"); library(patchwork)}if(!require(rnaturalearthdata)){install.packages("rnaturalearthdata"); library(rnaturalearthdata)}if(!require(rnaturalearth)){install.packages("rnaturalearth"); library(rnaturalearth)}if(!require(ggspatial)){install.packages("ggspatial"); library(ggspatial)}if(!require(scales)){install.packages("scales"); library(scales)}if(!require(leaflet)){install.packages("leaflet"); library(leaflet)}if(!require(glue)){install.packages("glue"); library(glue)}if(!require(showtext)){install.packages("showtext"); library(showtext)}if(!require(knitr)){install.packages("knitr"); library(knitr)}if(!require(ggrepel)){install.packages("ggrepel"); library(ggrepel)}# Colour Schemecustom_colors<-c("#004225", "#00007d", "#D4A5A5", "#1B998B", "#F2E86D", "#F25F5C", "#247BA0", "#662E9B")# Custom Themecustom_theme<-function(){ggplot2::theme( plot.title.position ="plot", plot.caption.position ="plot", plot.title =element_text(face ="bold", size =16, hjust =0.5, color ="#000036"), axis.title.x =element_text(face ="bold", size =10), axis.title.y =element_text(face ="bold", size =10), panel.border =element_blank(), panel.background =element_blank(), axis.line =element_line(linewidth =0.5, colour ="darkgrey"), axis.text.x =element_text(angle =45, hjust =1, size =10))}# Custom Fonts font_add(family ="Aniron", regular ="/home/pgr16/Documents/Coding/Middle Earth/Fonts/anirm___.ttf")showtext_auto()font_add(family ="Celtic", regular ="/home/pgr16/Documents/Coding/Middle Earth/Fonts/UncialAntiqua-Regular.ttf")showtext_auto()world_map<-read_sf("/home/pgr16/Documents/Coding/Middle Earth/World Countries/ne_50m_admin_0_countries.shp")coastline<-read_sf("/home/pgr16/Documents/Coding/Middle Earth/Middle Earth/Coastline2.shp")|>mutate(across(where(is.character), ~iconv(., from ="ISO-8859-1", to ="UTF-8")))contours<-read_sf("/home/pgr16/Documents/Coding/Middle Earth/Middle Earth/Contours_18.shp")|>mutate(across(where(is.character), ~iconv(., from ="ISO-8859-1", to ="UTF-8")))rivers<-read_sf("/home/pgr16/Documents/Coding/Middle Earth/Middle Earth/Rivers.shp")|>mutate(across(where(is.character), ~iconv(., from ="ISO-8859-1", to ="UTF-8")))roads<-read_sf("/home/pgr16/Documents/Coding/Middle Earth/Middle Earth/Roads.shp")|>mutate(across(where(is.character), ~iconv(., from ="ISO-8859-1", to ="UTF-8")))lakes<-read_sf("/home/pgr16/Documents/Coding/Middle Earth/Middle Earth/Lakes.shp")|>mutate(across(where(is.character), ~iconv(., from ="ISO-8859-1", to ="UTF-8")))regions<-read_sf("/home/pgr16/Documents/Coding/Middle Earth/Middle Earth/Regions_Anno.shp")|>mutate(across(where(is.character), ~iconv(., from ="ISO-8859-1", to ="UTF-8")))forests<-read_sf("/home/pgr16/Documents/Coding/Middle Earth/Middle Earth/Forests.shp")|>mutate(across(where(is.character), ~iconv(., from ="ISO-8859-1", to ="UTF-8")))mountains<-read_sf("/home/pgr16/Documents/Coding/Middle Earth/Middle Earth/Mountains_Anno.shp")|>mutate(across(where(is.character), ~iconv(., from ="ISO-8859-1", to ="UTF-8")))placenames<-read_sf("/home/pgr16/Documents/Coding/Middle Earth/Middle Earth/Combined_Placenames.shp")|>mutate(across(where(is.character), ~iconv(., from ="ISO-8859-1", to ="UTF-8")))list<-c("Hobbiton", "Rivendell", "Edoras", "Minas Tirith", "Bay of Bafalas", "Bay of Umbar", "Fangorn", "Grey Havens", "Helm's Deep", "Isengard", "Lórien", "Mirkwood", "Mt Doom", "Sea of Rhun", "Mt Doom")miles_to_meters<-function(x){x*1609.344}meters_to_miles<-function(x){x/1609.344}clr_green<-"#035711"clr_blue<-"#0776e0"clr_yellow<-"#fffce3"hobbiton<-placenames|>filter(NAME=="Hobbiton")|>mutate(geometry_x =map_dbl(geometry, ~as.numeric(.)[1]), geometry_y =map_dbl(geometry, ~as.numeric(.)[2]))|>select(LAYER, NAME, geometry_x, geometry_y)# Format numeric coordinates with degree symbols and cardinal directionsformat_coords<-function(coords){ns<-ifelse(coords[[1]][2]>0, "N", "S")ew<-ifelse(coords[[1]][1]>0, "E", "W")glue("{latitude}°{ns} {longitude}°{ew}", latitude =sprintf("%.6f", coords[[1]][2]), longitude =sprintf("%.6f", coords[[1]][1]))}europe_window<-st_sfc(st_point(c(-12.4, 29.31)), # left (west), bottom (south)st_point(c(44.74, 64.62)), # right (east), top (north) crs =st_crs("EPSG:4326")# WGS 84)%>%st_transform(crs =st_crs("EPSG:5633"))%>%# LAEA Europe, centered in Portugalst_coordinates()europe_plot<-ggplot()+geom_sf(data =world_map, fill ="#004225", alpha =0.5)+coord_sf(crs =st_crs("EPSG:5633"), xlim =europe_window[, "X"], ylim =europe_window[, "Y"], expand =FALSE)+custom_theme()+labs("Map of Europe")cardiff<-tribble(~place, ~lat, ~long,"Cardiff", 51.481583, -3.179090)%>%st_as_sf(coords =c("long", "lat"), crs =st_crs("EPSG:4326"))# Convert the Tolkien home coordinates to European coordinatescardiff<-cardiff%>%st_transform(crs =st_crs("EPSG:5633"))# Convert the Hobbiton coordinates to European coordinateshobbiton_in_europe<-hobbiton%>%st_transform(st_crs("EPSG:5633"))# Find the offset between Tolkien's home and Hobbitonme_to_europe<-st_coordinates(cardiff)-st_coordinates(hobbiton_in_europe)me_places_in_europe<-placenames%>%# Make the Middle Earth data match the Europe projectionst_transform(st_crs("EPSG:5633"))%>%# Just look at a handful of placesfilter(NAME%in%c("Hobbiton", "Rivendell", "Edoras", "Minas Tirith", "Mt Doom"))%>%# Double the distancesst_set_geometry((st_geometry(.)-st_geometry(hobbiton_in_europe))*2+st_geometry(hobbiton_in_europe))%>%# Shift everything around so that Hobbiton is in Oxfordst_set_geometry(st_geometry(.)+me_to_europe)%>%# All the geometry math made us lose the projection metadata; set it against_set_crs(st_crs("EPSG:5633"))coastline_in_europe<-coastline%>%st_transform(st_crs("EPSG:5633"))%>%st_set_geometry((st_geometry(.)-st_geometry(hobbiton_in_europe))*2+st_geometry(hobbiton_in_europe))%>%st_set_geometry(st_geometry(.)+me_to_europe)%>%st_set_crs(st_crs("EPSG:5633"))europe_me_plot<-ggplot()+geom_sf(data =world_map, fill ="#004225", alpha =0.5, color ="white", linewidth =0.25)+geom_sf(data =coastline_in_europe, linewidth =0.25, fill ="#39CCCC")+geom_sf(data =me_places_in_europe, fill ="#39CCCC", alpha =0.5)+geom_text_repel(data =filter(me_places_in_europe, NAME%in%list), aes(label =NAME, geometry =geometry), stat ="sf_coordinates", nudge_x =-70000, hjust =1, family ="Aniron", fontface ="plain", size =rel(10), box.padding =0.5, # Space around labels point.padding =0.5, # Space around labeled points max.overlaps =10)+# Adjust to control label repulsioncoord_sf(crs =st_crs("EPSG:5633"), xlim =europe_window[, "X"], ylim =europe_window[, "Y"], expand =FALSE)+theme_void()+labs(title =str_wrap("Plot of Middle Earth Superimposed over the map of Europe", 40), subtitle ="Hobbiton is centred on the great city of Cardiff")+theme(plot.background =element_rect(fill =clr_yellow), plot.title =element_text(family ="Aniron", size =rel(4), hjust =0.02), plot.subtitle =element_text(family ="Aniron", size =rel(2), hjust =0.02))