rggplot2plotgeospatialr-sf

How can I force longitude axis labels to display correctly with coord_sf() in ggplot2 (ticks at 160–180°E)?


I'm trying to plot a map of New Zealand using ggplot2 and sf, showing outer islands as well. I want the x-axis to show longitude ticks from 160°E to 180°E at 5-degree intervals, and the y-axis to show latitude.

However, I’m running into several problems:

I’ve tried using dummy geometries to extend the bounding box, but it doesn’t help consistently.

I want

What I’ve tried:

What I’m looking for

A reliable way to force graticule ticks at 160, 165, 170, 175, and 180 on the x-axis without messing up the rest of the map. Bonus points if the solution works in ggplot2 >= 3.5.0.

Let me know if any other detail is needed — I’ve tried several strategies already and I’m hoping for a clean solution that respects both x and y axes using coord_sf().

Minimal reproducible example:

library(sf)
library(ggplot2)

# Turn off s2 processing to avoid geometry issues
sf_use_s2(FALSE)

# Load high-res NZ shapefile (from LINZ)
nz_islands <- st_read("Shapefiles/nz-coastlines-and-islands-topo-150k.shp", quiet = TRUE)

# Add dummy point at 180E to try and force full extent
dummy_row <- nz_islands[1, ]
st_geometry(dummy_row) <- st_sfc(st_point(c(180, -50)), crs = st_crs(nz_islands))
dummy_row[] <- lapply(dummy_row, function(x) if (is.list(x)) x else NA)
nz_islands_extended <- rbind(nz_islands, dummy_row)

# Location of Wellington
wellington <- data.frame(
  lon = 174.7772,
  lat = -41.2889
) |> st_as_sf(coords = c("lon", "lat"), crs = 4326)

# Attempted plot
ggplot() +
  geom_sf(data = nz_islands_extended, fill = "#708090", color = "black") +
  geom_sf(data = wellington, shape = 21, color = "blue", fill = NA, size = 3) +
  coord_sf(
    xlim = c(160, 180),
    ylim = c(-53, -29),
    expand = FALSE,
    label_graticule = "SW",
    crs = st_crs(4326)
  ) +
  # This breaks the y-axis completely:
  # scale_x_continuous(breaks = seq(160, 180, 5)) +
  theme_minimal() +
  theme(
    axis.text.x = element_text(size = 8, color = "gray30"),
    axis.text.y = element_text(size = 8, color = "gray30"),
    axis.ticks = element_line(color = "gray50")
  )


Solution

  • Here's an answer that covers the concept I used here. However, it does not show the implementation, therefore, I am posting an answer instead of closing the question as a duplicate.

    Geographic coordinates can't represent the "wraparound" at ±180° (i.e. international dateline). We need a projected coordinate for this. So I transformed/projected the data before plotting.

    library(sf)
    
    sf_use_s2(FALSE)
    #> Spherical geometry (s2) switched off
    
    # data.linz.govt.nz/layer/51153-nz-coastlines-and-islands-polygons-topo-150k/
    nz_islands <- st_read("nz-coastlines-and-islands-polygons-topo-150k.shp", 
                          quiet = TRUE)
    
    wellington <- data.frame(
      lon = 174.7772,
      lat = -41.2889
    ) |> st_as_sf(coords = c("lon", "lat"), crs = 4326)
    
    pacific_crs <- st_crs("+proj=merc +lon_0=175 +lat_ts=0 +x_0=0 +y_0=0 
                          +datum=WGS84 +units=m +no_defs")
    
    nz_projected <- st_transform(nz_islands, pacific_crs)
    wellington_projected <- st_transform(wellington, pacific_crs)
    
    library(ggplot2)
    
    ggplot() +
      geom_sf(data = nz_projected, fill = "#708090", color = "black") +
      geom_sf(data = wellington_projected, shape = 21, 
              color = "blue", fill = NA, size = 3) +
      coord_sf(
        # we also need to transform limits to the new projection
        xlim = st_bbox(st_transform(st_as_sfc(st_bbox(c(xmin = 160, xmax = 180, 
                                                        ymin = -53, ymax = -29), 
                                                      crs = 4326)), 
                                    pacific_crs))[c(1,3)],
        ylim = st_bbox(st_transform(st_as_sfc(st_bbox(c(xmin = 160, xmax = 180, 
                                                        ymin = -53, ymax = -29), 
                                                      crs = 4326)), 
                                    pacific_crs))[c(2,4)],
        expand = FALSE,
        crs = pacific_crs,
        datum = st_crs(4326)
      ) +
      scale_x_continuous(
        breaks = seq(160, 180, 5),
        labels = function(x) paste0(x, "°E")
      ) +
      scale_y_continuous(
        breaks = seq(-50, -30, 5),
        labels = function(x) paste0(abs(x), "°S")
      ) +
      theme_minimal() +
      theme(
        axis.text.x = element_text(size = 8, color = "gray30"),
        axis.text.y = element_text(size = 8, color = "gray30"),
        axis.ticks = element_line(color = "gray50"),
        panel.grid.major = element_line(color = "gray80", linewidth= 0.5),
        panel.grid.minor = element_blank()
      ) +
      labs(
        x = "Longitude",
        y = "Latitude"
      )
    

    Created on 2025-07-09 with reprex v2.1.1