+ - 0:00:00
Notes for current slide
Notes for next slide

Geospatial Visualization using R

Part 7: Interactive Maps

Bhaskar V. Karambelkar

2017/07/04

1 / 38

ggiraph for simple effects

  • Use geom_*_interactive with hover, tooltip, onClick aesthetics, plot with ggiraph.
library(ggplot2); suppressPackageStartupMessages(library(ggiraph))
africa.spdf <- methods::as(africa, 'Spatial')
africa.spdf@data$id <- row.names(africa.spdf@data)
africa.tidy <- broom::tidy(africa.spdf)
africa.tidy <- dplyr::left_join(africa.tidy, africa.spdf@data, by='id')
g <- ggplot(africa.tidy) +
geom_polygon_interactive(
color='black',
aes(long, lat, group=group, fill=internet.usage.2015,
tooltip=sprintf("%s<br/>%s",iso_a3,internet.usage.2015))) +
ggthemes::theme_map() +
colormap::scale_fill_colormap(
colormap=colormap::colormaps$copper, reverse = T) +
labs(title='Internet Usage in Africa in 2015', subtitle='As Percent of Population',
caption='Source: World Bank Open Data.')
ggiraph(code=print(g))
2 / 38

ggiraph plot

3 / 38

plotly

  • htmlwidgets based wrapper for plot.ly JavaScript library.

  • Extremely easy and yet powerful. Commercial use requires a paid version.

  • Plays nicely with ggplot2

library(plotly)
usa <- albersusa::usa_sf("laea")
usd <- crosstalk::SharedData$new(usa)
p <- ggplot(usd) + geom_sf(aes(fill = pop_2010))
ggplotly(p) %>%
highlight(
"plotly_hover",
selected = attrs_selected(line = list(color = "black"))
)
4 / 38

plotly plot

## Setting the `off` event (i.e., 'plotly_doubleclick') to match the `on` event (i.e., 'plotly_hover'). You can change this default via the `highlight()` function.
5 / 38

rBokeh

  • Port of Python's bokeh library.

  • Has its own syntax.

library(maps)
data(world.cities)
library(rbokeh)
caps <- dplyr::filter(world.cities, capital == 1)
caps$population <- prettyNum(caps$pop, big.mark = ",")
suppressWarnings(figure(width = 800, height = 450, padding_factor = 0) %>%
ly_map("world", col = "gray") %>%
ly_points(long, lat, data = caps, size = 5,
hover = c(name, country.etc, population)))
6 / 38

rBokeh plot

7 / 38

highcharter

  • Wraps highcharts, a very popular JavaScript library.
download_map_data <- memoise::memoise(highcharter::download_map_data)
get_data_from_map <- memoise::memoise(highcharter::get_data_from_map)
hcmap <- memoise::memoise(highcharter::hcmap)
mapdata <- get_data_from_map(download_map_data("countries/au/au-all"))
data_fake <- mapdata %>%
dplyr::select(code = `hc-a2`) %>%
dplyr::mutate(value = 1e5 * abs(rt(nrow(.), df = 10)))
hcmap("countries/au/au-all", data = data_fake, value = "value",
joinBy = c("hc-a2", "code"), name = "Fake data",
dataLabels = list(enabled = TRUE, format = '{point.name}'),
borderColor = "#FAFAFA", borderWidth = 0.1,
tooltip = list(valueDecimals = 2, valuePrefix = "$", valueSuffix = " AUD")) %>%
highcharter::hc_title(text="Economy Down Under")
8 / 38

highcharter plot

9 / 38

leaflet

  • Mother of all interactive mappings!

  • Extremely powerful and Extremely flexible.

  • Current State

    • leaflet: Developed and actively maintained by RStudio.

    • leaflet.extras, leaflet.esri: Developed and maintained by yours truly. Provides additional plotting options using leaflet plugins.

    • mapview, mapedit: Developed and maintained by the good guys at r-spatial. Allow interactive exploratory spatial analysis.

  • There is another unrelated project leafletR CRAN Page, but really don't know much about it.

