Thin a track to a target time interval (tolerance-constrained)
Source:R/mt_thin_time.R
mt_thin_time.RdSelect 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
move2object.- interval
Target interval between successive retained fixes. A
difftime, anumericin seconds, or a string parseable bylubridate::duration()(e.g."45 mins").- tolerance
Half-width of the acceptance window around
interval, in the same units asinterval. Defaults tointerval / 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. DefaultFALSE— the full object is returned with thethin_selectedflag.
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
# }