Plot bathymetry and coastline using ggplot2 and marmap - r

I looked around and I have not found a nice solution for my goal.
I want to plot some data on a longitude/ latitude plot using ggplot2 and the coastline plus bathymetry with marmap, everything in one single plot.
This script is to plot mydata
ggplot(data = ctd, aes(x = Longitude, y = Latitude)) +
geom_raster(aes(fill = Temp)) +
scale_fill_gradientn(colours = rev(my_colours)) +
geom_contour(aes(z = Temp), binwidth = 2, colour = "black", alpha = 0.2) +
#plot stations locations
geom_point(data = ctd, aes(x = Longitude, y = Latitude),
colour = 'black', size = 3, alpha = 1, shape = 15) +
#plot legends
labs(y = "Latitude", x = "Longitude", fill = "Temp (°C)") +
coord_cartesian(expand = 0)+
ggtitle("Temperature distribution")
Using marmap I download the bathymetry
library(marmap)
Bathy <- getNOAA.bathy(lon1 = 37, lon2 = 38.7,
lat1 = -45.5, lat2 = -47.3, resolution = 1)
The result I would like to obtain is the distribution of mydata on Lon/Lat with the land colored in black plus grey lines for the bathymetry.

Well, there's a marmap function for that. It's called autoplot.bathy(). Have you checked it's help file?
library(marmap) ; library(ggplot2)
library(marmap)
Bathy <- getNOAA.bathy(lon1 = 37, lon2 = 38.7,
lat1 = -45.5, lat2 = -47.3, resolution = 1)
ctd <- data.frame(Longitude = c(37.5, 38, 38.5), Latitude = c(-47, -46.5, -46))
autoplot.bathy(Bathy, geom=c("tile","contour")) +
scale_fill_gradient2(low="dodgerblue4", mid="gainsboro", high="darkgreen") +
geom_point(data = ctd, aes(x = Longitude, y = Latitude),
colour = 'black', size = 3, alpha = 1, shape = 15) +
labs(y = "Latitude", x = "Longitude", fill = "Elevation") +
coord_cartesian(expand = 0)+
ggtitle("A marmap map with ggplot2")
Or, with base graphics (and the correct aspect ratio):
# Creating color palettes
blues <- c("lightsteelblue4", "lightsteelblue3", "lightsteelblue2", "lightsteelblue1")
greys <- c(grey(0.6), grey(0.93), grey(0.99))
# Plot
plot(Bathy, image = TRUE, land = TRUE, n=30, lwd = 0.1, bpal = list(c(0, max(Bathy), greys), c(min(Bathy), 0, blues)), drawlabels = TRUE)
# Add coastline
plot(Bathy, deep = 0, shallow = 0, step = 0, lwd=2, add = TRUE)
# Add stations
points(ctd, pch=15, cex=1.5)

Here is an approach:
get bathy data:
library(marmap)
Bathy <- getNOAA.bathy(lon1 = 37, lon2 = 38.7,
lat1 = -45.5, lat2 = -47.3, resolution = 1)
convert it to matrix:
Bathy <- as.matrix(Bathy)
class(Bathy) <- "matrix"
now reshape it to long format and plot
library(tidyverse)
Bathy %>%
as.data.frame() %>%
rownames_to_column(var = "lon") %>%
gather(lat, value, -1) %>%
mutate_all(funs(as.numeric)) %>%
ggplot()+
geom_contour(aes(x = lon, y = lat, z = value), bins = 10, colour = "black") +
coord_map()

Related

How to draw an arc on top of a plot map overlay made with ggmap in R

I created a multi-layer map using ggmap() in R. I would like to add an arc between two points on the map. I tried doing so with geom_curve(). This doesn't work. The two maps in the overlay are then no longer properly superimposed. I suspect this is due to the different projections within the map overlay, but I don't fully understand this.
Is there a way to specify a coordinate reference system (CRS) for an arc that matches the CRS in use in the map already? Or a totally different way I could add one arc between two points in my exisiting map?
A similar question with an accepted solution was already asked on stack overflow ("Draw curved lines in ggmap, geom_curve not working"). However, as noted there, the solution doesn't work for larger map areas.
Here is a minimal working example with some comments:
My system
I'm using R 4.2.0 with RStudio version 2022.02.3 Build 492.
Required Packages
install.packages("rnaturalearth")
install.packages("devtools") # Needed for rnaturalearthhires
devtools::install_github("ropensci/rnaturalearthhires") # Ditto
install.packages("ggmap")
install.packages("ggplot2")
install.packages("dplyr")
install.packages("PBSmapping") #For clipping polygons
install.packages("ggsn") #For the scale bar
library(rnaturalearth)
library(rnaturalearthhires) # Needed for scale = large in ne_countries()
library(ggmap)
library(ggplot2)
library(dplyr)
library(PBSmapping)
library(ggsn)
Plot a map that includes Germany and Saudi Arabia
Define the map for the region including Germany and Saudi Arabia (KSA) with
latitude and longitude (x min, y min, x max, y max)
GermanyKSA_map_coordinates <- c(5, 16, 56, 56)
Set unique values for the two countries for coloring them on the larger map
GermanyKSA_values <- data.frame(region = factor(c("Germany", "Saudi Arabia")), data = c(15, 15))
Get an existing map of the region including both countries
GermanyKSA_map <- get_map(location = GermanyKSA_map_coordinates, source = "stamen", zoom = 6)
Get country polygon data and join these to the unique values
world <- ne_countries(scale = "large", returnclass = "sf")
mapdata <- map_data("world")
GermanyKSA_mapdata <- left_join(mapdata, GermanyKSA_values, by = "region")`
Create a bounding box for the region map
GermanyKSA_map_bb <- attr(GermanyKSA_map, "bb")
GermanyKSA_ylim <- c(GermanyKSA_map_bb$ll.lat, GermanyKSA_map_bb$ur.lat)
GermanyKSA_xlim <- c(GermanyKSA_map_bb$ll.lon, GermanyKSA_map_bb$ur.lon)`
Clip polygons to match the map
colnames(GermanyKSA_mapdata)[1:6] <- c("X","Y","PID","POS","region","subregion")
GermanyKSA_mapdata <- clipPolys(GermanyKSA_mapdata, xlim = GermanyKSA_xlim, ylim = GermanyKSA_ylim, keepExtra = TRUE)`
Add dots for the cities Regensburg, Germany, and Al-Hofuf, Saudi Arabia
Regensburg_Dot <- c(12.1015461, 49.0204469)
AlHofuf_Dot <- c(49.401569, 25.363091)`
Create a data frame with the dot coordinates
Dots <- rbind(Regensburg_Dot, AlHofuf_Dot) %>% as.data.frame()
Rename the columns
colnames(Dots) <- c("long", "lat")
Plot map overlay. Provides exactly what I need, only an arc between the two cities is missing
ggmap(GermanyKSA_map) +
coord_map(xlim = GermanyKSA_xlim, ylim = GermanyKSA_ylim) +
geom_polygon(data = GermanyKSA_mapdata, aes(x = X, y = Y, group = PID, fill = data),
color = "black", alpha=0.5) +
geom_point(data = Dots, aes(x = long, y = lat), col = "#990022", size = 4) +
scalebar(x.min = 5, x.max = 56, y.min = 16, y.max = 56, dist = 500,
dist_unit = "km", transform = TRUE, anchor = c(x = 52, y = 54),
box.fill = c("#990022", 'grey'), box.color = c("#990022", 'grey'),
st.dist = .025, st.size = 3) +
theme(legend.position = "none") +
theme(axis.title.x = element_blank(), axis.title.y = element_blank()) +
ggtitle("Regensburg–Al-Hofuf: 4,093 km")`
Unsuccessful attempt to add an arc between both points (Regensburg and Al-Hofuf)
Create data frame for drawing an arc
RegensburgToAlHofuf <- data.frame(Regensburg_x = 12.1015461,
Alhofuf_x = 49.401569, Regensburg_y = 49.0204469, Alhofuf_y = 25.363091)`
Plot map overlay, now with an attempt to draw an arc with geom_curve(). Resulting warning: "geom_curve is not implemented for non-linear coordinates"
ggmap(GermanyKSA_map) +
coord_map(xlim = GermanyKSA_xlim, ylim = GermanyKSA_ylim) +
geom_polygon(data = GermanyKSA_mapdata, aes(x = X, y = Y, group = PID,
fill = data), color = "black", alpha=0.5) +
geom_point(data = Dots, aes(x = long, y = lat), col = "#990022", size = 4) +
geom_curve(data = RegensburgToAlHofuf,
aes(x = Regensburg_x, y = Regensburg_y, xend = Alhofuf_x, yend = Alhofuf_y),
size = 1.5, color = "#990022", curvature = 0.15, inherit.aes = TRUE) +
scalebar(x.min = 5, x.max = 56, y.min = 16, y.max = 56, dist = 500,
dist_unit = "km", transform = TRUE, anchor = c(x = 52, y = 54),
box.fill = c("#990022", 'grey'), box.color = c("#990022", 'grey'),
st.dist = .025, st.size = 3) +
theme(legend.position = "none") +
theme(axis.title.x = element_blank(), axis.title.y = element_blank()) +
ggtitle("Regensburg–Al-Hofuf: 4,093 km")`
I added a call to a map projection, coord_cartesian(), following suggestion in "Draw curved lines in ggmap, geom_curve not working". However, plots are not correctly superimposed. I also tried coord_quickmap() instead of coord_cartesian(), which also didn't work.
ggmap(GermanyKSA_map) +
coord_map(xlim = GermanyKSA_xlim, ylim = GermanyKSA_ylim) +
geom_polygon(data = GermanyKSA_mapdata, aes(x = X, y = Y, group = PID,
fill = data), color = "black", alpha=0.5) +
geom_point(data = Dots, aes(x = long, y = lat), col = "#990022", size = 4) +
geom_curve(data = RegensburgToAlHofuf,
aes(x = Regensburg_x, y = Regensburg_y, xend = Alhofuf_x, yend = Alhofuf_y),
size = 1.5, color = "#990022", curvature = 0.15, inherit.aes = TRUE) +
scalebar(x.min = 5, x.max = 56, y.min = 16, y.max = 56, dist = 500,
dist_unit = "km", transform = TRUE, anchor = c(x = 52, y = 54),
box.fill = c("#990022", 'grey'), box.color = c("#990022", 'grey'),
st.dist = .025, st.size = 3) +
theme(legend.position = "none") +
theme(axis.title.x = element_blank(), axis.title.y = element_blank()) +
ggtitle("Regensburg–Al-Hofuf: 4,093 km") +
coord_cartesian()`

