Working with sf and terra
The sf and terra packages form the modern foundation for spatial data analysis in R. While the previous tutorial introduced basic concepts, this guide dives deeper into advanced operations that you will use daily when working with real spatial data.
Prerequisites
Make sure you have the required packages installed:
install.packages(c("sf", "terra", "tidyverse", "spData"))
library(sf)
library(terra)
library(tidyverse)
Advanced Vector Operations with sf
Transforming Coordinate Reference Systems
Transforming between coordinate systems is a common task. The key is understanding when to use geographic versus projected CRS:
# Load example data
library(spData)
data(us_states)
# Check current CRS
st_crs(us_states)
# Geographic: NAD83 / US (latitude and longitude)
# Transform to a projected CRS (Albers Equal Area for USA)
us_albers <- st_transform(us_states, "ESRI:102003")
# Verify transformation
st_crs(us_albers)
# Projected CRS: USA Contiguous Albers Equal Area
# Now area calculations are in square meters
us_albers <- us_albers %>%
mutate(area_km2 = as.numeric(st_area(geometry)) / 1e6)
print(us_albers %>% select(NAME, area_km2) %>% head())
# # A tibble: 6 × 2
# NAME area_km2
# 1 Alabama 127095.
# 2 Alaska ...
Buffer Operations
Buffers create polygons around geometries at a specified distance:
# Create points for cities
cities <- tibble(
name = c("Denver", "Phoenix", "Salt Lake City"),
lat = c(39.7392, 33.4484, 40.7608),
lon = c(-104.9903, -112.0740, -111.8910)
) %>%
st_as_sf(coords = c("lon", "lat"), crs = 4326)
# Create buffers in meters (requires projected CRS)
cities_albers <- st_transform(cities, "ESRI:102003")
# Buffer: 100 km, 200 km, and 300 km
buffers_100 <- st_buffer(cities_albers, dist = 100000)
buffers_200 <- st_buffer(cities_albers, dist = 200000)
buffers_300 <- st_buffer(cities_albers, dist = 300000)
# Visualize
plot(st_geometry(buffers_300), col = "lightblue", border = NA)
plot(st_geometry(buffers_200), col = "lightgreen", border = NA, add = TRUE)
plot(st_geometry(buffers_100), col = "lightyellow", border = NA, add = TRUE)
plot(st_geometry(cities_albers), pch = 19, col = "red", add = TRUE)
Geometric Operations
The sf package provides many geometric operations:
# Union: combine polygons into one
library(spData)
data(us_states)
us_west <- us_states %>% filter(REGION == "West")
west_union <- st_union(us_west)
plot(west_union, main = "Western United States")
# Intersection: keep only overlapping areas
# Create a bounding box for Colorado region
bbox <- st_bbox(c(xmin = -115, ymin = 32, xmax = -100, ymax = 45), crs = 4326)
bbox_sfc <- st_as_sfc(bbox)
# Intersect with western states
west_crop <- st_intersection(us_west, bbox_sfc)
plot(west_crop$geometry, main = "Western States cropped to region")
# Difference: subtract one geometry from another
denver <- cities_albers %>% filter(name == "Denver")
denver_buffer <- st_buffer(denver, dist = 200000)
us_albers <- st_transform(us_states, "ESRI:102003")
colorado <- us_albers %>% filter(NAME == "Colorado")
outside_denver <- st_difference(denver_buffer, st_union(colorado))
plot(outside_denver, main = "200km around Denver, excluding Colorado")
Working with Geometry Collections
Some operations return geometry collections that need special handling:
# Create lines
line1 <- st_linestring(rbind(c(0, 0), c(1, 1), c(2, 0)))
line2 <- st_linestring(rbind(c(0, 1), c(1, 2), c(2, 1)))
# Union lines
lines <- st_sfc(line1, line2, crs = 4326)
combined <- st_line_merge(st_cast(st_union(lines), "LINESTRING"))
print(combined)
# LINESTRING (0 0, 1 1, 2 0, 2 1, 1 2, 0 1)
# Cast between geometry types
points <- st_sfc(st_point(c(0, 0)), st_point(c(1, 1)), crs = 4326)
multipoint <- st_cast(points, "MULTIPOINT")
# Polygons from points (convex hull)
hull <- st_convex_hull(st_union(points))
Advanced Raster Operations with terra
Creating and Manipulating Rasters
The terra package provides efficient raster operations:
# Create a multi-layer raster
r1 <- rast(nrows = 10, ncols = 10,
xmin = 0, xmax = 10, ymin = 0, ymax = 10,
crs = "EPSG:4326")
r2 <- rast(nrows = 10, ncols = 10,
xmin = 0, xmax = 10, ymin = 0, ymax = 10,
crs = "EPSG:4326")
# Fill with values
values(r1) <- runif(ncell(r1), 0, 100)
values(r2) <- runif(ncell(r2), 0, 50)
# Stack rasters
r_stack <- c(r1, r2)
names(r_stack) <- c("temperature", "precipitation")
print(r_stack)
# class : SpatRaster
# dimensions : 10, 10, 2
Raster Algebra
Perform calculations on rasters efficiently:
# Arithmetic operations
r_combined <- r1 + r2
r_scaled <- r1 * 2
# Conditional operations
r_hot <- r1 > 50 # TRUE/FALSE raster
# Focal operations (moving window)
r_smooth <- focal(r1, w = 3, fun = mean)
# Apply function to each cell
r_log <- app(r1, function(x) log(x + 1))
Cropping and Masking
Extract portions of rasters based on vector data:
# Load example raster
elevation <- rast(system.file("ex/elev.tif", package = "terra"))
# Create a spatial extent
ext <- ext(700000, 710000, 4800000, 4810000)
# Crop to extent
elev_crop <- crop(elevation, ext)
# Or use an sf object for cropping
library(spData)
data(us_states)
colorado <- us_states %>% filter(NAME == "Colorado")
colorado_sf <- st_geometry(colorado) %>% st_transform(crs(elevation))
# Crop to Colorado
elev_colorado <- crop(elevation, colorado_sf)
# Mask: set values to NA outside polygon
elev_masked <- mask(elev_colorado, colorado_sf)
# Inverse mask: keep only values inside polygon
elev_inside <- mask(elev_colorado, colorado_sf, inverse = TRUE)
Aggregating and Disaggregating
Change raster resolution:
# Aggregate: combine cells (increase cell size, decrease resolution)
elev_agg <- aggregate(elevation, fact = 2, fun = mean)
# Disaggregate: split cells (decrease cell size, increase resolution)
elev_disagg <- disaggregate(elevation, fact = 2)
# Resample: change to match another raster
target <- rast(nrows = 50, ncols = 50,
ext(elevation), crs = crs(elevation))
elev_resampled <- resample(elevation, target, method = "bilinear")
Zonal Statistics
Calculate statistics for zones defined by polygons:
# Load data
elevation <- rast(system.file("ex/elev.tif", package = "terra"))
library(spData)
data(us_states)
# Get only Alabama
alabama <- us_states %>% filter(NAME == "Alabama") %>%
st_geometry() %>%
st_transform(crs(elevation))
# Crop elevation to Alabama
elev_al <- crop(elevation, alabama)
# Calculate zonal statistics
# Note: terra uses SpatVector, so we convert
al_vect <- as(alabama, "SpatVector")
zonal_stats <- zonal(elev_al, al_vect, fun = c("mean", "min", "max", "sd"))
print(zonal_stats)
# layer mean min max sd
# 1 1 267.419 10.1667 572.67 118.0869
Combining sf and terra
The real power comes from combining vector and raster operations:
# Extract raster values at points
library(spData)
data(us_states)
# Get centroid points for states
centroids <- st_centroid(us_states) %>%
st_transform(4326)
# Sample raster values at these points
elevation <- rast(system.file("ex/elev.tif", package = "terra"))
coords <- st_coordinates(centroids)
elevation_values <- extract(elevation, coords[, c("X", "Y")])
# Add to dataframe
us_states <- us_states %>%
mutate(elevation = elevation_values$elevation)
# Plot
ggplot(us_states) +
geom_sf(aes(fill = elevation)) +
scale_fill_viridis_c(name = "Elevation (m)") +
labs(title = "State Centroids Elevation") +
theme_minimal()
# Rasterize vector data
# Create a template raster
template <- rast(nrows = 100, ncols = 100,
ext(us_states), crs = st_crs(us_states)$proj4string)
# Rasterize state areas
us_raster <- rasterize(st_geometry(us_states), template,
field = as.numeric(st_area(us_states)) / 1e6,
fun = "first")
plot(us_raster, main = "State Areas (km²)")
Performance Tips
Working with large spatial datasets requires optimization:
# Use spatial indices for faster operations
# sf uses spatial indexing automatically in most operations
# For very large data, consider:
# 1. Only load needed columns
us_states_small <- us_states %>% select(NAME, geometry)
# 2. Use spatial indexing explicitly
idx <- st_make_index(us_states, "bbox")
# 3. Parallel processing for large operations
library(future)
plan(multisession)
# 4. Write intermediate results
# st_write(us_states, "us_states.gpkg")
What You Have Learned
This tutorial covered advanced spatial operations:
| Topic | Package | Description |
|---|---|---|
| CRS transformation | sf | Transform between geographic and projected CRS |
| Buffers | sf | Create polygons around geometries |
| Geometric operations | sf | Union, intersection, difference |
| Raster algebra | terra | Perform calculations on rasters |
| Crop and mask | terra | Extract portions of raster data |
| Zonal statistics | terra | Calculate stats by zone |
| Extract values | terra + sf | Get raster values at points |
| Rasterize | terra + sf | Convert vectors to rasters |
See Also
- dplyr::filter — Filter rows based on conditions
- dplyr::mutate — Add new columns to sf objects