From 5a23b9aac60f121d6674a9f5666aee6e116be53a Mon Sep 17 00:00:00 2001 From: Matt Daloisio Date: Wed, 9 Dec 2020 16:07:41 +0000 Subject: [PATCH] refining algorithm --- app/controllers/midways_controller.rb | 14 +- app/services/midpoint_service.rb | 132 +++++++++++++++++- app/views/midways/new.html.erb | 5 + .../20201208162629_add_choice_to_midway.rb | 5 + db/schema.rb | 3 +- 5 files changed, 149 insertions(+), 10 deletions(-) create mode 100644 db/migrate/20201208162629_add_choice_to_midway.rb diff --git a/app/controllers/midways_controller.rb b/app/controllers/midways_controller.rb index c776854..57fa3bb 100644 --- a/app/controllers/midways_controller.rb +++ b/app/controllers/midways_controller.rb @@ -88,16 +88,24 @@ def create #accesses the venue type and any keywords the user wants @midway.venue_type = params[:midway][:venue_type].downcase @midway.keyword = params[:midway][:keyword].downcase + @midway.choice = params[:midway][:choice].downcase #assigns the midpoint midpoint_service = MidpointService.new(addresses: participants_locations, time_option: time_option, future_datetime: future_datetime) - midpoint_coordinates = midpoint_service.calculate[0] + if @midway.choice == "efficient" + results = midpoint_service.calculate_efficient + midpoint_coordinates = results[0] + durations = results[1] + else + results = midpoint_service.calculate_equal + midpoint_coordinates = results[0] + durations = results[1] + end @midpoint = "#{midpoint_coordinates[:lat]},#{midpoint_coordinates[:lng]}" @midway.midpoint = @midpoint @midway.save! #assigns a duration to each Midway Participant - durations = midpoint_service.calculate[1] participants.each_with_index do |participant, index| participant.duration_to_midpoint = durations[index] participant.save! @@ -229,7 +237,7 @@ def show private def midway_params - params.require(:midway).permit(:friends, :time_option, :future_time, :future_date, :future_datetime, :venue_type, :venue, :keyword) + params.require(:midway).permit(:friends, :time_option, :future_time, :future_date, :future_datetime, :venue_type, :venue, :keyword, :choice) end def venue_params diff --git a/app/services/midpoint_service.rb b/app/services/midpoint_service.rb index 6054d62..3c09f85 100644 --- a/app/services/midpoint_service.rb +++ b/app/services/midpoint_service.rb @@ -11,7 +11,7 @@ def initialize(attributes = {}) @midpoint = { lat: 0, lng: 0 } end - def calculate + def calculate_efficient # find the average lat and long of all user locations (set this to variable D) - this is the first attempt at the midpoint (based purely off geographical equidistance) @@ -33,7 +33,8 @@ def calculate p address_query candidates = [] - 5.times do + user_candidates = [] + 3.times do url = "https://api.distancematrix.ai/maps/api/distancematrix/json?units=imperial&origins=#{address_query}&destinations=#{@midpoint[:lat]},#{@midpoint[:lng]}&transit_mode=subway&mode=transit&#{url_time}&key=#{ENV["DISTANCE_MATRIX_KEY"]}" @@ -48,6 +49,7 @@ def calculate durations = @addresses.map { |address| address[:duration] } max_duration = durations.max md_index = durations.index(max_duration) + min_duration = durations.min durations_text = @addresses.map { |address| address[:duration_text] } @@ -57,15 +59,38 @@ def calculate percentage = avg / max_duration @midpoint[:lat] = lerp(@addresses[md_index][:lat], @midpoint[:lat], percentage) @midpoint[:lng] = lerp(@addresses[md_index][:lng], @midpoint[:lng], percentage) - min_duration = durations.min # stores as candidate_duration the discrepancy between the largest/smallest travel time candidate_duration = (max_duration - min_duration).abs - candidates.push([@midpoint, candidate_duration, durations_text]) + candidates.push([@midpoint, candidate_duration, durations_text, avg]) + end + + # also puts as candidate midpoints the home locations of each of the users + + @addresses.each do |address| + url = "https://api.distancematrix.ai/maps/api/distancematrix/json?units=imperial&origins=#{address_query}&destinations=#{address[:lat]},#{address[:lng]}&transit_mode=subway&mode=transit&#{url_time}&key=#{ENV["DISTANCE_MATRIX_KEY"]}" + + @matrix = JSON.parse(open(url).read, symbolize_names: true) + + @addresses.each_with_index do |address, index| + address[:duration] = @matrix[:rows][index+1][:elements][0][:duration][:value] + address[:duration_text] = @matrix[:rows][index+1][:elements][0][:duration][:text] + end + + # determines the longest route between a user and @midpoint + durations = @addresses.map { |address| address[:duration] } + max_duration = durations.max + md_index = durations.index(max_duration) + min_duration = durations.min + durations_text = @addresses.map { |address| address[:duration_text] } + avg = (durations.sum / durations.count).to_f + + candidate_duration = (max_duration - min_duration).abs + user_candidates.push([address, candidate_duration, durations_text, avg]) end - # of the 5 attempts, selects the midpoint with the smallest discrepancy (candidate_duration) + # of all the 5 attempts, selects the midpoint with the smallest discrepancy (candidate_duration) # returns the chosen midpoint candidate_durations = candidates.map { |candidate| candidate[1] } @@ -73,12 +98,107 @@ def calculate mcd_index = candidate_durations.index(minimum_candidate_duration) candidate_midpoints = candidates.map { |candidate| candidate[0] } @midpoint = candidate_midpoints[mcd_index] + p "Candidates:" p candidates - p candidate_durations + p "_________" + # p candidate_durations + p "User Candidates" + p user_candidates + p "_________" p @midpoint + # then it checks that this midpoint does not have an average duration greater than any of the user locations + # if it does it reassigns + + candidate_averages = candidates.map { |candidate| candidate[3] } + midpoint_average = candidate_averages[mcd_index] + p midpoint_average + user_candidate_midpoints = user_candidates.map { |candidate| candidate[0] } + user_candidate_averages = user_candidates.map { |candidate| candidate[3] } + min_user_candidate_average = user_candidate_averages.min + p min_user_candidate_average + mucd_index = user_candidate_averages.index(min_user_candidate_average) + if midpoint_average > user_candidate_averages.min + @midpoint = user_candidate_midpoints[mucd_index] + user_candidate_durations_text = user_candidates.map { |candidate| candidate[2] } + @durations_text = user_candidate_durations_text[mucd_index] + else + candidate_durations_text = candidates.map { |candidate| candidate[2] } + @durations_text = candidate_durations_text[mcd_index] + end + p @midpoint + + # p "Durations: #{@durations_text}" + return [@midpoint, @durations_text] + end + + def calculate_equal + + # find the average lat and long of all user locations (set this to variable D) - this is the first attempt at the midpoint (based purely off geographical equidistance) + + lat_sum = @addresses.map { |address| address[:lat] }.sum + lng_sum = @addresses.map { |address| address[:lng] }.sum + @midpoint[:lat] = lat_sum / @addresses.count + @midpoint[:lng] = lng_sum / @addresses.count + + # make the API calls to find the distances and duration between each user location and attempted midpoint + + @condition = false + url_time = @time_option == 1 ? "arrival_time=#{@future_datetime}" : "departure_time=#{@future_datetime}" + + address_query="" + @addresses.each do |address| + iaq = "|#{address[:lat]},#{address[:lng]}" + address_query = address_query + iaq + end + p address_query + + candidates = [] + user_candidates = [] + 3.times do + + url = "https://api.distancematrix.ai/maps/api/distancematrix/json?units=imperial&origins=#{address_query}&destinations=#{@midpoint[:lat]},#{@midpoint[:lng]}&transit_mode=subway&mode=transit&#{url_time}&key=#{ENV["DISTANCE_MATRIX_KEY"]}" + + @matrix = JSON.parse(open(url).read, symbolize_names: true) + + @addresses.each_with_index do |address, index| + address[:duration] = @matrix[:rows][index+1][:elements][0][:duration][:value] + address[:duration_text] = @matrix[:rows][index+1][:elements][0][:duration][:text] + end + + # determines the longest route between a user and @midpoint + durations = @addresses.map { |address| address[:duration] } + max_duration = durations.max + md_index = durations.index(max_duration) + min_duration = durations.min + + durations_text = @addresses.map { |address| address[:duration_text] } + + # reassigns the midpoint to a new location (skewed to be closer to the user who previously would have had to travel the longest) + + avg = (durations.sum / durations.count).to_f + percentage = avg / max_duration + @midpoint[:lat] = lerp(@addresses[md_index][:lat], @midpoint[:lat], percentage) + @midpoint[:lng] = lerp(@addresses[md_index][:lng], @midpoint[:lng], percentage) + + # stores as candidate_duration the discrepancy between the largest/smallest travel time + + candidate_duration = (max_duration - min_duration).abs + candidates.push([@midpoint, candidate_duration, durations_text, avg]) + end + + # of all the 5 attempts, selects the midpoint with the smallest discrepancy (candidate_duration) + # returns the chosen midpoint + + candidate_durations = candidates.map { |candidate| candidate[1] } + minimum_candidate_duration = candidate_durations.min + mcd_index = candidate_durations.index(minimum_candidate_duration) + candidate_midpoints = candidates.map { |candidate| candidate[0] } + @midpoint = candidate_midpoints[mcd_index] + candidate_durations_text = candidates.map { |candidate| candidate[2] } @durations_text = candidate_durations_text[mcd_index] + # p "Durations: #{@durations_text}" return [@midpoint, @durations_text] end diff --git a/app/views/midways/new.html.erb b/app/views/midways/new.html.erb index 1e6dc41..b76b9d9 100644 --- a/app/views/midways/new.html.erb +++ b/app/views/midways/new.html.erb @@ -160,6 +160,11 @@ + +
+ + +
diff --git a/db/migrate/20201208162629_add_choice_to_midway.rb b/db/migrate/20201208162629_add_choice_to_midway.rb new file mode 100644 index 0000000..17c1679 --- /dev/null +++ b/db/migrate/20201208162629_add_choice_to_midway.rb @@ -0,0 +1,5 @@ +class AddChoiceToMidway < ActiveRecord::Migration[6.0] + def change + add_column(:midways, :choice, :string) + end +end diff --git a/db/schema.rb b/db/schema.rb index c5de921..91c2259 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_12_03_130053) do +ActiveRecord::Schema.define(version: 2020_12_08_162629) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -81,6 +81,7 @@ t.time "future_time" t.date "future_date" t.datetime "future_datetime" + t.string "choice" t.index ["user_id"], name: "index_midways_on_user_id" t.index ["venue_id"], name: "index_midways_on_venue_id" end