cropping stat_density_2d output to a polygon

I am trying to use the stat_density_2d() function to create a heat map based on GPS coordinates and would like to crop the output to a polygon in ggplot(). Here is an example:
x_coord <- c(-83, -82, -82, -83, -83)
y_coord <- c(41, 41, 42, 42, 41)
xy_coords <- cbind(x_coord, y_coord)
library(sp)
poly <- Polygon(xy_coords)
polys <- Polygons(list(poly), 1)
sp.polys <- SpatialPolygons(list(polys))
set.seed(2)
lon <- c(rnorm(20, -82.2, 0.1), rnorm(20, -82.1, 0.04))
lat <- c(rnorm(20, 41.2, 0.1), rnorm(20, 41.1, 0.02))
lon_lat <- data.frame(lon = lon,
lat = lat)
ggplot() +
geom_polygon(data = sp.polys, aes(x_coord, y_coord), fill = 'transparent', color = 'black') +
stat_density_2d(data = lon_lat, aes(x = lon, y = lat, fill = ..level.., alpha = 0.3), geom =
'polygon') +
scale_fill_gradientn(colours=rev(brewer.pal(7, "Spectral"))) +
scale_x_continuous(limits = c(-83.25, -81.75)) +
scale_y_continuous(limits = c(40.75, 42.25))
which produces this figure
Is there a way to crop the stat_density output to remove the portion outside the square? Any thoughts or suggestions are greatly appreciated.
I was unable to find a way to crop the stat_density_2d output to a polygon but using the code below, can mask the area outside the polygon. The code below assumes that you have already used the code posted in the questions to create the polygon and stat_density_2d output.
library(sp)
library(ggplot2)
library(RColorBrewer)
library(rgeos)
library(raster)
x_coord2 <- c(-83.2, -81.8, -81.8, -83.2, -83.2)
y_coord2 <- c(40.8, 40.8, 42.2, 42.2, 40.8)
xy_coords2 <- cbind(x_coord2, y_coord2)
poly2 <- Polygon(xy_coords2)
polys2 <- Polygons(list(poly2), 1)
sp.polys2 <- SpatialPolygons(list(polys2))
r <- raster(x = extent(sp.polys2))
res(r) <- 0.003
r <- setValues(r, 1)
r <- mask(r, sp.polys, inverse = T)
rdf <- data.frame(rasterToPoints(r))
ggplot() +
stat_density_2d(data = lon_lat, aes(x = lon, y = lat, fill = ..level..), alpha =
0.7, geom = 'polygon') +
scale_fill_gradientn(colours=rev(brewer.pal(7, "Spectral"))) +
scale_x_continuous(limits = c(-83.25, -81.75)) +
scale_y_continuous(limits = c(40.75, 42.25)) +
geom_tile(data = rdf, aes(x, y), fill = 'white') +
geom_polygon(data = sp.polys, aes(x_coord, y_coord), fill = 'transparent', color =
'black') +
theme_bw() +
theme(panel.grid.minor = element_blank(),
panel.grid.major = element_blank())

