octranspo 0.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/.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
|