rails-geocoder 0.9.8 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
data/CHANGELOG.rdoc CHANGED
@@ -2,7 +2,22 @@
2
2
 
3
3
  Per-release changes to Geocoder.
4
4
 
5
- == 0.9.8 (TBA)
5
+ == 0.9.9 (2011 Mar 9)
6
+
7
+ * Add support for IP address geocoding via FreeGeoIp.net.
8
+ * Add support for Yahoo PlaceFinder geocoding API.
9
+ * Add support for custom geocoder data handling by passing a block to geocoded_by or reverse_geocoded_by.
10
+ * Add <tt>Rack::Request#location</tt> method for geocoding user's IP address.
11
+ * Change gem name to geocoder (no more rails-geocoder).
12
+ * Gem now works outside of Rails.
13
+ * DEPRECATION: +fetch_coordinates+ no longer takes an argument.
14
+ * DEPRECATION: +fetch_address+ no longer takes an argument.
15
+ * DEPRECATION: Geocoder.search now returns a single result instead of an array.
16
+ * DEPRECATION: <tt>fetch_coordinates!</tt> has been superceded by +geocode+ (then save your object manually).
17
+ * DEPRECATION: <tt>fetch_address!</tt> has been superceded by +reverse_geocode+ (then save your object manually).
18
+ * Fix: don't die when trying to get coordinates with a nil address (github.com/zmack).
19
+
20
+ == 0.9.8 (2011 Feb 8)
6
21
 
7
22
  * Include geocode:all Rake task in gem (was missing!).
8
23
  * Add Geocoder.search for access to Google's full response.
data/README.rdoc CHANGED
@@ -1,19 +1,22 @@
1
1
  = Geocoder
2
2
 
3
- Geocoder adds object geocoding and database-agnostic distance calculations to Ruby on Rails. It's as simple as calling <tt>fetch_coordinates!</tt> 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 MySQL or even SQLite.
3
+ Geocoder is a complete geocoding solution for Ruby. With Rails it adds object geocoding (by street or IP address), reverse geocoding (find street address based on given coordinates), and distance calculations to Ruby on Rails. 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.
4
4
 
5
- Geocoder is compatible with Rails 2.x and 3.x. <b>This is the README for the 3.x branch.</b> Please see the 2.x branch for installation instructions, documentation, and issues.
6
5
 
6
+ == Compatibility
7
7
 
8
- == 1. Install
8
+ 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).
9
+
10
+
11
+ == Install
9
12
 
10
13
  === As a Gem
11
14
 
12
- Add this to your Gemfile:
15
+ Add to your Gemfile:
13
16
 
14
- gem "rails-geocoder", :require => "geocoder"
17
+ gem "geocoder"
15
18
 
16
- and run this at the command prompt:
19
+ and run at the command prompt:
17
20
 
18
21
  bundle install
19
22
 
@@ -24,39 +27,35 @@ At the command prompt:
24
27
  rails plugin install git://github.com/alexreisner/geocoder.git
25
28
 
26
29
 
27
- == 2. Configure
28
-
29
- A) Add +latitude+ and +longitude+ columns to your model:
30
+ == Configure Object Geocoding
30
31
 
31
- rails generate migration AddLatitudeAndLongitudeToYourModel latitude:float longitude:float
32
- rake db:migrate
33
-
34
- B) Tell geocoder where your model stores its address:
32
+ === Required Attributes
35
33
 
36
- geocoded_by :address
34
+ 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
35
 
38
- C) Optionally, auto-fetch coordinates every time your model is saved:
36
+ rails generate migration AddLatitudeAndLongitudeToModel latitude:float longitude:float
37
+ rake db:migrate
39
38
 
40
- after_validation :fetch_coordinates
39
+ 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+).
41
40
 
42
- <i>Note that you are not stuck with the +latitude+ and +longitude+ column names, or the +address+ method. See "More On Configuration" below for details.</i>
41
+ === Model Behavior
43
42
 
43
+ In your model, tell Geocoder which method returns your object's full address:
44
44
 
45
- == 3. Use
45
+ geocoded_by :full_street_address # can also be an IP address
46
+ after_validation :geocode # auto-fetch coordinates
46
47
 
47
- Assuming +obj+ is an instance of a geocoded class, you can get its coordinates:
48
+ For reverse geocoding, tell Geocoder which methods return latitude and longitude:
48
49
 
49
- obj.fetch_coordinates # fetches and assigns coordinates
50
- obj.fetch_coordinates! # also saves lat, lon attributes
50
+ reverse_geocoded_by :lat, :lon
51
+ after_validation :reverse_geocode # auto-fetch address
51
52
 
52
- If you have a lot of objects you can use this Rake task to geocode them all:
53
+ 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:
53
54
 
