rails-geocoder 0.9.7 → 0.9.8
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +9 -0
- data/README.rdoc +6 -6
- data/lib/geocoder.rb +12 -42
- data/lib/geocoder/active_record.rb +2 -2
- data/lib/geocoder/configuration.rb +8 -0
- data/lib/geocoder/lookup.rb +55 -43
- data/lib/geocoder/railtie.rb +68 -0
- data/lib/geocoder/result.rb +42 -0
- data/lib/tasks/geocoder.rake +15 -0
- data/test/geocoder_test.rb +7 -1
- data/test/test_helper.rb +3 -1
- metadata +8 -4
data/CHANGELOG.rdoc
CHANGED
@@ -2,10 +2,19 @@
|
|
2
2
|
|
3
3
|
Per-release changes to Geocoder.
|
4
4
|
|
5
|
+
== 0.9.8 (TBA)
|
6
|
+
|
7
|
+
* Include geocode:all Rake task in gem (was missing!).
|
8
|
+
* Add Geocoder.search for access to Google's full response.
|
9
|
+
* Add ability to configure Google connection timeout.
|
10
|
+
* Emit warnings on Google connection problems and errors.
|
11
|
+
* Refactor: insert Geocoder into ActiveRecord via Railtie.
|
12
|
+
|
5
13
|
== 0.9.7 (2011 Feb 1)
|
6
14
|
|
7
15
|
* Add reverse geocoding (+reverse_geocoded_by+).
|
8
16
|
* Prevent exception (uninitialized constant Geocoder::Net) when net/http not already required (sleepycat).
|
17
|
+
* Refactor: split monolithic Geocoder module into several smaller ones.
|
9
18
|
|
10
19
|
== 0.9.6 (2011 Jan 19)
|
11
20
|
|
data/README.rdoc
CHANGED
@@ -97,8 +97,11 @@ If your model has +address+, +city+, +state+, and +country+ attributes you might
|
|
97
97
|
[address, city, state, country].compact.join(', ')
|
98
98
|
end
|
99
99
|
|
100
|
+
Please see the code (<tt>lib/geocoder/active_record.rb</tt>) for more methods and detailed information about arguments (eg, working with kilometers).
|
100
101
|
|
101
|
-
|
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:
|
103
|
+
|
104
|
+
Geocoder::Configuration.timeout = 5
|
102
105
|
|
103
106
|
|
104
107
|
== Reverse Geocoding
|
@@ -172,15 +175,12 @@ If anyone has a more elegant solution to this problem I am very interested in se
|
|
172
175
|
|
173
176
|
== To-do List
|
174
177
|
|
178
|
+
* support different ORMs (DataMapper, Mongoid, etc)
|
175
179
|
* use completely separate "drivers" for different AR adapters?
|
176
180
|
* seems reasonable since we're using very DB-specific features
|
177
181
|
* also need to make sure 'mysql2' is supported
|
178
182
|
* make 'near' scope work with AR associations
|
179
183
|
* http://stackoverflow.com/questions/3266358/geocoder-rails-plugin-near-search-problem-with-activerecord
|
180
|
-
* prepend table names to column names in SQL distance expression (required
|
181
|
-
to do joins on another geocoded model)
|
182
|
-
* unobtrusively add ability to get a result with more data (Geocoder object?)
|
183
|
-
* the default usage should remain dead simple
|
184
184
|
|
185
185
|
|
186
|
-
Copyright (c) 2009-
|
186
|
+
Copyright (c) 2009-11 Alex Reisner, released under the MIT license
|
data/lib/geocoder.rb
CHANGED
@@ -1,53 +1,23 @@
|
|
1
|
+
require "geocoder/configuration"
|
1
2
|
require "geocoder/calculations"
|
2
3
|
require "geocoder/lookup"
|
4
|
+
require "geocoder/result"
|
3
5
|
require "geocoder/active_record"
|
6
|
+
require "geocoder/railtie"
|
4
7
|
|
5
|
-
|
6
|
-
|
7
|
-
#
|
8
|
-
ActiveRecord::Base.class_eval do
|
8
|
+
module Geocoder
|
9
|
+
extend self
|
9
10
|
|
10
11
|
##
|
11
|
-
#
|
12
|
+
# Alias for Geocoder::Lookup.search.
|
12
13
|
#
|
13
|
-
def
|
14
|
-
|
15
|
-
:user_address => address_attr,
|
16
|
-
:latitude => options[:latitude] || :latitude,
|
17
|
-
:longitude => options[:longitude] || :longitude
|
18
|
-
)
|
14
|
+
def search(*args)
|
15
|
+
Lookup.search(*args)
|
19
16
|
end
|
20
17
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
def self.reverse_geocoded_by(latitude_attr, longitude_attr, options = {})
|
25
|
-
_geocoder_init(
|
26
|
-
:fetched_address => options[:address] || :address,
|
27
|
-
:latitude => latitude_attr,
|
28
|
-
:longitude => longitude_attr
|
29
|
-
)
|
30
|
-
end
|
31
|
-
|
32
|
-
def self._geocoder_init(options)
|
33
|
-
unless _geocoder_initialized?
|
34
|
-
class_inheritable_reader :geocoder_options
|
35
|
-
class_inheritable_hash_writer :geocoder_options
|
36
|
-
end
|
37
|
-
self.geocoder_options = options
|
38
|
-
unless _geocoder_initialized?
|
39
|
-
include Geocoder::ActiveRecord
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def self._geocoder_initialized?
|
44
|
-
included_modules.include? Geocoder::ActiveRecord
|
45
|
-
end
|
18
|
+
# exception classes
|
19
|
+
class Error < StandardError; end
|
20
|
+
class ConfigurationError < Error; end
|
46
21
|
end
|
47
22
|
|
48
|
-
|
49
|
-
class GeocoderError < StandardError
|
50
|
-
end
|
51
|
-
|
52
|
-
class GeocoderConfigurationError < GeocoderError
|
53
|
-
end
|
23
|
+
Geocoder::Railtie.insert
|
@@ -185,7 +185,7 @@ module Geocoder
|
|
185
185
|
def fetch_coordinates(save = false)
|
186
186
|
address_method = self.class.geocoder_options[:user_address]
|
187
187
|
unless address_method.is_a? Symbol
|
188
|
-
raise
|
188
|
+
raise Geocoder::ConfigurationError,
|
189
189
|
"You are attempting to fetch coordinates but have not specified " +
|
190
190
|
"a method which provides an address for the object."
|
191
191
|
end
|
@@ -213,7 +213,7 @@ module Geocoder
|
|
213
213
|
lat_attr = self.class.geocoder_options[:latitude]
|
214
214
|
lon_attr = self.class.geocoder_options[:longitude]
|
215
215
|
unless lat_attr.is_a?(Symbol) and lon_attr.is_a?(Symbol)
|
216
|
-
raise
|
216
|
+
raise Geocoder::ConfigurationError,
|
217
217
|
"You are attempting to fetch an address but have not specified " +
|
218
218
|
"attributes which provide coordinates for the object."
|
219
219
|
end
|
data/lib/geocoder/lookup.rb
CHANGED
@@ -6,73 +6,85 @@ module Geocoder
|
|
6
6
|
|
7
7
|
##
|
8
8
|
# Query Google for the coordinates of the given address.
|
9
|
-
# Returns array [lat,lon] if found, nil if not found or if network error.
|
10
9
|
#
|
11
10
|
def coordinates(address)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
['lat', 'lng'].map{ |i| place[i] }
|
11
|
+
if (results = search(address)).size > 0
|
12
|
+
place = results.first.geometry['location']
|
13
|
+
['lat', 'lng'].map{ |i| place[i] }
|
14
|
+
end
|
17
15
|
end
|
18
16
|
|
19
17
|
##
|
20
18
|
# Query Google for the address of the given coordinates.
|
21
|
-
# Returns string if found, nil if not found or if network error.
|
22
19
|
#
|
23
20
|
def address(latitude, longitude)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
doc['results'].first['formatted_address']
|
21
|
+
if (results = search(latitude, longitude)).size > 0
|
22
|
+
results.first.formatted_address
|
23
|
+
end
|
28
24
|
end
|
29
25
|
|
30
|
-
|
31
|
-
private # ---------------------------------------------------------------
|
32
|
-
|
33
26
|
##
|
34
|
-
#
|
35
|
-
#
|
36
|
-
# Returns
|
27
|
+
# Takes a search string (eg: "Mississippi Coast Coliseumf, Biloxi, MS") for
|
28
|
+
# geocoding, or coordinates (latitude, longitude) for reverse geocoding.
|
29
|
+
# Returns an array of Geocoder::Result objects,
|
30
|
+
# or nil if not found or if network error.
|
37
31
|
#
|
38
|
-
def search(
|
39
|
-
|
40
|
-
doc
|
32
|
+
def search(*args)
|
33
|
+
return nil if args[0].blank?
|
34
|
+
doc = parsed_response(args.join(","), args.size == 2)
|
35
|
+
[].tap do |results|
|
36
|
+
if doc
|
37
|
+
doc['results'].each{ |r| results << Result.new(r) }
|
38
|
+
end
|
39
|
+
end
|
41
40
|
end
|
42
41
|
|
42
|
+
|
43
|
+
private # ---------------------------------------------------------------
|
44
|
+
|
43
45
|
##
|
44
46
|
# Returns a parsed Google geocoder search result (hash).
|
45
|
-
#
|
47
|
+
# Returns nil if non-200 HTTP response, timeout, or other error.
|
46
48
|
#
|
47
|
-
def
|
48
|
-
|
49
|
-
ActiveSupport::JSON.decode(
|
49
|
+
def parsed_response(query, reverse = false)
|
50
|
+
begin
|
51
|
+
doc = ActiveSupport::JSON.decode(fetch_data(query, reverse))
|
52
|
+
rescue SocketError
|
53
|
+
warn "Google Geocoding API connection cannot be established."
|
54
|
+
rescue TimeoutError
|
55
|
+
warn "Google Geocoding API not responding fast enough " +
|
56
|
+
"(see Geocoder::Configuration.timeout to set limit)."
|
57
|
+
end
|
58
|
+
|
59
|
+
case doc['status']; when "OK"
|
60
|
+
doc
|
61
|
+
when "OVER_QUERY_LIMIT"
|
62
|
+
warn "Google Geocoding API error: over query limit."
|
63
|
+
when "REQUEST_DENIED"
|
64
|
+
warn "Google Geocoding API error: request denied."
|
65
|
+
when "INVALID_REQUEST"
|
66
|
+
warn "Google Geocoding API error: invalid request."
|
50
67
|
end
|
51
68
|
end
|
52
69
|
|
53
70
|
##
|
54
|
-
#
|
55
|
-
# This method is not intended for general use (prefer Geocoder.search).
|
71
|
+
# Fetches a raw Google geocoder search result (JSON string).
|
56
72
|
#
|
57
|
-
def
|
73
|
+
def fetch_data(query, reverse = false)
|
58
74
|
return nil if query.blank?
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
# build URL
|
64
|
-
params = { param => query, :sensor => "false" }
|
65
|
-
url = "http://maps.google.com/maps/api/geocode/json?" + params.to_query
|
66
|
-
|
67
|
-
# query geocoder and make sure it responds quickly
|
68
|
-
begin
|
69
|
-
resp = nil
|
70
|
-
timeout(3) do
|
71
|
-
Net::HTTP.get_response(URI.parse(url)).body
|
72
|
-
end
|
73
|
-
rescue SocketError, TimeoutError
|
74
|
-
return nil
|
75
|
+
url = query_url(query, reverse)
|
76
|
+
timeout(Geocoder::Configuration.timeout) do
|
77
|
+
Net::HTTP.get_response(URI.parse(url)).body
|
75
78
|
end
|
76
79
|
end
|
80
|
+
|
81
|
+
def query_url(query, reverse = false)
|
82
|
+
params = {
|
83
|
+
(reverse ? :latlng : :address) => query,
|
84
|
+
:sensor => "false"
|
85
|
+
}
|
86
|
+
"http://maps.google.com/maps/api/geocode/json?" + params.to_query
|
87
|
+
end
|
77
88
|
end
|
78
89
|
end
|
90
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'geocoder'
|
2
|
+
|
3
|
+
module Geocoder
|
4
|
+
if defined? Rails::Railtie
|
5
|
+
require 'rails'
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
initializer 'geocoder.insert_into_active_record' do
|
8
|
+
ActiveSupport.on_load :active_record do
|
9
|
+
Geocoder::Railtie.insert
|
10
|
+
end
|
11
|
+
end
|
12
|
+
rake_tasks do
|
13
|
+
load "tasks/geocoder.rake"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Railtie
|
19
|
+
def self.insert
|
20
|
+
|
21
|
+
return unless defined?(::ActiveRecord)
|
22
|
+
|
23
|
+
##
|
24
|
+
# Add methods to ActiveRecord::Base so Geocoder is accessible by models.
|
25
|
+
#
|
26
|
+
::ActiveRecord::Base.class_eval do
|
27
|
+
|
28
|
+
##
|
29
|
+
# Set attribute names and include the Geocoder module.
|
30
|
+
#
|
31
|
+
def self.geocoded_by(address_attr, options = {})
|
32
|
+
_geocoder_init(
|
33
|
+
:user_address => address_attr,
|
34
|
+
:latitude => options[:latitude] || :latitude,
|
35
|
+
:longitude => options[:longitude] || :longitude
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Set attribute names and include the Geocoder module.
|
41
|
+
#
|
42
|
+
def self.reverse_geocoded_by(latitude_attr, longitude_attr, options = {})
|
43
|
+
_geocoder_init(
|
44
|
+
:fetched_address => options[:address] || :address,
|
45
|
+
:latitude => latitude_attr,
|
46
|
+
:longitude => longitude_attr
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self._geocoder_init(options)
|
51
|
+
unless _geocoder_initialized?
|
52
|
+
class_inheritable_reader :geocoder_options
|
53
|
+
class_inheritable_hash_writer :geocoder_options
|
54
|
+
end
|
55
|
+
self.geocoder_options = options
|
56
|
+
unless _geocoder_initialized?
|
57
|
+
include Geocoder::ActiveRecord
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self._geocoder_initialized?
|
62
|
+
included_modules.include? Geocoder::ActiveRecord
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Geocoder
|
2
|
+
class Result
|
3
|
+
attr_accessor :data
|
4
|
+
|
5
|
+
##
|
6
|
+
# Takes a hash of result data from a parsed Google result document.
|
7
|
+
#
|
8
|
+
def initialize(data)
|
9
|
+
@data = data
|
10
|
+
end
|
11
|
+
|
12
|
+
def types
|
13
|
+
@data['types']
|
14
|
+
end
|
15
|
+
|
16
|
+
def formatted_address
|
17
|
+
@data['formatted_address']
|
18
|
+
end
|
19
|
+
|
20
|
+
def address_components
|
21
|
+
@data['address_components']
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Get address components of a given type. Valid types are defined in
|
26
|
+
# Google's Geocoding API documentation and include (among others):
|
27
|
+
#
|
28
|
+
# :street_number
|
29
|
+
# :locality
|
30
|
+
# :neighborhood
|
31
|
+
# :route
|
32
|
+
# :postal_code
|
33
|
+
#
|
34
|
+
def address_components_of_type(type)
|
35
|
+
address_components.select{ |c| c['types'].include?(type.to_s) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def geometry
|
39
|
+
@data['geometry']
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
def klass
|
2
|
+
class_name = ENV['CLASS'] || ENV['class']
|
3
|
+
raise "Please specify a CLASS (model)" unless class_name
|
4
|
+
Object.const_get(class_name)
|
5
|
+
end
|
6
|
+
|
7
|
+
namespace :geocode do
|
8
|
+
|
9
|
+
desc "Geocode all objects without coordinates."
|
10
|
+
task :all => :environment do
|
11
|
+
klass.not_geocoded.each do |obj|
|
12
|
+
obj.fetch_coordinates!
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/test/geocoder_test.rb
CHANGED
@@ -23,8 +23,14 @@ class GeocoderTest < Test::Unit::TestCase
|
|
23
23
|
|
24
24
|
def test_exception_raised_for_unconfigured_geocoding
|
25
25
|
l = Landmark.new("Mount Rushmore", 43.88, -103.46)
|
26
|
-
assert_raises
|
26
|
+
assert_raises Geocoder::ConfigurationError do
|
27
27
|
l.fetch_coordinates
|
28
28
|
end
|
29
29
|
end
|
30
|
+
|
31
|
+
def test_result_address_components_of_type
|
32
|
+
results = Geocoder::Lookup.search("Madison Square Garden, New York, NY")
|
33
|
+
assert_equal "Manhattan",
|
34
|
+
results.first.address_components_of_type(:sublocality).first['long_name']
|
35
|
+
end
|
30
36
|
end
|
data/test/test_helper.rb
CHANGED
@@ -39,7 +39,9 @@ require 'geocoder'
|
|
39
39
|
#
|
40
40
|
module Geocoder
|
41
41
|
module Lookup
|
42
|
-
|
42
|
+
extend self
|
43
|
+
private #-----------------------------------------------------------------
|
44
|
+
def fetch_data(query, reverse = false)
|
43
45
|
File.read(File.join("test", "fixtures", "madison_square_garden.json"))
|
44
46
|
end
|
45
47
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-geocoder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 43
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 9
|
9
|
-
-
|
10
|
-
version: 0.9.
|
9
|
+
- 8
|
10
|
+
version: 0.9.8
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Alex Reisner
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-02-
|
18
|
+
date: 2011-02-08 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -32,7 +32,11 @@ files:
|
|
32
32
|
- lib/geocoder.rb
|
33
33
|
- lib/geocoder/lookup.rb
|
34
34
|
- lib/geocoder/calculations.rb
|
35
|
+
- lib/geocoder/railtie.rb
|
36
|
+
- lib/geocoder/result.rb
|
37
|
+
- lib/geocoder/configuration.rb
|
35
38
|
- lib/geocoder/active_record.rb
|
39
|
+
- lib/tasks/geocoder.rake
|
36
40
|
- test/test_helper.rb
|
37
41
|
- test/geocoder_test.rb
|
38
42
|
- CHANGELOG.rdoc
|