Geocoding and Mapping in R

· 5 min read · Updated March 16, 2026 · intermediate
spatial geocoding mapping ggplot2 leaflet r gis intermediate

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