View as a slideshow.

Introduction

Today we’ll cover the basics for two topics that came up frequently in your project proposals: making maps and programatically manipulating text. This will be enough to get you started in the second half of class today; we’ll save more advanced treatments of both for Thursday.

Maps

There are many packages available for making maps in R. Here we’ll cover a quick tutorial about how to use one of them, Leaflet, that works well with Shiny.

The Leaflet for R interface is designed to be familiar to anyone who’s worked with ggplot2, which you have! As with dplyr and tidyr it’s designed to work nicely with the %>% operator.

Let’s get started by loading the leaflet package:

library(leaflet)

NB if you’ve loaded leaflet before please restart your R session to make sure you’re working with the latest version installed on RNA.

Getting started

The leaflet equivalent of the ggplot function is leaflet:

leaflet()

Just like ggplot with no geom_ specified, leaflet draws a blank map by default. We’ll see in a minute that we can pass custom geospatial information into this function; by default it will load a map of the world.

To make leaflet actually draw a map, we need to add “tiles”. The simplest tile function is addTiles; we’ll use pipe to chain the two together:

leaflet() %>% addTiles()

As in ggplot2, you can chain other kinds of specifications or visual elements in the pipe. For example, to set the default focus, you can use setView:

leaflet() %>% setView(lng = -79.442778, lat = 37.783889, zoom = 12) %>% addTiles()

By default, the addTiles function pulls maps from the OpenStreetMap project. You can choose alternative map styles using the addProviderTiles and specifying one of the options found in this gallery.

For example, NASA’s earth at night map:

leaflet() %>% 
  setView(lng = -79.442778, lat = 37.783889, zoom = 8) %>% 
  addProviderTiles(provider = providers$NASAGIBS.ViirsEarthAtNight2012)

Or CartoDB’s “Positron” map (nice if you want the map elements to be “light”):

leaflet() %>% 
  setView(lng = -79.442778, lat = 37.783889, zoom = 12) %>% 
  addProviderTiles(providers$CartoDB.Positron)

The Web Map Service standard provides a common data exchange format that you might run into in looking for geospatial data. For example, if we want to view cloud reflectance data provided by the University of Iowa, we can use the addWMSTiles function:

leaflet() %>% 
  addTiles() %>% 
  setView(lng = -79.442778, lat = 37.783889, zoom = 8) %>%
  addWMSTiles(
    "http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi",
    layers = "nexrad-n0r-900913",
    options = WMSTileOptions(format = "image/png", transparent = TRUE),
    attribution = "Weather data © 2012 IEM Nexrad"
  )

Depending on the weather this map could be borring. Note the use of both addTiles and addWMSTiles; the first draws the normal map, the secound weather data on top of it. As in ggplot2 it’s fine to chain multiple visualizations methods together to be plotted on top of one another.

Custom data and markers

Of course, many of you want to be able to plot custom annotation elements on top of your maps. There are many options for possible source data structures. The simplest that we’ll look at here is to use a table (aka data frame).

Here are the rules for your table:

  • Latitudes should be in column named “lat” or “latitude”
  • Longitudes should be in a column named “lon”, “lng”, “long” or “longitude”

Both should be numeric vectors (not string coordinates) with decimal coordinates. See the sp package for helper functions if you need to convert from DMS or other formats.

For this example, I’ll hand write a sample data frame with coordinates and annotation data, with some notable locations in Lexington (the data.frame function just lets you compose a table on the fly):

lex <- data.frame( lat    = c(37.789444 , 37.787673  ,  37.785624  )
                 , lon    = c(-79.441725, -79.443623 , -79.441544  )
                 , place  = c("Lab"     , "Classroom", "Thai food!")
                 , rating = c(7         , 5          , 10          )
                 , stringsAsFactors = FALSE
                 )

We can make a simple map as before, but this time we specify the data argument in our call to leaflet:

leaflet(data = lex) %>% 
  setView(lng = -79.442778, lat = 37.783889, zoom = 12) %>%
  addTiles()

Nothing’s changed; but now we can use the additional columns to add annotations onto our map, like markers:

leaflet(data = lex) %>% 
  addTiles() %>% 
  addMarkers(popup = ~place)
## Assuming "lon" and "lat" are longitude and latitude, respectively

Notice the similarity between how we specify the column to use for the marker information and how aesthetic mappings work in ggplot: ~place says “use the place column to find the text to show in the popup”. Actually, this text can be any valid HTML; the htmltools package for helper functions that make it easy to do HTML formatting without having to know any HTML. We’ll look at an example of how we can use it in the string processing section below.

If you have a lot of markers in one place the clustering feature can be useful (note the lower zoom level):

leaflet(data = lex) %>% 
  setView(lng = -79.442778, lat = 37.783889, zoom = 10) %>% 
  addTiles() %>% 
  addMarkers(label = ~place, clusterOptions = markerClusterOptions())
## Assuming "lon" and "lat" are longitude and latitude, respectively

You can also add two different kinds of “circles” onto your maps to visualize quantitative data. The first is addCircleMarkers:

leaflet(data = lex) %>% 
  addTiles() %>%
  addCircleMarkers(radius = ~rating, label = ~place)
## Assuming "lon" and "lat" are longitude and latitude, respectively

The second addCircles:

leaflet(data = lex) %>% addTiles() %>%
  addCircles(radius = ~rating, label = ~place)
## Assuming "lon" and "lat" are longitude and latitude, respectively

The difference between the two is that the quantitative variable being used for the radius different things:

  • In addCircleMarkers it specifies the size in pixels (image-land); no matter the zoom the circles are always the same size
  • In addCircles it specifies the size in meters (real-world); so they get bigger or smaller when you zoom.

Leaflet with Shiny

Leaflet provides a few functions that make it easy to integrate into a Shiny app. The first two are leafletOutput and renderLeaflet for use in ui.R and server.R, respectively.

Here’s an example:

ui.R

library(shiny)
library(leaflet)

fluidPage(
  # map output
  leafletOutput("worldMap"),
  
  # line break (puts some space between map and button)
  br(),
  
  # a button
  actionButton("newButton", "New place!")
)

server.R

library(shiny)
library(leaflet)

function(input, output, session) {
  
  lats <- -90:90
  lons <- -180:180
  
  output$worldMap <- renderLeaflet({
    
    # note the dummy use of the action button input
    btn <- input$newButton
    
    leaflet() %>% 
      setView(lng = sample(lons, 1), lat = sample(lats, 1), zoom = 5) %>% 
      addTiles()
  })
  
}

That worked, but it is a little kludgy because the entire leaflet widget reload every time we change the location. A cleaner way to update leaflet maps in real time is the use the leafletProxy function. Here’s an example refactoring of our little app that does that; our ui.R doesn’t change:

server.R

function(input, output, session) {
  
  lats <- -90:90
  lons <- -180:180
  
  output$worldMap <- renderLeaflet({
    leaflet() %>% 
      setView(lng = -79.442778, lat = 37.783889, zoom = 5) %>% 
      addTiles()
  })
  
  observe({
    # note the dummy use of the action button input
    btn <- input$newButton
    
    leafletProxy("worldMap") %>%
      setView(lng = sample(lons, 1), lat = sample(lats, 1), zoom = 5)
  })
  
}
## function(input, output, session) {
##   
##   lats <- -90:90
##   lons <- -180:180
##   
##   output$worldMap <- renderLeaflet({
##     leaflet() %>% 
##       setView(lng = -79.442778, lat = 37.783889, zoom = 5) %>% 
##       addTiles()
##   })
##   
##   observe({
##     # note the dummy use of the action button input
##     btn <- input$newButton
##     
##     leafletProxy("worldMap") %>%
##       setView(lng = sample(lons, 1), lat = sample(lats, 1), zoom = 5)
##   })
##   
## }
## <environment: 0x56485a2a6df8>