Nothing
#' Gradient scales for representing hypsometry and bathymetry
#'
#' @description
#'
#' Implementation of a selection of gradient palettes available in
#' [cpt-city](http://seaviewsensing.com/pub/cpt-city/).
#'
#' The following scales and palettes are provided:
#'
#' - `scale_*_hypso_d()`: For discrete values.
#' - `scale_*_hypso_c()`: For continuous values.
#' - `scale_*_hypso_b()`: For binning continuous values.
#' - `hypso.colors()`: A gradient color palette. See also
#' [grDevices::terrain.colors()] for details.
#'
#' An additional set of scales is provided. These scales can act as
#' [hypsometric (or bathymetric)
#' tints](https://en.wikipedia.org/wiki/Hypsometric_tints).
#'
#' - `scale_*_hypso_tint_d()`: For discrete values.
#' - `scale_*_hypso_tint_c()`: For continuous values.
#' - `scale_*_hypso_tint_b()`: For binning continuous values.
#' - `hypso.colors2()`: A gradient color palette. See also
#' [grDevices::terrain.colors()] for details.
#'
#' See **Details**.
#'
#' Additional parameters `...` would be passed on to:
#' - Discrete values: [ggplot2::discrete_scale()].
#' - Continuous values: [ggplot2::continuous_scale()].
#' - Binned continuous values: [ggplot2::binned_scale()].
#'
#' **Note that** \CRANpkg{tidyterra} just documents a selection of these
#' additional parameters, check the \CRANpkg{ggplot2} functions listed above to
#' see the full range of parameters accepted by these scales.
#'
#' @export
#'
#' @name scale_hypso
#'
#' @inheritDotParams ggplot2::discrete_scale breaks:drop
#' @inheritDotParams ggplot2::continuous_scale breaks:labels
#' @inheritDotParams ggplot2::binned_scale breaks:limits nice.breaks
#' @inheritParams scale_cross_blended
#' @param palette A valid palette name. The name is matched to the list of
#' available palettes, ignoring upper vs. lower case. See
#' [hypsometric_tints_db] for more info. Values available are:
#'
#' ```{r, echo=FALSE, results="asis", message = FALSE, warning = FALSE}
#'
#' suppressPackageStartupMessages(library(dplyr))
#' hypsometric_tints_db %>%
#' pull(pal) %>%
#' unique() %>%
#' paste0('`"', ., '"`', collapse = ", ") %>%
#' paste0(".") %>%
#' cat()
#'
#'
#' ```
#' @seealso [hypsometric_tints_db], [terra::plot()], [terra::minmax()],
#' [ggplot2::scale_fill_viridis_c()]
#'
#' See also \CRANpkg{ggplot2} docs on additional `...` parameters.
#'
#' @return
#' The corresponding \CRANpkg{ggplot2} layer with the values applied to the
#' `fill/colour` aesthetics.
#'
#' @family gradients
#'
#' @source
#'
#' cpt-city: <http://seaviewsensing.com/pub/cpt-city/>.
#'
#' @examples
#' \donttest{
#' filepath <- system.file("extdata/volcano2.tif", package = "tidyterra")
#'
#' library(terra)
#' volcano2_rast <- rast(filepath)
#'
#' # Palette
#' plot(volcano2_rast, col = hypso.colors(100, palette = "wiki-2.0_hypso"))
#'
#' # Palette with uneven colors
#' plot(volcano2_rast, col = hypso.colors2(100, palette = "wiki-2.0_hypso"))
#'
#'
#' library(ggplot2)
#' ggplot() +
#' geom_spatraster(data = volcano2_rast) +
#' scale_fill_hypso_c(palette = "colombia_hypso")
#'
#'
#' # Full map with true tints
#'
#' f_asia <- system.file("extdata/asia.tif", package = "tidyterra")
#' asia <- rast(f_asia)
#'
#' ggplot() +
#' geom_spatraster(data = asia) +
#' scale_fill_hypso_tint_c(
#' palette = "etopo1",
#' labels = scales::label_number(),
#' breaks = c(-10000, 0, 5000, 8000),
#' guide = guide_colorbar(reverse = TRUE)
#' ) +
#' labs(fill = "elevation (m)") +
#' theme(
#' legend.position = "bottom",
#' legend.title.position = "top",
#' legend.key.width = rel(3),
#' legend.ticks = element_line(colour = "black", linewidth = 0.3),
#' legend.direction = "horizontal"
#' )
#'
#' # Binned
#' ggplot() +
#' geom_spatraster(data = volcano2_rast) +
#' scale_fill_hypso_b(breaks = seq(70, 200, 25), palette = "wiki-2.0_hypso")
#'
#' # With breaks
#' ggplot() +
#' geom_spatraster(data = volcano2_rast) +
#' scale_fill_hypso_b(
#' breaks = seq(75, 200, 25),
#' palette = "wiki-2.0_hypso"
#' )
#'
#' # With discrete values
#' factor <- volcano2_rast %>% mutate(cats = cut(elevation,
#' breaks = c(100, 120, 130, 150, 170, 200),
#' labels = c(
#' "Very Low", "Low", "Average", "High",
#' "Very High"
#' )
#' ))
#'
#' ggplot() +
#' geom_spatraster(data = factor, aes(fill = cats)) +
#' scale_fill_hypso_d(na.value = "gray10", palette = "dem_poster")
#'
#'
#' # Tint version
#' ggplot() +
#' geom_spatraster(data = factor, aes(fill = cats)) +
#' scale_fill_hypso_tint_d(na.value = "gray10", palette = "dem_poster")
#' }
scale_fill_hypso_d <- function(palette = "etopo1_hypso", ...,
alpha = 1, direction = 1,
na.translate = FALSE,
drop = TRUE) {
if (alpha < 0 || alpha > 1) {
cli::cli_abort("{.arg alpha} {.field {alpha}} not in {.field [0,1]}")
}
if (!direction %in% c(-1, 1)) {
cli::cli_abort("{.arg direction} must be {.field 1} or {.field -1}")
}
ggplot2::discrete_scale(
aesthetics = "fill",
palette = hypso_pal(
alpha = alpha,
direction = direction,
palette = palette
),
na.translate = na.translate,
drop = drop,
...
)
}
#' @export
#' @rdname scale_hypso
scale_colour_hypso_d <- function(palette = "etopo1_hypso", ...,
alpha = 1, direction = 1,
na.translate = FALSE,
drop = TRUE) {
if (alpha < 0 || alpha > 1) {
cli::cli_abort("{.arg alpha} {.field {alpha}} not in {.field [0,1]}")
}
if (!direction %in% c(-1, 1)) {
cli::cli_abort("{.arg direction} must be {.field 1} or {.field -1}")
}
ggplot2::discrete_scale(
aesthetics = "colour",
palette = hypso_pal(
alpha = alpha,
direction = direction,
palette = palette
),
na.translate = na.translate,
drop = drop,
...
)
}
#' @export
#' @rdname scale_hypso
scale_fill_hypso_c <- function(palette = "etopo1_hypso", ...,
alpha = 1, direction = 1,
na.value = "transparent", guide = "colourbar") {
if (alpha < 0 || alpha > 1) {
cli::cli_abort("{.arg alpha} {.field {alpha}} not in {.field [0,1]}")
}
if (!direction %in% c(-1, 1)) {
cli::cli_abort("{.arg direction} must be {.field 1} or {.field -1}")
}
length_pal <- nrow(extract_pal(tidyterra::hypsometric_tints_db,
palette = palette
))
ggplot2::continuous_scale(
aesthetics = "fill",
palette = scales::gradient_n_pal(hypso_pal(
alpha = alpha,
direction = direction,
palette = palette
)(length_pal)),
na.value = na.value,
guide = guide,
...
)
}
#' @export
#' @rdname scale_hypso
scale_colour_hypso_c <- function(palette = "etopo1_hypso", ...,
alpha = 1, direction = 1,
na.value = "transparent",
guide = "colourbar") {
if (alpha < 0 || alpha > 1) {
cli::cli_abort("{.arg alpha} {.field {alpha}} not in {.field [0,1]}")
}
if (!direction %in% c(-1, 1)) {
cli::cli_abort("{.arg direction} must be {.field 1} or {.field -1}")
}
length_pal <- nrow(extract_pal(tidyterra::hypsometric_tints_db,
palette = palette
))
ggplot2::continuous_scale(
aesthetics = "colour",
palette = scales::gradient_n_pal(hypso_pal(
alpha = alpha,
direction = direction,
palette = palette
)(length_pal)),
na.value = na.value,
guide = guide,
...
)
}
#' @export
#' @rdname scale_hypso
scale_fill_hypso_b <- function(palette = "etopo1_hypso", ...,
alpha = 1, direction = 1,
na.value = "transparent",
guide = "coloursteps") {
if (alpha < 0 || alpha > 1) {
cli::cli_abort("{.arg alpha} {.field {alpha}} not in {.field [0,1]}")
}
if (!direction %in% c(-1, 1)) {
cli::cli_abort("{.arg direction} must be {.field 1} or {.field -1}")
}
length_pal <- nrow(extract_pal(tidyterra::hypsometric_tints_db,
palette = palette
))
ggplot2::binned_scale(
aesthetics = "fill",
palette = scales::gradient_n_pal(hypso_pal(
alpha = alpha,
direction = direction,
palette = palette
)(length_pal)),
na.value = na.value,
guide = guide,
...
)
}
#' @export
#' @rdname scale_hypso
scale_colour_hypso_b <- function(palette = "etopo1_hypso", ...,
alpha = 1, direction = 1,
na.value = "transparent",
guide = "coloursteps") {
if (alpha < 0 || alpha > 1) {
cli::cli_abort("{.arg alpha} {.field {alpha}} not in {.field [0,1]}")
}
if (!direction %in% c(-1, 1)) {
cli::cli_abort("{.arg direction} must be {.field 1} or {.field -1}")
}
length_pal <- nrow(extract_pal(tidyterra::hypsometric_tints_db,
palette = palette
))
ggplot2::binned_scale(
aesthetics = "colour",
palette = scales::gradient_n_pal(hypso_pal(
alpha = alpha,
direction = direction,
palette = palette
)(length_pal)),
na.value = na.value,
guide = guide,
...
)
}
#' @export
#' @rdname scale_hypso
#'
#' @inheritParams wiki.colors
#' @examples
#' # Display all the cpl_city palettes
#'
#' pals <- unique(hypsometric_tints_db$pal)
#'
#' # Helper fun for plotting
#'
#' ncols <- 128
#' rowcol <- grDevices::n2mfrow(length(pals))
#'
#' opar <- par(no.readonly = TRUE)
#' par(mfrow = rowcol, mar = rep(1, 4))
#'
#' for (i in pals) {
#' image(
#' x = seq(1, ncols), y = 1, z = as.matrix(seq(1, ncols)),
#' col = hypso.colors(ncols, i), main = i,
#' ylab = "", xaxt = "n", yaxt = "n", bty = "n"
#' )
#' }
#' par(opar)
hypso.colors <- function(n, palette = "etopo1_hypso",
alpha = 1, rev = FALSE) {
if ((n <- as.integer(n[1L])) > 0) {
paltab <- extract_pal(tidyterra::hypsometric_tints_db, palette = palette)
colors <- as.character(paltab$hex)
endcols <- tidyterra_ramp(colors, n, alpha, rev)
return(endcols)
} else {
character()
}
}
#' @export
#' @rdname scale_hypso
#' @details
#'
#' On `scale_*_hypso_tint_*` palettes, the position of the gradients and
#' the limits of the palette are redefined. Instead of treating the color
#' palette as a continuous gradient, they are rescaled to act as a hypsometric
#' tint. A rough description of these tints are:
#' - Blue colors: Negative values.
#' - Green colors: 0 to 1.000 values.
#' - Browns: 1000 to 4.000 values.
#' - Whites: Values higher than 4.000.
#'
#' The following orientation would vary depending on the palette definition
#' (see [hypsometric_tints_db] for an example on how this could be achieved).
#'
#' Note that the setup of the palette may not be always suitable for your
#' specific data. For example, a `SpatRaster` of small parts of the globe (and
#' with a limited range of elevations) may not be well represented. As an
#' example, a `SpatRaster` with a range of values on `[100, 200]` would appear
#' almost as an uniform color. This could be adjusted using the
#' `limits`/`values` parameters.
#'
#' When passing `limits` parameter to `scale_*_hypso_tint_*` the colors would
#' be restricted of those specified by this parameter, keeping the distribution
#' of the tint. You can combine this with `oob` (i.e.
#' `oob = scales::oob_squish`) to avoid blank pixels in the plot.
#'
#'
#' `hypso.colors2()` provides a gradient color palette where the distance
#' between colors is different depending of the type of color. In contrast,
#' `hypso.colors()` provides an uniform gradient across colors. See
#' **Examples**.
#'
scale_fill_hypso_tint_d <- function(palette = "etopo1_hypso", ...,
alpha = 1, direction = 1,
na.translate = FALSE,
drop = TRUE) {
if (alpha < 0 || alpha > 1) {
cli::cli_abort("{.arg alpha} {.field {alpha}} not in {.field [0,1]}")
}
if (!direction %in% c(-1, 1)) {
cli::cli_abort("{.arg direction} must be {.field 1} or {.field -1}")
}
ggplot2::discrete_scale(
aesthetics = "fill",
palette = hypso_pal2(
alpha = alpha,
direction = direction,
palette = palette
),
na.translate = na.translate,
drop = drop,
...
)
}
#' @export
#' @rdname scale_hypso
scale_colour_hypso_tint_d <- function(palette = "etopo1_hypso", ...,
alpha = 1, direction = 1,
na.translate = FALSE,
drop = TRUE) {
if (alpha < 0 || alpha > 1) {
cli::cli_abort("{.arg alpha} {.field {alpha}} not in {.field [0,1]}")
}
if (!direction %in% c(-1, 1)) {
cli::cli_abort("{.arg direction} must be {.field 1} or {.field -1}")
}
ggplot2::discrete_scale(
aesthetics = "colour",
palette = hypso_pal2(
alpha = alpha,
direction = direction,
palette = palette
),
na.translate = na.translate,
drop = drop,
...
)
}
#' @export
#' @rdname scale_hypso
scale_fill_hypso_tint_c <- function(palette = "etopo1_hypso", ...,
alpha = 1, direction = 1,
values = NULL,
limits = NULL,
na.value = "transparent",
guide = "colourbar") {
if (alpha < 0 || alpha > 1) {
cli::cli_abort("{.arg alpha} {.field {alpha}} not in {.field [0,1]}")
}
if (!direction %in% c(-1, 1)) {
cli::cli_abort("{.arg direction} must be {.field 1} or {.field -1}")
}
# Use pal limits
coltab <- tidyterra::hypsometric_tints_db
if (!palette %in% coltab$pal) {
cli::cli_abort(paste(
"{.arg palette} {.val palette} does not match any given palette.",
"See {.help tidyterra::hypsometric_tints_db}"
))
}
hypsocol <- coltab[coltab$pal == palette, ]
hexcol <- as.character(hypsocol$hex)
if (direction == -1) hexcol <- rev(hexcol)
if (alpha != 1) hexcol <- ggplot2::alpha(hexcol, alpha = alpha)
if (is.null(values)) values <- hypsocol$limit
# Reescale
if (is.null(limits)) limits <- range(values)
res <- scales::rescale(values, from = limits)
ggplot2::scale_fill_gradientn(...,
colors = hexcol,
values = res,
limits = limits,
na.value = na.value,
guide = guide
)
}
#' @export
#' @rdname scale_hypso
scale_colour_hypso_tint_c <- function(palette = "etopo1_hypso", ...,
alpha = 1, direction = 1,
values = NULL,
limits = NULL,
na.value = "transparent",
guide = "colourbar") {
if (alpha < 0 || alpha > 1) {
cli::cli_abort("{.arg alpha} {.field {alpha}} not in {.field [0,1]}")
}
if (!direction %in% c(-1, 1)) {
cli::cli_abort("{.arg direction} must be {.field 1} or {.field -1}")
}
# Use pal limits
coltab <- tidyterra::hypsometric_tints_db
if (!palette %in% coltab$pal) {
cli::cli_abort(paste(
"{.arg palette} {.val palette} does not match any given palette.",
"See {.help tidyterra::hypsometric_tints_db}"
))
}
hypsocol <- coltab[coltab$pal == palette, ]
hexcol <- as.character(hypsocol$hex)
if (direction == -1) hexcol <- rev(hexcol)
if (alpha != 1) hexcol <- ggplot2::alpha(hexcol, alpha = alpha)
if (is.null(values)) values <- hypsocol$limit
# Reescale
if (is.null(limits)) limits <- range(values)
res <- scales::rescale(values, from = limits)
ggplot2::scale_colour_gradientn(...,
colors = hexcol,
values = res,
limits = limits,
na.value = na.value,
guide = guide
)
}
#' @export
#' @rdname scale_hypso
scale_fill_hypso_tint_b <- function(palette = "etopo1_hypso", ...,
alpha = 1, direction = 1,
values = NULL,
limits = NULL,
na.value = "transparent",
guide = "coloursteps") {
if (alpha < 0 || alpha > 1) {
cli::cli_abort("{.arg alpha} {.field {alpha}} not in {.field [0,1]}")
}
if (!direction %in% c(-1, 1)) {
cli::cli_abort("{.arg direction} must be {.field 1} or {.field -1}")
}
# Use pal limits
coltab <- tidyterra::hypsometric_tints_db
if (!palette %in% coltab$pal) {
cli::cli_abort(paste(
"{.arg palette} {.val palette} does not match any given palette.",
"See {.help tidyterra::hypsometric_tints_db}"
))
}
hypsocol <- coltab[coltab$pal == palette, ]
hexcol <- as.character(hypsocol$hex)
if (direction == -1) hexcol <- rev(hexcol)
if (alpha != 1) hexcol <- ggplot2::alpha(hexcol, alpha = alpha)
if (is.null(values)) values <- hypsocol$limit
# Reescale
if (is.null(limits)) limits <- range(values)
res <- scales::rescale(values, from = limits)
ggplot2::scale_fill_stepsn(
...,
colors = hexcol,
values = res,
limits = limits,
na.value = na.value,
guide = guide
)
}
#' @export
#' @rdname scale_hypso
scale_colour_hypso_tint_b <- function(palette = "etopo1_hypso", ...,
alpha = 1, direction = 1,
values = NULL,
limits = NULL,
na.value = "transparent",
guide = "coloursteps") {
if (alpha < 0 || alpha > 1) {
cli::cli_abort("{.arg alpha} {.field {alpha}} not in {.field [0,1]}")
}
if (!direction %in% c(-1, 1)) {
cli::cli_abort("{.arg direction} must be {.field 1} or {.field -1}")
}
# Use pal limits
coltab <- tidyterra::hypsometric_tints_db
if (!palette %in% coltab$pal) {
cli::cli_abort(paste(
"{.arg palette} {.val palette} does not match any given palette.",
"See {.help tidyterra::hypsometric_tints_db}"
))
}
hypsocol <- coltab[coltab$pal == palette, ]
hexcol <- as.character(hypsocol$hex)
if (direction == -1) hexcol <- rev(hexcol)
if (alpha != 1) hexcol <- ggplot2::alpha(hexcol, alpha = alpha)
if (is.null(values)) values <- hypsocol$limit
# Reescale
if (is.null(limits)) limits <- range(values)
res <- scales::rescale(values, from = limits)
ggplot2::scale_colour_stepsn(
...,
colors = hexcol,
values = res,
limits = limits,
na.value = na.value,
guide = guide
)
}
#' @export
#' @rdname scale_hypso
#' @examples
#' # Display all the cpl_city palettes on version 2
#'
#' pals <- unique(hypsometric_tints_db$pal)
#'
#' # Helper fun for plotting
#'
#' ncols <- 128
#' rowcol <- grDevices::n2mfrow(length(pals))
#'
#' opar <- par(no.readonly = TRUE)
#' par(mfrow = rowcol, mar = rep(1, 4))
#'
#' for (i in pals) {
#' image(
#' x = seq(1, ncols), y = 1, z = as.matrix(seq(1, ncols)),
#' col = hypso.colors2(ncols, i), main = i,
#' ylab = "", xaxt = "n", yaxt = "n", bty = "n"
#' )
#' }
#' par(opar)
hypso.colors2 <- function(n, palette = "etopo1_hypso",
alpha = 1, rev = FALSE) {
if ((n <- as.integer(n[1L])) > 0) {
paltab <- extract_pal(tidyterra::hypsometric_tints_db, palette = palette)
colors <- as.character(paltab$hex)
limits <- sort(as.integer(paltab$limit))
endcols <- tidyterra_ramp2(colors, n, alpha, rev, limits)
return(endcols)
} else {
character()
}
}
# Helpers
tidyterra_ramp2 <- function(colors, n, alpha = 1, rev = FALSE, limits) {
if (rev) colors <- rev(colors)
# Rescale limits
limits <- scales::rescale(limits)
fn_cols <- scales::gradient_n_pal(colors, values = limits)
endcols <- fn_cols(seq(0, 1, length.out = n))
if (alpha != 1) endcols <- ggplot2::alpha(endcols, alpha)
return(endcols)
}
hypso_pal <- function(alpha = 1, direction = 1, palette) {
# nocov start
function(n) {
pal <- hypso.colors(n,
rev = direction != 1, alpha = alpha,
palette = palette
)
pal
}
# nocov end
}
hypso_pal2 <- function(alpha = 1, direction = 1, palette) {
# nocov start
function(n) {
pal <- hypso.colors2(n,
rev = direction != 1, alpha = alpha,
palette = palette
)
pal
}
# nocov end
}
#' @export
#' @rdname scale_hypso
#' @usage NULL
scale_color_hypso_d <- scale_colour_hypso_d
#' @export
#' @rdname scale_hypso
#' @usage NULL
scale_color_hypso_tint_d <- scale_colour_hypso_tint_d
#' @export
#' @rdname scale_hypso
#' @usage NULL
scale_color_hypso_c <- scale_colour_hypso_c
#' @export
#' @rdname scale_hypso
#' @usage NULL
scale_color_hypso_tint_c <- scale_colour_hypso_tint_c
#' @export
#' @rdname scale_hypso
#' @usage NULL
scale_color_hypso_b <- scale_colour_hypso_b
#' @export
#' @rdname scale_hypso
#' @usage NULL
scale_color_hypso_tint_b <- scale_colour_hypso_tint_b
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.