5. Getting Spatial with sftrack

knitr::opts_chunk$set(
    echo = TRUE,
    fig.width = 6,
    fig.asp = 0.7
)
library("lwgeom")
library("sftrack")
# Make tracks from raw data
# raccoon <- read.csv(system.file('extdata/raccoon_data.csv', package='sftrack'))
data("raccoon", package = "sftrack")
raccoon$month <- as.POSIXlt(raccoon$timestamp)$mon + 1

raccoon$time <- as.POSIXct(raccoon$timestamp, tz = "EST")
coords <- c("longitude","latitude")
group <- list(id = raccoon$animal_id, month = as.POSIXlt(raccoon$timestamp)$mon+1)
time <- "time"
error <- "fix"
crs <- 4326
my_sftrack <- as_sftrack(data = raccoon, coords = coords, group = group, time = time, error = error, crs = crs)
my_sftraj <- as_sftraj(data = raccoon, coords = coords, group = group, time = time, error = error, crs = crs)

Geometry column

As stated earlier, the geometry column is built using sf, so the column functions exactly as it would in sf. You can modify it and redefine it using the sf tools. More specifically the geometry column of an sf_track object is an sfc column. The main difference between a standard sf object created using st_as_sf is that we automatically allow empty geometries, where as this option is turned off by default in st_as_sf().

my_sftrack$geometry

An sftrack object is simply an sfc of sfc_POINTS, this contrasts with an sftraj object which is a mixture of a POINT and LINESTRING. This is because a trajectory can have a start point and an NA end point, a line segment, or an NA and an end point. This allows no-loss conversion back and forth between sftrack and an sftraj, and because linestrings can not have a NULL point in them.

my_sftraj$geometry

This does mean that not all sf functions will handle an sftraj object like it would an sftrack if there are NAs in the data set. In these cases st_is_empty() can help to subset the points that contain geometry data.

Working with sf

As an sftrack object is an sf object essentially all of the sf functions will work on it.

library("sf")
st_length(my_sftraj)[1:10]

df1 <- data.frame(
  id = c(1, 1, 1, 1),
  time = as.POSIXct("2020-01-01 12:00:00", tz = "UTC") + 60*60*(1:4),
  x = c(1, 3, 3, 2),
  y = c(1, 1, 3, 4)
)

road <- st_linestring(rbind(
  c(1, 2),
  c(5, 2),
  c(5, 0)
)
)

animal1 <- as_sftraj(df1)
plot(animal1)

plot(road,  col = "red", add = TRUE)

# Does the animal cross the road?

any(st_intersects(animal1, road, sparse = FALSE))

# When?
animal1$time[st_intersects(animal1, road, sparse = FALSE)]

# How often does the animal stay near the road?
st_is_within_distance(animal1, road, 1)

# How close is the animal from the road?

st_distance(animal1, road)

The only thing to remember, is that a sftraj is a GEOMETRY column, and occasionally a function may not work with it. In those cases is_linestring() can be used to filter out points that do not have a t2.

Plotting

Base plotting

sftrack is built to work with sf base plot methods. This means you can use most of the sf plot methods, sftrack largely just controls the grouping of the plot then feeds it back to plot.sf().

plot(my_sftraj)
plot(my_sftraj, key.pos = 4, key.width = lcm(4), key.length = 1)
get_key_pos(my_sftraj)

This means that everytime you change the active_group, the plot view will change.

active_group(my_sftraj$sft_group) <- "id"
active_group(my_sftraj$sft_group)
plot(my_sftraj)

Most arguments for plot.sf are available to use as additional arguments to plot.

plot(my_sftrack, axes = TRUE, cex = 5)

ggplot2

This is a work in progress, but theres a geom_sftrack function that feeds geom_sf with the correct plotting information. Like geom_sf you input data into the geom_sftrack function and not into ggplot(). Again ggplot assumes active_group is the grouping variable. Plots vary slightly based on if they're sftrack of sftraj class.

Geom_sftrack is essentially geom_sf(data = data, aes(color = group_labels(data))) with NULL points subetted out. This may help when a user requires more advanced modification than the geom_sftrack allows.

library("ggplot2")
ggplot() + geom_sftrack(data = my_sftraj)

You can use geom_sftrack just like any other ggplot layer, which means you can continue to make manuscript quality plots.

cols <- c("TTP-041_1" = "dodgerblue1", "TTP-041_2" = "darkseagreen2",
          "TTP-058_1" = "darkorchid1", "TTP-058_2" = "khaki3")

ggplot() + geom_sftrack(data = my_sftrack, size = 3, alpha = 0.5) +
scale_color_manual(values = cols) + 
  ggtitle("Raccoons at Tree Tops Park Winter 2020") +
  theme_bw() + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) 

Special functions for working with an sftraj

To help with working with more complex sftraj objects, there is a growing suite of sftraj specific functions:

Step mode in plotting

sftraj objects create linestrings for each row of data, as each row is not assumed to be related to the next row of data. This data structure may become inefficient to work with when plotting large data sets. When appropriate these trajectories can be generalized by combining linestrings where lines meet.

For plotting purposes we can create these linestrings quickly and plot them at much faster speeds than individual lines.

For plot and geom_sftrack there is an argument called step_mode that refers to whether youd like to plot the lines individually (step_mode = TRUE), or generalize them into connected linestrings (step_mode = FALSE). By default step_mode is set to TRUE.

library("ggplot2")
ggplot() + geom_sftrack(data = my_sftraj, step_mode = TRUE)

You'll notice that the appearance of the plot changes as the POINTs are also displayed. This is because step mode adds POINTS to the plot that contain a fill as well as a color property.

coord_traj

This function returns a data.frame (x,y,z) of the point at t1 of each sftraj geometry. It works nearly identically to sf::st_coordinates().

coord_traj(my_sftraj$geometry)[1:10, ]

pts_traj

If youd like to retain the geometries but still pull out t1 point you can use pts_traj(). This functions returns a list of the beginning point of each sftraj geometry, or an sfc column when using the argument sfc = TRUE.

pts_traj(my_sftraj$geometry)[1:5]

pts_traj(my_sftraj$geometry, sfc= TRUE)[1:5]

is_linestring

May help if you'd like to quickly filter an sftraj object to just contain pure linestrings. is_linestring() returns TRUE or FALSE if the geometry is a linestring. This does not recalculate anything, it just filters out steps that contained NAs in either phase. Its nearly identical to st_is(x,'LINESTRING'), but may be more intuitive for users.

is_linestring(my_sftraj$geometry)[1:10]
new_sftraj <- my_sftraj[is_linestring(my_sftraj$geometry), ]
head(new_sftraj)

Calculating step metrics of an sftraj

For use in movement models, you may need to calculate the dx, dy, length, and turn angles of each step. You can do that in sftrack using step_metrics(). It should be noted it will accept an sftrack object, however, it first converts the geometries internally to sftraj geometries and then calculates step metrics. As with other sf objects, the return is assumed to be in the units of the crs when not specified. Absolute angle is measured in radians.

step_metrics(my_sftraj[1:10, ])


Try the sftrack package in your browser

Any scripts or data that you put into this service are public.

sftrack documentation built on March 31, 2023, 7:27 p.m.