trimetter 1.0.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.
- data/bin/trimetter +61 -0
- data/lib/trimetter.rb +174 -0
- data/test/test_trimetter.rb +26 -0
- metadata +69 -0
data/bin/trimetter
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# Trimetter : a simple Ruby library for interfacing with Trimet data
|
3
|
+
# Copyright (C) 2012, Brian Enigma <http://netninja.com>
|
4
|
+
#
|
5
|
+
# For more information about this library see: (TBD)
|
6
|
+
# For more information about the Trimet API see: <http://developer.trimet.org/>
|
7
|
+
#
|
8
|
+
# This program is free software; you can redistribute it and/or
|
9
|
+
# modify it under the terms of the GNU General Public License
|
10
|
+
# as published by the Free Software Foundation; either version 2
|
11
|
+
# of the License, or (at your option) any later version.
|
12
|
+
#
|
13
|
+
# This program is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU General Public License
|
19
|
+
# along with this program; if not, write to the Free Software
|
20
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
21
|
+
|
22
|
+
require "trimetter"
|
23
|
+
|
24
|
+
if ARGV.length < 2
|
25
|
+
print "Usage: trimetter [developer_id] stop_id route_number\n"
|
26
|
+
print "\n"
|
27
|
+
print "developer_id is the token you received from Trimet for accessing the API.\n"
|
28
|
+
print " For more information, see http://developer.trimet.org\n"
|
29
|
+
print " It can be omitted if you have a ~/.trimetid file containing just your ID\n"
|
30
|
+
print "stop_id is the number printed on (or associated with) the bus stop\n"
|
31
|
+
print "route_number is the bus number\n"
|
32
|
+
print "\n"
|
33
|
+
print "Example: trimetter 11925 14\n"
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
|
37
|
+
developer_id = nil
|
38
|
+
developer_id = ARGV.shift if ARGV.length >= 3
|
39
|
+
stop_id = ARGV.shift
|
40
|
+
route_number = ARGV.shift
|
41
|
+
|
42
|
+
trimetter = TrimetterArrivalQuery.new()
|
43
|
+
trimetter.debug = false
|
44
|
+
stop_list = Array.new
|
45
|
+
stop_list << stop_id
|
46
|
+
trimetter.stop_list = stop_list
|
47
|
+
route_list = Array.new
|
48
|
+
route_list << route_number
|
49
|
+
trimetter.route_list = route_list
|
50
|
+
trimetter.developer_id = developer_id if developer_id != nil
|
51
|
+
results = Array.new()
|
52
|
+
error_string = ''
|
53
|
+
if !trimetter.fetch_update(results, error_string)
|
54
|
+
print "Error fetching Trimet data\n"
|
55
|
+
print "#{error_string}\n"
|
56
|
+
else
|
57
|
+
#print "\n\n"
|
58
|
+
print "Received #{results.length} result#{results.length == 1 ? '' : 's'}\n"
|
59
|
+
results.each { |result| print "#{result.to_s}\n" }
|
60
|
+
end
|
61
|
+
|
data/lib/trimetter.rb
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# Trimetter : a simple Ruby library for interfacing with Trimet data
|
3
|
+
# Copyright (C) 2012, Brian Enigma <http://netninja.com>
|
4
|
+
#
|
5
|
+
# For more information about this library see: (TBD)
|
6
|
+
# For more information about the Trimet API see: <http://developer.trimet.org/>
|
7
|
+
#
|
8
|
+
# This program is free software; you can redistribute it and/or
|
9
|
+
# modify it under the terms of the GNU General Public License
|
10
|
+
# as published by the Free Software Foundation; either version 2
|
11
|
+
# of the License, or (at your option) any later version.
|
12
|
+
#
|
13
|
+
# This program is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU General Public License
|
19
|
+
# along with this program; if not, write to the Free Software
|
20
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
21
|
+
|
22
|
+
require "rexml/document"
|
23
|
+
require "net/http"
|
24
|
+
require "uri"
|
25
|
+
|
26
|
+
# ----------------------------------------
|
27
|
+
|
28
|
+
# A pure-data class to bundle a number of arrival properties
|
29
|
+
class TrimetterArrival
|
30
|
+
attr_accessor :route, :location, :arrival_time, :arriving_in_minutes, :status, :sign_full, :sign_short
|
31
|
+
def initialize()
|
32
|
+
@route = ''
|
33
|
+
@location = 0
|
34
|
+
@arrival_time = Time.at(0)
|
35
|
+
@arriving_in_minutes = 0
|
36
|
+
@status = :invalid # can be invalid, estimated, scheduled, delayed, canceled, error
|
37
|
+
@sign_full = 0
|
38
|
+
@sign_short = 0
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s()
|
42
|
+
result = '#'
|
43
|
+
result << "#{@route} \"#{@sign_short}\"/\"#{@sign_full}\""
|
44
|
+
result << " @ StopID #{@location}, "
|
45
|
+
if :error == @status
|
46
|
+
result << "[error]"
|
47
|
+
elsif :canceled == @status
|
48
|
+
result << "[canceled]"
|
49
|
+
elsif :invalid == @status
|
50
|
+
result << "[uninitialized]"
|
51
|
+
elsif :estimated == @status
|
52
|
+
result << "Arriving in #{@arriving_in_minutes} minute#{@arriving_in_minutes == 1 ? '' : 's'}"
|
53
|
+
elsif :scheduled == @status
|
54
|
+
result << "Arriving in #{@arriving_in_minutes} minute#{@arriving_in_minutes == 1 ? '' : 's'}???"
|
55
|
+
elsif :delayed == @status
|
56
|
+
result << "Arriving @ #{@arrival_time.strftime('%I:%M%p')}"
|
57
|
+
else
|
58
|
+
result << "YOU SHOULDN'T BE SEEING THIS"
|
59
|
+
end
|
60
|
+
return result
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# ----------------------------------------
|
65
|
+
|
66
|
+
# You will need to supply three things when calling:
|
67
|
+
# - A Trimet developer ID. Get one from http://developer.trimet.org/registration/
|
68
|
+
# - An array of one or more stop IDs. These are the 4 or 5 digits numbers assigned to each stop.
|
69
|
+
# - An array of one or more bus numbers. For instance, 14 or 17 or 9.
|
70
|
+
# Once these have been assigned, you can then call fetch_update.
|
71
|
+
# This will return an array of TrimetArrival structures.
|
72
|
+
# This class will automatically check for ~/.trimetid and, if it exists, use the
|
73
|
+
# first line as your Trimet developer ID. This is so that you don't have to embed
|
74
|
+
# your ID in code (and risk accidentally checking it in to source control).
|
75
|
+
class TrimetterArrivalQuery
|
76
|
+
attr_accessor :developer_id, :debug
|
77
|
+
def initialize()
|
78
|
+
@developer_id = ''
|
79
|
+
@stop_list = Array.new
|
80
|
+
@route_list = Array.new
|
81
|
+
@debug = false;
|
82
|
+
if File.exists?(File.expand_path("~/.trimetid"))
|
83
|
+
f = File.new(File.expand_path("~/.trimetid"), "r")
|
84
|
+
@developer_id = f.readline().strip()
|
85
|
+
f.close()
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Add elements, forcing numbers, removing duplicates
|
90
|
+
def stop_list=(new_list)
|
91
|
+
@stop_list.clear()
|
92
|
+
new_list.flatten().each() { |i|
|
93
|
+
@stop_list << i.to_i
|
94
|
+
}
|
95
|
+
@stop_list.uniq!
|
96
|
+
end
|
97
|
+
|
98
|
+
# Add elements, removing duplicates
|
99
|
+
def route_list=(new_list)
|
100
|
+
@route_list = new_list.flatten().uniq
|
101
|
+
end
|
102
|
+
|
103
|
+
# After setting your query parameters, call this to retrieve
|
104
|
+
# live data from the server. Note that all requests are tied
|
105
|
+
# to your developer ID. Try to space them out and not
|
106
|
+
# overload their server. If you slam their server, they can
|
107
|
+
# block your ID.
|
108
|
+
def fetch_update(result_array, error_string)
|
109
|
+
result_array.clear()
|
110
|
+
error_string.gsub!(/.*/, '')
|
111
|
+
error_string << 'No developer ID given' if @developer_id.empty?
|
112
|
+
error_string << 'No stop list given' if @stop_list.empty?
|
113
|
+
error_string << 'No route list given' if @route_list.empty?
|
114
|
+
return false unless error_string.empty?
|
115
|
+
|
116
|
+
# Build request
|
117
|
+
url = "http://developer.trimet.org/ws/V1/arrivals?appID=#{@developer_id}&locIDs="
|
118
|
+
url << @stop_list.join(',')
|
119
|
+
|
120
|
+
# Send request
|
121
|
+
print "Sending request to #{url}\n" if @debug
|
122
|
+
response = Net::HTTP.get(URI(url))
|
123
|
+
print "Received #{response.length()} bytes\n" if @debug
|
124
|
+
if response.empty?
|
125
|
+
error_string << "Empty document returned"
|
126
|
+
return false
|
127
|
+
end
|
128
|
+
print "\n\n#{response}\n\n\n" if @debug
|
129
|
+
|
130
|
+
# Parse resulting XML
|
131
|
+
begin
|
132
|
+
document = REXML::Document.new(response)
|
133
|
+
rescue
|
134
|
+
error_string << "Error parsing XML"
|
135
|
+
return false
|
136
|
+
end
|
137
|
+
|
138
|
+
# Server returned valid XML, but it contained an error node
|
139
|
+
document.each_element("//errorMessage") { |el|
|
140
|
+
error_string << el.get_text() if !el.get_text().empty?
|
141
|
+
}
|
142
|
+
return false unless error_string.empty?
|
143
|
+
|
144
|
+
# Look for arrivals
|
145
|
+
now = Time.new
|
146
|
+
document.each_element("//arrival") { |el|
|
147
|
+
arrival = TrimetterArrival.new
|
148
|
+
arrival.route = el.attributes["route"].to_s.strip
|
149
|
+
arrival.location = el.attributes["locid"].to_s.strip
|
150
|
+
if el.attributes.has_key?("estimated")
|
151
|
+
arrival.arrival_time = Time.at(el.attributes["estimated"].to_s.strip.to_i / 1000)
|
152
|
+
arrival.status = :estimated
|
153
|
+
elsif el.attributes.has_key?("scheduled")
|
154
|
+
arrival.arrival_time = Time.at(el.attributes["scheduled"].to_s.strip.to_i / 1000)
|
155
|
+
arrival.status = :scheduled
|
156
|
+
end
|
157
|
+
arrival.arriving_in_minutes = ((arrival.arrival_time - now) / 60).to_i
|
158
|
+
if (arrival.arriving_in_minutes < 0 || arrival.arriving_in_minutes > 120)
|
159
|
+
arrival.status = :error
|
160
|
+
end
|
161
|
+
if el.attributes["status"].to_s == "delayed"
|
162
|
+
arrival.status = :delayed
|
163
|
+
elsif el.attributes["status"].to_s == "canceled"
|
164
|
+
arrival.status = :canceled
|
165
|
+
end
|
166
|
+
arrival.sign_full = el.attributes["fullSign"].to_s.strip
|
167
|
+
arrival.sign_short = el.attributes["shortSign"].to_s.strip
|
168
|
+
print "#{arrival.inspect}\n" if @debug
|
169
|
+
result_array << arrival
|
170
|
+
}
|
171
|
+
error_string << 'No records found' if result_array.empty?
|
172
|
+
return error_string.empty?
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'test/unit'
|
3
|
+
require 'trimetter'
|
4
|
+
|
5
|
+
class TrimetterTest < Test::Unit::TestCase
|
6
|
+
# This is a tough thing to test. It relies not only on a per-developer
|
7
|
+
# developer ID. It also relies on an external server being alive and
|
8
|
+
# reachable from the test workstation.
|
9
|
+
def test_query
|
10
|
+
if File.exists?(File.expand_path("~/.trimetid"))
|
11
|
+
trimetter = TrimetterArrivalQuery.new
|
12
|
+
trimetter.debug = false
|
13
|
+
trimetter.stop_list = [11925]
|
14
|
+
trimetter.route_list = [14]
|
15
|
+
results = Array.new
|
16
|
+
error_string = ''
|
17
|
+
rc = trimetter.fetch_update(results, error_string)
|
18
|
+
assert_equal true, rc
|
19
|
+
assert_equal '', error_string
|
20
|
+
else
|
21
|
+
print "No ~/.trimetid file found. Cannot run automated tests.\n"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trimetter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Brian Enigma
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-03-01 00:00:00 -08:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: This gem wrappers the Portland, Oregon Trimet transit tracker API in some simple objects. Note that a Trimet developer ID is required. See http://developer.trimet.org for more information on developer IDs.
|
23
|
+
email: brian@netninja.com
|
24
|
+
executables:
|
25
|
+
- trimetter
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- lib/trimetter.rb
|
32
|
+
- bin/trimetter
|
33
|
+
- test/test_trimetter.rb
|
34
|
+
has_rdoc: true
|
35
|
+
homepage: http://github.com/BrianEnigma/Trimetter
|
36
|
+
licenses: []
|
37
|
+
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
hash: 3
|
49
|
+
segments:
|
50
|
+
- 0
|
51
|
+
version: "0"
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
requirements:
|
62
|
+
- A Trimet developer ID, ether embedded in your app or in a dot-file in your home directory
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.3.7
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: Trimet transit tracker API library
|
68
|
+
test_files:
|
69
|
+
- test/test_trimetter.rb
|