Skip to content

Instantly share code, notes, and snippets.

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 harish86/5537951 to your computer and use it in GitHub Desktop.
Save harish86/5537951 to your computer and use it in GitHub Desktop.
To find distance between two locations and to find locations within the given radius in a Ruby on Rails application. Note: this code snippet is based on the ideas and examples found by googling.
Table schema for locations:
===========================
create_table "locations", :force => true do |t|
t.string "zipcode", :limit => 6
t.string "city", :limit => 25
t.string "state_code", :limit => 3
t.string "state_name", :limit => 25
t.float "latitude"
t.float "longitude"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "locations", ["zipcode"], :name => "index_locations_on_zipcode", :unique => true
Model code for Location:
========================
class Location < ActiveRecord::Base
# See: http://www.codeproject.com/dotnet/Zip_code_radius_search.asp
# Equatorial radius of the earth from WGS 84
# in meters, semi major axis = a
A = 6378137;
# flattening = 1/298.257223563 = 0.0033528106647474805
# first eccentricity squared = e = (2-flattening)*flattening
E = 0.0066943799901413165;
# Miles to Meters conversion factor (take inverse for opposite)
MILES_TO_METERS = 1609.347;
# Degrees to Radians converstion factor (take inverse for opposite)
DEGREES_TO_RADIANS = Math::PI/180;
validates_uniqueness_of :zipcode
attr_accessor :search_zip, :distance_to_search_zip
def find_objects_within_radius(radius_in_miles, &finder_block)
self.search_zip = self
self.distance_to_search_zip = 0
radius = radius_in_miles*MILES_TO_METERS
lat0 = self.latitude * DEGREES_TO_RADIANS
lon0 = self.longitude * DEGREES_TO_RADIANS
rm = calc_meridional_radius_of_curvature(lat0)
rpv = calc_ro_cin_prime_vertical(lat0)
#Find boundaries for curvilinear plane that encloses search ellipse
max_lat = (radius/rm+lat0)/DEGREES_TO_RADIANS;
max_lon = (radius/(rpv*Math::cos(lat0))+lon0)/DEGREES_TO_RADIANS;
min_lat = (lat0-radius/rm)/DEGREES_TO_RADIANS;
min_lon = (lon0-radius/(rpv*Math::cos(lat0)))/DEGREES_TO_RADIANS;
# Get all zips within min/mix here
#zip_codes = * DEGREES_TO_RADIANSself.find(:all, :conditions => ["latitude > ? AND longitude > ? AND latitude < ? AND longitude < ? ", min_lat, min_lon, max_lat, max_lon])
zip_codes = yield(min_lat, min_lon, max_lat, max_lon)
# Now calc distances from centroid, and remove results that were returned
# in the curvilinear plane, but are outside of the ellipsoid geodetic
result = []
zip_codes.each do |zip|
z_lat = zip.lat * DEGREES_TO_RADIANS
z_lon = zip.lon * DEGREES_TO_RADIANS
distance_to_centroid = calc_distance_lat_lon(rm, rpv, lat0, lon0, z_lat, z_lon)
if distance_to_centroid <= radius
zip.distance_to_search_zip = distance_to_centroid
result << zip
end
end
return result.sort { |a, b| a.distance_to_search_zip <=> b.distance_to_search_zip }
end
def distance_to_search_zip(units = 'kilometers')
if units =~ /mile/i
return @distance_to_search_zip/MILES_TO_METERS
elsif units =~/kilometer/
return @distance_to_search_zip/1000.0
else
return @distance_to_search_zip
end
end
def calc_distance_lat_lon(rm, rpv, lat0, lon0, lat, lon)
Math::sqrt( (rm ** 2) * ((lat-lat0)**2) + (rpv ** 2) * (Math::cos(lat0)**2) * ((lon-lon0) ** 2) )
end
def calc_meridional_radius_of_curvature(lat0)
A*(1-E)/((1 - E * ((Math::sin(lat0) ** 2))) ** 1.5)
end
def calc_ro_cin_prime_vertical(lat0)
A / Math::sqrt( 1 - E * (Math::sin(lat0) ** 2))
end
def lat() read_attribute(:latitude).to_f; end
def lon() read_attribute(:longitude).to_f; end
def latitude() read_attribute(:latitude).to_f; end
def longitude() read_attribute(:longitude).to_f; end
def nearest_locations(radius)
results = self.find_objects_within_radius(radius.to_i) do |min_lat, min_lon, max_lat, max_lon|
Location.find(:all,
:conditions => [ "(latitude > ? AND longitude > ? AND latitude < ? AND longitude < ? ) ",
min_lat, min_lon, max_lat, max_lon])
end
return results
end
def distance_from(location) #(lat1, lon1)
return 0 unless location.is_a?(Location)
r = 6371.0
lat1 = location.lat
lon1 = location.lon
lat2 = self.lat
lon2 = self.lon
dlat = (lat2 - lat1) * DEGREES_TO_RADIANS
dlon = (lon2 - lon1) * DEGREES_TO_RADIANS
a = Math::sin(dlat/2.0) * Math::sin(dlat/2.0) + Math::cos(lat1 * DEGREES_TO_RADIANS) * Math::cos(lat2 * DEGREES_TO_RADIANS) * Math::sin(dlon/2.0) * Math::sin(dlon/2.0)
c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1.0 - a))
return(r * c)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment