From fef31338a3d5d93fb0cbe7f7a8a5f80ab648af9e Mon Sep 17 00:00:00 2001 From: otherl Date: Sat, 28 Sep 2013 19:25:37 +0100 Subject: Renamed the blatube file so that the version is not included. --- blatube.rb | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ blatube_v1.0.0.rb | 220 ------------------------------------------------------ 2 files changed, 220 insertions(+), 220 deletions(-) create mode 100644 blatube.rb delete mode 100644 blatube_v1.0.0.rb diff --git a/blatube.rb b/blatube.rb new file mode 100644 index 0000000..0ecd5fb --- /dev/null +++ b/blatube.rb @@ -0,0 +1,220 @@ +require 'optparse' +require 'open-uri' +require 'nokogiri' +require 'yaml' +require 'json' + +@output = [] +@line = nil +@from_station = nil +@to_station = nil +@weight = 'peak_time' +@change_time = 5 +@colour = false +debug = false +help = false +ops = nil +VERSION = 'v1.0.0' +INFINITY = 1 << 64 +WEIGHTS = ['distance','peak_time','off_peak_time','unimpeded_time'] + +STDIN.gets.split(' ').each{|arg| ARGV << arg} if ARGV == [] +args = ARGV.dup + +options = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename($0)} -h" + opts.on( '-s', '--status LINE', 'Get the current status of a tube line.' ) {|l| @line = l } + opts.on( '-f', '--from-station STATION', 'Find route from this station.' ) {|f| @from_station = f } + opts.on( '-t', '--to-station STATION', 'Find route to this station.' ) {|t| @to_station = t } + opts.on( '-w', '--weight WEIGHT', 'Shortest route using either distance, peak_time, off_peak_time or unimpeded_time.' ) {|w| @weight = w if WEIGHTS.include? w || 'peak_time' } + opts.on( '-c', '--change-time TIME', 'Your expected average change time in minutes.' ) {|c| @change_time = c.to_f.round(2) } + opts.on( '-C', '--colour', 'Enable line colours in the output.' ) { @colour = true } + opts.on( '-d', '--debug', 'Display debug info.' ) { debug = true } + opts.on( '-v', '--version', 'blatube version number.' ) { puts VERSION; exit } + opts.on( '-h', '--help', 'Display this screen.' ) { help = true; ops = opts } +end;options.parse! + +@output << "args: #{args.inspect}" if debug +if args == [] + @output << "Usage: #{File.basename($0)} -h" + puts @output.join ' ' + exit -1 +end + +class Array + def pairs + pairs = [] + self.each_with_index do |element, index| + pairs << [element,self[index+1]] + end + pairs.pop + pairs + end +end + +#TODO: add via_station +def calculate_shortest_route + return unless interpret_user_input + @shortest_route = [] + calculate_routes + traverse_route @to_station.upcase + build_route_output +end + +def interpret_user_input + stations = @graph.keys.map {|station| station.split('_')[0]}.uniq.sort + found_from_station, @from_station = interpret stations, @from_station + found_to_station, @to_station = interpret stations, @to_station + return found_from_station && found_to_station +end + +def interpret stations, station + found_station = false + if stations.include?(station.upcase) + found_station = true + elsif (most_likely = stations.map {|stat| stat.match("^#{station.upcase}.*") ? stat : nil}.compact) != [] + if most_likely.size == 1 + station = most_likely.first + found_station = true + else + @output << "Did you mean: #{most_likely.map {|stat| stat.split(' ').map {|word| word.capitalize}.join(' ')}.join(', ')}?" + end + elsif (less_likely = stations.map {|stat| stat.match(".*#{station.upcase}.*") ? stat : nil}.compact) != [] + if less_likely.size == 1 + station = less_likely.first + found_station = true + else + @output << "Did you mean: #{less_likely.map {|stat| stat.split(' ').map {|word| word.capitalize}.join(' ')}.join(', ')}?" + end + else + @output << "Could not find a matching station for '#{station}'." + end + return found_station, station +end + +def calculate_routes + routes,distances = YAML::load(File.open("cache/#{@from_station}_#{@weight == 'distance' ? @weight : "#{@weight}_#{@change_time}"}.yaml", 'r')) + @distances = Hash.new(INFINITY).merge distances + @routes = Hash.new(-1).merge routes +rescue Errno::ENOENT + add_change_time unless @weight == 'distance' + run_algorithm @graph, @graph.keys, @from_station.upcase + File.new("cache/#{@from_station}_#{@weight == 'distance' ? @weight : "#{@weight}_#{@change_time}"}.yaml", 'w') << [@routes,@distances].to_yaml +end + +#TODO: changes on the same line (eg. 'CAMDEN TOWN') +def add_change_time + @graph.each do |from_station,to_stations| + next unless current_line = from_station.split('_')[1] + to_stations.each do |to_station,data| + data[@weight] += @change_time unless data['line'] == current_line + end + end +end + +def run_algorithm graph, nodes, start_node + @distances = Hash.new INFINITY + @routes = Hash.new -1 + @distances[start_node] = 0 + nodes_size = nodes.size + while nodes_size > 0 + current_node = nodes.first + nodes.each {|node| current_node = node if @distances[node] < @distances[current_node]} + break if @distances[current_node] == INFINITY + nodes.delete current_node + nodes_size -= 1 + graph[current_node].keys.each do |next_node| + new_distance = @distances[current_node] + graph[current_node][next_node][@weight] + if new_distance < @distances[next_node] + @distances[next_node] = new_distance + @routes[next_node] = current_node + end + end + end + @distances.delete_if {|node, distance| distance == INFINITY} +end + +def traverse_route to_station + if @routes[to_station] != -1 + traverse_route @routes[to_station] + end + @shortest_route << to_station +end + +#TODO: a nicer way of doing this +def build_route_output + line_colours = @colour ? YAML::load(File.open('line_colours.yaml')) : Hash.new('') + verbose_route = '' + pairs = @shortest_route.pairs + from,to = pairs.first + current_line = @graph[from][to]['line'] + verbose_route << "#{line_colours[current_line]}#{from} - #{current_line} (#{@graph[from][to]['direction']})" + pairs[1..-2].each do |from,to| + next if (new_line = @graph[from][to]['line']) == current_line || @graph[from][to]['direction'] == nil + current_line = new_line + from_s = from.split('_')[0] + from_1 = from_s[0..(from_s.length/2.0 - 1)] + from_2 = from_s[(from_s.length/2.0)..-1] + verbose_route << " - #{from_1}#{line_colours['reset_colour']}#{line_colours[new_line]}#{from_2} - #{new_line} (#{@graph[from][to]['direction']})" + end + from,to = pairs.last + new_line = @graph[from][to]['line'] + from_s = from.split('_')[0] + from_1 = from_s[0..(from_s.length/2.0 - 1)] + from_2 = from_s[(from_s.length/2.0)..-1] + verbose_route << " - #{from_1}#{line_colours['reset_colour']}#{line_colours[new_line]}#{from_2} - #{new_line} (#{@graph[from][to]['direction']})" unless new_line == current_line + verbose_route << " - #{to}#{line_colours['reset_colour']}" + @output << verbose_route + if @weight == 'distance' + @output << "Distance: #{@distances[@to_station.upcase].round(2)} km" + else + minutes = @distances[@to_station.upcase].round(2) + seconds = (minutes - minutes.to_i) * 60 + @output << "Travel Time: #{minutes.to_i}:#{seconds.round}" + end +end + +def get_line_status + xml = Nokogiri::HTML(open('http://cloud.tfl.gov.uk/TrackerNet/LineStatus')).remove_namespaces! + sorted_lines = xml.xpath('//linestatus').sort_by {|xml| xml.xpath('.//line/@name').text} + if @line && @line != 'summary' + l_s_xml = nil + sorted_lines.each {|xml| l_s_xml = xml and break if xml.xpath('.//line/@name').text.upcase.match @line.upcase} + @output << ("#{l_s_xml.xpath('.//line/@name').text}: #{l_s_xml.xpath('.//status/@description').text}. #{l_s_xml.xpath('./@statusdetails').text}" rescue "Could not find a line matching '#{@line}'") + else + line_summary = '' + sorted_lines.each {|xml| line_summary << "#{xml.xpath('.//line/@name').text}: #{xml.xpath('./@statusdetails').text} " unless xml.xpath('./@statusdetails').text == ''} + @output << (line_summary == '' ? 'Good Serice on all lines.' : "#{line_summary}Good Service on all other lines.") + end +end + +#TODO: store graph as yaml in a way that works with add_change_time +begin + if help + ops.to_s.split(/\n\s*|\s{2,}/).each{|opt| @output << opt} + elsif @line + get_line_status + elsif @from_station && @to_station + #@graph = YAML::load(File.open('graph.yaml')) + @graph = JSON.parse(File.open('graph.json').read) + calculate_shortest_route + elsif @from_station || @to_station + @output << 'Please specify the station you are travelling from (-f) and the station you are travelling to (-t).' + elsif ARGV.size == 2 + #@graph = YAML::load(File.open('graph.yaml')) + @graph = JSON.parse(File.open('graph.json').read) + @from_station = ARGV[0] + @to_station = ARGV[1] + calculate_shortest_route + elsif ARGV.size == 1 + @line = ARGV[0] + get_line_status + else + get_line_status + end + puts @output.join ' ' +rescue StandardError => e + puts (@output << e.message).join ' ' + puts e.message + puts e.backtrace +end diff --git a/blatube_v1.0.0.rb b/blatube_v1.0.0.rb deleted file mode 100644 index 0ecd5fb..0000000 --- a/blatube_v1.0.0.rb +++ /dev/null @@ -1,220 +0,0 @@ -require 'optparse' -require 'open-uri' -require 'nokogiri' -require 'yaml' -require 'json' - -@output = [] -@line = nil -@from_station = nil -@to_station = nil -@weight = 'peak_time' -@change_time = 5 -@colour = false -debug = false -help = false -ops = nil -VERSION = 'v1.0.0' -INFINITY = 1 << 64 -WEIGHTS = ['distance','peak_time','off_peak_time','unimpeded_time'] - -STDIN.gets.split(' ').each{|arg| ARGV << arg} if ARGV == [] -args = ARGV.dup - -options = OptionParser.new do |opts| - opts.banner = "Usage: #{File.basename($0)} -h" - opts.on( '-s', '--status LINE', 'Get the current status of a tube line.' ) {|l| @line = l } - opts.on( '-f', '--from-station STATION', 'Find route from this station.' ) {|f| @from_station = f } - opts.on( '-t', '--to-station STATION', 'Find route to this station.' ) {|t| @to_station = t } - opts.on( '-w', '--weight WEIGHT', 'Shortest route using either distance, peak_time, off_peak_time or unimpeded_time.' ) {|w| @weight = w if WEIGHTS.include? w || 'peak_time' } - opts.on( '-c', '--change-time TIME', 'Your expected average change time in minutes.' ) {|c| @change_time = c.to_f.round(2) } - opts.on( '-C', '--colour', 'Enable line colours in the output.' ) { @colour = true } - opts.on( '-d', '--debug', 'Display debug info.' ) { debug = true } - opts.on( '-v', '--version', 'blatube version number.' ) { puts VERSION; exit } - opts.on( '-h', '--help', 'Display this screen.' ) { help = true; ops = opts } -end;options.parse! - -@output << "args: #{args.inspect}" if debug -if args == [] - @output << "Usage: #{File.basename($0)} -h" - puts @output.join ' ' - exit -1 -end - -class Array - def pairs - pairs = [] - self.each_with_index do |element, index| - pairs << [element,self[index+1]] - end - pairs.pop - pairs - end -end - -#TODO: add via_station -def calculate_shortest_route - return unless interpret_user_input - @shortest_route = [] - calculate_routes - traverse_route @to_station.upcase - build_route_output -end - -def interpret_user_input - stations = @graph.keys.map {|station| station.split('_')[0]}.uniq.sort - found_from_station, @from_station = interpret stations, @from_station - found_to_station, @to_station = interpret stations, @to_station - return found_from_station && found_to_station -end - -def interpret stations, station - found_station = false - if stations.include?(station.upcase) - found_station = true - elsif (most_likely = stations.map {|stat| stat.match("^#{station.upcase}.*") ? stat : nil}.compact) != [] - if most_likely.size == 1 - station = most_likely.first - found_station = true - else - @output << "Did you mean: #{most_likely.map {|stat| stat.split(' ').map {|word| word.capitalize}.join(' ')}.join(', ')}?" - end - elsif (less_likely = stations.map {|stat| stat.match(".*#{station.upcase}.*") ? stat : nil}.compact) != [] - if less_likely.size == 1 - station = less_likely.first - found_station = true - else - @output << "Did you mean: #{less_likely.map {|stat| stat.split(' ').map {|word| word.capitalize}.join(' ')}.join(', ')}?" - end - else - @output << "Could not find a matching station for '#{station}'." - end - return found_station, station -end - -def calculate_routes - routes,distances = YAML::load(File.open("cache/#{@from_station}_#{@weight == 'distance' ? @weight : "#{@weight}_#{@change_time}"}.yaml", 'r')) - @distances = Hash.new(INFINITY).merge distances - @routes = Hash.new(-1).merge routes -rescue Errno::ENOENT - add_change_time unless @weight == 'distance' - run_algorithm @graph, @graph.keys, @from_station.upcase - File.new("cache/#{@from_station}_#{@weight == 'distance' ? @weight : "#{@weight}_#{@change_time}"}.yaml", 'w') << [@routes,@distances].to_yaml -end - -#TODO: changes on the same line (eg. 'CAMDEN TOWN') -def add_change_time - @graph.each do |from_station,to_stations| - next unless current_line = from_station.split('_')[1] - to_stations.each do |to_station,data| - data[@weight] += @change_time unless data['line'] == current_line - end - end -end - -def run_algorithm graph, nodes, start_node - @distances = Hash.new INFINITY - @routes = Hash.new -1 - @distances[start_node] = 0 - nodes_size = nodes.size - while nodes_size > 0 - current_node = nodes.first - nodes.each {|node| current_node = node if @distances[node] < @distances[current_node]} - break if @distances[current_node] == INFINITY - nodes.delete current_node - nodes_size -= 1 - graph[current_node].keys.each do |next_node| - new_distance = @distances[current_node] + graph[current_node][next_node][@weight] - if new_distance < @distances[next_node] - @distances[next_node] = new_distance - @routes[next_node] = current_node - end - end - end - @distances.delete_if {|node, distance| distance == INFINITY} -end - -def traverse_route to_station - if @routes[to_station] != -1 - traverse_route @routes[to_station] - end - @shortest_route << to_station -end - -#TODO: a nicer way of doing this -def build_route_output - line_colours = @colour ? YAML::load(File.open('line_colours.yaml')) : Hash.new('') - verbose_route = '' - pairs = @shortest_route.pairs - from,to = pairs.first - current_line = @graph[from][to]['line'] - verbose_route << "#{line_colours[current_line]}#{from} - #{current_line} (#{@graph[from][to]['direction']})" - pairs[1..-2].each do |from,to| - next if (new_line = @graph[from][to]['line']) == current_line || @graph[from][to]['direction'] == nil - current_line = new_line - from_s = from.split('_')[0] - from_1 = from_s[0..(from_s.length/2.0 - 1)] - from_2 = from_s[(from_s.length/2.0)..-1] - verbose_route << " - #{from_1}#{line_colours['reset_colour']}#{line_colours[new_line]}#{from_2} - #{new_line} (#{@graph[from][to]['direction']})" - end - from,to = pairs.last - new_line = @graph[from][to]['line'] - from_s = from.split('_')[0] - from_1 = from_s[0..(from_s.length/2.0 - 1)] - from_2 = from_s[(from_s.length/2.0)..-1] - verbose_route << " - #{from_1}#{line_colours['reset_colour']}#{line_colours[new_line]}#{from_2} - #{new_line} (#{@graph[from][to]['direction']})" unless new_line == current_line - verbose_route << " - #{to}#{line_colours['reset_colour']}" - @output << verbose_route - if @weight == 'distance' - @output << "Distance: #{@distances[@to_station.upcase].round(2)} km" - else - minutes = @distances[@to_station.upcase].round(2) - seconds = (minutes - minutes.to_i) * 60 - @output << "Travel Time: #{minutes.to_i}:#{seconds.round}" - end -end - -def get_line_status - xml = Nokogiri::HTML(open('http://cloud.tfl.gov.uk/TrackerNet/LineStatus')).remove_namespaces! - sorted_lines = xml.xpath('//linestatus').sort_by {|xml| xml.xpath('.//line/@name').text} - if @line && @line != 'summary' - l_s_xml = nil - sorted_lines.each {|xml| l_s_xml = xml and break if xml.xpath('.//line/@name').text.upcase.match @line.upcase} - @output << ("#{l_s_xml.xpath('.//line/@name').text}: #{l_s_xml.xpath('.//status/@description').text}. #{l_s_xml.xpath('./@statusdetails').text}" rescue "Could not find a line matching '#{@line}'") - else - line_summary = '' - sorted_lines.each {|xml| line_summary << "#{xml.xpath('.//line/@name').text}: #{xml.xpath('./@statusdetails').text} " unless xml.xpath('./@statusdetails').text == ''} - @output << (line_summary == '' ? 'Good Serice on all lines.' : "#{line_summary}Good Service on all other lines.") - end -end - -#TODO: store graph as yaml in a way that works with add_change_time -begin - if help - ops.to_s.split(/\n\s*|\s{2,}/).each{|opt| @output << opt} - elsif @line - get_line_status - elsif @from_station && @to_station - #@graph = YAML::load(File.open('graph.yaml')) - @graph = JSON.parse(File.open('graph.json').read) - calculate_shortest_route - elsif @from_station || @to_station - @output << 'Please specify the station you are travelling from (-f) and the station you are travelling to (-t).' - elsif ARGV.size == 2 - #@graph = YAML::load(File.open('graph.yaml')) - @graph = JSON.parse(File.open('graph.json').read) - @from_station = ARGV[0] - @to_station = ARGV[1] - calculate_shortest_route - elsif ARGV.size == 1 - @line = ARGV[0] - get_line_status - else - get_line_status - end - puts @output.join ' ' -rescue StandardError => e - puts (@output << e.message).join ' ' - puts e.message - puts e.backtrace -end -- cgit v1.2.3