How to draw a line on the 66th parallel north in ggplot when using a polar (epsg:3995) projection and ggspatial

I am trying to draw a blue circle on the 66th parallel north. on a map I am making.
I usually fortify() a SpatialPolygonsDataFrame and then use the combination geom_polygon(), coord_map("ortho" , ylim = c(50, 90)) and geom_hline() to achieve this.
However, for a new project I will have to use ggspatial::layer_spatial() on an object that already has a CRS date, so this straight line drawing and automatic re-projection does not work.
This is a demonstration of my usual approach, which works:
library(tidyverse)
map_data <- rnaturalearth::ne_countries()
map_data_df <- fortify(map_data)
ggplot() +
geom_polygon(data = map_data_df,
aes(x = long,
y = lat,
group = group),
col = "black",
fill = "lightgrey") +
coord_map("ortho" , ylim = c(50, 90)) +
geom_hline(yintercept = 66, col = "blue")
and I am trying to replicate it in this code, but that obviously does not work because the geom_hline() does not have the right projection:
map_data_transformed <- sp::spTransform(map_data, sp::CRS("+init=epsg:3995"))
# needs some fixing to close all the polygons
map_data_transformed <- rgeos::gBuffer(map_data_transformed, byid=TRUE, width=0)
ggplot() +
ggspatial::layer_spatial(data = map_data_transformed,
fill = "lightgrey", col = "black", size = .2)+
coord_sf( xlim = c(-4500000 , 4500000),
ylim = c(-4500000 , 4500000)) +
geom_hline(yintercept = 66, col = "blue")
See if this works for you?
# define parallel north line as a spatial polygon with latitude / longitude projection
new_line <- sp::Polygon(coords = matrix(c(seq(-180, 180),
rep(66, times = 361)), # change this for other
# intercept values
ncol = 2))
new_line <- sp::SpatialPolygons(Srl = list(sp::Polygons(srl = list(new_line),
ID = "new.line")),
proj4string = sp::CRS("+proj=longlat +datum=WGS84"))
# change projection to match that of data
new_line <- sp::spTransform(new_line, sp::CRS("+init=epsg:3995"))
# plot
ggplot() +
ggspatial::layer_spatial(data = map_data_transformed,
fill = "lightgrey", col = "black", size = .2) +
ggspatial::layer_spatial(data = new_line,
fill = NA, col = "blue") +
coord_sf( xlim = c(-4500000 , 4500000),
ylim = c(-4500000 , 4500000))

