dieghernan / tidyterra

tidyverse and ggplot2 methods for terra spatial objects

Home Page:https://dieghernan.github.io/tidyterra/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

grey scale map

aloboa opened this issue · comments

How can I display in grey scale?

> g1 <- ggplot() + 
+   geom_spatraster(data = tile, aes(fill=cyl_tile_1)) 
> g1
> g1 + scale_fill_grey()
Error: Continuous value supplied to discrete scale

To extend a bit my question: I'm trying to display individual spectral bands of multispectral images. I need plots to be in consistent grey scale, that is, an image with range 0-100 should be half bright as another in the 100-200 range.

Hi,

So I see here two issues:

  1. First, scale_fill_grey() is a discrete scale, meant to be used with categorical variables. You would need to create a
    equivalent version with scale_fill_gradientn().
  2. It is not exactly clear to me the desired output on multispectral images, but I think this is tackled automatically by ggplot2 if you use facet_wrap().

See if this code helps:

library(tidyterra)
#> ── Attaching packages ─────────────────────────────────────── tidyterra 0.2.0 ──
#> 
#> Suppress this startup message by setting Sys.setenv(tidyterra.quiet = TRUE)
#> ✔ tibble 3.1.7     ✔ dplyr  1.0.9
#> ✔ tidyr  1.2.0
library(ggplot2)

file <- system.file("extdata/cyl_tile.tif", package = "tidyterra")
tile <- terra::rast(file)


ggplot() +
  geom_spatraster(data = tile, aes(fill = cyl_tile_1)) +
  scale_fill_grey()
#> Error: Continuous value supplied to discrete scale


# Use a custom scale, scale_fill_grey is for discrete values
ggplot() +
  geom_spatraster(data = tile, aes(fill = cyl_tile_1)) +
  scale_fill_gradientn(colors = gray.colors(100,
    start = 0.2,
    end = .8, rev = TRUE
  ))

# Multiband

ggplot() +
  geom_spatraster(data = tile) +
  scale_fill_gradientn(colors = gray.colors(100,
    start = 0.2,
    end = .8, rev = TRUE
  )) +
  facet_wrap(~lyr)

# Now on multiband with different ranges
tile2 <- tile %>%
  mutate(cyl_tile_2_mod = as.integer(100 * cyl_tile_2 / 250)) %>%
  select(cyl_tile_1, cyl_tile_2_mod)


tile2
#> class       : SpatRaster 
#> dimensions  : 212, 261, 2  (nrow, ncol, nlyr)
#> resolution  : 2445.985, 2445.985  (x, y)
#> extent      : -812067, -173664.9, 4852834, 5371383  (xmin, xmax, ymin, ymax)
#> coord. ref. : WGS 84 / Pseudo-Mercator (EPSG:3857) 
#> sources     : memory  
#>               memory  
#> names       : cyl_tile_1, cyl_tile_2_mod 
#> min values  :         35,             14 
#> max values  :        253,            100

ggplot() +
  geom_spatraster(data = tile2) +
  scale_fill_gradientn(colors = gray.colors(100,
    start = 0.2,
    end = .8, rev = TRUE
  )) +
  facet_wrap(~lyr)

Created on 2022-09-20 by the reprex package (v2.0.1)

Yes, many thanks, scale_fill_gradientn() does the trick (with reverse=FALSE in my case), although I'm not sure color scales are consistent across plots.
The idea of using facet_wrap() is very good to ensure consistency. The problem is that I have to compare bands from one image to bands of another one, so I will have to select and build an intermediate multiband raster.
I have also problems with geom_spatraster_rgb(), but this goes to another ticket.

Why not normalize data so you would have the same scale?
library(terra)
norm <- function(r){
mnv <- global(r, 'min',na.rm=TRUE)
mxv <- global(r, 'max',na.rm=TRUE)
r_n <- (r - mnv$min) / (mxv$max - mnv$min)
return(r_n)
}

That is what I often do with terra::stretch(), but as the global range is known in this case (eg 0-4096), it would be easier to have a global scale.
It is not clear to me if geom_spatraster() assumes that the range is within 0-255, and whether scale_fill_gradientn() distributes de 0-1 values (0.2-0.8 in the examples above) along the actual range of the data (data often do not actually span the 0-4096 range) or a custom range can be set.

So all this issue is more related with how to work with the ggplot2 system rather than a bug or issue on tidyterra. I would suggests to ask a question on https://stackoverflow.com/questions/tagged/ggplot2.

However, I think I can put some light on this. I assume scale_fill_gradientn() distributes colors on the range of the values of the SpatRaster, but this can be adjusted with limits. On your case I would try scale_fill_gradientn(..., limits = c(0,4096)), that would re-escale the range of values of each SpatRaster to your desired range:

