├── VERSION ├── .rspec ├── .document ├── Gemfile ├── spec ├── spec_helper.rb ├── distance_spec.rb └── haversine_spec.rb ├── .gitignore ├── LICENSE.txt ├── lib ├── haversine.rb └── haversine │ └── distance.rb ├── Rakefile ├── haversine.gemspec └── README.textile /VERSION: -------------------------------------------------------------------------------- 1 | 0.3.2 -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | bin/* 3 | - 4 | features/**/*.feature 5 | LICENSE.txt 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | # Add dependencies required to use your gem here. 3 | # Example: 4 | # gem "activesupport", ">= 2.3.5" 5 | 6 | # Add dependencies to develop your gem here. 7 | # Include everything needed to run rake, tests, features, etc. 8 | group :development do 9 | gem "rspec", "~> 3.6", ">= 3.6.0" 10 | gem "bundler", ">= 1.3.0" 11 | gem "jeweler", ">= 1.8.6" 12 | gem "simplecov", "~> 0.7", ">= 0.7.1" 13 | end 14 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 3 | require 'rspec' 4 | require 'haversine' 5 | require 'simplecov' 6 | SimpleCov.start if ENV["COVERAGE"] 7 | # Requires supporting files with custom matchers and macros, etc, 8 | # in ./support/ and its subdirectories. 9 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 10 | 11 | RSpec.configure do |config| 12 | 13 | end 14 | -------------------------------------------------------------------------------- /spec/distance_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Haversine::Distance do 4 | describe "<=>" do 5 | context "equality" do 6 | it "is true when the great circle distance is equal" do 7 | dist1 = Haversine::Distance.new(0) 8 | dist2 = Haversine::Distance.new(0) 9 | expect(dist1 == dist2).to be(true) 10 | end 11 | 12 | it "is false when the great circle distance is not equal" do 13 | dist1 = Haversine::Distance.new(0) 14 | dist2 = Haversine::Distance.new(1) 15 | expect(dist1 == dist2).to be(false) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rcov generated 2 | coverage 3 | 4 | # rdoc generated 5 | rdoc 6 | 7 | # yard generated 8 | doc 9 | .yardoc 10 | 11 | # bundler 12 | .bundle 13 | 14 | # jeweler generated 15 | pkg 16 | 17 | # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: 18 | # 19 | # * Create a file at ~/.gitignore 20 | # * Include files you want ignored 21 | # * Run: git config --global core.excludesfile ~/.gitignore 22 | # 23 | # After doing this, these files will be ignored in all your git projects, 24 | # saving you from having to 'pollute' every project you touch with them 25 | # 26 | # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line) 27 | # 28 | # For MacOS: 29 | # 30 | #.DS_Store 31 | # 32 | # For TextMate 33 | #*.tmproj 34 | #tmtags 35 | # 36 | # For emacs: 37 | #*~ 38 | #\#* 39 | #.\#* 40 | # 41 | # For vim: 42 | #*.swp 43 | 44 | Gemfile.lock 45 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Kristian Mandrup 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/haversine.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Haversine formula to compute the great circle distance between two points given their latitude and longitudes 3 | # 4 | # Notes: 5 | # 6 | # translated into Ruby based on information contained in: 7 | # http://en.wikipedia.org/wiki/Haversine_formula 8 | # 9 | # This formula can compute accurate distances between two points given latitude and longitude, even for 10 | # short distances. 11 | 12 | require 'haversine/distance' 13 | 14 | module Haversine 15 | 16 | RAD_PER_DEG = Math::PI / 180 17 | 18 | # given two lat/lon points, compute the distance between the two points using the haversine formula 19 | def self.distance(*two_point_coordinates) 20 | # Accept two arrays of points in addition to four coordinates 21 | lat1, lon1, lat2, lon2 = Array(two_point_coordinates).flatten 22 | raise ArgumentError if [lat1, lon1, lat2, lon2].include? nil 23 | 24 | dlon = lon2 - lon1 25 | dlat = lat2 - lat1 26 | 27 | a = calc(dlat, lat1, lat2, dlon) 28 | c = 2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a)) 29 | 30 | Haversine::Distance.new(c) 31 | end 32 | 33 | # TODO How can this be more descriptively named? 34 | def self.calc(dlat, lat1, lat2, dlon) 35 | (Math.sin(rpd(dlat)/2))**2 + Math.cos(rpd(lat1)) * Math.cos((rpd(lat2))) * (Math.sin(rpd(dlon)/2))**2 36 | end 37 | 38 | # Radians per degree 39 | def self.rpd(num) 40 | num * RAD_PER_DEG 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/haversine_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Haversine do 4 | describe "#self.distance" do 5 | it "returns Haversine::Distance" do 6 | expect(Haversine.distance(0,0,0,0)).to be_a(Haversine::Distance) 7 | end 8 | 9 | it "accepts 4 numbers or 2 arrays as arguments" do 10 | new_york_city = [40.71427, -74.00597] 11 | santiago_chile = [-33.42628, -70.56656] 12 | point_dist = Haversine.distance(new_york_city[0], new_york_city[1], santiago_chile[0], santiago_chile[1]) 13 | array_dist = Haversine.distance(new_york_city, santiago_chile) 14 | 15 | expect(point_dist).to be_a(Haversine::Distance) 16 | expect(array_dist).to be_a(Haversine::Distance) 17 | expect(point_dist.to_m).to eq(array_dist.to_m) 18 | end 19 | 20 | it "computes nautical mile distances correctly" do 21 | new_york_city = [40.71427, -74.00597] 22 | santiago_chile = [-33.42628, -70.56656] 23 | dist = Haversine.distance(new_york_city, santiago_chile) 24 | expect(dist.to_miles).to eq(5123.736179853891) 25 | expect(dist.to_nautical_miles).to eq(4452.402874445064) 26 | end 27 | 28 | it "calculates the distance between the provided lat/lon pairs" do 29 | expect(Haversine.distance(0,0,0,0).to_miles).to eq(0) 30 | expect(round_to(6, Haversine.distance(0,0,0,360).to_miles)).to eq(0) 31 | expect(round_to(6, Haversine.distance(0,0,360,0).to_miles)).to eq(0) 32 | end 33 | end 34 | 35 | # Helpers 36 | def round_to(precision, num) 37 | (num * 10**precision).round.to_f / 10**precision 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/haversine/distance.rb: -------------------------------------------------------------------------------- 1 | module Haversine 2 | class Distance 3 | include Comparable 4 | 5 | GREAT_CIRCLE_RADIUS_MILES = 3956 6 | GREAT_CIRCLE_RADIUS_KILOMETERS = 6371 # some algorithms use 6367 7 | GREAT_CIRCLE_RADIUS_FEET = GREAT_CIRCLE_RADIUS_MILES * 5280 8 | GREAT_CIRCLE_RADIUS_METERS = GREAT_CIRCLE_RADIUS_KILOMETERS * 1000 9 | GREAT_CIRCLE_RADIUS_NAUTICAL_MILES = GREAT_CIRCLE_RADIUS_MILES / 1.15078 10 | 11 | attr_reader :great_circle_distance 12 | 13 | def initialize(great_circle_distance) 14 | @great_circle_distance = great_circle_distance 15 | end 16 | 17 | def to_miles 18 | @great_circle_distance * GREAT_CIRCLE_RADIUS_MILES 19 | end 20 | alias_method :to_mi, :to_miles 21 | 22 | def to_kilometers 23 | @great_circle_distance * GREAT_CIRCLE_RADIUS_KILOMETERS 24 | end 25 | alias_method :to_km, :to_kilometers 26 | 27 | def to_meters 28 | @great_circle_distance * GREAT_CIRCLE_RADIUS_METERS 29 | end 30 | alias_method :to_m, :to_meters 31 | 32 | def to_feet 33 | @great_circle_distance * GREAT_CIRCLE_RADIUS_FEET 34 | end 35 | alias_method :to_ft, :to_feet 36 | 37 | def to_nautical_miles 38 | @great_circle_distance * GREAT_CIRCLE_RADIUS_NAUTICAL_MILES 39 | end 40 | alias_method :to_nm, :to_nautical_miles 41 | 42 | def <=>(other) 43 | if other.respond_to? :great_circle_distance # it's duck if it quacks 44 | great_circle_distance <=> other.great_circle_distance 45 | else 46 | return nil # spitting out nil when objects are not comparable 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | begin 4 | Bundler.setup(:default, :development) 5 | rescue Bundler::BundlerError => e 6 | $stderr.puts e.message 7 | $stderr.puts "Run `bundle install` to install missing gems" 8 | exit e.status_code 9 | end 10 | require 'rake' 11 | include Rake::DSL if defined? Rake::DSL 12 | 13 | require 'jeweler' 14 | Jeweler::Tasks.new do |gem| 15 | # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options 16 | gem.name = "haversine" 17 | gem.homepage = "http://github.com/kristianmandrup/haversine" 18 | gem.license = "MIT" 19 | gem.summary = %Q{Calculates the haversine distance between two locations using longitude and latitude} 20 | gem.description = %Q{Calculates the haversine distance between two locations using longitude and latitude. 21 | This is done using Math formulas without resorting to Active Record or SQL DB functionality} 22 | gem.email = "kmandrup@gmail.com" 23 | gem.authors = ["Kristian Mandrup", "Ryan Greenberg"] 24 | # Include your dependencies below. Runtime dependencies are required when using your gem, 25 | # and development dependencies are only needed for development (ie running rake tasks, tests, etc) 26 | # gem.add_runtime_dependency 'jabber4r', '> 0.1' 27 | # gem.add_development_dependency 'rspec', '> 1.2.3' 28 | end 29 | Jeweler::RubygemsDotOrgTasks.new 30 | 31 | require 'rspec/core' 32 | require 'rspec/core/rake_task' 33 | RSpec::Core::RakeTask.new(:spec) do |spec| 34 | spec.pattern = FileList['spec/**/*_spec.rb'] 35 | end 36 | 37 | RSpec::Core::RakeTask.new(:coverage) do |spec| 38 | spec.pattern = 'spec/**/*_spec.rb' 39 | ENV['COVERAGE'] = 'true' 40 | end 41 | 42 | task :default => :spec 43 | 44 | require 'rdoc/task' 45 | Rake::RDocTask.new do |rdoc| 46 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 47 | 48 | rdoc.rdoc_dir = 'rdoc' 49 | rdoc.title = "haversine #{version}" 50 | rdoc.rdoc_files.include('README*') 51 | rdoc.rdoc_files.include('lib/**/*.rb') 52 | end 53 | -------------------------------------------------------------------------------- /haversine.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | # stub: haversine 0.3.2 ruby lib 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "haversine".freeze 9 | s.version = "0.3.2" 10 | 11 | s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= 12 | s.require_paths = ["lib".freeze] 13 | s.authors = ["Kristian Mandrup".freeze, "Ryan Greenberg".freeze] 14 | s.date = "2016-10-04" 15 | s.description = "Calculates the haversine distance between two locations using longitude and latitude. \nThis is done using Math formulas without resorting to Active Record or SQL DB functionality".freeze 16 | s.email = "kmandrup@gmail.com".freeze 17 | s.extra_rdoc_files = [ 18 | "LICENSE.txt", 19 | "README.textile" 20 | ] 21 | s.files = [ 22 | ".document", 23 | ".rspec", 24 | "Gemfile", 25 | "LICENSE.txt", 26 | "README.textile", 27 | "Rakefile", 28 | "VERSION", 29 | "haversine.gemspec", 30 | "lib/haversine.rb", 31 | "lib/haversine/distance.rb", 32 | "spec/distance_spec.rb", 33 | "spec/haversine_spec.rb", 34 | "spec/spec_helper.rb" 35 | ] 36 | s.homepage = "http://github.com/kristianmandrup/haversine".freeze 37 | s.licenses = ["MIT".freeze] 38 | s.rubygems_version = "2.6.2".freeze 39 | s.summary = "Calculates the haversine distance between two locations using longitude and latitude".freeze 40 | 41 | if s.respond_to? :specification_version then 42 | s.specification_version = 4 43 | 44 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 45 | s.add_development_dependency(%q.freeze, ["~> 3.6", ">= 3.6.0"]) 46 | s.add_development_dependency(%q.freeze, [">= 1.3.0"]) 47 | s.add_development_dependency(%q.freeze, [">= 1.8.6"]) 48 | s.add_development_dependency(%q.freeze, ["~> 0.7", ">= 0.7.1"]) 49 | else 50 | s.add_dependency(%q.freeze, ["~> 3.6", ">= 3.6.0"]) 51 | s.add_dependency(%q.freeze, [">= 1.3.0"]) 52 | s.add_dependency(%q.freeze, [">= 1.8.6"]) 53 | s.add_dependency(%q.freeze, ["~> 0.7", ">= 0.7.1"]) 54 | end 55 | else 56 | s.add_dependency(%q.freeze, ["~> 3.6", ">= 3.6.0"]) 57 | s.add_dependency(%q.freeze, [">= 1.3.0"]) 58 | s.add_dependency(%q.freeze, [">= 1.8.6"]) 59 | s.add_dependency(%q.freeze, ["~> 0.7", ">= 0.7.1"]) 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. Haversine Distance 2 | 3 | This gem calculates the Haversine distance between two points given their longitude and latitude. This is done using trigonometry without ActiveRecord or SQL. See http://en.wikipedia.org/wiki/Haversine_formula for details on the Haversine formula. 4 | 5 | This is a replacement for all geo libraries that use built-in SQL DB functionality for their calculations. 6 | 7 | h2. Install & Usage 8 | 9 | Install this gem with @gem install haversine@. Calling @Haversine.distance@ with four lat/lon coordinates returns a @Haversine::Distance@ object which can provide output in kilometers, meters, miles, or feet. 10 | 11 |
12 | require 'haversine'
13 | 
14 | distance = Haversine.distance(35.61488, 139.5813, 48.85341, 2.3488)
15 | 
16 | distance.class => Haversine::Distance
17 | distance.to_miles => 6032.71091869803
18 | distance.to_kilometers => 9715.47049115903
19 | distance.to_meters => 9715470.49115903
20 | distance.to_feet => 31852713.6507256
21 | distance.to_nautical_miles => 5242.2799481204265
22 | 
23 | 24 | Convenience aliases for the measurements exist: 25 |
26 | distance.to_kilometers == distance.to_km
27 | distance.to_meters == distance.to_m
28 | distance.to_miles == distance.to_mi
29 | distance.to_feet == distance.to_ft
30 | distance.to_nautical_miles == distance.to_nm
31 | 
32 | 33 | Note that @to_m@ is the distance in meters, not miles. 34 | 35 | If you have lat/lon pairs stored in an array, you can alternately provide two arrays when calling @Haversine.distance@: 36 | 37 |
38 | require 'haversine'
39 | new_york_city = [40.71427, -74.00597]
40 | santiago_chile = [-33.42628, -70.56656]
41 | Haversine.distance(new_york_city, santiago_chile).to_miles => 5123.73
42 | 
43 | 44 | Note: Haversine is used in the "geo_magic":https://github.com/kristianmandrup/geo_magic gem 45 | 46 | h2. Contributing to haversine 47 | 48 | * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet 49 | * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it 50 | * Fork the project 51 | * Start a feature/bugfix branch 52 | * Commit and push until you are happy with your contribution 53 | * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. 54 | * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. 55 | 56 | h2. Copyright 57 | 58 | Copyright (c) 2011 Kristian Mandrup. See LICENSE.txt for 59 | further details. 60 | --------------------------------------------------------------------------------