WebMaps in R with Leaflet

Welcome! While we’re waiting:

Workshop Outline

Introductions

Basic Maps

  • Marker Maps
  • Adding Popups

Data Maps

  • Symbology for mapping point data
  • Choropleth maps
  • Adding Legends

Doing More

  • Customizing the UI
  • Sharing your maps

Leaflet

Leaflet

Leaflet is a lightweight, yet powerful javascript library for creating interactive web maps.

Leaflet maps are a combination of HTML and Javascript code that is meant to be rendered in a web browser.

You can use the R leaflet package to create Leaflet maps in R without knowing HTML and Javascript!

Leaflet in R Workflow

  1. Find the geospatial data that you want to map.
  2. Use the leaflet R package to make a custom, interactive map of your data.
  3. Save your leaflet map to an html file.
  4. View your html file locally in your web browser.
  5. Share your map by emailing the html file or hosting it on a website.

Why the package leaflet?

There are a number of R packages for making Leaflet maps.

  • leaflet is actively maintained, highly customizable, and integrates well with other R libraries.

  • tmap is easier for getting started but less customizeable
    • also great for interactive data analysis
    • also great for static maps (similar to ggplot2)
  • tmap may be a better option for some folks!
    • we cover it in our Geospatial Data in R workshop series.

Getting Started

Download workshop files: https://github.com/dlab-geo/r-leaflet-workshop

Follow along:

  • Tutorial Page (leaflet-webmaps-in-R.html)
  • Raw code (leaflet-webmaps-in-R.R)
  • Slides (leaflet-webmaps-in-R-slides.html)

Make sure you can copy and paste from one of the above into the script.

Load R Packages

Load the packages we will use today

library(leaflet)
library(RColorBrewer)
library(sp)
library(rgdal)
library(htmlwidgets)
library(dplyr)

Install any packages that you do not have on your computer

Set working directory

to the folder in which you unzipped the workshop files

Our First Leaflet Map

Our first Leaflet map

map1 <- leaflet()       # Initialize the leaflet map object
map1 <- addTiles(map1)  # Add basemap - default is OpenStreetMap
map1                    # Display the map

WARNING don’t call your map object map

Our first Leaflet map

map1 <- leaflet()       # Initialize the leaflet map object
map1 <- addTiles(map1)  # Add basemap - default is OpenStreetMap
map1                    # Display the map

Piping Syntax

Its very common use piping or chaining syntax….

The output of one command becomes the input of the next command.

map1 <- leaflet() %>%    # Initialize the leaflet map object
        addTiles()       # Add basemap - default is OpenStreetMap

map1                     # Display the map  

Setting the Map’s Intial View

Use setView to specify the center of the map and the initial zoom level.

map1 <- leaflet() %>%
        addTiles() %>%  
        setView(lat=37.87004, lng=-122.25817, zoom = 15)
map1  

Setting the view

map1  # setView(lat=37.87004, lng=-122.25817, zoom = 15)

Challenge

Create a leaflet map centered on San Francisco with an intial zoom level of 14.

  • You can get the coordinates by right clicking in Google Maps and selecting what’s here

Questions

  • What zoom level shows all of the city without much more?

  • What is the max possible zoom level?

Challenge - Solution

The maximum zoom level is 18

leaflet() %>%  
  addTiles() %>%  
  setView(lat=37.76175, lng=-122.4470, zoom = 12)

Maximum Control

How does this code limit the map?

map1 <- leaflet(options=leafletOptions(minZoom=15, maxZoom=18)) %>%
  addTiles() %>%  
  setView(lat=37.87004, lng=-122.25817, zoom = 16) %>%
  setMaxBounds(-122.2570, 37.866458, -122.2553,37.877167)
map1 

Basemaps

By default, Leaflet uses the OpenStreetMap basemap, which is added with the addTiles() function

leaflet() %>% addTiles() %>% 
           setView(lat=37.870, lng=-122.258, zoom = 15)

addProviderTiles

Use addProviderTiles with the name of the basemap to add a different basemap.

Create a leaflet map with the ESRI World Street Map basemap.

map2 <- leaflet() %>%
        addProviderTiles("Esri.WorldStreetMap") %>% 
        setView(lat=37.870044, lng=-122.258169, zoom = 12)

View it

map2   #Using ESRI WorldStreetMap basemap

Specify a Basemap

Add a different basemap by taking a look at this web page of available basemaps

http://leaflet-extras.github.io/leaflet-providers/preview/

Use the provider name in quotes to access the basemap.

CartoDB Positron Basemap

leaflet() %>% addProviderTiles("CartoDB.Positron") %>% 
    setView(lat=37.870044, lng=-122.258169, zoom = 12)

Getting Help

For more info, read the documentation

?addProviderTiles

Mapping Data

Mapping Data

Map Point Data with Markers

Barrows Hall, longitude=-122.25817, latitude=37.87004

addMarkers

Use addMarkers to add one or more data points to the map.

