Geocoding and Mapping in R
This tutorial builds on the spatial data foundations from earlier tutorials in this series. You will learn how to convert addresses to coordinates using geocoding services and create both static and interactive maps in R.
Prerequisites
Install the required packages:
install.packages(c("sf", "tidyverse", "ggplot2", "leaflet", "tmap", "ggspatial", "tigris"))
library(sf)
library(tidyverse)
library(ggplot2)
library(leaflet)
library(tmap)
Geocoding Addresses
Geocoding converts human-readable addresses into spatial coordinates. Several R packages provide access to geocoding services.
Using tidygeocoder
The tidygeocoder package provides a clean interface to multiple geocoding services:
library(tidygeocoder)
# Create address data
addresses <- tibble(
name = c("Statue of Liberty", "Tower of London", "Sydney Opera House"),
address = c(
"Liberty Island, New York, NY 10004, USA",
"Tower of London, London, UK",
"Bennelong Point, Sydney NSW 2000, Australia"
)
)
# Geocode using Nominatim (free, OpenStreetMap-based)
geocoded <- addresses %>%
geocode(address, method = "osm", lat = latitude, long = longitude)
print(geocoded)
# # A tibble: 3 × 4
# name address latitude longitude
# 1 Statue of Liberty Liberty Island, New York, NY 1… 40.7 -74.0
# 2 Tower of London Tower of London, London, UK 51.5 -0.076
# 3 Sydney Opera House Bennelong Point, Sydney NSW 2… -33.9 151.
Converting to sf Objects
Once you have coordinates, convert to sf for spatial operations:
geocoded_sf <- geocoded %>%
st_as_sf(coords = c("longitude", "latitude"), crs = 4326)
print(geocoded_sf)
# Simple feature collection with 3 features and 2 fields
# Geometry type: POINT
# Dimension: XY
# Bounding box: xmin: -74.0 ymin: -33.9 xmax: 151. ymax: 51.5
# CRS: 4326
Rate Limiting and Best Practices
When geocoding at scale, respect rate limits:
# For bulk geocoding, add delays between requests
geocode_slowly <- function(addresses) {
results <- vector("list", length(addresses))
for (i in seq_along(addresses)) {
results[[i]] <- geo(addresses[i])
Sys.sleep(1) # Respect Nominatim's 1-second limit
}
bind_rows(results)
}
# Alternative: use batch geocoding services for large datasets
# Services like Google, ArcGIS, or US Census geocoder offer batch APIs
Static Maps with ggplot2
The ggplot2 package, combined with sf, creates publication-quality static maps.
Basic sf Visualization
library(ggspatial)
# Load example data
library(spData)
data(us_states)
# Filter to a region for a cleaner map
us_ne <- us_states %>%
filter(NAME %in% c("New York", "New Jersey", "Pennsylvania",
"Connecticut", "Massachusetts", "Vermont",
"New Hampshire", "Maine"))
# Create base map
ggplot(data = us_ne) +
geom_sf(aes(fill = NAME)) +
geom_sf_label(aes(label = NAME), size = 2.5) +
annotation_scale(location = "bl", width_hint = 0.5) +
annotation_north_arrow(location = "br", which_north = "true") +
labs(title = "Northeastern United States",
fill = "State") +
theme_minimal() +
theme(legend.position = "bottom")
Adding Points to Maps
Overlay geocoded points on base maps:
# Create a map showing our geocoded locations
# First, get US states background
us_main <- us_states %>% filter(!(NAME %in% c("Alaska", "Hawaii")))
# Add geocoded points
geocoded_sf_usa <- geocoded %>%
filter(longitude > -130, longitude < -50) %>%
st_as_sf(coords = c("longitude", "latitude"), crs = 4326)
ggplot() +
geom_sf(data = us_main, fill = "gray95", color = "gray80") +
geom_sf(data = geocoded_sf_usa, color = "red", size = 3, shape = 19) +
geom_sf_label(data = geocoded_sf_usa,
aes(label = name),
nudge_y = 2, size = 3) +
coord_sf(xlim = c(-85, -65), ylim = c(35, 50)) +
theme_minimal() +
labs(title = "Major Landmarks in the Northeastern US")
Choropleth Maps
Choropleth maps color regions by a variable:
# Calculate population density
us_main <- us_main %>%
mutate(pop_density = total_pop_2015 / as.numeric(st_area(geometry)) * 1e6)
# Create choropleth
ggplot(data = us_main) +
geom_sf(aes(fill = pop_density), color = "white", size = 0.1) +
scale_fill_viridis_c(name = "Pop. per sq km",
trans = "log10",
labels = scales::comma) +
labs(title = "Population Density by State, 2015",
subtitle = "United States") +
theme_minimal() +
theme(legend.position = "right")
Interactive Maps with Leaflet
Leaflet creates interactive web maps that work well for data exploration:
library(leaflet)
# Basic interactive map
leaflet() %>%
addTiles() %>%
addMarkers(lng = -74.0445, lat = 40.6892,
popup = "Statue of Liberty") %>%
addMarkers(lng = -0.0759, lat = 51.5081,
popup = "Tower of London") %>%
addMarkers(lng = 151.2153, lat = -33.8568,
popup = "Sydney Opera House")
Using sf with Leaflet
# Convert sf to format leaflet understands
leaflet(geocoded_sf) %>%
addTiles() %>%
addCircleMarkers(radius = 8, color = "red", fillOpacity = 0.7,
popup = ~name) %>%
addPopupNotifications()
Custom Base Maps
# Using different tile providers
leaflet() %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addMarkers(lng = -74.0445, lat = 40.6892,
popup = "Statue of Liberty")
# Satellite imagery
leaflet() %>%
addProviderTiles(providers$Esri.WorldImagery) %>%
addMarkers(lng = -74.0445, lat = 40.6892,
popup = "Statue of Liberty")
Interactive Maps with tmap
The tmap package offers a middle ground between static and interactive:
library(tmap)
# Switch to interactive mode
tmap_mode("view")
# Create interactive choropleth
tm_shape(us_main) +
tm_polygons("pop_density",
title = "Population Density",
style = "log10",
palette = "viridis") +
tm_layout(title = "US Population Density (2015)")
Common Geocoding Challenges
Handling Ambiguous Addresses
# Use additional parameters to disambiguate
geo("Springfield", method = "osm")
# Might return multiple results
# Be more specific
geo("Springfield, Illinois", method = "osm") # Springfield, IL
geo("Springfield, Massachusetts", method = "osm") # Springfield, MA
Handling Non-Standard Addresses
# Clean addresses before geocoding
clean_address <- function(address) {
address %>%
str_replace_all("#", "Number ") %>%
str_replace_all("\\s+", " ") %>%
str_trim()
}
# Use fuzzy matching when exact matches fail
geo("123 Main St New York NY", method = "osm", limit = 5)
US Census Geocoding
For US addresses, the tigris package provides access to the Census Geocoder:
library(tigris)
# Batch geocoding with Census API
addresses <- data.frame(
street = c("1600 Pennsylvania Ave NW", "350 Fifth Ave"),
city = c("Washington", "New York"),
state = c("DC", "NY")
)
# Note: For production use, consider the batch geocoding API
# https://geocoding.geo.census.gov/geocoder/locations/addressbatch
Summary
You have learned how to:
- Convert addresses to coordinates using geocoding services
- Transform geocoded results into sf objects for spatial analysis
- Create publication-quality static maps with ggplot2
- Build interactive maps with leaflet for web delivery
- Use tmap for flexible static and interactive mapping
These skills form the foundation for location-based analysis and visualization in R.
See Also
- dplyr::filter — Filter data based on conditions before mapping
- dplyr::mutate — Add derived columns like population density
- purrr::map — Apply geocoding to multiple addresses iteratively