bus_time 0.3.2
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/lib/bus_time/api.rb +143 -0
- data/lib/bus_time/bulletin.rb +14 -0
- data/lib/bus_time/bus_route.rb +45 -0
- data/lib/bus_time/bus_stop.rb +37 -0
- data/lib/bus_time/prediction.rb +33 -0
- data/lib/bus_time/version.rb +5 -0
- data/lib/bus_time.rb +32 -0
- metadata +151 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 408ccbc3ddfb388706e1736aa4679c1e21965b73f76fd621a8a2b2b0709f66f3
|
4
|
+
data.tar.gz: 95b079015f6b070df1217976971cc8023bc4c5387fcdb65a3b042f0c3cd80171
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 06b3e3402e7bc78c2d50a8a80e3e34c80ba5ce1fcda2827213a9a344779785ec621f28a5ead0e32d43fde55150415c552d355b1283f6abc5a1357c45131e3afa
|
7
|
+
data.tar.gz: 2d5d5240769ee10c3b342c67cfa765bdf483bf89ead7afb425607c9a1fb5c0b7878d77262c41e735af366e92be7b0e00f5c4a8bc9af1d790bdfc8e8710235ede
|
data/lib/bus_time/api.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# BusTime API interface and response handling
|
4
|
+
class BusTime::Api
|
5
|
+
SUPPORTED_ACTIONS = %w[
|
6
|
+
gettime getroutes getdirections
|
7
|
+
getstops getpredictions getservicebulletins
|
8
|
+
].freeze
|
9
|
+
|
10
|
+
BASE_RESPONSE_PROP = "bustime-response"
|
11
|
+
|
12
|
+
def initialize(api_key, api_url = nil)
|
13
|
+
@@api_key = api_key
|
14
|
+
@api_url = api_url || "https://ctabustracker.com/bustime/api/v2"
|
15
|
+
end
|
16
|
+
|
17
|
+
def fetch_time
|
18
|
+
request("gettime")["tm"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_routes
|
22
|
+
request("getroutes")["routes"].map do |route|
|
23
|
+
assemble_route(route)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def fetch_route(id)
|
28
|
+
route = fetch_routes.find { |returned_route| returned_route.id == id }
|
29
|
+
|
30
|
+
# Get route stops
|
31
|
+
route.directions.each do |dir|
|
32
|
+
route.stops += fetch_stops(route.id, dir)
|
33
|
+
end
|
34
|
+
|
35
|
+
route
|
36
|
+
end
|
37
|
+
|
38
|
+
def fetch_directions(route_id)
|
39
|
+
request("getdirections", { rt: route_id })["directions"].map do |direction|
|
40
|
+
direction["dir"]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def fetch_stops(route_id, direction)
|
45
|
+
fetch_stops_by_params(rt: route_id, dir: direction)
|
46
|
+
end
|
47
|
+
|
48
|
+
def fetch_predictions(stop_ids)
|
49
|
+
request("getpredictions", { stpid: stop_ids.join(",") })["prd"].map do |prediction|
|
50
|
+
assemble_prediction(prediction)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def fetch_bulletins
|
55
|
+
request("getservicebulletins")["sb"].map do |bulletin|
|
56
|
+
BusTime::Bulletin.new(
|
57
|
+
bulletin["nm"]
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def fetch_routes_and_directions_and_stops
|
63
|
+
routes = fetch_routes
|
64
|
+
|
65
|
+
routes.each do |route|
|
66
|
+
route.directions.each do |dir|
|
67
|
+
route.stops += fetch_stops(route.id, dir)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
routes
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def request(action, params = {})
|
77
|
+
require "net/http"
|
78
|
+
require "json"
|
79
|
+
|
80
|
+
raise ArgumentError, "Invalid action: #{action}" unless SUPPORTED_ACTIONS.include?(action)
|
81
|
+
|
82
|
+
res = Net::HTTP.get_response(
|
83
|
+
request_uri(action, params)
|
84
|
+
)
|
85
|
+
|
86
|
+
case res
|
87
|
+
when Net::HTTPSuccess
|
88
|
+
handle_request_response(res)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def request_uri(action, params = {})
|
93
|
+
params[:key] = @@api_key
|
94
|
+
params[:format] = "json"
|
95
|
+
params[:locale] = "en"
|
96
|
+
|
97
|
+
uri = URI.parse("#{@api_url}/#{action}")
|
98
|
+
uri.query = URI.encode_www_form(params)
|
99
|
+
uri
|
100
|
+
end
|
101
|
+
|
102
|
+
def fetch_stops_by_params(params)
|
103
|
+
request("getstops", params)["stops"].map do |stop|
|
104
|
+
assemble_stop(stop, params)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def handle_request_response(response)
|
109
|
+
processed = JSON.parse(response.body)[BASE_RESPONSE_PROP]
|
110
|
+
if processed["error"]
|
111
|
+
raise ArgumentError, "Error: #{
|
112
|
+
processed['error'].map { |err| err['msg'] }.join(', ')
|
113
|
+
}"
|
114
|
+
end
|
115
|
+
|
116
|
+
processed
|
117
|
+
end
|
118
|
+
|
119
|
+
def assemble_route(route)
|
120
|
+
BusTime::BusRoute.new(route["rt"], route["rtnm"])
|
121
|
+
end
|
122
|
+
|
123
|
+
def assemble_stop(stop, params = {})
|
124
|
+
BusTime::BusStop.new(stop["stpid"], stop["stpnm"],
|
125
|
+
coords: [stop["lat"], stop["lon"]],
|
126
|
+
direction: params[:dir],
|
127
|
+
routes: [params[:rt]])
|
128
|
+
end
|
129
|
+
|
130
|
+
def assemble_prediction(prediction)
|
131
|
+
BusTime::Prediction.new(prediction["rt"], prediction["rtdir"],
|
132
|
+
prediction["stpid"],
|
133
|
+
prediction["prdctdn"],
|
134
|
+
arrives_at: DateTime.parse(prediction["prdtm"]),
|
135
|
+
destination: prediction["des"],
|
136
|
+
prediction_type: prediction["typ"],
|
137
|
+
delayed: prediction["dly"],
|
138
|
+
stop_name: prediction["stpnm"],
|
139
|
+
vehicle_id: prediction["vid"],
|
140
|
+
trip_id: prediction["tatripid"],
|
141
|
+
generated_at: DateTime.parse(prediction["tmstmp"]))
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Service bulletins, including reroutes and stop closures
|
4
|
+
class BusTime::Bulletin
|
5
|
+
attr_reader :name, :subject, :url, :updated_at
|
6
|
+
|
7
|
+
def initialize(name, subject, affected, updated_at, **opts)
|
8
|
+
@name = name
|
9
|
+
@subject = subject
|
10
|
+
@affected = affected
|
11
|
+
@updated_at = updated_at
|
12
|
+
@url = opts[:url]
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Service bus routes handling, including direction and stop retrieval
|
4
|
+
# via `BusTime::Api`
|
5
|
+
class BusTime::BusRoute
|
6
|
+
attr_reader :id, :name
|
7
|
+
|
8
|
+
attr_writer :directions, :stops
|
9
|
+
|
10
|
+
def initialize(id, name)
|
11
|
+
@id = id
|
12
|
+
@name = name
|
13
|
+
end
|
14
|
+
|
15
|
+
def directions
|
16
|
+
@directions || fetch_directions
|
17
|
+
end
|
18
|
+
|
19
|
+
def stops
|
20
|
+
@stops || fetch_stops
|
21
|
+
end
|
22
|
+
|
23
|
+
def fetch_directions
|
24
|
+
@directions = BusTime.api.fetch_directions(@id)
|
25
|
+
end
|
26
|
+
|
27
|
+
def fetch_stops
|
28
|
+
@directions.each do |direction|
|
29
|
+
@stops = BusTime.api.fetch_stops(@id, direction)
|
30
|
+
end
|
31
|
+
|
32
|
+
@stops
|
33
|
+
end
|
34
|
+
|
35
|
+
def display_name
|
36
|
+
"#{@id} - #{@name}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def nearby_stops(_lat, _lon, _radius = DEFAULT_NEARBY_DISTANCE)
|
40
|
+
@stops.select do |stop|
|
41
|
+
# stop.distanceFrom(lat, lon) <= radius
|
42
|
+
stop
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Service bus stop handling, including prediction retrieval via `BusTime::Api`
|
4
|
+
class BusTime::BusStop
|
5
|
+
attr_reader :id, :name, :lat, :lon
|
6
|
+
|
7
|
+
attr_writer :predictions
|
8
|
+
|
9
|
+
attr_accessor :direction, :routes
|
10
|
+
|
11
|
+
def initialize(id, name, coords:, direction:, routes: [])
|
12
|
+
@id = id
|
13
|
+
@name = name
|
14
|
+
@lat, @lon = coords
|
15
|
+
@direction = direction
|
16
|
+
|
17
|
+
@routes = routes
|
18
|
+
end
|
19
|
+
|
20
|
+
def predictions
|
21
|
+
@predictions || fetch_predictions
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch_predictions
|
25
|
+
@predictions = BusTime.api.fetch_predictions(@id)
|
26
|
+
end
|
27
|
+
|
28
|
+
def distance_from(lat, lon)
|
29
|
+
require "geocoder"
|
30
|
+
|
31
|
+
Geocoder::Calculations.distance_between([@lat, @lon], [lat, lon])
|
32
|
+
end
|
33
|
+
|
34
|
+
def nearby?(lat, lon, max_nearby_distance = BusTime.nearby_distance)
|
35
|
+
distance_from(lat, lon) <= max_nearby_distance
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Service bus stop prediction handling, including prediction retrieval
|
4
|
+
# via `BusTime::Api`
|
5
|
+
class BusTime::Prediction
|
6
|
+
attr_reader :time, :prediction_type, :delayed, :generated_at,
|
7
|
+
:route_id, :stop_id, :stop_name, :direction, :arrival_minutes,
|
8
|
+
:arrives_at, :stop_name, :vehicle_id, :destination,
|
9
|
+
:trip_id
|
10
|
+
|
11
|
+
def initialize(route_id, direction, stop_id, arrival_minutes, **opts)
|
12
|
+
@route_id = route_id
|
13
|
+
@stop_id = stop_id
|
14
|
+
@direction = direction
|
15
|
+
@arrival_minutes = arrival_minutes
|
16
|
+
|
17
|
+
@trip_id = opts[:trip_id]
|
18
|
+
@vehicle_id = opts[:vehicle_id]
|
19
|
+
|
20
|
+
@destination = opts[:destination]
|
21
|
+
@stop_name = opts[:stop_name]
|
22
|
+
|
23
|
+
@arrives_at = opts[:arrives_at]
|
24
|
+
@prediction_type = opts[:prediction_type] || "arrival"
|
25
|
+
@delayed = opts[:delayed] || false
|
26
|
+
|
27
|
+
@generated_at = opts[:generated_at] || DateTime.now
|
28
|
+
end
|
29
|
+
|
30
|
+
def delayed?
|
31
|
+
@delayed
|
32
|
+
end
|
33
|
+
end
|
data/lib/bus_time.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# BusTimings
|
4
|
+
|
5
|
+
# Interact with BusTime APIs to retrieve bus routing and prediction info from transit authorities such as the Chicago Transit Authority (CTA).
|
6
|
+
#
|
7
|
+
# @author Glenn Turner
|
8
|
+
module BusTime
|
9
|
+
DEFAULT_TS_FORMAT = "%Y%m%d %h:%m"
|
10
|
+
MAX_NEARBY_DISTANCE = 0.5
|
11
|
+
|
12
|
+
def self.connection(api_key, api_url = nil)
|
13
|
+
@connection ||= BusTime::Api.new(api_key, api_url)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.nearby_distance
|
17
|
+
@nearby_distance || MAX_NEARBY_DISTANCE
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.nearby_distance=(distance)
|
21
|
+
@nearby_distance = distance
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.api
|
25
|
+
@connection
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
require "date"
|
30
|
+
require "zeitwerk"
|
31
|
+
loader = Zeitwerk::Loader.for_gem
|
32
|
+
loader.setup
|
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bus_time
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- G Turner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-03-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: geocoder
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: zeitwerk
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.7'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: m
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.6'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.6'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '13.2'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '13.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.6'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.6'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: test-unit
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.6'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.6'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: webmock
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.24'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.24'
|
111
|
+
description: " A BusTime API wrapper to retrieve bus routing
|
112
|
+
and prediction info from transit authorities such as the Chicago Transit Authority
|
113
|
+
(CTA).\n"
|
114
|
+
email:
|
115
|
+
- contact@iamgturner.com
|
116
|
+
executables: []
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- lib/bus_time.rb
|
121
|
+
- lib/bus_time/api.rb
|
122
|
+
- lib/bus_time/bulletin.rb
|
123
|
+
- lib/bus_time/bus_route.rb
|
124
|
+
- lib/bus_time/bus_stop.rb
|
125
|
+
- lib/bus_time/prediction.rb
|
126
|
+
- lib/bus_time/version.rb
|
127
|
+
homepage: https://github.com/glennturner/bus_time
|
128
|
+
licenses:
|
129
|
+
- MIT
|
130
|
+
metadata: {}
|
131
|
+
post_install_message:
|
132
|
+
rdoc_options: []
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '2.4'
|
140
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
requirements: []
|
146
|
+
rubygems_version: 3.5.23
|
147
|
+
signing_key:
|
148
|
+
specification_version: 4
|
149
|
+
summary: A BusTime API wrapper to retrieve bus routing and prediction info from transit
|
150
|
+
authorities.
|
151
|
+
test_files: []
|