1 # frozen_string_literal: true
11 MULTI_PLATFORM_STOPS = {
12 "BARRHAVEN CENTRE" => ["1A", "2A"],
13 "BASELINE" => ["1A", "1B", "1C", "1D", "2A", "2B", "STN OFF ONLY"],
14 "BAYSHORE" => ["1A", "2A", "3A", "4A", "4B", "A", "B", "C"],
15 "BAYVIEW" => ["A", "B", "O-TRAIN EAST / EST", "O-TRAIN WEST / OUEST"],
16 "BEATRICE" => ["1A", "2A"],
17 "BILLINGS BRIDGE" => ["1A", "2A", "3A", "3B", "3C", "4B", "4C", "4D"],
18 "BLAIR" => ["A", "B", "C", "D", "E", "H", "I", "O-TRAIN", "O-TRAIN WEST / OUEST"],
19 "CHAPEL HILL" => ["A", "B"],
20 "CYRVILLE" => ["A", "B", "O-TRAIN EAST / EST", "O-TRAIN WEST / OUEST"],
21 "DOMINION" => ["1A", "2A", "STN OFF ONLY"],
22 "FALLOWFIELD" => ["1A", "2A"],
23 "GREENBORO" => ["1A", "1B", "2A"],
24 "HERON" => ["1A", "2A", "3A", "4A"],
25 "HURDMAN" => ["A", "B", "C", "D", "E", "O-TRAIN EAST / EST", "O-TRAIN WEST / OUEST"],
26 "INNOVATION" => ["A", "B"],
27 "IRIS" => ["1A", "2A", "STN OFF ONLY"],
28 "JEANNE D'ARC" => ["1A", "1B", "2A", "3A", "3B", "4A", "4B"],
29 "LEES" => ["A", "B", "O-TRAIN EAST / EST", "O-TRAIN WEST / OUEST"],
30 "LEITRIM" => ["1A", "2A"],
31 "LINCOLN FIELDS" => ["1A", "1B", "2A", "3A", "4A", "4B", "5A", "OFF ONLY"],
32 "LONGFIELDS" => ["1A", "2A"],
33 "LYCÉE CLAUDEL" => ["1A", "2A"],
34 "LYON" => ["A", "B", "C", "O-TRAIN EAST / EST", "O-TRAIN WEST / OUEST"],
35 "MACKENZIE KING" => ["1A", "2A"],
36 "MARKETPLACE" => ["1A", "2A"],
37 "MILLENNIUM" => ["1A", "2A"],
38 "NEPEAN WOODS" => ["1A", "2A"],
39 "PARLIAMENT / PARLEMENT" => ["A", "B", "C", "O-TRAIN EAST / EST", "O-TRAIN WEST / OUEST"],
40 "PIMISI" => ["A", "B", "C", "D", "O-TRAIN EAST / EST", "O-TRAIN WEST / OUEST"],
41 "PINECREST" => ["1A", "2A", "A", "B", "C"],
42 "PLEASANT PARK" => ["1A", "2A", "3A", "4A"],
43 "QUEENSWAY" => ["1A", "2A", "3A", "4A"],
44 "RIDEAU" => ["A", "B", "C", "O-TRAIN EAST / EST", "O-TRAIN WEST / OUEST"],
45 "RIVERSIDE" => ["1A", "2A"],
46 "RIVERVIEW" => ["1A", "2A"],
47 "SMYTH" => ["1A", "2A"],
48 "SOUTH KEYS" => ["1A", "1B", "1C", "1D", "2A"],
49 "STRANDHERD" => ["1A", "2A"],
50 "ST-LAURENT" => ["A", "B", "C", "D", "O-TRAIN EAST / EST", "O-TRAIN WEST / OUEST"],
51 "TERON" => ["1A", "2A"],
52 "TREMBLAY" => ["O-TRAIN EAST / EST", "O-TRAIN WEST / OUEST"],
53 "TRIM" => ["1A", "2A"],
54 "TUNNEY'S PASTURE" => ["A", "B", "C", "D", "E", "F", "G", "O-TRAIN", "O-TRAIN EAST / EST"],
55 "WALKLEY" => ["1A", "2A", "3A", "4A", "4B", "4C"],
56 "WESTBORO" => ["1A", "1B", "2A", "3A", "3A (A)", "4A", "A", "B", "OFF ONLY", "STN OFF ONLY"],
61 @date_utils = DateUtils.new
65 def active_schedules(date_hash: nil)
66 target_date = date_hash || @date_utils.today
67 base_schedules = calendar_schedules(target_date)
68 overrides = calendar_overrides(target_date)
70 base_schedules + overrides[:add] - overrides[:remove]
73 def trips(stop_code: nil, stop_id: nil, stop_name: nil, date_hash: nil, originating_only: false)
74 args = [stop_code, stop_id, stop_name].compact
75 raise "Must specify exactly one of stop_code, stop_id, or stop_name" if args.count != 1
78 trips_for_stop_code(stop_code: stop_code, date_hash: date_hash, originating_only: originating_only)
80 trips_for_stop_ids(stop_ids: [stop_id], date_hash: date_hash, originating_only: originating_only)
82 trips_for_stop_name(stop_name: stop_name, date_hash: date_hash, originating_only: originating_only)
88 def trips_for_stop_code(stop_code:, date_hash:, originating_only: false)
89 stop_ids = @db.query("SELECT stop_id FROM stops WHERE stop_code=?", stop_code).to_a.map do |row|
93 trips_for_stop_ids(stop_ids: stop_ids, date_hash: date_hash, originating_only: originating_only)
96 def trips_for_stop_ids(stop_ids:, date_hash:, originating_only: false)
99 schedules = active_schedules(date_hash: date_hash)
100 schedule_placeholders = (["?"] * schedules.count).join(",")
101 stop_id_placeholders = (["?"] * stop_ids.count).join(",")
104 SELECT r.route_short_name,t.trip_headsign,t.direction_id,st.departure_time,t.trip_id
106 INNER JOIN trips t ON t.route_id=r.route_id
107 INNER JOIN stop_times st ON st.trip_id=t.trip_id
108 WHERE st.stop_id IN (#{stop_id_placeholders})
109 AND service_id IN (#{schedule_placeholders})
112 sql_filter = if originating_only
113 "AND st.stop_sequence=1\n"
119 ORDER BY r.route_short_name,st.departure_time;
122 sql = "#{sql_head}#{sql_filter}#{sql_tail}"
124 @db.query(sql, stop_ids, schedules).each do |row|
125 route = row[0..2] # [route_id, hedsign, direction_id]
126 departure_time = row[3]
129 if result[route].nil?
133 result[route] << [departure_time, trip_id]
136 # Sometimes OC Transpo lists the same departure under two separate schedules, *both* of which are
137 # simulteneously in effect. (?)
138 # Here we compensate for that by eliminating duplicates of the same departure time.
139 result.map do |route, departure_tuples|
142 departure_tuples.group_by do |tuple|
143 tuple.first # departure time
144 end.map do |group_key, group_members|
151 def trips_for_stop_name(stop_name:, date_hash:, originating_only: false)
154 base_name = stop_name.upcase
155 suffixes = MULTI_PLATFORM_STOPS[base_name] || [nil]
156 names = suffixes.map { |suffix| [base_name, suffix].compact.join(" ") }
157 placeholders = (["?"] * names.count).join(",")
159 stop_ids = @db.query(
160 "SELECT stop_id FROM stops WHERE stop_name IN (#{placeholders})",
166 trips_for_stop_ids(stop_ids: stop_ids, date_hash: date_hash, originating_only: originating_only)
172 SELECT service_id, monday, tuesday, wednesday, thursday, friday, saturday, sunday, start_date, end_date
174 ORDER BY start_date, end_date
180 def calendar_overrides(target_date)
181 result = {add: [], remove: []}
182 datestamp = @date_utils.date_for(target_date)
184 SELECT service_id, exception_type
186 WHERE calendar_date = ?
189 @db.query(sql).each do |row|
192 result[:add] << row[0]
194 result[:remove] << row[0]
196 raise "Unexpected exception_type #{row[1].inspect} found in calendar_dates"
203 def calendar_schedules(target_date)
205 datestamp = @date_utils.date_for(target_date)
206 weekday = @date_utils.weekday_for(target_date)
207 calendar.each do |service_id, mon, tue, wed, thu, fri, sat, sun, start_date, end_date|
208 if datestamp >= start_date && datestamp <= end_date
209 if (0 == weekday && "1" == sun) ||
210 (1 == weekday && "1" == mon) ||
211 (2 == weekday && "1" == tue) ||
212 (3 == weekday && "1" == wed) ||
213 (4 == weekday && "1" == thu) ||
214 (5 == weekday && "1" == fri) ||
215 (6 == weekday && "1" == sat)