geocoder 0.9.11 → 0.9.12

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/CHANGELOG.rdoc CHANGED
@@ -2,7 +2,18 @@
2
2
 
3
3
  Per-release changes to Geocoder.
4
4
 
5
- == 0.9.11 (2011 Mar ??)
5
+ == 0.9.12 (2011 Apr 6)
6
+
7
+ * Add support for Mongoid.
8
+ * Add bearing_to/from methods to geocoded objects.
9
+ * Improve SQLite's distance calculation heuristic.
10
+ * Fix: Geocoder::Calculations.geographic_center was modifying its argument in-place (reported by github.com/joelmats).
11
+ * Fix: sort 'near' query results by distance when using SQLite.
12
+ * Clean up input: search for coordinates as a string with space after comma yields zero results from Google. Now we get rid of any such space before sending the query.
13
+ * DEPRECATION: Geocoder.near should not take <tt>:limit</tt> or <tt>:offset</tt> options.
14
+ * DEPRECATION: Change argument format of all methods that take lat/lon as separate arguments. Now you must pass the coordinates as an array [lat,lon], but you may alternatively pass a address string (will look up coordinates) or a geocoded object (or any object that implements a to_coordinates method which returns a [lat,lon] array).
15
+
16
+ == 0.9.11 (2011 Mar 25)
6
17
 
7
18
  * Add support for result caching.
8
19
  * Add support for Geocoder.ca geocoding service.
data/README.rdoc CHANGED
@@ -1,15 +1,14 @@
1
1
  = Geocoder
2
2
 
3
- Geocoder is a complete geocoding solution for Ruby. With Rails it adds geocoding (by street or IP address), reverse geocoding (find street address based on given coordinates), and distance calculations for ActiveRecord objects. It's as simple as calling +geocode+ on your objects, and then using a scope like <tt>Venue.near("Billings, MT")</tt>. Since it does not rely on proprietary database functions finding geocoded objects in a given area works with out-of-the-box PostgreSQL, MySQL, and even SQLite.
3
+ Geocoder is a complete geocoding solution for Ruby. With Rails it adds geocoding (by street or IP address), reverse geocoding (find street address based on given coordinates), and distance queries. It's as simple as calling +geocode+ on your objects, and then using a scope like <tt>Venue.near("Billings, MT")</tt>.
4
4
 
5
5
 
6
6
  == Compatibility
7
7
 
8
- Geocoder has been successfully tested with Ruby (MRI) 1.8.7, 1.9.2, and JRuby 1.5.3.
9
-
10
- Geocoder is compatible with Rails 3. If you need to use it with Rails 2 please see the <tt>rails2</tt> branch (no longer maintained, limited feature set).
11
-
12
- Geocoder also works outside of Rails but you'll need to install either the +json+ (for MRI) or +json_pure+ (for JRuby) gem.
8
+ * Supports multiple Ruby versions: Ruby 1.8.7, 1.9.2, and JRuby.
9
+ * Supports multiple databases: MySQL, PostgreSQL, SQLite, and MongoDB (1.7.0 and higher).
10
+ * Supports Rails 3. If you need to use it with Rails 2 please see the <tt>rails2</tt> branch (no longer maintained, limited feature set).
11
+ * Works very well outside of Rails but you'll need to install either the +json+ (for MRI) or +json_pure+ (for JRuby) gem.
13
12
 
14
13
 
15
14
  == Install
@@ -33,40 +32,69 @@ At the command prompt:
33
32
 
34
33
  == Configure Object Geocoding
35
34
 
36
- === Required Attributes
35
+ In the below, note that addresses may be street or IP addresses.
37
36
 
38
- *ActiveRecord:* Your object must have two attributes (database columns) for storing latitude and longitude coordinates. By default they should be called +latitude+ and +longitude+ but this can be changed (see "More on Configuration" below):
37
+ === ActiveRecord
38
+
39
+ Your model must have two attributes (database columns) for storing latitude and longitude coordinates. By default they should be called +latitude+ and +longitude+ but this can be changed (see "More on Configuration" below):
39
40
 