10 / 38

leaflet One Page Summary

leaflet(data) | leafletProxy() %>%
setView(lat, lon, zoom) # Initial View OR
fitBounds(lat_se, lon_se, latnw, lon_nw) # Initial Bounds
setMaxBounds(lat_se, lon_se, latnw, lon_nw) # Max Bounds
addTiles() | addProviderTiles() | addWMSTiles() #Tiles
addMarkers() | addCircleMarkers() |
addAwesomeMarkers() | addLabelOnlyMarkers() # Markers
addPolylines() | addCircles() |
addRectangles() | addPolygons() # Shapes
addRasterImage(image) # Raster Data
addLegend() | addLayersControl() | addControl() # Controls
  • A Map is built by piping (%>%) several add* methods.

  • leaflet()/addXXX() methods take an optional options argument for customization.

11 / 38

leaflet Base Maps

leaflet() %>%
setView(lat = 50.85045, lng = 4.34878, zoom=13) %>%
addTiles(group="OSM") %>%
addProviderTiles(providers$CartoDB.DarkMatter, group="Dark") %>%
addProviderTiles(providers$CartoDB.Positron, group="Light") %>%
addLayersControl(baseGroups=c('OSM', 'Dark','Light'))
12 / 38

leaflet Base Maps

13 / 38

leaflet Markers

  • At a glance
leaflet(data) %>%
addMarkers(
lat = ~latitude, lon = ~longitude,
options = markerOptions(),
label=~label, labelOptions = labelOptions(),
popup=~popup, popupOptions = popupOptions(),
clusterOptions = clusterOptions(),
group = 'Group-A')
# Similarly
addCircleMarkers() # Fixed scale Circles
addAwesomeMarkers() # More choices for icons
addLabelOnlyMarkers() # No icon
14 / 38

leaflet Markers

quakes.df <- quakes %>% dplyr::mutate(
mag.level = cut(mag,c(3.5,4.5,5.5,6.5),
labels = c('> 3.5 & <=4.5', '>4.5 & <=5.5', '>5.5 & <=6.5'))) %>%
split(.$mag.level)
l <- leaflet() %>%
addProviderTiles(providers$Esri.OceanBasemap)
names(quakes.df) %>%
purrr::walk( function(df) {
l <<- l %>%
addMarkers(data=quakes.df[[df]], lng=~long, lat=~lat,
label=~as.character(mag), popup=~as.character(mag),
group = df,
clusterOptions = markerClusterOptions())
})
l <- l %>%
addLayersControl(
overlayGroups = names(quakes.df),
options = layersControlOptions(collapsed = FALSE)) %>%
addMiniMap(tiles = providers$Esri.OceanBasemap, width = 120, height=80)
15 / 38

leaflet Markers

16 / 38

leaflet Shapes

  • At a Glance
leaflet(data) %>%
addPolygons(
label=~label, labelOptions = labelOptions(),
popup=~popup, popupOptions = popupOptions(),
# Shape Options
options = pathOptions(),
weight = 1, opacity=0.8, color = "#000000",
fillColor="#ff0000", fillOpacity=0.7,
# Highlighting on mouse-over
highlightOptions = highlightOptions(
color='#00ff00', weight = 2,
opacity = 1, fillOpacity = 1,
bringToFront = TRUE, sendToBack = TRUE),
group = 'Group-A')
#Similarly
addCircles()
addPolylines()
addRectangles()
17 / 38

Shapes Example

# spdf is a sp::SpatialPolygonsDataFrame
qpal <- colorQuantile(rev(viridis::viridis(5)),
spdf$POPDENSITY, n=5)
l <- leaflet(spdf, options =
leafletOptions(attributionControl = FALSE, minzoom=1.5)) %>%
addPolygons(
label=~stringr::str_c(
NAME, ' ',
formatC(POPDENSITY, big.mark = ',', format='d')),
labelOptions= labelOptions(direction = 'auto'),
weight=1,color='#333333', opacity=1,
fillColor = ~qpal(POPDENSITY), fillOpacity = 1,
highlightOptions = highlightOptions(
color='#000000', weight = 2,
bringToFront = TRUE, sendToBack = TRUE)
) %>%
addLegend(
"topright", pal = qpal, values = ~POPDENSITY,
title = htmltools::HTML("Population Density<br/>(2005)"),
opacity = 1 )
18 / 38

