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