library(tidyterra)
#> ── Attaching packages ────────────────────────────────── tidyterra 0.2.0.9000 ──
#> 
#> Suppress this startup message by setting Sys.setenv(tidyterra.quiet = TRUE)
#> ✔ tibble 3.1.8      ✔ dplyr  1.0.10
#> ✔ tidyr  1.2.1
library(ggplot2)


r <- terra::rast(system.file("extdata/cyl_temp.tif",
  package = "tidyterra"
))

# Select two layers

r2 <- r %>%
  mutate(tavg_05_mod = tavg_05 * 2) %>%
  select(tavg_04, tavg_05_mod)

# Different ranges
r2
#> class       : SpatRaster 
#> dimensions  : 89, 116, 2  (nrow, ncol, nlyr)
#> resolution  : 3856.617, 3856.617  (x, y)
#> extent      : 2893583, 3340950, 2019451, 2362690  (xmin, xmax, ymin, ymax)
#> coord. ref. : ETRS89-extended / LAEA Europe (EPSG:3035) 
#> sources     : memory  
#>               memory  
#> names       :   tavg_04, tavg_05_mod 
#> min values  :  0.565614,    8.588204 
#> max values  : 13.283829,   33.481796

pal <- gray.colors(100, rev = FALSE)

ggplot() +
  geom_spatraster(data = r2) +
  facet_wrap(~lyr) +
  scale_fill_gradientn(colours = pal, na.value = NA)

# Adjust limits
# Note the change on the legend and the colors of the plot
ggplot() +
  geom_spatraster(data = r2) +
  facet_wrap(~lyr) +
  scale_fill_gradientn(
    colours = pal, na.value = NA,
    limits = c(0, 100)
  )

# Individual plots with the same limits
ggplot() +
  geom_spatraster(data = r2 %>% select(1)) +
  facet_wrap(~lyr) +
  scale_fill_gradientn(
    colours = pal, na.value = NA,
    limits = c(0, 100)
  )

ggplot() +
  geom_spatraster(data = r2 %>% select(2)) +
  facet_wrap(~lyr) +
  scale_fill_gradientn(
    colours = pal, na.value = NA,
    limits = c(0, 100)
  )

Created on 2022-09-20 with reprex v2.0.2

I agree with closing, but please note that some of these examples would be very useful in the doc, where there is nothing of grey scale display. Also, I would personally prefer having a grey scale by default when using geom_spatraster() instead of the current shades of blue.
Many thanks for the excellent package and your kind assistance.

Thanks, regarding

Also, I would personally prefer having a grey scale by default when using geom_spatraster() instead of the current shades of blue.

This is to be tackle on ggplot2 not tidyterra. The default blue palette is the one in use for ggplot2:

library(ggplot2)

# Default palette on ggplot2
ggplot(faithfuld, aes(waiting, eruptions, fill = density)) +
  geom_tile()

In the previous example tidyterra is not involved. But good news, as per ?scale_colour_continuous the default continuous palette con be changed with options(ggplot2.continuous.fill). See how we can change it to make a grey color palette as the default.

# Can be changed
# Get default
tmp <- getOption("ggplot2.continuous.fill") # store current setting

# Change the default
# Custom default palette function
scale_fill_cont_grey <- function(...) {
  greycols <- grey.colors(2)
  scale_fill_gradient(..., low = greycols[1], high = greycols[2], na.value = NA)
}
# Now make it by default
options(ggplot2.continuous.fill = scale_fill_cont_grey)

ggplot(faithfuld, aes(waiting, eruptions, fill = density)) +
  geom_tile()

In the previous example, we didn't need to add scale_fill_gradient(), so now after setting the option all your plots would have this new palette as the default palette.

Now see how this plays with tidyterra:

# Check with tidyterra
library(tidyterra)
#> ── Attaching packages ─────────────────────────────────────── tidyterra 0.2.0 ──
#> 
#> Suppress this startup message by setting Sys.setenv(tidyterra.quiet = TRUE)
#> ✔ tibble 3.1.7     ✔ dplyr  1.0.9
#> ✔ tidyr  1.2.0

r <- terra::rast(system.file("extdata/cyl_temp.tif", package = "tidyterra"))

ggplot() +
  geom_spatraster(data = r) +
  facet_wrap(~lyr)

We got it! By last, restore the ggplot blue palette as the default palette:

# Restore the ggplot2 default option

options(ggplot2.continuous.fill = tmp)

# Now back to blue

ggplot() +
  geom_spatraster(data = r) +
  facet_wrap(~lyr)

Created on 2022-09-21 by the reprex package (v2.0.1)