cta_redux 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,39 @@
1
1
  module CTA
2
+ # A {http://sequel.jeremyevans.net/rdoc/classes/Sequel/Model.html Sequel::Model}.
3
+ # This corresponds to {https://developers.google.com/transit/gtfs/reference?csw=1#stop_times_txt___Field_Definitions stop_times.txt} in the
4
+ # GTFS feed, though the CTA does not fully implement the standard.
5
+ # This object can give you the scheduled times that a vehicle should stop along a {CTA::Route} (or really, a {CTA::Trip} if you're being specific).
6
+ # It can also function as somewhat of a 'join table' between trips and stops.
7
+ # @note Current columns: [:trip_id, :arrival_time, :departure_time, :stop_id, :stop_sequence, :stop_headsign, :pickup_type, :shape_dist_traveled]
2
8
  class StopTime < Sequel::Model
9
+ # @!method trip
10
+ # @return [CTA::Trip] The {CTA::Trip} associated with this {CTA::StopTime}
3
11
  many_to_one :trip, :key => :trip_id
12
+ # @!method stop
13
+ # @return [CTA::Stop] The {CTA::Stop} associated with this {CTA::StopTime}
4
14
  many_to_one :stop, :key => :stop_id
15
+
16
+ # @!method trip_id
17
+ # @return [Integer]
18
+ # @!method arrival_time
19
+ # @return [String]
20
+ # @note This is a string because ruby has no concept of storing a time without a date
21
+ # @!method departure_time
22
+ # @return [String]
23
+ # @note This is a string because ruby has no concept of storing a time without a date
24
+ # @!method stop_id
25
+ # @return [Integer]
26
+ # @!method stop_sequence
27
+ # @return [Integer]
28
+ # @!method stop_headsign
29
+ # @return [String]
30
+ # @!method pickup_type
31
+ # @return [Integer]
32
+ # @!method shape_dist_traveled
33
+ # @return [Integer]
34
+ alias_method :sequence, :stop_sequence
35
+ alias_method :headsign, :stop_headsign
36
+ alias_method :distance, :shape_dist_traveled
37
+ alias_method :distance_traveled, :shape_dist_traveled
5
38
  end
6
39
  end
@@ -1,5 +1,13 @@
1
1
  module CTA
2
+ # A {http://sequel.jeremyevans.net/rdoc/classes/Sequel/Model.html Sequel::Model}, inherited from {CTA::Trip}
3
+ # This corresponds to {https://developers.google.com/transit/gtfs/reference?csw=1#trips_txt___Field_Definitions trips.txt} in the
4
+ # GTFS feed, though the CTA does not fully implement the standard.
5
+ # @note Current columns: [:route_id, :service_id, :trip_id, :direction_id, :block_id, :shape_id, :direction, :wheelchair_accessible, :schd_trip_id]
2
6
  class Train < CTA::Trip