40
41
  rails generate migration AddLatitudeAndLongitudeToModel latitude:float longitude:float
41
42
  rake db:migrate
42
43
 
43
44
  For reverse geocoding your model must provide a method that returns an address. This can be a single attribute, but it can also be a method that returns a string assembled from different attributes (eg: +city+, +state+, and +country+).
44
45
 
45
- *Mongoid:* Define your address and coordinate fields right in the model. You also need to include the <tt>Geocoder::Model::Mongoid</tt> module _before_ calling <tt>geocoded_by</tt>:
46
+ Next, your model must tell Geocoder which method returns your object's geocodable address:
46
47
 
47
- field :address
48
- field :latitude, :type => Float
49
- field :longitude, :type => Float
48
+ geocoded_by :full_street_address # can also be an IP address
49
+ after_validation :geocode # auto-fetch coordinates
50
50
 
51
- include Geocoder::Model::Mongoid
52
- geocoded_by :address
51
+ For reverse geocoding, tell Geocoder which attributes store latitude and longitude:
53
52
 
54
- === Model Behavior
53
+ reverse_geocoded_by :lat, :lon
54
+ after_validation :reverse_geocode # auto-fetch address
55
55
 
56
- In your model, tell Geocoder which method returns your object's full address:
56
+ === Mongoid
57
57
 
58
- geocoded_by :full_street_address # can also be an IP address
58
+ First, your model must have an array field for storing coordinates:
59
+
60
+ field :coordinates, :type => Array
61
+
62
+ You may also want an address field, like this:
63
+
64
+ field :address
65
+
66
+ but if you store address components (city, state, country, etc) in separate fields you can instead define a method called +address+ that combines them into a single string which will be used to query the geocoding service.
67
+
68
+ Once your fields are defined, include the <tt>Geocoder::Model::Mongoid</tt> module and then call <tt>geocoded_by</tt>:
69
+
70
+ include Geocoder::Model::Mongoid
71
+ geocoded_by :address # can also be an IP address
59
72
  after_validation :geocode # auto-fetch coordinates
60
73
 
61
- For reverse geocoding, tell Geocoder which methods return latitude and longitude:
74
+ Reverse geocoding is similar:
62
75
 
63
- reverse_geocoded_by :lat, :lon
76
+ include Geocoder::Model::Mongoid
77
+ reverse_geocoded_by :coordinates
64
78
  after_validation :reverse_geocode # auto-fetch address
65
79
 
66
- If you have just added geocoding to a class and have a lot of existing objects you can use this Rake task to geocode them all:
80
+ === Bulk Geocoding
81
+
82
+ If you have just added geocoding to an existing application with a lot of objects you can use this Rake task to geocode them all:
67
83
 
68
84
  rake geocode:all CLASS=YourModel
69
85
 
86
+ Geocoder will print warnings if you exceed the rate limit for your geocoding service.
87
+
88
+
89
+ == Request Geocoding by IP Address
90
+
91
+ Geocoder adds a +location+ method to the standard <tt>Rack::Request</tt> object so you can easily look up the location of any HTTP request by IP address. For example, in a Rails controller or a Sinatra app:
92
+
93
+ # returns Geocoder::Result object
94
+ result = request.location
95
+
96
+ See "Advanced Geocoding" below for more information about Geocoder::Result objects.
97
+
70
98
 
71
99
  == Location-Aware Database Queries
72
100
 
@@ -79,8 +107,9 @@ To find objects by location, use the following scopes:
79
107
 
80
108
  With geocoded objects you can do things like this:
81
109
 
82
- obj.nearbys(30) # other objects within 30 miles
83
- obj.distance_to(40.714, -100.234) # distance from object to arbitrary point
110
+ obj.nearbys(30) # other objects within 30 miles
111
+ obj.distance_from([40.714,-100.234]) # distance from arbitrary point to object
112
+ obj.bearing_to("Paris, France") # direction from object to arbitrary point
84
113
 
