bahn.rb 2.1.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8a42380d0b034d7aea7c91a276b6fbe34f342c5e
4
+ data.tar.gz: 097e4635b917d27595f865b883c9cfa45c418296
5
+ SHA512:
6
+ metadata.gz: 0b90aa919d174c77041d5b60a754cddc9ac9a832e375412a02afe258d31c13768ef83350b1bc00289c32f456cf7cbe2e230421f46634ac8a63dd6b593e73ad8c
7
+ data.tar.gz: dfdc478a203847ba81dd56075ffc33f9ccab49ab3992de120d347613d6b6624e40140c0fed44582cd08dbb85450bc045d2f5957428efe68af3f60652d67ea9bf
data/LICENSE CHANGED
@@ -1,7 +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.
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.
data/README.md CHANGED
@@ -1,35 +1,35 @@
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
- # or with Geocoder
22
- routes = agent.get_routes(
23
- Geocoder.search("Düsseldorf reisholz s bahn").first, # start address or station
24
- Geocoder.search("Düsseldorf, Heerdter Sandberg 40").first, # target address or station
25
- :include_coords => true, # include coordinates of stations
26
- :limit => 1 # how many connections?
27
- # you don't need start- and target-type with geocoder :)
28
- )
29
- # output the connection info
30
- routes.each {|route| route.parts.each {|part| puts part } }
31
-
32
- How to help
33
- =
34
- Feel free to implement some additions, refactor and create a pull request!
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
+ # or with Geocoder
22
+ routes = agent.get_routes(
23
+ Geocoder.search("Düsseldorf reisholz s bahn").first, # start address or station
24
+ Geocoder.search("Düsseldorf, Heerdter Sandberg 40").first, # target address or station
25
+ :include_coords => true, # include coordinates of stations
26
+ :limit => 1 # how many connections?
27
+ # you don't need start- and target-type with geocoder :)
28
+ )
29
+ # output the connection info
30
+ routes.each {|route| route.parts.each {|part| puts part } }
31
+
32
+ How to help
33
+ =
34
+ Feel free to implement some additions, refactor and create a pull request!
35
35
  ... and create tests of course ;)
