octranspo 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +26 -0
- data/Rakefile +8 -0
- data/lib/octranspo.rb +20 -0
- data/lib/octranspo/headsign.rb +2539 -0
- data/lib/octranspo/headsigns.yml +40565 -0
- data/lib/octranspo/landmarks.rb +37 -0
- data/lib/octranspo/lingo.rb +125 -0
- data/lib/octranspo/lingo/headsign_messages.yml +104 -0
- data/lib/octranspo/lingo/headsign_signatures.yml +3 -0
- data/lib/octranspo/lingo/landmarks.yml +205 -0
- data/lib/octranspo/lingo/roadways.yml +1957 -0
- data/lib/octranspo/mobile_resource_methods.rb +98 -0
- data/lib/octranspo/mobile_route_data.rb +85 -0
- data/lib/octranspo/mobile_route_schedule.rb +118 -0
- data/lib/octranspo/mobile_stop_schedule.rb +137 -0
- data/lib/octranspo/remote_resource_methods.rb +89 -0
- data/lib/octranspo/route.rb +31 -0
- data/lib/octranspo/routes.yml +507 -0
- data/lib/octranspo/service_date.rb +17 -0
- data/lib/octranspo/stations.yml +77 -0
- data/lib/octranspo/stop.rb +69 -0
- data/lib/octranspo/stop_resource.rb +46 -0
- data/lib/octranspo/stops.yml +30696 -0
- data/lib/octranspo/version.rb +3 -0
- data/octranspo.gemspec +22 -0
- data/test/fixtures/mobile_route_data/20090323/route_123_index_0.html +457 -0
- data/test/fixtures/mobile_route_data/20090323/route_123_index_1.html +99 -0
- data/test/fixtures/mobile_route_data/20090323/route_1_index_0.html +700 -0
- data/test/fixtures/mobile_route_data/20090323/route_1_index_1.html +691 -0
- data/test/fixtures/mobile_route_data/20090323/route_2_index_0.html +682 -0
- data/test/fixtures/mobile_route_data/20090323/route_2_index_1.html +646 -0
- data/test/fixtures/mobile_route_data/20100323/route_123_index_0.html +469 -0
- data/test/fixtures/mobile_route_data/20100323/route_123_index_1.html +105 -0
- data/test/fixtures/mobile_route_data/20100323/route_1_index_0.html +703 -0
- data/test/fixtures/mobile_route_data/20100323/route_1_index_1.html +694 -0
- data/test/fixtures/mobile_route_data/20100323/route_2_index_0.html +685 -0
- data/test/fixtures/mobile_route_data/20100323/route_2_index_1.html +649 -0
- data/test/fixtures/mobile_route_data/20100323/route_3_index_0.html +649 -0
- data/test/fixtures/mobile_route_data/20100323/route_3_index_1.html +721 -0
- data/test/fixtures/mobile_route_schedule/20090323/route_18_direction_2_location_25.html +1038 -0
- data/test/fixtures/mobile_route_schedule/20090323/route_1_direction_1_location_0.html +1127 -0
- data/test/fixtures/mobile_route_schedule/20090323/route_2_direction_2_location_11.html +1430 -0
- data/test/fixtures/mobile_route_schedule/20090323/route_2_direction__location_11.html +104 -0
- data/test/fixtures/mobile_route_schedule/20100323/route_1_direction_1_location_0.html +1180 -0
- data/test/fixtures/mobile_route_schedule/20100323/route_2_direction_2_location_11.html +1554 -0
- data/test/fixtures/mobile_route_schedule/20100323/route_3_direction_1_location_0.html +1082 -0
- data/test/fixtures/mobile_route_schedule/20100323/route_3_direction_1_location_1.html +1082 -0
- data/test/fixtures/mobile_stop_schedule/20090323/location_1987.yml +198 -0
- data/test/fixtures/mobile_stop_schedule/20090323/location_1987_route_index_0.html +1283 -0
- data/test/fixtures/mobile_stop_schedule/20090323/location_1987_route_index_1.html +194 -0
- data/test/fixtures/mobile_stop_schedule/20090323/location_1987_route_index_2.html +116 -0
- data/test/fixtures/mobile_stop_schedule/20090323/location_3058_route_index_12.html +149 -0
- data/test/fixtures/mobile_stop_schedule/20090323/location_8789_route_index_0.html +1002 -0
- data/test/fixtures/mobile_stop_schedule/20100323/location_1987.yml +209 -0
- data/test/fixtures/mobile_stop_schedule/20100323/location_1987_route_index_0.html +1393 -0
- data/test/fixtures/mobile_stop_schedule/20100323/location_1987_route_index_1.html +206 -0
- data/test/fixtures/mobile_stop_schedule/20100323/location_1987_route_index_2.html +123 -0
- data/test/fixtures/mobile_stop_schedule/20100323/location_3058_route_index_12.html +278 -0
- data/test/fixtures/mobile_stop_schedule/20100323/location_7558_route_index_0.html +979 -0
- data/test/fixtures/mobile_stop_schedule/20100323/location_8789_route_index_0.html +1059 -0
- data/test/fixtures/stop_resource/1987.html +126 -0
- data/test/test_helper.rb +15 -0
- data/test/unit/headsign_test.rb +34 -0
- data/test/unit/lingo_test.rb +139 -0
- data/test/unit/mobile_resource_methods_test.rb +25 -0
- data/test/unit/mobile_route_data_test.rb +124 -0
- data/test/unit/mobile_route_schedule_test.rb +210 -0
- data/test/unit/mobile_stop_schedule_test.rb +153 -0
- data/test/unit/service_date_test.rb +27 -0
- data/test/unit/stop_resource_test.rb +26 -0
- metadata +251 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
require "hpricot"
|
2
|
+
require "iconv"
|
3
|
+
|
4
|
+
module OCTranspo
|
5
|
+
module MobileResourceMethods
|
6
|
+
include RemoteResourceMethods
|
7
|
+
|
8
|
+
def site
|
9
|
+
"http://www.octranspo.com/mobileweb"
|
10
|
+
end
|
11
|
+
|
12
|
+
def date_stamp (date)
|
13
|
+
date.strftime("%Y%m%d")
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_options
|
17
|
+
{:index => 0, :date => Date.current}
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
# Return a Hpricot DOM representing source_code.
|
23
|
+
def interpret (source_code)
|
24
|
+
Hpricot(convert_to_utf_8(source_code), :fixup_tags => true)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return a copy of source_code converted to UTF-8.
|
28
|
+
def convert_to_utf_8 (source_code)
|
29
|
+
Iconv.new('UTF-8', 'ISO-8859-1').iconv(source_code).sub('charset=iso-8859-1', 'charset=utf-8')
|
30
|
+
end
|
31
|
+
|
32
|
+
# TODO: Use Lingo instead of local regexps.
|
33
|
+
def extract_route_and_direction_from (text)
|
34
|
+
if matches = /OTrn (.+)/.match(text)
|
35
|
+
{:route => "O-Train", :direction => matches[1].strip.mb_chars.upcase}
|
36
|
+
elsif matches = /^SHTL SCOTIA BANK PLACE BANQUE SCOTIA/.match(text)
|
37
|
+
{:route => nil, :direction => "SHUTTLE TO SCOTIA BANK PLACE BANQUE SCOTIA"}
|
38
|
+
elsif matches = /(\d+) (.+)/.match(text)
|
39
|
+
{:route => matches[1], :direction => matches[2].strip.mb_chars.upcase}
|
40
|
+
else
|
41
|
+
raise "Failed to extract route and direction: #{text}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# TODO: Use Lingo instead of local regexps.
|
46
|
+
def extract_location_from (text)
|
47
|
+
if matches = /(.+) ([1-9][A-E]) +\((\d\d\d\d)\)/.match(text)
|
48
|
+
{:station => matches[1], :platform => matches[2], :number => matches[3], :name => "#{matches[1]} #{matches[2]}"}
|
49
|
+
elsif matches = /(.+) \((\d\d\d\d)\)/.match(text)
|
50
|
+
{:name => matches[1], :number => matches[2]}
|
51
|
+
else
|
52
|
+
{:name => text.strip}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def extract_time_from (text, schedule)
|
57
|
+
match = /(\d+):(\d+) (AM|PM)/.match(text)
|
58
|
+
if match
|
59
|
+
hour = match[1].to_i
|
60
|
+
minute = match[2].to_i
|
61
|
+
meridian = match[3]
|
62
|
+
|
63
|
+
if meridian == "AM"
|
64
|
+
if hour < 4
|
65
|
+
hour = hour + 24
|
66
|
+
end
|
67
|
+
else
|
68
|
+
unless hour == 12
|
69
|
+
hour = hour + 12
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
seconds_after_midnight = hour.hours.seconds + minute.minutes.seconds
|
74
|
+
return Time.zone.parse(schedule[:date].to_s) + seconds_after_midnight
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def extract_departure_from (text, schedule)
|
79
|
+
flags = extract_flags_from(text)
|
80
|
+
destination_flag = schedule[:destinations].keys.detect { |key| flags.delete(key) }
|
81
|
+
departure = {}
|
82
|
+
departure[:time] = extract_time_from(text, schedule)
|
83
|
+
departure[:destination] = schedule[:destinations][destination_flag]
|
84
|
+
if flags.any?
|
85
|
+
departure[:notes] = flags.map { |key| schedule[:legend][key] }
|
86
|
+
end
|
87
|
+
departure
|
88
|
+
end
|
89
|
+
|
90
|
+
def extract_flags_from (text)
|
91
|
+
if flag_data = /\[(.+)\]/.match(text)
|
92
|
+
flag_data[1].split("")
|
93
|
+
else
|
94
|
+
[]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module OCTranspo
|
2
|
+
module MobileRouteData
|
3
|
+
|
4
|
+
include MobileResourceMethods
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# Provides an interface for retreiving basic information about an OC Transpo transit route. Data is
|
8
|
+
# sourced from http://octranspo.com/mobileweb/jnot/post.routelist.schedules.oci
|
9
|
+
#
|
10
|
+
# = Usage
|
11
|
+
#
|
12
|
+
# OCTranspo::MobileRouteData.find returns an array of direction hashes for a single route. Most routes
|
13
|
+
# operate in two directions so you will usually receive an array with two items in it. For example:
|
14
|
+
#
|
15
|
+
# # Find data for OCTranspo bus route 2 on today’s date
|
16
|
+
# route_data = OCTranspo::MobileRouteData.find(2)
|
17
|
+
#
|
18
|
+
# # Bus route 2 has two directions
|
19
|
+
# route_data.size # => 2
|
20
|
+
#
|
21
|
+
# # 2 BAYSHORE is the first direction
|
22
|
+
# route_data[0][:route] # => "2"
|
23
|
+
# route_data[0][:direction] # => "BAYSHORE"
|
24
|
+
# route_data[0][:direction_index] # => "2"
|
25
|
+
# route_data[0][:stops] # => [{:name => "RIDEAU 4A", :number => "3009"}, ...]
|
26
|
+
#
|
27
|
+
# # 2 DOWNTOWN is the opposite direction
|
28
|
+
# route_data[1][:route] # => "2"
|
29
|
+
# route_data[1][:direction] # => "DOWNTOWN / CENTRE-VILLE"
|
30
|
+
# route_data[1][:direction_index] # => "1"
|
31
|
+
# route_data[1][:stops] # => [{:name => "BAYSHORE 4B", :number => "3050"}, ...]
|
32
|
+
#
|
33
|
+
# = Notes
|
34
|
+
#
|
35
|
+
# The direction_index attributes returned by OCTranspo::MobileRouteData methods are always the values
|
36
|
+
# "1" or "2". These values correspond to the direction_index argument in OCTranspo::MobileRouteSchedule.
|
37
|
+
# Sometimes the first direction has the value "1", sometimes it has the value "2". When a route only has
|
38
|
+
# one direction it usually has the value "1". All in all very confusing.
|
39
|
+
|
40
|
+
# Returns an array of direction hashes or nil if the route was not found.
|
41
|
+
def find (route_name_or_number, options={})
|
42
|
+
options[:route] = route_name_or_number
|
43
|
+
directions = find_all_directions(options)
|
44
|
+
if directions.any?
|
45
|
+
return directions
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_all_directions (options={})
|
50
|
+
directions = []
|
51
|
+
0.upto(1) do |index|
|
52
|
+
directions << find_one_direction(options.merge(:index => index))
|
53
|
+
end
|
54
|
+
directions.compact
|
55
|
+
end
|
56
|
+
|
57
|
+
def find_one_direction (options={})
|
58
|
+
decode(read(options))
|
59
|
+
end
|
60
|
+
|
61
|
+
def url_for (options={})
|
62
|
+
options = default_options.merge(options)
|
63
|
+
url = "#{site}/jnot/post.routelist.schedules.oci?"
|
64
|
+
url << "rangeIndex=5&day=#{date_stamp(options[:date])}"
|
65
|
+
url << "&route=#{options[:route]}&routeIndex=#{options[:index]}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def path_for (options={})
|
69
|
+
options = default_options.merge(options)
|
70
|
+
"#{date_stamp(options[:date])}/route_#{options[:route]}_index_#{options[:index]}.html"
|
71
|
+
end
|
72
|
+
|
73
|
+
def decode (source_code)
|
74
|
+
document = interpret(source_code)
|
75
|
+
if table_of_stops = document.at("#SchedulesStopListStopsTable")
|
76
|
+
direction = extract_route_and_direction_from(table_of_stops.search(:td)[1].inner_text)
|
77
|
+
direction[:direction_index] = document.at("#direction")[:value].sub('Direction', '')
|
78
|
+
direction[:stops] = table_of_stops.search("td.checkbox").map do |cell|
|
79
|
+
extract_location_from(cell.inner_text.strip)
|
80
|
+
end
|
81
|
+
return direction
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module OCTranspo
|
2
|
+
module MobileRouteSchedule
|
3
|
+
|
4
|
+
include MobileResourceMethods
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# Provides an interface for retreiving OC Transpo timetables for a specific route, direction and location_index.
|
8
|
+
# This API is somewhat less convenient than OCTranpo::MobileStopSchedule.
|
9
|
+
#
|
10
|
+
# The data returned by this module is sourced from:
|
11
|
+
# http://www.octranspo.com/mobileweb/jnot/post.stoplist.schedules.oci
|
12
|
+
#
|
13
|
+
# == Usage
|
14
|
+
#
|
15
|
+
# # Find the schedule for the 85 going to HURDMAN from Bayshore Station (the first stop on the route)
|
16
|
+
# schedule = OCTranspo::MobileRouteSchedule.find(:route => 85, :direction => "HURDMAN", :location_index => 0)
|
17
|
+
# schedule[:route] # => "85"
|
18
|
+
# schedule[:direction] # => "HURDMAN"
|
19
|
+
# schedule[:location][:name] # => "BAYSHORE 4B"
|
20
|
+
# schedule[:location][:number] # => "3050"
|
21
|
+
# schedule[:date] # => Date.current
|
22
|
+
# schedule[:departures] # => [{:time => Time.zone.parse('04:12'), :destination => "HURDMAN"}, ...]
|
23
|
+
#
|
24
|
+
# # Find the schedule for the 85 going to HURDMAN from Bronson Ave. & Somerset St. (53rd stop on the route)
|
25
|
+
# schedule = OCTranspo::MobileRouteSchedule.find(:route => 85, :direction => "HURDMAN", :location_index => 53)
|
26
|
+
# schedule[:route] # => "85"
|
27
|
+
# schedule[:direction] # => "HURDMAN"
|
28
|
+
# schedule[:location][:name] # => "BRONSON / SOMERSET W-O"
|
29
|
+
# schedule[:location][:number] # => "6625"
|
30
|
+
# schedule[:date] # => Date.current
|
31
|
+
# schedule[:departures] # => [{:time => Time.zone.parse('04:42'), :destination => "HURDMAN"}, ...]
|
32
|
+
|
33
|
+
# Return a Hpricot DOM representing source_code.
|
34
|
+
def interpret (source_code)
|
35
|
+
Hpricot(convert_to_utf_8(source_code))
|
36
|
+
end
|
37
|
+
|
38
|
+
def find (options={})
|
39
|
+
options[:date] = if options[:date]
|
40
|
+
options[:date].to_date
|
41
|
+
else
|
42
|
+
Date.current
|
43
|
+
end
|
44
|
+
decode(read(options), options[:date])
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_one (options={})
|
48
|
+
options[:date] = if options[:date]
|
49
|
+
options[:date].to_date
|
50
|
+
else
|
51
|
+
Date.current
|
52
|
+
end
|
53
|
+
decode(read(options), options[:date])
|
54
|
+
end
|
55
|
+
|
56
|
+
def decode (source_code, date)
|
57
|
+
document = interpret(source_code)
|
58
|
+
|
59
|
+
route_and_direction_text = document.at("#SchedulesTimesSelectedTable").search(:tr)[2].search(:td)[1].inner_text
|
60
|
+
location_text = document.at("#SchedulesTimesListTable td.header").inner_text
|
61
|
+
departure_texts = Array(document.search('#SchedulesTimesListTable td'))[1..-1].map do |cell| cell.inner_text.strip end
|
62
|
+
|
63
|
+
schedule = {:date => date, :legend => {}}
|
64
|
+
schedule.merge! extract_route_and_direction_from(route_and_direction_text)
|
65
|
+
schedule[:destinations] = Hash.new(schedule[:direction])
|
66
|
+
|
67
|
+
Array(document.search("#SchedulesTimesNotesTable td.notes")).in_groups_of(2) do |flag_cell, value_cell|
|
68
|
+
flag = flag_cell.inner_text.sub('[', '').sub(']', '').strip
|
69
|
+
text = value_cell.inner_text.strip
|
70
|
+
if text.include?("Destination")
|
71
|
+
schedule[:destinations][flag] = text.sub("Destination ", "")
|
72
|
+
else
|
73
|
+
schedule[:legend][flag] = text
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
schedule[:location] = extract_location_from(location_text)
|
78
|
+
schedule[:departures] = departure_texts.inject([]) do |departures, text|
|
79
|
+
departures << extract_departure_from(text, schedule)
|
80
|
+
end
|
81
|
+
|
82
|
+
schedule[:headsigns] = schedule[:departures].map do |d| d[:destination] end.uniq
|
83
|
+
|
84
|
+
return schedule
|
85
|
+
end
|
86
|
+
|
87
|
+
def default_options
|
88
|
+
{:direction_index => 1, :location_index => 0, :date => Date.current}
|
89
|
+
end
|
90
|
+
|
91
|
+
def url_for (options={})
|
92
|
+
options = default_options.merge(convert_options(options))
|
93
|
+
url = "#{site}/jnot/post.stoplist.schedules.oci?"
|
94
|
+
url << "rangeIndex=5&day=#{date_stamp(options[:date])}"
|
95
|
+
url << "&route=#{options[:route]}"
|
96
|
+
url << "&direction=Direction#{options[:direction_index]}"
|
97
|
+
url << "&check#{options[:location_index]}=on"
|
98
|
+
end
|
99
|
+
|
100
|
+
def path_for (options={})
|
101
|
+
options = default_options.merge(convert_options(options))
|
102
|
+
"#{date_stamp(options[:date])}/route_#{options[:route]}_direction_#{options[:direction_index]}_location_#{options[:location_index]}.html"
|
103
|
+
end
|
104
|
+
|
105
|
+
def convert_options (options={})
|
106
|
+
if options[:direction]
|
107
|
+
route_data = MobileRouteData.find(options[:route], :date => options[:date])
|
108
|
+
direction_data = route_data.detect do |dataset|
|
109
|
+
dataset[:direction] === options[:direction]
|
110
|
+
end
|
111
|
+
if direction_data
|
112
|
+
options[:direction_index] = direction_data[:direction_index]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
return options
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module OCTranspo
|
2
|
+
module MobileStopSchedule
|
3
|
+
|
4
|
+
include MobileResourceMethods
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# Provides an interface for retreiving OCTranspo timetables for a specific route and location. This API
|
8
|
+
# is more convenient than OCTranpo::MobileStopSchedule but it doesn’t work for stops without numbers.
|
9
|
+
#
|
10
|
+
# The data returned by this module is sourced from:
|
11
|
+
# http:/octranspo.com/mobileweb/jnot/post.routelist.stoptimes.oci
|
12
|
+
#
|
13
|
+
# == Usage
|
14
|
+
#
|
15
|
+
# # Find the schedule for the 85 going to HURDMAN at Bayshore Station (the first stop on the route)
|
16
|
+
# schedule = OCTranspo::MobileStopSchedule.find(:route => 85, :direction => "HURDMAN", :location => "3050")
|
17
|
+
# schedule[:route] # => "85"
|
18
|
+
# schedule[:direction] # => "HURDMAN"
|
19
|
+
# schedule[:location][:name] # => "BAYSHORE 4B"
|
20
|
+
# schedule[:location][:number] # => "3050"
|
21
|
+
# schedule[:date] # => Date.current
|
22
|
+
# schedule[:departures] # => [{:time => Time.zone.parse('04:12'), :destination => "HURDMAN"}, ...]
|
23
|
+
#
|
24
|
+
# # Find the schedule for the 85 going to HURDMAN at Bronson Ave. & Somerset St.
|
25
|
+
# schedule = OCTranspo::MobileStopSchedule.find(:route => 85, :direction => "HURDMAN", :location => "6625")
|
26
|
+
# schedule[:route] # => "85"
|
27
|
+
# schedule[:direction] # => "HURDMAN"
|
28
|
+
# schedule[:location][:name] # => "BRONSON / SOMERSET W-O"
|
29
|
+
# schedule[:location][:number] # => "6625"
|
30
|
+
# schedule[:date] # => Date.current
|
31
|
+
# schedule[:departures] # => [{:time => Time.zone.parse('04:42'), :destination => "HURDMAN"}, ...]
|
32
|
+
|
33
|
+
def find (options={})
|
34
|
+
route = "#{options.delete(:route)}"
|
35
|
+
direction = "#{options.delete(:direction)}"
|
36
|
+
find_all(options).detect do |schedule|
|
37
|
+
(schedule[:route] == route) && (schedule[:direction] == direction)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_all (options={})
|
42
|
+
options[:date] ||= default_options[:date]
|
43
|
+
if File.exist? index_path_for(options)
|
44
|
+
localize YAML.load(open(index_path_for(options)))
|
45
|
+
else
|
46
|
+
all_routes = []
|
47
|
+
while schedule = find_one(options.merge(:route_index => all_routes.size))
|
48
|
+
all_routes << schedule
|
49
|
+
end
|
50
|
+
open index_path_for(options), 'w' do |f|
|
51
|
+
YAML.dump all_routes, f
|
52
|
+
end
|
53
|
+
|
54
|
+
find_all(options)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_one (options={})
|
59
|
+
source_code = read(options)
|
60
|
+
decode(source_code)
|
61
|
+
end
|
62
|
+
|
63
|
+
def localize (schedules)
|
64
|
+
schedules.each do |schedule|
|
65
|
+
schedule[:departures].each do |departure|
|
66
|
+
departure[:time] = departure[:time].in_time_zone
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def decode (source_code)
|
72
|
+
document = interpret(source_code)
|
73
|
+
unless document.at('.InfoBeanError')
|
74
|
+
schedule = {:legend => {}}
|
75
|
+
|
76
|
+
header_rows = document.search("#StopTimesTimesSelectedTable > tr")
|
77
|
+
|
78
|
+
date_text = header_rows[0].search(:td)[1].inner_text
|
79
|
+
schedule.merge! :date => Date.parse(date_text)
|
80
|
+
|
81
|
+
location_text = header_rows[2].search(:td)[1].inner_text
|
82
|
+
schedule.merge! :location => extract_location_from(location_text)
|
83
|
+
|
84
|
+
route_and_direction_text = header_rows[6].search(:td)[1].inner_text
|
85
|
+
schedule.merge! extract_route_and_direction_from(route_and_direction_text)
|
86
|
+
|
87
|
+
schedule[:destinations] = Hash.new(schedule[:direction])
|
88
|
+
header_rows.slice(8, header_rows.size).each do |row|
|
89
|
+
cells = row.search(:td)
|
90
|
+
flag = /\[(.)\]/.match(cells[0].inner_text)[1]
|
91
|
+
text = cells[1].inner_text
|
92
|
+
if text.include?("Destination")
|
93
|
+
schedule[:destinations][flag] = text.sub("Destination ", "")
|
94
|
+
else
|
95
|
+
schedule[:legend][flag] = text
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
departure_texts = document.search("#StopTimesTimesListTable0 td").map {|cell| cell.inner_text.strip }
|
100
|
+
schedule[:departures] = departure_texts.inject([]) do |departures, text|
|
101
|
+
departures << extract_departure_from(text, schedule)
|
102
|
+
end
|
103
|
+
|
104
|
+
schedule[:headsigns] = schedule[:departures].map { |d| d[:destination] }.uniq
|
105
|
+
|
106
|
+
return schedule
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def default_options
|
111
|
+
{:route_index => 0, :date => Date.current}
|
112
|
+
end
|
113
|
+
|
114
|
+
def url_for (options={})
|
115
|
+
options = default_options.merge(options)
|
116
|
+
url = "#{site}/jnot/post.routelist.stoptimes.oci?"
|
117
|
+
url << "rangeIndex=5&day=#{date_stamp(options[:date])}"
|
118
|
+
url << "&stop=#{options[:location]}"
|
119
|
+
url << "&check#{options[:route_index]}=on"
|
120
|
+
end
|
121
|
+
|
122
|
+
def path_for (options={})
|
123
|
+
options = default_options.merge(options)
|
124
|
+
"#{date_stamp(options[:date])}/location_#{options[:location]}_route_index_#{options[:route_index]}.html"
|
125
|
+
end
|
126
|
+
|
127
|
+
def index_path_for (options={})
|
128
|
+
options = default_options.merge(options)
|
129
|
+
case @source
|
130
|
+
when :fixtures
|
131
|
+
"#{fixtures_path}/#{date_stamp(options[:date])}/location_#{options[:location]}.yml"
|
132
|
+
else
|
133
|
+
"#{cache_path}/#{date_stamp(options[:date])}/location_#{options[:location]}.yml"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require "open-uri"
|
2
|
+
|
3
|
+
module OCTranspo
|
4
|
+
module RemoteResourceMethods
|
5
|
+
|
6
|
+
def read (options={})
|
7
|
+
# puts url_for(options)
|
8
|
+
case @source
|
9
|
+
when :fixtures
|
10
|
+
read_from_fixture(options)
|
11
|
+
else
|
12
|
+
read_from_cache(options) || cache(options)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def read_from_remote (options)
|
17
|
+
url = url_for(options)
|
18
|
+
puts "Opening #{url}"
|
19
|
+
open(url).read
|
20
|
+
end
|
21
|
+
|
22
|
+
def read_from_fixture (options)
|
23
|
+
address = fixture_path_for(options)
|
24
|
+
create_fixture(options) unless File.exist?(address)
|
25
|
+
open(address).read
|
26
|
+
end
|
27
|
+
|
28
|
+
def read_from_cache (options)
|
29
|
+
address = cache_path_for(options)
|
30
|
+
if File.exist?(address)
|
31
|
+
open(address).read
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def reset!
|
36
|
+
@source = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def use_fixtures!
|
40
|
+
@source = :fixtures
|
41
|
+
end
|
42
|
+
|
43
|
+
def site; end
|
44
|
+
|
45
|
+
def url_for (options={}); end
|
46
|
+
|
47
|
+
def path_for (options={}); end
|
48
|
+
|
49
|
+
def fixture_path_for (options={});
|
50
|
+
"#{fixtures_path}/#{path_for(options)}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def cache_path_for (options={});
|
54
|
+
"#{cache_path}/#{path_for(options)}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def fixtures_path
|
58
|
+
"test/fixtures/#{self.name.demodulize.underscore}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def cache_path
|
62
|
+
"cache/octranspo/#{self.name.demodulize.underscore}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def create_fixture (options={})
|
66
|
+
address = fixture_path_for(options)
|
67
|
+
puts "Creating fixture #{address}"
|
68
|
+
system "mkdir -p #{File.dirname(address)}"
|
69
|
+
File.open(address, "w") do |f|
|
70
|
+
f.write read_from_remote(options)
|
71
|
+
end
|
72
|
+
read_from_fixture(options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def cache (options={})
|
76
|
+
address = cache_path_for(options)
|
77
|
+
system "mkdir -p #{File.dirname(address)}"
|
78
|
+
File.open(address, "w") do |f|
|
79
|
+
f.write read_from_remote(options)
|
80
|
+
end
|
81
|
+
puts "Cached #{address}"
|
82
|
+
read_from_cache(options)
|
83
|
+
end
|
84
|
+
|
85
|
+
def logger
|
86
|
+
RAILS_DEFAULT_LOGGER
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|