trimetter 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+
@@ -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