Purpose

Demonstrate how to build animated tracking maps using tracking data in Movebank, environmental covariates in track and raster annotations from EnvDATA, and the moveVis package written by Jakob Schwalb-Willmann. Subsequent examples become more complete and cover more complicated situations.

Notes: * Consider exploring tracking data in Movebank, DynamoVis (https://doi.org/10.13020/D6PH49) and/or Google Maps first to evaluate datasets you’re not yet familiar with and plan out animations. * See more about the moveVis package at http://movevis.org.

Load libraries

library(ggplot2)
library(moveVis)
library(move)
library(magrittr)
library(raster)
options(max.print=100)
options(width=95)

Prepare the tracking data.

Load the Svalbard geese dataset (Griffin, 2014, https://doi.org/10.5441/001/1.5k6b1364).

geese.move <- move("data/annotations/Svalbard geese 1k 16d NDVI-3281758398705165226/Svalbard geese 1k 16d NDVI-3281758398705165226.csv")
geese.move
## class       : MoveStack 
## features    : 24488 
## extent      : -4.029, 22.421, 54.15566, 78.71467  (xmin, xmax, ymin, ymax)
## coord. ref. : +proj=longlat +ellps=WGS84 +datum=WGS84 +towgs84=0,0,0 
## variables   : 6
## names       :  event.id,           timestamp,  comments, height.raw, MODIS.Land.Vegetation.Indices.1km.16d.Aqua.NDVI, MODIS.Land.Vegetation.Indices.1km.16d.Terra.NDVI 
## min values  : 369331563, 2006-04-05 07:00:00, 170563-07,       -0.1,                                    1.000006e-02,                                     1.000000e-02 
## max values  : 369356755, 2011-06-18 17:00:00,  86828-09,       NULL,                                   -9.997746e-03,                                    -9.998666e-02 
## timestamps  : 2006-04-05 07:00:00 ... 2011-06-18 17:00:00 Time difference of 1900 days  (start ... end, duration) 
## sensors     : gps 
## indiv. data : visible, algorithm.marked.outlier, sensor.type, individual.taxon.canonical.name, tag.local.identifier, individual.local.identifier, study.name 
## min ID Data : true, NA, gps, Branta leucopsis,  33102, X170563, Migration timing in barnacle geese (Svalbard) (data from Kölzsch et al. and Shariatinajafabadi et al. 2014) 
## max ID Data : true, NA, gps, Branta leucopsis, 186827,  X86828, Migration timing in barnacle geese (Svalbard) (data from Kölzsch et al. and Shariatinajafabadi et al. 2014) 
## individuals : X33102, X33103, X33104, X33145, X33953, X33954, X64685, X64687, X70564, X70565, X70566, X70567, X70568, X70618, X70619 
## study name  : Migration timing in barnacle geese (Svalbard) (data from Kölzsch et al. and Shariatinajafabadi et al. 2014) 
## date created: 2018-09-12 16:39:55

As shown in “timestamps” this study covers 5 years (4/5/2006 to 6/18/2011, 1900 days). Before building animations, check the spread of the data over time.

geese.df <- as.data.frame(geese.move)
ggplot() +
  geom_point(data = geese.df, aes(x = timestamp, y = individual.local.identifier)) +
  theme(legend.position = "none") # hide legend

Let’s reduce it to the migration with the largest number of birds (spring 2007).

geese.s07 <- subset(geese.move, timestamp >= as.POSIXct('2007-04-01 00:00:00.000') 
                    & timestamp <= as.POSIXct('2007-06-15 00:00:00.000'))

Make frames of tracking data for animation.

Evaluate tracking data for sampling rates if unknown. Use this information to help decide the temporal resolution at which to align the data for the animation.

unique(timestamps(geese.s07))
##   [1] "2007-04-01 10:00:00 UTC" "2007-04-01 12:00:00 UTC" "2007-04-01 14:00:00 UTC"
##   [4] "2007-04-01 16:00:00 UTC" "2007-04-01 18:00:00 UTC" "2007-04-02 04:00:00 UTC"
##   [7] "2007-04-02 06:00:00 UTC" "2007-04-02 08:00:00 UTC" "2007-04-02 10:00:00 UTC"
##  [10] "2007-04-02 12:00:00 UTC" "2007-04-02 14:00:00 UTC" "2007-04-02 16:00:00 UTC"
##  [13] "2007-04-02 18:00:00 UTC" "2007-04-02 20:00:00 UTC" "2007-04-03 06:00:00 UTC"
##  [16] "2007-04-03 08:00:00 UTC" "2007-04-03 10:00:00 UTC" "2007-04-03 12:00:00 UTC"
##  [19] "2007-04-03 14:00:00 UTC" "2007-04-03 16:00:00 UTC" "2007-04-03 18:00:00 UTC"
##  [22] "2007-04-04 04:00:00 UTC" "2007-04-04 06:00:00 UTC" "2007-04-04 08:00:00 UTC"
##  [25] "2007-04-04 10:00:00 UTC" "2007-04-04 12:00:00 UTC" "2007-04-04 14:00:00 UTC"
##  [28] "2007-04-04 16:00:00 UTC" "2007-04-04 18:00:00 UTC" "2007-04-04 20:00:00 UTC"
##  [31] "2007-04-05 04:00:00 UTC" "2007-04-05 06:00:00 UTC" "2007-04-05 08:00:00 UTC"
##  [34] "2007-04-05 16:00:00 UTC" "2007-04-05 18:00:00 UTC" "2007-04-05 20:00:00 UTC"
##  [37] "2007-04-06 04:00:00 UTC" "2007-04-06 06:00:00 UTC" "2007-04-06 08:00:00 UTC"
##  [40] "2007-04-06 10:00:00 UTC" "2007-04-06 12:00:00 UTC" "2007-04-06 14:00:00 UTC"
##  [43] "2007-04-06 16:00:00 UTC" "2007-04-06 18:00:00 UTC" "2007-04-07 04:00:00 UTC"
##  [46] "2007-04-07 06:00:00 UTC" "2007-04-07 08:00:00 UTC" "2007-04-07 10:00:00 UTC"
##  [49] "2007-04-07 12:00:00 UTC" "2007-04-07 14:00:00 UTC" "2007-04-08 04:00:00 UTC"
##  [52] "2007-04-08 06:00:00 UTC" "2007-04-08 08:00:00 UTC" "2007-04-08 10:00:00 UTC"
##  [55] "2007-04-08 12:00:00 UTC" "2007-04-08 14:00:00 UTC" "2007-04-08 16:00:00 UTC"
##  [58] "2007-04-08 18:00:00 UTC" "2007-04-09 10:00:00 UTC" "2007-04-09 12:00:00 UTC"
##  [61] "2007-04-09 14:00:00 UTC" "2007-04-09 16:00:00 UTC" "2007-04-09 18:00:00 UTC"
##  [64] "2007-04-09 20:00:00 UTC" "2007-04-10 06:00:00 UTC" "2007-04-10 08:00:00 UTC"
##  [67] "2007-04-10 10:00:00 UTC" "2007-04-10 12:00:00 UTC" "2007-04-10 14:00:00 UTC"
##  [70] "2007-04-10 16:00:00 UTC" "2007-04-10 18:00:00 UTC" "2007-04-10 20:00:00 UTC"
##  [73] "2007-04-11 10:00:00 UTC" "2007-04-11 12:00:00 UTC" "2007-04-11 14:00:00 UTC"
##  [76] "2007-04-11 16:00:00 UTC" "2007-04-11 18:00:00 UTC" "2007-04-11 20:00:00 UTC"
##  [79] "2007-04-12 04:00:00 UTC" "2007-04-12 06:00:00 UTC" "2007-04-12 08:00:00 UTC"
##  [82] "2007-04-12 10:00:00 UTC" "2007-04-12 12:00:00 UTC" "2007-04-12 14:00:00 UTC"
##  [85] "2007-04-12 16:00:00 UTC" "2007-04-12 18:00:00 UTC" "2007-04-12 20:00:00 UTC"
##  [88] "2007-04-14 04:00:00 UTC" "2007-04-14 06:00:00 UTC" "2007-04-14 08:00:00 UTC"
##  [91] "2007-04-14 16:00:00 UTC" "2007-04-14 18:00:00 UTC" "2007-04-14 20:00:00 UTC"
##  [94] "2007-04-15 10:00:00 UTC" "2007-04-15 12:00:00 UTC" "2007-04-15 14:00:00 UTC"
##  [97] "2007-04-15 16:00:00 UTC" "2007-04-15 18:00:00 UTC" "2007-04-15 20:00:00 UTC"
## [100] "2007-04-16 04:00:00 UTC"
##  [ reached 'max' / getOption("max.print") -- omitted 1040 entries ]
timeLag(geese.s07, unit = "hours")
## $X64687
##   [1]  2  2  2  2 10  2  2  2  2  2  2  2  2 10  2  2  2  2  2  2 10  2  2  2  2  2  2  2  2  8
##  [31]  2  2  8  2  2  8  2  2  2  2  2  2  2 10  2  2  2  2  2 14  2  2  2  2  2  2  2 16  2  2
##  [61]  2  2  2 10  2  2  2  2  2  2  2 14  2  2  2  2  2  8  2  2  2  2  2  2  2  2 32  2  2  8
##  [91]  2  2 14  2  2  2  2  2  8  2
##  [ reached getOption("max.print") -- omitted 333 entries ]
## 
## $X70564
##   [1]  1  1  1  1  1  3  2  8  2  2  2  2  2  2  2 20  2  2 14  2  2  2  2  2  2  8  2  8  2  2
##  [31]  2  8  2  2  2  2  2  2  2  2  8  2  2  2  2 44  2  2  2  2  2  2 10  2  2  2  2  2  2  2
##  [61]  8  2  2  2  2  2  2  2 14  2  2  8  8  2  2  2  2  2  2  2 14  2  2  2  2  2  2  8  2  2
##  [91]  2  2  2  2  2 14  2  2  2  2
##  [ reached getOption("max.print") -- omitted 405 entries ]
## 
## $X70565
##   [1]  2  2  2  2  2  2  2  8  2  2  2  2  2  2  2  2 14 30  2  2 10  2  2  2  2  2  2  2 10  2
##  [31]  2  2  2  2  2  2 10  2  2  2  2 16  2  8  2 12  2  2  2  2  2  2  2  8  2  2  8  2 16  2
##  [61]  2  2  2  2 10  2  2  2  2  2  2  2  8  2  2  2  2  2  2  2  2 10  2  2  2  2  2  2  2 10
##  [91]  2  2  2  2  2  2  2 10  2  2
##  [ reached getOption("max.print") -- omitted 471 entries ]
## 
## $X70566
##   [1]  2  2 68  2  2  8  2  2  2  2  2  2  2  2  8  2  2 44  2  2  8  2  2  8  2  2  2  2  2  2
##  [31]  2  2 10  2  2  2  2  2  2  2 14  2  2  2  2  2  8  2  2  2  2  2  2  2  2  8  2  2  2  2
##  [61]  2  2  2  2  8  2  2  2  2  2 14  2  2 26  2  2  2  2  2  8  2  4  2  2  2  2  2 10  2  8
##  [91]  2  2 32  2  2 20  2 22  2  2
##  [ reached getOption("max.print") -- omitted 413 entries ]
## 
## $X70567
##   [1]  2  2  2  2 40  2  2  2  2  2  8  2  2  2  2  2  2  2  2  8  2  2 26  2  2 44  2  2  2  2
##  [31] 10  2  2  2  2  2 50  2 16  2  2  2  2  2  8  2  2  2  2  2  2  2  2 10  2 26  2  2 14  2
##  [61]  2  2  2  2  2  2  2 14  2  2  2  2  2  8  2  2 56  2  2 32  2  2 50  2  2  2  2  2 20  2
##  [91]  2  8  2  2  2  2  2  2  2  2
##  [ reached getOption("max.print") -- omitted 410 entries ]
## 
## $X70568
##   [1]  2  2  2  2  2 14  2  2  2  2  2  8  2  2  2  2  2  2  2  2 62  2  2  2  2  2  8  2  2  2
##  [31]  2  2  2 12  2  2 20  2  2  2  2  2  2  2  2  8  2  2  2  2  2  2  2  2  8  2  2  2  2  2
##  [61]  2  2  2  8  2  2 26  2  2  2  2  2  8  2  2  2  2  2  2  2  2  8  6  2  2  2  2  2 20  2
##  [91]  2  8  2  2 50  2  2 74  2  2
##  [ reached getOption("max.print") -- omitted 215 entries ]
## 
## $X70618
##   [1]  2  2  2  2  2  2  2  2  8  2  2 20  2  2  2  2  2 14  2  2  2  2  2  2  2  2  8  2  2  2
##  [31]  2  2  2  2  2  8  2  2 20  2  2 26  2  2  2  2  2  8  2  2  2  2  2  2  2 16  2  2  2  2
##  [61]  2 10  2  8  2  2  8  2  2  2  2  2  2  2  2  8  2  2  2  2  2 14  2  2  2  2  2 20  2  2
##  [91]  2  2  2  8  2  2  2  2  2  2
##  [ reached getOption("max.print") -- omitted 524 entries ]
## 
## $X70619
##   [1]  2  2  2  2  2  2  2 10  2  2  2  2  2 14  2  2  2  2  2  2  2  2  8  2  2  2  2  2  2  2
##  [31]  2  8  2  2  2  2  2  2  2  2  8  2  2  2  2  2  2  2  2  8  2  2 68  2  2  2  2  2  2  2
##  [61]  2 10  2 32  2  2  8  2  2  2  2  2  2  2  2  8  2  2  2  2  2  2  2  2  8  2  2  8  2  2
##  [91]  8  2  2  2  2  2  2  2  2 20
##  [ reached getOption("max.print") -- omitted 471 entries ]
## 
## $X170563
##   [1]  4  1  1  2  2  8  2  2  2  8  2  8  2  2  2  2  2  2 20  2  2  2 10  2  2  2  2  2  2  2
##  [31] 10  2  2  2  2  2  2  2  2  8  2  2  2  2  2  2 26  2  8  2  2  2  2  2  2  2  2 10  2  2
##  [61]  2  2  2  2  2 10  2  2 32  2  8  2  2  2  8  2 22  2  8  8  2  2 38  2  2  8  2  8  2  2
##  [91]  2  2  2  2  2  2  8  2  2  2
##  [ reached getOption("max.print") -- omitted 439 entries ]

Align tracking data to uniform temporal resolution for interpretation by frames_spatial.

geese <- align_move(geese.s07, res = 12, unit = "hours", spaceMethod = "greatcircle")

Could use 2-hr resolution for a final product but 12-hr should speed up processing.

Create animation with static web basemap.

Create map frames for animation (see p.25 of MoveVis manual). Note that equidistant = TRUE does not mean that the map will be displayed in an equidistant projection (i.e. that preserves distances). Instead it causes frames_spatial to stretch the displayed area to an square extent. If equidistant = FALSE, the extent is displayed in the projection-native axis ratio.

frames <- frames_spatial(geese, map_service = "osm", map_type = "watercolor", 
                         equidistant = FALSE, path_legend = T, path_legend_title = "Geese", 
                         alpha = 0.5)
## Processing movement data...
## Retrieving and compositing basemap imagery...
## Assigning raster maps to frames...
## Creating frames...

Have a look at one of the frames.

frames[[100]]

Note: Working with the frames I sometimes get a message “Error in grid.Call….polygon edge not found” but if I rerun the same code the command succeeds. Add labels and a progress bar.

frames.l <- add_labels(frames, x = "Longitude", y = "Latitude") %>% # axis labels
  add_progress() %>% # progress bar
  add_scalebar() %>% # scale bar
  add_northarrow() %>% # north arrow
  add_timestamps(geese, type = "label") # timestamps

See the changes.

frames.l[[100]]

Record an animation using defaults shown in in moveVis manual p.18.

animate_frames(frames.l, "/Users/sarahdavidson/Desktop/Svalbard_geese_osm_test.gif", fps = 25, 
               width = 500, height = 800, res = 100, 
               display = TRUE, overwrite = TRUE, verbose = TRUE)
## Rendering animation...
## 
Frame 1 (0%)
Frame 2 (1%)
Frame 3 (2%)
Frame 4 (2%)
Frame 5 (3%)
Frame 6 (4%)
Frame 7 (4%)
Frame 8 (5%)
Frame 9 (6%)
Frame 10 (6%)
Frame 11 (7%)
Frame 12 (8%)
Frame 13 (8%)
Frame 14 (9%)
Frame 15 (10%)
Frame 16 (10%)
Frame 17 (11%)
Frame 18 (12%)
Frame 19 (12%)
Frame 20 (13%)
Frame 21 (14%)
Frame 22 (14%)
Frame 23 (15%)
Frame 24 (16%)
Frame 25 (16%)
Frame 26 (17%)
Frame 27 (18%)
Frame 28 (18%)
Frame 29 (19%)
Frame 30 (20%)
Frame 31 (20%)
Frame 32 (21%)
Frame 33 (22%)
Frame 34 (22%)
Frame 35 (23%)
Frame 36 (24%)
Frame 37 (25%)
Frame 38 (25%)
Frame 39 (26%)
Frame 40 (27%)
Frame 41 (27%)
Frame 42 (28%)
Frame 43 (29%)
Frame 44 (29%)
Frame 45 (30%)
Frame 46 (31%)
Frame 47 (31%)
Frame 48 (32%)
Frame 49 (33%)
Frame 50 (33%)
Frame 51 (34%)
Frame 52 (35%)
Frame 53 (35%)
Frame 54 (36%)
Frame 55 (37%)
Frame 56 (37%)
Frame 57 (38%)
Frame 58 (39%)
Frame 59 (39%)
Frame 60 (40%)
Frame 61 (41%)
Frame 62 (41%)
Frame 63 (42%)
Frame 64 (43%)
Frame 65 (43%)
Frame 66 (44%)
Frame 67 (45%)
Frame 68 (45%)
Frame 69 (46%)
Frame 70 (47%)
Frame 71 (47%)
Frame 72 (48%)
Frame 73 (49%)
Frame 74 (50%)
Frame 75 (50%)
Frame 76 (51%)
Frame 77 (52%)
Frame 78 (52%)
Frame 79 (53%)
Frame 80 (54%)
Frame 81 (54%)
Frame 82 (55%)
Frame 83 (56%)
Frame 84 (56%)
Frame 85 (57%)
Frame 86 (58%)
Frame 87 (58%)
Frame 88 (59%)
Frame 89 (60%)
Frame 90 (60%)
Frame 91 (61%)
Frame 92 (62%)
Frame 93 (62%)
Frame 94 (63%)
Frame 95 (64%)
Frame 96 (64%)
Frame 97 (65%)
Frame 98 (66%)
Frame 99 (66%)
Frame 100 (67%)
Frame 101 (68%)
Frame 102 (68%)
Frame 103 (69%)
Frame 104 (70%)
Frame 105 (70%)
Frame 106 (71%)
Frame 107 (72%)
Frame 108 (72%)
Frame 109 (73%)
Frame 110 (74%)
Frame 111 (75%)
Frame 112 (75%)
Frame 113 (76%)
Frame 114 (77%)
Frame 115 (77%)
Frame 116 (78%)
Frame 117 (79%)
Frame 118 (79%)
Frame 119 (80%)
Frame 120 (81%)
Frame 121 (81%)
Frame 122 (82%)
Frame 123 (83%)
Frame 124 (83%)
Frame 125 (84%)
Frame 126 (85%)
Frame 127 (85%)
Frame 128 (86%)
Frame 129 (87%)
Frame 130 (87%)
Frame 131 (88%)
Frame 132 (89%)
Frame 133 (89%)
Frame 134 (90%)
Frame 135 (91%)
Frame 136 (91%)
Frame 137 (92%)
Frame 138 (93%)
Frame 139 (93%)
Frame 140 (94%)
Frame 141 (95%)
Frame 142 (95%)
Frame 143 (96%)
Frame 144 (97%)
Frame 145 (97%)
Frame 146 (98%)
Frame 147 (99%)
Frame 148 (100%)
## Finalizing encoding... done!

Running animate_frames takes a while, you can go grab a coffee. When it finishes, check out your gif. That was easy, thanks moveVis! Now what if we have our own raster basemap? Let’s try it with the ETOPO DEM we got from EnvDATA (see https://doi.org/10.7289/V5C8276M).

Prepare raster basemaps.

See EnvDataGridRequestWorksheet_SvalbardGeese.pdf for details on the requests submitted for this example and EnvDATAvizRaster.R for more about working with rasters. If you need to merge multiple rasters per basemap, create a separate folder for each set of raster files to merge and rename files if that helps keep things organized. Merge ETOPO files into a single background raster using the raster package.

DEMs <- lapply(list.files("data/annotations/ETOPO/rasters_ETOPO/", full.names = TRUE), raster)
DEM.basemap <- do.call(merge, DEMs)

View stats.

DEM.basemap
## class       : RasterLayer 
## dimensions  : 1800, 1800, 3240000  (nrow, ncol, ncell)
## resolution  : 0.01666667, 0.01666667  (x, y)
## extent      : -6, 24, 50, 80  (xmin, xmax, ymin, ymax)
## coord. ref. : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0 
## data source : in memory
## names       : layer 
## values      : -5558.5, 2127.75  (min, max)
hist(DEM.basemap, main = "Elevation (m)",
     col = "blue",
     maxpixels = 3240000) # if ncell > 100000, add this

Could use this histogram to help define optional breaks to use in colourmap, more below. Plot.

plot(DEM.basemap)

Create animation with static raster basemap.

Create map frames for animation (see p.25 of MoveVis manual). Using the start time for r_times since there is just 1 raster.

starttime <- as.POSIXct('2007-04-15 00:00:00.000')
frames <- frames_spatial(geese, r_list = DEM.basemap, r_times = starttime, 
                         r_type = "gradient", fade_raster = FALSE, equidistant = FALSE,
                         path_legend = T, path_legend_title = "Geese", alpha = 0.9)
## Processing movement data...
## Assigning raster maps to frames...
## Creating frames...

Check a frame.

frames[[10]]

Add labels and colorscale for the raster, clean up a bit from the previous example.

frames.l <- add_labels(frames, x = "Longitude", y = "Latitude") %>%
  add_progress() %>%
  add_scalebar(distance = 600, position = "bottomright") %>%
  add_timestamps(geese, x = 4, y = 78.5, type = "label") %>% # x & y in degrees
  add_colourscale(type = "gradient", 
                  colours = c("-5600" = "steelblue4", "-0.01" = "steelblue1", "0" = "white",
                              "0.01" = "wheat1", "2200" = "sienna4"),
                  legend_title = "Elevation\n(m amsl)")

Check a frame.

frames.l[[100]]

Currently the scale and color assignments are off: white should occur at 0 elevation but instead is in the middle of the range of values. This bug is fixed with v0.10.2 of moveVis, currently you can install this from GitHub using
remotes::install_github(“16eagle/moveVis”).
Record an animation.

animate_frames(frames.l, "animations/Svalbard_geese_dem_test.gif", fps = 25, width = 500, height = 800, res = 100, display = TRUE, overwrite = TRUE, verbose = TRUE)
## Rendering animation...
## 
Frame 1 (0%)
Frame 2 (1%)
Frame 3 (2%)
Frame 4 (2%)
Frame 5 (3%)
Frame 6 (4%)
Frame 7 (4%)
Frame 8 (5%)
Frame 9 (6%)
Frame 10 (6%)
Frame 11 (7%)
Frame 12 (8%)
Frame 13 (8%)
Frame 14 (9%)
Frame 15 (10%)
Frame 16 (10%)
Frame 17 (11%)
Frame 18 (12%)
Frame 19 (12%)
Frame 20 (13%)
Frame 21 (14%)
Frame 22 (14%)
Frame 23 (15%)
Frame 24 (16%)
Frame 25 (16%)
Frame 26 (17%)
Frame 27 (18%)
Frame 28 (18%)
Frame 29 (19%)
Frame 30 (20%)
Frame 31 (20%)
Frame 32 (21%)
Frame 33 (22%)
Frame 34 (22%)
Frame 35 (23%)
Frame 36 (24%)
Frame 37 (25%)
Frame 38 (25%)
Frame 39 (26%)
Frame 40 (27%)
Frame 41 (27%)
Frame 42 (28%)
Frame 43 (29%)
Frame 44 (29%)
Frame 45 (30%)
Frame 46 (31%)
Frame 47 (31%)
Frame 48 (32%)
Frame 49 (33%)
Frame 50 (33%)
Frame 51 (34%)
Frame 52 (35%)
Frame 53 (35%)
Frame 54 (36%)
Frame 55 (37%)
Frame 56 (37%)
Frame 57 (38%)
Frame 58 (39%)
Frame 59 (39%)
Frame 60 (40%)
Frame 61 (41%)
Frame 62 (41%)
Frame 63 (42%)
Frame 64 (43%)
Frame 65 (43%)
Frame 66 (44%)
Frame 67 (45%)
Frame 68 (45%)
Frame 69 (46%)
Frame 70 (47%)
Frame 71 (47%)
Frame 72 (48%)
Frame 73 (49%)
Frame 74 (50%)
Frame 75 (50%)
Frame 76 (51%)
Frame 77 (52%)
Frame 78 (52%)
Frame 79 (53%)
Frame 80 (54%)
Frame 81 (54%)
Frame 82 (55%)
Frame 83 (56%)
Frame 84 (56%)
Frame 85 (57%)
Frame 86 (58%)
Frame 87 (58%)
Frame 88 (59%)
Frame 89 (60%)
Frame 90 (60%)
Frame 91 (61%)
Frame 92 (62%)
Frame 93 (62%)
Frame 94 (63%)
Frame 95 (64%)
Frame 96 (64%)
Frame 97 (65%)
Frame 98 (66%)
Frame 99 (66%)
Frame 100 (67%)
Frame 101 (68%)
Frame 102 (68%)
Frame 103 (69%)
Frame 104 (70%)
Frame 105 (70%)
Frame 106 (71%)
Frame 107 (72%)
Frame 108 (72%)
Frame 109 (73%)
Frame 110 (74%)
Frame 111 (75%)
Frame 112 (75%)
Frame 113 (76%)
Frame 114 (77%)
Frame 115 (77%)
Frame 116 (78%)
Frame 117 (79%)
Frame 118 (79%)
Frame 119 (80%)
Frame 120 (81%)
Frame 121 (81%)
Frame 122 (82%)
Frame 123 (83%)
Frame 124 (83%)
Frame 125 (84%)
Frame 126 (85%)
Frame 127 (85%)
Frame 128 (86%)
Frame 129 (87%)
Frame 130 (87%)
Frame 131 (88%)
Frame 132 (89%)
Frame 133 (89%)
Frame 134 (90%)
Frame 135 (91%)
Frame 136 (91%)
Frame 137 (92%)
Frame 138 (93%)
Frame 139 (93%)
Frame 140 (94%)
Frame 141 (95%)
Frame 142 (95%)
Frame 143 (96%)
Frame 144 (97%)
Frame 145 (97%)
Frame 146 (98%)
Frame 147 (99%)
Frame 148 (100%)
## Finalizing encoding... done!

Hurray, now we can watch our animals move over any environmental layer in EnvDATA! But what about all the variables that change over time?

Create animation with multiple raster basemaps.

Let’s try this with 16-day NDVI from MODIS (see https://doi.org/10.5067/MODIS/MYD13A2.006).
First merge NDVI geotif files into a single background raster for each date (if needed).

scenes <- c("rasters_NDVI_MYD13A2_20070330", "rasters_NDVI_MYD13A2_20070415", 
            "rasters_NDVI_MYD13A2_20070430", "rasters_NDVI_MYD13A2_20070515", 
            "rasters_NDVI_MYD13A2_20070531", "rasters_NDVI_MYD13A2_20070615")
scenes <- paste0("data/annotations/NDVI/", scenes)

NDVIs.all <- stack(lapply(scenes, function(x) 
  do.call(merge, lapply(list.files(x, full = TRUE), raster))))

names(NDVIs.all) <- paste("NDVI", c("30 March", "15 April", "30 April", "15 May",  
                                    "31 May", "15 June"))

Prepare lists of rasters and times of the NDVI basemaps for frames_spatial.

NDVI.times <- list("2007-03-30 00:00:00", "2007-04-15 00:00:00", 
                   "2007-04-30 00:00:00", "2007-05-15 00:00:00", 
                   "2007-05-31 00:00:00", "2007-06-15 00:00:00")
NDVI.times <- as.POSIXct(strptime(NDVI.times, format = "%Y-%m-%d %H:%M:%OS"), tz="UTC")

Create map frames for animation.

frames <- frames_spatial(geese, r_list = unstack(NDVIs.all), r_times = NDVI.times, 
                         r_type = "gradient", fade_raster = FALSE, equidistant = FALSE,
                         tail_length = 1, path_legend = TRUE, 
                         path_legend_title = "Geese", alpha = 0.9)
## Processing movement data...
## Assigning raster maps to frames...
## Creating frames...

Check a frame.

frames[[100]]

Add labels and colorscale for the rasters.

frames.l <- add_labels(frames, x = "Longitude", y = "Latitude") %>%
  add_progress() %>%
  add_scalebar(distance = 600, position = "bottomright") %>%
  add_timestamps(geese, x = 4, y = 78.5, type = "label") %>%
  add_colourscale(type= "gradient", colours = c("wheat", "forestgreen"), legend_title = "NDVI")

Check a frame.

frames.l[[100]]

Record an animation, slowing down the animation a bit compared to the others.

animate_frames(frames.l, "animations/Svalbard_geese_ndvi_test.gif", fps = 10, width = 500,
               height = 800, res = 100, display = TRUE, overwrite = TRUE, verbose = TRUE)
## Rendering animation...
## 
Frame 1 (0%)
Frame 2 (1%)
Frame 3 (2%)
Frame 4 (2%)
Frame 5 (3%)
Frame 6 (4%)
Frame 7 (4%)
Frame 8 (5%)
Frame 9 (6%)
Frame 10 (6%)
Frame 11 (7%)
Frame 12 (8%)
Frame 13 (8%)
Frame 14 (9%)
Frame 15 (10%)
Frame 16 (10%)
Frame 17 (11%)
Frame 18 (12%)
Frame 19 (12%)
Frame 20 (13%)
Frame 21 (14%)
Frame 22 (14%)
Frame 23 (15%)
Frame 24 (16%)
Frame 25 (16%)
Frame 26 (17%)
Frame 27 (18%)
Frame 28 (18%)
Frame 29 (19%)
Frame 30 (20%)
Frame 31 (20%)
Frame 32 (21%)
Frame 33 (22%)
Frame 34 (22%)
Frame 35 (23%)
Frame 36 (24%)
Frame 37 (25%)
Frame 38 (25%)
Frame 39 (26%)
Frame 40 (27%)
Frame 41 (27%)
Frame 42 (28%)
Frame 43 (29%)
Frame 44 (29%)
Frame 45 (30%)
Frame 46 (31%)
Frame 47 (31%)
Frame 48 (32%)
Frame 49 (33%)
Frame 50 (33%)
Frame 51 (34%)
Frame 52 (35%)
Frame 53 (35%)
Frame 54 (36%)
Frame 55 (37%)
Frame 56 (37%)
Frame 57 (38%)
Frame 58 (39%)
Frame 59 (39%)
Frame 60 (40%)
Frame 61 (41%)
Frame 62 (41%)
Frame 63 (42%)
Frame 64 (43%)
Frame 65 (43%)
Frame 66 (44%)
Frame 67 (45%)
Frame 68 (45%)
Frame 69 (46%)
Frame 70 (47%)
Frame 71 (47%)
Frame 72 (48%)
Frame 73 (49%)
Frame 74 (50%)
Frame 75 (50%)
Frame 76 (51%)
Frame 77 (52%)
Frame 78 (52%)
Frame 79 (53%)
Frame 80 (54%)
Frame 81 (54%)
Frame 82 (55%)
Frame 83 (56%)
Frame 84 (56%)
Frame 85 (57%)
Frame 86 (58%)
Frame 87 (58%)
Frame 88 (59%)
Frame 89 (60%)
Frame 90 (60%)
Frame 91 (61%)
Frame 92 (62%)
Frame 93 (62%)
Frame 94 (63%)
Frame 95 (64%)
Frame 96 (64%)
Frame 97 (65%)
Frame 98 (66%)
Frame 99 (66%)
Frame 100 (67%)
Frame 101 (68%)
Frame 102 (68%)
Frame 103 (69%)
Frame 104 (70%)
Frame 105 (70%)
Frame 106 (71%)
Frame 107 (72%)
Frame 108 (72%)
Frame 109 (73%)
Frame 110 (74%)
Frame 111 (75%)
Frame 112 (75%)
Frame 113 (76%)
Frame 114 (77%)
Frame 115 (77%)
Frame 116 (78%)
Frame 117 (79%)
Frame 118 (79%)
Frame 119 (80%)
Frame 120 (81%)
Frame 121 (81%)
Frame 122 (82%)
Frame 123 (83%)
Frame 124 (83%)
Frame 125 (84%)
Frame 126 (85%)
Frame 127 (85%)
Frame 128 (86%)
Frame 129 (87%)
Frame 130 (87%)
Frame 131 (88%)
Frame 132 (89%)
Frame 133 (89%)
Frame 134 (90%)
Frame 135 (91%)
Frame 136 (91%)
Frame 137 (92%)
Frame 138 (93%)
Frame 139 (93%)
Frame 140 (94%)
Frame 141 (95%)
Frame 142 (95%)
Frame 143 (96%)
Frame 144 (97%)
Frame 145 (97%)
Frame 146 (98%)
Frame 147 (99%)
Frame 148 (100%)
## Finalizing encoding... done!

Awesome, now we can animate both our tracks and raster base layers over time. For these geese, we have the remaining problem that they are migrating to very high latitudes and the default projection has a lot of distortion. Let’s try using a different projection.

Reproject tracking data and rasters.

# WGS84/North Pole Lambert Azimuthal Equal Area (http://spatialreference.org/ref/sr-org/7250/)
crs.new = CRS("+proj=laea +lat_0=90 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +units=m +no_defs")

Reproject the tracks.

geese.polar <- spTransform(geese, crs.new)
geese.polar # check "extent" and "coord. ref."
## class       : MoveStack 
## features    : 1285 
## extent      : -248653.5, 621786.6, -3897316, -1179404  (xmin, xmax, ymin, ymax)
## coord. ref. : +proj=laea +lat_0=90 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +units=m +no_defs 
## variables   : 9
## names       :  event.id,           timestamp,  comments, height.raw, MODIS.Land.Vegetation.Indices.1km.16d.Aqua.NDVI, MODIS.Land.Vegetation.Indices.1km.16d.Terra.NDVI,            x,        y,                time 
## min values  : 369331564, 2007-04-04 13:00:00, 170563-07,         10,                                     0.015120786,                                       0.02013790,  0.004035225, 54.49865, 2007-04-01 13:00:00 
## max values  : 369351132, 2007-06-14 01:00:00,  70619-07,    neg alt,                                    -0.064717468,                                      -0.11660282, 21.110325713, 78.66116, 2007-06-14 01:00:00 
## timestamps  : 2007-04-01 13:00:00 ... 2007-06-14 01:00:00 Time difference of 74 days  (start ... end, duration) 
## sensors     : interpolateTime, gps 
## indiv. data : visible, algorithm.marked.outlier, sensor.type, individual.taxon.canonical.name, tag.local.identifier, individual.local.identifier, study.name 
## min ID Data : true, NA, gps, Branta leucopsis,  64687, X170563, Migration timing in barnacle geese (Svalbard) (data from Kölzsch et al. and Shariatinajafabadi et al. 2014) 
## max ID Data : true, NA, gps, Branta leucopsis, 170563,  X70619, Migration timing in barnacle geese (Svalbard) (data from Kölzsch et al. and Shariatinajafabadi et al. 2014) 
## individuals : X64687, X70564, X70565, X70566, X70567, X70568, X70618, X70619, X170563 
## date created: 2018-09-12 16:39:55

Reproject the NDVI rasters.

NDVIs.all.polar = raster::projectRaster(NDVIs.all, crs = crs.new, method = "bilinear")

Create map frames for animation. Note: moveVis 0.10.2 will allow you to assign a color for missing raster values: na.colour for continuous variables and na.show for discrete variables.

frames <- frames_spatial(geese.polar, r_list = unstack(NDVIs.all.polar), r_times = NDVI.times, 
                         r_type = "gradient", fade_raster = FALSE, equidistant = FALSE,
                         tail_length = 1, path_legend = TRUE, 
                         path_legend_title = "Geese", alpha = 0.9)
## Processing movement data...
## Assigning raster maps to frames...
## Creating frames...

Check a frame.

frames[[100]]

Add labels and raster colorscale.

frames.l <- add_labels(frames, x = "Easting (m)", y = "Northing (m)") %>%
  add_progress() %>%
  #add_scalebar(distance = 500, position = "bottomright") %>% # causes an error
  add_timestamps(geese.polar, type = "label") %>%
  add_colourscale(type= "gradient", colours = c("wheat", "forestgreen"), legend_title = "NDVI")

Check a frame.

frames.l[[100]]

Record a final animation, slowing down the animation a bit compared to the others.

animate_frames(frames.l, "animations/Svalbard_geese_ndvi_polar.gif", fps = 10, width = 500, height = 800, res = 100, display = TRUE, overwrite = TRUE, verbose = TRUE)
## Rendering animation...
## 
Frame 1 (0%)
Frame 2 (1%)
Frame 3 (2%)
Frame 4 (2%)
Frame 5 (3%)
Frame 6 (4%)
Frame 7 (4%)
Frame 8 (5%)
Frame 9 (6%)
Frame 10 (6%)
Frame 11 (7%)
Frame 12 (8%)
Frame 13 (8%)
Frame 14 (9%)
Frame 15 (10%)
Frame 16 (10%)
Frame 17 (11%)
Frame 18 (12%)
Frame 19 (12%)
Frame 20 (13%)
Frame 21 (14%)
Frame 22 (14%)
Frame 23 (15%)
Frame 24 (16%)
Frame 25 (16%)
Frame 26 (17%)
Frame 27 (18%)
Frame 28 (18%)
Frame 29 (19%)
Frame 30 (20%)
Frame 31 (20%)
Frame 32 (21%)
Frame 33 (22%)
Frame 34 (22%)
Frame 35 (23%)
Frame 36 (24%)
Frame 37 (25%)
Frame 38 (25%)
Frame 39 (26%)
Frame 40 (27%)
Frame 41 (27%)
Frame 42 (28%)
Frame 43 (29%)
Frame 44 (29%)
Frame 45 (30%)
Frame 46 (31%)
Frame 47 (31%)
Frame 48 (32%)
Frame 49 (33%)
Frame 50 (33%)
Frame 51 (34%)
Frame 52 (35%)
Frame 53 (35%)
Frame 54 (36%)
Frame 55 (37%)
Frame 56 (37%)
Frame 57 (38%)
Frame 58 (39%)
Frame 59 (39%)
Frame 60 (40%)
Frame 61 (41%)
Frame 62 (41%)
Frame 63 (42%)
Frame 64 (43%)
Frame 65 (43%)
Frame 66 (44%)
Frame 67 (45%)
Frame 68 (45%)
Frame 69 (46%)
Frame 70 (47%)
Frame 71 (47%)
Frame 72 (48%)
Frame 73 (49%)
Frame 74 (50%)
Frame 75 (50%)
Frame 76 (51%)
Frame 77 (52%)
Frame 78 (52%)
Frame 79 (53%)
Frame 80 (54%)
Frame 81 (54%)
Frame 82 (55%)
Frame 83 (56%)
Frame 84 (56%)
Frame 85 (57%)
Frame 86 (58%)
Frame 87 (58%)
Frame 88 (59%)
Frame 89 (60%)
Frame 90 (60%)
Frame 91 (61%)
Frame 92 (62%)
Frame 93 (62%)
Frame 94 (63%)
Frame 95 (64%)
Frame 96 (64%)
Frame 97 (65%)
Frame 98 (66%)
Frame 99 (66%)
Frame 100 (67%)
Frame 101 (68%)
Frame 102 (68%)
Frame 103 (69%)
Frame 104 (70%)
Frame 105 (70%)
Frame 106 (71%)
Frame 107 (72%)
Frame 108 (72%)
Frame 109 (73%)
Frame 110 (74%)
Frame 111 (75%)
Frame 112 (75%)
Frame 113 (76%)
Frame 114 (77%)
Frame 115 (77%)
Frame 116 (78%)
Frame 117 (79%)
Frame 118 (79%)
Frame 119 (80%)
Frame 120 (81%)
Frame 121 (81%)
Frame 122 (82%)
Frame 123 (83%)
Frame 124 (83%)
Frame 125 (84%)
Frame 126 (85%)
Frame 127 (85%)
Frame 128 (86%)
Frame 129 (87%)
Frame 130 (87%)
Frame 131 (88%)
Frame 132 (89%)
Frame 133 (89%)
Frame 134 (90%)
Frame 135 (91%)
Frame 136 (91%)
Frame 137 (92%)
Frame 138 (93%)
Frame 139 (93%)
Frame 140 (94%)
Frame 141 (95%)
Frame 142 (95%)
Frame 143 (96%)
Frame 144 (97%)
Frame 145 (97%)
Frame 146 (98%)
Frame 147 (99%)
Frame 148 (100%)
## Finalizing encoding... done!

As final note: One thing missing here is an animation with the animal locations colored based on an annotated covariate. The trick here is that when we use align_move to align the dataset to a consistent temporal resolution so that we can create frames to animate, our times and locations are interpolated, and we lose most of the information in the other columns of our dataset, including our annotated values. This might be solved by finding a a way to do something like align_move that retains or interpolates covariate values from the dataset, or interpolating values from the original dataset to the aligned one. Remember this will lead to some reduction in accuracy.