map3 <- leaflet() %>%
  addTiles() %>%
  addMarkers(lat=37.87004, lng=-122.25817, popup="Barrows Hall")
map3

addMarkers

Markers show location and a little information

Review the Code

map3 <- leaflet() %>%
  addTiles() %>%
  #setView(lat=37.870044, lng=-122.258169, zoom = 17) %>%
  addMarkers(lat=37.87004, lng=-122.25817, popup="Barrows Hall")
map3
  • You don’t need to use setView (commented out).

  • The map will automatically center on the marker data and determine the zoom level.

  • But, you can override this with setView.

  • What does popup= do?

addMarkers Documentation

You can always check the function documentation to see your options!

?addMarkers

Challenge

How would you add a second marker for Cafe Milano?

  • 37.868641, -122.258537

  • Try it?

Two Markers

map3 <- leaflet() %>%
  addTiles() %>%
  addMarkers(lat=37.87004, lng=-122.25817, popup="Barrows Hall") %>%
  addMarkers(lat=37.86850, lng=-122.25830, popup="Cafe Milano")
map3

Two Markers - another way

map3 <- leaflet() %>%
  addTiles() %>%
  addMarkers(lat = c(37.87004,37.86850), 
             lng = c(-122.25817,-122.25830),
             popup = c("Go Bears", "Cafe Milano"))

map3

Questions?

Mapping Data Frames

Bart Stations

Source: Caltrans GIS Data

bart <- read.csv('data/bart.csv', stringsAsFactors = FALSE)  
str(bart)
## 'data.frame':    44 obs. of  6 variables:
##  $ X       : num  -122 -122 -122 -122 -122 ...
##  $ Y       : num  37.9 37.9 37.9 37.8 37.8 ...
##  $ STATION : chr  "NORTH BERKELEY" "DOWNTOWN BERKELEY" "ASHBY" "ROCKRIDGE" ...
##  $ OPERATOR: chr  "BART" "BART" "BART" "BART" ...
##  $ DIST    : int  4 4 4 4 4 4 4 4 4 4 ...
##  $ CO      : chr  "ALA" "ALA" "ALA" "ALA" ...

What column(s) contain the geographic data?

Mapping Bart Stations as Markers

map4 <- leaflet() %>%
         addTiles() %>%   
         addMarkers(lat=bart$Y, lng=bart$X, 
         popup= paste("Station:", bart$STATION))
map4

Mapping Bart Stations as Markers

map4

Geographic Data Files

Point data are often stored in CSV files and loaded into R data frames.

These point data are manipulated like other R data frames.

Leaflet can map these data if the points use geographic coordinates (longitude & latitude).

More complex geographic data are commonly stored in ESRI Shapefiles.

To get the most out of spatial data in R you should use packages specifically for working with spatial data.

Spatial Data in R

Spatial Data in R

We can use the sp and rgdal packages to import, manipulate and map more complex spatial objects.

sp - R classes and methods for spatial data

rgdal - Functions for importing and transforming spatial data

Let’s use these to import data in ESRI Shapefiles

Read in an ESRI Shapefile

BART Lines

Source: Caltrans GIS Data

dir("data/BART_13/")
## [1] "BART_13.dbf"     "BART_13.prj"     "BART_13.sbn"     "BART_13.sbx"    
## [5] "BART_13.shp"     "BART_13.shp.xml" "BART_13.shx"

BART Lines

Use rgdal to read in the data from a Shapefile

library(rgdal)
bart_lines <- readOGR(dsn="data/BART_13",layer="BART_13")
## OGR data source with driver: ESRI Shapefile 
## Source: "data/BART_13", layer: "BART_13"
## with 6 features
## It has 3 fields

BART Lines

Take a look at the data

summary(bart_lines)
## Object of class SpatialLinesDataFrame
## Coordinates:
##          min        max
## x -122.47113 -121.89932
## y   37.55675   38.02386
## Is projected: FALSE 
## proj4string :
## [+proj=longlat +datum=NAD83 +no_defs +ellps=GRS80 +towgs84=0,0,0]
## Data attributes:
##                                     ROUTE     Shape_Leng        LINE  
##  Dublin/Pleasanton to Daly City        :1   Min.   : 3686   Blue  :1  
##  Fremont to Daly City                  :1   1st Qu.:62836   Green :1  
##  Fremont to Richmond                   :1   Median :64169   Orange:1  
##  Millbrae to SFO Shuttle               :1   Mean   :58883   Red   :1  
##  Pittsburg/Bay Point to SFO to Millbrae:1   3rd Qu.:64962   Yellow:1  
##  Richmond to Daly City to Millbrae     :1   Max.   :93658   NA's  :1

BART Lines

Use addPolyLines to add linear features

map4 <- leaflet() %>%
         addTiles() %>%   
         addMarkers(lat=bart$Y, lng=bart$X, 
         popup= paste("Station:", bart$STATION)) %>% 
         
         addPolylines(data=bart_lines, color="black", weight=3)
         
map4

