lib/gis/distance.rb in gis-distance-1.0.1 vs lib/gis/distance.rb in gis-distance-1.0.2

- old
+ new

@@ -1,135 +1,151 @@ # The GIS module serves as a namespace only. module GIS - # The Distance class encapsulates methods related to geographic distance. - class Distance - # Error raised if latitude or longitude values are invalid. - class Error < StandardError; end + # The Distance class encapsulates methods related to geographic distance. + class Distance + # Error raised if latitude or longitude values are invalid. + class Error < StandardError; end - # The version of the gis-distance library - VERSION = '1.0.1' + # The version of the gis-distance library + VERSION = '1.0.2'.freeze - # Create a new GIS::Distance object using the two sets of coordinates - # that are provided. - # - # If invalid coordinates are provided a GIS::Distance::Error is raised. - # - def initialize(latitude1, longitude1, latitude2, longitude2) - validate(latitude1, longitude1, latitude2, longitude2) + # Create a new GIS::Distance object using the two sets of coordinates + # that are provided. + # + # If invalid coordinates are provided a GIS::Distance::Error is raised. + # + def initialize(latitude1, longitude1, latitude2, longitude2) + validate(latitude1, longitude1, latitude2, longitude2) - @latitude1 = latitude1 - @longitude1 = longitude1 - @latitude2 = latitude2 - @longitude2 = longitude2 + @latitude1 = latitude1 + @longitude1 = longitude1 + @latitude2 = latitude2 + @longitude2 = longitude2 - @radius = 6367.45 - @formula = 'haversine' - @distance = nil - end + @radius = 6367.45 + @formula = 'haversine' + @distance = nil + end - # Returns the radius of the Earth in kilometers. The default is 6367.45. - # - def radius - @radius - end + # Returns the radius of the Earth in kilometers. The default is 6367.45. + # + def radius + @radius + end - # Sets the radius of the Earth in kilometers. This is variable because - # the Earth is not perfectly spherical, and you may wish to adjust it. - # - # However, the possible range of values is limited from 6357.0 to 6378.0. - # If a value outside of this range is provided a GIS::Distance::Error - # is raised. - # - # The default value is 6367.45. - # - # See http://en.wikipedia.org/wiki/Earth_radius for more information. - # - def radius=(kms) - if kms < 6357.0 || kms > 6378.0 - raise Error, "Proposed radius '#{kms}' is out of range" - end - @radius = kms + # Sets the radius of the Earth in kilometers. This is variable because + # the Earth is not perfectly spherical, and you may wish to adjust it. + # + # However, the possible range of values is limited from 6357.0 to 6378.0. + # If a value outside of this range is provided a GIS::Distance::Error + # is raised. + # + # The default value is 6367.45. + # + # See http://en.wikipedia.org/wiki/Earth_radius for more information. + # + def radius=(kms) + if kms < 6357.0 || kms > 6378.0 + raise Error, "Proposed radius '#{kms}' is out of range" end + @radius = kms + end - # Returns the formula used to calculate the distance. The default formula - # is 'haversine'. - #-- - # See http://en.wikipedia.org/wiki/Haversine_formula for details. - def formula - @formula - end + # Returns the formula used to calculate the distance. The default formula + # is 'haversine'. + #-- + # See http://en.wikipedia.org/wiki/Haversine_formula for details. + def formula + @formula + end - # Sets the formula to be used internally for calculating the distance. - # The default is 'haversine'. - # - # If an unsupported formula is provided a GIS::Distance::Error is raised. - # - def formula=(formula) - case formula.to_s.downcase - when 'haversine' - @formula = 'haversine' - else - raise Error, "Formula '#{formula}' not supported" - end + # Sets the formula to be used internally for calculating the distance. + # The default is 'haversine'. Your other option is 'cosines' (i.e. the + # Law of Cosines). + # + # If an unsupported formula is provided a GIS::Distance::Error is raised. + # + def formula=(formula) + case formula.to_s.downcase + when 'haversine' + @formula = 'haversine' + when 'cosines' + @formula = 'cosines' + else + raise Error, "Formula '#{formula}' not supported" end + end - # Returns the distance (in kilometers) between the two coordinates - # provided in the constructor. - # - def distance - @distance = - case @formula.to_s.downcase - when 'haversine' - haversine_formula - end + # Returns the distance (in kilometers) between the two coordinates + # provided in the constructor. + # + def distance + @distance = + case @formula.to_s.downcase + when 'haversine' + haversine_formula + when 'cosines' + law_of_cosines_formula end + end - private + private - # Validate the latitude and longitude values. Latitudes must be between - # 90.0 and -90.0 while longitudes must be between 180.0 and -180.0. - # - def validate(lat1, lon1, lat2, lon2) - [lat1, lat2].each{ |lat| - if lat > 90 || lat < -90 - msg = "Latitude '#{lat}' is invalid - must be between -90 and 90" - raise Error, msg - end - } + # Validate the latitude and longitude values. Latitudes must be between + # 90.0 and -90.0 while longitudes must be between 180.0 and -180.0. + # + def validate(lat1, lon1, lat2, lon2) + [lat1, lat2].each{ |lat| + if lat > 90 || lat < -90 + msg = "Latitude '#{lat}' is invalid - must be between -90 and 90" + raise Error, msg + end + } - [lon1, lon2].each{ |lon| - if lon > 180 || lon < -180 - msg = "Longitude '#{lon}' is invalid - must be between -180 and 180" - raise Error, msg - end - } - end + [lon1, lon2].each{ |lon| + if lon > 180 || lon < -180 + msg = "Longitude '#{lon}' is invalid - must be between -180 and 180" + raise Error, msg + end + } + end - # See http://en.wikipedia.org/wiki/Haversine_formula - # - def haversine_formula - lat1 = @latitude1 * Math::PI / 180 - lon1 = @longitude1 * Math::PI / 180 - lat2 = @latitude2 * Math::PI / 180 - lon2 = @longitude2 * Math::PI / 180 + # See https://en.wikipedia.org/wiki/Law_of_cosines + # + def law_of_cosines_formula + sin1 = Math.sin(@latitude1 * Math::PI / 180) + sin2 = Math.sin(@latitude2 * Math::PI / 180) + cos1 = Math.cos(@latitude1 * Math::PI / 180) + cos2 = Math.cos(@latitude2 * Math::PI / 180) + cos3 = Math.cos(@longitude2 * Math::PI / 180 - @longitude1 * Math::PI / 180) - dlat = lat2 - lat1 - dlon = lon2 - lon1 + Math.acos(sin1 * sin2 + cos1 * cos2 * cos3) * radius + end - a = ((Math.sin(dlat/2))**2) + (Math.cos(lat1) * Math.cos(lat2) * (Math.sin(dlon/2)) ** 2) - c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)) + # See http://en.wikipedia.org/wiki/Haversine_formula + # + def haversine_formula + lat1 = @latitude1 * Math::PI / 180 + lon1 = @longitude1 * Math::PI / 180 + lat2 = @latitude2 * Math::PI / 180 + lon2 = @longitude2 * Math::PI / 180 - @radius * c - end + dlat = lat2 - lat1 + dlon = lon2 - lon1 - # Add a custom method to the base Float class if it isn't already defined. - class ::Float - unless self.respond_to?(:mi) - # Convert miles to kilometers. - def mi - self * 0.621371192 - end - end + a = ((Math.sin(dlat/2))**2) + (Math.cos(lat1) * Math.cos(lat2) * (Math.sin(dlon/2)) ** 2) + c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)) + + radius * c + end + + # Add a custom method to the base Float class if it isn't already defined. + class ::Float + unless self.respond_to?(:mi) + # Convert miles to kilometers. + def mi + self * 0.621371192 + end end - end + end + end end -