85
114
  Some utility methods are also available:
86
115
 
@@ -89,11 +118,11 @@ Some utility methods are also available:
89
118
  => [42.700149, -74.922767]
90
119
 
91
120
  # distance (in miles) between Eiffel Tower and Empire State Building
92
- Geocoder::Calculations.distance_between( 47.858205,2.294359, 40.748433,-73.985655 )
121
+ Geocoder::Calculations.distance_between([47.858205,2.294359], [40.748433,-73.985655])
93
122
  => 3619.77359999382
94
123
 
95
124
  # find the geographic center (aka center of gravity) of objects or points
96
- Geocoder::Calculations.geographic_center([ city1, city2, [40.22,-73.99], city4 ])
125
+ Geocoder::Calculations.geographic_center(city1, city2, [40.22,-73.99], city4)
97
126
  => [35.14968, -90.048929]
98
127
 
99
128
  Please see the code for more methods and detailed information about arguments (eg, working with kilometers).
@@ -101,19 +130,19 @@ Please see the code for more methods and detailed information about arguments (e
101
130
 
102
131
  == Distance and Bearing
103
132
 
104
- When you run a location-aware query the returned objects have two attributes added to them:
133
+ When you run a location-aware query the returned objects have two attributes added to them (only w/ ActiveRecord):
105
134
 
106
135
  * <tt>obj.distance</tt> - number of miles from the search point to this object
107
136
  * <tt>obj.bearing</tt> - direction from the search point to this object
108
137
 
109
- The bearing is given as a number (between 0 and 360): clockwise degrees from due north. Some examples:
138
+ Results are automatically sorted by distance from the search point, closest to farthest. Bearing is given as a number of clockwise degrees from due north, for example:
110
139
 
111
- * +0+ - due north
112
- * +180+ - due south
113
- * +90+ - due east
114
- * +270+ - due west
115
- * +230.1+ - southwest
116
- * +359.9+ - almost due north
140
+ * <tt>0</tt> - due north
141
+ * <tt>180</tt> - due south
142
+ * <tt>90</tt> - due east
143
+ * <tt>270</tt> - due west
144
+ * <tt>230.1</tt> - southwest
145
+ * <tt>359.9</tt> - almost due north
117
146
 
118
147
  You can convert these numbers to compass point names by using the utility method provided:
119
148
 
@@ -121,14 +150,23 @@ You can convert these numbers to compass point names by using the utility method
121
150
  Geocoder::Calculations.compass_point(45) # => "NE"
122
151
  Geocoder::Calculations.compass_point(208) # => "SW"
123
152
 
124
- <i>Note: when using SQLite +distance+ and +bearing+ values are provided for interface consistency only. They are not accurate.</i>
153
+ <i>Note: when using SQLite +distance+ and +bearing+ values are provided for interface consistency only. They are not very accurate.</i>
154
+
155
+ To calculate accurate distance and bearing with SQLite or Mongoid:
156
+
157
+ obj.distance_to([43.9,-98.6]) # distance from obj to point
158
+ obj.bearing_to([43.9,-98.6]) # bearing from obj to point
159
+ obj.bearing_from(obj2) # bearing from obj2 to obj
160
+
161
+ The <tt>bearing_from/to</tt> methods take a single argument which can be: a <tt>[lat,lon]</tt> array, a geocoded object, or a geocodable address (string). The <tt>distance_from/to</tt> methods also take a units argument (<tt>:mi</tt> or <tt>:km</tt>).
125
162
 
126
163
 
127
164
  == More on Configuration
128
165
 
129
- You are not stuck with using the +latitude+ and +longitude+ database column names for storing coordinates. For example, to use +lat+ and +lon+:
166
+ You are not stuck with using the +latitude+ and +longitude+ database column names (with ActiveRecord) or the +coordinates+ array (Mongoid) for storing coordinates. For example:
130
167
 
131
- geocoded_by :address, :latitude => :lat, :longitude => :lon
168
+ geocoded_by :address, :latitude => :lat, :longitude => :lon # ActiveRecord
169
+ geocoded_by :address, :coordinates => :coords # Mongoid
132
170
 
133
171
  The +address+ method can return any string you'd use to search Google Maps. For example, any of the following are acceptable:
134
172
 
@@ -146,30 +184,33 @@ If your model has +street+, +city+, +state+, and +country+ attributes you might
146
184
 
147
185
  For reverse geocoding you can also specify an alternate name attribute where the address will be stored, for example:
148
186
 
149
- reverse_geocoded_by :lat, :lon, :address => :location
187
+ reverse_geocoded_by :lat, :lon, :address => :location # ActiveRecord
188
+ reverse_geocoded_by :coordinates, :address => :loc # Mongoid
150
189
 
151
190
 
152
191
  == Advanced Geocoding
153
192
 
154
- So far we have looked at shortcuts for assigning geocoding results to object attributes. However, if you need to do something fancy you can skip the auto-assignment by providing a block (takes the object to be geocoded and a <tt>Geocoder::Result</tt> object) in which you handle the parsed geocoding result any way you like, for example:
193
+ So far we have looked at shortcuts for assigning geocoding results to object attributes. However, if you need to do something fancy you can skip the auto-assignment by providing a block (takes the object to be geocoded and an array of <tt>Geocoder::Result</tt> objects) in which you handle the parsed geocoding result any way you like, for example:
155
194
 
156
- reverse_geocoded_by :lat, :lon do |obj,geo|
157
- obj.city = geo.city
158
- obj.zipcode = geo.postal_code
159
- obj.country = geo.country_code
195
+ reverse_geocoded_by :lat, :lon do |obj,results|
196
+ if geo = results.first
197
+ obj.city = geo.city
198
+ obj.zipcode = geo.postal_code
199
+ obj.country = geo.country_code
200
+ end
160
201
  end
161
202
  after_validation :reverse_geocode
162
203
 
163
204
  Every <tt>Geocoder::Result</tt> object, +result+, provides the following data:
164
205
 
165
- * +result.latitude+ - float
166
- * +result.longitude+ - float
167
- * +result.coordinates+ - array of the above two
168
- * +result.address+ - string
169
- * +result.city+ - string
170
- * +result.postal_code+ - string
171
- * +result.country_name+ - string
172
- * +result.country_code+ - string
206
+ * <tt>result.latitude</tt> - float
207
+ * <tt>result.longitude</tt> - float
208
+ * <tt>result.coordinates</tt> - array of the above two
209
+ * <tt>result.address</tt> - string
210
+ * <tt>result.city</tt> - string
211
+ * <tt>result.postal_code</tt> - string
212
+ * <tt>result.country_name</tt> - string
213
+ * <tt>result.country_code</tt> - string
173
214
 
174
215
  and if you're familiar with the results returned by the geocoding service you're using, you can access even more (see code comments for details: <tt>lib/geocoder/results/*</tt>).
175
216
 
@@ -288,14 +329,6 @@ However, there can be only one set of latitude/longitude attributes, and whichev
288
329
  The reason for this is that we don't want ambiguity when doing distance calculations. We need a single, authoritative source for coordinates!
289
330
 
290
331
 
291
- == Request Geocoding by IP Address
292
-
293
- Geocoder adds a +location+ method to the standard <tt>Rack::Request</tt> object so you can easily look up the location of any HTTP request by IP address. For example, in a Rails controller or a Sinatra app:
294
-
295
- # returns Geocoder::Result object
296
- result = request.location
297
-
298
-
299
332
  == Use Outside of Rails
300
333
 
301
334
  You can use Geocoder outside of Rails by calling the <tt>Geocoder.search</tt> method:
@@ -305,6 +338,17 @@ You can use Geocoder outside of Rails by calling the <tt>Geocoder.search</tt> me
305
338
  This returns an array of <tt>Geocoder::Result</tt> objects with all information provided by the geocoding service. Please see above and in the code for details.
306
339
 
307
340
 
341
+ == Notes on Mongoid
342
+
343
+ === The Near Method
344
+
345
+ Mongoid document classes have a built-in +near+ scope, but since it only works two-dimensions Geocoder overrides it with its own spherical +near+ method in geocoded classes.
346
+
347
+ === Latitude/Longitude Order
348
+
349
+ Coordinates are generally printed and spoken as latitude, then logitude ([lat,lon]). Geocoder respects this convention and always expects method arguments to be given in [lat,lon] order. However, MongoDB requires that coordinates be stored in [lon,lat] order as per the GeoJSON spec (http://geojson.org/geojson-spec.html#positions), so internally they are stored "backwards." However, this does not affect order of arguments to methods when using Mongoid. I mention this only in case you notice it and freak out. Don't worry. Everything is going to be OK.
350
+
351
+
308
352
  == Distance Queries in SQLite
309
353
 
310
354
  SQLite's lack of trigonometric functions requires an alternate implementation of the +near+ scope. When using SQLite, Geocoder will automatically use a less accurate algorithm for finding objects near a given point. Results of this algorithm should not be trusted too much as it will return objects that are outside the given radius, along with inaccurate distance and bearing calculations.
@@ -323,6 +367,13 @@ There are few options for finding objects near a given point in SQLite without i
323
367
  Because Geocoder needs to provide this functionality as a scope, we must go with option #1, but feel free to implement #2 or #3 if you need more accuracy.
324
368
 
325
369
 
370
+ == Tests
371
+
372
+ Geocoder comes with a test suite (just run <tt>rake test</tt>) that mocks ActiveRecord and is focused on testing the aspects of Geocoder that do not involve executing database queries. Geocoder uses many database engine-specific queries which must be tested against all supported databases (SQLite, MySQL, etc). Ideally this involves creating a full, working Rails application, and that seems beyond the scope of the included test suite. As such, I have created a separate repository which includes a full-blown Rails application and some utilities for easily running tests against multiple environments:
373
+
374
+ http://github.com/alexreisner/geocoder_test
375
+
376
+
326
377
  == Known Issue
327
378
 
328
379
  You cannot use the +near+ scope with another scope that provides an +includes+ option because the +SELECT+ clause generated by +near+ will overwrite it (or vice versa). Instead, try using +joins+ and pass a <tt>:select</tt> option to the +near+ scope to get the columns you want. For example, in Rails 2 syntax:
data/Rakefile CHANGED
@@ -20,6 +20,6 @@ Rake::RDocTask.new do |rdoc|
20
20
 
21
21
  rdoc.rdoc_dir = 'rdoc'
22
22
  rdoc.title = "geocoder #{version}"
23
- rdoc.rdoc_files.include('README*')
23
+ rdoc.rdoc_files.include('*.rdoc')
24
24
  rdoc.rdoc_files.include('lib/**/*.rb')
25
25
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.11
1
+ 0.9.12
data/lib/geocoder.rb CHANGED
@@ -2,6 +2,8 @@ require "geocoder/configuration"
2
2
  require "geocoder/calculations"
3
3
  require "geocoder/cache"
4
4
  require "geocoder/request"
5
+ require "geocoder/models/active_record"
6
+ require "geocoder/models/mongoid"
5
7
 
6
8
  module Geocoder
7
9
  extend self
@@ -9,12 +11,16 @@ module Geocoder
9
11
  ##
10
12
  # Search for information about an address or a set of coordinates.
11
13
  #
12
- def search(*args)
13
- if blank_query?(args[0])
14
+ def search(query, *args)
15
+ # convert coordinates as separate arguments to an array
16
+ if query.is_a?(Numeric) and args.first.is_a?(Numeric)
17
+ 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."
18
+ query = [query, args.first]
19
+ end
20
+ if blank_query?(query)
14
21
  results = []
15
22
  else
16
- ip = (args.size == 1 and ip_address?(args.first))
17
- results = lookup(ip).search(*args)
23
+ results = lookup(ip_address?(query)).search(query)
18
24
  end
19
25
  results.instance_eval do
20
26
  def warn_search_deprecation(attr)
@@ -43,10 +49,15 @@ module Geocoder
43
49
  end
44
50
 
45
51
  ##
46
- # Look up the address of the given coordinates.
52
+ # Look up the address of the given coordinates ([lat,lon])
53
+ # or IP address (string).
47
54
  #
48
- def address(latitude, longitude)
49
- if (results = search(latitude, longitude)).size > 0
55
+ def address(query, *args)
56
+ if lon = args.first
57
+ warn "DEPRECATION WARNING: Instead of passing latitude/longitude as separate arguments to the address method, please pass an array: [#{query},#{args.first}]. The old argument format will not be supported in Geocoder v.1.0."
58
+ query = [query, lon]
59
+ end
60
+ if (results = search(query)).size > 0
50
61
  results.first.address
51
62
  end
52
63
  end
@@ -124,7 +135,7 @@ module Geocoder
124
135
  # dot-delimited 8-bit numbers.
125
136
  #
126
137
  def ip_address?(value)
127
- !!value.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
138
+ !!value.to_s.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
128
139
  end
129
140
 
130
141
  ##
@@ -22,51 +22,70 @@ module Geocoder
22
22
  KM_IN_MI = 0.621371192
23
23
 
24
24
  ##
25
- # Calculate the distance spanned by one
26
- # degree of latitude in the given units.
25
+ # Distance spanned by one degree of latitude in the given units.
27
26
  #
28
27
  def latitude_degree_distance(units = :mi)
29
28
  2 * Math::PI * earth_radius(units) / 360
30
29
  end
31
30
 
32
31
  ##
33
- # Calculate the distance spanned by one degree of longitude
34
- # at the given latitude. This ranges from around 69 miles at
35
- # the equator to zero at the poles.
32
+ # Distance spanned by one degree of longitude at the given latitude.
33
+ # This ranges from around 69 miles at the equator to zero at the poles.
36
34
  #
37
35
  def longitude_degree_distance(latitude, units = :mi)
38
36
  latitude_degree_distance(units) * Math.cos(to_radians(latitude))
39
37
  end
40
38
 
41
39
  ##
42
- # Calculate the distance between two points on Earth (Haversine formula).
43
- # Takes two sets of coordinates and an options hash:
40
+ # Distance between two points on Earth (Haversine formula).
41
+ # Takes two points and an options hash.
42
+ # The points are given in the same way that points are given to all
43
+ # Geocoder methods that accept points as arguments. They can be:
44
+ #
45
+ # * an array of coordinates ([lat,lon])
46
+ # * a geocodable address (string)
47
+ # * a geocoded object (one which implements a +to_coordinates+ method
48
+ # which returns a [lat,lon] array
49
+ #
50
+ # The options hash supports:
44
51
  #
45
52
  # * <tt>:units</tt> - <tt>:mi</tt> (default) or <tt>:km</tt>
46
53
  #
47
- def distance_between(lat1, lon1, lat2, lon2, options = {})
54
+ def distance_between(point1, point2, options = {}, *args)
55
+ if args.size > 0
56
+ warn "DEPRECATION WARNING: Instead of passing lat1/lon1/lat2/lon2 as separate arguments to the distance_between method, please pass two two-element arrays: [#{point1},#{point2}], [#{options}, #{args.first}]. The old argument format will not be supported in Geocoder v.1.0."
57
+ point1 = [point1, point2]
58
+ point2 = [options, args.shift]
59
+ options = args.shift || {}
60
+ end
48
61
 
49
62
  # set default options
50
63
  options[:units] ||= :mi
51
64
 
65
+ # convert to coordinate arrays
66
+ point1 = extract_coordinates(point1)
67
+ point2 = extract_coordinates(point2)
68
+
52
69
  # convert degrees to radians
53
- lat1, lon1, lat2, lon2 = to_radians(lat1, lon1, lat2, lon2)
70
+ point1 = to_radians(point1)
71
+ point2 = to_radians(point2)
54
72
 
55
73
  # compute deltas
56
- dlat = lat2 - lat1
57
- dlon = lon2 - lon1
74
+ dlat = point2[0] - point1[0]
75
+ dlon = point2[1] - point1[1]
58
76
 
59
- a = (Math.sin(dlat / 2))**2 + Math.cos(lat1) *
60
- (Math.sin(dlon / 2))**2 * Math.cos(lat2)
77
+ a = (Math.sin(dlat / 2))**2 + Math.cos(point1[0]) *
78
+ (Math.sin(dlon / 2))**2 * Math.cos(point2[0])
61
79
  c = 2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a))
