rails-geocoder 0.9.7 → 0.9.8
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.
- 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
|