Leaflet can map both data frames & spatial objects!

BART Stations & Lines

map4

BART Service Areas

Let’s add BART Service Area data, a subset from the Metropolitan Transportation Commission (MTC) Transit Service Area data.

dir("data/Transit_Service_Areas_2016")
##  [1] "bart_service_area.dbf"          "bart_service_area.prj"         
##  [3] "bart_service_area.qpj"          "bart_service_area.shp"         
##  [5] "bart_service_area.shx"          "Transit_Service_Areas_2016.cpg"
##  [7] "Transit_Service_Areas_2016.dbf" "Transit_Service_Areas_2016.prj"
##  [9] "Transit_Service_Areas_2016.shp" "Transit_Service_Areas_2016.shx"
## [11] "Transit_Service_Areas_2016.xml"

BART Service Areas

Let’s add BART Service Area data from the Metropolitan Transportation Commission (MTC)

bart_service <- readOGR(dsn="data/Transit_Service_Areas_2016",layer="bart_service_area")
## OGR data source with driver: ESRI Shapefile 
## Source: "data/Transit_Service_Areas_2016", layer: "bart_service_area"
## with 3 features
## It has 7 fields
## Integer64 fields read as strings:  OBJECTID

BART Service Areas

Take a look at the data

summary(bart_service)
## Object of class SpatialPolygonsDataFrame
## Coordinates:
##          min        max
## x -122.47784 -121.77617
## y   37.32445   38.02598
## Is projected: FALSE 
## proj4string :
## [+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0]
## Data attributes:
##  OBJECTID agency_id agency_nam                status   system 
##  13:1     BA:3      BART:3     Existing          :1   Rail:3  
##  14:1                          Planned           :1           
##  15:1                          Under Construction:1           
##                                                               
##                                                               
##                                                               
##    Shape__Are         Shape__Len    
##  Min.   : 9885713   Min.   : 22981  
##  1st Qu.:10027470   1st Qu.: 24130  
##  Median :10169227   Median : 25279  
##  Mean   :35531127   Mean   : 84825  
##  3rd Qu.:48353833   3rd Qu.:115747  
##  Max.   :86538439   Max.   :206215

BART Service Areas

Use addPolygons to add area features

map4 <- leaflet() %>%
         setView(lat=37.857900, lng=-122.245156, zoom = 12) %>% 
         addTiles() %>%   
         addMarkers(lat=bart$Y, lng=bart$X, 
         popup= paste("Station:", bart$STATION)) %>%   
         addPolylines(data=bart_lines, color="black", weight=4) %>%
         
         addPolygons(data=bart_service, color="blue", opacity = 0.6)
         
map4

BART Stations, Lines & Service Areas

map4

CHALLENGE

Redo the previous map and:

  • change the basemap to “CartoDB.Positron”

  • set the default view to center on San Francisco

Challenge Solution

map4 <- leaflet() %>%
         setView(lat=37.76175, lng=-122.4470, zoom = 12) %>%
         addProviderTiles("CartoDB.Positron") %>%   
         addMarkers(lat=bart$Y, lng=bart$X, 
         popup= paste("Station:", bart$STATION)) %>%   
         addPolylines(data=bart_lines, color="black", weight=4) %>%
         addPolygons(data=bart_service, color="blue", opacity = 0.6)

Challenge Solution

map4

Questions?

A note about working with Geospatial Data

All geospatial data are referenced to a coordinate reference system, or CRS.

  • A CRS is also referred to as a projection or map projection

A discussion of CRSs is beyond the scope of this workshop!

  • Please take our Geospatial Data in R workshop series to learn more.

CRSs and R Leaflet

  • Leaflet package expects data to be specified in latitude and longitude coordinates and assumes the using WGS 84 (a.k.a. EPSG:4326) CRS.

  • See the Leaflet for R tutorial for more information.

Saving your maps

Save the Map

Save the map as an HTML file that you can then open in a browser, share with friends, put online.

You will need to have the htmlwidgets package installed and loaded to save to HTML.

#library(htmlwidgets)
saveWidget(map4, file="bartmap.html")

Questions?

Part II: Mapping larger datasets

Mapping SF Property Data

San Francisco Open Data Portal

SF Property Tax Rolls

This data set includes the Office of the Assessor-Recorder’s secured property tax roll spanning from 2015.

We are using this as a proxy for home values.

We are working with a simplified version of the full data set.

Load the CSV file into a data frame

Set your working directory first to the folder where you downloaded the workshop files!

