geocoder 0.9.11 → 0.9.12
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.
Potentially problematic release.
This version of geocoder might be problematic. Click here for more details.
- data/CHANGELOG.rdoc +12 -1
- data/README.rdoc +108 -57
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/geocoder.rb +19 -8
- data/lib/geocoder/calculations.rb +97 -35
- data/lib/geocoder/lookups/base.rb +19 -5
- data/lib/geocoder/models/active_record.rb +41 -0
- data/lib/geocoder/models/base.rb +44 -0
- data/lib/geocoder/models/mongoid.rb +47 -0
- data/lib/geocoder/railtie.rb +3 -57
- data/lib/geocoder/{orms → stores}/active_record.rb +18 -15
- data/lib/geocoder/{orms → stores}/active_record_legacy.rb +1 -1
- data/lib/geocoder/{orms → stores}/base.rb +37 -11
- data/lib/geocoder/stores/mongoid.rb +73 -0
- data/test/geocoder_test.rb +78 -25
- metadata +56 -52
@@ -20,9 +20,23 @@ module Geocoder
|
|
20
20
|
# "205.128.54.202") for geocoding, or coordinates (latitude, longitude)
|
21
21
|
# for reverse geocoding. Returns an array of <tt>Geocoder::Result</tt>s.
|
22
22
|
#
|
23
|
-
def search(*args)
|
24
|
-
|
25
|
-
|
23
|
+
def search(query, *args)
|
24
|
+
# convert coordinates as separate arguments to an array
|
25
|
+
if query.is_a?(Numeric) and args.first.is_a?(Numeric)
|
26
|
+
warn "DEPRECATION WARNING: Instead of passing latitude/longitude as separate arguments to the search method, please pass an array: [#{query},#{args.first}]. The old argument format will not be supported in Geocoder v.1.0."
|
27
|
+
query = [query, args.first]
|
28
|
+
end
|
29
|
+
|
30
|
+
# if coordinates given as string, turn into array
|
31
|
+
query = query.split(/\s*,\s*/) if coordinates?(query)
|
32
|
+
|
33
|
+
if query.is_a?(Array)
|
34
|
+
reverse = true
|
35
|
+
query = query.join(',')
|
36
|
+
else
|
37
|
+
reverse = false
|
38
|
+
end
|
39
|
+
results(query, reverse).map{ |r| result_class.new(r) }
|
26
40
|
end
|
27
41
|
|
28
42
|
|
@@ -113,14 +127,14 @@ module Geocoder
|
|
113
127
|
# Is the given string a loopback IP address?
|
114
128
|
#
|
115
129
|
def loopback_address?(ip)
|
116
|
-
!!(ip == "0.0.0.0" or ip.match(/^127/))
|
130
|
+
!!(ip == "0.0.0.0" or ip.to_s.match(/^127/))
|
117
131
|
end
|
118
132
|
|
119
133
|
##
|
120
134
|
# Does the given string look like latitude/longitude coordinates?
|
121
135
|
#
|
122
136
|
def coordinates?(value)
|
123
|
-
!!value.to_s.match(/^[0-9\.\-]+,
|
137
|
+
!!value.to_s.match(/^[0-9\.\-]+, *[0-9\.\-]+$/)
|
124
138
|
end
|
125
139
|
|
126
140
|
##
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'geocoder/models/base'
|
2
|
+
|
3
|
+
module Geocoder
|
4
|
+
module Model
|
5
|
+
module ActiveRecord
|
6
|
+
include Base
|
7
|
+
|
8
|
+
##
|
9
|
+
# Set attribute names and include the Geocoder module.
|
10
|
+
#
|
11
|
+
def geocoded_by(address_attr, options = {}, &block)
|
12
|
+
geocoder_init(
|
13
|
+
:geocode => true,
|
14
|
+
:user_address => address_attr,
|
15
|
+
:latitude => options[:latitude] || :latitude,
|
16
|
+
:longitude => options[:longitude] || :longitude,
|
17
|
+
:geocode_block => block
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Set attribute names and include the Geocoder module.
|
23
|
+
#
|
24
|
+
def reverse_geocoded_by(latitude_attr, longitude_attr, options = {}, &block)
|
25
|
+
geocoder_init(
|
26
|
+
:reverse_geocode => true,
|
27
|
+
:fetched_address => options[:address] || :address,
|
28
|
+
:latitude => latitude_attr,
|
29
|
+
:longitude => longitude_attr,
|
30
|
+
:reverse_block => block
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
private # --------------------------------------------------------------
|
36
|
+
|
37
|
+
def geocoder_file_name; "active_record"; end
|
38
|
+
def geocoder_module_name; "ActiveRecord"; end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'geocoder'
|
2
|
+
|
3
|
+
module Geocoder
|
4
|
+
|
5
|
+
##
|
6
|
+
# Methods for invoking Geocoder in a model.
|
7
|
+
#
|
8
|
+
module Model
|
9
|
+
module Base
|
10
|
+
|
11
|
+
def geocoder_options
|
12
|
+
@geocoder_options
|
13
|
+
end
|
14
|
+
|
15
|
+
def geocoded_by
|
16
|
+
fail
|
17
|
+
end
|
18
|
+
|
19
|
+
def reverse_geocoded_by
|
20
|
+
fail
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
private # ----------------------------------------------------------------
|
25
|
+
|
26
|
+
def geocoder_init(options)
|
27
|
+
unless geocoder_initialized?
|
28
|
+
@geocoder_options = {}
|
29
|
+
require "geocoder/stores/#{geocoder_file_name}"
|
30
|
+
include eval("Geocoder::Store::" + geocoder_module_name)
|
31
|
+
end
|
32
|
+
@geocoder_options.merge! options
|
33
|
+
end
|
34
|
+
|
35
|
+
def geocoder_initialized?
|
36
|
+
begin
|
37
|
+
included_modules.include? eval("Geocoder::Store::" + geocoder_module_name)
|
38
|
+
rescue NameError
|
39
|
+
false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'geocoder/models/base'
|
2
|
+
|
3
|
+
module Geocoder
|
4
|
+
module Model
|
5
|
+
module Mongoid
|
6
|
+
include Base
|
7
|
+
|
8
|
+
def self.included(base); base.extend(self); end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Set attribute names and include the Geocoder module.
|
12
|
+
#
|
13
|
+
def geocoded_by(address_attr, options = {}, &block)
|
14
|
+
geocoder_init(
|
15
|
+
:geocode => true,
|
16
|
+
:user_address => address_attr,
|
17
|
+
:coordinates => options[:coordinates] || :coordinates,
|
18
|
+
:geocode_block => block
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Set attribute names and include the Geocoder module.
|
24
|
+
#
|
25
|
+
def reverse_geocoded_by(coordinates_attr, options = {}, &block)
|
26
|
+
geocoder_init(
|
27
|
+
:reverse_geocode => true,
|
28
|
+
:fetched_address => options[:address] || :address,
|
29
|
+
:coordinates => coordinates_attr,
|
30
|
+
:reverse_block => block
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
private # --------------------------------------------------------------
|
36
|
+
|
37
|
+
def geocoder_file_name; "mongoid"; end
|
38
|
+
def geocoder_module_name; "Mongoid"; end
|
39
|
+
|
40
|
+
def geocoder_init(options)
|
41
|
+
super(options)
|
42
|
+
index [[ geocoder_options[:coordinates], Mongo::GEO2D ]],
|
43
|
+
:min => -180, :max => 180 # create 2d index
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/geocoder/railtie.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'geocoder'
|
2
|
+
require 'geocoder/models/active_record'
|
2
3
|
|
3
4
|
module Geocoder
|
4
5
|
if defined? Rails::Railtie
|
@@ -17,63 +18,8 @@ module Geocoder
|
|
17
18
|
|
18
19
|
class Railtie
|
19
20
|
def self.insert
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
##
|
26
|
-
# Methods available in the model class before Geocoder is invoked.
|
27
|
-
#
|
28
|
-
module ModelMethods
|
29
|
-
|
30
|
-
##
|
31
|
-
# Set attribute names and include the Geocoder module.
|
32
|
-
#
|
33
|
-
def geocoded_by(address_attr, options = {}, &block)
|
34
|
-
geocoder_init(
|
35
|
-
:geocode => true,
|
36
|
-
:user_address => address_attr,
|
37
|
-
:latitude => options[:latitude] || :latitude,
|
38
|
-
:longitude => options[:longitude] || :longitude,
|
39
|
-
:geocode_block => block
|
40
|
-
)
|
41
|
-
end
|
42
|
-
|
43
|
-
##
|
44
|
-
# Set attribute names and include the Geocoder module.
|
45
|
-
#
|
46
|
-
def reverse_geocoded_by(latitude_attr, longitude_attr, options = {}, &block)
|
47
|
-
geocoder_init(
|
48
|
-
:reverse_geocode => true,
|
49
|
-
:fetched_address => options[:address] || :address,
|
50
|
-
:latitude => latitude_attr,
|
51
|
-
:longitude => longitude_attr,
|
52
|
-
:reverse_block => block
|
53
|
-
)
|
54
|
-
end
|
55
|
-
|
56
|
-
def geocoder_options
|
57
|
-
@geocoder_options
|
58
|
-
end
|
59
|
-
|
60
|
-
|
61
|
-
private # ----------------------------------------------------------------
|
62
|
-
|
63
|
-
def geocoder_init(options)
|
64
|
-
unless geocoder_initialized?
|
65
|
-
@geocoder_options = {}
|
66
|
-
require 'geocoder/orms/active_record'
|
67
|
-
include Geocoder::Orm::ActiveRecord
|
68
|
-
end
|
69
|
-
@geocoder_options.merge! options
|
70
|
-
end
|
71
|
-
|
72
|
-
def geocoder_initialized?
|
73
|
-
begin
|
74
|
-
included_modules.include? Geocoder::Orm::ActiveRecord
|
75
|
-
rescue NameError
|
76
|
-
false
|
21
|
+
if defined?(::ActiveRecord)
|
22
|
+
::ActiveRecord::Base.extend(Model::ActiveRecord)
|
77
23
|
end
|
78
24
|
end
|
79
25
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
require 'geocoder/
|
2
|
-
require 'geocoder/
|
1
|
+
require 'geocoder/stores/base'
|
2
|
+
require 'geocoder/stores/active_record_legacy'
|
3
3
|
|
4
4
|
##
|
5
5
|
# Add geocoding functionality to any ActiveRecord object.
|
6
6
|
#
|
7
|
-
module Geocoder::
|
7
|
+
module Geocoder::Store
|
8
8
|
module ActiveRecord
|
9
9
|
include Base
|
10
10
|
include ActiveRecord::Legacy
|
@@ -34,8 +34,7 @@ module Geocoder::Orm
|
|
34
34
|
# for details).
|
35
35
|
#
|
36
36
|
scope :near, lambda{ |location, *args|
|
37
|
-
latitude, longitude =
|
38
|
-
location : Geocoder.coordinates(location)
|
37
|
+
latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
|
39
38
|
if latitude and longitude
|
40
39
|
near_scope_options(latitude, longitude, *args)
|
41
40
|
else
|
@@ -50,6 +49,8 @@ module Geocoder::Orm
|
|
50
49
|
#
|
51
50
|
module ClassMethods
|
52
51
|
|
52
|
+
private # ----------------------------------------------------------------
|
53
|
+
|
53
54
|
##
|
54
55
|
# Get options hash suitable for passing to ActiveRecord.find to get
|
55
56
|
# records within a radius (in miles) of the given point.
|
@@ -63,9 +64,7 @@ module Geocoder::Orm
|
|
63
64
|
# between the given point and each found nearby point;
|
64
65
|
# set to false for no bearing calculation
|
65
66
|
# * +:select+ - string with the SELECT SQL fragment (e.g. “id, name”)
|
66
|
-
# * +:order+ - column(s) for ORDER BY SQL clause
|
67
|
-
# * +:limit+ - number of records to return (for LIMIT SQL clause)
|
68
|
-
# * +:offset+ - number of records to skip (for OFFSET SQL clause)
|
67
|
+
# * +:order+ - column(s) for ORDER BY SQL clause; default is distance
|
69
68
|
# * +:exclude+ - an object to exclude (used by the +nearbys+ method)
|
70
69
|
#
|
71
70
|
def near_scope_options(latitude, longitude, radius = 20, options = {})
|
@@ -77,9 +76,6 @@ module Geocoder::Orm
|
|
77
76
|
end
|
78
77
|
end
|
79
78
|
|
80
|
-
|
81
|
-
private # ----------------------------------------------------------------
|
82
|
-
|
83
79
|
##
|
84
80
|
# Scope options hash for use with a database that supports POWER(),
|
85
81
|
# SQRT(), PI(), and trigonometric functions SIN(), COS(), ASIN(),
|
@@ -157,12 +153,16 @@ module Geocoder::Orm
|
|
157
153
|
dx = Geocoder::Calculations.longitude_degree_distance(30, options[:units] || :mi)
|
158
154
|
dy = Geocoder::Calculations.latitude_degree_distance(options[:units] || :mi)
|
159
155
|
|
160
|
-
|
161
|
-
|
156
|
+
# sin of 45 degrees = average x or y component of vector
|
157
|
+
factor = Math.sin(Math::PI / 4)
|
158
|
+
|
159
|
+
distance = "(#{dy} * ABS(#{lat_attr} - #{latitude}) * #{factor}) + " +
|
160
|
+
"(#{dx} * ABS(#{lon_attr} - #{longitude}) * #{factor})"
|
162
161
|
default_near_scope_options(latitude, longitude, radius, options).merge(
|
163
162
|
:select => "#{options[:select] || '*'}, " +
|
164
163
|
"#{distance} AS distance" +
|
165
|
-
(bearing ? ", #{bearing} AS bearing" : "")
|
164
|
+
(bearing ? ", #{bearing} AS bearing" : ""),
|
165
|
+
:order => distance
|
166
166
|
)
|
167
167
|
end
|
168
168
|
|
@@ -172,7 +172,7 @@ module Geocoder::Orm
|
|
172
172
|
def default_near_scope_options(latitude, longitude, radius, options)
|
173
173
|
lat_attr = geocoder_options[:latitude]
|
174
174
|
lon_attr = geocoder_options[:longitude]
|
175
|
-
b = Geocoder::Calculations.bounding_box(latitude, longitude, radius, options)
|
175
|
+
b = Geocoder::Calculations.bounding_box([latitude, longitude], radius, options)
|
176
176
|
conditions = \
|
177
177
|
["#{lat_attr} BETWEEN ? AND ? AND #{lon_attr} BETWEEN ? AND ?"] +
|
178
178
|
[b[0], b[2], b[1], b[3]]
|
@@ -180,6 +180,9 @@ module Geocoder::Orm
|
|
180
180
|
conditions[0] << " AND #{table_name}.id != ?"
|
181
181
|
conditions << obj.id
|
182
182
|
end
|
183
|
+
if options[:limit] || options[:offset]
|
184
|
+
warn "DEPRECATION WARNING: The :limit and :offset options to Geocoder's 'near' method are deprecated and will be removed in Geocoder v1.0. Please specify these options using ARel relations instead, for example: Place.near(...).limit(10).offset(20)."
|
185
|
+
end
|
183
186
|
{
|
184
187
|
:group => columns.map{ |c| "#{table_name}.#{c.name}" }.join(','),
|
185
188
|
:order => options[:order],
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Geocoder
|
2
|
-
module
|
2
|
+
module Store
|
3
3
|
module Base
|
4
4
|
|
5
5
|
##
|
@@ -18,17 +18,44 @@ module Geocoder
|
|
18
18
|
|
19
19
|
##
|
20
20
|
# Calculate the distance from the object to an arbitrary point.
|
21
|
-
#
|
22
|
-
#
|
21
|
+
# See Geocoder::Calculations.distance_between for ways of specifying
|
22
|
+
# the point. Also takes a symbol specifying the units
|
23
|
+
# (:mi or :km; default is :mi).
|
23
24
|
#
|
24
|
-
def distance_to(
|
25
|
+
def distance_to(point, *args)
|
26
|
+
if point.is_a?(Numeric) and args[0].is_a?(Numeric)
|
27
|
+
warn "DEPRECATION WARNING: Instead of passing latitude/longitude as separate arguments to the distance_to/from method, please pass an array [#{point},#{args[0]}], a geocoded object, or a geocodable address (string). The old argument format will not be supported in Geocoder v.1.0."
|
28
|
+
point = [point, args.shift]
|
29
|
+
end
|
25
30
|
return nil unless geocoded?
|
26
|
-
|
27
|
-
|
31
|
+
Geocoder::Calculations.distance_between(
|
32
|
+
to_coordinates, point, :units => args.pop || :mi)
|
28
33
|
end
|
29
34
|
|
30
35
|
alias_method :distance_from, :distance_to
|
31
36
|
|
37
|
+
##
|
38
|
+
# Calculate the bearing from the object to another point.
|
39
|
+
# See Geocoder::Calculations.distance_between for
|
40
|
+
# ways of specifying the point.
|
41
|
+
#
|
42
|
+
def bearing_to(point, options = {})
|
43
|
+
return nil unless geocoded?
|
44
|
+
Geocoder::Calculations.bearing_between(
|
45
|
+
to_coordinates, point, options)
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Calculate the bearing from another point to the object.
|
50
|
+
# See Geocoder::Calculations.distance_between for
|
51
|
+
# ways of specifying the point.
|
52
|
+
#
|
53
|
+
def bearing_from(point, options = {})
|
54
|
+
return nil unless geocoded?
|
55
|
+
Geocoder::Calculations.bearing_between(
|
56
|
+
point, to_coordinates, options)
|
57
|
+
end
|
58
|
+
|
32
59
|
##
|
33
60
|
# Get nearby geocoded objects.
|
34
61
|
# Takes the same options hash as the near class method (scope).
|
@@ -40,7 +67,7 @@ module Geocoder
|
|
40
67
|
warn "DEPRECATION WARNING: The units argument to the nearbys method has been replaced with an options hash (same options hash as the near scope). You should instead call: obj.nearbys(#{radius}, :units => #{options[:units]}). The old syntax will not be supported in Geocoder v1.0."
|
41
68
|
end
|
42
69
|
options.merge!(:exclude => self)
|
43
|
-
self.class.near(
|
70
|
+
self.class.near(self, radius, options)
|
44
71
|
end
|
45
72
|
|
46
73
|
##
|
@@ -72,15 +99,14 @@ module Geocoder
|
|
72
99
|
def do_lookup(reverse = false)
|
73
100
|
options = self.class.geocoder_options
|
74
101
|
if reverse and options[:reverse_geocode]
|
75
|
-
|
102
|
+
query = to_coordinates
|
76
103
|
elsif !reverse and options[:geocode]
|
77
|
-
|
104
|
+
query = send(options[:user_address])
|
78
105
|
else
|
79
106
|
return
|
80
107
|
end
|
81
|
-
args.map!{ |a| send(options[a]) }
|
82
108
|
|
83
|
-
if (results = Geocoder.search(
|
109
|
+
if (results = Geocoder.search(query)).size > 0
|
84
110
|
|
85
111
|
# execute custom block, if specified in configuration
|
86
112
|
block_key = reverse ? :reverse_block : :geocode_block
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'geocoder/stores/base'
|
2
|
+
|
3
|
+
module Geocoder::Store
|
4
|
+
module Mongoid
|
5
|
+
include Base
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.class_eval do
|
9
|
+
|
10
|
+
scope :geocoded, lambda {
|
11
|
+
where(geocoder_options[:coordinates].ne => nil)
|
12
|
+
}
|
13
|
+
|
14
|
+
scope :not_geocoded, lambda {
|
15
|
+
where(geocoder_options[:coordinates] => nil)
|
16
|
+
}
|
17
|
+
|
18
|
+
scope :near, lambda{ |location, *args|
|
19
|
+
coords = Geocoder::Calculations.extract_coordinates(location)
|
20
|
+
radius = args.size > 0 ? args.shift : 20
|
21
|
+
options = args.size > 0 ? args.shift : {}
|
22
|
+
conds = {:coordinates => {
|
23
|
+
"$nearSphere" => coords.reverse,
|
24
|
+
"$maxDistance" => Geocoder::Calculations.distance_to_radians(
|
25
|
+
radius, options[:units] || :mi)
|
26
|
+
}}
|
27
|
+
if obj = options[:exclude]
|
28
|
+
conds[:_id.ne] = obj.id
|
29
|
+
end
|
30
|
+
criteria.where(conds)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Coordinates [lat,lon] of the object.
|
37
|
+
# This method always returns coordinates in lat,lon order,
|
38
|
+
# even though internally they are stored in the opposite order.
|
39
|
+
#
|
40
|
+
def to_coordinates
|
41
|
+
coords = send(self.class.geocoder_options[:coordinates])
|
42
|
+
coords.is_a?(Array) ? coords.reverse : []
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Look up coordinates and assign to +latitude+ and +longitude+ attributes
|
47
|
+
# (or other as specified in +geocoded_by+). Returns coordinates (array).
|
48
|
+
#
|
49
|
+
def geocode
|
50
|
+
do_lookup(false) do |o,rs|
|
51
|
+
r = rs.first
|
52
|
+
unless r.coordinates.nil?
|
53
|
+
o.send :write_attribute, self.class.geocoder_options[:coordinates], r.coordinates.reverse
|
54
|
+
end
|
55
|
+
r.coordinates
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Look up address and assign to +address+ attribute (or other as specified
|
61
|
+
# in +reverse_geocoded_by+). Returns address (string).
|
62
|
+
#
|
63
|
+
def reverse_geocode
|
64
|
+
do_lookup(true) do |o,rs|
|
65
|
+
r = rs.first
|
66
|
+
unless r.address.nil?
|
67
|
+
o.send :write_attribute, self.class.geocoder_options[:fetched_address], r.address
|
68
|
+
end
|
69
|
+
r.address
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|