--- title: "Trajectory analysis of Turkey vultures" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Trajectory analysis of Turkey vultures} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, fig.width = 7, fig.height = 7, comment = "#>" ) if (Sys.info()["user"] != "bart") { if (Sys.getenv("MBPWD") != "") { options(keyring_backend = "env") move2::movebank_store_credentials("move2_user", Sys.getenv("MBPWD")) } else { knitr::opts_chunk$set(eval = FALSE) } } Sys.setlocale("LC_TIME", "en_US.utf8") ``` ## Retrieve data ```{r setup} library(move2) vulture_data <- movebank_download_study("Turkey vultures in North and South America") vulture_data ``` In this case some tracks have very long time gaps, to prevent distant points to be connected by a line we split those tracks by creating a new id. ```{r map_of_vulture_track, fig.alt="A map showing the trajectories of vultures using coast lines as a reference"} library(dplyr, quietly = TRUE) library(ggplot2, quietly = TRUE) library(rnaturalearth, quietly = TRUE) library(units, quietly = TRUE) vulture_lines <- vulture_data %>% mutate_track_data(name = individual_local_identifier) %>% mutate( large_gaps = !(mt_time_lags(.) < set_units(1500, "h") | is.na(mt_time_lags(.))), track_sub_id = cumsum(lag(large_gaps, default = FALSE)), new_track_id = paste(mt_track_id(.), track_sub_id) ) %>% mt_set_track_id("new_track_id") %>% mt_track_lines() ggplot() + geom_sf(data = ne_coastline(returnclass = "sf", 50)) + theme_linedraw() + geom_sf( data = vulture_lines, aes(color = name) ) + coord_sf( crs = sf::st_crs("+proj=aeqd +lon_0=-83 +lat_0=8 +units=km"), xlim = c(-3500, 3800), ylim = c(-4980, 4900) ) ``` ## Categorize seasons ```{r categorize_seasons} library(magrittr, quietly = TRUE) library(lubridate, quietly = TRUE) vulture_data <- vulture_data %>% mutate( month = month(mt_time(), label = TRUE, abbr = FALSE), season = recode_factor(month, January = "Wintering", February = "Wintering", March = "Wintering", April = "North migration", May = "North migration", June = "Breeding", July = "Breeding", August = "Breeding", September = "South migration", October = "South migration", November = "Wintering", December = "Wintering" ), # Here we change season to NA if the next location is either from # a different track or season season = if_else(season == lead(season, 1) & mt_track_id() == lead(mt_track_id(), 1), season, NA ) ) ``` Annotate speed and azimuth to the trajectory. ```{r annotate_track} vulture_data <- vulture_data %>% mutate(azimuth = mt_azimuth(.), speed = mt_speed(.)) ``` ## Seasonal distribution per individual ```{r azimuth_distribution, fig.height=6, fig.alt="A plot of the azimuths of the tracks split out per season and individual"} library(circular, quietly = TRUE) vulture_azimuth_distributions <- vulture_data %>% filter(speed > set_units(2, "m/s"), !is.na(season)) %>% group_by(season, track_id = mt_track_id()) %>% filter(n() > 50) %>% summarise(azimuth_distribution = list(density( as.circular( drop_units(set_units( azimuth, "degrees" )), units = "degrees", modulo = "asis", zero = 0, template = "geographic", rotation = "clock", type = "angles" ), bw = 180, kernel = "vonmises" ))) # Load purrr for map function library(purrr, quietly = TRUE) # Load tidy r for unnest function library(tidyr, quietly = TRUE) vulture_azimuth_distributions %>% mutate( x = map(azimuth_distribution, ~ as.numeric(.$x)), y = map(azimuth_distribution, ~ .$y) ) %>% select(-azimuth_distribution) %>% unnest(c(x, y)) %>% ggplot() + geom_path(aes(x = as.numeric(x), y = y, color = season)) + coord_polar(start = pi / 2) + theme_linedraw() + facet_wrap(~ factor(track_id)) + scale_x_continuous( name = NULL, breaks = (-2:1) * 90, labels = c("S", "W", "N", "E") ) + scale_y_continuous(name = NULL, limits = c(-0.8, 1.4), expand = c(0L, 0L)) + labs(color = "Season") ``` ## Individual trajectory ```{r speed_direction, fig.height=5, fig.alt="A plot exploring the relation between direction and speed for one track"} leo <- vulture_data |> filter_track_data(individual_local_identifier == "Leo") |> mutate(speed_categorical = cut(speed, breaks = c(2, 5, 10, 15, 35))) leo |> ggplot(aes(x = azimuth, y = speed)) + geom_point() + scale_x_units(unit = "degrees", breaks = -2:2 * 90, expand = c(0L, 0L)) + theme_linedraw() ``` ```{r seasonal_speed_distribution, fig.alt="Plot that visualizes the speed and directions per season for one individual"} leo |> filter(speed > set_units(2L, "m/s"), !is.na(season)) |> ggplot() + coord_polar(start = pi) + geom_histogram( aes( x = set_units(azimuth, "degrees"), fill = speed_categorical ), breaks = set_units(seq(-180L, 180L, by = 10L), "degrees"), position = position_stack(reverse = TRUE) ) + scale_x_units( name = NULL, limits = set_units(c(-180L, 180L), "degrees"), breaks = (-2L:2L) * 90L ) + facet_wrap(~season) + scale_fill_ordinal("Speed") + theme_linedraw() ``` Plot turn angle distribution. ```{r turnangle_plot, fig.height=4, fig.width=5, fig.alt="Plot of the turning angle distribution"} pi_r <- set_units(pi, "rad") leo %>% mutate(turnangle = mt_turnangle(.)) %>% filter(speed > set_units(2L, "m/s"), lag(speed, 1L) > set_units(2L, "m/s")) %>% ggplot() + geom_histogram( aes( x = turnangle, fill = speed_categorical ), position = position_stack(reverse = TRUE) ) + scale_fill_ordinal("Speed") + coord_polar(start = pi) + scale_x_units(limits = c(-pi_r, pi_r), name = NULL) + scale_y_continuous(limits = c(-500L, 650L), breaks = c(0L, 250L, 500L)) + theme_linedraw() ``` ## Net displacement Here we visualize the distance to the first location of each trajectory. Notice the distances from the first location directly have the correct units associated due to the projection that is included. ```{r nsd, fig.height=5, fig.alt="Plot of the net squared displacement overtime per individual"} vulture_data <- vulture_data %>% group_by(mt_track_id()) %>% mutate(displacement = c(st_distance( !!!syms(attr(., "sf_column")), (!!!syms(attr(., "sf_column")))[row_number() == 1] ))) vulture_data %>% ggplot() + geom_line(aes( x = timestamp, y = set_units(displacement, "km"), color = individual_local_identifier )) + ylab("Distance from start") + theme_linedraw() ``` Using `dplyr` it is also possible to do more complex operations per individual (or other grouping variables for that matter). Here we do a spatial intersection for each time an individual crosses a circular buffer a 1000 kilometers away from its initial location. ```{r buffer} vulture_data %>% # Group by individual/track_iu group_by(mt_track_id()) %>% # Create a list with each individual being a element of the list group_split() %>% # Use purrr::map to do some operation on each track purrr::map(~ { # Create a circular buffer around the first location buffer <- sf::st_cast(sf::st_buffer( sf::st_geometry(head(.x, 1)), units::set_units(1000, "km") ), "LINESTRING") # Do a spatial interpolation to the locations the track crosses the buffer mt_interpolate( .x, sf = buffer, omit = TRUE ) }) |> # Use purrr compact to omit individuals that never reach a 1000 kilometers from the first location and thus do not have an crossing purrr::compact() |> # Combine back into one move2 mt_stack() ``` Using `dplyr::group_map()` we can calculate statistics for groups, such as here a yearly convex hull for each individual. ```{r convex} yearly_convex_hull <- vulture_data %>% group_by(year = factor(lubridate::year(timestamp)), track=mt_track_id()) %>% group_map(~ st_sf(.y, geom = sf::st_convex_hull(sf::st_union(.x)))) %>% bind_rows() yearly_convex_hull |> ggplot() + geom_sf(data = ne_coastline(returnclass = "sf", 50)) + geom_sf(aes(fill = year), alpha = .1) + theme_linedraw() + coord_sf( crs = sf::st_crs("+proj=aeqd +lon_0=-83 +lat_0=8 +units=km"), xlim = c(-3500, 3800), ylim = c(-4980, 4900) ) ```