geocoder-sgonyea 1.1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/.travis.yml +23 -0
- data/CHANGELOG.md +298 -0
- data/LICENSE +20 -0
- data/README.md +656 -0
- data/Rakefile +25 -0
- data/bin/geocode +5 -0
- data/examples/autoexpire_cache.rb +28 -0
- data/gemfiles/Gemfile.mongoid-2.4.x +15 -0
- data/lib/generators/geocoder/config/config_generator.rb +14 -0
- data/lib/generators/geocoder/config/templates/initializer.rb +21 -0
- data/lib/geocoder.rb +55 -0
- data/lib/geocoder/cache.rb +85 -0
- data/lib/geocoder/calculations.rb +319 -0
- data/lib/geocoder/cli.rb +114 -0
- data/lib/geocoder/configuration.rb +130 -0
- data/lib/geocoder/configuration_hash.rb +11 -0
- data/lib/geocoder/exceptions.rb +21 -0
- data/lib/geocoder/lookup.rb +82 -0
- data/lib/geocoder/lookups/base.rb +250 -0
- data/lib/geocoder/lookups/bing.rb +47 -0
- data/lib/geocoder/lookups/freegeoip.rb +47 -0
- data/lib/geocoder/lookups/geocoder_ca.rb +54 -0
- data/lib/geocoder/lookups/google.rb +62 -0
- data/lib/geocoder/lookups/google_premier.rb +47 -0
- data/lib/geocoder/lookups/mapquest.rb +43 -0
- data/lib/geocoder/lookups/maxmind.rb +88 -0
- data/lib/geocoder/lookups/nominatim.rb +45 -0
- data/lib/geocoder/lookups/ovi.rb +52 -0
- data/lib/geocoder/lookups/test.rb +38 -0
- data/lib/geocoder/lookups/yahoo.rb +84 -0
- data/lib/geocoder/lookups/yandex.rb +54 -0
- data/lib/geocoder/models/active_record.rb +46 -0
- data/lib/geocoder/models/base.rb +42 -0
- data/lib/geocoder/models/mongo_base.rb +60 -0
- data/lib/geocoder/models/mongo_mapper.rb +26 -0
- data/lib/geocoder/models/mongoid.rb +32 -0
- data/lib/geocoder/query.rb +103 -0
- data/lib/geocoder/railtie.rb +26 -0
- data/lib/geocoder/request.rb +23 -0
- data/lib/geocoder/results/base.rb +67 -0
- data/lib/geocoder/results/bing.rb +48 -0
- data/lib/geocoder/results/freegeoip.rb +45 -0
- data/lib/geocoder/results/geocoder_ca.rb +60 -0
- data/lib/geocoder/results/google.rb +106 -0
- data/lib/geocoder/results/google_premier.rb +6 -0
- data/lib/geocoder/results/mapquest.rb +51 -0
- data/lib/geocoder/results/maxmind.rb +136 -0
- data/lib/geocoder/results/nominatim.rb +94 -0
- data/lib/geocoder/results/ovi.rb +62 -0
- data/lib/geocoder/results/test.rb +16 -0
- data/lib/geocoder/results/yahoo.rb +55 -0
- data/lib/geocoder/results/yandex.rb +80 -0
- data/lib/geocoder/sql.rb +106 -0
- data/lib/geocoder/stores/active_record.rb +259 -0
- data/lib/geocoder/stores/base.rb +120 -0
- data/lib/geocoder/stores/mongo_base.rb +85 -0
- data/lib/geocoder/stores/mongo_mapper.rb +13 -0
- data/lib/geocoder/stores/mongoid.rb +13 -0
- data/lib/geocoder/version.rb +3 -0
- data/lib/hash_recursive_merge.rb +74 -0
- data/lib/oauth_util.rb +112 -0
- data/lib/tasks/geocoder.rake +25 -0
- data/test/active_record_test.rb +15 -0
- data/test/cache_test.rb +19 -0
- data/test/calculations_test.rb +195 -0
- data/test/configuration_test.rb +78 -0
- data/test/custom_block_test.rb +32 -0
- data/test/error_handling_test.rb +43 -0
- data/test/fixtures/bing_invalid_key +1 -0
- data/test/fixtures/bing_madison_square_garden +40 -0
- data/test/fixtures/bing_no_results +16 -0
- data/test/fixtures/bing_reverse +42 -0
- data/test/fixtures/freegeoip_74_200_247_59 +12 -0
- data/test/fixtures/freegeoip_no_results +1 -0
- data/test/fixtures/geocoder_ca_madison_square_garden +1 -0
- data/test/fixtures/geocoder_ca_no_results +1 -0
- data/test/fixtures/geocoder_ca_reverse +34 -0
- data/test/fixtures/google_garbage +456 -0
- data/test/fixtures/google_madison_square_garden +57 -0
- data/test/fixtures/google_no_city_data +44 -0
- data/test/fixtures/google_no_locality +51 -0
- data/test/fixtures/google_no_results +4 -0
- data/test/fixtures/mapquest_madison_square_garden +52 -0
- data/test/fixtures/mapquest_no_results +7 -0
- data/test/fixtures/maxmind_24_24_24_21 +1 -0
- data/test/fixtures/maxmind_24_24_24_22 +1 -0
- data/test/fixtures/maxmind_24_24_24_23 +1 -0
- data/test/fixtures/maxmind_24_24_24_24 +1 -0
- data/test/fixtures/maxmind_74_200_247_59 +1 -0
- data/test/fixtures/maxmind_invalid_key +1 -0
- data/test/fixtures/maxmind_no_results +1 -0
- data/test/fixtures/nominatim_madison_square_garden +150 -0
- data/test/fixtures/nominatim_no_results +1 -0
- data/test/fixtures/ovi_madison_square_garden +72 -0
- data/test/fixtures/ovi_no_results +8 -0
- data/test/fixtures/yahoo_error +1 -0
- data/test/fixtures/yahoo_invalid_key +2 -0
- data/test/fixtures/yahoo_madison_square_garden +52 -0
- data/test/fixtures/yahoo_no_results +10 -0
- data/test/fixtures/yahoo_over_limit +2 -0
- data/test/fixtures/yandex_invalid_key +1 -0
- data/test/fixtures/yandex_kremlin +48 -0
- data/test/fixtures/yandex_no_city_and_town +112 -0
- data/test/fixtures/yandex_no_results +16 -0
- data/test/geocoder_test.rb +59 -0
- data/test/https_test.rb +16 -0
- data/test/integration/smoke_test.rb +26 -0
- data/test/lookup_test.rb +116 -0
- data/test/method_aliases_test.rb +25 -0
- data/test/mongoid_test.rb +39 -0
- data/test/mongoid_test_helper.rb +43 -0
- data/test/near_test.rb +43 -0
- data/test/oauth_util_test.rb +30 -0
- data/test/proxy_test.rb +23 -0
- data/test/query_test.rb +51 -0
- data/test/request_test.rb +29 -0
- data/test/result_test.rb +42 -0
- data/test/services_test.rb +277 -0
- data/test/test_helper.rb +279 -0
- data/test/test_mode_test.rb +50 -0
- metadata +170 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'bundler'
|
|
2
|
+
Bundler::GemHelper.install_tasks
|
|
3
|
+
|
|
4
|
+
require 'rake/testtask'
|
|
5
|
+
Rake::TestTask.new(:test) do |test|
|
|
6
|
+
test.libs << 'lib' << 'test'
|
|
7
|
+
test.pattern = 'test/*_test.rb'
|
|
8
|
+
test.verbose = true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
Rake::TestTask.new(:integration) do |test|
|
|
12
|
+
test.libs << 'lib' << 'test'
|
|
13
|
+
test.pattern = 'test/integration/*_test.rb'
|
|
14
|
+
test.verbose = true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
task :default => [:test, :integration]
|
|
18
|
+
|
|
19
|
+
require 'rdoc/task'
|
|
20
|
+
Rake::RDocTask.new do |rdoc|
|
|
21
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
22
|
+
rdoc.title = "Geocoder #{Geocoder::VERSION}"
|
|
23
|
+
rdoc.rdoc_files.include('*.rdoc')
|
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
25
|
+
end
|
data/bin/geocode
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# This class implements a cache with simple delegation to the Redis store, but
|
|
2
|
+
# when it creates a key/value pair, it also sends an EXPIRE command with a TTL.
|
|
3
|
+
# It should be fairly simple to do the same thing with Memcached.
|
|
4
|
+
class AutoexpireCache
|
|
5
|
+
def initialize(store)
|
|
6
|
+
@store = store
|
|
7
|
+
@ttl = 86400
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def [](url)
|
|
11
|
+
@store.[](url)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def []=(url, value)
|
|
15
|
+
@store.[]=(url, value)
|
|
16
|
+
@store.expire(url, @ttl)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def keys
|
|
20
|
+
@store.keys
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def del(url)
|
|
24
|
+
@store.del(url)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
Geocoder.configure(:cache => AutoexpireCache.new(Redis.new))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'rails/generators'
|
|
2
|
+
|
|
3
|
+
module Geocoder
|
|
4
|
+
class ConfigGenerator < Rails::Generators::Base
|
|
5
|
+
source_root File.expand_path("../templates", __FILE__)
|
|
6
|
+
|
|
7
|
+
desc "This generator creates an initializer file at config/initializers, " +
|
|
8
|
+
"with the default configuration options for Geocoder."
|
|
9
|
+
def add_initializer
|
|
10
|
+
template "initializer.rb", "config/initializers/geocoder.rb"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Geocoder.configure(
|
|
2
|
+
# geocoding options
|
|
3
|
+
# :timeout => 3, # geocoding service timeout (secs)
|
|
4
|
+
# :lookup => :google, # name of geocoding service (symbol)
|
|
5
|
+
# :language => :en, # ISO-639 language code
|
|
6
|
+
# :use_https => false, # use HTTPS for lookup requests? (if supported)
|
|
7
|
+
# :http_proxy => nil, # HTTP proxy server (user:pass@host:port)
|
|
8
|
+
# :https_proxy => nil, # HTTPS proxy server (user:pass@host:port)
|
|
9
|
+
# :api_key => nil, # API key for geocoding service
|
|
10
|
+
# :cache => nil, # cache object (must respond to #[], #[]=, and #keys)
|
|
11
|
+
# :cache_prefix => "geocoder:", # prefix (string) to use for all cache keys
|
|
12
|
+
|
|
13
|
+
# exceptions that should not be rescued by default
|
|
14
|
+
# (if you want to implement custom error handling);
|
|
15
|
+
# supports SocketError and TimeoutError
|
|
16
|
+
# :always_raise => [],
|
|
17
|
+
|
|
18
|
+
# calculation options
|
|
19
|
+
# :units => :mi, # :km for kilometers or :mi for miles
|
|
20
|
+
# :distances => :linear # :spherical or :linear
|
|
21
|
+
)
|
data/lib/geocoder.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require "geocoder/configuration"
|
|
2
|
+
require "geocoder/query"
|
|
3
|
+
require "geocoder/calculations"
|
|
4
|
+
require "geocoder/exceptions"
|
|
5
|
+
require "geocoder/cache"
|
|
6
|
+
require "geocoder/request"
|
|
7
|
+
require "geocoder/lookup"
|
|
8
|
+
require "geocoder/models/active_record" if defined?(::ActiveRecord)
|
|
9
|
+
require "geocoder/models/mongoid" if defined?(::Mongoid)
|
|
10
|
+
require "geocoder/models/mongo_mapper" if defined?(::MongoMapper)
|
|
11
|
+
|
|
12
|
+
module Geocoder
|
|
13
|
+
extend self
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# Search for information about an address or a set of coordinates.
|
|
17
|
+
#
|
|
18
|
+
def search(query, options = {})
|
|
19
|
+
query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
|
|
20
|
+
query.blank? ? [] : query.execute
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# Look up the coordinates of the given street or IP address.
|
|
25
|
+
#
|
|
26
|
+
def coordinates(address, options = {})
|
|
27
|
+
if (results = search(address, options)).size > 0
|
|
28
|
+
results.first.coordinates
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# Look up the address of the given coordinates ([lat,lon])
|
|
34
|
+
# or IP address (string).
|
|
35
|
+
#
|
|
36
|
+
def address(query, options = {})
|
|
37
|
+
if (results = search(query, options)).size > 0
|
|
38
|
+
results.first.address
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# The working Cache object, or +nil+ if none configured.
|
|
44
|
+
#
|
|
45
|
+
def cache
|
|
46
|
+
warn "WARNING: Calling Geocoder.cache is DEPRECATED. The #cache method now belongs to the Geocoder::Lookup object."
|
|
47
|
+
Geocoder::Lookup.get(Geocoder.config.lookup).send(:configuration).cache
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# load Railtie if Rails exists
|
|
52
|
+
if defined?(Rails)
|
|
53
|
+
require "geocoder/railtie"
|
|
54
|
+
Geocoder::Railtie.insert
|
|
55
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module Geocoder
|
|
2
|
+
class Cache
|
|
3
|
+
|
|
4
|
+
def initialize(store, prefix)
|
|
5
|
+
@store = store
|
|
6
|
+
@prefix = prefix
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Read from the Cache.
|
|
11
|
+
#
|
|
12
|
+
def [](url)
|
|
13
|
+
interpret case
|
|
14
|
+
when store.respond_to?(:[])
|
|
15
|
+
store[key_for(url)]
|
|
16
|
+
when store.respond_to?(:get)
|
|
17
|
+
store.get key_for(url)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Write to the Cache.
|
|
23
|
+
#
|
|
24
|
+
def []=(url, value)
|
|
25
|
+
case
|
|
26
|
+
when store.respond_to?(:[]=)
|
|
27
|
+
store[key_for(url)] = value
|
|
28
|
+
when store.respond_to?(:set)
|
|
29
|
+
store.set key_for(url), value
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# Delete cache entry for given URL,
|
|
35
|
+
# or pass <tt>:all</tt> to clear all URLs.
|
|
36
|
+
#
|
|
37
|
+
def expire(url)
|
|
38
|
+
if url == :all
|
|
39
|
+
urls.each{ |u| expire(u) }
|
|
40
|
+
else
|
|
41
|
+
expire_single_url(url)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
private # ----------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
attr_reader :prefix, :store
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
# Cache key for a given URL.
|
|
52
|
+
#
|
|
53
|
+
def key_for(url)
|
|
54
|
+
[prefix, url].join
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# Array of keys with the currently configured prefix
|
|
59
|
+
# that have non-nil values.
|
|
60
|
+
#
|
|
61
|
+
def keys
|
|
62
|
+
store.keys.select{ |k| k.match /^#{prefix}/ and interpret(store[k]) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
##
|
|
66
|
+
# Array of cached URLs.
|
|
67
|
+
#
|
|
68
|
+
def urls
|
|
69
|
+
keys.map{ |k| k[/^#{prefix}(.*)/, 1] }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
# Clean up value before returning. Namely, convert empty string to nil.
|
|
74
|
+
# (Some key/value stores return empty string instead of nil.)
|
|
75
|
+
#
|
|
76
|
+
def interpret(value)
|
|
77
|
+
value == "" ? nil : value
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def expire_single_url(url)
|
|
81
|
+
key = key_for(url)
|
|
82
|
+
store.respond_to?(:del) ? store.del(key) : store.delete(key)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
module Geocoder
|
|
2
|
+
module Calculations
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# Compass point names, listed clockwise starting at North.
|
|
7
|
+
#
|
|
8
|
+
# If you want bearings named using more, fewer, or different points
|
|
9
|
+
# override Geocoder::Calculations.COMPASS_POINTS with your own array.
|
|
10
|
+
#
|
|
11
|
+
COMPASS_POINTS = %w[N NE E SE S SW W NW]
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Radius of the Earth, in kilometers.
|
|
15
|
+
# Value taken from: http://en.wikipedia.org/wiki/Earth_radius
|
|
16
|
+
#
|
|
17
|
+
EARTH_RADIUS = 6371.0
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# Conversion factor: multiply by kilometers to get miles.
|
|
21
|
+
#
|
|
22
|
+
KM_IN_MI = 0.621371192
|
|
23
|
+
|
|
24
|
+
# Not a number constant
|
|
25
|
+
NAN = defined?(::Float::NAN) ? ::Float::NAN : 0 / 0.0
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Returns true if all given arguments are valid latitude/longitude values.
|
|
29
|
+
#
|
|
30
|
+
def coordinates_present?(*args)
|
|
31
|
+
args.each do |a|
|
|
32
|
+
# note that Float::NAN != Float::NAN
|
|
33
|
+
# still, this could probably be improved:
|
|
34
|
+
return false if (!a.is_a?(Numeric) or a.to_s == "NaN")
|
|
35
|
+
end
|
|
36
|
+
true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
##
|
|
40
|
+
# Distance spanned by one degree of latitude in the given units.
|
|
41
|
+
#
|
|
42
|
+
def latitude_degree_distance(units = nil)
|
|
43
|
+
units ||= Geocoder.config.units
|
|
44
|
+
2 * Math::PI * earth_radius(units) / 360
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Distance spanned by one degree of longitude at the given latitude.
|
|
49
|
+
# This ranges from around 69 miles at the equator to zero at the poles.
|
|
50
|
+
#
|
|
51
|
+
def longitude_degree_distance(latitude, units = nil)
|
|
52
|
+
units ||= Geocoder.config.units
|
|
53
|
+
latitude_degree_distance(units) * Math.cos(to_radians(latitude))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# Distance between two points on Earth (Haversine formula).
|
|
58
|
+
# Takes two points and an options hash.
|
|
59
|
+
# The points are given in the same way that points are given to all
|
|
60
|
+
# Geocoder methods that accept points as arguments. They can be:
|
|
61
|
+
#
|
|
62
|
+
# * an array of coordinates ([lat,lon])
|
|
63
|
+
# * a geocodable address (string)
|
|
64
|
+
# * a geocoded object (one which implements a +to_coordinates+ method
|
|
65
|
+
# which returns a [lat,lon] array
|
|
66
|
+
#
|
|
67
|
+
# The options hash supports:
|
|
68
|
+
#
|
|
69
|
+
# * <tt>:units</tt> - <tt>:mi</tt> or <tt>:km</tt>
|
|
70
|
+
# Use Geocoder.configure(:units => ...) to configure default units.
|
|
71
|
+
#
|
|
72
|
+
def distance_between(point1, point2, options = {})
|
|
73
|
+
|
|
74
|
+
# set default options
|
|
75
|
+
options[:units] ||= Geocoder.config.units
|
|
76
|
+
|
|
77
|
+
# convert to coordinate arrays
|
|
78
|
+
point1 = extract_coordinates(point1)
|
|
79
|
+
point2 = extract_coordinates(point2)
|
|
80
|
+
|
|
81
|
+
# convert degrees to radians
|
|
82
|
+
point1 = to_radians(point1)
|
|
83
|
+
point2 = to_radians(point2)
|
|
84
|
+
|
|
85
|
+
# compute deltas
|
|
86
|
+
dlat = point2[0] - point1[0]
|
|
87
|
+
dlon = point2[1] - point1[1]
|
|
88
|
+
|
|
89
|
+
a = (Math.sin(dlat / 2))**2 + Math.cos(point1[0]) *
|
|
90
|
+
(Math.sin(dlon / 2))**2 * Math.cos(point2[0])
|
|
91
|
+
c = 2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a))
|
|
92
|
+
c * earth_radius(options[:units])
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
##
|
|
96
|
+
# Bearing between two points on Earth.
|
|
97
|
+
# Returns a number of degrees from due north (clockwise).
|
|
98
|
+
#
|
|
99
|
+
# See Geocoder::Calculations.distance_between for
|
|
100
|
+
# ways of specifying the points. Also accepts an options hash:
|
|
101
|
+
#
|
|
102
|
+
# * <tt>:method</tt> - <tt>:linear</tt> or <tt>:spherical</tt>;
|
|
103
|
+
# the spherical method is "correct" in that it returns the shortest path
|
|
104
|
+
# (one along a great circle) but the linear method is less confusing
|
|
105
|
+
# (returns due east or west when given two points with the same latitude).
|
|
106
|
+
# Use Geocoder.configure(:distances => ...) to configure calculation method.
|
|
107
|
+
#
|
|
108
|
+
# Based on: http://www.movable-type.co.uk/scripts/latlong.html
|
|
109
|
+
#
|
|
110
|
+
def bearing_between(point1, point2, options = {})
|
|
111
|
+
|
|
112
|
+
# set default options
|
|
113
|
+
options[:method] ||= Geocoder.config.distances
|
|
114
|
+
options[:method] = :linear unless options[:method] == :spherical
|
|
115
|
+
|
|
116
|
+
# convert to coordinate arrays
|
|
117
|
+
point1 = extract_coordinates(point1)
|
|
118
|
+
point2 = extract_coordinates(point2)
|
|
119
|
+
|
|
120
|
+
# convert degrees to radians
|
|
121
|
+
point1 = to_radians(point1)
|
|
122
|
+
point2 = to_radians(point2)
|
|
123
|
+
|
|
124
|
+
# compute deltas
|
|
125
|
+
dlat = point2[0] - point1[0]
|
|
126
|
+
dlon = point2[1] - point1[1]
|
|
127
|
+
|
|
128
|
+
case options[:method]
|
|
129
|
+
when :linear
|
|
130
|
+
y = dlon
|
|
131
|
+
x = dlat
|
|
132
|
+
|
|
133
|
+
when :spherical
|
|
134
|
+
y = Math.sin(dlon) * Math.cos(point2[0])
|
|
135
|
+
x = Math.cos(point1[0]) * Math.sin(point2[0]) -
|
|
136
|
+
Math.sin(point1[0]) * Math.cos(point2[0]) * Math.cos(dlon)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
bearing = Math.atan2(x,y)
|
|
140
|
+
# Answer is in radians counterclockwise from due east.
|
|
141
|
+
# Convert to degrees clockwise from due north:
|
|
142
|
+
(90 - to_degrees(bearing) + 360) % 360
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
##
|
|
146
|
+
# Translate a bearing (float) into a compass direction (string, eg "North").
|
|
147
|
+
#
|
|
148
|
+
def compass_point(bearing, points = COMPASS_POINTS)
|
|
149
|
+
seg_size = 360 / points.size
|
|
150
|
+
points[((bearing + (seg_size / 2)) % 360) / seg_size]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
##
|
|
154
|
+
# Compute the geographic center (aka geographic midpoint, center of
|
|
155
|
+
# gravity) for an array of geocoded objects and/or [lat,lon] arrays
|
|
156
|
+
# (can be mixed). Any objects missing coordinates are ignored. Follows
|
|
157
|
+
# the procedure documented at http://www.geomidpoint.com/calculation.html.
|
|
158
|
+
#
|
|
159
|
+
def geographic_center(points)
|
|
160
|
+
|
|
161
|
+
# convert objects to [lat,lon] arrays and convert degrees to radians
|
|
162
|
+
coords = points.map{ |p| to_radians(extract_coordinates(p)) }
|
|
163
|
+
|
|
164
|
+
# convert to Cartesian coordinates
|
|
165
|
+
x = []; y = []; z = []
|
|
166
|
+
coords.each do |p|
|
|
167
|
+
x << Math.cos(p[0]) * Math.cos(p[1])
|
|
168
|
+
y << Math.cos(p[0]) * Math.sin(p[1])
|
|
169
|
+
z << Math.sin(p[0])
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# compute average coordinate values
|
|
173
|
+
xa, ya, za = [x,y,z].map do |c|
|
|
174
|
+
c.inject(0){ |tot,i| tot += i } / c.size.to_f
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# convert back to latitude/longitude
|
|
178
|
+
lon = Math.atan2(ya, xa)
|
|
179
|
+
hyp = Math.sqrt(xa**2 + ya**2)
|
|
180
|
+
lat = Math.atan2(za, hyp)
|
|
181
|
+
|
|
182
|
+
# return answer in degrees
|
|
183
|
+
to_degrees [lat, lon]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
##
|
|
187
|
+
# Returns coordinates of the southwest and northeast corners of a box
|
|
188
|
+
# with the given point at its center. The radius is the shortest distance
|
|
189
|
+
# from the center point to any side of the box (the length of each side
|
|
190
|
+
# is twice the radius).
|
|
191
|
+
#
|
|
192
|
+
# This is useful for finding corner points of a map viewport, or for
|
|
193
|
+
# roughly limiting the possible solutions in a geo-spatial search
|
|
194
|
+
# (ActiveRecord queries use it thusly).
|
|
195
|
+
#
|
|
196
|
+
# See Geocoder::Calculations.distance_between for
|
|
197
|
+
# ways of specifying the point. Also accepts an options hash:
|
|
198
|
+
#
|
|
199
|
+
# * <tt>:units</tt> - <tt>:mi</tt> or <tt>:km</tt>.
|
|
200
|
+
# Use Geocoder.configure(:units => ...) to configure default units.
|
|
201
|
+
#
|
|
202
|
+
def bounding_box(point, radius, options = {})
|
|
203
|
+
lat,lon = extract_coordinates(point)
|
|
204
|
+
radius = radius.to_f
|
|
205
|
+
units = options[:units] || Geocoder.config.units
|
|
206
|
+
[
|
|
207
|
+
lat - (radius / latitude_degree_distance(units)),
|
|
208
|
+
lon - (radius / longitude_degree_distance(lat, units)),
|
|
209
|
+
lat + (radius / latitude_degree_distance(units)),
|
|
210
|
+
lon + (radius / longitude_degree_distance(lat, units))
|
|
211
|
+
]
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
##
|
|
215
|
+
# Convert degrees to radians.
|
|
216
|
+
# If an array (or multiple arguments) is passed,
|
|
217
|
+
# converts each value and returns array.
|
|
218
|
+
#
|
|
219
|
+
def to_radians(*args)
|
|
220
|
+
args = args.first if args.first.is_a?(Array)
|
|
221
|
+
if args.size == 1
|
|
222
|
+
args.first * (Math::PI / 180)
|
|
223
|
+
else
|
|
224
|
+
args.map{ |i| to_radians(i) }
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
##
|
|
229
|
+
# Convert radians to degrees.
|
|
230
|
+
# If an array (or multiple arguments) is passed,
|
|
231
|
+
# converts each value and returns array.
|
|
232
|
+
#
|
|
233
|
+
def to_degrees(*args)
|
|
234
|
+
args = args.first if args.first.is_a?(Array)
|
|
235
|
+
if args.size == 1
|
|
236
|
+
(args.first * 180.0) / Math::PI
|
|
237
|
+
else
|
|
238
|
+
args.map{ |i| to_degrees(i) }
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def distance_to_radians(distance, units = nil)
|
|
243
|
+
units ||= Geocoder.config.units
|
|
244
|
+
distance.to_f / earth_radius(units)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def radians_to_distance(radians, units = nil)
|
|
248
|
+
units ||= Geocoder.config.units
|
|
249
|
+
radians * earth_radius(units)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
##
|
|
253
|
+
# Convert miles to kilometers.
|
|
254
|
+
#
|
|
255
|
+
def to_kilometers(mi)
|
|
256
|
+
mi * mi_in_km
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
##
|
|
260
|
+
# Convert kilometers to miles.
|
|
261
|
+
#
|
|
262
|
+
def to_miles(km)
|
|
263
|
+
km * km_in_mi
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
##
|
|
267
|
+
# Radius of the Earth in the given units (:mi or :km).
|
|
268
|
+
# Use Geocoder.configure(:units => ...) to configure default units.
|
|
269
|
+
#
|
|
270
|
+
def earth_radius(units = nil)
|
|
271
|
+
units ||= Geocoder.config.units
|
|
272
|
+
units == :km ? EARTH_RADIUS : to_miles(EARTH_RADIUS)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
##
|
|
276
|
+
# Conversion factor: km to mi.
|
|
277
|
+
#
|
|
278
|
+
def km_in_mi
|
|
279
|
+
KM_IN_MI
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
##
|
|
283
|
+
# Conversion factor: mi to km.
|
|
284
|
+
#
|
|
285
|
+
def mi_in_km
|
|
286
|
+
1.0 / KM_IN_MI
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
##
|
|
290
|
+
# Takes an object which is a [lat,lon] array, a geocodable string,
|
|
291
|
+
# or an object that implements +to_coordinates+ and returns a
|
|
292
|
+
# [lat,lon] array. Note that if a string is passed this may be a slow-
|
|
293
|
+
# running method and may return nil.
|
|
294
|
+
#
|
|
295
|
+
def extract_coordinates(point)
|
|
296
|
+
case point
|
|
297
|
+
when Array
|
|
298
|
+
if point.size == 2
|
|
299
|
+
lat, lon = point
|
|
300
|
+
if !lat.nil? && lat.respond_to?(:to_f) and
|
|
301
|
+
!lon.nil? && lon.respond_to?(:to_f)
|
|
302
|
+
then
|
|
303
|
+
return [ lat.to_f, lon.to_f ]
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
when String
|
|
307
|
+
point = Geocoder.coordinates(point) and return point
|
|
308
|
+
else
|
|
309
|
+
if point.respond_to?(:to_coordinates)
|
|
310
|
+
if Array === array = point.to_coordinates
|
|
311
|
+
return extract_coordinates(array)
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
[ NAN, NAN ]
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|