Skip to content

Instantly share code, notes, and snippets.

@cblavier
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cblavier/8916592 to your computer and use it in GitHub Desktop.
Save cblavier/8916592 to your computer and use it in GitHub Desktop.
Clustered geoloc search
def within_box_events
# reading lower_left corner and upper_right corner parameters
ll_lng = Float(params[:lower_left][:lng])
ll_lat = Float(params[:lower_left][:lat])
ur_lng = Float(params[:upper_right][:lng])
ur_lat = Float(params[:upper_right][:lat])
# getting coordinates of a 9 times broader box, to get better clustering behavior at edges
q_ll_lng = [ll_lng - ((ur_lng - ll_lng) / 2), -180].max
q_ur_lng = [ur_lng + ((ur_lng - ll_lng) / 2), 180].min
q_ll_lat = [ll_lat - ((ur_lat - ll_lat) / 2), -90].max
q_ur_lat = [ur_lat + ((ur_lat - ll_lat) / 2), 90].min
# adjustement variable, the lower it is, the more events will be clustered
cluster_treshold = Float(params[:cluster_treshold]) rescue 50
# when 2 events are closer than this distance, only the latest one is returned
min_distance = Float(params[:min_distance]) rescue 0.2
max_age = Time.at(Integer(params[:max_age])) rescue 6.months.ago
# calculating cluster_distance: if distance between 2 events is lower than this, they are merged as a cluster
cluster_distance = Geocoder::Calculations.distance_between([ll_lat, ll_lng],[ur_lat, ur_lng], {:unit => :km}) / cluster_treshold
events = []
clusters = []
# iterating over all events within the box
Event.granted_for(current_user)
.where(:created_at.gt => max_age, :visible_picture_count.gte => 5)
.within_box(:location => [[ q_ll_lng, q_ll_lat], [ q_ur_lng, q_ur_lat]])
.order_by(:created_at => :desc).each do |event|
current_event_in_cluster = false
# looking for existing clusters if current event must be merged into one of them
clusters.each do |cluster|
distance = distance_between_locations(event.location.to_hsh(:lng, :lat), cluster[:location])
if distance < cluster_distance
if cluster[:event_locations].none?{ |location| distance_between_locations(location, event.location.to_hsh(:lng,:lat)) <= min_distance}
cluster[:count] += 1 if distance > min_distance
cluster[:event_locations] << event.location.to_hsh(:lng, :lat)
end
current_event_in_cluster = true
break
end
end
next if current_event_in_cluster
# looking for previous events if the 2 events must be merged into a single cluster
events.each_with_index do |previous_event, i|
distance = distance_between_locations(event.location.to_hsh(:lng, :lat), previous_event.location.to_hsh(:lng, :lat))
if distance < cluster_distance
if distance > min_distance
clusters << {:count => 2, :location => event.location.to_hsh(:lng, :lat), :event_locations => [previous_event.location.to_hsh(:lng, :lat), event.location.to_hsh(:lng, :lat)], :cover_picture_url => event.cover_picture_url}
events.delete_at(i)
end
current_event_in_cluster = true
break
end
end
next if current_event_in_cluster
# if event has not been merged into a cluster, append it to the list
events << event
end
# removing any points not within the box
clusters.reject!{|c| c[:location][:lng] < ll_lng || c[:location][:lng] > ur_lng || c[:location][:lat] < ll_lat || c[:location][:lat] > ur_lat }
events.reject!{|e| e.location.x < ll_lng || e.location.x > ur_lng || e.location.y < ll_lat || e.location.y > ur_lat }
{
:clusters => clusters.each{|cluster| cluster.delete(:event_locations) },
:events => events
}
end
private
def distance_between_locations(location_1, location_2)
Geocoder::Calculations.distance_between(
[location_1[:lat], location_1[:lng]],
[location_2[:lat], location_2[:lng]],
{:unit => :km}
)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment