Skip to content

Commit 858e499

Browse files
authored
Merge pull request #225 from r-transit/dev/raptor_transfer_type
Support in-seat trip transfers in travel time calculations
2 parents dbfe7fa + a1c1f28 commit 858e499

File tree

11 files changed

+1141
-597
lines changed

11 files changed

+1141
-597
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: tidytransit
22
Type: Package
33
Title: Read, Validate, Analyze, and Map GTFS Feeds
4-
Version: 1.7.1
4+
Version: 1.7.1.900
55
Authors@R: c(
66
person("Flavio", "Poletti", role = c("aut", "cre"), email = "flavio.poletti@hotmail.ch"),
77
person(given = "Daniel",family = "Herszenhut",role = c("aut"),email = "dhersz@gmail.com",comment = c(ORCID = "0000-0001-8066-1105")),

R/filter_stop_times.r

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#' Filter a `stop_times` table for a given date and timespan.
2+
#'
3+
#' @param gtfs_obj gtfs feed (tidygtfs object)
4+
#' @param extract_date date to extract trips from this day (Date or "YYYY-MM-DD" string)
5+
#' @param min_departure_time (optional) The earliest departure time. Can be given as "HH:MM:SS",
6+
#' hms object or numeric value in seconds.
7+
#' @param max_arrival_time (optional) The latest arrival time. Can be given as "HH:MM:SS",
8+
#' hms object or numeric value in seconds.
9+
#'
10+
#' @return Filtered `stop_times` data.table for [travel_times()] and [raptor()].
11+
#'
12+
#' @export
13+
#' @examples
14+
#' feed_path <- system.file("extdata", "routing.zip", package = "tidytransit")
15+
#' g <- read_gtfs(feed_path)
16+
#'
17+
#' # filter the sample feed
18+
#' stop_times <- filter_stop_times(g, "2018-10-01", "06:00:00", "08:00:00")
19+
filter_stop_times = function(gtfs_obj,
20+
extract_date,
21+
min_departure_time,
22+
max_arrival_time) {
23+
if(!feed_has_non_empty_table(gtfs_obj, "stop_times")) {
24+
stop("`gtfs_obj` has no `stop_times`")
25+
}
26+
# check feasibility for routing
27+
assert_routable_feed(gtfs_obj)
28+
gtfs_obj$stop_times <- replace_NA_times(gtfs_obj$stop_times)
29+
30+
# check transfers
31+
if(feed_contains(gtfs_obj, "transfers")) {
32+
transfers <- gtfs_obj[["transfers"]]
33+
} else {
34+
warning("No transfers found in feed, travel_times() or raptor() might produce unexpected results")
35+
transfers <- data.frame()
36+
}
37+
38+
# trim min/max times
39+
departure_time_num <- arrival_time_num <- NULL
40+
if(is.character(extract_date)) {
41+
extract_date <- as.Date(extract_date)
42+
}
43+
if(missing(min_departure_time)) {
44+
min_departure_time <- 0
45+
} else if(is.character(min_departure_time)) {
46+
min_departure_time <- hhmmss_to_seconds(min_departure_time)
47+
}
48+
if(missing(max_arrival_time)) {
49+
max_arrival_time <- max(gtfs_obj$stop_times$arrival_time, na.rm = TRUE)+1
50+
} else if(is.character(max_arrival_time)) {
51+
max_arrival_time <- hhmmss_to_seconds(max_arrival_time)
52+
}
53+
min_departure_time <- as.numeric(min_departure_time)
54+
max_arrival_time <- as.numeric(max_arrival_time)
55+
56+
if(max_arrival_time <= min_departure_time) {
57+
stop("`max_arrival_time` is before `min_departure_time`", call. = FALSE)
58+
}
59+
60+
# trips running on day
61+
service_ids = filter(gtfs_obj$.$dates_services, date == extract_date)
62+
if(nrow(service_ids) == 0) {
63+
stop("No stop_times on ", extract_date)
64+
}
65+
trips = inner_join(gtfs_obj$trips, service_ids, by = "service_id")
66+
trips = as.data.table(unique(trips[,c("trip_id")]))
67+
68+
# prepare stop_times
69+
stop_times_dt = as.data.table(gtfs_obj$stop_times)
70+
stop_times_dt <- stop_times_dt[trips, on = "trip_id"]
71+
set_num_times(stop_times_dt)
72+
stop_times_dt <- stop_times_dt[departure_time_num >= min_departure_time &
73+
arrival_time_num <= max_arrival_time,]
74+
setindex(stop_times_dt, "stop_id")
75+
if(nrow(stop_times_dt) == 0) {
76+
stop("No stop times between `min_departure_time` and `max_arrival_time`", call. = FALSE)
77+
}
78+
79+
# store stops and transfers in attributes
80+
attributes(stop_times_dt)$stops <- stops_as_dt(gtfs_obj$stops)
81+
attributes(stop_times_dt)$transfers <- transfers
82+
attributes(stop_times_dt)$extract_date <- extract_date
83+
attributes(stop_times_dt)$min_departure_time <- min_departure_time
84+
attributes(stop_times_dt)$max_arrival_time <- max_arrival_time
85+
86+
return(stop_times_dt)
87+
}

R/raptor-checks.r

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# raptor()
2+
assert_routable_stop_times = function(stop_times, warn_on_demand = TRUE) {
3+
if(!all(c("arrival_time", "departure_time") %in% colnames(stop_times))) {
4+
stop("`stop_times` must have `arrival_time` and `departure_time` columns for routing", call. = FALSE)
5+
}
6+
if(sum(is.na(stop_times[["arrival_time"]]) & is.na(stop_times[["departure_time"]])) == nrow(stop_times)) {
7+
if(has_on_demand(stop_times)) {
8+
stop("Feed contains on-demand services which are not supported by tidytransit routing", call. = FALSE)
9+
} else {
10+
stop("No arrival and departure times found in `stop_times`", call. = FALSE)
11+
}
12+
}
13+
if(has_on_demand(stop_times) && warn_on_demand) {
14+
warning("Feed contains on-demand services which are not supported by tidytransit routing", call. = FALSE)
15+
}
16+
invisible(TRUE)
17+
}
18+
19+
has_on_demand = function(stop_times) {
20+
any(c("start_pickup_drop_off_window", "end_pickup_drop_off_window") %in% colnames(stop_times))
21+
}
22+
23+
# filter_stop_times()
24+
assert_routable_feed = function(g) {
25+
assert_routable_stop_times(g$stop_times, FALSE)
26+
27+
# missing dates
28+
if(is.null(g[["."]][["dates_services"]])) {
29+
stop("No valid dates defined in feed", call. = FALSE)
30+
}
31+
32+
# stops
33+
if(is.null(g[["stops"]][["stop_id"]]) || is.null(g[["stop_times"]][["stop_id"]])) {
34+
stop("`stops` and `stop_times` must have a `stop_id` column", call. = FALSE)
35+
}
36+
37+
invisible(TRUE)
38+
}
39+
40+
# travel_times()
41+
check_max_departure_time = function(max_departure_time, arrival, time_range,
42+
missing_time_range, filtered_stop_times) {
43+
if(!is.null(max_departure_time)) {
44+
warning("max_departure_time is deprecated, use time_range")
45+
if(!missing_time_range) {
46+
stop("cannot set max_departure_time and time_range")
47+
}
48+
if(arrival) {
49+
stop("cannot set max_departure_time and arrival=TRUE")
50+
}
51+
if(is.character(max_departure_time)) {
52+
max_departure_time <- hhmmss_to_seconds(max_departure_time)
53+
}
54+
min_departure_time = min(filtered_stop_times$departure_time_num)
55+
stopifnot(max_departure_time > min_departure_time)
56+
time_range <- max_departure_time - min_departure_time
57+
}
58+
return(time_range)
59+
}
60+
61+
check_stop_dists = function(stops, stop_dist_check) {
62+
.time_prev = Sys.time()
63+
stop_dists = stop_group_distances(stops, "stop_name", max_only = TRUE)
64+
.time_post = Sys.time()
65+
66+
if(max(stop_dists$dist_max) > stop_dist_check) {
67+
stop("Some stops with the same name are more than ", stop_dist_check, " meters apart, see stop_group_distances().\n",
68+
"Using travel_times() might lead to unexpected results. Set stop_dist_check=FALSE to ignore this error.",
69+
call. = FALSE)
70+
}
71+
72+
.time_check = as.numeric(difftime(.time_post, .time_prev, units = "secs"))
73+
if(.time_check > 1) {
74+
message("Stop distance check took longer than 1 second (", round(.time_check, 1), # nocov
75+
"s). Set stop_dist_check=FALSE to skip it.") # nocov
76+
}
77+
}

0 commit comments

Comments
 (0)