Skip to content

Instantly share code, notes, and snippets.

@moserrya
Created March 22, 2013 01:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save moserrya/5218204 to your computer and use it in GitHub Desktop.
Save moserrya/5218204 to your computer and use it in GitHub Desktop.
require_relative 'geocoder'
require 'shoulda-matchers'
require 'geocoder'
require 'debugger'
class ActivityCluster
MAX_ROUTE_LENGTH = 4
MAX_DISTANCE = 20
def initialize(destinations)
raise ArgumentError unless destinations.is_a?(Array) && destinations.any?
@destinations = destinations
# partition_destinations
# best_cluster_and_route
end
# def best_cluster_and_route
# @non_meal_dest.each do |dest|
# shortest_route(dest, @destinations)
# end
# return shortest_route
# end
# def shortest_route(starting_point)
# route = [starting_point]
# until route.length == MAX_ROUTE_LENGTH do
# next_destination = closest_location(route.last, destinations)
# route << destinations.slice!(destinations.index(next_destination))
# end
# route
# end
def closest_location(location, destinations)
distances = destinations.map do |destination|
{location: destination, distance: self.distance_between(location, destination)}
end
distances.sort{|a, b| a[:distance] <=> b[:distance]}.first[:location]
end
def distance_between(location, destination)
Geocoder::Calculations.distance_between(location, destination)
end
# def partition_destinations
# @non_meal_dest, @meal_dest = @destinations.partition {|dest| dest.meal.zero? }
# end
end
class FakeLocation
attr_reader :meal
def initialize(latitude, longitude, meal)
@latitude = latitude
@longitude = longitude
@meal = meal
end
def to_coordinates
[@latitude, @longitude]
end
end
describe ActivityCluster do
before do
@los_angeles = FakeLocation.new 34.080185, -118.4692524, 0
@san_francisco_m = FakeLocation.new 37.7935572, -122.4218255, 1
@seattle_m = FakeLocation.new 47.6218387802915, -122.3482996197085, 1
@vancouver = FakeLocation.new 49.2776215, -123.1061451, 0
@austin = FakeLocation.new 30.2760276802915, -97.73898386970849, 0
@boulder_m = FakeLocation.new 39.9128860000000, -105.6943651, 1
end
let(:activity_cluster) { ActivityCluster.new [@los_angeles,
@san_francisco_m,
@seattle_m,
@vancouver,
@austin,
@boulder_m]}
context ".new" do
it 'requires an array of destinations' do
expect {
ActivityCluster.new
}.to raise_error(ArgumentError)
expect {
ActivityCluster.new(@los_angeles)
}.to raise_error(ArgumentError)
expect {
ActivityCluster.new(@los_angeles, @seattle_m)
}.to raise_error(ArgumentError)
expect {
ActivityCluster.new([])
}.to raise_error(ArgumentError)
end
end
context "#closest_location" do
it "returns a location" do
activity_cluster.closest_location(@los_angeles, [@san_francisco_m, @seattle_m, @vancouver]).
should be_a(FakeLocation)
end
it "knows the nearest location" do
activity_cluster.
closest_location(@san_francisco_m, [@los_angeles, @seattle_m, @vancouver]).
should eq @los_angeles
end
end
context '#shortest_route' do
context 'returns ordered array' do
it 'returns an array of length 4 given at least 2 meal and 2 non-meal locations' do
activity_cluster.shortest_route(@los_angeles).
length.should eq 4
end
it 'alternating meal, non-meal' do
activity_cluster.shortest_route(@los_angeles).
map { |destination| destination.meal }.should eq [0, 1, 0, 1]
end
it 'of locations ordered by shortest distance start to finish' do
activity_cluster.shortest_route(@los_angeles).
should eq [@los_angeles, @san_francisco_m, @vancouver, @seattle_m]
end
it 'does not include the starting point twice in the resulting array' do
shortest_route = activity_cluster.shortest_route(@los_angeles)
shortest_route.length.should eq(shortest_route.uniq.length)
end
end
end
end
require 'shoulda-matchers'
require 'geocoder'
require 'debugger'
class InvalidAddressError < StandardError
end
class NoStreetError < StandardError
end
class Location
attr_reader :street, :city, :country, :zip_code, :state, :longitude, :latitude
def initialize(address)
@street = address[:street]
@city = address[:city]
@country = address[:country]
@zip_code = address[:zip_code]
@state = address[:state]
raise NoStreetError unless @street
geocode
end
private
def address_string
"#{street}, #{city}, #{state} #{zip_code} #{country}"
end
def geocode
api_details = Geocoder.search(address_string).first
raise InvalidAddressError if api_details.nil?
@latitude = api_details.data["geometry"]["location"]["lat"]
@longitude = api_details.data["geometry"]["location"]["lng"]
end
end
# describe Location do
# let(:address) { {:street => "717 California St.",
# :city => "San Francisco",
# :country => "USA",
# :zip_code => "94108",
# :state => "CA"} }
# let(:location) { Location.new(address) }
# context '.new' do
# it { location.should be_a(Location) }
# it { location.should respond_to(:street) }
# it { location.should respond_to(:city) }
# it { location.should respond_to(:country) }
# it { location.should respond_to(:zip_code) }
# it { location.should respond_to(:state) }
# it { location.should respond_to(:longitude) }
# it { location.should respond_to(:latitude) }
# context 'arguments' do
# it 'accepts one argument hash' do
# expect {
# Location.new
# }.to raise_error(ArgumentError)
# end
# let(:invalid_address) { { :city => "San Francisco",
# :country => "USA",
# :zip_code => "94108",
# :state => "CA"} }
# it 'must have a street' do
# expect {
# Location.new(invalid_address)
# }.to raise_error(NoStreetError)
# end
# end
# context 'invalid address' do
# let(:address) { {:street => ""}}
# it "raises an invalid addres error" do
# expect {
# Location.new(address)
# }.to raise_error(InvalidAddressError)
# end
# end
# context 'longitude and latitude should be calculated' do
# it { location.longitude.should_not be_nil }
# it { location.latitude.should_not be_nil }
# end
# end
# end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment