Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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())


like image 418
reinoud Avatar asked Sep 03 '25 03:09

reinoud


1 Answers

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

like image 87
Kat Avatar answered Sep 05 '25 01:09

Kat