View as a slideshow.
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.
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.
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.
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:
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:
addCircleMarkers
it specifies the size in pixels (image-land); no matter the zoom the circles are always the same sizeaddCircles
it specifies the size in meters (real-world); so they get bigger or smaller when you zoom.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>