cta_redux 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +37 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +16 -0
- data/Rakefile +2 -0
- data/cta_redux.gemspec +32 -0
- data/data/.gitkeep +0 -0
- data/data/cta-gtfs.db.gz +0 -0
- data/lib/cta_redux/api/api_response.rb +45 -0
- data/lib/cta_redux/api/bus_tracker.rb +178 -0
- data/lib/cta_redux/api/customer_alerts.rb +68 -0
- data/lib/cta_redux/api/train_tracker.rb +89 -0
- data/lib/cta_redux/bus_tracker.rb +183 -0
- data/lib/cta_redux/customer_alerts.rb +72 -0
- data/lib/cta_redux/faraday_middleware/bus_tracker_parser.rb +46 -0
- data/lib/cta_redux/faraday_middleware/customer_alerts_parser.rb +39 -0
- data/lib/cta_redux/faraday_middleware/simple_cache.rb +32 -0
- data/lib/cta_redux/faraday_middleware/train_tracker_parser.rb +37 -0
- data/lib/cta_redux/models/agency.rb +4 -0
- data/lib/cta_redux/models/bus.rb +46 -0
- data/lib/cta_redux/models/calendar.rb +7 -0
- data/lib/cta_redux/models/route.rb +62 -0
- data/lib/cta_redux/models/shape.rb +4 -0
- data/lib/cta_redux/models/stop.rb +66 -0
- data/lib/cta_redux/models/stop_time.rb +6 -0
- data/lib/cta_redux/models/train.rb +74 -0
- data/lib/cta_redux/models/transfer.rb +6 -0
- data/lib/cta_redux/models/trip.rb +64 -0
- data/lib/cta_redux/train_tracker.rb +103 -0
- data/lib/cta_redux/version.rb +3 -0
- data/lib/cta_redux.rb +50 -0
- data/script/gtfs_to_sqlite.rb +137 -0
- data/spec/bus_tracker_spec.rb +149 -0
- data/spec/customer_alerts_spec.rb +48 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/stubs/alerts_response.xml +362 -0
- data/spec/stubs/getdirections_response.xml +16 -0
- data/spec/stubs/getpatterns_rt22_response.xml +2768 -0
- data/spec/stubs/getpredictions_rt22stpid15895_response.xml +59 -0
- data/spec/stubs/getpredictions_vid4361_response.xml +647 -0
- data/spec/stubs/getroutes_response.xml +774 -0
- data/spec/stubs/getservicebulletins_rt8_response.xml +110 -0
- data/spec/stubs/getstops_response.xml +614 -0
- data/spec/stubs/gettime_response.xml +2 -0
- data/spec/stubs/getvehicles_rt22_response.xml +239 -0
- data/spec/stubs/getvehicles_vid4394_response.xml +23 -0
- data/spec/stubs/route_status8_response.xml +1 -0
- data/spec/stubs/routes_response.xml +1 -0
- data/spec/stubs/ttarivals_stpid30141_response.xml +1 -0
- data/spec/stubs/ttfollow_run217_response.xml +1 -0
- data/spec/stubs/ttpositions_response.xml +1 -0
- data/spec/train_tracker_spec.rb +70 -0
- metadata +234 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
module CTA
|
2
|
+
class Trip < Sequel::Model
|
3
|
+
L_ROUTES = ["Brn", "G", "Pink", "P", "Org", "Red", "Blue", "Y"]
|
4
|
+
BUS_ROUTES = CTA::Trip.exclude(:route_id => L_ROUTES).select_map(:route_id).uniq
|
5
|
+
plugin :single_table_inheritance,
|
6
|
+
:route_id,
|
7
|
+
:model_map => proc { |v|
|
8
|
+
if L_ROUTES.include?(v)
|
9
|
+
'CTA::Train'
|
10
|
+
else
|
11
|
+
'CTA::Bus'
|
12
|
+
end
|
13
|
+
},
|
14
|
+
:key_map => proc { |klass|
|
15
|
+
if klass.name == 'CTA::Train'
|
16
|
+
L_ROUTES
|
17
|
+
else
|
18
|
+
BUS_ROUTES
|
19
|
+
end
|
20
|
+
}
|
21
|
+
|
22
|
+
set_primary_key :trip_id
|
23
|
+
|
24
|
+
many_to_one :calendar, :key => :service_id
|
25
|
+
one_to_many :stop_times, :key => :trip_id
|
26
|
+
|
27
|
+
many_to_one :route, :key => :route_id
|
28
|
+
|
29
|
+
many_to_many :stops, :left_key => :trip_id, :right_key => :stop_id, :join_table => :stop_times
|
30
|
+
|
31
|
+
# DRAGONS
|
32
|
+
# The CTA doesn't exactly honor the GTFS spec (nor do they return GTFS trip_ids
|
33
|
+
# in the API, grr). They specify multiple entries of # (schd_trip_id, block_id, service_id)
|
34
|
+
# so the only way to know which trip_id to pick is to join against stop_times and
|
35
|
+
# calendar dates, and # find out which run (according to stop_times) is happening *right now*.
|
36
|
+
# Of course, this will break if the train is delayed more the total time
|
37
|
+
# it takes to complete the run... so a delayed train will start to disappear
|
38
|
+
# as it progresses through the run. We allow for a 'fuzz factor' to account
|
39
|
+
# for this...
|
40
|
+
def self.find_active_run(run, timestamp, fuzz = false)
|
41
|
+
if self.to_s == "CTA::Train" # This is admittedly hacky.
|
42
|
+
join_str = "WHERE t.schd_trip_id = 'R#{run}'"
|
43
|
+
else
|
44
|
+
join_str = "WHERE t.route_id = '#{run}'"
|
45
|
+
end
|
46
|
+
d = timestamp.is_a?(DateTime) ? timestamp : DateTime.parse(timestamp)
|
47
|
+
wday = d.strftime("%A").downcase
|
48
|
+
end_ts = (fuzz ? (d.to_time + (60 * 60 * 6) - (90 * 60)) : d).strftime("%H:%M:%S")
|
49
|
+
Trip.with_sql(<<-SQL)
|
50
|
+
SELECT t.*
|
51
|
+
FROM trips t
|
52
|
+
JOIN stop_times st ON t.trip_id = st.trip_id
|
53
|
+
JOIN calendar c ON t.service_id = c.service_id
|
54
|
+
#{join_str}
|
55
|
+
AND c.start_date <= '#{d.to_s}'
|
56
|
+
AND c.end_date >= '#{d.to_s}'
|
57
|
+
AND c.#{wday}
|
58
|
+
GROUP BY t.trip_id, st.departure_time
|
59
|
+
HAVING MAX(st.departure_time) >= '#{end_ts}'
|
60
|
+
SQL
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module CTA
|
2
|
+
class TrainTracker
|
3
|
+
def self.connection
|
4
|
+
raise "You need to set a developer key first. Try CTA::TrainTracker.key = 'foo'." unless @key
|
5
|
+
|
6
|
+
@connection ||= Faraday.new do |faraday|
|
7
|
+
faraday.url_prefix = 'http://lapi.transitchicago.com/api/1.0/'
|
8
|
+
faraday.params = { :key => @key }
|
9
|
+
|
10
|
+
faraday.use CTA::TrainTracker::Parser, !!@debug
|
11
|
+
faraday.response :caching, SimpleCache.new(Hash.new)
|
12
|
+
faraday.adapter Faraday.default_adapter
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.arrivals!(options={})
|
17
|
+
allowed_keys = [:route, :parent_station, :station, :limit]
|
18
|
+
if options.keys.any? { |k| !allowed_keys.include?(k) }
|
19
|
+
raise "Illegal option!"
|
20
|
+
end
|
21
|
+
|
22
|
+
has_map = options.has_key?(:parent_station)
|
23
|
+
has_stop = options.has_key?(:station)
|
24
|
+
|
25
|
+
route = Array.wrap(options[:route]).flatten.compact.uniq
|
26
|
+
map = Array.wrap(options[:parent_station]).flatten.compact.uniq
|
27
|
+
stop = Array.wrap(options[:station]).flatten.compact.uniq
|
28
|
+
limit = Array.wrap(options[:limit]).flatten.compact.uniq.first.to_i if options[:limit]
|
29
|
+
|
30
|
+
if route.size > 1
|
31
|
+
raise "No more than 1 route may be specified!"
|
32
|
+
end
|
33
|
+
|
34
|
+
if map.size > 1 || stop.size > 1
|
35
|
+
raise "No more than 1 station or parent_station may be specified!"
|
36
|
+
end
|
37
|
+
|
38
|
+
if !(has_map || has_stop)
|
39
|
+
raise "You must specify a station or a parent_station! Try arrivals(:station => 30280..."
|
40
|
+
end
|
41
|
+
|
42
|
+
params = {}
|
43
|
+
params.merge!({ :mapid => map.first }) if options[:parent_station]
|
44
|
+
params.merge!({ :stpid => stop.first }) if options[:station]
|
45
|
+
params.merge!({ :max => limit }) if options[:limit]
|
46
|
+
params.merge!({ :rt => route.first }) if route.any?
|
47
|
+
|
48
|
+
connection.get('ttarrivals.aspx', params)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.predictions!(options={})
|
52
|
+
self.arrivals!(options)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.follow!(options={})
|
56
|
+
raise "Must specify a run! Try follow(:run => 914)..." unless options.has_key?(:run)
|
57
|
+
|
58
|
+
runs = Array.wrap(options[:run]).flatten.compact.uniq
|
59
|
+
|
60
|
+
if runs.size > 1
|
61
|
+
raise "Only one run may be specified!"
|
62
|
+
end
|
63
|
+
|
64
|
+
connection.get('ttfollow.aspx', { :runnumber => runs.first })
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.locations!(options={})
|
68
|
+
unless options.has_key?(:routes)
|
69
|
+
raise "Must specify at least one route! (Try locations(:routes => [:red, :blue]) )"
|
70
|
+
end
|
71
|
+
|
72
|
+
rt = Array.wrap(options[:routes]).flatten.compact.map { |r| (CTA::Train::FRIENDLY_L_ROUTES[r] || r).to_s }
|
73
|
+
|
74
|
+
if rt.size > 8
|
75
|
+
raise "No more than 8 routes may be specified!"
|
76
|
+
end
|
77
|
+
|
78
|
+
connection.get('ttpositions.aspx', { :rt => rt.join(',') })
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.positions!(options={})
|
82
|
+
self.locations!(options)
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.key
|
86
|
+
@key
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.key=(key)
|
90
|
+
@key = key
|
91
|
+
@connection = nil
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.debug
|
95
|
+
!!@debug
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.debug=(debug)
|
99
|
+
@debug = debug
|
100
|
+
@connection = nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/cta_redux.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require "cta_redux/version"
|
2
|
+
require "sequel"
|
3
|
+
require "sqlite3"
|
4
|
+
require "faraday"
|
5
|
+
require "faraday_middleware"
|
6
|
+
require "multi_xml"
|
7
|
+
require "zlib"
|
8
|
+
|
9
|
+
module CTA
|
10
|
+
base_path = File.expand_path("..", __FILE__)
|
11
|
+
data_dir = File.join(File.expand_path("../..", __FILE__), 'data')
|
12
|
+
Dir.glob("#{base_path}/cta_redux/faraday_middleware/*") { |lib| require lib }
|
13
|
+
Dir.glob("#{base_path}/cta_redux/api/*") { |lib| require lib }
|
14
|
+
require "#{base_path}/cta_redux/train_tracker.rb"
|
15
|
+
require "#{base_path}/cta_redux/bus_tracker.rb"
|
16
|
+
require "#{base_path}/cta_redux/customer_alerts.rb"
|
17
|
+
|
18
|
+
db_filename = File.join(data_dir, 'cta-gtfs.db')
|
19
|
+
|
20
|
+
# First run
|
21
|
+
if !File.exists?(db_filename)
|
22
|
+
dbf = File.open(db_filename, 'wb')
|
23
|
+
Zlib::GzipReader.open("#{db_filename}.gz") do |gz|
|
24
|
+
dbf.puts gz.read
|
25
|
+
end
|
26
|
+
dbf.close
|
27
|
+
end
|
28
|
+
|
29
|
+
DB = Sequel.sqlite(:database => db_filename, :readonly => true)
|
30
|
+
|
31
|
+
Dir.glob("#{base_path}/cta_redux/models/*") do |lib|
|
32
|
+
next if lib =~ /train/ || lib =~ /bus/
|
33
|
+
require lib
|
34
|
+
end
|
35
|
+
|
36
|
+
require "#{base_path}/cta_redux/models/train.rb"
|
37
|
+
require "#{base_path}/cta_redux/models/bus.rb"
|
38
|
+
end
|
39
|
+
|
40
|
+
class Array
|
41
|
+
def self.wrap(object)
|
42
|
+
if object.nil?
|
43
|
+
[]
|
44
|
+
elsif object.respond_to?(:to_ary)
|
45
|
+
object.to_ary || [object]
|
46
|
+
else
|
47
|
+
[object]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'sqlite3'
|
4
|
+
require 'csv'
|
5
|
+
require 'date'
|
6
|
+
|
7
|
+
KNOWN_COLUMN_TYPES = {
|
8
|
+
"calendar" => {
|
9
|
+
"service_id" => "INTEGER",
|
10
|
+
"monday" => "BOOLEAN",
|
11
|
+
"tuesday" => "BOOLEAN",
|
12
|
+
"wednesday" => "BOOLEAN",
|
13
|
+
"thursday" => "BOOLEAN",
|
14
|
+
"friday" => "BOOLEAN",
|
15
|
+
"saturday" => "BOOLEAN",
|
16
|
+
"sunday" => "BOOLEAN",
|
17
|
+
"start_date" => "DATE",
|
18
|
+
"end_date" => "DATE"
|
19
|
+
},
|
20
|
+
"routes" => {
|
21
|
+
"route_type" => "INTEGER"
|
22
|
+
},
|
23
|
+
"shapes" => {
|
24
|
+
"shape_id" => "INTEGER",
|
25
|
+
"shape_pt_lat" => "FLOAT",
|
26
|
+
"shape_pt_lon" => "FLOAT",
|
27
|
+
"shape_pt_sequence" => "INTEGER",
|
28
|
+
"shape_dist_traveled" => "INTEGER"
|
29
|
+
},
|
30
|
+
"stop_times" => {
|
31
|
+
"trip_id" => "INTEGER",
|
32
|
+
# This would be nice to store as a native SQL type,
|
33
|
+
# but ruby really has no notion of a time object w/o
|
34
|
+
# an associated date, because ruby stores time as epoch.
|
35
|
+
# so, in order to prevent ruby from attaching dates to our
|
36
|
+
# stop_times, we just let it store naturally as a string.
|
37
|
+
#"arrival_time" => "TIME",
|
38
|
+
#"departure_time" => "TIME",
|
39
|
+
"stop_id" => "INTEGER",
|
40
|
+
"stop_sequence" => "INTEGER",
|
41
|
+
"shape_dist_traveled" => "INTEGER",
|
42
|
+
"pickup_type" => "INTEGER"
|
43
|
+
},
|
44
|
+
"stops" => {
|
45
|
+
"stop_id" => "INTEGER",
|
46
|
+
"stop_code" => "INTEGER",
|
47
|
+
"stop_lat" => "FLOAT",
|
48
|
+
"stop_lon" => "FLOAT",
|
49
|
+
"location_type" => "INTEGER",
|
50
|
+
"parent_station" => "INTEGER",
|
51
|
+
"wheelchair_boarding" => "BOOLEAN"
|
52
|
+
},
|
53
|
+
"transfers" => {
|
54
|
+
"from_stop_id" => "INTEGER",
|
55
|
+
"to_stop_id" => "INTEGER",
|
56
|
+
"transfer_type" => "INTEGER"
|
57
|
+
},
|
58
|
+
"trips" => {
|
59
|
+
"service_id" => "INTEGER",
|
60
|
+
"trip_id" => "INTEGER",
|
61
|
+
"direction_id" => "INTEGER",
|
62
|
+
"block_id" => "INTEGER",
|
63
|
+
"shape_id" => "INTEGER",
|
64
|
+
"wheelchair_accessible" => "BOOLEAN"
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
def usage
|
69
|
+
"Usage: gtfs_to_sqlite.rb input_file output_db"
|
70
|
+
end
|
71
|
+
|
72
|
+
abort usage unless ARGV.size == 2
|
73
|
+
|
74
|
+
base_path = File.expand_path("..", __FILE__)
|
75
|
+
input_file = File.join(base_path, ARGV[0])
|
76
|
+
output_db = File.join(base_path, ARGV[1])
|
77
|
+
|
78
|
+
table = input_file.split(File::SEPARATOR).last.gsub(".txt", "")
|
79
|
+
|
80
|
+
puts input_file
|
81
|
+
abort usage unless File.exists?(input_file)
|
82
|
+
|
83
|
+
db = SQLite3::Database.new(output_db)
|
84
|
+
|
85
|
+
first_row = true
|
86
|
+
n = 0
|
87
|
+
headers = nil
|
88
|
+
|
89
|
+
print "Loading"
|
90
|
+
CSV.foreach(input_file, :headers => true) do |row|
|
91
|
+
if n % 100 == 0
|
92
|
+
print "."
|
93
|
+
end
|
94
|
+
if n % 1000 == 0
|
95
|
+
print n
|
96
|
+
end
|
97
|
+
|
98
|
+
if first_row
|
99
|
+
columns = row.headers.map do |r|
|
100
|
+
if KNOWN_COLUMN_TYPES[table] && KNOWN_COLUMN_TYPES[table][r]
|
101
|
+
col = "#{r} #{KNOWN_COLUMN_TYPES[table][r]}"
|
102
|
+
else
|
103
|
+
col = r
|
104
|
+
end
|
105
|
+
end
|
106
|
+
stmt = "CREATE TABLE #{table} (#{columns.join(',')})"
|
107
|
+
db.execute(stmt)
|
108
|
+
first_row = false
|
109
|
+
headers = row.headers
|
110
|
+
end
|
111
|
+
|
112
|
+
stmt = "INSERT INTO #{table} (#{row.headers.join(',')}) VALUES (#{row.headers.map { '?' }.join(',')})"
|
113
|
+
if row.headers.any? { |h| h =~ /date/ }
|
114
|
+
fields = []
|
115
|
+
row.headers.each_with_index do |header, index|
|
116
|
+
if header =~ /date/
|
117
|
+
fields << DateTime.parse(row.fields[index]).to_s
|
118
|
+
else
|
119
|
+
fields << row.fields[index]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
else
|
123
|
+
fields = row.fields
|
124
|
+
end
|
125
|
+
db.execute(stmt, fields)
|
126
|
+
n +=1
|
127
|
+
end
|
128
|
+
|
129
|
+
puts "\ndone"
|
130
|
+
puts "Creating indices..."
|
131
|
+
if headers
|
132
|
+
headers.each do |h|
|
133
|
+
if h =~ /_id/
|
134
|
+
db.execute("CREATE INDEX #{table}_#{h}_index ON #{table} (#{h})")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
base_path = File.expand_path(File.dirname(__FILE__))
|
2
|
+
data_path = File.expand_path(File.join(base_path, 'stubs'))
|
3
|
+
|
4
|
+
require File.join(base_path, "..", "lib", "cta_redux")
|
5
|
+
|
6
|
+
RSpec.describe CTA::BusTracker do
|
7
|
+
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
|
8
|
+
stub.get('/bustime/api/v2/gettime?key=') { |env| [200, {}, File.read(File.join(data_path, 'gettime_response.xml'))] }
|
9
|
+
stub.get('/bustime/api/v2/getvehicles?key=&rt=22') { |env| [200, {}, File.read(File.join(data_path, 'getvehicles_rt22_response.xml'))] }
|
10
|
+
stub.get('/bustime/api/v2/getvehicles?key=&vid=4394') { |env| [200, {}, File.read(File.join(data_path, 'getvehicles_vid4394_response.xml'))] }
|
11
|
+
stub.get('/bustime/api/v2/getroutes?key=') { |env| [200, {}, File.read(File.join(data_path, 'getroutes_response.xml'))] }
|
12
|
+
stub.get('/bustime/api/v2/getdirections?key=&rt=22') { |env| [200, {}, File.read(File.join(data_path, 'getdirections_response.xml'))] }
|
13
|
+
stub.get('/bustime/api/v2/getstops?key=&rt=22&dir=Northbound') { |env| [200, {}, File.read(File.join(data_path, 'getstops_response.xml'))] }
|
14
|
+
stub.get('/bustime/api/v2/getpatterns?key=&rt=22') { |env| [200, {}, File.read(File.join(data_path, 'getpatterns_rt22_response.xml'))] }
|
15
|
+
stub.get('/bustime/api/v2/getpredictions?key=&rt=22&stpid=15895&top=&vid=') { |env| [200, {}, File.read(File.join(data_path, 'getpredictions_rt22stpid15895_response.xml'))] }
|
16
|
+
stub.get('/bustime/api/v2/getpredictions?key=&vid=4361') { |env| [200, {}, File.read(File.join(data_path, 'getpredictions_vid4361_response.xml'))] }
|
17
|
+
stub.get('/bustime/api/v2/getservicebulletins?key=&rt=8') { |env| [200, {}, File.read(File.join(data_path, 'getservicebulletins_rt8_response.xml'))] }
|
18
|
+
end
|
19
|
+
|
20
|
+
CTA::BusTracker.key = ''
|
21
|
+
CTA::BusTracker.connection.instance_variable_get(:@builder).delete(Faraday::Adapter::NetHttp)
|
22
|
+
CTA::BusTracker.connection.adapter :test, stubs
|
23
|
+
|
24
|
+
describe "time!" do
|
25
|
+
result = CTA::BusTracker.time!
|
26
|
+
|
27
|
+
it "returns an error code" do
|
28
|
+
expect(result.error).to be_instance_of(CTA::API::Error)
|
29
|
+
expect(result.error.code).to eq(0)
|
30
|
+
expect(result.error.message).to eq("OK")
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns a time" do
|
34
|
+
expect(result.timestamp).to be_instance_of(DateTime)
|
35
|
+
expect(result.timestamp.to_s).to eq("2015-02-14T11:31:13+00:00")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "vehicles!" do
|
40
|
+
it "returns vehicles for route 22" do
|
41
|
+
result = CTA::BusTracker.vehicles!(:routes => 22)
|
42
|
+
|
43
|
+
expect(result).to be_instance_of(CTA::BusTracker::VehiclesResponse)
|
44
|
+
expect(result.vehicles.size).to eq(13)
|
45
|
+
|
46
|
+
expect(result.vehicles.first.route).to be_instance_of(CTA::Route)
|
47
|
+
expect(result.vehicles.first.route.route_id).to eq("22")
|
48
|
+
expect(result.vehicles.first.vehicle_id).to eq(4394)
|
49
|
+
expect(result.vehicles.first.pattern_distance).to eq(115)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "returns information about one vehicle" do
|
53
|
+
result = CTA::BusTracker.vehicles!(:vehicles => 4394)
|
54
|
+
|
55
|
+
expect(result).to be_instance_of(CTA::BusTracker::VehiclesResponse)
|
56
|
+
expect(result.vehicles.size).to eq(1)
|
57
|
+
expect(result.vehicles.first.route).to be_instance_of(CTA::Route)
|
58
|
+
expect(result.vehicles.first.route.route_id).to eq("22")
|
59
|
+
expect(result.vehicles.first.vehicle_id).to eq(4394)
|
60
|
+
expect(result.vehicles.first.heading).to eq(359)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "routes!" do
|
65
|
+
it "returns information about all routes" do
|
66
|
+
result = CTA::BusTracker.routes!
|
67
|
+
|
68
|
+
expect(result).to be_instance_of(CTA::BusTracker::RoutesResponse)
|
69
|
+
expect(result.routes.size).to eq(127)
|
70
|
+
expect(result.routes.first.route_id).to eq("1")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "directions!" do
|
75
|
+
it "returns two directions for route 22" do
|
76
|
+
result = CTA::BusTracker.directions!(:route => 22)
|
77
|
+
|
78
|
+
expect(result).to be_instance_of(CTA::BusTracker::DirectionsResponse)
|
79
|
+
expect(result.directions.size).to eq(2)
|
80
|
+
expect(result.directions.first.direction).to eq("Northbound")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "stops!" do
|
85
|
+
it "returns information about Northbound route 22 stops" do
|
86
|
+
result = CTA::BusTracker.stops!(:route => 22, :direction => :northbound)
|
87
|
+
|
88
|
+
expect(result).to be_instance_of(CTA::BusTracker::StopsResponse)
|
89
|
+
expect(result.stops.size).to eq(86)
|
90
|
+
expect(result.stops.first).to be_instance_of(CTA::Stop)
|
91
|
+
expect(result.stops.first.stop_name).to eq("Clark & Addison")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "patterns!" do
|
96
|
+
it "returns information about patterns for route 22" do
|
97
|
+
result = CTA::BusTracker.patterns!(:route => 22)
|
98
|
+
|
99
|
+
expect(result).to be_instance_of(CTA::BusTracker::PatternsResponse)
|
100
|
+
expect(result.patterns.first.direction).to be_instance_of(CTA::BusTracker::Direction)
|
101
|
+
expect(result.patterns.first.id).to eq(3936)
|
102
|
+
expect(result.patterns.first.points.size).to eq(124)
|
103
|
+
expect(result.patterns.first.points.first.sequence).to eq(1)
|
104
|
+
expect(result.patterns.first.points.first.stop).to be_instance_of(CTA::Stop)
|
105
|
+
expect(result.patterns.first.points.first.stop.stop_id).to eq(14096)
|
106
|
+
expect(result.patterns.first.points[1].lat).to eq(42.019043088282)
|
107
|
+
expect(result.patterns.first.points[1].type).to eq(:waypoint)
|
108
|
+
expect(result.patterns.first.points[1].sequence).to eq(2)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "predictions!" do
|
113
|
+
it "returns predictions for route 22 stop 15898" do
|
114
|
+
result = CTA::BusTracker.predictions!(:routes => 22, :stops => 15895)
|
115
|
+
|
116
|
+
expect(result).to be_instance_of(CTA::BusTracker::PredictionsResponse)
|
117
|
+
expect(result.vehicles.size).to eq(2)
|
118
|
+
expect(result.vehicles.first.route.route_id).to eq("22")
|
119
|
+
expect(result.predictions.size).to eq(2)
|
120
|
+
expect(result.predictions.first.type).to eq("D")
|
121
|
+
expect(result.predictions.first.minutes).to eq(5)
|
122
|
+
expect(result.predictions.first.arrival_time.to_s).to eq("2015-02-14T12:25:00+00:00")
|
123
|
+
expect(result.predictions.first.delayed).to eq(false)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "returns predictions for vehicle 4361" do
|
127
|
+
result = CTA::BusTracker.predictions!(:vehicles => 4361)
|
128
|
+
|
129
|
+
expect(result).to be_instance_of(CTA::BusTracker::PredictionsResponse)
|
130
|
+
expect(result.vehicles.size).to eq(30)
|
131
|
+
expect(result.vehicles.first.route.route_id).to eq("22")
|
132
|
+
expect(result.predictions.size).to eq(30)
|
133
|
+
expect(result.predictions.first.type).to eq("A")
|
134
|
+
expect(result.predictions.first.minutes).to eq(2)
|
135
|
+
expect(result.predictions.first.arrival_time.to_s).to eq("2015-02-14T12:38:00+00:00")
|
136
|
+
expect(result.predictions.first.delayed).to eq(false)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
it "returns bulletins for route 8" do
|
141
|
+
result = CTA::BusTracker.bulletins!(:routes => 8)
|
142
|
+
|
143
|
+
expect(result).to be_instance_of(CTA::BusTracker::ServiceBulletinsResponse)
|
144
|
+
expect(result.bulletins.size).to eq(6)
|
145
|
+
expect(result.bulletins.first.subject).to eq("#8 Halsted reroute Halsted/35th")
|
146
|
+
expect(result.bulletins.first.affected_services.size).to eq(1)
|
147
|
+
expect(result.bulletins.first.affected_services.first.route.route_id).to eq("8")
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
base_path = File.expand_path(File.dirname(__FILE__))
|
2
|
+
data_path = File.expand_path(File.join(base_path, 'stubs'))
|
3
|
+
|
4
|
+
require File.join(base_path, "..", "lib", "cta_redux")
|
5
|
+
|
6
|
+
RSpec.describe CTA::TrainTracker do
|
7
|
+
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
|
8
|
+
stub.get('/api/1.0/routes.aspx?type=&routeid=&stationid=') { |env| [200, {}, File.read(File.join(data_path, 'routes_response.xml'))] }
|
9
|
+
stub.get('/api/1.0/routes.aspx?type=&routeid=8&stationid=') { |env| [200, {}, File.read(File.join(data_path, 'route_status8_response.xml'))] }
|
10
|
+
stub.get('/api/1.0/alerts.aspx') { |env| [200, {}, File.read(File.join(data_path, 'alerts_response.xml'))] }
|
11
|
+
end
|
12
|
+
|
13
|
+
CTA::CustomerAlerts.connection.instance_variable_get(:@builder).delete(Faraday::Adapter::NetHttp)
|
14
|
+
CTA::CustomerAlerts.connection.adapter :test, stubs
|
15
|
+
|
16
|
+
describe "status!" do
|
17
|
+
it "returns all status information" do
|
18
|
+
response = CTA::CustomerAlerts.status!
|
19
|
+
|
20
|
+
expect(response).to be_instance_of(CTA::CustomerAlerts::RouteStatusResponse)
|
21
|
+
expect(response.routes.size).to eq(140)
|
22
|
+
expect(response.routes.first.route.route_id).to eq("Red")
|
23
|
+
expect(response.routes.first.status).to eq("Planned Work w/Reroute")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "returns status information for one route" do
|
27
|
+
response = CTA::CustomerAlerts.status!(:routes => 8)
|
28
|
+
|
29
|
+
expect(response).to be_instance_of(CTA::CustomerAlerts::RouteStatusResponse)
|
30
|
+
expect(response.routes.size).to eq(1)
|
31
|
+
expect(response.routes.first.route.route_id).to eq("8")
|
32
|
+
expect(response.routes.first.status).to eq("Bus Stop Relocation")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "alerts!" do
|
37
|
+
it "returns all alerts" do
|
38
|
+
response = CTA::CustomerAlerts.alerts!
|
39
|
+
|
40
|
+
expect(response).to be_instance_of(CTA::CustomerAlerts::AlertsResponse)
|
41
|
+
expect(response.alerts.size).to eq(44)
|
42
|
+
expect(response.alerts.first.alert_id).to eq(23322)
|
43
|
+
expect(response.alerts.first.category).to eq(:normal)
|
44
|
+
expect(response.alerts.first.major_alert).to eq(false)
|
45
|
+
expect(response.alerts.first.tbd).to eq(true)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
RSpec.configure do |config|
|
2
|
+
config.expect_with :rspec do |expectations|
|
3
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
4
|
+
end
|
5
|
+
|
6
|
+
config.mock_with :rspec do |mocks|
|
7
|
+
mocks.verify_partial_doubles = true
|
8
|
+
end
|
9
|
+
|
10
|
+
if config.files_to_run.one?
|
11
|
+
config.default_formatter = 'doc'
|
12
|
+
end
|
13
|
+
|
14
|
+
config.profile_examples = 10
|
15
|
+
config.order = :random
|
16
|
+
end
|