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 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 ;)
@@ -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 35 nach Heerdter Sandberg U, Düsseldorf via Fußweg
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 => 1 = station, 2 = address
45
- # * :target_type => 1 = station, 2 = address
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 => 2, :target_type => 2, :depth => 0}.merge(options)
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"] = "#{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]
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 do |link|
69
- page = link.click
70
- routes << Route.new(page, type)
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 && 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}
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
- r = JSON.parse(result)["suggestions"].first["value"]
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["value"]
121
+ Station.new(JSON.parse(result)["suggestions"].first)
105
122
  end
106
123
  end
107
124
  end
@@ -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
- include ActionView::Helpers::DateHelper
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
- # * type => :door2door or :station2station
15
- def initialize page, type
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
- 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
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.start_time
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
@@ -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.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}"
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/bahn_agent'
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: 23
4
+ hash: 15
5
5
  prerelease:
6
6
  segments:
7
- - 1
7
+ - 2
8
8
  - 0
9
9
  - 0
10
- version: 1.0.0
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-07 00:00:00 Z
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/version.rb
89
+ - lib/bahn/bahn_station.rb
90
90
  - lib/bahn.rb
91
91
  - LICENSE
92
- homepage: https://github.com/swoker/random_stuff
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
@@ -1,3 +0,0 @@
1
- module Bahn
2
- VERSION = "1.0.0"
3
- end