The Insightful Troll

Rants and Ruminations.

GPS Distance

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.

Comments