Creating a photo key map and table with R

mapping R

A complicated approach to make a simple map.

A simple table of photos with a corresponding photo key map is a common type of figure for many different planning documents and reports. Typically, making this type of map can be a bit tedious: pulling a base map into Adobe Illustrator (or another graphics editor), marking the location of each photo on the map, and hoping you don’t get anything mixed up.

This post illustrates a reproducible process for making this style of map using R. Using the coordinates embedded in the photo metadata, you can make a map and then combine the map with an table showing each photo alongside a short description pulled from the embedded title or description. At the end, I show how you can use the same approach using photos from Flickr.

Check your photo metadata at the start

Get started by checking the location metadata attached to your photographs. By default, every photo you take with your cell phone saves your location when you took the photo to the Exif metadata attached to the image file. The accuracy of coordinates attached to photographs varies but you can you can check and update the location associated with the photograph using the OSX Photos app or a dedicated utility like GeoTag for OSX or GeoSetter for Windows.

I also used the Photos app to add a brief description to each image (as the title for each photo). You could also add titles or modify coordinates for one or more images after importing the image exif metadata by modifying the data frame.

Read the Exif metadata from photos with exiftoolr

Once you have a folder of photos with locations and descriptions, you want to use the exiftoolr package to import the image metadata. Josh OBrien describes this package as a wrapper around ExifTool “a comprehensive open source utility for reading, writing and editing meta information in a wide variety of files.” You can use the exiftoolr::install_exiftool function to install ExifTool (if you don’t have it installed already).

For this example, I am using five photographs of Baltimore’s Ellwood Park neighborhood. I added titles to each image in the Photos app then exported them to the jpg subfolder I created in the image folder for this post. The following code shows how to use purrr::map_dfr to import selected metadata for each image, sort the images by longitude, and then number the images in order.

exif_table <-
  purrr::map_dfr(
  fs::dir_ls(path = "images", glob = "*.jpg"),
  ~ exiftoolr::exif_read(
    .x,
    tags = c(
      "SourceFile",
      "DateCreated",
      "Title",
      "GPSImgDirection",
      "GPSLatitude",
      "GPSLongitude"
    )
  )
) %>% 
  # Rename variables
  rename(
    title = Title,
    latitude = GPSLatitude,
    longitude = GPSLongitude,
    direction = GPSImgDirection,
    date = DateCreated,
    photo = SourceFile
  ) %>%
  # Sort by longitude
  arrange(longitude) %>%
  mutate(
    # Number the images
    number = row_number(),
    # Remove city, state and zipcode from image title
    title = stringr::str_remove(
      title,
      ",[:space:]Baltimore,[:space:]MD[:space:]212([:digit:]+)"
    )
  )

Make a photo table with gt

Now that we have the metadata for all of the images in a data frame, I can use gt to display the images and descriptions in a simple HTML table.

gt currently supports image columns created using local images, web images, or images created with ggplot. The example for the local_image helper function in the gt documentation is a bit unclear but I found a helpful example from a user on Stackoverflow that I adapted below.

# iPhone photos default to a 4:3 aspect ratio
image_height <- 240
image_width <- 320

image_table <-
  exif_table %>%
  select(title, number, photo) %>%
  gt() %>%
  # Label columns
  cols_label(
    title = "Description",
    number = "#",
    photo = "Photo"
  ) %>%
  # Set photo column width
  cols_width(
    vars(photo) ~ px(image_width),
    vars(number) ~ px(64)
  ) %>%
  # Set the font, weight, size, and alignment of the photo numbers
  tab_style(
    style = cell_text(
      font = "Fira Code",
      weight = "bolder",
      size = "xlarge",
      v_align = "top",
      align = "center"
    ),
    locations = cells_body(columns = vars(number))
  ) %>%
  # Set vertical alignment for numbers and description
  tab_style(
    style = cell_text(
      align = "right",
      v_align = "top",
      size = "large"
    ),
    locations = cells_body(columns = vars(title))
  ) %>%
  # Capitalize column headers
  opt_all_caps()

I want the numbers in the table to match the style I plan to use for the key map so I am using the html helper function from gt to stack the number on a FontAwesome circle icon.

image_table <- image_table %>%
  # Replace file path to the jpg with the image
  text_transform(
    locations = cells_body(vars(number)),
    fn = function(number) {
      purrr::map(
        number,
        ~ html(
          paste0('<span class="fa-stack">
                 <span class="fa fa-circle fa-stack-2x" style="color:#2f4f4f"></span>
                 <strong class="fa-stack-1x", style="color:#ffff00">',
                 .x,
                 '</strong></span>'
          )
        )
      )
    }
  )
image_table <- image_table %>%
  # Replace file path to the jpg with the image
  text_transform(
    locations = cells_body(vars(photo)),
    fn = function(x) {
      purrr::map(x, ~ local_image(.x, height = image_height))
    }
  )

image_table
Description # Photo
Tres Amigas / Three Friends (2015; Pablo Machioli, Edgar Reyes, and area youth, muralists) Jefferson Street and N. Decker Street 1
Rowhouses, 3100 block of McElderry Street (north side) 2
Rowhouses, 411-415 N. Ellwood Avenue 3
Rowhouses, 400 block of N. Robinson Street (east side) 4
Formstone-covered backs of rowhouses, 300 block of Loney Lane (west side) 5