sfhomes <- read.csv('data/sfhomes15.csv', stringsAsFactors = FALSE)  
str(sfhomes)
## 'data.frame':    680 obs. of  11 variables:
##  $ SalesDate   : chr  "2015-08-21" "2015-08-13" "2015-12-29" "2015-07-06" ...
##  $ Address     : chr  "0000 2760 19TH                AV0015" "0000 0560AMISSOURI            ST0000" "0000 0718 LONG BRIDGE         ST1202" "0000 0899 VALENCIA            ST0202" ...
##  $ YrBuilt     : int  1979 2003 2016 2015 1961 1900 2015 NA 1947 1907 ...
##  $ NumBeds     : int  2 2 2 3 3 2 2 0 0 3 ...
##  $ NumBaths    : int  2 2 2 3 3 2 2 0 0 3 ...
##  $ NumUnits    : int  1 1 1 1 1 1 1 1 1 1 ...
##  $ AreaSqFt    : int  1595 1191 1346 1266 1840 1256 1520 536 950 1837 ...
##  $ Neighborhood: chr  "Twin Peaks" "Potrero Hill" "Mission Bay" "Mission" ...
##  $ Value       : int  865000 1402560 2260993 1700000 2309692 2700564 1925000 583768 944180 1750001 ...
##  $ lat         : num  37.7 37.8 37.8 37.8 37.8 ...
##  $ lon         : num  -122 -122 -122 -122 -122 ...

Explore the data

head(sfhomes)
##    SalesDate                              Address YrBuilt NumBeds NumBaths
## 1 2015-08-21 0000 2760 19TH                AV0015    1979       2        2
## 2 2015-08-13 0000 0560AMISSOURI            ST0000    2003       2        2
## 3 2015-12-29 0000 0718 LONG BRIDGE         ST1202    2016       2        2
## 4 2015-07-06 0000 0899 VALENCIA            ST0202    2015       3        3
## 5 2015-06-12 0000 1333 JONES               ST0808    1961       3        3
## 6 2015-04-14 0000 1904 BAKER               ST0000    1900       2        2
##   NumUnits AreaSqFt    Neighborhood   Value      lat       lon
## 1        1     1595      Twin Peaks  865000 37.73601 -122.4741
## 2        1     1191    Potrero Hill 1402560 37.75920 -122.3965
## 3        1     1346     Mission Bay 2260993 37.77181 -122.3942
## 4        1     1266         Mission 1700000 37.75876 -122.4210
## 5        1     1840        Nob Hill 2309692 37.79362 -122.4149
## 6        1     1256 Pacific Heights 2700564 37.78881 -122.4437

Map the data

map4 <- leaflet() %>%
  addTiles() %>%   
  addMarkers(lat=sfhomes$lat, lng=sfhomes$lon, 
            popup= paste("Address:", sfhomes$Address,
                         "<br>", # add line break
                         "Property Value: ", sfhomes$Value))

Display the Map

map4    

Popups Made Easier

We can add to and save the popup code and re-use it instead of typing it over and over again.

popup_content <- paste("<b>Address:</b>", sfhomes$Address,"<br>", 
                       "<b>Property Value</b>: ", sfhomes$Value, "<br>",
                       "<b>Neighborhood:</b> ", sfhomes$Neighborhood, "<br>",
                       "<b>Num Bedrooms: </b>", sfhomes$NumBeds, "<br>",
                       "<b>Num Bathrooms:</b>", sfhomes$NumBaths
                       )
map4 <- leaflet() %>%
          addTiles() %>%   
          addMarkers(lat=sfhomes$lat, lng=sfhomes$lon, 
                     popup= popup_content)

Customizing the Popup

map4

Shorter syntax

Instead of this:

leaflet() %>%  
  addTiles() %>%   
  addMarkers(lat=sfhomes$lat, lng=sfhomes$lon, popup= popup_content)

We can use this syntax:

leaflet(sfhomes) %>%
  addTiles() %>%   
  addMarkers(~lon, ~lat, popup = popup_content)

When the addMarkers function arguments lng= and lat= are not named they must be in the expected order (longitude, latitude)!

Too Many Markers!

Read the addMarker documentation for options to address this.

addMarkers(map, lng = NULL, lat = NULL, layerId = NULL, 
           group = NULL, icon = NULL, popup = NULL, 
           options = markerOptions(), 
           clusterOptions = NULL, clusterId = NULL, 
           data = getMapData(map))

Cluster Option

map4 <- leaflet(sfhomes) %>%
  addTiles() %>%   
  addMarkers(~lon, ~lat, popup= popup_content,
            
            clusterOptions = 1)

Cluster Option

map4  # Explore the Map - hover over a cluster marker, zoom in.

Mapping Points as Circles

addCircleMarker

map4 <- leaflet(sfhomes) %>%
  addTiles() %>%   
  addCircleMarkers(~lon, ~lat, popup = popup_content)

Mapping Points as Circles

addCircleMarker

map4 

addCircleMarkers

addCircleMarkers(map, lng = NULL, lat = NULL, radius = 10, 
    layerId = NULL, group = NULL, stroke = TRUE, color = "#03F", 
    weight = 5, opacity = 0.5, 
    fill = TRUE, fillColor = color, ....)

Customize the circleMarkers

Change color, radius and stroke weight of circle markers

map4 <- leaflet(sfhomes) %>%
  addTiles() %>%   
  addCircleMarkers(~lon, ~lat, popup = popup_content,
             color="white", radius=6, weight=2,   # stroke
             fillColor="red",fillOpacity = 0.75   # fill
             )

