Created
May 8, 2013 03:20
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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