bahn.rb 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +24 -0
- data/lib/bahn/bahn_agent.rb +39 -22
- data/lib/bahn/bahn_route.rb +62 -66
- data/lib/bahn/bahn_routepart.rb +15 -1
- data/lib/bahn/bahn_station.rb +30 -0
- data/lib/bahn.rb +6 -3
- metadata +7 -6
- data/lib/bahn/version.rb +0 -3
data/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Load connections for public transportation from the m.bahn.de website.
|
2
|
+
|
3
|
+
This is under heavy development. Don't expect a working solution!
|
4
|
+
|
5
|
+
Example
|
6
|
+
=
|
7
|
+
require 'bahn.rb'
|
8
|
+
|
9
|
+
agent = Bahn::Agent.new
|
10
|
+
routes = agent.get_routes(
|
11
|
+
"Düsseldorf reisholz s bahn", # start address or station
|
12
|
+
"Düsseldorf, Heerdter Sandberg 40 ", # target address or station
|
13
|
+
:include_coords => true, # include coordinates of stations
|
14
|
+
:limit => 1, # how many connections?
|
15
|
+
:start_type => :station, # :station or :address
|
16
|
+
:target_type => :address # :station or :address
|
17
|
+
)
|
18
|
+
# output the connection info
|
19
|
+
routes.each {|route| route.parts.each {|part| puts part } }
|
20
|
+
|
21
|
+
How to help
|
22
|
+
=
|
23
|
+
Feel free to implement some additions, refactor and create a pull request!
|
24
|
+
... and create tests of course ;)
|
data/lib/bahn/bahn_agent.rb
CHANGED
@@ -8,7 +8,7 @@ module Bahn
|
|
8
8
|
# You can go even use far distances...
|
9
9
|
# routes = agent.get_routes "Heerdter Sandberg 35 Düsseldorf, Düsseldorf", "Berlin Hauptstraße 10"
|
10
10
|
# routes.each {|route| route.parts.each {|part| puts part } }
|
11
|
-
# => Am 2013-02-01 von 17:32 bis 17:33 : Düsseldorf - Oberkassel, Heerdter Sandberg
|
11
|
+
# => Am 2013-02-01 von 17:32 bis 17:33 : Düsseldorf - Oberkassel, Heerdter Sandberg 60 nach Heerdter Sandberg U, Düsseldorf via Fußweg
|
12
12
|
# => Am 2013-02-01 von 17:33 bis 17:47 : Heerdter Sandberg U, Düsseldorf nach Düsseldorf Hauptbahnhof via U 7
|
13
13
|
# => Am 2013-02-01 von 17:47 bis 17:53 : Düsseldorf Hauptbahnhof nach Düsseldorf Hbf via Fußweg
|
14
14
|
# => Am 2013-02-01 von 17:53 bis 22:22 : Düsseldorf Hbf nach Berlin Hbf via ICE 945
|
@@ -28,6 +28,12 @@ module Bahn
|
|
28
28
|
@@user_agent = val
|
29
29
|
end
|
30
30
|
|
31
|
+
TYPES =
|
32
|
+
{
|
33
|
+
:station => 1,
|
34
|
+
:address => 2
|
35
|
+
}.freeze
|
36
|
+
|
31
37
|
# Initialize a new Agent
|
32
38
|
# options:
|
33
39
|
# :user_agent => Set the user agent. Default: "bahn.rb"
|
@@ -41,41 +47,52 @@ module Bahn
|
|
41
47
|
# :start_type and :target_type should be the same, no other options is implemented yet
|
42
48
|
# Options:
|
43
49
|
# * :time => start time for the connection
|
44
|
-
# * :start_type =>
|
45
|
-
# * :target_type =>
|
50
|
+
# * :start_type => :station or :address
|
51
|
+
# * :target_type => :station or :address
|
52
|
+
# * :include_coords => Include coordiantes for the station. This takes a while especially for longer routes! default: true
|
46
53
|
# Returns:
|
47
54
|
# Array of Bahn::Route(s)
|
48
55
|
# Raises:
|
49
56
|
# "no_route" if no route could be found
|
50
57
|
def get_routes from, to, options = {}
|
51
|
-
options = {:time => Time.now, :start_type =>
|
58
|
+
options = {:time => Time.now, :start_type => :address, :target_type => :address, :depth => 0, :include_coords => true, :limit => 2}.merge(options)
|
59
|
+
options[:time] = options[:time] + 10.minutes # Ansonsten liegt die letzte Verbindung in der Vergangenheit
|
60
|
+
|
52
61
|
page = @agent.get @@options[:url_route]
|
53
62
|
form = page.forms.first
|
54
|
-
form["REQ0JourneyDate"] =
|
55
|
-
form["REQ0JourneyTime"] =
|
56
|
-
form["REQ0JourneyStopsS0A"] = options[:start_type]
|
57
|
-
form["REQ0JourneyStopsZ0A"] = options[:target_type]
|
63
|
+
form["REQ0JourneyDate"] = options[:time].strftime "%d.%m.%y"
|
64
|
+
form["REQ0JourneyTime"] = options[:time].to_formatted_s :time
|
65
|
+
form["REQ0JourneyStopsS0A"] = TYPES[options[:start_type]]
|
66
|
+
form["REQ0JourneyStopsZ0A"] = TYPES[options[:target_type]]
|
58
67
|
form["REQ0JourneyStopsS0G"] = from
|
59
|
-
form["REQ0JourneyStopsZ0G"] = to
|
68
|
+
form["REQ0JourneyStopsZ0G"] = to
|
69
|
+
form["REQ0JourneyProduct_prod_list"] = "4:0001111111000000"
|
60
70
|
result = form.submit(form.button_with(:value => "Suchen"))
|
61
71
|
|
62
|
-
type = :undefined
|
63
|
-
type = :door2door if options[:start_type] == 2 && options[:target_type] == 2
|
64
|
-
type = :station2station if options[:start_type] == 1 && options[:target_type] == 1
|
65
|
-
|
66
72
|
routes = []
|
67
73
|
links = result.links_with(:href => /details=opened!/)
|
68
|
-
links.each
|
69
|
-
page = link.click
|
70
|
-
routes << Route.new(page,
|
74
|
+
links.each do |link|
|
75
|
+
page = link.click
|
76
|
+
routes << Route.new(page, options)
|
77
|
+
break if routes.count == options[:limit]
|
71
78
|
end
|
72
79
|
|
73
80
|
# Keine Station gefunden und es werden keine Vorschläge angezeigt...
|
74
81
|
# also suchen wir nachder nächstbesten Adresse und nutzen dies
|
75
|
-
if links.count == 0 && options[:depth] == 0
|
76
|
-
|
77
|
-
|
78
|
-
|
82
|
+
if links.count == 0 && options[:depth] == 0
|
83
|
+
if options[:start_type] == :address
|
84
|
+
from = find_address(from).name
|
85
|
+
elsif options[:start_type] == :station
|
86
|
+
from = find_station(from).name
|
87
|
+
end
|
88
|
+
|
89
|
+
if options[:target_type] == :address
|
90
|
+
to = find_address(to).name
|
91
|
+
elsif options[:target_type] == :station
|
92
|
+
to = find_station(to).name
|
93
|
+
end
|
94
|
+
|
95
|
+
return get_routes from, to, options.merge(:depth => options[:depth]+1)
|
79
96
|
end
|
80
97
|
|
81
98
|
raise "no_route" if routes.count == 0 || links.count == 0
|
@@ -90,7 +107,7 @@ module Bahn
|
|
90
107
|
result = @agent.get("#{@@options[:uri_stations]}#{name}").body.gsub("SLs.sls=", "").gsub(";SLs.showSuggestion();", "")
|
91
108
|
# a Mechanize::File instead of a Page is returned so we have to convert manually
|
92
109
|
result = Iconv.conv("utf-8", "iso-8859-1", result)
|
93
|
-
|
110
|
+
Station.new(JSON.parse(result)["suggestions"].first)
|
94
111
|
end
|
95
112
|
|
96
113
|
# Finds the first usable address for the given parameter. The returned address can then be used for further processing in routes
|
@@ -101,7 +118,7 @@ module Bahn
|
|
101
118
|
result = @agent.get("#{@@options[:uri_adresses]}#{address}").body.gsub("SLs.sls=", "").gsub(";SLs.showSuggestion();", "")
|
102
119
|
# a Mechanize::File instead of a Page is returned so we have to convert manually
|
103
120
|
result = Iconv.conv("utf-8", "iso-8859-1", result)
|
104
|
-
JSON.parse(result)["suggestions"].first
|
121
|
+
Station.new(JSON.parse(result)["suggestions"].first)
|
105
122
|
end
|
106
123
|
end
|
107
124
|
end
|
data/lib/bahn/bahn_route.rb
CHANGED
@@ -4,40 +4,72 @@ module Bahn
|
|
4
4
|
# At the end you'll have a nice step by step navigation
|
5
5
|
# Feel free to refactor ;)
|
6
6
|
class Route
|
7
|
-
|
8
|
-
attr_accessor :price, :type, :parts
|
7
|
+
attr_accessor :price, :parts, :notes, :start_type, :target_type
|
9
8
|
|
10
9
|
# Initialize with a Mechanize::Page and a specific type
|
11
10
|
# The page should be the detail view of m.bahn.de
|
12
11
|
# Parameters:
|
13
12
|
# * page => Mechanize::Page
|
14
|
-
#
|
15
|
-
def initialize page,
|
13
|
+
# * type => :door2door or :station2station
|
14
|
+
def initialize page, options = {}
|
15
|
+
options = {:start_type => :address, :target_type => :address, :include_coords => true}.merge(options)
|
16
|
+
@do_load = options[:include_coords]
|
17
|
+
self.start_type = options[:start_type]
|
18
|
+
self.target_type = options[:target_type]
|
16
19
|
summary_time = page.search("//div[contains(@class, 'querysummary2')]").text.gsub("\n", " ").strip
|
17
20
|
html_parts = page.search("//div[contains(@class, 'haupt')]")
|
18
21
|
price = html_parts.pop.text
|
19
22
|
html_parts.pop
|
20
|
-
@type = type
|
21
23
|
|
22
24
|
route = ""
|
23
25
|
html_parts.each do |part|
|
24
26
|
text = part.text.strip
|
25
27
|
next if text.start_with? "Reiseprofil" # not important
|
28
|
+
if text.starts_with?("Hinweis", "Aktuelle Informationen")
|
29
|
+
self.notes = "#{self.notes}\n#{text}" if !text.include?("Start/Ziel mit äquivalentem Bahnhof ersetzt")
|
30
|
+
next
|
31
|
+
end
|
32
|
+
|
26
33
|
route << text << "\n"
|
27
34
|
end
|
28
|
-
route = route.split("\n")
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
route = route.split("\n")
|
36
|
+
|
37
|
+
idx = 3
|
38
|
+
@start = RoutePart.new
|
39
|
+
@target = RoutePart.new
|
40
|
+
if options[:start_type] == :address
|
41
|
+
@start.start = Station.new({"value" => route[0], :load => :foot, :do_load => @do_load})
|
42
|
+
@start.type = "Fußweg" # route[2]
|
43
|
+
@start.end_time = DateTime.parse(summary_time.split("-").first.gsub(".13", ".2013"))
|
44
|
+
@start.start_time = @start.end_time - route[1].to_i.minutes
|
45
|
+
@start.target = route[3]
|
46
|
+
elsif options[:start_type] == :station
|
47
|
+
@start.start = Station.new({"value" => route[0], :load => :station, :do_load => @do_load})
|
48
|
+
@start.start_time = DateTime.parse(@date.to_s + route[1])
|
49
|
+
@start.type = route[2]
|
50
|
+
@start.end_time = DateTime.parse(@date.to_s + route[3])
|
51
|
+
@start.target = Station.new({"value" => route[4], :load => :station, :do_load => @do_load})
|
52
|
+
idx = 4
|
53
|
+
end
|
54
|
+
|
55
|
+
@date = @start.start_time.to_date # otherwise all dates will be "today"
|
56
|
+
create_parts idx, route
|
57
|
+
if options[:target_type] == :station
|
58
|
+
@target = @parts.last
|
59
|
+
elsif options[:target_type] == :address
|
60
|
+
@target.type = "Fußweg"
|
61
|
+
@target.target = Station.new({"value" => route.last, :load => :foot, :do_load => @do_load})
|
62
|
+
if summary_time.split("-").last.strip.length != 5
|
63
|
+
# Date is included in the string
|
64
|
+
@target.end_time = DateTime.parse(summary_time.split("-").last.gsub(".13", ".2013"))
|
65
|
+
else
|
66
|
+
# no date given, use start date
|
67
|
+
@target.end_time = DateTime.parse("#{@start.start_time.to_date} #{summary_time.split("-").last}")
|
68
|
+
end
|
69
|
+
|
70
|
+
@target.end_time += route[route.length-3].to_i.minutes
|
71
|
+
@parts << @target
|
72
|
+
end
|
41
73
|
end
|
42
74
|
|
43
75
|
# Start time of the route
|
@@ -47,7 +79,7 @@ module Bahn
|
|
47
79
|
|
48
80
|
# End time of the route
|
49
81
|
def end_time
|
50
|
-
@parts.last.
|
82
|
+
@parts.last.end_time
|
51
83
|
end
|
52
84
|
|
53
85
|
# Starting point of the route
|
@@ -62,74 +94,33 @@ module Bahn
|
|
62
94
|
|
63
95
|
private
|
64
96
|
|
65
|
-
# Create the station 2 station route parts...
|
66
|
-
def create_station2station route
|
67
|
-
@start = RoutePart.new
|
68
|
-
@start.start = route[0]
|
69
|
-
@start.start_time = DateTime.parse(@date.to_s + route[1])
|
70
|
-
@start.type = route[2]
|
71
|
-
@start.end_time = DateTime.parse(@date.to_s + route[3])
|
72
|
-
@start.target = route[4]
|
73
|
-
|
74
|
-
@target = RoutePart.new # avoid nullpointer
|
75
|
-
|
76
|
-
@parts = [@start]
|
77
|
-
create_parts 0, route
|
78
|
-
@target = @parts.last
|
79
|
-
end
|
80
|
-
|
81
|
-
# Create the door 2 door route parts
|
82
|
-
def create_door2door route, summary_time
|
83
|
-
@start = RoutePart.new
|
84
|
-
@start.start = route[0]
|
85
|
-
@start.type = "Fußweg" # route[2]
|
86
|
-
@start.end_time = DateTime.parse(summary_time.split("-").first.gsub(".13", ".2013"))
|
87
|
-
@start.start_time = @start.end_time - route[1].to_i.minutes
|
88
|
-
@start.target = route[3]
|
89
|
-
|
90
|
-
@target = RoutePart.new
|
91
|
-
@target.type = "Fußweg"
|
92
|
-
@target.target = route.last
|
93
|
-
if summary_time.split("-").last.strip.length != 5
|
94
|
-
# Date is included in the string
|
95
|
-
@target.end_time = DateTime.parse(summary_time.split("-").last.gsub(".13", ".2013"))
|
96
|
-
else
|
97
|
-
# no date given, use start date
|
98
|
-
@target.end_time = DateTime.parse("#{@start.start_time.to_date} #{summary_time.split("-").last}")
|
99
|
-
end
|
100
|
-
|
101
|
-
@target.end_time += route[route.length-3].to_i.minutes
|
102
|
-
|
103
|
-
@date = (@start.start_time.to_date) # otherwise all dates will be "today"
|
104
|
-
@parts = [@start]
|
105
|
-
create_parts 3, route
|
106
|
-
@parts << @target
|
107
|
-
end
|
108
|
-
|
109
97
|
# Create all general parts.
|
110
98
|
# Set @parts, @target and @date first!
|
111
99
|
def create_parts start_index, route
|
100
|
+
@parts = [@start]
|
112
101
|
i = start_index
|
102
|
+
done_anything = false
|
113
103
|
while i < route.length do
|
114
104
|
if route[i..i+4].count != 5 || route[i..i+4].include?(nil)
|
115
105
|
break
|
116
106
|
end
|
117
107
|
|
108
|
+
done_anything = true
|
118
109
|
part = RoutePart.new
|
119
|
-
part.start = route[i]
|
110
|
+
part.start = Station.new({"value" => route[i], :load => :station, :do_load => @do_load})
|
120
111
|
@parts.last.target = part.start
|
121
112
|
part.type = route[i+2].squeeze
|
122
113
|
|
123
114
|
begin
|
124
115
|
part.start_time = DateTime.parse(@date.to_s + route[i+1])
|
125
116
|
part.end_time = DateTime.parse(@date.to_s + route[i+3])
|
126
|
-
part.target = route[i+4]
|
117
|
+
part.target = Station.new({"value" => route[i+4], :load => :station, :do_load => @do_load})
|
127
118
|
i += 4
|
128
119
|
rescue ArgumentError
|
129
120
|
# occures if there is a "Fußweg" in between
|
130
121
|
part.start_time = @parts.last.end_time
|
131
122
|
part.end_time = part.start_time + route[i+1].to_i.minutes
|
132
|
-
part.target = route[i+3]
|
123
|
+
part.target = Station.new({"value" => route[i+3], :load => :foot, :do_load => @do_load})
|
133
124
|
i += 3
|
134
125
|
end
|
135
126
|
|
@@ -142,6 +133,11 @@ module Bahn
|
|
142
133
|
# we don't want to show Fußwege from and to the same station
|
143
134
|
@parts << part unless part.start == part.target
|
144
135
|
end
|
136
|
+
|
137
|
+
unless done_anything
|
138
|
+
@target.start_time = @parts.last.end_time
|
139
|
+
@target.start = @parts.last.start
|
140
|
+
end
|
145
141
|
end
|
146
142
|
end
|
147
143
|
end
|
data/lib/bahn/bahn_routepart.rb
CHANGED
@@ -7,12 +7,26 @@ module Bahn
|
|
7
7
|
# Return a nicely formatted route
|
8
8
|
# Raises errors if not everything is set properly
|
9
9
|
def to_s
|
10
|
-
"Am #{start_time.to_date} von #{start_time.
|
10
|
+
"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}"
|
11
11
|
end
|
12
12
|
|
13
13
|
# Set the type, e.g. Fußweg
|
14
14
|
def type= val
|
15
15
|
@type = val.squeeze(" ")
|
16
16
|
end
|
17
|
+
|
18
|
+
def transport_type
|
19
|
+
short_type = self.type.split.first.downcase
|
20
|
+
if ["str", "u", "s", "re", "erb", "ic", "ice"].include? short_type
|
21
|
+
return :train
|
22
|
+
elsif ["bus", "ne"].include? short_type
|
23
|
+
return :bus
|
24
|
+
elsif "Fußweg" == short_type
|
25
|
+
return :foot
|
26
|
+
end
|
27
|
+
|
28
|
+
# nothing else works
|
29
|
+
self.type
|
30
|
+
end
|
17
31
|
end
|
18
32
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Bahn
|
2
|
+
class Station
|
3
|
+
attr_accessor :lat, :lon, :name
|
4
|
+
def initialize json={}
|
5
|
+
self.name = json["value"] unless json["value"].nil?
|
6
|
+
|
7
|
+
if json[:do_load]
|
8
|
+
station = Agent.new.find_station(name) if json[:load] == :station
|
9
|
+
station = Agent.new.find_address(name) if json[:load] == :foot
|
10
|
+
end
|
11
|
+
|
12
|
+
if station.nil?
|
13
|
+
self.lat = json["ycoord"].insert(-7, ".") unless json["ycoord"].nil?
|
14
|
+
self.lon = json["xcoord"].insert(-7, ".") unless json["xcoord"].nil?
|
15
|
+
else
|
16
|
+
self.lat = station.lat
|
17
|
+
self.lon = station.lon
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"#{self.name} (#{self.lat},#{self.lon})"
|
23
|
+
end
|
24
|
+
|
25
|
+
def == other
|
26
|
+
return false if other.nil?
|
27
|
+
other.name == self.name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/bahn.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'mechanize'
|
3
3
|
require 'active_support/all'
|
4
|
-
require 'action_view'
|
5
4
|
require 'json'
|
6
5
|
|
7
|
-
|
8
6
|
require 'bahn/bahn_routepart'
|
9
7
|
require 'bahn/bahn_route'
|
10
|
-
require 'bahn/
|
8
|
+
require 'bahn/bahn_station'
|
9
|
+
require 'bahn/bahn_agent'
|
10
|
+
|
11
|
+
module Bahn
|
12
|
+
VERSION = "2.0.0"
|
13
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bahn.rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
|
-
-
|
7
|
+
- 2
|
8
8
|
- 0
|
9
9
|
- 0
|
10
|
-
version:
|
10
|
+
version: 2.0.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Simon Woker
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2013-02-
|
18
|
+
date: 2013-02-20 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
version_requirements: &id001 !ruby/object:Gem::Requirement
|
@@ -86,10 +86,11 @@ files:
|
|
86
86
|
- lib/bahn/bahn_agent.rb
|
87
87
|
- lib/bahn/bahn_route.rb
|
88
88
|
- lib/bahn/bahn_routepart.rb
|
89
|
-
- lib/bahn/
|
89
|
+
- lib/bahn/bahn_station.rb
|
90
90
|
- lib/bahn.rb
|
91
91
|
- LICENSE
|
92
|
-
|
92
|
+
- README.md
|
93
|
+
homepage: https://github.com/swoker/bahn.rb
|
93
94
|
licenses: []
|
94
95
|
|
95
96
|
post_install_message:
|
data/lib/bahn/version.rb
DELETED