Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

variable-dependent fills not rendering in geoms stacked on geom_spatial_rgb #72

Closed
alanfarahani opened this issue Aug 15, 2024 · 2 comments

Comments

@alanfarahani
Copy link

Hi all,

I am wondering if I have encountered a bug or do not yet fully grasp the syntax of the (excellent) geom_spatial_rgb. In short, the issue I am encountering is that when a fill is set to a variable in a geom that is stacked on top of a geom_spatial_rgb object, that fill does not render, and in fact, interrupts the code computation or else generates some seriously funky colors.

Here is a reprex:

library(terrainr)
library(sf)

location_of_interest <- tmaptools::geocode_OSM("Hyampom California")$coords

location_of_interest <- data.frame(
  x = location_of_interest[["x"]],
  y = location_of_interest[["y"]]
)

location_of_interest <- st_as_sf(
  location_of_interest, 
  coords = c("x", "y"), 
  crs = 4326
)

location_of_interest <- set_bbox_side_length(location_of_interest, 8000)

output_tiles <- get_tiles(location_of_interest,
                          services = c("elevation", "ortho"),
                          resolution = 30 # pixel side length in meters
)

library(ggplot2)

# works
ggplot() + 
  geom_spatial_rgb(data = output_tiles[["ortho"]],
                   aes(x = x, y = y, r = red, g = green, b = blue)) + 
  coord_sf(crs = 4326) + 
  theme_void()

# generate random points
random_points = st_sf(
  geom = st_sample(location_of_interest, 10),
  label = sample(100:500, 10)
)

random_points_df = random_points |> 
  as.data.frame() |> 
  mutate(x = st_coordinates(random_points$geom)[,1],
         y = st_coordinates(random_points$geom)[,2]
  ) |> 
  select(-geom)

# works for regular points
ggplot() + 
  geom_spatial_rgb(data = output_tiles[["ortho"]],
                   aes(x = x, y = y, r = red, g = green, b = blue)) + 
  geom_point(data = random_points_df, aes(x = x, y = y), size = 4) +
  coord_sf(crs = 4326) + 
  theme_void()

# works with a color
ggplot() + 
  geom_spatial_rgb(data = output_tiles[["ortho"]],
                   aes(x = x, y = y, r = red, g = green, b = blue)) + 
  geom_point(data = random_points_df, aes(x = x, y = y, color = label), size = 4) +
  coord_sf(crs = 4326) + 
  theme_void()

# does not work with a fill!
# setting label to as.character() has no impact either
ggplot() + 
  geom_spatial_rgb(data = output_tiles[["ortho"]], aes(x = x, y = y, r = red, g = green, b = blue)) + 
  geom_point(data = random_points_df, aes(x = x, y = y, fill = label), shape = 21, size = 4) +
  coord_sf(crs = 4326) + 
  theme_void()
Session Info
 version  R version 4.3.0 (2023-04-21 ucrt)
 os       Windows 10 x64 (build 19045)
 system   x86_64, mingw32

ggplot2           * 3.4.4     2023-10-12 [1] CRAN (R 4.3.2)
terrainr          * 0.7.5     2023-10-04 [1] CRAN (R 4.3.3)
@mikemahoney218
Copy link
Member

So this is a ggplot thing, not a terrainr thing, and it comes down to a relatively simple explanation: there's no easy way to have more than one fill in a single plot. Under the hood, geom_spatial_rgb() is using the fill argument to color in your raster, and so adding another fill layer on top winds up messing things up.

This is why color works: it's a different aesthetic, so there's no conflict.

As for workarounds... I unfortunately don't have a ton for you. One option is to use the old, seemingly abandoned relayer package (unfortunately ggnewscale doesn't seem to work), as so:

# following from your example above
ggplot() + 
  geom_spatial_rgb(data = output_tiles[["ortho"]], aes(x = x, y = y, r = red, g = green, b = blue)) + 
  # note this needs to be a discrete value passed to fill; continuous doesn't work
  geom_point(data = random_points_df, aes(x = x, y = y, fill2 = factor(label)), shape = 21, size = 4) |> # note the pipe!
    relayer::rename_geom_aes(c("fill" = "fill2"))  +
  coord_sf(crs = 4326)

image

It's not a great workaround (note the no legend, for instance) but it's something.

Apologies I don't have a better solution for you here; my understanding is that this (the assumption that only one variable can be mapped to an individual aesthetic) is baked deep into how ggplot works.

@alanfarahani
Copy link
Author

alanfarahani commented Aug 16, 2024

Thanks for this very helpful response (and workaround), Mike. It makes perfect sense that there can't be two fills for fundamental ggplot reasons.

What is interesting is that fills do work so long as they are outside of the aes, i.e. static calls rather than variable dependent. Yet another workaround is to define the fills in advance (i.e. by mapping them onto values), and then call the vector of colors.

Here's an example building off the previous reprex in case anyone is interested:

label_scale <- cut(scale(random_points_df$label), breaks = seq(from = -2, to = 2,  len = 100), include.lowest = T)

my.cols <- RColorBrewer::brewer.pal(3, "RdBu")

random_points_df$label_cols = colorRampPalette(my.cols)(99)[label_scale]

ggplot() + 
    geom_spatial_rgb(data = output_tiles[["ortho"]], aes(x = x, y = y, r = red, g = green, b = blue)) + 
    geom_point(data = random_points_df, aes(x = x, y = y, size = label), fill = random_points_df$label_cols, shape = 21) +
    coord_sf(crs = 4326) +
    scale_size_continuous(breaks = seq(from = 0, to = 500, by = 100), limits = c(100, 500))

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants