Skip to contents

Select a subset of fixes such that every successive retained pair has a time lag within [interval - tolerance, interval + tolerance], and such that the number of retained fixes is maximised. The original move2 object is returned whole with a logical column thin_selected marking retained fixes.

Usage

mt_thin_time(
  x,
  interval,
  tolerance = NULL,
  criterion = c("closest", "first"),
  remove = FALSE
)

Arguments

x

A move2 object.

interval

Target interval between successive retained fixes. A difftime, a numeric in seconds, or a string parseable by lubridate::duration() (e.g. "45 mins").

tolerance

Half-width of the acceptance window around interval, in the same units as interval. Defaults to interval / 10.

criterion

Tie-breaking rule when multiple chains of equal length exist: "closest" (minimum total deviation from the target interval, default) or "first" (earliest-starting chain).

remove

Logical. If TRUE, return only the retained fixes. Default FALSE — the full object is returned with the thin_selected flag.

Value

A move2 object. With remove = FALSE, the input is returned unchanged except for a new logical column thin_selected. With remove = TRUE, only rows with thin_selected == TRUE are returned. For multi-track input the thinning is performed independently per track.

Details

This is the move2 analogue of move::thinTrackTime(). The legacy implementation used memoised recursion on 100-fix chunks; this one uses a linear-time dynamic-programming sweep, so it scales cleanly to tracks with \(10^5\)+ fixes.

The problem is to find the longest path in a directed acyclic graph whose nodes are fixes and whose edges i -> j exist iff t[j] - t[i] is within [interval - tolerance, interval + tolerance]. On sorted timestamps it is solved with the recursion $$f(i) = 1 + \max_{j < i, \; t_i - t_j \in [d - \tau, d + \tau]} f(j)$$ followed by a single backtrace through the predecessor array.

Tracks are pre-split at any gap longer than interval + tolerance; thinning never bridges such a gap.

When several chains are equally long, criterion = "closest" (the default) picks the chain whose summed absolute deviation from the target interval is smallest; criterion = "first" picks the chain that starts earliest. The "all" mode of the legacy function is not reproduced — it can explode combinatorially and is rarely the operationally useful choice. If you need exact parity with move::thinTrackTime(..., criteria = "all"), call the legacy function on a move2::to_move() conversion.

See also

move2::mt_filter_per_interval() for bucket-aligned thinning (different semantics), move2::mt_interpolate() for the inverse problem of increasing resolution.

Examples

# \donttest{
library(move2)
fishers <- mt_read(mt_example())
fishers <- fishers[!sf::st_is_empty(fishers), ]
leroy   <- fishers[mt_track_id(fishers) == "M4", ][seq_len(500), ]

out <- mt_thin_time(leroy, interval = "45 min", tolerance = "5 min")
table(out$thin_selected)
#> 
#> FALSE  TRUE 
#>   432    68 

kept <- out[out$thin_selected, ]
summary(as.numeric(mt_time_lags(kept, units = "min")))
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.     NAs 
#>   40.40   44.77   49.90  119.27   60.65 1160.75       1 
# }