rggplot2animationvisualizationgganimate

How to dynamically adjust plot background colour in ggplot2 based on sun elevation?


I've created an animated plot in ggplot2 that visualizes the GPS tracks of a diurnal species over time. To improve the visualisation, I would like to overlay a semi-transparent layer that dynamically changes based on whether it is day or night. I don't want to use static values, but rather use the actual sun elevation relative to the horizon. I've supplemented my dataset with the sun positions using the suncalc package. A reproducible example with dummy data can be found below. Anyone who can help me in the right direction?

library(ggplot2)
library(gganimate)
library(sf)
library(suncalc)
library(ggspatial)  
library(gifski)
library(prettymapr)


#  sample data
set.seed(123)
gps_data_sf <- data.frame(
  IMEI = rep(35664665, 300),  
  Pick.Time = seq.POSIXt(as.POSIXct("2024-08-13 00:00:00"), by = "15 mins", length.out = 300),
  Longitude = runif(300, 2.7, 2.8),
  Latitude = runif(300, 51.1, 51.2),
  Bird = rep("Bird A", 300)
)

# add sun height
gps_data_sf$sunheight <- mapply(function(lat, lon, time) {
  sun_position <- getSunlightPosition(date = time, lat = lat, lon = lon)
  return(sun_position$altitude) 
}, gps_data_sf$Latitude, gps_data_sf$Longitude, gps_data_sf$Pick.Time)

# Convert to sf 
gps_data_sf <- st_as_sf(gps_data_sf, coords = c("Longitude", "Latitude"), crs = 4326)

# animate
animated_plot <- ggplot() +
  annotation_map_tile(type = "osm", zoom = 12) + 
  geom_sf(data = gps_data_sf, aes(geometry = geometry, color = Bird, group = Bird), size = 5, alpha = 0.8) +
  labs(
    title = "Movement",
    subtitle = "Time: {frame_time}",
    x = "Longitude",
    y = "Latitude"
  ) +
  transition_time(Pick.Time) + 
  ease_aes('linear')

# Render
animate(animated_plot, nframes = 100, fps = 10, width = 800, height = 600, renderer = gifski_renderer())



Solution

  • Using geom_rect, along with scale_fill_gradient, can create the look you're trying to achieve.

    First, I moved the data and grouping to ggplot, so that it can be inherited by geom_rect.

    When calling geom_rect with an sf, if you want to fill the entire plot, you can use -Inf to Inf. I used an alpha of .4 for this layer arbitrarily.

    Lastly, using scale_fill_gradient created a high and low color gradient to emulate the sun. (I used black and yellow for night and day.)

    The call for geom_sf is the same as in your question, less the arguments that were moved to ggplot() (data and group).

    # animate
    animated_plot <- ggplot(gps_data_sf, group = Bird) +   # move data and grouping to ggplot
      annotation_map_tile(type = "osm", zoom = 12) + 
      geom_sf(aes(geometry = geometry, color = Bird),
              size = 5, alpha = 0.8) +
      geom_rect(xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf,  # add color space
                aes(fill = sunheight), alpha = .4) +
      scale_fill_gradient(low = "black", high = "yellow") +        # specify colors
      labs(
        title = "Movement", subtitle = "Time: {frame_time}",
        x = "Longitude", y = "Latitude") +
      transition_time(Pick.Time) + ease_aes('linear')
    

    A few images so you can see how the gradient overlay changes (this is 3am and 10am)

    at 02:45 at 10:08