bahn.rb 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +7 -0
- data/lib/bahn/bahn_agent.rb +107 -0
- data/lib/bahn/bahn_route.rb +147 -0
- data/lib/bahn/bahn_routepart.rb +18 -0
- data/lib/bahn/version.rb +3 -0
- data/lib/bahn.rb +10 -0
- metadata +128 -0
data/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2012 Simon Woker
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Bahn
|
2
|
+
# Agent class that searches stations, street addresses and public transportation routes for germany
|
3
|
+
# Example:
|
4
|
+
# agent = Agent.new
|
5
|
+
# routes = agent.get_routes "Düsseldorf Heerdter Sandberg 25", "Düsseldorf Am Dreieck 1"
|
6
|
+
# routes.each {|route| route.parts.each {|part| puts part } }
|
7
|
+
#
|
8
|
+
# You can go even use far distances...
|
9
|
+
# routes = agent.get_routes "Heerdter Sandberg 35 Düsseldorf, Düsseldorf", "Berlin Hauptstraße 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 35 nach Heerdter Sandberg U, Düsseldorf via Fußweg
|
12
|
+
# => Am 2013-02-01 von 17:33 bis 17:47 : Heerdter Sandberg U, Düsseldorf nach Düsseldorf Hauptbahnhof via U 7
|
13
|
+
# => Am 2013-02-01 von 17:47 bis 17:53 : Düsseldorf Hauptbahnhof nach Düsseldorf Hbf via Fußweg
|
14
|
+
# => Am 2013-02-01 von 17:53 bis 22:22 : Düsseldorf Hbf nach Berlin Hbf via ICE 945
|
15
|
+
# => Am 2013-02-01 von 22:34 bis 22:40 : Berlin Hbf nach Berlin Alexanderplatz via RE 37386
|
16
|
+
# => Am 2013-02-01 von 22:40 bis 22:46 : Berlin Alexanderplatz nach Alexanderplatz (U), Berlin via Fußweg
|
17
|
+
# => Am 2013-02-01 von 22:46 bis 23:07 : Alexanderplatz (U), Berlin nach Oberseestr., Berlin via STR M5
|
18
|
+
# => Am 2013-02-01 von 23:07 bis 23:15 : Oberseestr., Berlin nach Berlin - Alt-Hohenschönhausen, Hauptstraße 10 via Fußweg
|
19
|
+
class Agent
|
20
|
+
@@options = {
|
21
|
+
:url_route => 'http://mobile.bahn.de/bin/mobil/query.exe/dox?country=DEU&rt=1&use_realtime_filter=1&searchMode=ADVANCED',
|
22
|
+
:uri_adresses => 'http://reiseauskunft.bahn.de/bin/ajax-getstop.exe/en?REQ0JourneyStopsS0A=2&REQ0JourneyStopsS0G=',
|
23
|
+
:uri_stations => 'http://reiseauskunft.bahn.de/bin/ajax-getstop.exe/en?REQ0JourneyStopsS0A=1&REQ0JourneyStopsS0G='
|
24
|
+
}
|
25
|
+
|
26
|
+
# Set the used user agent
|
27
|
+
def self.user_agent=val
|
28
|
+
@@user_agent = val
|
29
|
+
end
|
30
|
+
|
31
|
+
# Initialize a new Agent
|
32
|
+
# options:
|
33
|
+
# :user_agent => Set the user agent. Default: "bahn.rb"
|
34
|
+
def initialize
|
35
|
+
@agent = Mechanize.new
|
36
|
+
@agent.user_agent = @@user_agent ||= "bahn.rb"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get the next few routes with public transportation from A to B.
|
40
|
+
#
|
41
|
+
# :start_type and :target_type should be the same, no other options is implemented yet
|
42
|
+
# Options:
|
43
|
+
# * :time => start time for the connection
|
44
|
+
# * :start_type => 1 = station, 2 = address
|
45
|
+
# * :target_type => 1 = station, 2 = address
|
46
|
+
# Returns:
|
47
|
+
# Array of Bahn::Route(s)
|
48
|
+
# Raises:
|
49
|
+
# "no_route" if no route could be found
|
50
|
+
def get_routes from, to, options = {}
|
51
|
+
options = {:time => Time.now, :start_type => 2, :target_type => 2, :depth => 0}.merge(options)
|
52
|
+
page = @agent.get @@options[:url_route]
|
53
|
+
form = page.forms.first
|
54
|
+
form["REQ0JourneyDate"] = "#{options[:time].day}.#{options[:time].month}.#{options[:time].year-2000}"
|
55
|
+
form["REQ0JourneyTime"] = "#{options[:time].hour}:#{options[:time].min}"
|
56
|
+
form["REQ0JourneyStopsS0A"] = options[:start_type]
|
57
|
+
form["REQ0JourneyStopsZ0A"] = options[:target_type]
|
58
|
+
form["REQ0JourneyStopsS0G"] = from
|
59
|
+
form["REQ0JourneyStopsZ0G"] = to
|
60
|
+
result = form.submit(form.button_with(:value => "Suchen"))
|
61
|
+
|
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
|
+
routes = []
|
67
|
+
links = result.links_with(:href => /details=opened!/)
|
68
|
+
links.each do |link|
|
69
|
+
page = link.click
|
70
|
+
routes << Route.new(page, type)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Keine Station gefunden und es werden keine Vorschläge angezeigt...
|
74
|
+
# also suchen wir nachder nächstbesten Adresse und nutzen dies
|
75
|
+
if links.count == 0 && options[:depth] == 0 && type == :door2door
|
76
|
+
from = find_address from
|
77
|
+
to = find_address to
|
78
|
+
return get_routes from, to, {:time => options[:time], :depth => options[:depth]+1}
|
79
|
+
end
|
80
|
+
|
81
|
+
raise "no_route" if routes.count == 0 || links.count == 0
|
82
|
+
routes
|
83
|
+
end
|
84
|
+
|
85
|
+
# Find the first best station by name
|
86
|
+
# Example:
|
87
|
+
# Input: HH Allee Düsseldorf
|
88
|
+
# Output: Heinrich-Heine-Allee U, Düsseldorf
|
89
|
+
def find_station name
|
90
|
+
result = @agent.get("#{@@options[:uri_stations]}#{name}").body.gsub("SLs.sls=", "").gsub(";SLs.showSuggestion();", "")
|
91
|
+
# a Mechanize::File instead of a Page is returned so we have to convert manually
|
92
|
+
result = Iconv.conv("utf-8", "iso-8859-1", result)
|
93
|
+
r = JSON.parse(result)["suggestions"].first["value"]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Finds the first usable address for the given parameter. The returned address can then be used for further processing in routes
|
97
|
+
# Example:
|
98
|
+
# Input: Roßstr. 41 40476 Düsseldorf
|
99
|
+
# Output: Düsseldorf - Golzheim, Rossstraße 41
|
100
|
+
def find_address address
|
101
|
+
result = @agent.get("#{@@options[:uri_adresses]}#{address}").body.gsub("SLs.sls=", "").gsub(";SLs.showSuggestion();", "")
|
102
|
+
# a Mechanize::File instead of a Page is returned so we have to convert manually
|
103
|
+
result = Iconv.conv("utf-8", "iso-8859-1", result)
|
104
|
+
JSON.parse(result)["suggestions"].first["value"]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Bahn
|
2
|
+
# The whole Route from A to B.
|
3
|
+
# This is created from the m.bahn.de detail view page and parses the given data.
|
4
|
+
# At the end you'll have a nice step by step navigation
|
5
|
+
# Feel free to refactor ;)
|
6
|
+
class Route
|
7
|
+
include ActionView::Helpers::DateHelper
|
8
|
+
attr_accessor :price, :type, :parts
|
9
|
+
|
10
|
+
# Initialize with a Mechanize::Page and a specific type
|
11
|
+
# The page should be the detail view of m.bahn.de
|
12
|
+
# Parameters:
|
13
|
+
# * page => Mechanize::Page
|
14
|
+
# * type => :door2door or :station2station
|
15
|
+
def initialize page, type
|
16
|
+
summary_time = page.search("//div[contains(@class, 'querysummary2')]").text.gsub("\n", " ").strip
|
17
|
+
html_parts = page.search("//div[contains(@class, 'haupt')]")
|
18
|
+
price = html_parts.pop.text
|
19
|
+
html_parts.pop
|
20
|
+
@type = type
|
21
|
+
|
22
|
+
route = ""
|
23
|
+
html_parts.each do |part|
|
24
|
+
text = part.text.strip
|
25
|
+
next if text.start_with? "Reiseprofil" # not important
|
26
|
+
route << text << "\n"
|
27
|
+
end
|
28
|
+
route = route.split("\n")
|
29
|
+
create_door2door route, summary_time if @type == :door2door
|
30
|
+
create_station2station route if @type == :station2station
|
31
|
+
end
|
32
|
+
|
33
|
+
# Start time from now in words
|
34
|
+
def start_time_from_now
|
35
|
+
distance_of_time_in_words DateTime.now, @parts.first.start_time
|
36
|
+
end
|
37
|
+
|
38
|
+
# End time from now in words
|
39
|
+
def end_time_from_now
|
40
|
+
distance_of_time_in_words DateTime.now, @parts.last.start_time
|
41
|
+
end
|
42
|
+
|
43
|
+
# Start time of the route
|
44
|
+
def start_time
|
45
|
+
@parts.first.start_time
|
46
|
+
end
|
47
|
+
|
48
|
+
# End time of the route
|
49
|
+
def end_time
|
50
|
+
@parts.last.start_time
|
51
|
+
end
|
52
|
+
|
53
|
+
# Starting point of the route
|
54
|
+
def start
|
55
|
+
@parts.first.start
|
56
|
+
end
|
57
|
+
|
58
|
+
# Target point of the route
|
59
|
+
def target
|
60
|
+
@parts.last.target
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
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
|
+
# Create all general parts.
|
110
|
+
# Set @parts, @target and @date first!
|
111
|
+
def create_parts start_index, route
|
112
|
+
i = start_index
|
113
|
+
while i < route.length do
|
114
|
+
if route[i..i+4].count != 5 || route[i..i+4].include?(nil)
|
115
|
+
break
|
116
|
+
end
|
117
|
+
|
118
|
+
part = RoutePart.new
|
119
|
+
part.start = route[i]
|
120
|
+
@parts.last.target = part.start
|
121
|
+
part.type = route[i+2].squeeze
|
122
|
+
|
123
|
+
begin
|
124
|
+
part.start_time = DateTime.parse(@date.to_s + route[i+1])
|
125
|
+
part.end_time = DateTime.parse(@date.to_s + route[i+3])
|
126
|
+
part.target = route[i+4]
|
127
|
+
i += 4
|
128
|
+
rescue ArgumentError
|
129
|
+
# occures if there is a "Fußweg" in between
|
130
|
+
part.start_time = @parts.last.end_time
|
131
|
+
part.end_time = part.start_time + route[i+1].to_i.minutes
|
132
|
+
part.target = route[i+3]
|
133
|
+
i += 3
|
134
|
+
end
|
135
|
+
|
136
|
+
part.end_time += 1.day if part.end_time.hour < @start.start_time.hour
|
137
|
+
part.start_time += 1.day if part.start_time.hour < @start.start_time.hour
|
138
|
+
|
139
|
+
@target.start_time = part.end_time
|
140
|
+
@target.start = part.target
|
141
|
+
|
142
|
+
# we don't want to show Fußwege from and to the same station
|
143
|
+
@parts << part unless part.start == part.target
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Bahn
|
2
|
+
# Route Parts show a small step of the route from A to B with one specific type of transportation
|
3
|
+
# Example: "Am 2013-02-01 von 17:33 bis 17:47 : Heerdter Sandberg U, Düsseldorf nach Düsseldorf Hauptbahnhof via U 7"
|
4
|
+
class RoutePart
|
5
|
+
attr_accessor :start, :target, :type, :price, :start_time, :end_time
|
6
|
+
|
7
|
+
# Return a nicely formatted route
|
8
|
+
# Raises errors if not everything is set properly
|
9
|
+
def to_s
|
10
|
+
"Am #{start_time.to_date} von #{start_time.hour}:#{start_time.min} bis #{end_time.to_date.to_s + ' ' if end_time.to_date != start_time.to_date}#{end_time.hour}:#{end_time.min} : #{start} nach #{target} via #{type}"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Set the type, e.g. Fußweg
|
14
|
+
def type= val
|
15
|
+
@type = val.squeeze(" ")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/bahn/version.rb
ADDED
data/lib/bahn.rb
ADDED
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bahn.rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Simon Woker
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2013-02-07 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
22
|
+
none: false
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
hash: 3
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 6
|
30
|
+
version: "1.6"
|
31
|
+
requirement: *id001
|
32
|
+
prerelease: false
|
33
|
+
type: :runtime
|
34
|
+
name: json
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
hash: 3
|
42
|
+
segments:
|
43
|
+
- 0
|
44
|
+
version: "0"
|
45
|
+
requirement: *id002
|
46
|
+
prerelease: false
|
47
|
+
type: :runtime
|
48
|
+
name: mechanize
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 3
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
requirement: *id003
|
60
|
+
prerelease: false
|
61
|
+
type: :runtime
|
62
|
+
name: activesupport
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
hash: 3
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
requirement: *id004
|
74
|
+
prerelease: false
|
75
|
+
type: :development
|
76
|
+
name: rake
|
77
|
+
description: Load connections for public transportation from the m.bahn.de website.
|
78
|
+
email: github@simonwoker.de
|
79
|
+
executables: []
|
80
|
+
|
81
|
+
extensions: []
|
82
|
+
|
83
|
+
extra_rdoc_files: []
|
84
|
+
|
85
|
+
files:
|
86
|
+
- lib/bahn/bahn_agent.rb
|
87
|
+
- lib/bahn/bahn_route.rb
|
88
|
+
- lib/bahn/bahn_routepart.rb
|
89
|
+
- lib/bahn/version.rb
|
90
|
+
- lib/bahn.rb
|
91
|
+
- LICENSE
|
92
|
+
homepage: https://github.com/swoker/random_stuff
|
93
|
+
licenses: []
|
94
|
+
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
hash: 3
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
version: "0"
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
hash: 23
|
115
|
+
segments:
|
116
|
+
- 1
|
117
|
+
- 3
|
118
|
+
- 6
|
119
|
+
version: 1.3.6
|
120
|
+
requirements: []
|
121
|
+
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 1.8.25
|
124
|
+
signing_key:
|
125
|
+
specification_version: 3
|
126
|
+
summary: "Bahn \xC3\x96PNV information"
|
127
|
+
test_files: []
|
128
|
+
|