Animal movement visualization

For our project, we wanted to test different methods and packages to visualize animal movement data. Specifically, we wanted to figure out the best way to create maps and plots to display GPS data.

Two Australian Invasive Lethal Species (TAILS)

We wanted to utilize data that was collected and uploaded to Movebank to familiarize ourselves with that platform. We found a unique data set of GPS collared feral cats and European red foxes in Australia. These mammals are both invasive to the area. We wanted to communicate their movement patterns and home range sizes in a comparative framework without statistical analyses.

Home ranges

Although we wanted to largely stya away from statistics, some tools are a useful way to visualize differenes in the boundaries of areas being used by individuals. Here, we created minimum convex polygons (MCP) and kernel density estimators (KDE) for three cats and three foxes from August through November in 2017.

Visualization 1a: Home ranges: Cats

# Creating home ranges for cats

# Read in libraries ----
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.1.3
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(lubridate)
## 
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
## 
##     date, intersect, setdiff, union
library(amt)
## Warning: package 'amt' was built under R version 4.1.3
## 
## Attaching package: 'amt'
## The following object is masked from 'package:stats':
## 
##     filter
# Read in data ----
# Cat GPS data and reference data
x <- read.csv("Data/Feral cat (Felis catus) - Scotia, NSW-reference-data.csv")
cat <- read.csv("Data/Feral cat (Felis catus) - Scotia, NSW.csv")

# Prepare the data ----
# Filter individuals in 2017 with greater than 600 data pints
cat_choice <- cat %>% 
  filter(year(timestamp) %in% 2017) %>% 
  group_by(individual.local.identifier) %>% 
  summarise(n = length(location.long)) %>% 
  filter(n >= 600) 
  
# Filter individuals in reference file to see metadata
cat_check <- x %>% 
  filter(animal.id %in% cat_choice$individual.local.identifier) 

# visualize in table the cats per year
# table(x$animal.id, x$animal.sex, year(x$deploy.on.date))  

# Grab sex from reference data
sex <- x %>% 
  select(animal.id,
         animal.sex)

# Okay, not lets do some renaming
cat_choice <- cat %>% 
  filter(year(timestamp) %in% 2017) %>% 
  # select cols and rename
  select(animal.id = individual.local.identifier,
         y = location.lat,
         x = location.long,
         utm.y = utm.easting,
         utm.x = utm.northing,
         utm.zone,
         dt = timestamp) %>% 
  # join with ref data
  left_join(sex, by = "animal.id") %>% 
  # rename again
  rename(sex = animal.sex,
         id = animal.id) %>% 
  # filter time
  filter(hour(dt) %in% c(0, 6, 12, 18),
         month(dt) %in% 8:11) %>% 
  # change class of col
  mutate(x = as.numeric(x),
         y = as.numeric(y),
         dt = as.Date(dt)) %>% 
  # arrange asc date and time
  arrange(dt)


# Home range analysis:
# Make track for hr analysis
cat_track <- mk_track(tbl = cat_choice, .x = x, .y = y, .t = dt, 
                      id = id, crs = 23884)


# create hrs per cat
cat <- unique(cat_choice$id)
cat_mcp <- list()
cat_kde <- list()

# loop
for(i in 1:length(cat)){
  x <- cat_track %>% 
    filter(id == cat[i]) 
  
    cat_mcp[[i]] <- hr_mcp(x = x, 
                    levels = c(0.95, 0.5), 
                    keep.data = TRUE)
    
    # Fit KDE
    cat_kde[[i]] <- hr_kde(x = x,
                       levels = c(0.95, 0.5),
                       keep.data = TRUE,
                       h = hr_kde_ref(x), #default
                       trast = make_trast(x))
    

}

# name the list elements
names(cat_kde) <- names(cat_mcp) <- cat

# plot: minimum convex Polygon for 3 cats August - November 2017
plot(cat_mcp[[1]], main = "MCP: Ben", col = "coral")

plot(cat_mcp[[2]], main = "MCP: Ivy", col = "lightblue")

plot(cat_mcp[[3]], main = "MCP: Ken", col = "darkolivegreen3")

# plot: Kernel density Estimates for 3 cats August - November 2017
plot(cat_kde[[1]], main = "KDE: Ben", col = "coral")