62
80
  c * earth_radius(options[:units])
63
81
  end
64
82
 
65
83
  ##
66
- # Calculate bearing between two sets of coordinates.
84
+ # Bearing between two points on Earth.
67
85
  # Returns a number of degrees from due north (clockwise).
68
86
  #
69
- # Also accepts an options hash:
87
+ # See Geocoder::Calculations.distance_between for
88
+ # ways of specifying the points. Also accepts an options hash:
70
89
  #
71
90
  # * <tt>:method</tt> - <tt>:linear</tt> (default) or <tt>:spherical</tt>;
72
91
  # the spherical method is "correct" in that it returns the shortest path
@@ -76,15 +95,27 @@ module Geocoder
76
95
  #
77
96
  # Based on: http://www.movable-type.co.uk/scripts/latlong.html
78
97
  #
79
- def bearing_between(lat1, lon1, lat2, lon2, options = {})
98
+ def bearing_between(point1, point2, options = {}, *args)
99
+ if args.size > 0
100
+ warn "DEPRECATION WARNING: Instead of passing lat1/lon1/lat2/lon2 as separate arguments to the bearing_between method, please pass two two-element arrays: [#{point1},#{point2}], [#{options}, #{args.first}]. The old argument format will not be supported in Geocoder v.1.0."
101
+ point1 = [point1, point2]
102
+ point2 = [options, args.shift]
103
+ options = args.shift || {}
104
+ end
105
+
80
106
  options[:method] = :linear unless options[:method] == :spherical