Shapes Example

  • Performance Tip: Use rmapshaper::ms_simplify with impunity!
19 / 38

leaflet Other Misc. Stuff

  • addRasterImage for adding raster image data. Example.

  • addMiniMap for a small map inside the main map at a zoom offset. Example.

  • addMeasure to measure distances/area. Example.

  • addGraticule adds a graticule. Example.

  • addEasyButton/addEasyButtonBar for customized buttons Example.

  • addControl for custom Control element.

  • addScaleBar to display a scale.

20 / 38

leaflet Projections Support

  • ALL MAPS OF EARTH ARE WRONG! Obligatory XKCD ref.

  • By Default leaflet ...

    • uses EPSG:3857 (a.k.a. Spherical Mercator) projection to display data/tiles.

    • expects tiles in EPSG:3857.

    • expects input vector/raster data in lat/lon EPSG:4326 and internally converts them to EPSG:3857 before plotting.

  • Which means ...

    • You can't use tile services which provide tiles in non-spherical-Mercator projections.

    • You need to convert any vector/raster data in any non-epsg:4326 to epsg:4326 before adding to the leaflet map.

21 / 38

leaflet + Proj4Leaflet

  • Enter Proj4Leaflet a leaflet plugin allowing use of proj4js to display map in non-spherical-Mercator projection.

  • Basic use

leaflet(options =
leafletOptions(crs = leafletCRS()))
  • So now you can display data/tiles in a non-spherical-Mercator projection.

  • But you still need to specify vector/raster data in EPSG:4326 (lat/lon) which will get internally converted to the custom projection specified.

  • Another caveat: You can have only one projection at a time which is set during initialization. To change projection you need to destroy and rebuild the map.

22 / 38

leaflet Projections Example 1.

crs.molvidde <- leafletCRS(
crsClass="L.Proj.CRS", code='ESRI:53009',
proj4def= '+proj=moll +lon_0=0 +x_0=0 +y_0=0 +a=6371000 +b=6371000 +units=m +no_defs',
resolutions = c(65536, 32768, 16384, 8192, 4096, 2048))
l <- leaflet(
spdf,
options = leafletOptions(
maxZoom = 5, crs= crs.molvidde, attributionControl = FALSE)) %>%
addGraticule(style= list(color= '#999', weight= 0.5, opacity= 1)) %>%
addGraticule(sphere = TRUE,
style= list(color= '#777', weight= 1, opacity= 0.25)) %>%
addPolygons(
label=~stringr::str_c(
NAME, ' ', formatC(POPDENSITY, big.mark = ',', format='d')),
labelOptions= labelOptions(direction = 'auto'),
weight=1,color='#ffffff', opacity=1,
fillColor = ~qpal(POPDENSITY), fillOpacity = 1,
highlightOptions = highlightOptions(
color='#000000', weight = 2,
bringToFront = TRUE, sendToBack = TRUE))
23 / 38

Projections Example 1.

24 / 38

Projections Example 2.

spdf <- rmapshaper::ms_simplify(albersusa::usa_composite())
pal <- colorNumeric(palette = "Blues", domain = spdf@data$pop_2014)
crs.laea <- leafletCRS(
crsClass="L.Proj.CRS", code='EPSG:2163',
proj4def='+proj=laea +lat_0=45 +lon_0=-100 +x_0=0 +y_0=0 +a=6370997 +b=6370997 +units=m +no_defs',
resolutions = c(65536, 32768, 16384, 8192, 4096, 2048,1024, 512, 256, 128))
l <- leaflet(
options= leafletOptions(
worldCopyJump = FALSE, crs=crs.laea, attributionControl = FALSE)) %>%
addPolygons(
data=spdf, label=~stringr::str_c(
name, ' ', formatC(pop_2014, big.mark = ',', format='d')),
labelOptions= labelOptions(direction = 'auto'),
weight = 1, color = "#000000",
fillColor=~pal(pop_2014), fillOpacity=0.7,
highlightOptions = highlightOptions(
color='#ff0000', opacity = 1, weight = 2, fillOpacity = 1,
bringToFront = TRUE, sendToBack = TRUE))
25 / 38

