I recently had to calculate the distance between a large number of co-ordinates. I could have used one of the many Ruby Gems that are available but due to business limitations had to develop the code myself. After some research I came across the haversine formula.
The haversine formula is an equation important in navigation, giving great-circle distances between two points on a sphere from their longitudes and latitudes. It is a special case of a more general formula in spherical trigonometry, the law of haversines, relating the sides and angles of spherical triangles.
You can read the full details on the maths here. For those who just want the code here is the implemenation I came up with:
module GPS
class Distance
RAD_PER_DEG = Math::PI / 180
GREAT_CIRCLE_RADIUS_MILES = 3956
GREAT_CIRCLE_RADIUS_KILOMETERS = 6371 # some algorithms use 6367
GREAT_CIRCLE_RADIUS_FEET = GREAT_CIRCLE_RADIUS_MILES * 5280
GREAT_CIRCLE_RADIUS_METERS = GREAT_CIRCLE_RADIUS_KILOMETERS * 1000
GREAT_CIRCLE_RADIUS_NAUTICAL_MILES = GREAT_CIRCLE_RADIUS_MILES / 1.15078
attr_accessor :great_circle_distance
attr_accessor :point1
attr_accessor :point2
def initialize(great_circle_distance = 0)
@great_circle_distance = great_circle_distance
@point1 = [0,0]
@point2 = [0,0]
end
def to_miles
calculate
@great_circle_distance * GREAT_CIRCLE_RADIUS_MILES
end
alias_method :to_mi, :to_miles
def to_kilometers
calculate
@great_circle_distance * GREAT_CIRCLE_RADIUS_KILOMETERS
end
alias_method :to_km, :to_kilometers
def to_meters
calculate
@great_circle_distance * GREAT_CIRCLE_RADIUS_METERS
end
alias_method :to_m, :to_meters
def to_feet
calculate
@great_circle_distance * GREAT_CIRCLE_RADIUS_FEET
end
alias_method :to_ft, :to_feet
def to_nautical_miles
calculate
@great_circle_distance * GREAT_CIRCLE_RADIUS_NAUTICAL_MILES
end
alias_method :to_nm, :to_nautical_miles
private
# Radians per degree
def rpd(num)
num * RAD_PER_DEG
end
def calculate
# Accept two arrays of points in addition to four coordinates
if point1.is_a?(Array) && point2.is_a?(Array)
lat2, lon2 = point2
lat1, lon1 = point1
elsif
raise ArgumentError
end
dlon = lon2 - lon1
dlat = lat2 - lat1
a = (Math.sin(rpd(dlat)/2))**2 + Math.cos(rpd(lat1)) * Math.cos((rpd(lat2))) * (Math.sin(rpd(dlon)/2))**2
@great_circle_distance = 2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a))
end
end
end
Here is a quick script on how to use it:
irb(main):001:0> d = GPS::Distance.new
=> #<GPS::Distance:0x007fb93b036050 @great_circle_distance=0, @point1=[0, 0], @point2=[0, 0]>
irb(main):002:0> d.point1 = [40.7457395,-73.991623]
=> [40.7457395, -73.991623]
irb(main):003:0> d.point2 = [40.9176771,-74.2082467]
=> [40.9176771, -74.2082467]
irb(main):004:0> d.to_meters
=> 26413.70207758391
irb(main):005:0> d.to_kilometers
=> 26.413702077583906
irb(main):006:0> d.to_miles
=> 16.40128793265138
irb(main):007:0> d.to_feet
=> 86598.80028439929
irb(main):008:0>
Hope this helps. If you have any questions, or comments feel free to leave a message or contact me directly.