I am helping a set of data managers across the Pacific plot fisheries data within their EEZ. In trying to make a standard example that works for everyone, I have an issue with the latitude/longitude graticule for several countries that either cross or are near the dateline (180°). It's difficult to provide a MWE given the size of map shapefiles (EEZ, 12nm, 24nm shapefiles, etc.). So I will see if by showing my code and an example of what happens, someone can suggest a general fix. As others in the past have sought help with Fiji, I'll use that as an example.
The map shapefiles are all downloaded from marineregions.org, using the 0-360 versions, so that the Pacific is centered. The world's EEZs are read in, and Fiji extracted to its own object. It's then plotted using reasonable lat/lon limits
library(ggplot2)
library(sf)
library(maps)
ALL_EEZ <- sf::st_read(paste0(map.dir,"EEZ/eez_v12_0_360.shp")) #this is the World's EEZ shapefile to download from marineregions.org
FJ_EEZ<- ALL_EEZ[ALL_EEZ$TERRITORY1=="Fiji",]
geo_bounds<- c(170, 185,-25,-10) #west long, east lon, south lat, north lat for Fiji
ww=abs(geo_bounds[2]-geo_bounds[1])*100 # set window width
wh=abs(geo_bounds[4]-geo_bounds[3])*100 # set window height
if (dev.cur()!=1) dev.off() #close other windows
windows(ww,wh) # open window of correct dimensions (for mercator)
gg1<-ggplot() +
geom_sf(data = FJ_EEZ, colour = "black", fill=alpha("steelblue1",0.6), linewidth = 0.75) +
coord_sf(xlim = c(geo_bounds[1],geo_bounds[2]), ylim = c(geo_bounds[3],geo_bounds[4])) +
theme(panel.grid.major = element_line(color = gray(.5), linetype = "dotted", linewidth = 0.3), panel.background = element_rect(fill = NA, colour="black"))
print(gg1)
This produces this figure Fiji map with incomplete graticule
Adding scale_y_continuous and scale_x_continuous such as
scale_x_continuous(breaks = seq(geo_bounds[1], geo_bounds[2], 5)) +
scale_y_continuous(breaks = seq(geo_bounds[3], geo_bounds[4], 5)) +
doesn't result in a graticule extending past 180°.
however if I change the geobounds to
geo_bounds<- c(170, 195,-25,-10) #west long, east lon, south lat, north lat
The graticule draws correctly but I have a lot of area plotted east of Fiji that I don't want in the plot. Fiji map with correct (but extended) graticule
I have the same plotting issue with the EEZs of Gilbert Islands, New Zealand, Tonga, Tuvalu, and Wallis and Futuna.
I have searched the web and stackoverflow extensively for a simple solution to this but have not found one. I note again I'm trying to make a standard example for all the countries and territories that works the same and gives a centered map of the EEZ, on which they later plot data. The geobounds for each is read on from a .csv file I prepare for all 37 EEZs we are working with.
Without resorting to new map projections, is there a way to keep sensible geobounds and have the graticule drawn for each map?
As noted in the comments, this is a known issue and the solutions offered here involve compromise e.g. they do not not avoid resorting to new projections. However, by creating helper functions, it will help streamline the process somewhat. They may look daunting, particularly if you have less experienced R users in your group, but distributing the functions as-is will allow plotting to be standardised.
The following demonstrates two options:
The helper functions:
cent
value for centering map and creating custom PROJ string prj
xlims
and breaks xbreaks
from st_bbox(sf)
based on original CRS, and creates equivalent WGS84/EPSG:4326 x-axis labels xlabels
. This replaces the need for your geo_bounds valuessf
to prj
CRS and recreate xlims
and xbreaks
Option 1: Plot EEZ only
library(sf)
library(ggplot2)
# Set data path
map.dir <- "C:/path/to/data/"
# Load full EEZ data previously unzipped, downloaded from
# https://marineregions.org/downloads.php
ALL_EEZ <- st_read(paste0(map.dir,"EEZ/eez_v12_0_360.shp"))
# Helper function to manage antimeridian/>180 longitude coordinates
# EEZ only
am_plot <- \(country, breaks) {
if (!(country %in% ALL_EEZ$TERRITORY1)) {
stop(paste("Error: Country", country, "not found in ALL_EEZ$TERRITORY1.",
"Please check spelling and try again"))
}
sf <- ALL_EEZ[ALL_EEZ$TERRITORY1 == country,]
cent <- suppressWarnings(round(st_coordinates(st_centroid(sf))[1]))
xlims <- unname(st_bbox(sf)[c(1,3)])
xbreaks <- round(seq(floor(xlims[1]), ceiling(xlims[2]), length.out = breaks), 2)
xlabels <- ifelse(xbreaks > 180,
paste0(sprintf("%.2f", xbreaks - 360), "°E"),
paste0(sprintf("%.2f", xbreaks), "°W"))
prj <- paste0("+proj=longlat +datum=WGS84 +lon_0=", cent, " +no_defs")
sf <- st_transform(sf, prj)
xlims <- unname(st_bbox(sf)[c(1,3)])
xbreaks <- round(seq(floor(xlims[1]), ceiling(xlims[2]), length.out = breaks), 2)
ggplot() +
geom_sf(data = sf,
colour = "black",
fill = alpha("steelblue1", 0.6),
linewidth = 0.75) +
scale_x_continuous(breaks = xbreaks,
labels = xlabels) +
coord_sf(datum = prj,
xlim = range(xbreaks)) +
theme(panel.grid.major = element_line(color = grey(0.5),
linetype = "dotted",
linewidth = 0.3),
panel.background = element_rect(fill = NA, colour = "black"))
}
if (dev.cur()!=1) dev.off()
windows()
# Plot
fiji_plot <- am_plot("Fiji", 4)
print(fiji_plot)
Option 2: Plot EEZ with other data
This is essentially the same as above with two main modifications:
xlims
, xbreaks
, and xlabels
variables are written to the global environment using <<-
so they can be called outside the functionscale_x_continuous()
and coord_sf()
are called outside the function to sidestep a message warning that a coordinate system and scale is already present in the plot (in order to avoid potentially confusing your stakeholders)# Helper function to manage antimeridian/>180 longitude coordinates
# and add subsequent data
am_plot <- \(country, breaks) {
if (!(country %in% ALL_EEZ$TERRITORY1)) {
stop(paste("Error: Country", country, "not found in ALL_EEZ$TERRITORY1.",
"Please check spelling and try again"))
}
sf <- ALL_EEZ[ALL_EEZ$TERRITORY1 == country,]
cent <- suppressWarnings(round(st_coordinates(st_centroid(sf))[1]))
xlims <- unname(st_bbox(sf)[c(1,3)])
xbreaks <- round(seq(floor(xlims[1]), ceiling(xlims[2]), length.out = breaks), 2)
xlabels <<- ifelse(xbreaks > 180,
paste0(sprintf("%.2f", xbreaks - 360), "°E"),
paste0(sprintf("%.2f", xbreaks), "°W"))
prj <<- paste0("+proj=longlat +datum=WGS84 +lon_0=", cent, " +no_defs")
sf <- st_transform(sf, prj)
xlims <- unname(st_bbox(sf)[c(1,3)])
xbreaks <<- round(seq(floor(xlims[1]), ceiling(xlims[2]), length.out = breaks), 2)
ggplot() +
geom_sf(data = sf,
colour = "black",
fill = alpha("steelblue1", 0.6),
linewidth = 0.75) +
theme(panel.grid.major = element_line(color = grey(0.5),
linetype = "dotted",
linewidth = 0.3),
panel.background = element_rect(fill = NA, colour = "black"))
}
if(dev.cur()!=1) dev.off()
windows()
# Plot
tuvalu_plot <- am_plot("Tuvalu", 4)
print(tuvalu_plot)
# Create sample data
set.seed(42)
sf_points <- ALL_EEZ[ALL_EEZ$TERRITORY1 == "Tuvalu",] %>%
st_sample(50)
# OPTIONAL: Project to current plot data CRS
# sf_points <- st_transform(sf_points, prj)
# Add data and plot parameters to current plot
tuvalu_plot +
geom_sf(data = sf_points, colour = "#F8766D", size = 4) +
scale_x_continuous(breaks = xbreaks,
labels = xlabels) +
coord_sf(datum = prj,
xlim = range(xbreaks))