54
55
  rake geocode:all CLASS=YourModel
55
56
 
56
- Once +obj+ is geocoded you can do things like this:
57
57
 
58
- obj.nearbys(30) # other objects within 30 miles
59
- obj.distance_to(40.714, -100.234) # distance to arbitrary point
58
+ == Location-Aware Database Queries
60
59
 
61
60
  To find objects by location, use the following scopes:
62
61
 
@@ -65,67 +64,121 @@ To find objects by location, use the following scopes:
65
64
  Venue.geocoded # venues with coordinates
66
65
  Venue.not_geocoded # venues without coordinates
67
66
 
68
- Some utility methods are also available:
67
+ With geocoded objects you can do things like this:
69
68
 
70
- # distance (in miles) between Eiffel Tower and Empire State Building
71
- Geocoder::Calculations.distance_between( 48.858205,2.294359, 40.748433,-73.985655 )
69
+ obj.nearbys(30) # other objects within 30 miles
70
+ obj.distance_to(40.714, -100.234) # distance from object to arbitrary point
71
+
72
+ Some utility methods are also available:
72
73
 
73
74
  # look up coordinates of some location (like searching Google Maps)
74
- Geocoder.fetch_coordinates("25 Main St, Cooperstown, NY")
75
+ Geocoder.coordinates("25 Main St, Cooperstown, NY")
76
+ => [42.700149, -74.922767]
77
+
78
+ # distance (in miles) between Eiffel Tower and Empire State Building
79
+ Geocoder::Calculations.distance_between( 47.858205,2.294359, 40.748433,-73.985655 )
80
+ => 3619.77359999382
75
81
 
76
82
  # find the geographic center (aka center of gravity) of objects or points
77
83
  Geocoder::Calculations.geographic_center([ city1, city2, city3, [40.22,-73.99], city4 ])
84
+ => [35.14968, -90.048929]
78
85
 
86
+ Please see the code for more methods and detailed information about arguments (eg, working with kilometers).
79
87
 
80
- == More On Configuration
88
+
89
+ == More on Configuration
81
90
 
82
91
  You are not stuck with using the +latitude+ and +longitude+ database column names for storing coordinates. For example, to use +lat+ and +lon+:
83
92
 
84
93
  geocoded_by :address, :latitude => :lat, :longitude => :lon
85
94
 
86
- The string to use for geocoding can be anything you'd use to search Google Maps. For example, any of the following are acceptable:
95
+ The +address+ method can return any string you'd use to search Google Maps. For example, any of the following are acceptable:
87
96
 
88
- 714 Green St, Big Town, MO
89
- Eiffel Tower, Paris, FR
90
- Paris, TX, US
97
+ * "714 Green St, Big Town, MO"
98
+ * "Eiffel Tower, Paris, FR"
99
+ * "Paris, TX, US"
91
100
 
92
- If your model has +address+, +city+, +state+, and +country+ attributes you might do something like this:
101
+ If your model has +street+, +city+, +state+, and +country+ attributes you might do something like this:
93
102
 
94
- geocoded_by :location
103
+ geocoded_by :address
95
104
 
96
- def location
97
- [address, city, state, country].compact.join(', ')
105
+ def address
106
+ [street, city, state, country].compact.join(', ')
98
107
  end
99
108
 
100
- Please see the code (<tt>lib/geocoder/active_record.rb</tt>) for more methods and detailed information about arguments (eg, working with kilometers).
109
+ For reverse geocoding you can also specify an alternate name attribute where the address will be stored, for example:
101
110
 
102
- You can also set the timeout used for connections to Google's geocoding service. The default is 3 seconds, but if you want to set it to 5 you could put the following in an initializer:
111
+ reverse_geocoded_by :lat, :lon, :address => :location
103
112
 
104
- Geocoder::Configuration.timeout = 5
105
113
 
114
+ == Advanced Geocoding
106
115
 
107
- == Reverse Geocoding
116
+ 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:
108
117
 
109
- If you need reverse geocoding (lat/long coordinates to address), do something like the following in your model:
118
+ reverse_geocoded_by :lat, :lon do |obj,geo|
119
+ obj.city = geo.city
120
+ obj.zipcode = geo.postal_code
121
+ obj.country = geo.country_code
122
+ end
123
+ after_validation :reverse_geocode
110
124
 
111
- reverse_geocoded_by :latitude, :longitude
112
- after_validation :fetch_address
125
+ Every <tt>Geocoder::Result</tt> object, +result+, provides the following data:
113
126
 
