forked from codeforamerica/transpochoices
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchoices.rb
174 lines (150 loc) · 6.22 KB
/
choices.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
require 'net/http'
require 'cgi'
require './fare.rb'
require './constants.rb'
require 'pp'
def get_info_from_bing(params)
base_url="http://dev.virtualearth.net/REST/v1/Routes/"
query_params = "?" + {
"wayPoint.1" => params[:origin],
"waypoint.2" => params[:destination],
"dateTime" => params[:time] || Time.now.strftime("%H:%M"),
"timeType" => "Arrival",
"key" => ENV['BING_KEY']
}.map {|k,v| "#{k}=#{CGI.escape(v)}"}*"&"
modes=%w{driving walking transit}
results =modes.map do |mode|
Thread.new do
begin
usable_url=URI.parse(base_url+mode+query_params)
#puts "calling url #{usable_url}"
response = JSON.parse(Net::HTTP.get(usable_url))
resource = response["resourceSets"][0]["resources"][0]
info = {
:distance=>resource["travelDistance"],
:duration=>resource["travelDuration"]
}
[mode,resource]
rescue
[mode,nil]
end
end
end
results.map!(&:value)
Hash[*results.flatten]
end
def generic_by_bing_resource(resource)
{
:distance=>resource["travelDistance"],
:duration=>resource["travelDuration"]
}
end
def calculate_transit_by_bing_resource(resource)
info_by_type = resource["routeLegs"].map do |leg|
leg["itineraryItems"].map do |item|
type = item["details"][0]["maneuverType"]
type = "TakeTransit" if type == "Transfer" #HACK FOR NOW
{
:type=>type,
:distance=>item["travelDistance"],
:duration=>item["travelDuration"],
:item=>item
}
end
end.flatten.group_by {|i| i[:type]}
walking_duration = info_by_type["Walk"].inject(0) {|s,x| s+x[:duration]}
transit_duration = info_by_type["TakeTransit"].inject(0) {|s,x| s+x[:duration]}
#calculate the total fare
#look at a TakeTransit, it has:
#child itinerary items, with [details][maneuverType] == TransitDepart and TransitArrive, each with [details][names] = [station name]
#[transitLine][agencyName] == agency name
#[transitLine][abbreviatedName/verboseName] == route name of some sort.
#general strategy:
#chunk up the routes by agency
#sum: for each agency, parse up the fares, then calculate the best fare for that series of rides.
#puts "all info = "
#require 'pp'
#pp info_by_type
cost = info_by_type["TakeTransit"].map {|x| x[:item]}.chunk {|x| (x["transitLine"] || {})["agencyName"]}.inject(0) do |sum,(agency,agency_chunk)|
#puts "doing: #{agency}"
dir,agency_id = GTFS_MAPPING[agency]
break nil if (dir.nil? || agency_id.nil?) #if we don't have the agency's info, don't try to calculate a fare for it.
#fares_for(agency)
fares = Fare.load(dir+"/fare_attributes.txt",dir+"/fare_rules.txt") #todo: check that rules exist
fares = fares[agency_id] || fares[nil]
routes = csv_to_hash(dir+"/routes.txt")
stops = csv_to_hash(dir+"/stops.txt")
rides = agency_chunk.map do |itinerary_item|
#puts "itinerary = "
#pp itinerary_item
#horrible assumption here, should check maneuvertype or something
start_match = Amatch::Levenshtein.new(itinerary_item["childItineraryItems"][0]["details"][0]["names"][0].downcase)
finish_match= Amatch::Levenshtein.new(itinerary_item["childItineraryItems"][1]["details"][0]["names"][0].downcase)
Ride.new(:start_time=>0, #fancy_parse(start["time"]), TODO: actually parse time, don't give infinite transfer capability
:end_time=>0, #fancy_parse(finish["time"]),
:origin=> stops.min_by {|s| start_match.match((s["stop_name"] || "").downcase)}["zone_id"],
:destination=> stops.min_by {|s| finish_match.match((s["stop_name"] || "").downcase)}["zone_id"],
:route=>(routes.find {|r| r["agency_id"]==agency_id && r.values_at("route_long_name","route_short_name").include?(itinerary_item["transitLine"]["verboseName"])} || {})["route_id"])
end
puts "got some rides for #{agency}:"
pp rides
fare = best_fare(rides,fares)
break nil if fare.nil?
sum += fare
end
{
:duration=>resource["travelDuration"],
:calories=>walking_duration * CALORIES_PER_SECOND_WALKING + transit_duration * CALORIES_PER_SECOND_SITTING,
:emissions=> resource[:distance] * BUS_LBS_CO2_PASSENGER_KM,
:cost=>(cost.nil? ? nil : cost.to_f)
}
end
get "/info_for_route_bing" do
results = get_info_from_bing(params)
if params[:raw_data]=="yes_please"
#puts "here"
return [200,{},JSON.pretty_generate(results)]
end
if (resource=results["driving"])
results["driving"]=generic_by_bing_resource(resource)
results["driving"][:emissions] = results["driving"][:distance] * SOV_LBS_CO2_PASSENGER_KM
results["driving"][:cost] = (results["driving"][:distance] * AAA_COST_PER_KM).round(2)
results["driving"][:calories] = (results["driving"][:duration] * CALORIES_PER_SECOND_SITTING).round(2)
results["taxi"]=results["driving"].clone
#taxi is like driving, but with a taxi rate
start_point = resource["routeLegs"][0]["actualStart"]["coordinates"]
closest_rate = TAXI_RATES.sort_by {|rate| (rate[:lat]-start_point[0])**2 + (rate[:lon]-start_point[1])**2}.first
puts "closest_rate = #{closest_rate.inspect}"
approx_time_waiting = [(results["taxi"][:duration] - results["taxi"][:distance] * AVG_CAR_SPEED),0].max
results["taxi"][:cost] = (closest_rate[:initial_charge] + closest_rate[:per_km] * (results["taxi"][:distance] - closest_rate[:initial_increment_km]) + (approx_time_waiting/3600) * closest_rate[:per_hour_waiting]).round(2)
end
if (resource=results["walking"])
results["walking"]=generic_by_bing_resource(resource)
results["walking"][:calories]=(results["walking"][:duration] * CALORIES_PER_SECOND_WALKING).round(1)
results["walking"][:emissions]=0
results["walking"][:cost]=0
results["biking"]=generic_by_bing_resource(resource)
results["biking"][:duration] = (results["biking"][:distance] / BIKE_SPEED_IN_KM_PER_SECOND).round(0)
results["biking"][:calories] = results["biking"][:distance] * CALORIES_PER_KM_BIKING
results["biking"][:emissions] = 0
results["biking"][:cost]= (results["biking"][:distance] * BIKING_COST_PER_KM).round(2)
end
if (resource=results["transit"])
resource[:distance] = results["driving"][:distance]
results["transit"] = calculate_transit_by_bing_resource(resource)
end
output = {:units=>
{
:distance=>"km",
:duration=>"sec",
:emissions=>"kg_co2",
:cost=>"usd",
:calories=>"cal"
},
:results=>results
}
[200,{},JSON.pretty_generate(output)]
end
get "/" do
File.read(File.join('public', 'index.html'))
end