data/changelog.md ADDED
@@ -0,0 +1,10 @@
1
+ Changelog
2
+ =
3
+
4
+ none so far... forgot to make one
5
+
6
+ Roadmap
7
+ =
8
+
9
+ 3.0.0
10
+ - Possibility to add stops between the stations ("Zwischenstops") to display a more beautiful route
data/lib/bahn.rb CHANGED
@@ -1,11 +1,11 @@
1
- require 'rubygems'
2
- require 'mechanize'
3
- require 'active_support/all'
4
- require 'json'
5
- require 'geocoder'
6
-
7
- require 'bahn/bahn_routepart'
8
- require 'bahn/bahn_route'
9
- require 'bahn/bahn_station'
10
- require 'bahn/bahn_agent'
11
- require 'bahn/version'
1
+ require 'rubygems'
2
+ require 'mechanize'
3
+ require 'active_support/all'
4
+ require 'json'
5
+ require 'geocoder'
6
+
7
+ require 'bahn/bahn_routepart'
8
+ require 'bahn/bahn_route'
9
+ require 'bahn/bahn_station'
10
+ require 'bahn/bahn_agent'
11
+ require 'bahn/version'
@@ -1,177 +1,271 @@
1
- # encoding: utf-8
2
-
3
- module Bahn
4
- # Agent class that searches stations, street addresses and public transportation routes for germany
5
- # Example:
6
- # agent = Agent.new
7
- # routes = agent.get_routes "Düsseldorf Heerdter Sandberg 25", "Düsseldorf Am Dreieck 1"
8
- # routes.each {|route| route.parts.each {|part| puts part } }
9
- #
10
- # You can go even use far distances...
11
- # routes = agent.get_routes "Heerdter Sandberg 35 Düsseldorf, Düsseldorf", "Berlin Hauptstraße 10"
12
- # routes.each {|route| route.parts.each {|part| puts part } }
13
- # => 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
14
- # => Am 2013-02-01 von 17:33 bis 17:47 : Heerdter Sandberg U, Düsseldorf nach Düsseldorf Hauptbahnhof via U 7
15
- # => Am 2013-02-01 von 17:47 bis 17:53 : Düsseldorf Hauptbahnhof nach Düsseldorf Hbf via Fußweg
16
- # => Am 2013-02-01 von 17:53 bis 22:22 : Düsseldorf Hbf nach Berlin Hbf via ICE 945
17
- # => Am 2013-02-01 von 22:34 bis 22:40 : Berlin Hbf nach Berlin Alexanderplatz via RE 37386
18
- # => Am 2013-02-01 von 22:40 bis 22:46 : Berlin Alexanderplatz nach Alexanderplatz (U), Berlin via Fußweg
19
- # => Am 2013-02-01 von 22:46 bis 23:07 : Alexanderplatz (U), Berlin nach Oberseestr., Berlin via STR M5
20
- # => Am 2013-02-01 von 23:07 bis 23:15 : Oberseestr., Berlin nach Berlin - Alt-Hohenschönhausen, Hauptstraße 10 via Fußweg
21
- class Agent
22
- @@options = {
23
- :url_route => 'http://mobile.bahn.de/bin/mobil/query.exe/dox?country=DEU&rt=1&use_realtime_filter=1&searchMode=ADVANCED',
24
- :uri_adresses => 'http://reiseauskunft.bahn.de/bin/ajax-getstop.exe/en?REQ0JourneyStopsS0A=2&REQ0JourneyStopsS0G=',
25
- :uri_stations => 'http://reiseauskunft.bahn.de/bin/ajax-getstop.exe/en?REQ0JourneyStopsS0A=1&REQ0JourneyStopsS0G='
26
- }
27
-
28
- # Set the used user agent
29
- def self.user_agent=val
30
- @@user_agent = val
31
- end
32
-
33
- TYPES =
34
- {
35
- :station => 1,
36
- :address => 2
37
- }.freeze
38
-
39
- # Initialize a new Agent
40
- # options:
41
- # :user_agent => Set the user agent. Default: "bahn.rb"
42
- def initialize
43
- @agent = Mechanize.new
44
- @agent.user_agent = @@user_agent ||= "bahn.rb"
45
- end
46
-
47
- # Get the next few routes with public transportation from A to B.
48
- #
49
- # Options:
50
- # * :time => start time for the connection
51
- # * :include_coords => Include coordiantes for the station. This takes a while especially for longer routes! default: true
52
- # Returns:
53
- # Array of Bahn::Route(s)
54
- # Raises:
55
- # "no_route" if no route could be found
56
- def get_routes from, to, options = {}
57
- options[:start_type] = check_point_type(from) || options[:start_type]
58
- options[:target_type] = check_point_type(to) || options[:target_type]
59
- options = {:time => Time.now, :depth => 0, :include_coords => true, :limit => 2}.merge(options)
60
- options[:time] = options[:time] + 10.minutes # Ansonsten liegt die letzte Verbindung in der Vergangenheit
61
- page = @agent.get @@options[:url_route]
62
-
63
- result = submit_form page.forms.first, from, to, options
64
-
65
- routes = []
66
- links = result.links_with(:href => /details=opened!/)
67
- links.each do |link|
68
- page = link.click
69
- routes << Route.new(page, options)
70
- break if routes.count == options[:limit]
71
- end
72
-
73
- # Keine Station gefunden und es werden keine Vorschläge angezeigt...
74
- # also suchen wir nach der nächstbesten Adresse/Station und nutzen diese
75
- if links.count == 0 && options[:depth] == 0
76
- if options[:start_type] == :address
77
- from = find_address(from, options).name
78
- elsif options[:start_type] == :station
79
- from = find_station(from, options).name
80
- end
81
-
82
- if options[:target_type] == :address
83
- to = find_address(to, options).name
84
- elsif options[:target_type] == :station
85
- to = find_station(to, options).name
86
- end
87
-
88
- return get_routes from, to, options.merge(:depth => options[:depth]+1)
89
- end
90
-
91
- raise "no_route" if routes.count == 0 || links.count == 0
92
- routes
93
- end
94
-
95
- # Find the first best station by name
96
- # Example:
97
- # Input: HH Allee Düsseldorf
98
- # Output: Heinrich-Heine-Allee U, Düsseldorf
99
- def find_station name, options={}
100
- val = get_address_or_station(name, :station)
101
- options[:coords] = name.coordinates if name.respond_to?(:coordinates)
102
- result = @agent.get("#{@@options[:uri_stations]}#{val}").body.gsub("SLs.sls=", "").gsub(";SLs.showSuggestion();", "")
103
- find_nearest_station result, options
104
- end
105
-
106
- # Finds the first usable address for the given parameter. The returned address can then be used for further processing in routes
107
- # Example:
108
- # Input: Roßstr. 41 40476 Düsseldorf
109
- # Output: Düsseldorf - Golzheim, Rossstraße 41
110
- def find_address address, options={}
111
- val = get_address_or_station(address, :address)
112
- options[:coords] = address.coordinates if address.respond_to?(:coordinates)
113
- result = @agent.get("#{@@options[:uri_adresses]}#{val}").body.gsub("SLs.sls=", "").gsub(";SLs.showSuggestion();", "")
114
- find_nearest_station result, options
115
- end
116
-
117
- ############################################################################################
118
- private
119
- ############################################################################################
120
-
121
- def find_nearest_station result, options={}
122
- # a Mechanize::File instead of a Page is returned so we have to convert manually
123
- result = encode result
124
- result = JSON.parse(result)["suggestions"]
125
-
126
- if options[:coords].nil?
127
- s = Station.new(result.first)
128
- else
129
- result.map!{|r| Station.new(r)}
130
- result.each {|s| s.distance = Geocoder::Calculations.distance_between(options[:coords], s)}
131
- result.sort! {|a,b| a.distance <=> b.distance}
132
- s = result.first
133
- end
134
-
135
- s
136
- end
137
-
138
- def encode str
139
- if str.respond_to? :encode
140
- str.force_encoding("iso-8859-1").encode("utf-8")
141
- else
142
- Iconv.conv("utf-8", "iso-8859-1", str)
143
- end
144
- end
145
-
146
- def check_point_type geocoder_result
147
- return nil unless geocoder_result.is_a? Geocoder::Result::Base
148
- return :address unless geocoder_result.address_components.index{|a| a["types"].include?("route")}.nil?
149
- return :station if geocoder_result.address_components.index{|a| a["types"].include?("train_station") || a["types"].include?("transit_station")}.nil?
150
- return :station
151
- end
152
-
153
- def get_address_or_station geocoder_result, type
154
- return geocoder_result.to_s unless geocoder_result.is_a? Geocoder::Result::Base
155
- addy = geocoder_result.address
156
- if type == :station
157
- begin
158
- addy = geocoder_result.address_components.find{|a| a["types"].include? "transit_station"}["short_name"]
159
- addy += " #{geocoder_result.city}" unless addy.include?(geocoder_result.city)
160
- rescue StandardError
161
- end
162
- end
163
- addy
164
- end
165
-
166
- def submit_form form, from, to, options
167
- form["REQ0JourneyDate"] = options[:time].strftime "%d.%m.%y"
168
- form["REQ0JourneyTime"] = options[:time].to_formatted_s :time
169
- form["REQ0JourneyStopsS0A"] = TYPES[options[:start_type]]
170
- form["REQ0JourneyStopsZ0A"] = TYPES[options[:target_type]]
171
- form["REQ0JourneyStopsS0G"] = get_address_or_station(from, options[:start_type])
172
- form["REQ0JourneyStopsZ0G"] = get_address_or_station(to, options[:target_type])
173
- form["REQ0JourneyProduct_prod_list"] = "4:0001111111000000"
174
- form.submit(form.button_with(:value => "Suchen"))
175
- end
176
- end
177
- end
1
+ # encoding: utf-8
2
+
3
+ module Bahn
4
+ # Agent class that searches stations, street addresses and public transportation routes for germany
5
+ # Example:
6
+ # agent = Agent.new
7
+ # routes = agent.get_routes "Düsseldorf Heerdter Sandberg 25", "Düsseldorf Am Dreieck 1"
8
+ # routes.each {|route| route.parts.each {|part| puts part } }
9
+ #
10
+ # You can go even use far distances...
11
+ # routes = agent.get_routes "Heerdter Sandberg 35 Düsseldorf, Düsseldorf", "Berlin Hauptstraße 10"
12
+ # routes.each {|route| route.parts.each {|part| puts part } }
13
+ # => 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
14
+ # => Am 2013-02-01 von 17:33 bis 17:47 : Heerdter Sandberg U, Düsseldorf nach Düsseldorf Hauptbahnhof via U 7
15
+ # => Am 2013-02-01 von 17:47 bis 17:53 : Düsseldorf Hauptbahnhof nach Düsseldorf Hbf via Fußweg
16
+ # => Am 2013-02-01 von 17:53 bis 22:22 : Düsseldorf Hbf nach Berlin Hbf via ICE 945
17
+ # => Am 2013-02-01 von 22:34 bis 22:40 : Berlin Hbf nach Berlin Alexanderplatz via RE 37386
18
+ # => Am 2013-02-01 von 22:40 bis 22:46 : Berlin Alexanderplatz nach Alexanderplatz (U), Berlin via Fußweg
19
+ # => Am 2013-02-01 von 22:46 bis 23:07 : Alexanderplatz (U), Berlin nach Oberseestr., Berlin via STR M5
20
+ # => Am 2013-02-01 von 23:07 bis 23:15 : Oberseestr., Berlin nach Berlin - Alt-Hohenschönhausen, Hauptstraße 10 via Fußweg
21
+ class Agent
22
+ @@options = {
23
+ :url_route => 'http://mobile.bahn.de/bin/mobil/query.exe/dox?country=DEU&rt=1&use_realtime_filter=1&searchMode=ADVANCED',
24
+ :uri_adresses => 'http://reiseauskunft.bahn.de/bin/ajax-getstop.exe/en?REQ0JourneyStopsS0A=2&REQ0JourneyStopsS0G=',
25
+ :uri_stations => 'http://reiseauskunft.bahn.de/bin/ajax-getstop.exe/en?REQ0JourneyStopsS0A=1&REQ0JourneyStopsS0G=',
26
+ :uri_stations_near => 'http://mobile.bahn.de/bin/mobil/query.exe/dox?ld=9627&n=1&rt=1&use_realtime_filter=1&performLocating=2&tpl=stopsnear&look_maxdist=1000&look_stopclass=1023&look_y=%i&look_x=%i'
27
+ }
28
+
29
+ # Set the used user agent
30
+ def self.user_agent=val
31
+ @@user_agent = val
32
+ end
33
+
34
+ TYPES =
35
+ {
36
+ :station => 1,
37
+ :address => 2
38
+ }.freeze
39
+
40
+ TIME_RELATION =
41
+ {
42
+ :arrival => 0,
43
+ :depature => 1
44
+ }.freeze
45
+
46
+ JOURNEY_PRODUCT_PRESETS = {
47
+ :alle => "1:1111111111000000",
48
+ :alle_ohne_ice => "2:0111111111000000",
49
+ :nur_nahverkehr => "4:0001111111000000",
50
+ }.freeze
51
+
52
+ # Initialize a new Agent
53
+ # options:
54
+ # :user_agent => Set the user agent. Default: "bahn.rb"
55
+ def initialize
56
+ @agent = Mechanize.new
57
+ @agent.set_defaults if @agent.respond_to?(:set_defaults)
58
+ @agent.user_agent = @@user_agent ||= "bahn.rb"
59
+ end
60
+
61
+ # Get the next few routes with public transportation from A to B.
62
+ #
63
+ # Options:
64
+ # * :time => start time for the connection
65
+ # * :include_coords => Include coordiantes for the station. This takes a while especially for longer routes! default: true
66
+ # Returns:
67
+ # Array of Bahn::Route(s)
68
+ # Raises:
69
+ # "no_route" if no route could be found
70
+ def get_routes from, to, options = {}
71
+ options[:start_type] = check_point_type(from) || options[:start_type]
72
+ options[:target_type] = check_point_type(to) || options[:target_type]
73
+ # default options
74
+ options = {
75
+ :time => Time.now,
76
+ :depth => 0,
77
+ :include_coords => true,
78
+ :limit => 2,
79
+ :time_relation => :depature }.merge(options)
80
+
81
+ options[:time] = options[:time].in_time_zone("Berlin") #+ 10.minutes # Ansonsten liegt die erste Verbindung in der Vergangenheit
82
+ page = @agent.get @@options[:url_route]
83
+
84
+ result = submit_form page.forms.first, from, to, options
85
+
86
+ routes = []
87
+ links = result.links_with(:href => /details=opened!/).select { |l| l.to_s.size > 0} # only select connection links, no warning links
88
+ links.each do |link|
89
+ page = link.click
90
+ routes << Route.new(page, options)
91
+ break if routes.count == options[:limit]
92
+ end
93
+
94
+ # Keine Station gefunden also suchen wir nach der nächstbesten Adresse/Station
95
+ if links.count == 0 && options[:depth] == 0
96
+ if options[:start_type] == :address
97
+ from = find_address(from, options)
98
+ options[:start_type] = from.station_type
99
+ from = from.name
100
+ elsif options[:start_type] == :station
101
+ from = find_station(from, options)
102
+ options[:start_type] = from.station_type
103
+ from = from.name
104
+ end
105
+
106
+ if options[:target_type] == :address
107
+ to = find_address(to, options)
108
+ options[:target_type] = to.station_type
109
+ to = to.name
110
+ elsif options[:target_type] == :station
111
+ to = find_station(to, options)
112
+ options[:target_type] = to.station_type
113
+ to = to.name
114
+ end
115
+
116
+ return get_routes from, to, options.merge(:depth => options[:depth]+1)
117
+ end
118
+
119
+ raise "no_route" if routes.count == 0 || links.count == 0
120
+ routes
121
+ end
122
+
123
+ # Find the first best station by name
124
+ # Example:
125
+ # Input: HH Allee Düsseldorf
126
+ # Output: Heinrich-Heine-Allee U, Düsseldorf
127
+ def find_station name, options={}
128
+ val = get_address_or_station(name, :station)
129
+ options[:coords] = name.respond_to?(:coordinates) ? name.coordinates : nil
130
+ result = @agent.get("#{@@options[:uri_stations]}#{val}").body.gsub("SLs.sls=", "").gsub(";SLs.showSuggestion();", "")
131
+ options[:current_station_type] = :station
132
+ options[:searched_name] = name
133
+ find_nearest_station result, options
134
+ end
135
+
136
+ # Finds the first usable address for the given parameter. The returned address can then be used for further processing in routes
137
+ # Example:
138
+ # Input: Roßstr. 41 40476 Düsseldorf
139
+ # Output: Düsseldorf - Golzheim, Rossstraße 41
140
+ def find_address address, options={}
141
+ val = get_address_or_station(address, :address)
142
+ options[:coords] = address.respond_to?(:coordinates) ? address.coordinates : nil
143
+ result = @agent.get("#{@@options[:uri_adresses]}#{val}").body.gsub("SLs.sls=", "").gsub(";SLs.showSuggestion();", "")
144
+ options[:current_station_type] = :address
145
+ find_nearest_station result, options
146
+ end
147
+
148
+ def find_station_at lat, lon, options={}
149
+ uri = @@options[:uri_stations_near] % [(lat * 1000000), (lon * 1000000)]
150
+ result = @agent.get(uri)
151
+ stations = result.links.select{|l| l.href.include?("HWAI=STATION!")}.map do |link|
152
+ s = Station.new({:do_load => false})
153
+ s.name = link.text
154
+ lat_match = link.href.match(/Y=(\d{7,})/)
155
+ s.lat = lat_match[1].insert(-7, ".") unless lat_match.nil?
156
+ lon_match = link.href.match(/X=(\d{7,})/)
157
+ s.lon = lon_match[1].insert(-7, ".") unless lon_match.nil?
158
+ # not needed but hey, since we got it ;)
159
+ dist_match = link.href.match(/dist=(\d+)!/)
160
+ s.distance = (dist_match[1].to_i / 1000) unless dist_match.nil? # meter to km
161
+ s.station_type = :station
162
+ s
163
+ end
164
+
165
+ # prefer HBF or Hauptbahnhof if there is one nearby
166
+ station = stations.select{|s| s.name.include?("Hauptbahnhof") || s.name.include?("HBF")}.first
167
+ return (station.nil? ? stations.first : station)
168
+ end
169
+
170
+ ############################################################################################
171
+ private
172
+ ############################################################################################
173
+
174
+ def find_nearest_station result, options={}
175
+ # a Mechanize::File instead of a Page is returned so we have to convert manually
176
+ result = encode result
177
+ result = JSON.parse(result)["suggestions"]
178
+
179
+ stations = result.map{|r| (Station.new(r) rescue StandardError) }.compact
180
+ stations = stations.delete_if{|s| s.instance_of?(StandardError)}
181
+ station = options[:searched_name].to_s.length > 0 ? stations.select{|s| s.name == options[:searched_name]}.first : nil
182
+ if options[:coords].nil?
183
+ station = stations.first if station.nil?
184
+ station.station_type = options[:current_station_type]
185
+ elsif station.nil? # 100% match not found, so look for the next best match
186
+ stations.each {|s| s.distance = Geocoder::Calculations.distance_between(options[:coords], s, :units => :km)}
187
+ stations.sort! {|a,b| a.distance <=> b.distance}
188
+ station = stations.first
189
+ station.station_type = options[:current_station_type]
190
+
191
+ # more than 1 km? This seems to be wrong...
192
+ if station.distance > 1
193
+ nearest_station = find_station_at options[:coords][0], options[:coords][1]
194
+ station = nearest_station if nearest_station
195
+ end
196
+ end
197
+ station
198
+ end
199
+
200
+ def encode str
201
+ if str.respond_to? :encode
202
+ str.force_encoding("iso-8859-1").encode("utf-8")
203
+ else
204
+ Iconv.conv("utf-8", "iso-8859-1", str)
205
+ end
206
+ end
207
+
208
+ def encode_to_iso str
209
+ if str.respond_to? :encode
210
+ str.encode("iso-8859-1")
211
+ else
212
+ Iconv.conv("iso-8859-1", "utf-8", str)
213
+ end
214
+ end
215
+
216
+ def check_point_type geocoder_result
217
+ return nil unless geocoder_result.respond_to?(:types)
218
+ return :station if geocoder_result.types.include?("transit_station") # subway, bus, ...
219
+
220
+ return :address if geocoder_result.types.include?("street_address") # full address
221
+ return :address if geocoder_result.types.include?("route") # street name only
222
+ return :address if geocoder_result.types.include?("postal_code") # plz only
223
+
224
+ # city without transit station => use address + HBF
225
+ if geocoder_result.types.include?("locality") && geocoder_result.transit_station.to_s.length == 0
226
+ geocoder_result.transit_station = geocoder_result.address.gsub(", Deutschland", "").gsub(", Germany", "")
227
+ geocoder_result.transit_station << " HBF"
228
+ return :station
229
+ end
230
+
231
+ # (sub-)locality or political or anything else => treat as station (most likely main station)
232
+ return :station
233
+ end
234
+
235
+ def get_address_or_station geocoder_result, type
236
+ return geocoder_result.to_s unless geocoder_result.respond_to?(:address)
237
+ addy = ""
238
+
239
+ if type == :station
240
+ addy = geocoder_result.transit_station if geocoder_result.respond_to?(:transit_station)
241
+
242
+ if geocoder_result.respond_to?(:address_components_of_type)
243
+ begin
244
+ addy = geocoder_result.address_components_of_type("transit_station").first["short_name"]
245
+ addy += " #{geocoder_result.city}" unless addy.include?(geocoder_result.city)
246
+ rescue StandardError
247
+ # use address instead
248
+ end
249
+ end
250
+ end
251
+
252
+ if addy.to_s.empty?
253
+ addy = geocoder_result.address
254
+ end
255
+
256
+ addy
257
+ end
258
+
259
+ def submit_form form, from, to, options
260
+ form["REQ0JourneyDate"] = options[:time].strftime "%d.%m.%y"
261
+ form["REQ0JourneyTime"] = options[:time].to_formatted_s :time
262
+ form["REQ0JourneyStopsS0A"] = TYPES[options[:start_type]]
263
+ form["REQ0JourneyStopsZ0A"] = TYPES[options[:target_type]]
264
+ form["REQ0JourneyStopsS0G"] = encode_to_iso(get_address_or_station(from, options[:start_type]))
265
+ form["REQ0JourneyStopsZ0G"] = encode_to_iso(get_address_or_station(to, options[:target_type]))
266
+ form["REQ0HafasSearchForw"] = TIME_RELATION[options[:time_relation]] # 0 -> Abfahrt, 1 -> Ankunft
267
+ form["REQ0JourneyProduct_prod_list"] = JOURNEY_PRODUCT_PRESETS[options[:journey_product]]
268
+ form.submit(form.button_with(:value => "Suchen"))
269
+ end
270
+ end
271
+ end