plot(cat_kde[[2]], main = "KDE: Ivy", col = "lightblue")

plot(cat_kde[[3]], main = "KDE: Ken", col = "darkolivegreen3")

# To extract the areas from all MCPs
mcp_areas <- lapply(cat_mcp, hr_area)
kde_areas <- lapply(cat_kde, hr_area)

c_kde_df <- bind_rows(kde_areas, .id = "id")
c_mcp_df <- bind_rows(mcp_areas, .id = "id")

Visualization 1b: Home ranges: Foxes

Home range area comparison

We wanted to also see if the size of the area the species used differed. Here are histogram plots of the area of MCP/KDE for each species. Looks like the cats use a larger area than foxes! Which is neat.

Movetrack visualization: cats

Now that we can see static home ranges to visualize size and shape variance, we wanted to explore animating the GPS location points to trace the movements of individuals and plot them on a map. The package we used is called moveVis, which requires the data to be formatted as a “movestack” object.

library(tidyverse)
## -- Attaching packages --------------------------------------- tidyverse 1.3.1 --
## v ggplot2 3.3.6     v purrr   0.3.4
## v tibble  3.1.6     v stringr 1.4.1
## v tidyr   1.2.0     v forcats 0.5.2
## v readr   2.1.2
## Warning: package 'ggplot2' was built under R version 4.1.3
## Warning: package 'tibble' was built under R version 4.1.3
## Warning: package 'tidyr' was built under R version 4.1.3
## Warning: package 'readr' was built under R version 4.1.3
## Warning: package 'stringr' was built under R version 4.1.3
## Warning: package 'forcats' was built under R version 4.1.3
## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x lubridate::as.difftime() masks base::as.difftime()
## x lubridate::date()        masks base::date()
## x amt::filter()            masks dplyr::filter(), stats::filter()
## x lubridate::intersect()   masks base::intersect()
## x dplyr::lag()             masks stats::lag()
## x lubridate::setdiff()     masks base::setdiff()
## x lubridate::union()       masks base::union()
library(lubridate)
library(move)
## Warning: package 'move' was built under R version 4.1.3
## Loading required package: geosphere
## 
## Attaching package: 'geosphere'
## The following object is masked from 'package:amt':
## 
##     centroid
## Loading required package: sp
## 
## Attaching package: 'sp'
## The following object is masked from 'package:amt':
## 
##     bbox
## Loading required package: raster
## 
## Attaching package: 'raster'
## The following object is masked from 'package:amt':
## 
##     select
## The following object is masked from 'package:dplyr':
## 
##     select
## Loading required package: rgdal
## Warning: package 'rgdal' was built under R version 4.1.3
## Please note that rgdal will be retired by the end of 2023,
## plan transition to sf/stars/terra functions using GDAL and PROJ
## at your earliest convenience.
## 
## rgdal: version: 1.5-32, (SVN revision 1176)
## Geospatial Data Abstraction Library extensions to R successfully loaded
## Loaded GDAL runtime: GDAL 3.4.1, released 2021/12/27
## Path to GDAL shared files: C:/Users/veron/OneDrive - The Pennsylvania State University/Documents/R/win-library/4.1/rgdal/gdal
## GDAL binary built with GEOS: TRUE 
## Loaded PROJ runtime: Rel. 7.2.1, January 1st, 2021, [PJ_VERSION: 721]
## Path to PROJ shared files: C:\Program Files\PostgreSQL\13\share\contrib\postgis-3.0\proj
## PROJ CDN enabled: FALSE
## Linking to sp version:1.4-7
## To mute warnings of possible GDAL/OSR exportToProj4() degradation,
## use options("rgdal_show_exportToProj4_warnings"="none") before loading sp or rgdal.
## 
## Attaching package: 'move'
## The following object is masked from 'package:amt':
## 
##     speed
library(moveVis)
## Warning: package 'moveVis' was built under R version 4.1.3
## Find out about new features added to moveVis at
## http://movevis.org/news/index.html
library(RColorBrewer)
## Warning: package 'RColorBrewer' was built under R version 4.1.3
library(leaflet)
## Warning: package 'leaflet' was built under R version 4.1.3
#load cat data from Movebank as Movestack
# create 'login'
loginStored <- movebankLogin(username="mbstum", password="Cq6j9m4KrQRQGJ@")
cats <- getMovebankData("Feral cat (Felis catus) - Scotia, NSW", login = loginStored, includeExtraSensors = TRUE)
# https://cran.r-project.org/web/packages/move/index.html