Add plots on a map in R

I would like to add plots on a world map on several locations.
With this code, I can add points on a map:
# Worldmap with two sites
library(rworldmap)
plot(worldmap[which(worldmap$REGION != "Antarctica"), ])
points(c(3, 80), c(10, 40), cex = 3, pch = 16)
But instead of these two points, I would like to add the two following plots:
# Plots associated to these two sites
x_list <- list()
x_list[[1]] <- rnorm(12, 10, 1)
x_list[[2]] <- rnorm(12, 15, 1)
y_list <- list()
y_list[[1]] <- rnorm(12, 10, 1)
y_list[[2]] <- rnorm(12, 15, 1)
plot(x_list[[1]], y_list[[1]], pch = 16)
plot(x_list[[2]], y_list[[2]], pch = 16)
The result should return something like this:
How can I achieve this (with base syntax or with ggplot2)?
You can use annotation_custom from ggplot2 to insert inset plots:
library(ggplot2)
library(dplyr)
worldmap <- map_data("world") %>%
filter(region != "Antarctica")
g1 <- ggplotGrob(
ggplot() +
geom_point(aes(x = x_list[[1]], y = y_list[[1]])) +
theme_void() +
theme(plot.background = element_rect(fill = "white")))
g2 <- ggplotGrob(
ggplot() +
geom_point(aes(x = x_list[[2]], y = y_list[[2]])) +
theme_void() +
theme(plot.background = element_rect(fill = "white")))
p <- ggplot(worldmap, aes(x=long, y=lat, group=group)) +
geom_map(map = worldmap,
aes(map_id = region),
fill = "white",
colour = "#7f7f7f",
size = 0.5)
p +
annotation_custom(grob = g1, xmin = 0, xmax = 40, ymin = 20, ymax = 40) +
annotation_custom(grob = g1, xmin = 80, xmax = 120, ymin = 0, ymax = 20)

Setting map limits in ggplot2 with Mercator projection

