bahn.rb 2.1.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,145 +1,208 @@
1
- # encoding: utf-8
2
-
3
- module Bahn
4
- # The whole Route from A to B.
5
- # This is created from the m.bahn.de detail view page and parses the given data.
6
- # At the end you'll have a nice step by step navigation
7
- # Feel free to refactor ;)
8
- class Route
9
- attr_accessor :price, :parts, :notes, :start_type, :target_type
10
-
11
- # Initialize with a Mechanize::Page and a specific type
12
- # The page should be the detail view of m.bahn.de
13
- # Parameters:
14
- # * page => Mechanize::Page
15
- # * type => :door2door or :station2station
16
- def initialize page, options = {}
17
- options = {:start_type => :address, :target_type => :address, :include_coords => true}.merge(options)
18
- @do_load = options[:include_coords]
19
- self.start_type = options[:start_type]
20
- self.target_type = options[:target_type]
21
- summary_time = page.search("//div[contains(@class, 'querysummary2')]").text.gsub("\n", " ").strip
22
- html_parts = page.search("//div[contains(@class, 'haupt')]")
23
- price = html_parts.pop.text
24
- html_parts.pop
25
-
26
- route = ""
27
- html_parts.each do |part|
28
- text = part.text.strip
29
- next if text.start_with? "Reiseprofil" # not important
30
- if text.starts_with?("Hinweis", "Aktuelle Informationen")
31
- self.notes = "#{self.notes}\n#{text}" if !text.include?("Start/Ziel mit äquivalentem Bahnhof ersetzt")
32
- next
33
- end
34
-
35
- route << text << "\n"
36
- end
37
- route = route.split("\n")
38
-
39
- idx = 3
40
- @start = RoutePart.new
41
- @target = RoutePart.new
42
- if options[:start_type] == :address
43
- @start.start = Station.new({"value" => route[0], :load => :foot, :do_load => @do_load})
44
- @start.type = "Fußweg" # route[2]
45
- @start.end_time = DateTime.parse(summary_time.split("-").first.gsub(".13", ".2013"))
46
- @start.start_time = @start.end_time - route[1].to_i.minutes
47
- @start.target = route[3]
48
- elsif options[:start_type] == :station
49
- @start.start = Station.new({"value" => route[0], :load => :station, :do_load => @do_load})
50
- @start.start_time = DateTime.parse(@date.to_s + route[1])
51
- @start.type = route[2]
52
- @start.end_time = DateTime.parse(@date.to_s + route[3])
53
- @start.target = Station.new({"value" => route[4], :load => :station, :do_load => @do_load})
54
- idx = 4
55
- end
56
-
57
- @date = @start.start_time.to_date # otherwise all dates will be "today"
58
- create_parts idx, route
59
- if options[:target_type] == :station
60
- @target = @parts.last
61
- elsif options[:target_type] == :address
62
- @target.type = "Fußweg"
63
- @target.target = Station.new({"value" => route.last, :load => :foot, :do_load => @do_load})
64
- if summary_time.split("-").last.strip.length != 5
65
- # Date is included in the string
66
- @target.end_time = DateTime.parse(summary_time.split("-").last.gsub(".13", ".2013"))
67
- else
68
- # no date given, use start date
69
- @target.end_time = DateTime.parse("#{@start.start_time.to_date} #{summary_time.split("-").last}")
70
- end
71
-
72
- @target.end_time += route[route.length-3].to_i.minutes
73
- @parts << @target
74
- end
75
- end
76
-
77
- # Start time of the route
78
- def start_time
79
- @parts.first.start_time
80
- end
81
-
82
- # End time of the route
83
- def end_time
84
- @parts.last.end_time
85
- end
86
-
87
- # Starting point of the route
88
- def start
89
- @parts.first.start
90
- end
91
-
92
- # Target point of the route
93
- def target
94
- @parts.last.target
95
- end
96
-
97
- private
98
-
99
- # Create all general parts.
100
- # Set @parts, @target and @date first!
101
- def create_parts start_index, route
102
- @parts = [@start]
103
- i = start_index
104
- done_anything = false
105
- while i < route.length do
106
- if route[i..i+4].count != 5 || route[i..i+4].include?(nil)
107
- break
108
- end
109
-
110
- done_anything = true
111
- part = RoutePart.new
112
- part.start = Station.new({"value" => route[i], :load => :station, :do_load => @do_load})
113
- @parts.last.target = part.start
114
- part.type = route[i+2].squeeze
115
-
116
- begin
117
- part.start_time = DateTime.parse(@date.to_s + route[i+1])
118
- part.end_time = DateTime.parse(@date.to_s + route[i+3])
119
- part.target = Station.new({"value" => route[i+4], :load => :station, :do_load => @do_load})
120
- i += 4
121
- rescue ArgumentError
122
- # occures if there is a "Fußweg" in between
123
- part.start_time = @parts.last.end_time
124
- part.end_time = part.start_time + route[i+1].to_i.minutes
125
- part.target = Station.new({"value" => route[i+3], :load => :foot, :do_load => @do_load})
126
- i += 3
127
- end
128
-
129
- part.end_time += 1.day if part.end_time.hour < @start.start_time.hour
130
- part.start_time += 1.day if part.start_time.hour < @start.start_time.hour
131
-
132
- @target.start_time = part.end_time
133
- @target.start = part.target
134
-
135
- # we don't want to show Fußwege from and to the same station
136
- @parts << part unless part.start == part.target
137
- end
138
-
139
- unless done_anything
140
- @target.start_time = @parts.last.end_time
141
- @target.start = @parts.last.start
142
- end
143
- end
144
- end
145
- end
1
+ # encoding: utf-8
2
+
3
+ module Bahn
4
+ # The whole Route from A to B.
5
+ # This is created from the m.bahn.de detail view page and parses the given data.
6
+ # At the end you'll have a nice step by step navigation
7
+ # Feel free to refactor ;)
8
+ class Route
9
+ attr_accessor :parts, :notes, :start_type, :target_type, :price
10
+
11
+ # Initialize with a Mechanize::Page and a specific type
12
+ # The page should be the detail view of m.bahn.de
13
+ # Parameters:
14
+ # * page => Mechanize::Page
15
+ # * type => :door2door or :station2station
16
+ def initialize page, options = {}
17
+ options = {:start_type => :address, :target_type => :address, :include_coords => true}.merge(options)
18
+ @do_load = options[:include_coords]
19
+ self.start_type = options[:start_type]
20
+ self.target_type = options[:target_type]
21
+ self.notes = Array.new
22
+ summary_time = page.search("//div[contains(@class, 'querysummary2')]").text.strip
23
+
24
+ # we'll add it for now...
25
+ #includes = ["Einfache Fahrt", "Preisinformationen", "Weitere Informationen", "Start/Ziel mit äquivalentem Bahnhof ersetzt"]
26
+ #start_withs = ["Reiseprofil", "Hinweis", "Aktuelle Informationen"]
27
+ notes = Array.new
28
+ #notes << page.search("//div[contains(@class, 'haupt rline')]").map(&:text).map(&:strip)
29
+ notes << page.search("//div[contains(@class, 'red bold haupt')]").map(&:text).map(&:strip)
30
+ notes.each do |note|
31
+ self.notes << note if note.size > 0
32
+ end
33
+
34
+ self.price = parse_price(page.search("//div[contains(@class, 'formular')]").map(&:text).map(&:strip))
35
+
36
+ change = page.search("//div[contains(@class, 'routeStart')]")
37
+ name = station_to_name change
38
+
39
+ type = page.search("//div[contains(@class, 'routeStart')]/following::*[1]").text.strip
40
+ last_lines = get_lines(change)
41
+
42
+ part = RoutePart.new
43
+ part.type = type
44
+ part.start_time = parse_date(summary_time.split("\n")[0...2].join(" "))
45
+ part.start_time -= last_lines.last.to_i.minutes if options[:start_type] == :address
46
+ part.start_delay = parse_delay(summary_time.split("\n")[0...2].join(" "))
47
+ part.start = Station.new({"value" => name, :load => options[:start_type] == :address ? :foot : :station, :do_load => @do_load})
48
+ part.platform_start = parse_platform(last_lines.last)
49
+
50
+ @parts = [part]
51
+
52
+ page.search("//div[contains(@class, 'routeChange')]").each_with_index do |change, idx|
53
+ part = RoutePart.new
54
+ name = station_to_name change
55
+ type = page.search("//div[contains(@class, 'routeChange')][#{idx+1}]/following::*[1]").text.strip
56
+ lines = change.text.split("\n")
57
+
58
+ part.type = type
59
+ part.start = Station.new({"value" => name, :load => :station, :do_load => @do_load})
60
+
61
+ lines = get_lines(change)
62
+ if lines.last.start_with?("ab")
63
+ part.start_time = parse_date(lines.last)
64
+ part.start_delay = parse_delay(lines.last)
65
+ part.platform_start = parse_platform(lines.last)
66
+ unless lines.first.starts_with?("an")
67
+ @parts.last.end_time = @parts.last.start_time + last_lines.last.to_i.minutes
68
+ end
69
+ end
70
+
71
+ if lines.first.starts_with?("an")
72
+ @parts.last.end_time = parse_date(lines.first)
73
+ @parts.last.target_delay = parse_delay(lines.first)
74
+ @parts.last.platform_target = parse_platform(lines.first)
75
+ unless lines.last.start_with?("ab")
76
+ # Fußweg for part
77
+ part.start_time = @parts.last.end_time
78
+ end
79
+ end
80
+
81
+ last_lines = lines
82
+
83
+ @parts.last.target = part.start
84
+ @parts << part #unless @parts.last.start == part.start
85
+ end
86
+
87
+
88
+ change = page.search("//div[contains(@class, 'routeEnd')]")
89
+ name = station_to_name change
90
+ @parts.last.target = Station.new({"value" => name, :load => options[:target_type] == :address ? :foot : :station, :do_load => @do_load})
91
+ lines = get_lines(change)
92
+
93
+ if lines.first.starts_with?("an")
94
+ @parts.last.end_time = parse_date(lines.first)
95
+ @parts.last.target_delay = parse_delay(lines.first)
96
+ @parts.last.platform_target = parse_platform(lines.first)
97
+ else
98
+ @parts.last.end_time = @parts.last.start_time + last_lines.last.to_i.minutes
99
+ end
100
+
101
+
102
+ end
103
+
104
+ # Start time of the route
105
+ def start_time
106
+ @parts.first.start_time
107
+ end
108
+
109
+ # End time of the route
110
+ def end_time
111
+ @parts.last.end_time
112
+ end
113
+
114
+ # Duration of a route
115
+ def duration
116
+ end_time_plus_delay = end_time + @parts.last.target_delay
117
+ duration_in_s = end_time_plus_delay.to_i - start_time.to_i
118
+ return duration_in_s
119
+ end
120
+
121
+ # Starting point of the route
122
+ def start
123
+ @parts.first.start
124
+ end
125
+
126
+ # Target point of the route
127
+ def target
128
+ @parts.last.target
129
+ end
130
+
131
+ def has_delay?
132
+ @parts.any? { |part| (part.start_delay > 0 || part.target_delay > 0) }
133
+ end
134
+
135
+ private
136
+
137
+ def station_to_name change
138
+ change.search("span").select{|s| s.attributes["class"].value != "red"}.inject(" "){|r, s| r << s.text}.strip.gsub(/\+\d+/, "")
139
+ end
140
+
141
+ def get_lines change
142
+ change.text.split("\n").reject{|s| s.to_s.length == 0}
143
+ end
144
+
145
+ def parse_price(to_parse)
146
+ return Array.new unless to_parse.first.match("EUR")
147
+ tags = to_parse.uniq.map { |p| p.split(/\d+\,\d{1,2}.EUR/) }.flatten
148
+ tags.map! { |t| t.gsub(/\p{Space}/, " ").strip } # remove ugly whitespaces: ruby >= 1.9.3
149
+ prices = to_parse.uniq.map { |p| p.scan(/\d+\,\d{1,2}.EUR/) }.flatten.map { |p| p.gsub(",",".").to_f }
150
+ price_information = Array.new
151
+ (0...prices.size).each do |idx|
152
+ price_information << {
153
+ :price => prices[idx],
154
+ :class => tags[idx+1].scan(/\d\.\sKlasse/).first.scan(/\d/).first,
155
+ :details => tags[idx+1].split(/\d\.\sKlasse/).last
156
+ }
157
+ end
158
+
159
+ return price_information
160
+ end
161
+
162
+ def parse_platform(to_parse)
163
+ return to_parse.split("Gl.").last.strip if to_parse.match("Gl.")
164
+ return nil
165
+ end
166
+
167
+ def parse_delay(to_parse)
168
+ to_parse = to_parse.split("+") # + sign indicates delay information
169
+
170
+ if to_parse.size > 1 # extract delay information
171
+ delay_information = to_parse.last.split.first.to_i
172
+ else
173
+ delay_information = 0
174
+ end
175
+ return delay_information
176
+ end
177
+
178
+ def parse_date to_parse
179
+ to_parse = to_parse.split("+") # clears time errors e.g.: "an 18:01 +4 Gl. 17"
180
+
181
+ # fix number of year digits from 2 to 4
182
+ to_parse = to_parse.first.gsub(".#{DateTime.now.year.to_s[2..4]} ", ".#{DateTime.now.year.to_s} ")
183
+
184
+ # fix missing year information in route parts (interesting for
185
+ # past or future connections)
186
+ unless to_parse.match(/\d{1,2}\.\d{1,2}\.\d{4}/)
187
+ tmp_date = DateTime.parse(to_parse[/\d{1,2}:\d{1,2}/])
188
+
189
+ tmp_date = DateTime.new(self.start_time.year,
190
+ self.start_time.month,
191
+ self.start_time.day,
192
+ tmp_date.hour,
193
+ tmp_date.min,
194
+ tmp_date.sec)
195
+
196
+ tmp_date = tmp_date +1 if tmp_date < self.start_time
197
+ to_parse = tmp_date.to_s
198
+ end
199
+
200
+ to_parse = DateTime.parse(to_parse).to_s
201
+
202
+ # fix timezone
203
+ time_zone = DateTime.now.in_time_zone("Berlin").strftime("%z")
204
+ to_parse = to_parse.gsub("+00:00", time_zone).gsub("+0000", time_zone)
205
+ return DateTime.parse(to_parse)
206
+ end
207
+ end
208
+ end
@@ -1,34 +1,55 @@
1
- # encoding: utf-8
2
-
3
- module Bahn
4
- # Route Parts show a small step of the route from A to B with one specific type of transportation
5
- # Example: "Am 2013-02-01 von 17:33 bis 17:47 : Heerdter Sandberg U, Düsseldorf nach Düsseldorf Hauptbahnhof via U 7"
6
- class RoutePart
7
- attr_accessor :start, :target, :type, :price, :start_time, :end_time
8
-
9
- # Return a nicely formatted route
10
- # Raises errors if not everything is set properly
11
- def to_s
12
- "Am #{start_time.to_date} von #{start_time.to_formatted_s :time} bis #{end_time.to_date.to_s + ' ' if end_time.to_date != start_time.to_date}#{end_time.to_formatted_s :time} : #{start} nach #{target} via #{type}"
13
- end
14
-
15
- # Set the type, e.g. Fußweg
16
- def type= val
17
- @type = val.squeeze(" ")
18
- end
19
-
20
- def transport_type
21
- short_type = self.type.split.first.downcase
22
- if ["str", "u", "s", "re", "erb", "ic", "ice"].include? short_type
23
- return :train
24
- elsif ["bus", "ne"].include? short_type
25
- return :bus
26
- elsif "Fußweg" == short_type
27
- return :foot
28
- end
29
-
30
- # nothing else works
31
- self.type
32
- end
33
- end
34
- end
1
+ # encoding: utf-8
2
+
3
+ module Bahn
4
+ # Route Parts show a small step of the route from A to B with one specific type of transportation
5
+ # Example: "Am 2013-02-01 von 17:33 bis 17:47 : Heerdter Sandberg U, Düsseldorf nach Düsseldorf Hauptbahnhof via U 7"
6
+ class RoutePart
7
+ attr_accessor :start, :target, :type, :platform_start, :platform_target, :price, :start_time, :end_time, :start_delay, :target_delay
8
+
9
+ def initialize
10
+ @start_delay = 0
11
+ @target_delay = 0
12
+ end
13
+
14
+ # Return a nicely formatted route
15
+ # Raises errors if not everything is set properly
16
+ def to_s
17
+ "Am %s von %s%s bis %s%s: %s (Gl. %s) nach %s (Gl. %s) via %s" %
18
+ [ start_time.to_date,
19
+ start_time.to_formatted_s(:time),
20
+ (start_delay > 0 ? " (+%i)" % start_delay : ""),
21
+ (end_time.to_date != start_time.to_date ? end_time.to_date.to_s + ' ' : "") + end_time.to_formatted_s(:time),
22
+ target_delay > 0 ? " (+%i)" % target_delay : "",
23
+ start.name, platform_start, target.name, platform_target, type]
24
+ end
25
+
26
+ # Set the type, e.g. Fußweg
27
+ def type= val
28
+ @type = val.squeeze(" ")
29
+ end
30
+
31
+ def transport_type
32
+ short_type = self.type.split.first.downcase
33
+ if ["str", "u", "s", "re", "erb", "ic", "ice"].include? short_type
34
+ return :train
35
+ elsif ["bus", "ne"].include? short_type
36
+ return :bus
37
+ elsif "Fußweg" == short_type
38
+ return :foot
39
+ end
40
+
41
+ # nothing else works
42
+ self.type
43
+ end
44
+
45
+ def ==(rp)
46
+ self.start == rp.start &&
47
+ self.target == rp.target &&
48
+ self.type == rp.type &&
49
+ self.platform_start == rp.platform_start &&
50
+ self.platform_target == rp.platform_target &&
51
+ self.start_time == rp.start_time &&
52
+ self.end_time == rp.end_time
53
+ end
54
+ end
55
+ end
@@ -1,36 +1,50 @@
1
- module Bahn
2
- class Station
3
- attr_accessor :lat, :lon, :distance, :name
4
-
5
- def initialize json={}
6
- self.name = json["value"] unless json["value"].nil?
7
-
8
- if json[:do_load]
9
- station = Agent.new.find_station(name) if json[:load] == :station
10
- station = Agent.new.find_address(name) if json[:load] == :foot
11
- end
12
-
13
- if station.nil?
14
- self.lat = json["ycoord"].insert(-7, ".") unless json["ycoord"].nil?
15
- self.lon = json["xcoord"].insert(-7, ".") unless json["xcoord"].nil?
16
- else
17
- self.lat = station.lat
18
- self.lon = station.lon
19
- end
20
- end
21
-
22
- def to_s
23
- "#{self.name} (#{self.lat},#{self.lon})"
24
- end
25
-
26
- def to_coordinates
27
- [lat, lon]
28
- end
29
- alias_method :coordinates, :to_coordinates
30
-
31
- def == other
32
- return false if other.nil?
33
- other.name == self.name
34
- end
35
- end
36
- end
1
+ module Bahn
2
+ class Station
3
+ attr_accessor :lat, :lon, :distance, :name, :station_type
4
+
5
+ def initialize json={}
6
+ self.name = json["value"].to_s.gsub("(S-Bahn)", "(S)") unless json["value"].nil?
7
+
8
+ if json[:do_load]
9
+ station = Agent.new.find_station(name) if json[:load] == :station
10
+ station = Agent.new.find_address(name) if json[:load] == :foot
11
+ end
12
+
13
+ if station.nil?
14
+ self.lat = json["ycoord"].insert(-7, ".") unless json["ycoord"].nil?
15
+ self.lon = json["xcoord"].insert(-7, ".") unless json["xcoord"].nil?
16
+ else
17
+ self.lat = station.lat
18
+ self.lon = station.lon
19
+ end
20
+ end
21
+
22
+ def to_s
23
+ return "#{self.name}" if (self.lat.nil? || self.lon.nil?)
24
+ return "#{self.name} (#{self.lat},#{self.lon})"
25
+ end
26
+
27
+ def to_coordinates
28
+ [lat, lon]
29
+ end
30
+ alias_method :coordinates, :to_coordinates
31
+
32
+ def == other
33
+ return false if other.nil?
34
+ remove_parenthesis(other.name) == remove_parenthesis(name)
35
+ end
36
+
37
+
38
+ #############################################
39
+ private
40
+ #############################################
41
+
42
+ # Often we have stations like "Berlin Gesundbrunnen (S)" and "Berlin Gesundbrunnen"
43
+ # we want these stations to be different
44
+ def remove_parenthesis string
45
+ x = string.dup
46
+ while x.gsub!(/\([^()]*\)/,""); end
47
+ x.strip
48
+ end
49
+ end
50
+ end