From 0f66eee72b24eb37ca8c065bdd5b0d5c08d99bd2 Mon Sep 17 00:00:00 2001 From: Ian Fijolek Date: Sat, 22 Nov 2014 17:05:03 -0800 Subject: [PATCH] Initial commit --- .gitignore | 2 + Gemfile | 16 +++ Gemfile.lock | 65 +++++++++ README.md | 18 +++ Rakefile | 2 + app.json | 18 +++ build_agency_bounds.rb | 48 +++++++ cleartransit.rb | 126 ++++++++++++++++++ config.ru | 2 + continue_agency_bounds.rb | 54 ++++++++ db/migrate/20140118012229_initial_db.rb | 27 ++++ db/schema.rb | 36 +++++ environments.rb | 29 ++++ lib/nextbus.rb | 41 ++++++ lib/utils.rb | 35 +++++ models/active.rb | 5 + models/agency_bounds.rb | 8 ++ models/route_matches.rb | 9 ++ routes.rb | 168 ++++++++++++++++++++++++ tags | 24 ++++ 20 files changed, 733 insertions(+) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 README.md create mode 100644 Rakefile create mode 100644 app.json create mode 100644 build_agency_bounds.rb create mode 100644 cleartransit.rb create mode 100644 config.ru create mode 100644 continue_agency_bounds.rb create mode 100644 db/migrate/20140118012229_initial_db.rb create mode 100644 db/schema.rb create mode 100644 environments.rb create mode 100644 lib/nextbus.rb create mode 100644 lib/utils.rb create mode 100644 models/active.rb create mode 100644 models/agency_bounds.rb create mode 100644 models/route_matches.rb create mode 100644 routes.rb create mode 100644 tags diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5162913 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +dev.db +.bundle diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..3b4b292 --- /dev/null +++ b/Gemfile @@ -0,0 +1,16 @@ +source 'https://rubygems.org' +ruby '1.9.3' +gem 'sinatra' +gem 'activerecord' +gem 'sinatra-activerecord' +gem 'httparty' +gem 'geocoder' +gem 'algorithms' +gem 'levenshtein-ffi', :require => 'levenshtein' + +group :development, :test do + gem 'sqlite3' +end +group :production, :staging do + gem 'pg' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..1f05ce0 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,65 @@ +GEM + remote: https://rubygems.org/ + specs: + activemodel (4.0.2) + activesupport (= 4.0.2) + builder (~> 3.1.0) + activerecord (4.0.2) + activemodel (= 4.0.2) + activerecord-deprecated_finders (~> 1.0.2) + activesupport (= 4.0.2) + arel (~> 4.0.0) + activerecord-deprecated_finders (1.0.3) + activesupport (4.0.2) + i18n (~> 0.6, >= 0.6.4) + minitest (~> 4.2) + multi_json (~> 1.3) + thread_safe (~> 0.1) + tzinfo (~> 0.3.37) + algorithms (0.6.1) + arel (4.0.1) + atomic (1.1.14) + builder (3.1.4) + ffi (1.1.5) + geocoder (1.1.9) + httparty (0.12.0) + json (~> 1.8) + multi_xml (>= 0.5.2) + i18n (0.6.9) + json (1.8.1) + levenshtein-ffi (1.0.3) + ffi + ffi (~> 1.1.5) + minitest (4.7.5) + multi_json (1.8.4) + multi_xml (0.5.5) + pg (0.17.1) + rack (1.5.2) + rack-protection (1.5.1) + rack + sinatra (1.4.4) + rack (~> 1.4) + rack-protection (~> 1.4) + tilt (~> 1.3, >= 1.3.4) + sinatra-activerecord (1.2.3) + activerecord (>= 3.0) + sinatra (~> 1.0) + sqlite3 (1.3.8) + thread_safe (0.1.3) + atomic + tilt (1.4.1) + tzinfo (0.3.38) + +PLATFORMS + ruby + +DEPENDENCIES + activerecord + algorithms + geocoder + httparty + levenshtein-ffi + pg + sinatra + sinatra-activerecord + sqlite3 diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae6b39d --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +Clear Transit Server +==================== + +What is this? +------------- +This is a server component of Clear Transit. An Android and Google Glass application for retrieving transit times from the NextBus Api + +The Android client for this Heroku app is located at [Clear Transit](https://github.com/IamTheFij/ClearTransit") + +I'm really not much of a Ruby dev, so this may be a little hacky. It's designed to run on Heroku using a Postgres DB. +The initial database is built by running: `ruby ./build_agency_bounds.rb` or, if it fails midway, running `ruby ./continue_agency_bounds.rb`. + +Heroku button +------------- +This buttons should install the server on Heroku. Should... no promises. + +[![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy?template=https://github.com/IamTheFij/ClearTransitServer) + diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..29e5cd4 --- /dev/null +++ b/Rakefile @@ -0,0 +1,2 @@ +require './routes.rb' +require 'sinatra/activerecord/rake' diff --git a/app.json b/app.json new file mode 100644 index 0000000..03fde4a --- /dev/null +++ b/app.json @@ -0,0 +1,18 @@ +{ + "name": "Clear Transit", + "description": "Clear Transit web server", + "keywords": [ + "transit", + "Sinatra", + "Ruby" + ], + "website": "https://github.com/IamTheFij/ClearTransit-Web", + "repository": "https://github.com/IamTheFij/ClearTransit-Web", + "success_url": "/working", + "scripts": { + "postdeploy": "ruby ./build_agency_bounds.rb" + }, + "addons": [ + "heroku-postgresql:hobby-dev" + ] +} diff --git a/build_agency_bounds.rb b/build_agency_bounds.rb new file mode 100644 index 0000000..04189f5 --- /dev/null +++ b/build_agency_bounds.rb @@ -0,0 +1,48 @@ +require './environments.rb' +require './models/active.rb' +require './lib/nextbus.rb' +require './lib/utils.rb' + +agencies = {} + +AgencyBound.all.each do |bound| + bound.lat_min = nil + bound.lat_max = nil + bound.lon_min = nil + bound.lon_max = nil + agencies[bound.agency] = bound +end + +NextBus.agency_list.parsed_response['agency'].each do |agency| + unless agencies.has_key?(agency['tag']) + agencies[agency['tag']] = AgencyBound.new( + agency: agency['tag'], + lat_min: nil, + lat_max: nil, + lon_min: nil, + lon_max: nil + ) + end +end + +agencies.each_value do |bound| + routes = NextBus.routes(bound.agency).parsed_response['route'] + print "#{routes}\n" + unless routes.nil? + unless routes.kind_of?(Array) + routes = [ routes ] + end + routes.each do |route| + route_config = NextBus.route_config(bound.agency, route['tag']).parsed_response + #print "Route Config:\n#{route_config}\n" + route_config = route_config['route'] + print "Agency: #{bound.agency} Route: #{route['tag']} Bound Min: #{bound.lat_min}, Config Min #{route_config['latMin']}\n" + bound.lat_min = min_val(bound.lat_min, route_config['latMin'].to_f) + bound.lat_max = max_val(bound.lat_max, route_config['latMax'].to_f) + bound.lon_min = min_val(bound.lon_min, route_config['lonMin'].to_f) + bound.lon_max = max_val(bound.lon_max, route_config['lonMax'].to_f) + end + bound.save + end +end + diff --git a/cleartransit.rb b/cleartransit.rb new file mode 100644 index 0000000..3a5bd18 --- /dev/null +++ b/cleartransit.rb @@ -0,0 +1,126 @@ +require 'sinatra' +#require 'sinatra/activerecord' +require 'json' +require 'geocoder' +require 'algorithms' +require 'levenshtein' + +require './environments.rb' +require './models/active.rb' +require './lib/nextbus.rb' +require './lib/utils.rb' + +# Uses bounds of the user and returns any agencies they are in the bounds of +def get_contained_agencies(lat, lon) + agencies = [] + + AgencyBound.all.each do |bound| + if lat.between?(bound.lat_min, bound.lat_max) and lon.between?(bound.lon_min, bound.lon_max) + agencies.push(bound.agency) + end + end + + return agencies +end + +# Takes user input and agency and tries to find the most likely intended route +def find_best_routes(agency, user_route) + + # Remove Line from end of user_route + user_route = user_route.downcase + user_route_words = user_route.split + if user_route_words.last == 'line' + user_route_words.pop + end + user_route = user_route_words.join(' ') + + # TODO: Remove before Prod + routes = NextBus.routes(agency).parsed_response + #routes = { + # 'route' => [ + # { + # 'title' => 'F - Market Warves', + # 'tag' => 'F' + # }, + # { + # 'title' => 'P - ', + # 'tag' => 'F' + # } + # ] + #} + + probable_routes = Containers::MinHeap.new + + routes['route'].each do |route| + dist = min_val(Levenshtein.distance(user_route, route['title'].downcase), Levenshtein.distance(user_route, route['tag'].downcase)) + route_match = RouteMatch.new( + user_route: user_route, + match_route: route['tag'], + match_title: route['title'], + agency: agency, + # TODO: Depending on App implementation, default to false and set true or oposite + accepted: false, + distance: dist + ) + probable_routes.push(dist, route_match) + end + + routes = [] + while not probable_routes.empty? and routes.length < 10 + routes.push(probable_routes.pop) + end + + return routes +end + +# Takes an agency, route, and lat-lon and returns nearest predictions +def get_nearest_predictions(agency, route, lat, lon) + current_location = [lat, lon] + print current_location + route_config = NextBus.route_config(agency, route).parsed_response + + min_stops = Containers::MinHeap.new + + route_config['route']['stop'].each do |stop| + dist = Geocoder::Calculations.distance_between(current_location, [stop['lat'], stop['lon']]) + + min_stops.push(dist, stop) + end + + #stop_directions = build_direction_map(route_config) + + stops = [], route_stops = [] + + while not min_stops.empty? and stops.length < 5 + stop = min_stops.pop + # Build array out of the 5 closest stops + stops.push(stop) + # Push route stops for fetching predictions + route_stops.push({:route => route, :stop => stop['tag']}) + end + + min_stops.clear + + prediction_multiple = NextBus.prediction_multiple(agency, route_stops).parsed_response + + # TODO: Possibly adjust sort order here if these appear to be wrong + + return prediction_multiple +end + +def build_direction_map(route_config) + # TODO: DEPRECIATE + stop_directions = {} + + route_config['route']['direction'].each do |direction| + direction['stop'].each do |stop| + unless stop_directions[stop['tag']] + stop_directions[stop['tag']] = [] + end + stop_directions[stop['tag']].push(direction['name']) + end + end + + return stop_directions +end + diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..150337e --- /dev/null +++ b/config.ru @@ -0,0 +1,2 @@ +require './routes.rb' +run Sinatra::Application diff --git a/continue_agency_bounds.rb b/continue_agency_bounds.rb new file mode 100644 index 0000000..ad3ebdb --- /dev/null +++ b/continue_agency_bounds.rb @@ -0,0 +1,54 @@ +require './environments.rb' +require './models/active.rb' +require './lib/nextbus.rb' +require './lib/utils.rb' + +agencies = {} + +AgencyBound.all.each do |bound| + if bound.lat_max != nil + agencies[bound.agency] = bound + end +end + +NextBus.agency_list.parsed_response['agency'].each do |agency| + if agencies.has_key?(agency['tag']) + agencies.delete(agency['tag']) + else + agencies[agency['tag']] = AgencyBound.new( + agency: agency['tag'], + lat_min: nil, + lat_max: nil, + lon_min: nil, + lon_max: nil + ) + end +end + +print "Agencies to get? #{agencies}" + +if agencies.empty? + print "No new agencies" +end + +agencies.each_value do |bound| + routes = NextBus.routes(bound.agency).parsed_response['route'] + print "#{routes}\n" + unless routes.nil? + unless routes.kind_of?(Array) + routes = [ routes ] + end + routes.each do |route| + route_config = NextBus.route_config(bound.agency, route['tag']).parsed_response + #print "Route Config:\n#{route_config}\n" + route_config = route_config['route'] + print "Agency: #{bound.agency} Route: #{route['tag']} Bound Min: #{bound.lat_min}, Config Min #{route_config['latMin']}\n" + bound.lat_min = min_val(bound.lat_min, route_config['latMin'].to_f) + bound.lat_max = max_val(bound.lat_max, route_config['latMax'].to_f) + bound.lon_min = min_val(bound.lon_min, route_config['lonMin'].to_f) + bound.lon_max = max_val(bound.lon_max, route_config['lonMax'].to_f) + end + bound.save + end +end + diff --git a/db/migrate/20140118012229_initial_db.rb b/db/migrate/20140118012229_initial_db.rb new file mode 100644 index 0000000..5d269ee --- /dev/null +++ b/db/migrate/20140118012229_initial_db.rb @@ -0,0 +1,27 @@ +class InitialDb < ActiveRecord::Migration + def up + create_table :route_matches do |t| + t.string :user_route + t.string :match_route + t.string :match_title + t.integer :distance + t.string :agency + t.boolean :accepted + t.timestamps + end + + create_table :agency_bounds do |t| + t.string :agency + t.float :lat_max + t.float :lat_min + t.float :lon_max + t.float :lon_min + t.timestamps + end + end + + def down + drop_table :route_matches + drop_table :agency_bounds + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..076bbbe --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,36 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 20140118012229) do + + create_table "agency_bounds", force: true do |t| + t.string "agency" + t.float "lat_max" + t.float "lat_min" + t.float "lon_max" + t.float "lon_min" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table "route_matches", force: true do |t| + t.string "user_route" + t.string "match_route" + t.string "match_title" + t.integer "distance" + t.string "agency" + t.boolean "accepted" + t.datetime "created_at" + t.datetime "updated_at" + end + +end diff --git a/environments.rb b/environments.rb new file mode 100644 index 0000000..cf53361 --- /dev/null +++ b/environments.rb @@ -0,0 +1,29 @@ +require 'cgi' +require 'uri' +require 'sinatra' +require 'sinatra/activerecord' + +configure :development do + set :database, 'sqlite:///dev.db' + set :show_exceptions, true +end + +configure :staging, :production do + begin + db = URI.parse(ENV["DATABASE_URL"]) + rescue URI::InvalidURIError + raise "Invalid DATABASE_URL" + end + + ActiveRecord::Base.establish_connection( + :adapter => db.scheme == 'postgres' ? 'postgresql' : db.scheme, + :encoding => 'unicode', + :pool => 5, + :database => db.path[1..-1], + :username => db.user, + :password => db.password, + :host => db.host, + :port => db.port + ) +end + diff --git a/lib/nextbus.rb b/lib/nextbus.rb new file mode 100644 index 0000000..0120b9a --- /dev/null +++ b/lib/nextbus.rb @@ -0,0 +1,41 @@ +require 'httparty' + +class NextBus + include HTTParty + + def self.get_command(command, query={}) + # Set the command + query[:command] = command + get('http://webservices.nextbus.com/service/publicJSONFeed', :query => query) + end + + def self.agency_list + get_command('agencyList') + end + + def self.routes(agency) + get_command('routeList', { :a => agency }) + end + + def self.route_config(agency, route) + get_command('routeConfig', { :a => agency, :r => route }) + end + + def self.prediction(agency, route, stop, short_titles=true) + get_command('predictions', { :a => agency, :r => route, :s => stop, :useShortTitles => short_titles }) + end + + def self.prediction_multiple(agency, route_stops=[]) + endpoint = 'http://webservices.nextbus.com/service/publicJSONFeed' + endpoint += '?command=predictionsForMultiStops' + endpoint += '&a=' + agency + route_stops.each do |route_stop| + endpoint += '&stops=' + route_stop[:route] + '%7C' + route_stop[:stop] + end + + print endpoint + + get(endpoint) + end + +end diff --git a/lib/utils.rb b/lib/utils.rb new file mode 100644 index 0000000..d5de401 --- /dev/null +++ b/lib/utils.rb @@ -0,0 +1,35 @@ +def min_val(v1, v2) + if v2 == nil + return v1 + elsif v1 == nil + return v2 + elsif v1 < v2 + return v1 + else + return v2 + end +end + +def max_val(v1, v2) + if v2 == nil + return v1 + elsif v1 == nil + return v2 + elsif v1 > v2 + return v1 + else + return v2 + end +end + +def is_number?(object) + true if Float(object) rescue false +end + +def to_f_nil(s) + begin + return Float(s) + rescue + return nil + end +end diff --git a/models/active.rb b/models/active.rb new file mode 100644 index 0000000..01325fa --- /dev/null +++ b/models/active.rb @@ -0,0 +1,5 @@ +# Require individual models here +require 'active_record' + +require './models/agency_bounds.rb' +require './models/route_matches.rb' diff --git a/models/agency_bounds.rb b/models/agency_bounds.rb new file mode 100644 index 0000000..a066a66 --- /dev/null +++ b/models/agency_bounds.rb @@ -0,0 +1,8 @@ +# Table for storing boundries of given agencies to auto-detect +class AgencyBound < ActiveRecord::Base + validates :agency, :lat_max, :lat_min, :lon_max, :lon_min, presence: true + # validates :lat_max, presence: true + # validates :lat_min, presence: true + # validates :lon_max, presence: true + # validates :lon_min, presence: true +end diff --git a/models/route_matches.rb b/models/route_matches.rb new file mode 100644 index 0000000..9dfce97 --- /dev/null +++ b/models/route_matches.rb @@ -0,0 +1,9 @@ +# Table for storing the user input and levenshtein match +# Will be used to assess accuracy and user acceptance +class RouteMatch < ActiveRecord::Base + validates :user_route, :match_route, :distance, presence: true + # validates :match_route, presence: true + # validates :distance, presence: true + # TODO: Default value for accepted? +end + diff --git a/routes.rb b/routes.rb new file mode 100644 index 0000000..5510487 --- /dev/null +++ b/routes.rb @@ -0,0 +1,168 @@ +require 'sinatra' +require 'sinatra/activerecord' +require 'json' + +require './cleartransit.rb' +require './lib/nextbus.rb' + +get '/' do + # TODO: Provide link to download the app + 'Hello world! Go check out the repo: CanHazMuni-Web' +end + +get '/working' do + 'Hello world! Go check out the repo: CanHazMuni-Web' +end + +# List all agencies +get '/agency' do + status 200 + body(NextBus.agency_list.to_json) +end + +# List all routes in an agency +get '/agency/:agency/route' do |agency| + status 200 + body(NextBus.routes(agency).to_json) +end + +# List all stops in a route +get '/agency/:agency/route/:route/stop' do |agency, route| + status 200 + body(NextBus.route_config(agency, route).to_json) +end + +# Get predictions for a given stop +get '/agency/:agency/route/:route/stop/:stop' do |agency, route, stop| + status 200 + body(NextBus.prediction(agency, route, stop).to_json) +end + +# Get predictions for nearest stop on given agency and route +get '/agency/:agency/route/:route/nearest' do |agency, route| + # will match /agency/sf-muni/route/F/nearest?lat=37.804016399999995&lon=-122.40376609999998 + lat = to_f_nil(params[:lat]) + lon = to_f_nil(params[:lon]) + if params[:lat].nil? or lon.nil? or route.nil? + status 404 + else + status 200 + body(get_nearest_predictions(agency, route, lat, lon).to_json) + end +end + +# Primary feature +# Usine Lat, Lon, determine nearest agency, and stops and provide predictions +=begin rdoc +get '/icanhaz' do + # will match /icanhaz?lat=37.804016399999995&lon=-122.40376609999998&route=F%20line + + if params[:lat].nil? or params[:lon].nil? or params[:route].nil? + status 404 + else + status 200 + # TODO: Implement + body({:thing1 => 'value'}.to_json) + end +end +=end + +# Usine Lat, Lon and given agency, and user route and provide predictions +get '/icanhaz/:agency' do |agency| + # will match /icanhaz/sf-muni?lat=37.804016399999995&lon=-122.40376609999998&route=F%20line + lat = to_f_nil(params[:lat]) + lon = to_f_nil(params[:lon]) + if agency.nil? or lat.nil? or lon.nil? or params[:route].nil? + body("Error: Post convert agency: #{agency} lat: #{lat} lon: #{lon} route: #{params[:route]}") + status 404 + else + status 200 + + # Use only the best route for now + route = find_best_routes(agency, params[:route])[0] + + body(get_nearest_predictions(agency, route[:tag], params[:lat], params[:lon]).to_json) + end +end + +get '/icanhaz/agency/route' do + # will match /icanhaz/agency/route?lat=37.804016399999995&lon=-122.40376609999998&route=F%20line + lat = to_f_nil(params[:lat]) + lon = to_f_nil(params[:lon]) + if lat.nil? or lon.nil? or params[:route].nil? + body("Error: Post convert lat: #{lat} lon: #{lon} route: #{params[:route]}") + status 404 + else + agencies = get_contained_agencies(lat, lon) + + if agencies.empty? + # TODO: Find better status code for something like this + status 404 + + body("No service area found") + else + status 200 + + agency = agencies[0] + user_route = params[:route]; + + # Get the most likely route in the agency + # TODO: Match all the results returned to the RouteMatch class and insert all. + # This will be super useful to provide a scroll list for the user to pick + # alternate matches. The client can then mark which was actually matched. + # When doing this, will probably want a unique "session" Id as well + route_match = find_best_routes(agency, user_route)[0] + route_match.save + + body(route_match.to_json) + end + end +end + +# Usine given agency, and user route, guess the route +get '/icanhaz/:agency/route' do |agency| + # will match /icanhaz/sf-muni/route?route=F%20line + if agency.nil? or params[:route].nil? + status 404 + else + status 200 + + user_route = params[:route]; + + # Get the most likely route in the agency + # TODO: Match all the results returned to the RouteMatch class and insert all. + # This will be super useful to provide a scroll list for the user to pick + # alternate matches. The client can then mark which was actually matched. + # When doing this, will probably want a unique "session" Id as well + route_match = find_best_routes(agency, user_route)[0] + route_match.save + + body(route_match.to_json) + end +end + +# Usine given agency, and actual route, get the predictions +get '/icanhaz/:agency/route/:route' do |agency, route| + # will match /icanhaz/sf-muni/route/F?lat=37.804016399999995&lon=-122.40376609999998 + # Optionally takes &match_id=123 + lat = to_f_nil(params[:lat]) + lon = to_f_nil(params[:lon]) + if agency.nil? or route.nil? or lat.nil? or lon.nil? + body("Error: Post convert lat: #{lat} lon: #{lon} route: #{params[:route]}") + status 404 + else + status 200 + + # Get Id of databse tracked route match and indicate that it was accepted for predictions + if not params[:match_id].nil? + RouteMatch.update(params[:match_id], :accepted => true) + end + + body(get_nearest_predictions(agency, route, lat, lon).to_json) + end +end + +get '/route_matches' do + status 200 + body(RouteMatch.all.to_json) +end diff --git a/tags b/tags new file mode 100644 index 0000000..2d7a2d7 --- /dev/null +++ b/tags @@ -0,0 +1,24 @@ +!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ +!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ +!_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/ +!_TAG_PROGRAM_NAME Exuberant Ctags // +!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ +!_TAG_PROGRAM_VERSION 5.8 // +AgencyBound models/agency_bounds.rb /^class AgencyBound < ActiveRecord::Base$/;" c +InitialDb db/migrate/20140118012229_initial_db.rb /^class InitialDb < ActiveRecord::Migration$/;" c +NextBus lib/nextbus.rb /^class NextBus$/;" c +RouteMatch models/route_matches.rb /^class RouteMatch < ActiveRecord::Base$/;" c +agency_list lib/nextbus.rb /^ def self.agency_list$/;" F class:NextBus +build_direction_map canhazmuni.rb /^def build_direction_map(route_config)$/;" f +down db/migrate/20140118012229_initial_db.rb /^ def down$/;" f class:InitialDb +find_best_routes canhazmuni.rb /^def find_best_routes(agency, user_route)$/;" f +get_command lib/nextbus.rb /^ def self.get_command(command, query={})$/;" F class:NextBus +get_contained_agencies canhazmuni.rb /^def get_contained_agencies(lat, lon)$/;" f +get_nearest_predictions canhazmuni.rb /^def get_nearest_predictions(agency, route, lat, lon)$/;" f +max_val lib/utils.rb /^def max_val(v1, v2)$/;" f +min_val lib/utils.rb /^def min_val(v1, v2)$/;" f +prediction lib/nextbus.rb /^ def self.prediction(agency, route, stop, short_titles=true)$/;" F class:NextBus +prediction_multiple lib/nextbus.rb /^ def self.prediction_multiple(agency, route_stops=[])$/;" F class:NextBus +route_config lib/nextbus.rb /^ def self.route_config(agency, route)$/;" F class:NextBus +routes lib/nextbus.rb /^ def self.routes(agency)$/;" F class:NextBus +up db/migrate/20140118012229_initial_db.rb /^ def up$/;" f class:InitialDb