Animating time series on a map using Solar Analytic data and R’s gganimate package!

Objectives

  • Find package that can easily create time series animations on a map
  • Animates up to a day’s worth of 5-minute data
  • Handles 1000 + sites
  • Shows chosen areas on a basic map — determined by input of longitude and latitude coordinates
  • Ability to control the speed of the animation
  • Saves animation as a gif

Sample Data

I created the following data frame in Python:

Input data can be accessed here: https://gist.github.com/claudiasolar/77d5bd6f59d18ea708a3ef31f3351f28
  • lon, lat: GPS coordinates of this site. NOTE: I have added noise to these fields for privacy purposes.
  • t_stamp: 5-minute time stamps for each site between 7am and 8pm on the 2/3/2018.
  • energy _norm: normalised values of energy produced in each time stamp on a scale of 0 to 1.
  • over_voltage: Whether or not the network voltage at the site was above the specification (voltage > 253 V) in that time stamp.

Required Packages

library(readr)library(dplyr)library(ggplot2)library(ggthemes)library(gganimate)

Main Aspects of Function

Arguments

  • df: data frame with all required information
  • location_id (column reference): indicates column to order df by, such that the animation recognises which points correspond to one another. E.g. df$site_id
  • time_stamp (column reference): indicates column of time to iterate frames over
  • colour_col (column reference): indicates column which point colour is based off, this is what we are trying to graph e.g. df$energy_norm or df$over_voltage
  • selected_title (string): title of animated map e.g. ‘Energy Generated’ or ‘Over Voltage’
  • point_colours (vector): gives colours of points on map or colours to define a colour scale e.g. c(‘#7a4eb7’, ‘#FF6100’) or c(‘#000000’, ‘#fec533’, ‘#fec533’, ‘#fec533’, ‘#f99f1e’, ‘#f99f1e’, ‘#f99f1e’, ‘#FF6100’, ‘#FF6100’, ‘#F90707’, ‘#F90707’)
  • num_frames (integer): The number of frames to render (default 100), e.g. 280
  • dur (integer): The length of the animation in seconds (unset by default) e.g. 50.
  • latitude_lim (vector of length = 2): governs longitude shown on map e.g. c(20, 40)

Order df

It is extremely important to correctly order your data frame. I have included the argument location_id in the function so that the df input is appropriately ordered:

df = df[order(location_id),]
location_id = df$energy_norm
location_id = df$site_id

Create Map

  • Choose general map location
  • dictate colour of map
  • Choose gps coordinates to determine area covered by map,
  • Add and edit titles and legend aesthetics
base_map <- ggplot() +
            borders("world", region = 'Australia', 
                    colour = "gray85", fill = "gray80") +
            theme_map() +
            coord_fixed(xlim = longitude_lim, ylim = latitude_lim) +
            labs(title = selected_title, 
                 subtitle = '{closest_state}') +
            theme(plot.title = element_text(hjust = 0, size = 14, 
                                            lineheight = .5), 
                  plot.subtitle = element_text(hjust = 0, size = 12,
                                               lineheight = 0),
                  legend.position = c(0,.1), 
                  legend.title = element_blank(),
                  legend.text = element_text(size = 12))

Determine colouring of points on map

  • If colour_level is a column of characters than point_colours should contain as many distinct colours as there are distinct entries in this column so that it may map a colour to each unique entry.
  • the first and last colour in vector point_colours are used to create a colour scale if colour_level is a column of integers
colour_length = length(point_colours)if (typeof(colour_col) == 'character') {
  coloured <- scale_color_manual(values = point_colours)
}
else {
  coloured <- scale_color_gradientn(colours = point_colours)
}

Animate Map

  • combine base_map, coloured, geom_point from ggplot2 and transition_states from gganimate to create our animation
  • alpha argument in geom_point specifies transparency of points
  • transition_length argument in transition_states determines the relative length of the transition.
  • animate from gganimate controls animation speed and length
anim <- base_map +
        coloured +
        geom_point(aes(x = lon, y = lat, color = colour_col), 
                   data = df, alpha = .5) +
        transition_states(state_col, transition_length = 0)anim <- animate(anim, nframes = num_fr, duration = dur

Examples

  • Melbourne Over Voltage
animated_map(df = be,
             order_col = be$site_id,
             state_col = be$t_stamp,
             colour_col = be$over_voltage,
             selected_title = 'Over Voltage',
             point_colours = c('#7a4eb7', '#FF6100'),
             num_fr = 280,
             dur = 50)
animated_map(df = be,
             order_col = be$site_id,
             state_col = be$t_stamp,
             colour_col = be$energy_norm,
             selected_title = 'Solar Generation',
             point_colours = c('#000000', '#fec533', '#fec533', 
                               '#fec533', '#f99f1e', '#f99f1e', 
                               '#f99f1e', '#FF6100', '#FF6100', 
                               '#F90707', '#F90707'),
             num_fr = 280,
             dur = 50)

Saving Animation

Save the last animation created as a gif:

anim_save(‘/example.gif’)

Entire Function

animated_map = function(df, 
                        order_col, 
                        state_col, 
                        colour_col, 
                        selected_title, 
                        point_colours, 
                        num_fr, 
                        dur,
                        longitude_lim, 
                        latitude_lim) {  # ensure df is ordered correctly
  df = df[order(order_col),]  # Check for longitude and latitude limits
  # if missing automatically set them
  if (missing(longitude_lim)) {
    longitude_lim = sort(c(max(df$lon), min(df$lon)))
  }
  if (missing(latitude_lim)) {
    latitude_lim = sort(c(max(df$lat), min(df$lat)))
  }  # create basic map, dictate colour of map, area covered by map,
  # labels and label aesthetics
  base_map <- ggplot() +
              borders("world", region = 'Australia', 
                      colour = "gray85", fill = "gray80") +
              theme_map() +
              coord_fixed(xlim = longitude_lim, ylim = latitude_lim)
              +
              labs(title = selected_title,
                   subtitle = '{closest_state}') +
              theme(plot.title = element_text(hjust = 0, size = 14,
                                              lineheight = .5), 
                    plot.subtitle = element_text(hjust = 0, 
                                                 size = 12,
                                                 lineheight = 0),
              legend.position = c(0,.1), 
              legend.title = element_blank(),
              legend.text = element_text(size = 12))  # determine colouring of points on map
  colour_length = length(point_colours)
  if (typeof(colour_col) == 'character') {
    coloured <- scale_color_manual(values = point_colours)
  }
  else {
    coloured <- scale_color_gradientn(colours = point_colours)
    #low = point_colours[1], high = point_colours[colour_length])
  }  # combine previous map lists, add points to map and animate
  anim <- base_map +
          coloured +
          geom_point(aes(x = lon, y = lat, color = colour_col), 
          data = df, alpha = .5) +
          transition_states(state_col, transition_length = 0)
 
  # animate map
  anim <- animate(anim, nframes = num_fr, duration = dur)  return(anim)
}

Business as Usual at Solar Analytics

Our team of scientists, solar PV engineers, designers and software developers are dedicated to improving the performance of solar energy systems. Here are a collection of blog posts on ways they achieve this.

Claudia Wallis

Written by

Working in Data Science after completing a degree in Math

Business as Usual at Solar Analytics

Our team of scientists, solar PV engineers, designers and software developers are dedicated to improving the performance of solar energy systems. Here are a collection of blog posts on ways they achieve this.