Cuts a stream of vehicle GPS pings into individual trips and returns one row
of features per trip: its vehicle, direction, start and end times, duration,
and day of week. Takes a data.frame or a path to a CSV, and optionally writes
the result to output_path.
Usage
g2g_extract_trips(
gps_data,
terminals_data = NULL,
terminals_buffer_radius = NULL,
output_path = NULL,
projected_crs = NULL,
backend = "auto",
projected = NULL,
vehicle_col = "vehicle_id",
time_col = "timestamp",
tz = NULL,
trip_col = NULL,
direction_col = NULL,
cut_on_direction_change = FALSE,
direction_debounce_min_pings = 2L,
direction_debounce_min_seconds = 600,
session_gap = 4 * 3600,
segmentation = c("auto", "terminals", "layover"),
layover_gap = 10 * 60,
layover_radius = 50,
diagnostics_warn = getOption("gps2gtfs.diagnostics_warn", TRUE)
)Arguments
- gps_data
A data.frame or path to the raw GPS CSV.
- terminals_data
A data.frame or path to the terminal coordinates CSV. Optional (
NULL) withsegmentation = "layover"or withdirection_col; required for terminal-buffer segmentation.- terminals_buffer_radius
Numeric. Buffer radius for terminals (in meters). Only consumed by terminal-buffer segmentation; may be omitted otherwise.
- output_path
Character. Optional path to write output trip features as CSV. Default is
NULL(no file written).- projected_crs
Numeric. The EPSG code of a projected coordinate system used for metric distance calculations. Required when
projected = TRUE; otherwise a suitable UTM projection is selected when omitted.- backend
Character. The backend to use:
"auto","rust","rcpp", or"pure_r". Automatic selection prefers Rust, then Rcpp, then pure R. Explicit unavailable backends produce an error.- projected
Logical. Whether plain-table coordinates are already projected. Out-of-bounds coordinates require explicit
TRUE.- vehicle_col
Character. Name of the vehicle identifier column in
gps_data. Default"vehicle_id"(GTFS-Realtime convention).- time_col
Character. Name of the timestamp column in
gps_data. Default"timestamp"(GTFS-Realtime convention).- tz
Character. Timezone in which non-
POSIXcttimestamps are interpreted; also the timezone whose midnight bounds service days and thus every downstream GTFS clock string. Pass the feed's operating timezone, or supply localizedPOSIXcttimestamps.NULL(default) parses character input as UTC with a warning. Passed tog2g_clean_gps.- trip_col
Character. Optional name(s) of column(s) holding supplied trip identities (e.g.
"trip_id"from GTFS-Realtime Vehicle Positions). When given, trips are segmented by those identities (fast path) instead of inferred from terminal-buffer crossings. Withoutdirection_col, the first/last ping of each segment is matched to the nearest terminal for direction assignment. May be several columns that jointly identify a trip - e.g.c("route_id", "direction_id", "start_date", "start_time"), the GTFS-Realtime TripDescriptor, for feeds whose positions carry notrip_id; they are combined into one identity. DefaultNULL(spatial inference).- direction_col
Character. Optional column giving each ping's travel direction (e.g. GTFS-Realtime
"direction_id"). Only used withtrip_col. When supplied, trip direction is taken from the data rather than inferred from which of two terminals a trip started at, soterminals_databecomes optional and routes that are not simple two-terminal lines (short-turns, variants, one-way services) are handled. For stop-time extraction, stops are grouped for matching by theirdirectionlabel, which must use the same values asdirection_col. By default this column labels a trip's direction; it does not cut trips. A trip has exactly one direction, so ifdirection_coltakes more than one value inside a segment thattrip_coldeclared to be one trip, the two inputs contradict each other and extraction stops with an error: either add the direction column totrip_colto segment by it, setcut_on_direction_change = TRUE, or collapse it to one value per supplied trip identity. DefaultNULL.- cut_on_direction_change
Logical. When
TRUE, a change indirection_colcuts a trip rather than raising the mid-trip direction conflict error above. Raw direction fields flip spuriously, so the change is debounced first: only a run of consecutive identical values that is both long enough in pings and long enough in seconds counts as a real direction change, and shorter contrary bursts are absorbed into the direction around them. Requiresdirection_col. DefaultFALSE, which leaves behaviour unchanged.- direction_debounce_min_pings
Integer. Minimum consecutive pings for a direction run to be treated as a real direction change. Inert unless
cut_on_direction_change = TRUE. Note this floor only binds on high-cadence feeds or at smalldirection_debounce_min_seconds: at a 15 s ping cadence a run needs aboutmin_seconds / 15 + 1pings to span the seconds threshold at all, so from roughly 300 s upwards the seconds threshold binds first. Default2L.- direction_debounce_min_seconds
Numeric. Minimum intra-run span, in seconds, for a direction run to be treated as a real direction change. A single-ping run spans 0 seconds and is therefore always absorbed. Inert unless
cut_on_direction_change = TRUE. Default 600.- session_gap
Numeric. Successive observations of a vehicle further apart than this many seconds start a new driving session; trips never span sessions. This replaces the former calendar-date boundary, so overnight trips crossing midnight stay intact while overnight parking still separates one day's operations from the next. Default 4 hours (
4 * 3600).- segmentation
Character. How the raw-GPS spatial path cuts trips when no
trip_colis supplied:"terminals"(classic two-terminal buffer model),"layover"(cut wherever the vehicle dwells longer thanlayover_gap, anywhere on the route - handles short-turn, loop, and multi-branch services; all trips share a single direction group), or"auto"(default:"terminals"whenterminals_datais given,"layover"otherwise). Not used withtrip_col.- layover_gap
Numeric. Layover segmentation only: dwells longer than this many seconds bound trips - whether a silent gap between successive pings or a stationary spell (pings present but staying within
layover_radius). Mode-dependent: must exceed the feed's ping interval and normal in-service stop dwell, and stay below the shortest real layover. Default 10 minutes (10 * 60).- layover_radius
Numeric. Layover segmentation only: a vehicle counts as stationary while successive pings stay within this many meters of the dwell's first ping (anchoring absorbs GPS jitter while parked). Default 50.
- diagnostics_warn
Logical. Emit a one-line
warning()when the run drops coverage worth surfacing (pings with no usable trip identity, unmatched segments, stops out of range) so unattended or agent-driven pipelines notice silent loss. The full breakdown is always available viag2g_diagnosticsregardless of this flag. DefaultgetOption("gps2gtfs.diagnostics_warn", TRUE).
Value
A data.table containing extracted trip features. start_time
and end_time are absolute POSIXct times in the timezone of
the input timestamps. Rows are returned in a stable, backend-invariant
order: sorted by the internal integer trip_id. The result carries
an attr(., "diagnostics") coverage table (see
g2g_diagnostics). See the "Inference tables, not GTFS files"
section of g2g_extract_trips_and_stop_times.
Details
Where the cuts fall is the whole problem, and there are three regimes. If the
data already carries trip identities (trip_col, e.g. a GTFS-Realtime
trip_id), segmentation follows them and nothing is inferred. Otherwise
the trips are inferred from the trajectory: segmentation = "terminals"
cuts on crossings of two known terminal buffers, and
segmentation = "layover" cuts wherever the vehicle dwells longer than
layover_gap, anywhere on the route, which is what handles short-turns,
loops and multi-branch service. session_gap bounds all three: a vehicle
unseen for that long starts a new driving session and no trip spans one, so
overnight trips stay intact while overnight parking still separates one day's
operations from the next.
Inferred boundaries are estimates, and layover_gap in particular is
mode- and feed-dependent: it must exceed the ping interval and the normal
in-service dwell, and stay below the shortest real layover. The result carries
a coverage table (g2g_diagnostics) reporting what the run
dropped, which is the way to tell a clean extraction from a plausible-looking
one.
To get stop times as well, use
g2g_extract_trips_and_stop_times, which runs this segmentation
and then matches the pings of each trip to stop buffers.
Examples
# \donttest{
data(g2g_data_gps)
data(g2g_data_terminals)
trips <- g2g_extract_trips(
gps_data = g2g_data_gps,
terminals_data = g2g_data_terminals,
terminals_buffer_radius = 50
)
#> Starting Pipeline for extracting Trip Data using backend: rust
#> [INFO] Removed 56 duplicated (vehicle_id, timestamp) row(s).
#> [INFO] Auto-detected local UTM projection EPSG:32644 (Zone 44N)
#> Pipeline finished successfully!
#> Warning: gps2gtfs extraction lost coverage: kept 6 trips from 2,167 input pings. Dropped pings_dropped_zero_coord (28). Inspect the full coverage table with g2g_diagnostics(result) (also attr(result, "diagnostics")). Silence with diagnostics_warn = FALSE or options(gps2gtfs.diagnostics_warn = FALSE).
# }