Mapping the Ancient World: A Digital Odyssey through Ptolemy’s Geography

[This article was first published on Having Fun and Creating Value With the R Language on Lucid Manager, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

In the chronicles of cartographic history, few works have captivated the imagination, such as Claudius Ptolemy's “Geography“. This monumental second-century creation is the oldest fully preserved theoretical treatise on cartography, written almost 2000 years ago. This article discusses Mapping Ptolemy's Geography with ggplot2.

His cartography is not just a collection of maps of the known world in Hellenic times. Ptolemy also provides latitudes and longitudes with fantastic accuracy. Interestingly, he wrote that the best way to store geographic information is as a collection of numbers because copying maps leads to distortions over time. Thus, he created one of the world's first geographic information systems. His book contains the locations of thousands of towns, rivers, mountains and other points of interest.

Unfortunately, no freely available database of these coordinates exists in electronic format, but there are some textual versions, such as the example below.

Extract from Ptolemy's Geography (Nobbe edition, 1843)
Extract from Ptolemy's Geography (Nobbe edition, 1843).

I thus set forth to scrape these sources and create a Ptolemaic geographic database. The code and data for this project are available on GitHub.

Obtaining the Data

The process begins with the scraping of Ptolemy’s text from ToposText, which is a treasure trove of classical literature. This website contains an almost complete transcription of Ptolemy's tome. I saved the webpage to disk to prevent hitting the ToposText server too often. The source data is available on the Github repo in RDS format.

The website provides an accurate transcription of a large portion of Ptolemy's work. Some typographical quirks and curious coordinates were standardised using regular expressions.

  library(stringr)
  library(dplyr)
  library(ggplot2)

  # library(rvest)
  # Scrape the website and save it to disk
  #
  # url <- "https://topostext.org/work/209"
  #
  # raw_html <- read_html(url) %>%
  #     html_nodes("p") %>%
  #     html_text()
  # saveRDS(raw_html, "topostext.org-work-209.rds")

  raw <- readRDS("topostext.org-work-209.rds")

  # Fix issues and typos
  raw <- str_replace_all(raw, "\\. equator", "0°0") # Equator
  raw <- str_replace_all(raw, "0°0 and", "0°0'")
  raw <- str_replace_all(raw, "(\\d+.\\d+') S ", "-\\1") # Southern hemisphere
  raw <- str_replace_all(raw, "(\\d) c", "\\1'")
  raw <- str_replace_all(raw, "(\\d) d", "\\1' \\.")
  raw <- str_replace_all(raw, "(\\d) e", "\\1' \\.")
  raw <- str_replace_all(raw, "5°00' . 3°00", "45°15") # Estimated lat
  raw <- str_replace_all(raw, "4°00' . 3°00", "43°00") # Estimated lat
  raw <- str_replace_all(raw, "(\\d+)'(\\d+)", "\\1°\\2")
  raw <- str_replace_all(raw, "(Elateia . 51°38' )", "\\1. 38°15'")
  raw <- str_replace_all(raw, "(Kambyses)'", "\\1")
  raw <- str_replace_all(raw, "(Libison)'", "\\1")

The script then loops through all paragraphs that include coordinates and extracts the place names, longitude and latitude in degrees and minutes.

  # Detect place names and coordinates
  locs <- str_split(raw[str_detect(raw, "°|º")], "'")

  p_regex <- "\\b[A-Za-z ,:\\-]+\\b"
  c_regex <- "(-?[0-9]{1,3}[°|º][0-9]{1,3})"

  place <- vector()
  lon_raw <- vector()
  lat_raw <- vector()
  i <- 1

  for (j in seq_along(locs)) {
    t <- unlist(locs[j])
    n <- ceiling(sum(str_detect(t,  "°|º")) / 2)
    k <- i + (n - 1)
    place[i:k] <- str_extract(t, p_regex)[(1:n) * 2 + - 1]
    lon_txt <- unlist(str_extract_all(t[(1:n) * 2 + - 1], c_regex))
    lat_txt <- unlist(str_extract_all(t[(1:n) * 2], c_regex))
    if (length(lat_txt) == 0) lat_txt <- NA
    if (length(lon_txt) != n) stop(j)
    lon_raw[i:k] <- lon_txt
    lat_raw[i:k] <- lat_txt
    i <- i + n
    j <- j + 1
  }

The next phase creates a table of coordinates by converting the coordinates into numeric values and cleaning the place names. The degrees and minutes of longitude and latitude are stored in separate variables. Regular expressions split the geographic coordinates (e.g. 14°20' . 54°30') into four numbers (14, 20, 54, 30).

  # Generate data frame
  extract_nums <- function(x, regex) {
    as.numeric(unlist(str_extract_all(x, regex)))
  }

  d_regex <- "(-?[0-9]+)(?=°|º)"
  m_regex <- "(?<=°|º)([0-9]+)"

  locations <- tibble(place, lon_raw, lat_raw) %>%
    mutate(lon = extract_nums(lon_raw, d_regex),
           lon_min = extract_nums(lon_raw, m_regex),
           lat = extract_nums(lat_raw, d_regex),
           lat_min = extract_nums(lat_raw, m_regex),
           place = str_squish(place),
           place = str_to_sentence(place)) %>%
    select(placename = place, 4:7)

The table of place names and their coordinates is available on the GitHub repository.

Mapping Ptolemy's Geography

The visualisation was realised through the ggplot2 mapping geometry. Ptolemy's ancient world is reborn; its cities and landmarks emerge from the mists of time on this digital canvas.

This map uses the Conic projection, similar to one of the projections used by Ptolemy himself. This projection distorts the southern part of Africa slightly.

The longitudes are corrected to better match them with modern coordinates. Ptolemy used the Canary Islands as the zero meridian of his coordinate system, which is 18 degrees west of the Greenwich meridian. Research also shows that Ptolemy's longitudes are stretched by a factor of 1.428.

Ptolemy's work might seem full of mistakes from our modern perspective, but in fact, every world map we have ever produced is essentially wrong. Turning a globe into a two-dimensional object always results in distortions, no matter how hard you try.

Mapping Ptolemaic coordinates on a modern map
Mapping Ptolemaic coordinates on a modern map.
# Visualise
world <- map_data("world")

ggplot() +
    geom_map(data = world, map = world,
             aes(long, lat, map_id = region),
            fill = "darkgreen", colour = "lightgrey", linewidth = .1) + 
    geom_point(data = locations,
               aes(x = (lon + lon_min / 60 - 18) / 1.428,
                   y = (lat + lat_min / 60)),
               col = "orange", alpha = 0.7, size = .1) +
    scale_x_continuous(limits = c(-15, 70)) +
    scale_y_continuous(limits = c(-10, 60)) +
    coord_map(projection = "conic", lat0 = 37) +
    labs(title = "Ptolemaeus, Geography (II-VI)",
         subtitle = "Longitude correction: 0.7(lon - 18)",
         caption = "Source: topostext.org/work/209",
         x = NULL, y = NULL) + 
    theme_minimal(base_size = 8) +
    theme(panel.background = element_rect(fill = "lightblue", colour = NA))

Ptolemy knew the Earth was spherical – a fact established by Greek philosophers well before his time. In his Geographic, Ptolemy provides a cornucopia of coordinates, illustrating the known world from Britain to Southeast Asia and from the northern reaches of Scandinavia to below the equator. But their knowledge only covered a small part of the whole globe.

The Greeks blended mythology, philosophy, and early scientific speculation to theorise what lies beyond the world they mapped. They imagined a vast, unending ocean encircling the world. Beyond Oceanus, some thought, might be other worlds or unknown lands. The concept of "antipodes" was also bandied about - these are places where people's feet are directly opposite to ours. Though the existence of such lands or peoples was often debated or outright denied.

To leave a comment for the author, please follow the link and comment on their blog: Having Fun and Creating Value With the R Language on Lucid Manager.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)