7
+ # @return [Live] Returns the {Live} data associated with this {CTA::Train} object, if available.
8
+ # @note a {CTA::Train} will only contain live data when augmented with an {API::Response}
9
+ attr_accessor :live
10
+
3
11
  L_ROUTES = {
4
12
  "red" => { :name => "Red",
5
13
  :directions => { "1" => "Howard-bound", "5" => "95th/Dan Ryan-bound" }
@@ -28,27 +36,86 @@ module CTA
28
36
  }
29
37
  FRIENDLY_L_ROUTES = Hash[L_ROUTES.values.map { |r| r[:name].downcase.to_sym }.zip(L_ROUTES.keys)]
30
38
 
39
+ # @!method route_id
40
+ # @return [String]
41
+ # @!method service_id
42
+ # @return [Integer]
43
+ # @!method trip_id
44
+ # @return [Integer]
45
+ # @!method direction_id
46
+ # @return [Integer]
47
+ # @!method block_id
48
+ # @return [Integer]
49
+ # @!method shape_id
50
+ # @return [Integer]
51
+ # @!method direction
52
+ # @return [String]
53
+ # @!method wheelchair_accessible
54
+ # @return [true,false]
55
+ # @!method schd_trip_id
56
+ # @return [String]
57
+ alias_method :id, :route_id
58
+ alias_method :scheduled_trip_id, :schd_trip_id
59
+ alias_method :run, :schd_trip_id
60
+
61
+ # Follows a train, using the TrainTracker follow API
62
+ # @return [CTA::TrainTracker::FollowResponse] A {CTA::TrainTracker::FollowResponse} with predictions for this train
31
63
  def follow!
32
64
  CTA::TrainTracker.follow!(:run => self.schd_trip_id.gsub("R", ""))
33
65
  end
34
66
 
35
- def live!(position, predictions)
36
- class << self
37
- attr_reader :lat, :lon, :heading, :predictions
38
- end
67
+ class Live
68
+ # @return [Float] The current latitude of the train
69
+ attr_reader :lat
70
+ # @return [Float] The current longitude of the train
71
+ attr_reader :lon
72
+ # @return [Integer] The current heading of the train
73
+ attr_reader :heading
74
+ # @return [Array<Prediction>] An array of {Prediction} objects that correspond to predictions returned from the TrainTracker API
75
+ attr_reader :predictions
39
76
 
40
- @lat = position["lat"].to_f
41
- @lon = position["lon"].to_f
42
- @heading = position["heading"].to_i
43
-
44
- @predictions = Array.wrap(predictions).map { |p| Prediction.new(p) }
77
+ def initialize(position, predictions)
78
+ @lat = position["lat"].to_f
79
+ @lon = position["lon"].to_f
80
+ @heading = position["heading"].to_i
81
+ @predictions = Array.wrap(predictions).map { |p| Prediction.new(p) }
82
+ end
45
83
  end
46
84
 
47
85
  class Prediction
48
- attr_reader :run, :trip, :destination, :direction, :next_station,
49
- :prediction_generated_at, :arrival_time, :minutes, :seconds,
50
- :approaching, :scheduled, :delayed, :flags, :route, :lat, :lon,
51
- :heading, :route, :direction
86
+ # @return [String] The run identifier for this train.
87
+ # @note This is returned as a string, because the API will return results like "004" and the leading zeroes are important.
88
+ attr_reader :run
89
+ # @return [CTA::Trip] The {CTA::Trip} associated with this train
90
+ attr_reader :trip
91
+ # @return [CTA::Stop] The final {CTA::Stop} of this train
92
+ attr_reader :destination
93
+ # @return [String] A human-readable direction of this train, eg "O'Hare-bound"
94
+ attr_reader :direction
95
+ # @return [CTA::Stop] The next {CTA::Stop} of this train
96
+ attr_reader :next_station
97
+ # @return [DateTime] The time this {Prediction} was generated on the TrainTracker servers
98
+ attr_reader :prediction_generated_at
99
+ # @return [DateTime] The time this train is predicted to arrive at the next_station
100
+ attr_reader :arrival_time
101
+ # @return [Integer] The number of minutes until this train arrives at the next_station
102
+ attr_reader :minutes
103
+ # @return [Integer] The number of seconds until this train arrives at the next_station
104
+ attr_reader :seconds
105
+ # @return [true,false] True if this train is considered to be 'approaching' the next_station by the CTA
106
+ attr_reader :approaching
107
+ # @return [true,false] True if this train has not yet left it's origin station and started it's run
108
+ attr_reader :scheduled
109
+ # @return [true,false] True if this train is considered to be 'delayed' by the CTA
110
+ # @note The CTA considers a train 'delayed' if it's not progressing along the tracks. This is *not* an indication
111
+ # that a predicted arrival time will be later than a scheduled arrival time (which is how most people would consider a train
112
+ # to be 'delayed'). The CTA recommends that you indicate a train is 'delayed' rather than continuing to display the last predicted
113
+ # arrival time, which may no longer be accurate.
114
+ attr_reader :delayed
115
+ # @return [String] Flags for this train. Unused at this time.
116
+ attr_reader :flags
117
+ # @return [CTA::Route] The {CTA::Route} this train is running.
118
+ attr_reader :route
52
119
 
53
120
  def initialize(data)
54
121
  @run = data["rn"]
@@ -63,9 +130,6 @@ module CTA
63
130
  @delayed = (data["isDly"] == "1")
64
131
  @scheduled = (data["isSch"] == "1")
65
132
  @flags = data["flags"]
66
- @lat = data["lat"].to_f
67
- @lon = data["lon"].to_f
68
- @heading = data["heading"].to_i
69
133
  @route = @trip.route
70
134
  @direction = L_ROUTES[@route.route_id.downcase][:directions][data["trDr"]]
71
135
  end
@@ -1,6 +1,23 @@
1
1
  module CTA
2
+ # A {http://sequel.jeremyevans.net/rdoc/classes/Sequel/Model.html Sequel::Model}.
3
+ # This corresponds to {https://developers.google.com/transit/gtfs/reference?csw=1#transfers_txt___Field_Definitions transfers.txt} in the
4
+ # GTFS feed, though the CTA does not fully implement the standard.
5
+ # @note Current columns: [:from_stop_id, :to_stop_id, :transfer_type]
2
6
  class Transfer < Sequel::Model
7
+ # @!method from_stop
8
+ # The stop to transfer *from*
9
+ # @return [CTA::Stop]
3
10
  many_to_one :from_stop, :class => 'CTA::Stop', :key => :from_stop_id
11
+ # @!method to_stop
12
+ # The stop to transfer *to*
13
+ # @return [CTA::Stop]
4
14
  many_to_one :to_stop, :class => 'CTA::Stop', :key => :to_stop_id
15
+
16
+ # @!method from_stop_id
17
+ # @return [Integer]
18
+ # @!method to_stop_id
19
+ # @return [Integer]
20
+ # @!method transfer_type
21
+ # @return [Integer]
5
22
  end
6
23
  end
@@ -1,4 +1,8 @@
1
1
  module CTA
2
+ # A {http://sequel.jeremyevans.net/rdoc/classes/Sequel/Model.html Sequel::Model}.
3
+ # This corresponds to {https://developers.google.com/transit/gtfs/reference?csw=1#trips_txt___Field_Definitions trips.txt} in the
4
+ # GTFS feed, though the CTA does not fully implement the standard.
5
+ # @note Current columns: [:route_id, :service_id, :trip_id, :direction_id, :block_id, :shape_id, :direction, :wheelchair_accessible, :schd_trip_id]
2
6
  class Trip < Sequel::Model
3
7
  L_ROUTES = ["Brn", "G", "Pink", "P", "Org", "Red", "Blue", "Y"]
4
8
  BUS_ROUTES = CTA::Trip.exclude(:route_id => L_ROUTES).select_map(:route_id).uniq
@@ -21,22 +25,63 @@ module CTA
21
25
 
22
26
  set_primary_key :trip_id
23
27
 
28
+ # @!method calendar
29
+ # @return [CTA::Calendar] The {CTA::Calendar} entry for this {CTA::Trip}. Can be used to determine
30
+ # if a given {CTA::Trip} is valid for a given date/time
24
31
  many_to_one :calendar, :key => :service_id
32
+
33
+ # @!method stop_times
34
+ # @return [Array<CTA::StopTime>] The {CTA::StopTimes} that are serviced on this {CTA::Trip}
25
35
  one_to_many :stop_times, :key => :trip_id
26
36
 
37
+ # @!method route
38
+ # @return [CTA::Route] The {CTA::Route} associated with this {CTA::Trip}
27
39
  many_to_one :route, :key => :route_id
28
40
 
41
+ # @!method stops
42
+ # @return [Array<CTA::Stop>] All {CTA::Stop}s serviced on this {CTA::Trip}
29
43
  many_to_many :stops, :left_key => :trip_id, :right_key => :stop_id, :join_table => :stop_times
30
44
 
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...
45
+ # @!method route_id
46
+ # @return [String]
47
+ # @!method service_id
48
+ # @return [Integer]
49
+ # @!method trip_id
50
+ # @return [Integer]
51
+ # @!method direction_id
52
+ # @return [Integer]
53
+ # @!method block_id
54
+ # @return [Integer]
55
+ # @!method shape_id
56
+ # @return [Integer]
57
+ # @!method direction
58
+ # @return [String]
59
+ # @!method wheelchair_accessible
60
+ # @return [true,false]
61
+ # @!method schd_trip_id
62
+ # @return [String]
63
+ alias_method :id, :route_id
64
+ alias_method :scheduled_trip_id, :schd_trip_id
65
+ alias_method :run, :schd_trip_id
66
+
67
+ # Find a {CTA::Trip} that should be happening, given a timestamp and a route or run.
68
+ # The CTA does not return GTFS trip_id information in either the BusTracker or TrainTracker API, so
69
+ # it is actually somewhat difficult to associate an API response to a {CTA::Trip}. However, we
70
+ # know what *should* be happening at any given time. This method attempts a fuzzy find - internally,
71
+ # we often first try to find the exact Trip that should be happening according to the schedule, and
72
+ # then failing that we assume that the CTA is running late and look for trips that should have
73
+ # ended within the past 90 minutes. This almost always finds something.
74
+ # That said, however, it means we may sometimes find a {CTA::Trip} that's incorrect. In practical usage
75
+ # however, that doesn't matter too much - most Trips for a run service the same stops.
76
+ # However, to be safe, your program may wish to compare certain other bits of the API responses to ensure
77
+ # we found something valid. For example, almost all Brown line trains service the same stops, so
78
+ # finding the wrong Trip doesn't matter too much. However, a handful of Brown line runs throughout the dat
79
+ # actually change to Orange line trains at Midway - so, you may wish to verify that the destination of the
80
+ # Trip matches the reported destination of the API.
81
+ # Suggestions on how to approach this problem are most welcome (as are patches for better behavior).
82
+ # @param [String] run The run or route to search for
83
+ # @param [DateTime, String] timestamp The timestamp to search against.
84
+ # @param [true,false] fuzz Whether or not to do an exact schedule search or a fuzzy search.
40
85
  def self.find_active_run(run, timestamp, fuzz = false)
41
86
  if self.to_s == "CTA::Train" # This is admittedly hacky.
42
87
  join_str = "WHERE t.schd_trip_id = 'R#{run}'"
@@ -1,5 +1,7 @@
1
1
  module CTA
2
2
  class TrainTracker
3
+ @cache_responses = true
4
+
3
5
  # Returns the connection object we use to talk to the TrainTracker API
4
6
  def self.connection
5
7
  raise "You need to set a developer key first. Try CTA::TrainTracker.key = 'foo'." unless @key
@@ -9,7 +11,9 @@ module CTA
9
11
  faraday.params = { :key => @key }
10
12
 
11
13
  faraday.use CTA::TrainTracker::Parser, !!@debug
12
- faraday.response :caching, SimpleCache.new(Hash.new)
14
+ if @cache_responses
15
+ faraday.response :caching, (@cache || SimpleCache.new(Hash.new))
16
+ end
13
17
  faraday.adapter Faraday.default_adapter
14
18
  end
15
19
  end
@@ -146,5 +150,38 @@ module CTA
146
150
  @debug = debug
147
151
  @connection = nil
148
152
  end
153
+
154
+ # Returns whether or not cta_redux is caching responses
155
+ # @return [true, false]
156
+ def self.cache_responses
157
+ @cache_responses
158
+ end
159
+
160
+ # Sets whether or not cta_redux is caching responses
161
+ # @param [true, false] should_cache
162
+ def self.cache_responses=(should_cache)
163
+ @cache_responses = should_cache
164
+ @connection = nil
165
+ end
166
+
167
+ # Returns the underlying cache object caching responses (if we're actually caching responses)
168
+ # @return [Object]
169
+ def self.cache
170
+ if self.cache_responses
171
+ # This is ugly
172
+ @cache || self.connection.builder.handlers.find { |x| x == FaradayMiddleware::Caching }.instance_variable_get(:@args).first
173
+ else
174
+ nil
175
+ end
176
+ end
177
+
178
+ # Sets the underlying cache object caching responses. Any object can be used that responds to #read, #write, and #fetch
179
+ # @note Setting the cache object resets the connection. If you're using the default SimpleCache strategy (built-in 60
180
+ # second caching), then it will also *clear* the cache.
181
+ # @param [Object] cache
182
+ def self.cache=(cache)
183
+ @cache = cache
184
+ @connection = nil
185
+ end
149
186
  end
150
187
  end
@@ -1,3 +1,3 @@
1
1
  module CTA
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -45,8 +45,8 @@ RSpec.describe CTA::BusTracker do
45
45
 
46
46
  expect(result.vehicles.first.route).to be_instance_of(CTA::Route)
47
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)
48
+ expect(result.vehicles.first.live.vehicle_id).to eq(4394)
49
+ expect(result.vehicles.first.live.pattern_distance).to eq(115)
50
50
  end