114
- and make sure it has +latitude+ and +longitude+ attributes, as well as an +address+ attribute. As with regular geocoding, you can specify alternate names for all of these attributes, for example:
127
+ * <tt>result.latitude # float</tt>
128
+ * <tt>result.longitude # float</tt>
129
+ * <tt>result.coordinates # array of the above two</tt>
130
+ * <tt>result.address # string</tt>
131
+ * <tt>result.city # string</tt>
132
+ * <tt>result.postal_code # string</tt>
133
+ * <tt>result.country_name # string</tt>
134
+ * <tt>result.country_code # string</tt>
115
135
 
116
- reverse_geocoded_by :lat, :lon, :address => :location
136
+ 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>).
137
+
138
+
139
+ == Geocoding Services
140
+
141
+ By default Geocoder uses Google's geocoding API to fetch coordinates and addresses. However if you wish to use Yahoo's geocoding API you can simply add this to an initializer:
142
+
143
+ # config/initializers/geocoder.rb
144
+ Geocoder::Configuration.lookup = :yahoo
145
+ Geocoder::Configuration.yahoo_appid = "..."
146
+
147
+ To obtain a Yahoo app id go to:
148
+
149
+ https://developer.apps.yahoo.com/wsregapp
150
+
151
+ Note that the result objects returned by different geocoding services all implement the methods listed above. Beyond that, however, you must be familiar with your particular subclass of <tt>Geocoder::Result</tt> and the geocoding service's result structure:
152
+
153
+ Google: http://code.google.com/apis/maps/documentation/geocoding/#JSON
154
+
155
+ Yahoo: http://developer.yahoo.com/geo/placefinder/guide/responses.html
156
+
157
+ FreeGeoIP: http://github.com/fiorix/freegeoip/blob/master/README.rst
158
+
159
+ === Timeouts
160
+
161
+ You can set the timeout used for connections to the geocoding service. The default is 3 seconds but if you want to set it to 5, for example, put the following in an initializer:
162
+
163
+ # config/initializers/geocoder.rb
164
+ Geocoder::Configuration.timeout = 5
117
165
 
118
166
 
119
167
  == Forward and Reverse Geocoding in the Same Model
120
168
 
121
- If you apply both forward and reverse geocoding functionality to the same model, you can provide different methods for storing the fetched address (reverse geocoding) and providing an address to use when fetching coordinates (forward geocoding), for example:
169
+ If you apply both forward and reverse geocoding functionality to the same model, you will provide two address methods:
170
+
171
+ * one for storing the fetched address (reverse geocoding)
172
+ * one for providing an address to use when fetching coordinates (forward geocoding)
173
+
174
+ For example:
122
175
 
123
176
  class Venue
124
177
 
125
178
  # build an address from street, city, and state attributes
126
179
  geocoded_by :address_from_components
127
180
 
128
- # store the Google-provided address in the full_address attribute
181
+ # store the fetched address in the full_address attribute
129
182
  reverse_geocoded_by :latitude, :longitude, :address => :full_address
130
183
  end
131
184
 
@@ -143,9 +196,26 @@ However, there can be only one set of latitude/longitude attributes, and whichev
143
196
  The reason for this is that we don't want ambiguity when doing distance calculations. We need a single, authoritative source for coordinates!
144
197
 
145
198
 
146
- == SQLite
199
+ == Request Geocoding by IP Address
200
+
201
+ 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:
202
+
203
+ # returns Geocoder::Result object
204
+ result = request.location
205
+
206
+
207
+ == Use Outside of Rails
208
+
209
+ You can use Geocoder outside of Rails by calling the <tt>Geocoder.search</tt> method:
210
+
211
+ result = Geocoder.search("McCarren Park, Brooklyn, NY")
212
+
213
+ This returns a <tt>Geocoder::Result</tt> object with all information provided by the geocoding service. Please see above and in the code for details.
214
+
215
+
216
+ == Distance Queries in SQLite
147
217
 
148
- SQLite's lack of trigonometric functions requires an alternate implementation of the +near+ method (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.
218
+ 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.
149
219
 
150
220
  It is also not possible to calculate distances between points without the trig functions so you cannot sort results by "nearness."
151
221
 
@@ -175,10 +245,8 @@ If anyone has a more elegant solution to this problem I am very interested in se
175
245
 
176
246
  == To-do List
177
247
 
178
- * support different ORMs (DataMapper, Mongoid, etc)
179
- * use completely separate "drivers" for different AR adapters?
180
- * seems reasonable since we're using very DB-specific features
181
- * also need to make sure 'mysql2' is supported
248
+ * add support for DataMapper
249
+ * add support for Mongoid
182
250
  * make 'near' scope work with AR associations
183
251
  * http://stackoverflow.com/questions/3266358/geocoder-rails-plugin-near-search-problem-with-activerecord
184
252
 
