Search code examples
rggplot2colorsterratidyterra

truncate colour pallete with ggplot2 and geom_spatraster() in different layers


I want to be able truncate the colour pallete for certain values of a raster using ggplot, which I am able to do using basic plotting of the terra object, but not with ggplot2.

This is what I have been able to produce

library(terra)
library(tidyterra)
library(ggplot2)

rast1 <- rast(ncol=100, nrow=100, nlyr=1)
rast1 <- init(rast1, "cell")
rast1 <- spatSample(rast1, ncell(rast1), "random", as.raster=TRUE)

#full raster with basic terra plotting
plot(rast1)


#try to truncate values for better viz
mean.val <- mean(terra::values(rast1), na.rm = TRUE)
low.val<- min(terra::values(rast1), na.rm = TRUE)
zmin <- max(mean.val, 0.001, na.rm = TRUE)
zmin <- max(zmin, 0.01, na.rm = TRUE)
zmax <- max(terra::values(rast1), na.rm = TRUE)
q99 <- quantile(terra::values(rast1), probs=c(0.999), na.rm=TRUE)

#Truncated raster using terra base plotting
plot(rast1, col = whitebox.colors(100), range=c(zmin,q99), axes=FALSE)

#plotting high values
plot(rast1, col="#255668", range=c(q99,zmax), axes=FALSE, legend=FALSE, add=TRUE)

#plotting low values
plot(rast1, col="#F9FFAF", range=c(low.val,zmin), axes=FALSE, legend=FALSE, add=TRUE)

Which produces this output desired ouput

#So far I can only set limits with ggplot
#mid values
ggplot() +
  geom_spatraster(data = rast1) +
  scale_fill_whitebox_c(limits = c(zmin,q99))

incomplete ggplot2 output

I would need to incorporate the remaining colors for the highest and lowest range of values. But I staill haven't figure out how to do it, without "reclassifying" the data, and I still think it may be a bit tricky because I only want to truncate to a high and low value, but retain the full range of mid values. Which I believe reclassifying wont really do.

Any input would be greatly appreciated!


Solution

  • One possible option would be to create a custom color scale using scale_fill_gradientn (which under the hood is also used by scale_fill_whitebox_c). The vectors to be passed to the colors= and values= argument are pretty straightforward with the exception that the values= argument requires a vector in the range c(0, 1) and hence require some rescaling. Finally, set oob = scales::oob_squish which in contrast to the default oob_censor "squishes" values which fall outside of the limits into the range of the scale. This way the low values are assigned the low color and the high values the high color. There is still a small drawback though as the low and (especially) the high color are visible in the legend if you take a close look.

    library(terra)
    library(tidyterra)
    library(ggplot2)
    library(scales)
    
    ggplot() +
      geom_spatraster(data = rast1) +
      scale_fill_gradientn(
        colors = c(
          "#F9FFAF",
          whitebox.colors(100),
          "#255668"
        ),
        values = scales::rescale(
          sort(c(range(terra::values(rast1)), c(zmin, q99))),
          to = c(0, 1)
        ),
        oob = scales::squish,
        limits = c(zmin, q99)
      ) +
      theme_void()
    

    enter image description here