Customize the circleMarkers

map4 

Question

Can you cluster circleMarkers?

Recap

Basic leaflet map with a basemap

Mapping point locations with Markers

Mapping linear features with addPolylines

Mapping polygon (or area) features with addPolygons

Clustering lots of point features

Reading in data from CSV and Spatial Data Files

Working with Popups

Data Driven Symbology

Data Driven Symbology

Not just interested in location

Interested in how data values vary by location

Use data values to determine the size and color of symbols.

Mapping Data by Size

We can map points & lines by making their size a function of a data value.

Earthquakes by Magnitude, 1989 - 2019

Fetch Data to map

quake_url <-"http://earthquake.usgs.gov/fdsnws/event/1/query?format=csv&minmagnitude=3.5"

startdate <- "1989-01-01"
enddate   <- "2019-01-31"
#
bayminlat <- 36.433
baymaxlat <- 38.694
bayminlon <- -123.865
baymaxlon <- -120.951

quake_url <- paste0(quake_url, "&starttime=", startdate, "&endtime=", enddate,
                    "&minlatitude=",bayminlat, "&maxlatitude=", baymaxlat, 
                    "&minlongitude=", bayminlon,"&maxlongitude=",baymaxlon)

quakes <- read.csv(quake_url)

#print(quake_url)

Mapping Points by size

Set the radius of the circle to be a function of the magnitude of the earthquake.

quake_map <- leaflet(quakes) %>%

  addProviderTiles("Esri.WorldTopoMap") %>%
  
  addCircleMarkers(~longitude, ~latitude, 
      
       popup=paste0("Magnitude: ", quakes$mag, "<br>Date: ", quakes$time),
        
       fillColor= "Red", color="Red", weight=1, fillOpacity = 0.25,
       
                   
                   radius= 1.75^quakes$mag #exponential
                   
  ) 

Earthquakes by Magnitude

With addCircleMarkers the radii are pixels so they adjust dynamically with the map.

addCircles

With addCircle radii are specified absolutely in meters

quake_map <- leaflet(quakes) %>%

  addProviderTiles("Esri.WorldTopoMap") %>%
  
  addCircles(~longitude, ~latitude,     
       fillColor= "Red", color="black", weight=1, fillOpacity = 0.25,
       
       popup=paste0("Magnitude: ", quakes$mag, "<br>Date: ", quakes$time),
       
        
        radius= 5000 # 5 kilometer neighborhood
        
  ) 

addCircles

Use addCircle when you want to map specific not relative size.

5 KM radius around earthquake epicenters

Layering addCircleMarkers and addCircles

quake_map <- leaflet(quakes) %>%

  addProviderTiles("Esri.WorldTopoMap") %>%
  
  addCircles(~longitude, ~latitude,     
       color="black", fillColor= "Red", weight=1, fillOpacity = 0.25,
        
        radius= 5000 # 5 kilometer neighborhood
        
  ) %>%
  addCircleMarkers(~longitude, ~latitude, 
       popup=paste0("Magnitude: ", quakes$mag, "<br>Date: ", quakes$time),
       color="black", fillColor="black",fillOpacity=1,
       
       radius=4 
       
  )

addCircleMarkers and addCircles

Questions?

Mapping Data by Color

Color

Mapping Data by Color

Data values & Color palettes

Mapping Data by Color

Data Binning & Classification methods

Color Palettes

Qualitative Palettes

emphasize different categories with contrasting colors

display.brewer.all(type="qual") 

display.brewer.pal(7, "Set3" )  # Try a different number of colors

Sequential Palettes

highlight trends in numerical data

display.brewer.all(type="seq")

Diverging Palettes

highlight outliers

display.brewer.all(type="div")

Color Palettes and Data Values

Map Homes by Neighborhood

Let’s map sfhomes by the values in the Neighborhood column.

First, check out the RColorBrewer qualitative color palettes

display.brewer.all(type="qual")

Associate Colors with Data

colorfactor takes as input a color palette and a domain that contains the full range of possible values to be mapped.

colorfactor returns a function specific to that domain that can be used to output a set of color values.

# Create a qualitative color palette
myColor_function <- colorFactor("Paired", sfhomes$Neighborhood) 

Using colorFactor Function

myCategoryColor_function <- colorFactor("Paired", sfhomes$Neighborhood) 

category_map <- leaflet(sfhomes) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(~lon, ~lat,  
             popup= popup_content,
             
             fillColor= ~myCategoryColor_function(Neighborhood),
             
             radius=6, color=NA, weight=2, fillOpacity = 1
             )

Homes by Neighborhood

category_map  # what neighborhood had the most 2015 transactions?

Add a Legend

Add a Legend

myCategoryColor_function <- colorFactor("Paired", sfhomes$Neighborhood)