81
107
 
108
+ # convert to coordinate arrays
109
+ point1 = extract_coordinates(point1)
110
+ point2 = extract_coordinates(point2)
111
+
82
112
  # convert degrees to radians
83
- lat1, lon1, lat2, lon2 = to_radians(lat1, lon1, lat2, lon2)
113
+ point1 = to_radians(point1)
114
+ point2 = to_radians(point2)
84
115
 
85
116
  # compute deltas
86
- dlat = lat2 - lat1
87
- dlon = lon2 - lon1
117
+ dlat = point2[0] - point1[0]
118
+ dlon = point2[1] - point1[1]
88
119
 
89
120
  case options[:method]
90
121
  when :linear
@@ -92,9 +123,9 @@ module Geocoder
92
123
  x = dlat
93
124
 
94
125
  when :spherical
95
- y = Math.sin(dlon) * Math.cos(lat2)
96
- x = Math.cos(lat1) * Math.sin(lat2) -
97
- Math.sin(lat1) * Math.cos(lat2) * Math.cos(dlon)
126
+ y = Math.sin(dlon) * Math.cos(point2[0])
127
+ x = Math.cos(point1[0]) * Math.sin(point2[0]) -
128
+ Math.sin(point1[0]) * Math.cos(point2[0]) * Math.cos(dlon)
98
129
  end
