Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Separate land and water using two gradients in ggplot

I'm trying to get two separate gradients for land and water on a map. I am able to get the water gradient (first figure), but not the land.

How can I set a grey gradient for land in this code (similar to figure 2 below)?

Sample data

library(marmap)
library(ggplot2)

greys <- c(grey(0.6), grey(0.93), grey(0.99))

# Get map data
bat <- getNOAA.bathy(-68, -51, -48, -39, res = 1, keep = TRUE)

Generate Plot with water gradient

autoplot(bat, geom = c("raster", "contour"), colour = "white", size = 0.1) + 
  scale_fill_gradientn(limits = c(-6600, 0), colors = c("steelblue4", "#C7E0FF")) +
  NULL

enter image description here

I have tried setting different limits in scale_fill_gradientn, but without much luck:

autoplot(bat, geom = c("raster", "contour"), colour = "white", size = 0.1) + 
  scale_fill_gradientn(limits = c(min(bat), max(bat)), 
                       colors = c("steelblue4", "#C7E0FF", greys)) +
  NULL

Desired output (done using base R's plotting functions)

plot(bat, image = TRUE, land = TRUE, lwd = 0.1, bpal = list(c(0, max(bat), greys), c(min(bat), 0, blues)))
plot(bat, lwd = 0.8, deep = 0, shallow = 0, step = 0, add = TRUE) # highlight coastline

enter image description here

like image 324
Vedda Avatar asked Oct 19 '25 05:10

Vedda


2 Answers

Approach with cut and scale_fill_manual:

It's much more involved than @Z.Lin's answer (which I personally would go with), but this method potentially gives you more control, and the geom_raster plots pretty fast.

I've done it all manually, but you can imagine a function that takes a vector of elevations and a number of desired breaks, then cuts the vector into a set of categorical breaks and makes appropriate labels. This is what sp::spplot() does by default with continuous fields, and it uses nbreaks = 16. You'll need to force the zero elevation break to distinguish between land and ocean.

Here's the general idea that can be developed.

# convert to raster, then data frame
library(raster)
d <- as.raster(bat)
d <- as.data.frame(d, xy=TRUE)

# upper and lower elevation bounds
z <- max(d$layer)
a <- min(d$layer)

# breaks and labels for color scale
brks <- c(a, 1000, 500, 0, -500, -1000, -2000, -3000, -4000, -5000, z)
labs <- c("> 1000", "500:1000", "0:500", "-500:0", "-1000:-500", "-2000:-1000",
          "-3000:-2000", "-4000:-3000", "-5000:-4000", "< -6514")
d$bin <- cut(d$layer, breaks = brks, labels = labs)
d <- d[!is.na(d$bin), ] # filter sneaky NA values

library(colormap)
gr   <- colormap(colormaps$greys, nshades = 10)[4:6]
bl   <- colormap(colormaps$velocity_blue, nshades = 13)[3:9]
cols <- c(bl, gr)

# plot
ggplot(d, aes(x, y, fill = bin)) +
  geom_raster() +
  scale_fill_manual(values = cols, limits = labs, labels = rev(labs)) +
  theme_minimal() +
  labs(fill = "Elevation (ft)")

enter image description here

like image 118
Rich Pauloo Avatar answered Oct 20 '25 18:10

Rich Pauloo


Maybe something like this? Range of fill values is obtained from checking summary(fortify.bathy(bat)) for z's range.

autoplot(bat, geom = c("raster", "contour"), colour = "white", size = 0.1) + 
  scale_fill_gradientn(values = scales::rescale(c(-6600, 0, 1, 1500)),
                       colors = c("steelblue4", "#C7E0FF", "grey50", "grey80"))

plot

Note: I used c("grey50", "grey80") as the range for grey values here, but of course you can change that based on your requirements.

like image 35
Z.Lin Avatar answered Oct 20 '25 20:10

Z.Lin