51
51
 
52
52
  it "returns information about one vehicle" do
@@ -56,8 +56,8 @@ RSpec.describe CTA::BusTracker do
56
56
  expect(result.vehicles.size).to eq(1)
57
57
  expect(result.vehicles.first.route).to be_instance_of(CTA::Route)
58
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)
59
+ expect(result.vehicles.first.live.vehicle_id).to eq(4394)
60
+ expect(result.vehicles.first.live.heading).to eq(359)
61
61
  end
62
62
  end
63
63
 
@@ -43,7 +43,7 @@ RSpec.describe CTA::TrainTracker do
43
43
  expect(response.train).to be_instance_of(CTA::Train)
44
44
  expect(response.train.route_id).to eq("Blue")
45
45
  expect(response.train.route.route_id).to eq("Blue")
46
- expect(response.train.predictions).to eq(response.predictions)
46
+ expect(response.train.live.predictions).to eq(response.predictions)
47
47
  expect(response.predictions.first.direction).to eq("O'Hare-bound")
48
48
  expect(response.predictions.first.destination.stop_name).to eq("O'Hare")
49
49
  expect(response.predictions.first.approaching).to eq(false)
@@ -61,7 +61,7 @@ RSpec.describe CTA::TrainTracker do
61
61
  expect(response.trains.size).to eq(80)
62
62
 
63
63
  expect(response.trains.last.route.route_id).to eq("Y")
64
- expect(response.predictions.first.lon).to eq(-87.65338)
64
+ expect(response.trains.first.live.lon).to eq(-87.65338)
65
65
  expect(response.predictions.first.route.route_id).to eq("Red")
66
66
  expect(response.predictions.first.destination.stop_id).to eq(30173)
67
67
  expect(response.predictions.first.trip.service_id).to eq(104701)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cta_redux
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Hayworth
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-15 00:00:00.000000000 Z
11
+ date: 2015-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -151,6 +151,7 @@ files:
151
151
  - ".gitignore"
152
152
  - ".rspec"
153
153
  - ".travis.yml"
154
+ - CONTRIBUTING.md
154
155
  - Gemfile
155
156
  - LICENSE
156
157
  - README.md