Contour lines

r
ggplot2
germansea
Y2025

In this blog post, we’ll explore how to plot contour lines from a raster using bathymetry data as an example.

Author

Miriam Lerma

Published

December 4, 2025

Intro

This months blog post is about how to create contour lines from a raster file, and how to add labels using the package shadowtext.

Read

For the example, we will be using Bathymetry data. Bathymetry gives us information on the water depth around an area.

The data comes from: GEBCO: General Bathymetry Chart of the Oceans provides information from bathymetry in the ocean.

To download visit webpage. For more details and instructions on how to load bathymatry data in R go to my bathymetry post

Bath_0tif<-'https://github.com/MiriamLL/data_supporting_webpage/raw/refs/heads/main/Blog/2025/Bathymetry/gebco_2024_n60.0_s50.0_w1.0_e9.0.tif'

Load the terra package for reading raster data.

library(terra)

The function rast helps to read raster data, replacing package raster.

Bath_1raster<-rast(Bath_0tif)

Crop

Keep a reduced area for making the calculations faster.

ext(Bath_1raster)
crop_extent <- ext(3, 9, 53, 56)
Bath_2cropped <- crop(Bath_1raster, crop_extent)
plot(Bath_2cropped)

Contour lines.

Calculate contour lines

Depth_range <- global(Bath_2cropped, fun = "range", na.rm = TRUE)
Depth_range
Contour_levels <- seq(0, -80, by = -5)
Bath_3contours <- as.contour(Bath_2cropped, levels = Contour_levels)

The resulting object class is “SpatVector”.

class(Bath_3contours)

Check by plotting the data.

plot(Bath_2cropped, col = terrain.colors(100), main = "Bathymetry 5m Contours")
lines(Bath_3contours, col = "blue")

Export the shapefile using the function writeVector.

writeVector(Bath_3contours, "Bathymetry_10m_contours.shp", overwrite = TRUE)

The contour lines are also available to download here

Plot

Load the package sf to transform from SpatVector to sf.

library(sf)

From SpatVector to sf.

Bath_4lines <- st_as_sf(Bath_3contours)

Plot

Load ggplot2 to use geom_sf.

library(ggplot2)

Lines

Bath_5df <- as.data.frame(Bath_1raster, xy = TRUE)
library(tidyverse)
Bath_6sub <- Bath_5df  %>%
  filter(x > 2 & x < 10)%>%
  filter(y > 52 & y < 57)%>%
  rename(Bathymetry=3) %>%
  filter(Bathymetry < 10)
Bath_7centroids <- Bath_4lines %>%
  group_by(level) %>%
  summarise(geometry = st_centroid(st_union(geometry)))
Bath_8plot<-ggplot() +
  geom_raster(data = Bath_6sub , aes(x = x, y = y, fill = Bathymetry)) +
  scale_fill_viridis_c(option = "mako")+
  geom_sf(data = Bath_4lines, color = "grey", size = 0.3) +
  geom_sf(data = GermanNorthSea::German_land , color='#ffffbe', fill='#ffffbe')+
  geom_sf_text(data = Bath_7centroids, aes(label = -level), 
               inherit.aes = FALSE, size = 4, color = "white") +
  theme_void() +
  coord_sf(xlim = c(3.3,8.8),
           ylim = c(53.2,55.8),
           label_axes = list(left = "N", bottom = 'E'))+
  theme(legend.position = 'none')
Bath_8plot

Shadows

To be able to better see the text use the package shadowtext

library(shadowtext)
Bath_7centroids_df <- cbind(Bath_7centroids, st_coordinates(Bath_7centroids))
Bath_9plot<-Bath_8plot+geom_shadowtext(
    data = Bath_7centroids_df,
    aes(x = X, y = Y, label = -level),  # label is -level
    size = 4,
    color = "black",
    bg.color = "white",   # white shadow
    bg.r = 0.15,          # radius of the shadow
    inherit.aes = FALSE
  )
Bath_9plot

Further reading