#subsetting cats dataset as a Movestack object
#visual data exploration to chose cats with the most gps fixes within a similar time period
#subset cats for only Aug - Nov 2017

#list of cats to use
cat_list <- c("Ben_MC236", "Ivy_FC489", "Ken_MC536", "Ray_MC729", "Roy_MC769" )

#subset movestack by cat names
subcats <- cats[[cat_list]]

#subset movestack by year
subcats <- subcats[year(timestamps(subcats))==2017] 

#subset movestack by months aug - nov
subcats <- subcats[month(timestamps(subcats))%in% c(8,9,10,11)]

#check timestamps
min(timestamps(subcats))
## [1] "2017-08-01 00:36:43 UTC"
max(timestamps(subcats))
## [1] "2017-11-30 23:00:37 UTC"
# align data to a uniform time scale, resample 4x daily
#reduces frames of movement animation to a resonable time
mC <- align_move(subcats, res = 6, unit = "hours")
## Warning in FUN(X[[i]], ...): CRS object has comment, which is lost in output; in tests, see
## https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html
## Warning in FUN(X[[i]], ...): CRS object has comment, which is lost in output; in tests, see
## https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html

## Warning in FUN(X[[i]], ...): CRS object has comment, which is lost in output; in tests, see
## https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html

## Warning in FUN(X[[i]], ...): CRS object has comment, which is lost in output; in tests, see
## https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html

## Warning in FUN(X[[i]], ...): CRS object has comment, which is lost in output; in tests, see
## https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html

Interactive location maps

Before creating an animation, you can view all the location points for each individual on an interactive map, which allows you to zoom in/out and hover over each point to view information.

#colors for movement tracks
col = brewer.pal(n = 5, name = "Set2")

#create interactive map
view_spatial(mC, path_colours = col, render_as = "leaflet")

Finally, animate the movement paths into a GIF

# create spatial frames with a basemap 
frames <- frames_spatial(mC, path_colours = col,
                         tail_colour = "white", tail_size = 0.8, #how to include tail
                         trace_show = TRUE, trace_colour = "darkgray", #how to include path trace
                         map_service = 'osm', map_type = "terrain",
                         alpha = 0.5) %>% 
  add_labels(x = "Longitude", y = "Latitude") %>% # add some customizations, such as axis labels
  add_northarrow() %>% 
  add_scalebar() %>% 
  add_timestamps(type = "label") %>% 
  add_progress()

frames[[100]] # preview one of the frames, e.g. the 100th frame

animate_frames(frames, out_file = "catVisFinal.gif", overwrite = TRUE)
knitr::include_graphics("catVisFinal.gif")

Movement of 5 individual cats from Aug. 2017 - Nov. 2017

Movetrack visualization: foxes

We also created an interactive map of the fox locations and an animated path of the fox movement during the same time period as the cats.

Notice that with the moveVis package we are able to customize point and line colors, and map imagery.

## # A tibble: 9 x 2
##   local_identifier     n
##   <chr>            <int>
## 1 Don               5045
## 2 Ern               3908
## 3 Flo                717
## 4 Jen               3909
## 5 Joy               3150
## 6 Kev               4679
## 7 Lou               5269
## 8 Oli               4215
## 9 Ros               3963
## Warning in FUN(X[[i]], ...): CRS object has comment, which is lost in output; in tests, see
## https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html

## Warning in FUN(X[[i]], ...): CRS object has comment, which is lost in output; in tests, see
## https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html

## Warning in FUN(X[[i]], ...): CRS object has comment, which is lost in output; in tests, see
## https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html

## Warning in FUN(X[[i]], ...): CRS object has comment, which is lost in output; in tests, see
## https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html

## Warning in FUN(X[[i]], ...): CRS object has comment, which is lost in output; in tests, see
## https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html

## Warning in FUN(X[[i]], ...): CRS object has comment, which is lost in output; in tests, see
## https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html