99
130
 
100
131
  bearing = Math.atan2(x,y)
@@ -119,15 +150,12 @@ module Geocoder
119
150
  #
120
151
  def geographic_center(points)
121
152
 
122
- # convert objects to [lat,lon] arrays and remove nils
123
- points.map!{ |p| p.is_a?(Array) ? p : p.to_coordinates }.compact
124
-
125
- # convert degrees to radians
126
- points.map!{ |p| to_radians(p) }
153
+ # convert objects to [lat,lon] arrays and convert degrees to radians
154
+ coords = points.map{ |p| to_radians(extract_coordinates(p)) }
127
155
 
128
156
  # convert to Cartesian coordinates
129
157
  x = []; y = []; z = []
130
- points.each do |p|
158
+ coords.each do |p|
131
159
  x << Math.cos(p[0]) * Math.cos(p[1])
132
160
  y << Math.cos(p[0]) * Math.sin(p[1])
133
161
  z << Math.sin(p[0])
@@ -157,14 +185,26 @@ module Geocoder
157
185
  # roughly limiting the possible solutions in a geo-spatial search
158
186
  # (ActiveRecord queries use it thusly).
159
187
  #
160
- def bounding_box(latitude, longitude, radius, options = {})
161
- units = options[:units] || :mi
162
- radius = radius.to_f
188
+ # See Geocoder::Calculations.distance_between for
189
+ # ways of specifying the point. Also accepts an options hash:
190
+ #
191
+ # * <tt>:units</tt> - <tt>:mi</tt> (default) or <tt>:km</tt>
192
+ #
193
+ def bounding_box(point, radius, options = {}, *args)
194
+ if point.is_a?(Numeric)
195
+ warn "DEPRECATION WARNING: Instead of passing latitude/longitude as separate arguments to the bounding_box method, please pass an array [#{point},#{radius}], a geocoded object, or a geocodable address (string). The old argument format will not be supported in Geocoder v.1.0."
196
+ point = [point, radius]
197
+ radius = options
198
+ options = args.first || {}
199
+ end
200
+ lat,lon = extract_coordinates(point)
201
+ radius = radius.to_f
202
+ units = options[:units] || :mi
163
203
  [
164
- latitude - (radius / latitude_degree_distance(units)),
165
- longitude - (radius / longitude_degree_distance(latitude, units)),
166
- latitude + (radius / latitude_degree_distance(units)),
167
- longitude + (radius / longitude_degree_distance(latitude, units))
204
+ lat - (radius / latitude_degree_distance(units)),
205
+ lon - (radius / longitude_degree_distance(lat, units)),
206
+ lat + (radius / latitude_degree_distance(units)),
207
+ lon + (radius / longitude_degree_distance(lat, units))
168
208
  ]
169
209
  end
170
210
 
@@ -196,6 +236,14 @@ module Geocoder
196
236
  end
197
237
  end
198
238
 
239
+ def distance_to_radians(distance, units = :mi)
240
+ distance.to_f / earth_radius(units)
241
+ end
242
+
243
+ def radians_to_distance(radians, units = :mi)
244
+ radians * earth_radius(units)
245
+ end
246
+
199
247
  ##
200
248
  # Convert miles to kilometers.
201
249
  #
@@ -230,5 +278,19 @@ module Geocoder
230
278
  def mi_in_km
231
279
  1.0 / KM_IN_MI
232
280
  end
281
+
282
+ ##
283
+ # Takes an object which is a [lat,lon] array, a geocodable string,
284
+ # or an object that implements +to_coordinates+ and returns a
285
+ # [lat,lon] array. Note that if a string is passed this may be a slow-
286
+ # running method and may return nil.
287
+ #
288
+ def extract_coordinates(point)
289
+ case point
290
+ when Array; point
291
+ when String; Geocoder.coordinates(point)
292
+ else point.to_coordinates
293
+ end
294
+ end
233
295
  end
234
296
  end