Projections Example 2.

26 / 38

leaflet + tilegramsR

l <- leaflet(
options=leafletOptions(
crs = leafletCRS("L.CRS.Simple"),
minZoom = -2, maxZoom = -2,
dragging = FALSE, zoomControl = FALSE, attributionControl = FALSE)) %>%
addPolygons(
data=FiveThirtyEightElectoralCollege,
weight=1,color='#000000', fillOpacity = 0.5, opacity=0.2,
fillColor= ~factpal(state)) %>%
addPolygons(
data=FiveThirtyEightElectoralCollege.states, group = 'states',
weight=2,color='#000000',
fill = T, opacity = 1, fillOpacity = 0,
highlightOptions = highlightOptions(weight = 4)) %>%
addLabelOnlyMarkers(
data=FiveThirtyEightElectoralCollege.centers,
label = ~as.character(state),
labelOptions = labelOptions(
noHide = 'T', textOnly = T,
offset=c(-8,-20), textsize = '12px'))
27 / 38

Projections Example 3

28 / 38

leaflet.extras and leaflet.esri

  • Add-on packages for leaflet. Developed by yours truly to integrate the plethora of leaflet plugins available.

  • Published to CRAN just before UserR! 2017.

  • Actively being developed and maintained. Contributions welcome.

  • leaflet package will be stable and leaflet.extras will be very dynamic.

29 / 38

leaflet.extras

  • Add/Modify/delete/style markers/shapes using Leaflet.Draw.

  • Add GeoJSON, TopoJSON, KML, GPX, CSV files directly.

    • Customizable Markers and Shapes
    • Choropleths from polygon data w/ auto legends and bi-directional highlighting
    • Popup showing properties in a tables
  • Create Heatmap from point data.

  • Search Markers. Geo-locate using OSM Nominatum API.

  • Pulsating and Weather icons for markers.

  • Tiles Caching, GPS, and many more!

30 / 38

TopoJSON Example

library(leaflet.extras)
fName <- system.file('extdata','crimes_by_district.topojson', package='user2017.geodataviz')
l <- leaflet() %>%
addBootstrapDependency() %>%
setView(-75.14, 40, zoom = 11) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addGeoJSONChoropleth(
readr::read_file(fName), valueProperty ='incidents',
scale = 'OrRd', mode='q', steps = 5, padding = c(0.2,0),
popupProperty = propstoHTMLTable(
props = c('dist_numc', 'location', 'incidents', '_feature_id_string'),
table.attrs = list(class='table table-striped table-bordered'),drop.na = T),
labelProperty = JS('function(feature){return "WARD: " + feature.properties.dist_numc;}'),
color='#ffffff', weight=1, fillOpacity = 0.7,
highlightOptions = highlightOptions(
fillOpacity=1, weight=2, opacity=1, color='#000000',
bringToFront=TRUE, sendToBack = TRUE),
legendOptions = legendOptions(title='Crimes', position='topright'))
31 / 38

TopoJSON Example

32 / 38

crosstalk Inter-widget communication

  • Pure JavaScript solution works anywhere a normal htmlwidget will work.1

  • Works on the concept of shared data between widgets.

library(crosstalk)
library(leaflet)
library(DT)
# Wrap data frame in SharedData
sd <- SharedData$new(quakes[sample(nrow(quakes), 100),])
# Create a filter input
filter_slider("mag", "Magnitude", sd, column=~mag, step=0.1, width=250)
# Use SharedData like a dataframe with Crosstalk-enabled widgets
bscols(
leaflet(sd) %>% addTiles() %>% addMarkers(),
datatable(sd, extensions="Scroller", style="bootstrap", class="compact", width="100%",
options=list(deferRender=TRUE, scrollY=300, scroller=TRUE))
)