Very much related to this question I am trying to plot some world regions, now using a Mercator projection, but keep getting into trouble when adding x and y limits:
ggplot(world, mapping = aes(x = long, y = lat, group = group)) +
geom_polygon(fill = "black", colour = "black") +
coord_map(projection = "mercator", xlim = c(-125, -30), ylim = c(-60, 35))
Obviously not great. When I use coord_cartesian (as suggested here) to set the limits, I loose the Mercator projection:
ggplot(world, mapping = aes(x = long, y = lat, group = group)) +
geom_polygon(fill = "black", colour = "black") +
coord_map(projection = "mercator") +
coord_cartesian(xlim = c(-125, -30), ylim = c(-60, 35))
When I use lims I get what I want for Latin America:
ggplot(world, mapping = aes(x = long, y = lat, group = group)) +
geom_polygon(fill = "black", colour = "black") +
coord_map(projection = "mercator") +
lims(x = c(-125, -30), y = c(-60, 35))
Problem is, this approach does not always work, for example for Africa or Asia I start to get some crazy behaviour towards the plot border:
ggplot(world, mapping = aes(x = long, y = lat, group = group)) +
geom_polygon(fill = "black", colour = "black") +
coord_map(projection = "mercator") +
lims(x = c(-20, 45), y = c(-50, 40))
# lims(x = c(40, 150), y = c(-10, 55))
A solution here could be to convert the lat/lon coordinates to "proper" web mercator coordinates (here I'm using epsg 3857, which is the "google" projection), and then plotting using those "new" coordinates.
Assuming that original coordinates are latlon wgs84 (epsg 4326), this can be achieved like this:
worldmerc <- SpatialPointsDataFrame(coords = data_frame(x = world$long, y = world$lat),
data = world, proj4string = CRS("+proj=longlat +datum=WGS84")) %>%
subset((lat < 90 & lat > -90)) %>% # needed because transform not defined at the poles !!!!
spTransform(CRS("+init=epsg:3857"))
worldmerc <- mutate(worldmerc#data, longmerc = coordinates(worldmerc)[,1], latmerc = coordinates(worldmerc)[,2])
Plotting the whole data gives you this (Note the use of coord_fixed to preserve aspect ratio !:
ggplot(worldmerc, mapping = aes(x = longmerc, y = latmerc, group = group)) +
geom_polygon(fill = "black", colour = "black") +coord_fixed()
Now, the problem is that to do subsetting you would now need to enter "map" coordinates instead than lat long, but also that can be tweaked:
#For South America
xlim = c(-125, -30)
ylim = c(-60, 35)
lims = SpatialPoints(coords = data_frame(x = xlim, y = ylim), proj4string = CRS("+proj=longlat +datum=WGS84"))%>%
spTransform(CRS("+init=epsg:3857"))
ggplot(worldmerc, mapping = aes(x = longmerc, y = latmerc, group = group)) +
geom_polygon(fill = "black", colour = "black")+
coord_fixed(xlim = coordinates(lims)[,1], ylim = coordinates(lims)[,2])
#for africa
xlim = c(-20,45)
ylim = c(-50,40)
lims = SpatialPoints(coords = data_frame(x = xlim, y = ylim), proj4string = CRS("+proj=longlat +datum=WGS84"))%>%
spTransform(CRS("+init=epsg:3857"))
ggplot(worldmerc, mapping = aes(x = longmerc, y = latmerc, group = group)) +
geom_polygon(fill = "black", colour = "black")+
coord_fixed(xlim = coordinates(lims)[,1], ylim = coordinates(lims)[,2])
As you can see, in both cases you get "correct" maps.
Now, last thing you may want to do is maybe have "lat/lon" coordinates on the axis. That's a bit of a hack but can be done like this:
library(magrittr)
xlim = c(-125, -30)
ylim = c(-60, 35)
# Get the coordinates of the limits in mercator projection
lims = SpatialPoints(coords = data_frame(x = xlim, y = ylim),
proj4string = CRS("+proj=longlat +datum=WGS84"))%>%
spTransform(CRS("+init=epsg:3857"))
# Create regular "grids" of latlon coordinates and find points
# within xlim/ylim - will be our labels
majgrid_wid_lat = 20
majgrid_wid_lon = 30
majbreaks_lon = data_frame(x=seq(-180, 180, majgrid_wid_lon)) %>%
filter(x >= xlim[1] & x <= xlim[2]) %>%
as.data.frame()
majbreaks_lat = data_frame(x=seq(-90, 90, majgrid_wid_lat)) %>%
filter(x >= ylim[1] & x <= ylim[2]) %>%
as.data.frame()
#Find corresponding mercator coordinates
mercbreaks_lat = SpatialPoints(coords = expand.grid(x = majbreaks_lon$x, y = majbreaks_lat$x), proj4string = CRS("+init=epsg:4326"))%>%
spTransform(CRS("+init=epsg:3857")) %>% coordinates() %>% extract(,2) %>% unique()
mercbreaks_lon = SpatialPoints(coords = expand.grid(x = majbreaks_lon$x, y = majbreaks_lat$x), proj4string = CRS("+init=epsg:4326"))%>%
spTransform(CRS("+init=epsg:3857")) %>% coordinates() %>% extract(,1) %>% unique()
# Plot using mercator coordinates, but latlon labels
ggplot(worldmerc, mapping = aes(x = longmerc, y = latmerc, group = group)) +
geom_polygon(fill = "black", colour = "black") +
coord_fixed(xlim = coordinates(lims)[,1], ylim = coordinates(lims)[,2])+
scale_x_continuous("lon", breaks = mercbreaks_lon, labels = signif(majbreaks_lon$x, 2)) +
scale_y_continuous("lat", breaks = mercbreaks_lat, labels = signif(majbreaks_lat$x,2))+theme_bw()
, which gives:
It's a bit convoluted and there could be better ways, but it does the trick, and could be easily transformed in a function.
HTH,
Lorenzo

Resources