bahn.rb 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ module Bahn
2
+ VERSION = "1.0.0"
3
+ end
data/lib/bahn.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'mechanize'
3
+ require 'active_support/all'
4
+ require 'action_view'
5
+ require 'json'
6
+
7
+
8
+ require 'bahn/bahn_routepart'
9
+ require 'bahn/bahn_route'
10
+ require 'bahn/bahn_agent'
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
+