category_map <- leaflet(sfhomes) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(~lon, ~lat, popup=popup_content,
             
             fillColor= ~myCategoryColor_function(Neighborhood),
             
             radius=6, color=NA, weight=2,fillOpacity = 1
             ) %>%
  
      
      addLegend(title = "Neighborhood", 
                pal =  myCategoryColor_function,
                values = ~Neighborhood, opacity = 1, 
                position="bottomleft")

Add a legend

category_map

Challenge

Recreate the categorical map of SF homes by neigborhood using a different color palette:

display.brewer.all(type="qual")

Be sure to add a legend

Mapping Colors to Numeric Values

Let’s map the homes by property value.

First, check out the sequential color palettes

display.brewer.all(type="seq")

leaflet::colorNumeric

To map data values to a continuous range of colors use colorNumeric.

First, create the color mapping function

myNumericColor_function <- colorNumeric("Reds", sfhomes$Value)

Continuous Color Map

Use the numColor_function to create a continuous color map

myNumericColor_function <- colorNumeric("Reds", sfhomes$Value)

numeric_map <- leaflet(sfhomes) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(~lon, ~lat, popup=popup_content,
            
             fillColor= ~myNumericColor_function(Value),
            
             radius=6, color="grey", weight=1, fillOpacity = 1
             ) %>%
  
      addLegend(title = "Property Values", 
                
                pal =  myNumericColor_function,
                
                values = ~Value, opacity = 1, 
                position="bottomleft")

numeric_map  # continuous color map

Challenge

Recreate the continuous map of SF homes by property using a different color palette:

display.brewer.all(type="seq")

Be sure to add the legend

Quantile Colors

Use colorQuantile to create a color palette based on quantile binning of the data.

First, create the color mapping function

myQuantileColor_function <- colorQuantile("Reds", sfhomes$Value, n=5)

Graduated Color Map

Use the color function to map colors to house values

myQuantileColor_function <- colorQuantile("Reds", sfhomes$Value, n=5)

quantile_map <- leaflet(sfhomes) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(~lon, ~lat, popup=popup_content,
            
            fillColor= ~myQuantileColor_function(Value),
            
             radius=6, color="grey", weight=1,fillOpacity = 1
             ) %>%
                    
            addLegend(title = "Property Values, 2015",
               
               pal =  myQuantileColor_function,
               
               values = ~Value, opacity = 1, 
               position="bottomleft")

quantile_map  # Graduated quantile color map

Customize the Legend

myQuantileColor_function <- colorQuantile("Reds", sfhomes$Value, n=5)

quantile_map2 <- leaflet(sfhomes) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(~lon, ~lat, popup=popup_content,
        
        fillColor= ~myQuantileColor_function(Value),
        
        radius=6, color="grey", weight=1,fillOpacity = 1) %>% 
            
        addLegend(pal = myQuantileColor_function, values = ~Value,
            title = "Property Values, 2015",
            position="bottomleft",
            opacity=1,
            
            labFormat = function(type, cuts, p) {
                  n = length(cuts)
                  cuts = paste0("$", format(cuts[-n], big.mark=","), 
                          " - ", "$",format(cuts[-1], big.mark=","))
            }
            
       )

Graduated Color Map

with custom legend

quantile_map2

Challenge

Recreate the quantile map of SF homes by property using a different color palette. Be sure to add a custom legend.

Select your palette from one of the following from the Viridis package:

Challenge Solution

myQuantileColor_function <- colorQuantile("inferno", sfhomes$Value, n=5, reverse=T)

quantile_map3 <- leaflet(sfhomes) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(~lon, ~lat, popup=popup_content,
        
        fillColor= ~myQuantileColor_function(Value),
        
        radius=6, color="grey", weight=1,fillOpacity = 1) %>% 
            
        addLegend(pal = myQuantileColor_function, values = ~Value,
            title = "Property Values, 2015",
            position="bottomleft",
            opacity=1,
            
            labFormat = function(type, cuts, p) {
                  n = length(cuts)
                  cuts = paste0("$", format(cuts[-n], big.mark=","), 
                          " - ", "$",format(cuts[-1], big.mark=","))
            }
            
       )

Challenge - solution

Inferno Color Palette

colorBin

For more control over customizing colors see the colorBin function which can be used to create color palettes based on different classification methods for binning the data, eg equal interval, natural breaks etc.

?colorBin

Questions

Recap

Recap

Basic Maps

  • addMarkers - Simple Marker Maps
  • addCircleMarkers - Circle Marker Maps

Data Maps

  • addCircles Proportional symbol maps
  • colorFactor - Category Maps
  • colorNumeric - Continuous color maps
  • colorQuantile - Graduated color maps

Choropleth Maps

Choropleth Maps

San Francisco Median Household Income, 2016

Choropleth Maps

Color areas based on data values.

The most common type of data map.

The data values are classified into bins.

Quantile classification is the default.

Each bin gets a unique color from a color palette.

Read in SF Income Data

