geocoder 1.1.1 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of geocoder might be problematic. Click here for more details.
- data/.gitignore +1 -0
- data/CHANGELOG.rdoc +11 -0
- data/README.rdoc +24 -19
- data/lib/generators/geocoder/config/config_generator.rb +14 -0
- data/lib/generators/geocoder/config/templates/initializer.rb +25 -0
- data/lib/geocoder.rb +4 -4
- data/lib/geocoder/cache.rb +12 -2
- data/lib/geocoder/calculations.rb +45 -17
- data/lib/geocoder/configuration.rb +87 -42
- data/lib/geocoder/lookups/base.rb +23 -20
- data/lib/geocoder/lookups/freegeoip.rb +4 -0
- data/lib/geocoder/lookups/geocoder_ca.rb +1 -0
- data/lib/geocoder/models/active_record.rb +7 -2
- data/lib/geocoder/models/base.rb +4 -2
- data/lib/geocoder/models/mongo_base.rb +13 -8
- data/lib/geocoder/models/mongo_mapper.rb +4 -2
- data/lib/geocoder/models/mongoid.rb +4 -2
- data/lib/geocoder/results/base.rb +2 -1
- data/lib/geocoder/results/nominatim.rb +1 -1
- data/lib/geocoder/stores/active_record.rb +57 -28
- data/lib/geocoder/stores/base.rb +6 -3
- data/lib/geocoder/stores/mongo_base.rb +3 -1
- data/lib/geocoder/version.rb +1 -1
- data/lib/tasks/geocoder.rake +14 -1
- data/test/calculations_test.rb +36 -1
- data/test/configuration_test.rb +90 -0
- data/test/custom_block_test.rb +1 -0
- data/test/input_handling_test.rb +2 -0
- data/test/lookup_test.rb +7 -0
- data/test/mongoid_test.rb +15 -8
- data/test/mongoid_test_helper.rb +11 -0
- data/test/test_helper.rb +2 -1
- metadata +5 -5
@@ -33,7 +33,11 @@ module Geocoder
|
|
33
33
|
else
|
34
34
|
reverse = false
|
35
35
|
end
|
36
|
-
results(query, reverse).map{ |r|
|
36
|
+
results(query, reverse).map{ |r|
|
37
|
+
result = result_class.new(r)
|
38
|
+
result.cache_hit = @cache_hit if cache
|
39
|
+
result
|
40
|
+
}
|
37
41
|
end
|
38
42
|
|
39
43
|
##
|
@@ -95,7 +99,7 @@ module Geocoder
|
|
95
99
|
# Return false if exception not raised.
|
96
100
|
#
|
97
101
|
def raise_error(error, message = nil)
|
98
|
-
if Geocoder::Configuration.always_raise.include?(error.class)
|
102
|
+
if Geocoder::Configuration.always_raise.include?( error.is_a?(Class) ? error : error.class )
|
99
103
|
raise error, message
|
100
104
|
else
|
101
105
|
false
|
@@ -106,29 +110,25 @@ module Geocoder
|
|
106
110
|
# Returns a parsed search result (Ruby hash).
|
107
111
|
#
|
108
112
|
def fetch_data(query, reverse = false)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
"(see Geocoder::Configuration.timeout to set limit)."
|
116
|
-
end
|
113
|
+
parse_raw_data fetch_raw_data(query, reverse)
|
114
|
+
rescue SocketError => err
|
115
|
+
raise_error(err) or warn "Geocoding API connection cannot be established."
|
116
|
+
rescue TimeoutError => err
|
117
|
+
raise_error(err) or warn "Geocoding API not responding fast enough " +
|
118
|
+
"(see Geocoder::Configuration.timeout to set limit)."
|
117
119
|
end
|
118
120
|
|
119
121
|
##
|
120
122
|
# Parses a raw search result (returns hash or array).
|
121
123
|
#
|
122
124
|
def parse_raw_data(raw_data)
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
JSON.parse(raw_data)
|
128
|
-
end
|
129
|
-
rescue
|
130
|
-
warn "Geocoding API's response was not valid JSON."
|
125
|
+
if defined?(ActiveSupport::JSON)
|
126
|
+
ActiveSupport::JSON.decode(raw_data)
|
127
|
+
else
|
128
|
+
JSON.parse(raw_data)
|
131
129
|
end
|
130
|
+
rescue
|
131
|
+
warn "Geocoding API's response was not valid JSON."
|
132
132
|
end
|
133
133
|
|
134
134
|
##
|
@@ -146,14 +146,17 @@ module Geocoder
|
|
146
146
|
timeout(Geocoder::Configuration.timeout) do
|
147
147
|
url = query_url(query, reverse)
|
148
148
|
uri = URI.parse(url)
|
149
|
-
|
149
|
+
if cache and body = cache[url]
|
150
|
+
@cache_hit = true
|
151
|
+
else
|
150
152
|
client = http_client.new(uri.host, uri.port)
|
151
153
|
client.use_ssl = true if Geocoder::Configuration.use_https
|
152
|
-
response = client.get(uri.request_uri)
|
154
|
+
response = client.get(uri.request_uri, Geocoder::Configuration.http_headers)
|
153
155
|
body = response.body
|
154
156
|
if cache and (200..399).include?(response.code.to_i)
|
155
157
|
cache[url] = body
|
156
158
|
end
|
159
|
+
@cache_hit = false
|
157
160
|
end
|
158
161
|
body
|
159
162
|
end
|
@@ -6,6 +6,10 @@ module Geocoder::Lookup
|
|
6
6
|
|
7
7
|
private # ---------------------------------------------------------------
|
8
8
|
|
9
|
+
def parse_raw_data(raw_data)
|
10
|
+
raw_data.match(/^<html><title>404/) ? nil : super(raw_data)
|
11
|
+
end
|
12
|
+
|
9
13
|
def results(query, reverse = false)
|
10
14
|
# don't look up a loopback address, just return the stored result
|
11
15
|
return [reserved_result(query)] if loopback_address?(query)
|
@@ -14,7 +14,9 @@ module Geocoder
|
|
14
14
|
:user_address => address_attr,
|
15
15
|
:latitude => options[:latitude] || :latitude,
|
16
16
|
:longitude => options[:longitude] || :longitude,
|
17
|
-
:geocode_block => block
|
17
|
+
:geocode_block => block,
|
18
|
+
:units => options[:units],
|
19
|
+
:method => options[:method]
|
18
20
|
)
|
19
21
|
end
|
20
22
|
|
@@ -27,7 +29,9 @@ module Geocoder
|
|
27
29
|
:fetched_address => options[:address] || :address,
|
28
30
|
:latitude => latitude_attr,
|
29
31
|
:longitude => longitude_attr,
|
30
|
-
:reverse_block => block
|
32
|
+
:reverse_block => block,
|
33
|
+
:units => options[:units],
|
34
|
+
:method => options[:method]
|
31
35
|
)
|
32
36
|
end
|
33
37
|
|
@@ -39,3 +43,4 @@ module Geocoder
|
|
39
43
|
end
|
40
44
|
end
|
41
45
|
end
|
46
|
+
|
data/lib/geocoder/models/base.rb
CHANGED
@@ -12,7 +12,9 @@ module Geocoder
|
|
12
12
|
if defined?(@geocoder_options)
|
13
13
|
@geocoder_options
|
14
14
|
elsif superclass.respond_to?(:geocoder_options)
|
15
|
-
superclass.geocoder_options
|
15
|
+
superclass.geocoder_options || { }
|
16
|
+
else
|
17
|
+
{ }
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
@@ -24,7 +26,6 @@ module Geocoder
|
|
24
26
|
fail
|
25
27
|
end
|
26
28
|
|
27
|
-
|
28
29
|
private # ----------------------------------------------------------------
|
29
30
|
|
30
31
|
def geocoder_init(options)
|
@@ -38,3 +39,4 @@ module Geocoder
|
|
38
39
|
end
|
39
40
|
end
|
40
41
|
end
|
42
|
+
|
@@ -16,7 +16,10 @@ module Geocoder
|
|
16
16
|
:geocode => true,
|
17
17
|
:user_address => address_attr,
|
18
18
|
:coordinates => options[:coordinates] || :coordinates,
|
19
|
-
:geocode_block => block
|
19
|
+
:geocode_block => block,
|
20
|
+
:units => options[:units],
|
21
|
+
:method => options[:method],
|
22
|
+
:skip_index => options[:skip_index] || false
|
20
23
|
)
|
21
24
|
end
|
22
25
|
|
@@ -28,7 +31,10 @@ module Geocoder
|
|
28
31
|
:reverse_geocode => true,
|
29
32
|
:fetched_address => options[:address] || :address,
|
30
33
|
:coordinates => coordinates_attr,
|
31
|
-
:reverse_block => block
|
34
|
+
:reverse_block => block,
|
35
|
+
:units => options[:units],
|
36
|
+
:method => options[:method],
|
37
|
+
:skip_index => options[:skip_index] || false
|
32
38
|
)
|
33
39
|
end
|
34
40
|
|
@@ -36,7 +42,7 @@ module Geocoder
|
|
36
42
|
|
37
43
|
def geocoder_init(options)
|
38
44
|
unless geocoder_initialized?
|
39
|
-
@geocoder_options = {}
|
45
|
+
@geocoder_options = { }
|
40
46
|
require "geocoder/stores/#{geocoder_file_name}"
|
41
47
|
include Geocoder::Store.const_get(geocoder_module_name)
|
42
48
|
end
|
@@ -44,12 +50,11 @@ module Geocoder
|
|
44
50
|
end
|
45
51
|
|
46
52
|
def geocoder_initialized?
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
false
|
51
|
-
end
|
53
|
+
included_modules.include? Geocoder::Store.const_get(geocoder_module_name)
|
54
|
+
rescue NameError
|
55
|
+
false
|
52
56
|
end
|
53
57
|
end
|
54
58
|
end
|
55
59
|
end
|
60
|
+
|
@@ -16,8 +16,10 @@ module Geocoder
|
|
16
16
|
|
17
17
|
def geocoder_init(options)
|
18
18
|
super(options)
|
19
|
-
|
20
|
-
|
19
|
+
if options[:skip_index] == false
|
20
|
+
ensure_index [[ geocoder_options[:coordinates], Mongo::GEO2D ]],
|
21
|
+
:min => -180, :max => 180 # create 2d index
|
22
|
+
end
|
21
23
|
end
|
22
24
|
end
|
23
25
|
end
|
@@ -16,8 +16,10 @@ module Geocoder
|
|
16
16
|
|
17
17
|
def geocoder_init(options)
|
18
18
|
super(options)
|
19
|
-
|
20
|
-
|
19
|
+
if options[:skip_index] == false
|
20
|
+
index [[ geocoder_options[:coordinates], Mongo::GEO2D ]],
|
21
|
+
:min => -180, :max => 180 # create 2d index
|
22
|
+
end
|
21
23
|
end
|
22
24
|
end
|
23
25
|
end
|
@@ -1,13 +1,14 @@
|
|
1
1
|
module Geocoder
|
2
2
|
module Result
|
3
3
|
class Base
|
4
|
-
attr_accessor :data
|
4
|
+
attr_accessor :data, :cache_hit
|
5
5
|
|
6
6
|
##
|
7
7
|
# Takes a hash of result data from a parsed Google result document.
|
8
8
|
#
|
9
9
|
def initialize(data)
|
10
10
|
@data = data
|
11
|
+
@cache_hit = nil
|
11
12
|
end
|
12
13
|
|
13
14
|
##
|
@@ -33,10 +33,10 @@ module Geocoder::Store
|
|
33
33
|
#
|
34
34
|
scope :near, lambda{ |location, *args|
|
35
35
|
latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
|
36
|
-
if latitude and longitude
|
36
|
+
if latitude and longitude and ![latitude, longitude].include?(Geocoder::Calculations::NAN)
|
37
37
|
near_scope_options(latitude, longitude, *args)
|
38
38
|
else
|
39
|
-
where(
|
39
|
+
where(false_condition) # no results if no lat/lon given
|
40
40
|
end
|
41
41
|
}
|
42
42
|
|
@@ -49,7 +49,7 @@ module Geocoder::Store
|
|
49
49
|
#
|
50
50
|
scope :within_bounding_box, lambda{ |bounds|
|
51
51
|
sw_lat, sw_lng, ne_lat, ne_lng = bounds.flatten if bounds
|
52
|
-
return where(
|
52
|
+
return where(false_condition) unless sw_lat && sw_lng && ne_lat && ne_lng
|
53
53
|
spans = "#{geocoder_options[:latitude]} BETWEEN #{sw_lat} AND #{ne_lat} AND "
|
54
54
|
spans << if sw_lng > ne_lng # Handle a box that spans 180
|
55
55
|
"#{geocoder_options[:longitude]} BETWEEN #{sw_lng} AND 180 OR #{geocoder_options[:longitude]} BETWEEN -180 AND #{ne_lng}"
|
@@ -75,22 +75,24 @@ module Geocoder::Store
|
|
75
75
|
|
76
76
|
##
|
77
77
|
# Get options hash suitable for passing to ActiveRecord.find to get
|
78
|
-
# records within a radius (in
|
78
|
+
# records within a radius (in kilometers) of the given point.
|
79
79
|
# Options hash may include:
|
80
80
|
#
|
81
|
-
# * +:units+ - <tt>:mi</tt>
|
81
|
+
# * +:units+ - <tt>:mi</tt> or <tt>:km</tt>; to be used.
|
82
82
|
# for interpreting radius as well as the +distance+ attribute which
|
83
|
-
# is added to each found nearby object
|
84
|
-
#
|
83
|
+
# is added to each found nearby object.
|
84
|
+
# See Geocoder::Configuration to know how configure default units.
|
85
|
+
# * +:bearing+ - <tt>:linear</tt> or <tt>:spherical</tt>.
|
85
86
|
# the method to be used for calculating the bearing (direction)
|
86
87
|
# between the given point and each found nearby point;
|
87
|
-
# set to false for no bearing calculation
|
88
|
+
# set to false for no bearing calculation.
|
89
|
+
# See Geocoder::Configuration to know how configure default method.
|
88
90
|
# * +:select+ - string with the SELECT SQL fragment (e.g. “id, name”)
|
89
91
|
# * +:order+ - column(s) for ORDER BY SQL clause; default is distance
|
90
92
|
# * +:exclude+ - an object to exclude (used by the +nearbys+ method)
|
91
93
|
#
|
92
94
|
def near_scope_options(latitude, longitude, radius = 20, options = {})
|
93
|
-
if
|
95
|
+
if using_sqlite?
|
94
96
|
approx_near_scope_options(latitude, longitude, radius, options)
|
95
97
|
else
|
96
98
|
full_near_scope_options(latitude, longitude, radius, options)
|
@@ -98,7 +100,7 @@ module Geocoder::Store
|
|
98
100
|
end
|
99
101
|
|
100
102
|
def distance_from_sql_options(latitude, longitude, options = {})
|
101
|
-
if
|
103
|
+
if using_sqlite?
|
102
104
|
approx_distance_from_sql(latitude, longitude, options)
|
103
105
|
else
|
104
106
|
full_distance_from_sql(latitude, longitude, options)
|
@@ -116,33 +118,35 @@ module Geocoder::Store
|
|
116
118
|
def full_near_scope_options(latitude, longitude, radius, options)
|
117
119
|
lat_attr = geocoder_options[:latitude]
|
118
120
|
lon_attr = geocoder_options[:longitude]
|
119
|
-
options[:bearing]
|
121
|
+
options[:bearing] ||= (options[:method] ||
|
122
|
+
geocoder_options[:method] ||
|
123
|
+
Geocoder::Configuration.distances)
|
120
124
|
bearing = case options[:bearing]
|
121
125
|
when :linear
|
122
126
|
"CAST(" +
|
123
127
|
"DEGREES(ATAN2( " +
|
124
|
-
"RADIANS(#{lon_attr} - #{longitude}), " +
|
125
|
-
"RADIANS(#{lat_attr} - #{latitude})" +
|
128
|
+
"RADIANS(#{full_column_name(lon_attr)} - #{longitude}), " +
|
129
|
+
"RADIANS(#{full_column_name(lat_attr)} - #{latitude})" +
|
126
130
|
")) + 360 " +
|
127
131
|
"AS decimal) % 360"
|
128
132
|
when :spherical
|
129
133
|
"CAST(" +
|
130
134
|
"DEGREES(ATAN2( " +
|
131
|
-
"SIN(RADIANS(#{lon_attr} - #{longitude})) * " +
|
132
|
-
"COS(RADIANS(#{lat_attr})), (" +
|
133
|
-
"COS(RADIANS(#{latitude})) * SIN(RADIANS(#{lat_attr}))" +
|
135
|
+
"SIN(RADIANS(#{full_column_name(lon_attr)} - #{longitude})) * " +
|
136
|
+
"COS(RADIANS(#{full_column_name(lat_attr)})), (" +
|
137
|
+
"COS(RADIANS(#{latitude})) * SIN(RADIANS(#{full_column_name(lat_attr)}))" +
|
134
138
|
") - (" +
|
135
|
-
"SIN(RADIANS(#{latitude})) * COS(RADIANS(#{lat_attr})) * " +
|
136
|
-
"COS(RADIANS(#{lon_attr} - #{longitude}))" +
|
139
|
+
"SIN(RADIANS(#{latitude})) * COS(RADIANS(#{full_column_name(lat_attr)})) * " +
|
140
|
+
"COS(RADIANS(#{full_column_name(lon_attr)} - #{longitude}))" +
|
137
141
|
")" +
|
138
142
|
")) + 360 " +
|
139
143
|
"AS decimal) % 360"
|
140
144
|
end
|
141
|
-
|
145
|
+
options[:units] ||= (geocoder_options[:units] || Geocoder::Configuration.units)
|
142
146
|
distance = full_distance_from_sql(latitude, longitude, options)
|
143
147
|
conditions = ["#{distance} <= ?", radius]
|
144
148
|
default_near_scope_options(latitude, longitude, radius, options).merge(
|
145
|
-
:select => "#{options[:select] || "
|
149
|
+
:select => "#{options[:select] || full_column_name("*")}, " +
|
146
150
|
"#{distance} AS distance" +
|
147
151
|
(bearing ? ", #{bearing} AS bearing" : ""),
|
148
152
|
:conditions => add_exclude_condition(conditions, options[:exclude])
|
@@ -160,9 +164,9 @@ module Geocoder::Store
|
|
160
164
|
earth = Geocoder::Calculations.earth_radius(options[:units] || :mi)
|
161
165
|
|
162
166
|
"#{earth} * 2 * ASIN(SQRT(" +
|
163
|
-
"POWER(SIN((#{latitude} - #{
|
164
|
-
"COS(#{latitude} * PI() / 180) * COS(#{
|
165
|
-
"POWER(SIN((#{longitude} - #{
|
167
|
+
"POWER(SIN((#{latitude} - #{full_column_name(lat_attr)}) * PI() / 180 / 2), 2) + " +
|
168
|
+
"COS(#{latitude} * PI() / 180) * COS(#{full_column_name(lat_attr)} * PI() / 180) * " +
|
169
|
+
"POWER(SIN((#{longitude} - #{full_column_name(lon_attr)}) * PI() / 180 / 2), 2) ))"
|
166
170
|
end
|
167
171
|
|
168
172
|
def approx_distance_from_sql(latitude, longitude, options)
|
@@ -175,8 +179,8 @@ module Geocoder::Store
|
|
175
179
|
# sin of 45 degrees = average x or y component of vector
|
176
180
|
factor = Math.sin(Math::PI / 4)
|
177
181
|
|
178
|
-
"(#{dy} * ABS(#{
|
179
|
-
"(#{dx} * ABS(#{
|
182
|
+
"(#{dy} * ABS(#{full_column_name(lat_attr)} - #{latitude}) * #{factor}) + " +
|
183
|
+
"(#{dx} * ABS(#{full_column_name(lon_attr)} - #{longitude}) * #{factor})"
|
180
184
|
end
|
181
185
|
|
182
186
|
##
|
@@ -191,7 +195,11 @@ module Geocoder::Store
|
|
191
195
|
def approx_near_scope_options(latitude, longitude, radius, options)
|
192
196
|
lat_attr = geocoder_options[:latitude]
|
193
197
|
lon_attr = geocoder_options[:longitude]
|
194
|
-
|
198
|
+
unless options.include?(:bearing)
|
199
|
+
options[:bearing] = (options[:method] || \
|
200
|
+
geocoder_options[:method] || \
|
201
|
+
Geocoder::Configuration.distances)
|
202
|
+
end
|
195
203
|
if options[:bearing]
|
196
204
|
bearing = "CASE " +
|
197
205
|
"WHEN (#{lat_attr} >= #{latitude} AND #{lon_attr} >= #{longitude}) THEN 45.0 " +
|
@@ -204,6 +212,7 @@ module Geocoder::Store
|
|
204
212
|
end
|
205
213
|
|
206
214
|
distance = approx_distance_from_sql(latitude, longitude, options)
|
215
|
+
options[:units] ||= (geocoder_options[:units] || Geocoder::Configuration.units)
|
207
216
|
|
208
217
|
b = Geocoder::Calculations.bounding_box([latitude, longitude], radius, options)
|
209
218
|
conditions = [
|
@@ -211,7 +220,7 @@ module Geocoder::Store
|
|
211
220
|
[b[0], b[2], b[1], b[3]
|
212
221
|
]
|
213
222
|
default_near_scope_options(latitude, longitude, radius, options).merge(
|
214
|
-
:select => "#{options[:select] || "
|
223
|
+
:select => "#{options[:select] || full_column_name("*")}, " +
|
215
224
|
"#{distance} AS distance" +
|
216
225
|
(bearing ? ", #{bearing} AS bearing" : ""),
|
217
226
|
:conditions => add_exclude_condition(conditions, options[:exclude])
|
@@ -235,11 +244,30 @@ module Geocoder::Store
|
|
235
244
|
#
|
236
245
|
def add_exclude_condition(conditions, exclude)
|
237
246
|
if exclude
|
238
|
-
conditions[0] << " AND #{
|
247
|
+
conditions[0] << " AND #{full_column_name(:id)} != ?"
|
239
248
|
conditions << exclude.id
|
240
249
|
end
|
241
250
|
conditions
|
242
251
|
end
|
252
|
+
|
253
|
+
def using_sqlite?
|
254
|
+
connection.adapter_name.match /sqlite/i
|
255
|
+
end
|
256
|
+
|
257
|
+
##
|
258
|
+
# Value which can be passed to where() to produce no results.
|
259
|
+
#
|
260
|
+
def false_condition
|
261
|
+
using_sqlite? ? 0 : "false"
|
262
|
+
end
|
263
|
+
|
264
|
+
##
|
265
|
+
# Prepend table name if column name doesn't already contain one.
|
266
|
+
#
|
267
|
+
def full_column_name(column)
|
268
|
+
column = column.to_s
|
269
|
+
column.include?(".") ? column : [table_name, column].join(".")
|
270
|
+
end
|
243
271
|
end
|
244
272
|
|
245
273
|
##
|
@@ -278,3 +306,4 @@ module Geocoder::Store
|
|
278
306
|
alias_method :fetch_address, :reverse_geocode
|
279
307
|
end
|
280
308
|
end
|
309
|
+
|