## Warning in FUN(X[[i]], ...): CRS object has comment, which is lost in output; in tests, see
## https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html

## Warning in FUN(X[[i]], ...): CRS object has comment, which is lost in output; in tests, see
## https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html

## Warning in FUN(X[[i]], ...): CRS object has comment, which is lost in output; in tests, see
## https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html
knitr::include_graphics("foxVis.gif")

Movement of 9 individual foxes from Aug. 2017 - Nov. 2017

Lessons Learned

The moveVis package works exclusively with objects of class ‘movestack.’ While the package is supposed to allow easy conversion from a data frame to a movestack object, we were not able to successfully make this conversion within moveVis. We resorted to downloading the data directly from Movebank, which requires creating an account and accepting data use terms on the website first. This is not to say converting from a data frame is impossible, but this could make it more difficult for people to use moveVis if they are not housing their GPS data on Movebank.

There are large differences in processing time of the animated map depending on the frequency of location fixes. For example, 2 years of data at 20 minute fixes took several orders of magnitude longer (2 hours) to process than 6 months of data at 6 hour fixes (5 minutes).

Overall, it was quick to get started with the moveVis package, and fairly easy to get a basic map or gif created.

Other Visualization Tools

We investigated several other interactive mapping tools.

Leaflet and Mapview are utilized by the moveVis package, so the interactive maps created within moveVis are technically Leaflet maps. However, it is possible to customize interactive maps using Leaflet or Mapview directly. The customization process is very similar to ggplot2, and there are plentiful helpful resources online (https://rstudio.github.io/leaflet/).

Plotly is a commonly used package to upgrade a traditional ggplot to an interactive plot. This can be used for non-spatial data, too!

A package called DynamoVIS (https://dynamovis.geog.ucsb.edu/index) serves a similar purpose as moveVis, but it is not housed on CRAN and it must be downloaded from GitHub. The capabilities appear to be greater than moveVis, but it likely requires more effort from the user.

Next Steps

There were several customizations and visual integrations we hoped to accomplish with moveVis, but were not able to complete. 1. We attempted to set the path (“tail”) colors equal to the animated line, unique to each individual. It seems the package only allows a single parameter value for the tail color, but this is something we could explore further. 2. There is a function in moveVis that allows animated maps to play side-by-side, which would create a useful visualization. There are some underlying steps to align the maps properly, and we simply ran out of time to attempt this mapping feature. 3. Similarly, we wanted to combine the fox and cat movement paths into a single animated map, grouped by species. We encountered difficulties with this goal because the movestack data class can be difficult to manipulate. We have not determined if combining the data sets for a single animated map is impossible in the moveVis package, but we were not able to complete our attempt.

Citations

Roshier DA, Carter A (2021) Data from: Space use and interactions of two introduced mesopredators, European red fox and feral cat, in an arid landscape. Movebank Data Repository. doi:10.5441/001/1.6m6h9s33

Roshier DA, Carter A. 2021. Space use and interactions of two introduced mesopredators, European red fox and feral cat, in an arid landscape. Ecosphere. 12(7):e03628. doi:10.1002/ecs2.3628

Schwalb-Willmann J, Remelgado R, Safi K, Wegmann M (2020). “moveVis: Animating movement trajectories in synchronicity with static or temporally dynamic environmental data in R.” Methods in Ecology and Evolution. doi:10.1111/2041-210X.13374.

Signer J, Fieberg J, Avgar T (2019). “Animal movement tools (amt): R package for managing tracking data and conducting habitat selection analyses.” Ecology and Evolution, 9, 880–890. https://doi.org/10.1002/ece3.4823.

Wickham H, Averick M, Bryan J, Chang W, McGowan LD, François R, Grolemund G, Hayes A, Henry L, Hester J, Kuhn M, Pedersen TL, Miller E, Bache SM, Müller K, Ooms J, Robinson D, Seidel DP, Spinu V, Takahashi K, Vaughan D, Wilke C, Woo K, Yutani H (2019). “Welcome to the tidyverse.” Journal of Open Source Software, 4(43), 1686. doi:10.21105/joss.01686.

Graul, Christian (2016): leafletR: Interactive Web-Maps Based on the Leaflet JavaScript Library. R package version 0.4-0, http://cran.r-project.org/package=leafletR.