sf_md_hhi <- readOGR(dsn="data",layer="sf_medhhincome_acs5y_16")
## OGR data source with driver: ESRI Shapefile 
## Source: "data", layer: "sf_medhhincome_acs5y_16"
## with 196 features
## It has 5 fields

Explore the data

summary(sf_md_hhi)
## Object of class SpatialPolygonsDataFrame
## Coordinates:
##          min        max
## x -123.01392 -122.32756
## y   37.69274   37.86334
## Is projected: FALSE 
## proj4string :
## [+proj=longlat +datum=NAD83 +no_defs +ellps=GRS80 +towgs84=0,0,0]
## Data attributes:
##          GEOID    
##  06075010100:  1  
##  06075010200:  1  
##  06075010300:  1  
##  06075010400:  1  
##  06075010500:  1  
##  06075010600:  1  
##  (Other)    :190  
##                                                  NAME           variable  
##  Census Tract 101, San Francisco County, California:  1   B19013_001:196  
##  Census Tract 102, San Francisco County, California:  1                   
##  Census Tract 103, San Francisco County, California:  1                   
##  Census Tract 104, San Francisco County, California:  1                   
##  Census Tract 105, San Francisco County, California:  1                   
##  Census Tract 106, San Francisco County, California:  1                   
##  (Other)                                           :190                   
##     estimate           moe       
##  Min.   : 11971   Min.   :  739  
##  1st Qu.: 65893   1st Qu.: 9744  
##  Median : 89668   Median :15343  
##  Mean   : 91151   Mean   :16211  
##  3rd Qu.:117922   3rd Qu.:20032  
##  Max.   :205865   Max.   :89454  
##  NA's   :2        NA's   :2

Map a SpatialPolygonsDataFrame

Map sf_md_hhi with addPolygons

poly_map <- leaflet() %>%
  addTiles() %>%
  addPolygons(data=sf_md_hhi)

Map a SpatialPolygonsDataFrame

poly_map # using addPolygons to map sf_md_hhi

Customizing the symbology

?addPolygons

addPolygons(map, lng = NULL, lat = NULL, layerId = NULL, group = NULL,
            stroke = TRUE, color = "#03F", weight = 5, opacity = 0.5,
            fill = TRUE, fillColor = color, fillOpacity = 0.2, 
            dashArray = NULL, smoothFactor = 1, noClip = FALSE, 
            popup = NULL, popupOptions = NULL, label = NULL, 
            labelOptions = NULL, options = pathOptions(),
            highlightOptions = NULL, data = getMapData(map))

Customizing the symbology

poly_map <- leaflet() %>%
  setView(lng=-122.448889, lat=37.764645, zoom=12) %>%
  addProviderTiles("CartoDB.Positron") %>%
  
  # Customize the symbology of the polygons
  addPolygons(data=sf_md_hhi, 
              color="grey",  # Outline color
              weight=1,      # Outline thickness
              
              fillColor="Orange", 
              fillOpacity = 0.25)  

SF Census Tracts

poly_map  # color="grey", weight=1, fillColor="Orange", fillOpacity = 0.25

Create a choropleth map

Median Household Income is in the estimate column

Recipe:

  1. Create a color function based on the values of estimate
  2. Map the polygons setting the color to values produced by the color function

Question:

What type of color function?

  • colorFactor, colorNumeric or colorQuantile?

Create the Color Palette Function

First, select the name of the color palette

#display.brewer.all(type="seq") 

Then create the color mapping function

##
myQuantileColor_function <- colorQuantile("YlOrRd", sf_md_hhi$estimate, n=5)

Create the Choropleth map

A choropleth map is a graduated color map of polygon data.

choro_map <- leaflet() %>%
  setView(lng=-122.448889, lat=37.764645, zoom=12) %>%
  addProviderTiles("CartoDB.Positron") %>%
  # 
  
  addPolygons(data=sf_md_hhi, 
              color="white", 
              weight=1, 
              opacity=0.5,
              fillColor=~myQuantileColor_function(estimate), 
              fillOpacity = 0.65,
              popup = paste0("$",sf_md_hhi$estimate))

Census Tracts by Med HH Income

choro_map # choropleth map of median household income by census tract

Add a custom legend

choro_map <- choro_map %>% 
                addLegend(pal = myQuantileColor_function, 
                                     
                   values = sf_md_hhi$estimate,
                   title = "Median HH Income",
                   position="bottomleft",
                   opacity=1,
                   
                   labFormat = function(type, cuts, p) {
                     n = length(cuts)
                     cuts = paste0("$", format(cuts[-n], big.mark=","), 
                             " - ", "$",format(cuts[-1], big.mark=","))
                   }
)

Med HH Income with Legend

choro_map  # choropleth map with custom legend

Map Overlays

You can add multiple data layers to a leaflet map.

Let’s add the sfhomes to the map

cheap_homes <- sfhomes[sfhomes$Value < 1000000,]

choro_map2 <- choro_map %>%
      # sfhomes under 1 million as points
      addCircleMarkers(data=cheap_homes, 
                       popup=paste0("$",cheap_homes$Value),
                       color="black",weight=1, radius=6, 
                       fillColor="white", fillOpacity = 0.75)