1: Documentation: https://rstudio.github.io/crosstalk/using.html

33 / 38

Shiny!

  • Shiny allows you to build visualizations dynamically, be they static or interactive.

  • You need a Shiny server to run Shiny apps. Free/Commercial versions available from RStudio.

  • Static visualizations need to destroyed and rebuilt every time the input data for the viz changes.

  • Interactive visualizations (i.e. htmlwidgets) however can support manipulating existing viz in response to shiny events.

  • Plotly, leaflet etc. support changing an existing map.

  • manipulateWidget package allows building htmlwidgets which can respond to input events.

34 / 38

leaflet + shiny

  • Use leafletProxy() to update already existing map.

  • Use clear* methods to remove stuff already on a map.

  • leaflet package traps many leaflet events and makes then available as shiny events.

  • Use observeEvent(input$<MAP_ID>_<EVENT_NAME>) to act on these events.

35 / 38

leaflet + shiny

library(shiny); library(leaflet)
r_colors <- rgb(t(col2rgb(colors()) / 255))
names(r_colors) <- colors()
ui <- fluidPage(
leafletOutput("mymap"),
p(),
actionButton("recalc", "New points")
)
server <- function(input, output, session) {
points <- eventReactive(input$recalc, {
cbind(rnorm(40) * 2 + 13, rnorm(40) + 48)
}, ignoreNULL = FALSE)
output$mymap <- renderLeaflet({
leaflet() %>%
addProviderTiles(providers$Stamen.TonerLite, options = providerTileOptions(noWrap = TRUE)) %>%
addMarkers(data = points())
})
}
shinyApp(ui, server)
36 / 38

Part 7: The End!

Thus marks the end of 'Geospatial visualization in R' tutorial by Bhaskar V. Karambelkar, the first of his name!

Wait there's more.

37 / 38

Thanks!

  • So many people to thank....

    • Bob Rudis: Mentor and teacher.
    • Joe Cheng: RStudio CTO and leaflet author.
    • Tim Appelhans: mapview author and my virtual GIS guru.
    • Kent Russels: mapedit and many more widgets author and collaborator for leaflet and leaflet.extras
    • Michale Sumner: Author of many R GIS packages and helped me a lot understanding GIS.
    • Kyle Walker: Author of tigris/tidycensus, and super helpful guy.
    • Edzer Pebesma: R GIS super guru and author of sf and many many R GIS pacakges.
    • All package owners of the R GIS ecosystem.
38 / 38

ggiraph for simple effects

  • Use geom_*_interactive with hover, tooltip, onClick aesthetics, plot with ggiraph.
library(ggplot2); suppressPackageStartupMessages(library(ggiraph))
africa.spdf <- methods::as(africa, 'Spatial')
africa.spdf@data$id <- row.names(africa.spdf@data)
africa.tidy <- broom::tidy(africa.spdf)
africa.tidy <- dplyr::left_join(africa.tidy, africa.spdf@data, by='id')
g <- ggplot(africa.tidy) +
geom_polygon_interactive(
color='black',
aes(long, lat, group=group, fill=internet.usage.2015,
tooltip=sprintf("%s<br/>%s",iso_a3,internet.usage.2015))) +
ggthemes::theme_map() +
colormap::scale_fill_colormap(
colormap=colormap::colormaps$copper, reverse = T) +
labs(title='Internet Usage in Africa in 2015', subtitle='As Percent of Population',
caption='Source: World Bank Open Data.')
ggiraph(code=print(g))
2 / 38
Paused

Help

Keyboard shortcuts

, , Pg Up, k Go to previous slide
, , Pg Dn, Space, j Go to next slide
Home Go to first slide
End Go to last slide
Number + Return Go to specific slide
b / m / f Toggle blackout / mirrored / fullscreen mode
c Clone slideshow
p Toggle presenter mode
t Restart the presentation timer
?, h Toggle this help
Esc Back to slideshow