cta_redux 0.2.0 → 0.3.1

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.
@@ -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