Xiaofan Liang Created in 2024; Updated in 2025
By the end of this practical lab you will be able to:
leaflet
leaflet
mapCHEATSHEETS! You are encouraged to download RStudio Cheatsheets. You can find them on RStudio website here, or a comprehensive list (more than what has been listed) here. For example, here you can find base R syntax, dplyr, ggplot2, sf, leaflet.
Functions | Package | Tasks |
---|---|---|
leaflet() |
leaflet | Initializes a Leaflet map object. |
addTiles() |
leaflet | Adds a default OpenStreetMap (OSM) basemap to the Leaflet map. |
addCircleMarkers() |
leaflet | Adds circle markers to the map, useful for representing point data. |
addProviderTiles() |
leaflet | Adds a tile layer from various providers (e.g., CartoDB, Stamen, etc.). |
setView() |
leaflet | Sets the initial map view by defining the latitude, longitude, and zoom level. |
addLegend() |
leaflet | Adds a legend to the map for better visualization of data categories. |
leafletProxy() |
leaflet | Modifies an existing Leaflet map without redrawing it completely, improving performance. |
clearMarkers() |
leaflet | Removes all existing markers from the map before adding new ones. |
clearControls() |
leaflet | Clears map controls such as legends and overlays. |
fluidPage() |
shiny | Defines a fluid and responsive UI layout for the Shiny app. |
titlePanel() |
shiny | Adds a title to the Shiny app interface. |
absolutePanel() |
shiny | Creates a fixed-position panel for UI components in Shiny. |
selectInput() |
shiny | Creates a dropdown menu for user selection in the Shiny UI. |
leafletOutput() |
shiny | Creates a UI placeholder for rendering a Leaflet map in a Shiny app. |
renderLeaflet() |
shiny | Generates and updates the Leaflet map dynamically in the Shiny app. |
reactive() |
shiny | Creates a reactive expression that updates based on user input. |
observe() |
shiny | Monitors reactive expressions and executes actions when input values change. |
shinyApp() |
shiny | Runs the Shiny app by combining the UI and server components. |
rsconnect::setAccountInfo() |
rsconnect | Sets authentication details for deploying a Shiny app on shinyapps.io. |
rsconnect::deployApp() |
rsconnect | Deploys the Shiny app to the shinyapps.io server. |
rsconnect::showLogs() |
rsconnect | Displays logs for debugging deployment issues on shinyapps.io. |
For Python users, you may reference
Folium
for visualization with leaflet capacity. You can
also explore leafmap
, a Python package for geospatial
analysis and interactive mapping in a Jupyter environment.
leafmap
(link to documentation here) is developed by Prof. Qiusheng Wu,
who is well-known for sharing open-source tutorials and developing
packages for geospatial analysis and visualization in Python (see Prof. Wu’s Youtube
Channel).
The other alternative to leaflet
R package is to use mapgl
,
which makes the latest versions of Mapbox GL JS and MapLibre GL JS
available to R users. The package interface is designed to make the
powerful capabilities of both libraries available in R mapping projects.
It allows you to also use mapgl
with shiny
and
build story maps. This package is released in Jan, 2025, so could be a
little unstable and need more time for specific tutorials. As of now, we
would still go with leaflet
as it has a longer history.
For this lab, we are going to build a little web dashboard that shows the OSM amenity data in Ann Arbor
The new package in this lab is leaflet
,
shiny
, and `rsconnect``.
leaflet
R package allows you to interactive with Leaflet
JS, a javascript-based language for web map visualization. You can use
leaflet in R, Python, or its original form in Javascript.
shiny
allows you to design data-driven interactive UI
(User Interface) component (e.g., update web visualization based on a
data filter).
rsconnect
allows you to deploy to the web map with a
limited free tier service at shinyapp.io
.
#replace my path with yours to the Lab 5 folder
# setwd("/Users/xfliang/University of Michigan Dropbox/Xiaofan Liang/UM_Teaching/URP535_Urban_Informatics/W25/Lab/Lab10/")
setwd("/Users/susancheng/Desktop/Labs/Lab10")
#install.packages('tidyverse')
library(tidyverse)
#install.packages('tmap')
library(tmap)
#install.packages('sf')
library(sf)
#install.packages('tmap')
library(osmdata)
#install.packages('tmap')
library(osmdata)
#install.packages('leaflet')
library(leaflet) # for visualizing leaflet maps
#install.packages("shiny")
library(shiny) # for writing a shiny app
# install.packages("rsconnect")
library(rsconnect)# for deploying a shiny app
To find out all OSM features for the key amenity, you can check out OSM wiki.
# Get all food-related amenities in Ann Arbor
q <- opq(bbox = getbb("Ann Arbor, US")) %>%
add_osm_features(features = c(
"amenity" = "restaurant",
"amenity" = "bar",
"amenity" = "biergarden",
"amenity" = "fast_food",
"amenity" = "food_court",
"amenity" = "cafe",
"amenity" = "pub",
"amenity" = "ice_cream"
)) %>%
osmdata_sf()
# only take point geometry and drop entries that do not have values in name and amenity category
amenity_point <- q$osm_points %>%
select(osm_id, name, amenity, geometry) %>%
drop_na(name, amenity)
head(amenity_point)
## Simple feature collection with 6 features and 3 fields
## Geometry type: POINT
## Dimension: XY
## Bounding box: xmin: -83.74839 ymin: 42.25572 xmax: -83.68812 ymax: 42.28785
## Geodetic CRS: WGS 84
## osm_id name amenity geometry
## 305088494 305088494 Casey's Tavern pub POINT (-83.74434 42.28785)
## 541900696 541900696 Starbucks cafe POINT (-83.68812 42.25572)
## 560917683 560917683 Good Time Charley's bar POINT (-83.73485 42.27482)
## 695043656 695043656 Real Seafood Company restaurant POINT (-83.74839 42.27837)
## 748964467 748964467 Pita Kabob Grill restaurant POINT (-83.74112 42.27797)
## 749007070 749007070 Ashley's Ann Arbor pub POINT (-83.74095 42.2781)
Let’s create the simplest leaflet map first. The basic map component
includes data for mapping, a base map tile (the default is the
OpenStreetMap tile), and how you want to visualize the data. In
leaflet
, the points are called
CircleMarkers.
leaflet(data = amenity_point) %>%
# Adds a default OpenStreetMap basemap.
addTiles() %>%
# Adds interactive markers to the map.
addCircleMarkers()
This is an equivalent web map you can create using
tmap v4
. With this simple demonstration, the result and
complexity of code looks pretty similar.
tmap_mode('view')
## ℹ tmap mode set to "view".
tm_shape(amenity_point) +
# change the default tmap basemap to OpenStreetMap to be consistent for comparison
tm_basemap("OpenStreetMap.Mapnik") +
tm_symbols(fill='blue', fill_alpha = 0.5)
The major difference between tmap
and
leaflet
map is that leaflet
is built
for interactive web map, so excels at interactivity. Here is a
comparison table for the two:
leaflet
strength:
leaflet
weakness:
tmap v4
strength:
tmap_mode("plot")
) or interactive maps
(tmap_mode("view")
).tmap v4
weakness:
We would like to add aesthetics and interactivity to the leaflet map, by changing to a grey color base map, varying the color of the amenity based on amenity types, adding pop up to show the amenity name, and adding a legend.
Both tmap
and leaflet
accepts basemaps in
the names listed here.
This link helps you see what the basemap looks like by toggling to
different basemap names and see the effects. An example of changing
basemap for tmap
is shown in the section above. This
tutorial provides more details on how to call various basemaps with
leaflet
.
# Checking how many unique values are needed to assign color by the amenity type
amenity_type <- unique(amenity_point$amenity)
amenity_type
## [1] "pub" "cafe" "bar" "restaurant" "fast_food"
## [6] "ice_cream"
amenity_colors <- c(
"pub" = "red",
"cafe" = "blue",
"bar" = "darkgreen",
"restaurant" = "orange",
"fast_food" = "brown",
"ice_cream" = "purple"
)
# Initializes the Leaflet map using the amenity_point dataset.
leaflet(data = amenity_point) %>%
# Use the CartoDB.Positron basemap instead of OSM basemap
addProviderTiles(providers$CartoDB.Positron) %>%
# Adds interactive markers to the map.
addCircleMarkers(
# Extracts longitude values from the sf geometry column.
lng = st_coordinates(amenity_point)[,1],
# Extracts latitude values from the sf geometry column.
lat = st_coordinates(amenity_point)[,2],
# Displays a popup with amenity details when clicked.
popup = ~paste0("<b>", name, "</b><br>", amenity),
# Defines marker size.
radius = 3,
# Vectorize color mapping
color = ~unname(amenity_colors[amenity]),
# Make markers slightly transparent for better visibility
fillOpacity = 0.8,
) %>%
addLegend(
# Position the legend at the bottom right of the map
"bottomright",
# Use the predefined colors assigned to each amenity type
colors = amenity_colors,
# Display corresponding labels (amenity names)
labels = names(amenity_colors),
# Add a title to the legend
title = "Amenity Type",
# Ensure the legend is fully visible
opacity = 1
) %>%
# Set the initial map view centered on Ann Arbor
setView(lng = -83.7430, lat = 42.2808, zoom = 12)
Try zooming in and out of the map and click on the circles / points to see the pop up!
If you want to see more examples of using leaflet
for
mapping, such as Choropleths, Lines and Shapes, or even with Raster
Images, please go to official
pacakge documentation and click on Articles to see a series of
tutorials.
The output in the previous section is what we called a
“static web map”. This is different from the
“static map” like what is produced by tmap
in the plot mode.
In the “static web map”, you can still interact with the map components, but you cannot give input to the map on the interface (e.g., filter to see only amenities that are restaurant) and expect the map to respond dynamically. If you purpose is simply to display data and allow users to SEE what the data are like (e.g., with pop up), then you DON’T NEED an interactive web map. The code above is sufficient for your purpose.
At this point, you can publish this map in RPub like we have done
with tmap
in the last lab and receive a RPub link.
The other option is to export the map in the HTML format and host on
GitHub Pages. This
tutorial shows you how to save leaflet
map into an HTML
widget. You will need knowledge of how GitHub works to deploy in this
approach.
Shiny App is an interactive application where users
can filter, update, or analyze spatial data in real-time, which is what
we refer to here as an interactive web map. For
instance, users can filter data, change layers, and run analysis with
sliders, dropdowns, and buttons. It is great for making data dashboards
with leaflet
.
UI (User Interface) determines what components users see on the web page. Here for the UI, we want to add a title, a sidebar that allows users to filter restaurants based on amenity types, and display the interactive map.
# ----- Define the Shiny UI ----- #
# Define the UI for a Shiny web application
ui <- fluidPage(
# Set the title of the application
titlePanel("Interactive Amenity Map of Ann Arbor"),
# Create a Leaflet map output with a height of 700 pixels
leafletOutput("map", height = "700"),
# Create an absolute panel (floating panel) for UI controls
absolutePanel(
# Position the panel 70 pixels from the top and 30 pixels from the right
top = 70, right = 30,
# Allow the panel to be draggable by the user
draggable = TRUE,
# Apply custom styling: high z-index to ensure it's above other elements,
# white background, padding, and rounded corners
style = "z-index:500; background-color: white; padding: 10px; border-radius: 5px;",
# Create a dropdown menu (select input) for choosing the type of amenity to display
selectInput(
inputId = "selected_amenity", # The ID used to access this input in the server logic
label = "Select Amenity Type:", # The label displayed above the dropdown
choices = c("All", unique(amenity_point$amenity)), # Populate dropdown with unique amenity types plus "All"
selected = "All" # Default selected option is "All"
)
)
)
Servers determines how you want the user inputs to interact with data and how you want to map to response to user inputs. Here, we define the server functions to be two things:
# Define the server logic for the Shiny application
server <- function(input, output, session) {
# Define a named vector to assign colors to different amenity types
amenity_colors <- c(
"pub" = "red", # Assign red color to pubs
"cafe" = "blue", # Assign blue color to cafes
"bar" = "darkgreen", # Assign dark green color to bars
"restaurant" = "orange", # Assign orange color to restaurants
"fast_food" = "brown", # Assign brown color to fast food places
"ice_cream" = "purple" # Assign purple color to ice cream shops
)
# Create a reactive expression to filter the dataset based on the selected amenity type
filtered_data <- reactive({
if (input$selected_amenity == "All") {
return(amenity_point) # If "All" is selected, return the entire dataset
} else {
return(amenity_point %>% filter(amenity == input$selected_amenity)) # Otherwise, filter by selected amenity
}
})
# Render the initial Leaflet map with a tile layer and legend
output$map <- renderLeaflet({
leaflet(amenity_point) %>% # Create a Leaflet map using the full dataset
addProviderTiles(providers$CartoDB.Positron) %>% # Add a light-colored basemap
setView(lng = -83.7430, lat = 42.2808, zoom = 12) %>% # Center the map on Ann Arbor with zoom level 12
addLegend(
"bottomright", # Position the legend at the bottom right
colors = amenity_colors, # Use the predefined colors for amenities
labels = names(amenity_colors), # Use the names of amenities as legend labels
title = "Amenity Type", # Set the title of the legend
opacity = 1 # Set the legend opacity to fully visible
)
})
# Observe changes in the selected amenity type and update the map markers dynamically
observe({
leafletProxy("map", data = filtered_data()) %>% # Use leafletProxy to update the existing map
clearMarkers() %>% # Remove existing markers before adding new ones
addCircleMarkers(
lng = ~st_coordinates(geometry)[,1], # Extract longitude from geometry column
lat = ~st_coordinates(geometry)[,2], # Extract latitude from geometry column
popup = ~paste0("<b>", name, "</b><br>", amenity), # Create a popup with the name and amenity type
radius = 3, # Set marker size
color = ~unname(amenity_colors[amenity]), # Assign color based on amenity type
fillOpacity = 0.8 # Set marker fill opacity
)
})
# Observe changes and update the legend dynamically when the selection changes
observe({
leafletProxy("map") %>% # Use leafletProxy to modify the existing map
clearControls() %>% # Remove the existing legend before adding a new one
addLegend(
"bottomright", # Position the legend at the bottom right
colors = amenity_colors, # Use the predefined colors for amenities
labels = names(amenity_colors), # Use the names of amenities as legend labels
title = "Amenity Type", # Set the title of the legend
opacity = 1 # Set the legend opacity to fully visible
)
})
}
Deploy a web map on the public internet domain using Shiny App requires a server. You can test to see what that App look like by deploying it locally first. This is a standard practice in app development. The local deployment is free and fast, though only you can see the outcome. The code below will pop up a new window that shows you what the deployment looks like.
Feel free to adjust the aesthetics and variables. This is a
tutorial that introduces more about using leaflet
with
R Shiny app, such as how to understand layers, how to add inputs/events,
etc.
shinyApp(ui, server)
To further deploy it on the web rather than locally, you will have to save all lines that start from loading libraries, importing data and setting up ui and server into one file. Let’s first export our data. You can export as either shp or geojson. For simplicity, we export the data as geojson, so that it only has one file (instead of a series of files as in shp).
# export amenity_point to save at the local folder
st_write(amenity_point, 'a2_amenity/amenity_point.geojson')
IMPORTANT
Next, copy all the code below into a new R script named
“app.R” that will be deployed to a the Shiny App server (next
section). You can consider this as “exporting” all the
environment/library, data, and code, to a server so that the code will
be run in the cloud. Since deployment will send everything in the same
folder as app.R
to the cloud, we created a separate folder
called a2_amenity
to separate lab materials (which we don’t
want to send to the cloud) from app.R
. This folder
name will also become part of the domain, so you can customize what you
want to show in the domain by changing the folder name.
library(sf)
library(shiny)
library(leaflet)
library(tidyverse)
amenity_point = st_read('a2_amenity/amenity_point.geojson')
ui <- fluidPage(
titlePanel("Interactive Amenity Map of Ann Arbor"),
leafletOutput("map", height = "700"),
absolutePanel(
top = 70, right = 30,
draggable = TRUE,
style = "z-index:500; background-color: white; padding: 10px; border-radius: 5px;",
selectInput(
inputId = "selected_amenity",
label = "Select Amenity Type:",
choices = c("All", unique(amenity_point$amenity)),
selected = "All"
)
)
)
server <- function(input, output, session) {
amenity_colors <- c(
"pub" = "red",
"cafe" = "blue",
"bar" = "darkgreen",
"restaurant" = "orange",
"fast_food" = "brown",
"ice_cream" = "purple"
)
filtered_data <- reactive({
if (input$selected_amenity == "All") {
return(amenity_point)
} else {
return(amenity_point %>% filter(amenity == input$selected_amenity))
}
})
output$map <- renderLeaflet({
leaflet(amenity_point) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
setView(lng = -83.7430, lat = 42.2808, zoom = 12) %>%
addLegend(
"bottomright",
colors = amenity_colors,
labels = names(amenity_colors),
title = "Amenity Type",
opacity = 1
)
})
observe({
leafletProxy("map", data = filtered_data()) %>%
clearMarkers() %>%
addCircleMarkers(
lng = ~st_coordinates(geometry)[,1],
lat = ~st_coordinates(geometry)[,2],
popup = ~paste0("<b>", name, "</b><br>", amenity),
radius = 3,
color = ~unname(amenity_colors[amenity]),
fillOpacity = 0.8
)
})
observe({
leafletProxy("map") %>%
clearControls() %>%
addLegend(
"bottomright",
colors = amenity_colors,
labels = names(amenity_colors),
title = "Amenity Type",
opacity = 1
)
})
}
shinyApp(ui, server)
shinyapps.io is a free hosting service for Shiny apps, which is developed by the same company that develops R. After deployment, you would be able to view the interactive web map at a domain like below: https://your-username.shinyapps.io/your-app-name/.
Here is what my link looks like and you can see what the final deployment would look like: https://xiaofanliang.shinyapps.io/a2_amenity/
Create a shinyapps.io Account. Go to https://www.shinyapps.io and sign up.
Install and load the rsconnect
package if not
already installed or loaded.
Find your name, token, and
secret values by going to Account -> Tokens ->
Add Token (or Show). Replace shiny_name
,
shiny_token
, and shiny_secret
with your own
name, token, and secret values.
library(rsconnect)
# don't run this line. You won't have access to the file shiny_key.R as it stores the instructor's key.
source("shiny_key.R")
rsconnect::setAccountInfo(name=shiny_name,
token=shiny_token,
secret=shiny_secret)
app.R
. We reset the working
directory to point to the a2_amenity
folder.setwd("/Users/xfliang/University of Michigan Dropbox/Xiaofan Liang/UM_Teaching/URP535_Urban_Informatics/W25/Lab/Lab10/a2_amenity/")
# This will upload and publish your app on shinyapps.io.; adding "::" between package and function ensures that the function `deployApp()` come from the package `rsconnect`
rsconnect::deployApp()
If there is any error for dployment, run the following line to see where the errors occur.
rsconnect::showLogs()
It is also always a good practice to make sure your local deployment is correct before you deploy to shinyapps.io. The reason is that local deployment is faster so you can adapt based on error message faster.
Some of the common problems for Shiny App includes:
shinyApp(ui, server)
at the
end of the app.RExplore leaflet documentation - Add markers to leaflet page. Pick one feature to implement in addition to the amenity map in the lab (colored by amenity type), such as customizing marker icons, marker cluster, or customize the circle markers by radius.
amenity_icons <- iconList(
pub = makeIcon(
iconUrl = "https://cdn-icons-png.flaticon.com/512/1146/1146002.png",
iconRetinaUrl = "https://cdn-icons-png.flaticon.com/512/1146/1146002.png",
iconWidth = 10, iconHeight = 10
),
cafe = makeIcon(
iconUrl = "https://static.thenounproject.com/png/1149467-200.png",
iconRetinaUrl = "https://static.thenounproject.com/png/1149467-200.png",
iconWidth = 10, iconHeight = 10
),
bar = makeIcon(
iconUrl = "https://cdn-icons-png.flaticon.com/512/3514/3514280.png",
iconRetinaUrl = "https://cdn-icons-png.flaticon.com/512/3514/3514280.png",
iconWidth = 10, iconHeight = 10
),
restaurant = makeIcon(
iconUrl = "https://static.thenounproject.com/png/323504-200.png",
iconRetinaUrl = "https://static.thenounproject.com/png/323504-200.png",
iconWidth = 10, iconHeight = 10
),
fast_food = makeIcon(
iconUrl = "https://cdn-icons-png.flaticon.com/512/1046/1046886.png",
iconRetinaUrl = "https://cdn-icons-png.flaticon.com/512/1046/1046886.png",
iconWidth = 10, iconHeight = 10
),
ice_cream = makeIcon(
iconUrl = "https://static.thenounproject.com/png/43136-200.png",
iconRetinaUrl = "https://static.thenounproject.com/png/43136-200.png",
iconWidth = 10, iconHeight = 10
)
)
leaflet(data = amenity_point) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addMarkers(
lng = st_coordinates(amenity_point)[,1],
lat = st_coordinates(amenity_point)[,2],
popup = ~paste0("<b>", name, "</b><br>", amenity),
icon = ~amenity_icons[amenity]
) %>%
addLegend(
"bottomright",
colors = c("red", "blue", "darkgreen", "orange", "brown", "purple"),
labels = c("Pub", "Cafe", "Bar", "Restaurant", "Fast Food", "Ice Cream"),
title = "Amenity Type",
opacity = 1
) %>%
setView(lng = -83.7430, lat = 42.2808, zoom = 12)
Explore leaflet
documentation - Choropleths page. Use MI_counties.shp
in Lab 4 and replicate a similar Choropleth as in the tutorial. The
following features should present, while the rest is up to you:
Deploy the leaflet map in Q1 and the choropleth map Q2 to RPub or as a Shiny App (Advanced dynamic feature with Shiny App is optional). Save the links to submit to the Quiz.