bahn.rb 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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