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:
The x-axis does not reach 180°E (even with xlim = c(160, 180)
).
When I try scale_x_continuous()
, the y-axis labels break completely.
Sometimes the x-axis labels repeat vertically (see attached image).
I’ve tried using dummy geometries to extend the bounding box, but it doesn’t help consistently.
I want
What I’ve tried:
breaks = seq(160, 180, 5)
) — breaks the y-axisc(180, -50)
— doesn’t force full extentcoord_sf(..., label_graticule = "SW")
— doesn’t help force exact breaksattr(nz_islands, "bbox") <- ...
, to no availWhat 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")
)
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