Map Overlays

choro_map2 # SF HH income and cheap homes

Questions?

Layer Controls

We can add a layer display control with addLayersControl().

Layer Controls

To implemente a Layer Control you need to

  1. assign a group to each map layer that you want to display in the control

  2. add the layer control and reference your groups

?addLayersControl

Groups & addLayersControl

choro_map3 <- leaflet() %>% setView(lng=-122.448889, lat=37.764645, zoom=12) %>%
          addProviderTiles("CartoDB.Positron") %>%

          addPolygons(data=sf_md_hhi, color="white", weight=1, opacity=0.5,
              fillColor=~myQuantileColor_function(estimate), fillOpacity = 0.65,
              popup = paste0("$",sf_md_hhi$estimate),
              
              group="Median HH Income"
              
          ) %>%
          addCircleMarkers(data=cheap_homes, popup=paste0("$",cheap_homes$Value),
              color="black",weight=1, radius=6, 
              fillColor="white", fillOpacity = 0.75,
              
              group="Property Values"
              
          ) %>%
          
          addLayersControl(
            overlayGroups = c("Property Values","Median HH Income"),
            options = layersControlOptions(collapsed = FALSE)
          
        )

Map with Layer Controls

choro_map3

Add Basemap(s) to Layer Control

choro_map3 <- leaflet() %>% setView(lng=-122.448889, lat=37.764645, zoom=12) %>%
          addProviderTiles("CartoDB.Positron", group="Simple Basemap") %>%
          addProviderTiles("Esri.WorldStreetMap", group="Streets Basemap")  %>%
          addTiles("", group="No Basemap") %>%  
  
          addPolygons(data=sf_md_hhi, color="white", weight=1, opacity=0.5,
              fillColor=~myQuantileColor_function(estimate), fillOpacity = 0.65,
              popup = paste0("$",sf_md_hhi$estimate),
              group="Median HH Income"
          ) %>%
          addCircleMarkers(data=cheap_homes, popup=paste0("$",cheap_homes$Value),
              color="black",weight=1, radius=6, 
              fillColor="white", fillOpacity = 0.75,
              group="Property Values"
          ) %>%
          addLayersControl(
            baseGroups = c("Simple Basemap", "Streets Basemap", "No Basemap"),
            overlayGroups = c("Property Values","Median HH Income"),
            options = layersControlOptions(collapsed = FALSE))

Layer Controls

choro_map3

Question

What parameter in addLayersControl allows you to set the layer control to be expanded or closed by default?

Questions?

Sharing

Sharing your web map

Web maps can be used to explore your data or to share your data.

One common way to share a web map is to put it on a website.

The RPubs platform provides an easy way to do this in R.

  • Let’s take a look at that web site.

Publishing Leaflet Maps on RPubs

  1. Create an account on RPubs.

  2. Enter the code to create your map in RStudio.

  3. In the Viewer window, click on the Publish icon.

Saving your map

Another way to share your map is to save it to a file.

You can then email it, host it on your own web server or host it on github, etc.

#library(htmlwidgets)  

saveWidget(choro_map2, file="testmap.html")

Open your file to by double-clicking on it in the Mac Finder or Windows File Explorer.

Doing more with your leaflet maps

Three great options:

  • Flexdashboard

  • Shiny

  • Crosstalk

Flexdashboard

Flexdashboard is a tool for creating interactive dashboards with R Markdown.

Flexdashboard Demo

  • Let’s take a look at the code inside that demo app

Shiny

A (complex) framework for creating interactive web apps based on R code.

Shiny Demo

Shiny User Gallery

Shiny online tutorial

http://www.shinyapps.io - A freemium web platform for hosting your shiny apps.

Crosstalk

An R package for creating interactive web documents without needing a server.

https://rstudio.github.io/crosstalk

  • More functionality than Flexdashboard, less than Shiny

  • Check out this awesome crosstalk demo & sourcecode here

Questions?

Next Steps

Getting more Practice - work through this great tutorial:

Leaflet for R Tutorial

Thanks

To you!

and to Josh Pepper who did an earlier workshop on which these materials are loosely based.

Extras

Add A Custom Map Image

Add A Custom Map Image

You can add an online, georectified scanned map to leaflet.

There are many of these online at the New York Public Library or MapWarper, and other websites.

Let’s add this map of Berkeley in 1880

Berkeley, 1880

Here we are combining addTiles and addProviderTiles

mapurl <- "https://mapwarper.net/maps/tile/25477/{z}/{x}/{y}.png"

map2 <- leaflet() %>%
  addProviderTiles("CartoDB.Positron") %>%
  addTiles(mapurl) %>%  # custom map image
  setView(lat=37.870044, lng=-122.258169, zoom = 13)

Berkeley, 1880

map2  # Map of Berkeley, 1880 overlaid on the CartoDB basemap

Questions?