geo-calculator 0.0.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.
@@ -0,0 +1,124 @@
1
+ require 'singleton'
2
+ require 'geo-calculator/configuration_hash'
3
+
4
+ module Geocoder
5
+
6
+ ##
7
+ # Configuration options should be set by passing a hash:
8
+ #
9
+ # Geocoder.configure(
10
+ # :timeout => 5,
11
+ # :lookup => :yandex,
12
+ # :api_key => "2a9fsa983jaslfj982fjasd",
13
+ # :units => :km
14
+ # )
15
+ #
16
+ def self.configure(options = nil, &block)
17
+ if !options.nil?
18
+ Configuration.instance.configure(options)
19
+ end
20
+ end
21
+
22
+ ##
23
+ # Read-only access to the singleton's config data.
24
+ #
25
+ def self.config
26
+ Configuration.instance.data
27
+ end
28
+
29
+ ##
30
+ # Read-only access to lookup-specific config data.
31
+ #
32
+ def self.config_for_lookup(lookup_name)
33
+ data = config.clone
34
+ data.reject!{ |key,value| !Configuration::OPTIONS.include?(key) }
35
+ if config.has_key?(lookup_name)
36
+ data.merge!(config[lookup_name])
37
+ end
38
+ data
39
+ end
40
+
41
+ class Configuration
42
+ include Singleton
43
+
44
+ OPTIONS = [
45
+ :timeout,
46
+ :lookup,
47
+ :ip_lookup,
48
+ :language,
49
+ :http_headers,
50
+ :use_https,
51
+ :http_proxy,
52
+ :https_proxy,
53
+ :api_key,
54
+ :cache,
55
+ :cache_prefix,
56
+ :always_raise,
57
+ :units,
58
+ :distances
59
+ ]
60
+
61
+ attr_accessor :data
62
+
63
+ def self.set_defaults
64
+ instance.set_defaults
65
+ end
66
+
67
+ OPTIONS.each do |o|
68
+ define_method o do
69
+ @data[o]
70
+ end
71
+ define_method "#{o}=" do |value|
72
+ @data[o] = value
73
+ end
74
+ end
75
+
76
+ def configure(options)
77
+ @data.rmerge!(options)
78
+ end
79
+
80
+ def initialize # :nodoc
81
+ @data = Geocoder::ConfigurationHash.new
82
+ set_defaults
83
+ end
84
+
85
+ def set_defaults
86
+
87
+ # geocoding options
88
+ @data[:timeout] = 3 # geocoding service timeout (secs)
89
+ @data[:lookup] = :google # name of street address geocoding service (symbol)
90
+ @data[:ip_lookup] = :freegeoip # name of IP address geocoding service (symbol)
91
+ @data[:language] = :en # ISO-639 language code
92
+ @data[:http_headers] = {} # HTTP headers for lookup
93
+ @data[:use_https] = false # use HTTPS for lookup requests? (if supported)
94
+ @data[:http_proxy] = nil # HTTP proxy server (user:pass@host:port)
95
+ @data[:https_proxy] = nil # HTTPS proxy server (user:pass@host:port)
96
+ @data[:api_key] = nil # API key for geocoding service
97
+ @data[:cache] = nil # cache object (must respond to #[], #[]=, and #keys)
98
+ @data[:cache_prefix] = "geocoder:" # prefix (string) to use for all cache keys
99
+
100
+ # exceptions that should not be rescued by default
101
+ # (if you want to implement custom error handling);
102
+ # supports SocketError and TimeoutError
103
+ @data[:always_raise] = []
104
+
105
+ # calculation options
106
+ @data[:units] = :mi # :mi or :km
107
+ @data[:distances] = :linear # :linear or :spherical
108
+ end
109
+
110
+ instance_eval(OPTIONS.map do |option|
111
+ o = option.to_s
112
+ <<-EOS
113
+ def #{o}
114
+ instance.data[:#{o}]
115
+ end
116
+
117
+ def #{o}=(value)
118
+ instance.data[:#{o}] = value
119
+ end
120
+ EOS
121
+ end.join("\n\n"))
122
+
123
+ end
124
+ end
@@ -0,0 +1,11 @@
1
+ require 'geo-calculator/hash_recursive_merge'
2
+
3
+ module Geocoder
4
+ class ConfigurationHash < Hash
5
+ include HashRecursiveMerge
6
+
7
+ def method_missing(meth, *args, &block)
8
+ has_key?(meth) ? self[meth] : super
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,74 @@
1
+ #
2
+ # = Hash Recursive Merge
3
+ #
4
+ # Merges a Ruby Hash recursively, Also known as deep merge.
5
+ # Recursive version of Hash#merge and Hash#merge!.
6
+ #
7
+ # Category:: Ruby
8
+ # Package:: Hash
9
+ # Author:: Simone Carletti <weppos@weppos.net>
10
+ # Copyright:: 2007-2008 The Authors
11
+ # License:: MIT License
12
+ # Link:: http://www.simonecarletti.com/
13
+ # Source:: http://gist.github.com/gists/6391/
14
+ #
15
+ module HashRecursiveMerge
16
+
17
+ #
18
+ # Recursive version of Hash#merge!
19
+ #
20
+ # Adds the contents of +other_hash+ to +hsh+,
21
+ # merging entries in +hsh+ with duplicate keys with those from +other_hash+.
22
+ #
23
+ # Compared with Hash#merge!, this method supports nested hashes.
24
+ # When both +hsh+ and +other_hash+ contains an entry with the same key,
25
+ # it merges and returns the values from both arrays.
26
+ #
27
+ # h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
28
+ # h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
29
+ # h1.rmerge!(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
30
+ #
31
+ # Simply using Hash#merge! would return
32
+ #
33
+ # h1.merge!(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
34
+ #
35
+ def rmerge!(other_hash)
36
+ merge!(other_hash) do |key, oldval, newval|
37
+ oldval.class == self.class ? oldval.rmerge!(newval) : newval
38
+ end
39
+ end
40
+
41
+ #
42
+ # Recursive version of Hash#merge
43
+ #
44
+ # Compared with Hash#merge!, this method supports nested hashes.
45
+ # When both +hsh+ and +other_hash+ contains an entry with the same key,
46
+ # it merges and returns the values from both arrays.
47
+ #
48
+ # Compared with Hash#merge, this method provides a different approch
49
+ # for merging nasted hashes.
50
+ # If the value of a given key is an Hash and both +other_hash+ abd +hsh
51
+ # includes the same key, the value is merged instead replaced with
52
+ # +other_hash+ value.
53
+ #
54
+ # h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
55
+ # h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
56
+ # h1.rmerge(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
57
+ #
58
+ # Simply using Hash#merge would return
59
+ #
60
+ # h1.merge(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
61
+ #
62
+ def rmerge(other_hash)
63
+ r = {}
64
+ merge(other_hash) do |key, oldval, newval|
65
+ r[key] = oldval.class == self.class ? oldval.rmerge(newval) : newval
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+
72
+ class Hash
73
+ include HashRecursiveMerge
74
+ end
@@ -0,0 +1,107 @@
1
+ module Geocoder
2
+ module Sql
3
+ extend self
4
+
5
+ ##
6
+ # Distance calculation for use with a database that supports POWER(),
7
+ # SQRT(), PI(), and trigonometric functions SIN(), COS(), ASIN(),
8
+ # ATAN2(), DEGREES(), and RADIANS().
9
+ #
10
+ # Based on the excellent tutorial at:
11
+ # http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
12
+ #
13
+ def full_distance(latitude, longitude, lat_attr, lon_attr, options = {})
14
+ units = options[:units] || Geocoder.config.units
15
+ earth = Geocoder::Calculations.earth_radius(units)
16
+
17
+ "#{earth} * 2 * ASIN(SQRT(" +
18
+ "POWER(SIN((#{latitude.to_f} - #{lat_attr}) * PI() / 180 / 2), 2) + " +
19
+ "COS(#{latitude.to_f} * PI() / 180) * COS(#{lat_attr} * PI() / 180) * " +
20
+ "POWER(SIN((#{longitude.to_f} - #{lon_attr}) * PI() / 180 / 2), 2)" +
21
+ "))"
22
+ end
23
+
24
+ ##
25
+ # Distance calculation for use with a database without trigonometric
26
+ # functions, like SQLite. Approach is to find objects within a square
27
+ # rather than a circle, so results are very approximate (will include
28
+ # objects outside the given radius).
29
+ #
30
+ # Distance and bearing calculations are *extremely inaccurate*. To be
31
+ # clear: this only exists to provide interface consistency. Results
32
+ # are not intended for use in production!
33
+ #
34
+ def approx_distance(latitude, longitude, lat_attr, lon_attr, options = {})
35
+ units = options[:units] || Geocoder.config.units
36
+ dx = Geocoder::Calculations.longitude_degree_distance(30, units)
37
+ dy = Geocoder::Calculations.latitude_degree_distance(units)
38
+
39
+ # sin of 45 degrees = average x or y component of vector
40
+ factor = Math.sin(Math::PI / 4)
41
+
42
+ "(#{dy} * ABS(#{lat_attr} - #{latitude.to_f}) * #{factor}) + " +
43
+ "(#{dx} * ABS(#{lon_attr} - #{longitude.to_f}) * #{factor})"
44
+ end
45
+
46
+ def within_bounding_box(sw_lat, sw_lng, ne_lat, ne_lng, lat_attr, lon_attr)
47
+ spans = "#{lat_attr} BETWEEN #{sw_lat} AND #{ne_lat} AND "
48
+ # handle box that spans 180 longitude
49
+ if sw_lng.to_f > ne_lng.to_f
50
+ spans + "#{lon_attr} BETWEEN #{sw_lng} AND 180 OR " +
51
+ "#{lon_attr} BETWEEN -180 AND #{ne_lng}"
52
+ else
53
+ spans + "#{lon_attr} BETWEEN #{sw_lng} AND #{ne_lng}"
54
+ end
55
+ end
56
+
57
+ ##
58
+ # Fairly accurate bearing calculation. Takes a latitude, longitude,
59
+ # and an options hash which must include a :bearing value
60
+ # (:linear or :spherical).
61
+ #
62
+ # Based on:
63
+ # http://www.beginningspatial.com/calculating_bearing_one_point_another
64
+ #
65
+ def full_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
66
+ degrees_per_radian = Geocoder::Calculations::DEGREES_PER_RADIAN
67
+ case options[:bearing] || Geocoder.config.distances
68
+ when :linear
69
+ "MOD(CAST(" +
70
+ "(ATAN2( " +
71
+ "((#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian}), " +
72
+ "((#{lat_attr} - #{latitude.to_f}) / #{degrees_per_radian})" +
73
+ ") * #{degrees_per_radian}) + 360 " +
74
+ "AS decimal), 360)"
75
+ when :spherical
76
+ "MOD(CAST(" +
77
+ "(ATAN2( " +
78
+ "SIN( (#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian} ) * " +
79
+ "COS( (#{lat_attr}) / #{degrees_per_radian} ), (" +
80
+ "COS( (#{latitude.to_f}) / #{degrees_per_radian} ) * SIN( (#{lat_attr}) / #{degrees_per_radian})" +
81
+ ") - (" +
82
+ "SIN( (#{latitude.to_f}) / #{degrees_per_radian}) * COS((#{lat_attr}) / #{degrees_per_radian}) * " +
83
+ "COS( (#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian})" +
84
+ ")" +
85
+ ") * #{degrees_per_radian}) + 360 " +
86
+ "AS decimal), 360)"
87
+ end
88
+ end
89
+
90
+ ##
91
+ # Totally lame bearing calculation. Basically useless except that it
92
+ # returns *something* in databases without trig functions.
93
+ #
94
+ def approx_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
95
+ "CASE " +
96
+ "WHEN (#{lat_attr} >= #{latitude.to_f} AND " +
97
+ "#{lon_attr} >= #{longitude.to_f}) THEN 45.0 " +
98
+ "WHEN (#{lat_attr} < #{latitude.to_f} AND " +
99
+ "#{lon_attr} >= #{longitude.to_f}) THEN 135.0 " +
100
+ "WHEN (#{lat_attr} < #{latitude.to_f} AND " +
101
+ "#{lon_attr} < #{longitude.to_f}) THEN 225.0 " +
102
+ "WHEN (#{lat_attr} >= #{latitude.to_f} AND " +
103
+ "#{lon_attr} < #{longitude.to_f}) THEN 315.0 " +
104
+ "END"
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,290 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'geo-calculator/sql'
3
+ require 'geo-calculator/store/base'
4
+ require 'byebug'
5
+
6
+ ##
7
+ # Add geocoding functionality to any ActiveRecord object.
8
+ #
9
+ module Geocoder::Store
10
+ module ActiveRecord
11
+ include Base
12
+
13
+ ##
14
+ # Implementation of 'included' hook method.
15
+ #
16
+ def self.included(base)
17
+ base.extend ClassMethods
18
+ base.class_eval do
19
+
20
+ # scope: geocoded objects
21
+ scope :geocoded, lambda {
22
+ where("#{table_name}.#{geocoder_options[:latitude]} IS NOT NULL " +
23
+ "AND #{table_name}.#{geocoder_options[:longitude]} IS NOT NULL")
24
+ }
25
+
26
+ # scope: not-geocoded objects
27
+ scope :not_geocoded, lambda {
28
+ where("#{table_name}.#{geocoder_options[:latitude]} IS NULL " +
29
+ "OR #{table_name}.#{geocoder_options[:longitude]} IS NULL")
30
+ }
31
+
32
+ ##
33
+ # Find all objects within a radius of the given location.
34
+ # Location may be either a string to geocode or an array of
35
+ # coordinates (<tt>[lat,lon]</tt>). Also takes an options hash
36
+ # (see Geocoder::Store::ActiveRecord::ClassMethods.near_scope_options
37
+ # for details).
38
+ #
39
+ scope :near, lambda{ |location, *args|
40
+ latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
41
+ if Geocoder::Calculations.coordinates_present?(latitude, longitude)
42
+ options = near_scope_options(latitude, longitude, *args)
43
+ select(options[:select]).where(options[:conditions]).
44
+ order(options[:order])
45
+ else
46
+ # If no lat/lon given we don't want any results, but we still
47
+ # need distance and bearing columns so you can add, for example:
48
+ # .order("distance")
49
+ select(select_clause(nil, null_value, null_value)).where(false_condition)
50
+ end
51
+ }
52
+
53
+ ##
54
+ # Find all objects within the area of a given bounding box.
55
+ # Bounds must be an array of locations specifying the southwest
56
+ # corner followed by the northeast corner of the box
57
+ # (<tt>[[sw_lat, sw_lon], [ne_lat, ne_lon]]</tt>).
58
+ #
59
+ scope :within_bounding_box, lambda{ |bounds|
60
+ sw_lat, sw_lng, ne_lat, ne_lng = bounds.flatten if bounds
61
+ if sw_lat && sw_lng && ne_lat && ne_lng
62
+ where(Geocoder::Sql.within_bounding_box(
63
+ sw_lat, sw_lng, ne_lat, ne_lng,
64
+ full_column_name(geocoder_options[:latitude]),
65
+ full_column_name(geocoder_options[:longitude])
66
+ ))
67
+ else
68
+ select(select_clause(nil, null_value, null_value)).where(false_condition)
69
+ end
70
+ }
71
+ end
72
+ end
73
+
74
+ ##
75
+ # Methods which will be class methods of the including class.
76
+ #
77
+ module ClassMethods
78
+
79
+ def distance_from_sql(location, *args)
80
+ latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
81
+ if Geocoder::Calculations.coordinates_present?(latitude, longitude)
82
+ distance_sql(latitude, longitude, *args)
83
+ end
84
+ end
85
+
86
+ private # ----------------------------------------------------------------
87
+
88
+ ##
89
+ # Get options hash suitable for passing to ActiveRecord.find to get
90
+ # records within a radius (in kilometers) of the given point.
91
+ # Options hash may include:
92
+ #
93
+ # * +:units+ - <tt>:mi</tt> or <tt>:km</tt>; to be used.
94
+ # for interpreting radius as well as the +distance+ attribute which
95
+ # is added to each found nearby object.
96
+ # Use Geocoder.configure[:units] to configure default units.
97
+ # * +:bearing+ - <tt>:linear</tt> or <tt>:spherical</tt>.
98
+ # the method to be used for calculating the bearing (direction)
99
+ # between the given point and each found nearby point;
100
+ # set to false for no bearing calculation. Use
101
+ # Geocoder.configure[:distances] to configure default calculation method.
102
+ # * +:select+ - string with the SELECT SQL fragment (e.g. “id, name”)
103
+ # * +:select_distance+ - whether to include the distance alias in the
104
+ # SELECT SQL fragment (e.g. <formula> AS distance)
105
+ # * +:select_bearing+ - like +:select_distance+ but for bearing.
106
+ # * +:order+ - column(s) for ORDER BY SQL clause; default is distance;
107
+ # set to false or nil to omit the ORDER BY clause
108
+ # * +:exclude+ - an object to exclude (used by the +nearbys+ method)
109
+ # * +:distance_column+ - used to set the column name of the calculated distance.
110
+ # * +:bearing_column+ - used to set the column name of the calculated bearing.
111
+ # * +:min_radius+ - the value to use as the minimum radius.
112
+ # ignored if database is sqlite.
113
+ # default is 0.0
114
+ #
115
+ def near_scope_options(latitude, longitude, radius = 20, options = {})
116
+ if options[:units]
117
+ options[:units] = options[:units].to_sym
118
+ end
119
+ latitude_attribute = options[:latitude] || geocoder_options[:latitude]
120
+ longitude_attribute = options[:longitude] || geocoder_options[:longitude]
121
+ options[:units] ||= (geocoder_options[:units] || Geocoder.config.units)
122
+ select_distance = options.fetch(:select_distance) { true }
123
+ options[:order] = "" if !select_distance && !options.include?(:order)
124
+ select_bearing = options.fetch(:select_bearing) { true }
125
+ bearing = bearing_sql(latitude, longitude, options)
126
+ distance = distance_sql(latitude, longitude, options)
127
+ distance_column = options.fetch(:distance_column) { 'distance' }
128
+ bearing_column = options.fetch(:bearing_column) { 'bearing' }
129
+
130
+ b = Geocoder::Calculations.bounding_box([latitude, longitude], radius, options)
131
+ args = b + [
132
+ full_column_name(latitude_attribute),
133
+ full_column_name(longitude_attribute)
134
+ ]
135
+ bounding_box_conditions = Geocoder::Sql.within_bounding_box(*args)
136
+
137
+ if using_sqlite?
138
+ conditions = bounding_box_conditions
139
+ else
140
+ min_radius = options.fetch(:min_radius, 0).to_f
141
+ conditions = [bounding_box_conditions + " AND (#{distance}) BETWEEN ? AND ?", min_radius, radius]
142
+ end
143
+ {
144
+ :select => select_clause(options[:select],
145
+ select_distance ? distance : nil,
146
+ select_bearing ? bearing : nil,
147
+ distance_column,
148
+ bearing_column),
149
+ :conditions => add_exclude_condition(conditions, options[:exclude]),
150
+ :order => options.include?(:order) ? options[:order] : "#{distance_column} ASC"
151
+ }
152
+ end
153
+
154
+ ##
155
+ # SQL for calculating distance based on the current database's
156
+ # capabilities (trig functions?).
157
+ #
158
+ def distance_sql(latitude, longitude, options = {})
159
+ method_prefix = using_sqlite? ? "approx" : "full"
160
+ Geocoder::Sql.send(
161
+ method_prefix + "_distance",
162
+ latitude, longitude,
163
+ full_column_name(options[:latitude] || geocoder_options[:latitude]),
164
+ full_column_name(options[:longitude]|| geocoder_options[:longitude]),
165
+ options
166
+ )
167
+ end
168
+
169
+ ##
170
+ # SQL for calculating bearing based on the current database's
171
+ # capabilities (trig functions?).
172
+ #
173
+ def bearing_sql(latitude, longitude, options = {})
174
+ if !options.include?(:bearing)
175
+ options[:bearing] = Geocoder.config.distances
176
+ end
177
+ if options[:bearing]
178
+ method_prefix = using_sqlite? ? "approx" : "full"
179
+ Geocoder::Sql.send(
180
+ method_prefix + "_bearing",
181
+ latitude, longitude,
182
+ full_column_name(options[:latitude] || geocoder_options[:latitude]),
183
+ full_column_name(options[:longitude]|| geocoder_options[:longitude]),
184
+ options
185
+ )
186
+ end
187
+ end
188
+
189
+ ##
190
+ # Generate the SELECT clause.
191
+ #
192
+ def select_clause(columns, distance = nil, bearing = nil, distance_column = 'distance', bearing_column = 'bearing')
193
+ if columns == :id_only
194
+ return full_column_name(primary_key)
195
+ elsif columns == :geo_only
196
+ clause = ""
197
+ else
198
+ clause = (columns || full_column_name("*"))
199
+ end
200
+ if distance
201
+ clause += ", " unless clause.empty?
202
+ clause += "#{distance} AS #{distance_column}"
203
+ end
204
+ if bearing
205
+ clause += ", " unless clause.empty?
206
+ clause += "#{bearing} AS #{bearing_column}"
207
+ end
208
+ clause
209
+ end
210
+
211
+ ##
212
+ # Adds a condition to exclude a given object by ID.
213
+ # Expects conditions as an array or string. Returns array.
214
+ #
215
+ def add_exclude_condition(conditions, exclude)
216
+ conditions = [conditions] if conditions.is_a?(String)
217
+ if exclude
218
+ conditions[0] << " AND #{full_column_name(primary_key)} != ?"
219
+ conditions << exclude.id
220
+ end
221
+ conditions
222
+ end
223
+
224
+ def using_sqlite?
225
+ connection.adapter_name.match(/sqlite/i)
226
+ end
227
+
228
+ def using_postgres?
229
+ connection.adapter_name.match(/postgres/i)
230
+ end
231
+
232
+ ##
233
+ # Use OID type when running in PosgreSQL
234
+ #
235
+ def null_value
236
+ using_postgres? ? 'NULL::text' : 'NULL'
237
+ end
238
+
239
+ ##
240
+ # Value which can be passed to where() to produce no results.
241
+ #
242
+ def false_condition
243
+ using_sqlite? ? 0 : "false"
244
+ end
245
+
246
+ ##
247
+ # Prepend table name if column name doesn't already contain one.
248
+ #
249
+ def full_column_name(column)
250
+ column = column.to_s
251
+ column.include?(".") ? column : [table_name, column].join(".")
252
+ end
253
+ end
254
+
255
+ ##
256
+ # Look up coordinates and assign to +latitude+ and +longitude+ attributes
257
+ # (or other as specified in +geocoded_by+). Returns coordinates (array).
258
+ #
259
+ def geocode
260
+ do_lookup(false) do |o,rs|
261
+ if r = rs.first
262
+ unless r.latitude.nil? or r.longitude.nil?
263
+ o.__send__ "#{self.class.geocoder_options[:latitude]}=", r.latitude
264
+ o.__send__ "#{self.class.geocoder_options[:longitude]}=", r.longitude
265
+ end
266
+ r.coordinates
267
+ end
268
+ end
269
+ end
270
+
271
+ alias_method :fetch_coordinates, :geocode
272
+
273
+ ##
274
+ # Look up address and assign to +address+ attribute (or other as specified
275
+ # in +reverse_geocoded_by+). Returns address (string).
276
+ #
277
+ def reverse_geocode
278
+ do_lookup(true) do |o,rs|
279
+ if r = rs.first
280
+ unless r.address.nil?
281
+ o.__send__ "#{self.class.geocoder_options[:fetched_address]}=", r.address
282
+ end
283
+ r.address
284
+ end
285
+ end
286
+ end
287
+
288
+ alias_method :fetch_address, :reverse_geocode
289
+ end
290
+ end