Part 2: Create an interactive web map with geotagged photos
See the other parts in this series of blog posts.
In part 1 we looked at making a simple interactive map using locations from our geotagged photos. Those photos have data associated with them (counts of oysters in quadrats) so here we will look at how you would plot that data on your map.
We will also add a geo-referenced pop-up that includes a picture of our study site. You might be familiar with this idea from Google Earth or Flickr.
To get you excited, here is the end result (try clicking on things! If you are viewing this blog on Rbloggers you will need to go here to see the map):
First up here are the packages we need:
The next list of packages is optional, but I will use here to make data processing more convenient:
library(readr) #for the updated version of read.csv library(dplyr) #for data wrangling library(stringr) #for wrangling strings
Finally to get some nice colours:
Now load in the data. I am using
read_csv which comes from the
read_csv is a modernised version of the base R function
read.csv. Here are the exif data and associated oyster counts if you are repeating this example.
edat <- read_csv('Exifdata.csv') odat <- read_csv('oyster_data.csv') n <- nrow(odat) #number of sample sites
Joining the locations to the counts
Now we need to join our counts of oysters to the locations. We can match by the picture numbers, which I recorded when I counted the oysters.
First up we need to clean the clean the picture numbers in the exif data to remove the leading ‘DSC_’. We do this using
stringr to extract the first four digits in each picture’s filename.
edat<- edat %>% mutate(pic_num = as.numeric( str_extract(SourceFile, '\\d\\d\\d\\d') ))
If you haven’t seen
%>% before it is a ‘pipe’ that puts the dataframe
edat into the first argument of our
mutate function. The pipe isn’t essential, but I think it improves readibility. See Wickham’s book for more guidance on pipes.
Now we can join the oysters and pictures.
left_join will automatically identify
pic_num to join on, because it is the common column across both dataframes:
dat <- odat %>% left_join(edat)
Create the content for the pop-up image
To create the pop-up you need to know a little bit of html. There is plenty of helpful guides on the web and html is pretty simple to learn. The pop-up will show an image and some text. Here we define the content:
content <- paste(sep = "<br/>", "<img src='http://www.seascapemodels.org/Images/intertidal_scene.JPG' style='width:230px;height:300px;'>", "The intertidal zone", "at Hornby Island" )
In short we have specified a string with some html code.
html tag creates a new line. The
tag specifies the insertion of an image (the link gives the image’s location, which is on my webpage. We have also set the width and height of the image. Then we follow with a bit of text explaining the image.
We will use
content in our
leaflet map below.
We also need the location to place our pop-up. Because the image is gps tagged, we can extract the gps locations from the exif data. Download the image from the link given above in
content then you can get the exif data associated with it like this:
scene <- exifr('intertidal_scene.JPG') x1 <- scene $GPSLongitude y1 <- scene $GPSLatitude
We now have coordinates for the pop-up in our
Colour scale for the oyster counts
leaflet has some pretty convenient functions for creating nice colour scales. Here we use
colourBin to bin counts of oysters into different categories that we set in the vector
brks <- c(0,1, 5, 10, 20, 70) ncol <- length(brks)-1 oystercols <- c('grey20',brewer.pal(ncol-1, 'Purples')) pal <- colorBin(oystercols, dat$oysters_live, bins = brks)
The end result
pal is a function that will generate a colour palette when some data is input to it.
Make a map with images
We have prepped our data, so now the fun bit, making the map!
This map is somewhat complicated so I will step you through it using our friend ‘pipes’ (the full code without text breaks is below if you want to cut and paste). Note how convenient pipes are here, we can essentially just layer up our map data in an intuitive fashion.
mapout <- leaflet(dat) %>%
We name our map and set the dataframe it will use.
Set’s the background (a satellite image)
setView(lng = x1, lat = y1, zoom = 16) %>%
Chooses the region to zoom into.
leaflet will guess the zoom level from our dataframe, but we have more control if we choose the region. The default here was a higher zoom level where the tiles don’t render when using Esri.WorldImagery.
Now we add some markers, setting their colours using oyster counts:
addCircleMarkers(~ GPSLongitude, ~ GPSLatitude, color = 'white',opacity =1, weight = 1, fillColor = ~pal(oysters_live), popup = as.character(dat$oysters_live), fillOpacity = 0.8, radius = 6) %>%
This looks complex, but it is just a series of simple commands.
~ GPSLongitude tells leaflet which column of
dat is longitude.
weight refer to the colour, opacity (solid in this case) and width of the border of the markers.
fillColor uses our palette and applies it the
oysters_live column of
popup creates a popup when we click on a marker, which gives the number of oysters.
fillOpacity and radius refer to the fill of the markers.
We will also add a marker for the picture I took of the shoreline. Note that this time I use our
content data created above to specify what goes in the popup. We also change some of the marker options with
?markerOptions for other options:
addMarkers(x1, y1, popup = content, options = markerOptions(opacity = 0.9, draggable=T)) %>%
Now let’s add a legend:
addLegend("topright", pal = pal, values = brks, title = "Number of live oysters <a href = 'https://en.wikipedia.org/wiki/Pacific_oyster' target = '_blank'> (Crassostrea gigas)</a>", opacity = 1)
the nice thing about using
colorBin to create our colours is we can map them directly to the legend, as we have done here with
pal = pal (telling leaflet to use our
pal function for the colour palette). Note in the
title command we have also inserted some more html, this time a
tag which just turns the species name ‘(Crassostrea gigas)’ into a link to the Wikipedia page for the Pacific oyster.
And that’s it. Run that code then type
mapout to print your map.
Next up we will look at how to build a spatial model using this data.
Extra task: Getting the map on your webpage
If you want to save the map to use on your own page, you can do so with the
htmlwidgets package, like this:
htmlwidgets::saveWidget(mapout, file = 'hornby_oysters_map.html', selfcontained = F, libdir = "leafletmap_files")
Map code as one block for cutting and pasting
mapout <- leaflet(dat) %>% #Use satellite image as base addProviderTiles("Esri.WorldImagery") %>% setView(lng = x1, lat = y1, zoom = 16) %>% #Add markers for oyster quadrats addCircleMarkers(~ GPSLongitude, ~ GPSLatitude, color = 'white',opacity =1, weight = 1, fillColor = ~pal(oysters_live), popup = as.character(dat$oysters_live), fillOpacity = 0.8, radius = 6) %>% # add a popup for number of oysters #Add marker showing a picture of the survey site addMarkers(x1, y1, popup = content, options = markerOptions(opacity = 0.9, draggable=T)) %>% #Add a legend addLegend("topright", pal = pal, values = brks, title = "Number of live oysters <a href = 'https://en.wikipedia.org/wiki/Pacific_oyster' target = '_blank'> (Crassostrea gigas)</a>", opacity = 1)