data/Rakefile CHANGED
@@ -1,21 +1,5 @@
1
- require 'rubygems'
2
- require 'rake'
3
-
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "rails-geocoder"
8
- gem.summary = %Q{Add geocoding functionality to Rails models.}
9
- gem.description = %Q{Geocoder adds object geocoding and database-agnostic distance calculations to Ruby on Rails. It does not rely on proprietary database functions so finding geocoded objects in a given area is easily done using out-of-the-box MySQL or even SQLite.}
10
- gem.email = "alex@alexreisner.com"
11
- gem.homepage = "http://github.com/alexreisner/geocoder"
12
- gem.authors = ["Alex Reisner"]
13
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
- end
15
- Jeweler::GemcutterTasks.new
16
- rescue LoadError
17
- puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
- end
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
19
3
 
20
4
  require 'rake/testtask'
21
5
  Rake::TestTask.new(:test) do |test|
@@ -24,21 +8,6 @@ Rake::TestTask.new(:test) do |test|
24
8
  test.verbose = true
25
9
  end
26
10
 
27
- begin
28
- require 'rcov/rcovtask'
29
- Rcov::RcovTask.new do |test|
30
- test.libs << 'test'
31
- test.pattern = 'test/**/*_test.rb'
32
- test.verbose = true
33
- end
34
- rescue LoadError
35
- task :rcov do
36
- abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
- end
38
- end
39
-
40
- task :test => :check_dependencies
41
-
42
11
  task :default => :test
43
12
 
44
13
  require 'rake/rdoctask'
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.9.9
data/lib/geocoder.rb CHANGED
@@ -1,23 +1,97 @@
1
+ warn "DEPRECATION WARNING: The 'rails-geocoder' gem has been renamed 'geocoder'. Please switch to that gem as this one will not be kept up to date."
1
2
  require "geocoder/configuration"
2
3
  require "geocoder/calculations"
3
- require "geocoder/lookup"
4
- require "geocoder/result"
5
- require "geocoder/active_record"
6
4
  require "geocoder/railtie"
5
+ require "geocoder/request"
7
6
 
8
7
  module Geocoder
9
8
  extend self
10
9
 
11
10
  ##
12
- # Alias for Geocoder::Lookup.search.
11
+ # Search for information about an address or a set of coordinates.
13
12
  #
14
13
  def search(*args)
15
- Lookup.search(*args)
14
+ return nil if blank_query?(args[0])
15
+ ip = (args.size == 1 and ip_address?(args.first))
16
+ lookup(ip).search(*args)
16
17
  end
17
18
 
19
+ ##
20
+ # Look up the coordinates of the given street or IP address.
21
+ #
22
+ def coordinates(address)
23
+ if result = search(address)
24
+ result.coordinates
25
+ end
26
+ end
27
+
28
+ ##
29
+ # Look up the address of the given coordinates.
30
+ #
31
+ def address(latitude, longitude)
32
+ if result = search(latitude, longitude)
33
+ result.address
34
+ end
35
+ end
36
+
37
+
18
38
  # exception classes
19
39
  class Error < StandardError; end
20
40
  class ConfigurationError < Error; end
41
+
42
+
43
+ private # -----------------------------------------------------------------
44
+
45
+ ##
46
+ # Get the lookup object (which communicates with the remote geocoding API).
47
+ # Returns an IP address lookup if +ip+ parameter true.
48
+ #
49
+ def lookup(ip = false)
50
+ if ip
51
+ get_lookup :freegeoip
52
+ else
53
+ get_lookup Geocoder::Configuration.lookup || :google
54
+ end
55
+ end
56
+
57
+ def get_lookup(name)
58
+ unless defined?(@lookups)
59
+ @lookups = {}
60
+ end
61
+ unless @lookups.include?(name)
62
+ @lookups[name] = spawn_lookup(name)
63
+ end
64
+ @lookups[name]
65
+ end
66
+
67
+ def spawn_lookup(name)
68
+ if valid_lookups.include?(name)
69
+ name = name.to_s
70
+ require "geocoder/lookups/#{name}"
71
+ eval("Geocoder::Lookup::#{name[0...1].upcase + name[1..-1]}.new")
72
+ end
73
+ end
74
+
75
+ def valid_lookups
76
+ [:google, :yahoo, :freegeoip]
77
+ end
78
+
79
+ ##
80
+ # Does the given value look like an IP address?
81
+ #
82
+ # Does not check for actual validity, just the appearance of four
83
+ # dot-delimited 8-bit numbers.
84
+ #
85
+ def ip_address?(value)
86
+ !!value.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
87
+ end
88
+
89
+ ##
90
+ # Is the given search query blank? (ie, should we not bother searching?)
91
+ #
92
+ def blank_query?(value)
93
+ !value.to_s.match(/[A-z0-9]/)
94
+ end
21
95
  end
22
96
 
23
97
  Geocoder::Railtie.insert