geocoder 1.1.3 → 1.1.4
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 +12 -0
- data/README.md +580 -0
- data/examples/autoexpire_cache.rb +30 -0
- data/lib/geocoder.rb +9 -85
- data/lib/geocoder/calculations.rb +1 -1
- data/lib/geocoder/cli.rb +9 -10
- data/lib/geocoder/configuration.rb +3 -1
- data/lib/geocoder/lookup.rb +81 -0
- data/lib/geocoder/lookups/base.rb +20 -32
- data/lib/geocoder/lookups/bing.rb +12 -8
- data/lib/geocoder/lookups/freegeoip.rb +5 -5
- data/lib/geocoder/lookups/geocoder_ca.rb +13 -9
- data/lib/geocoder/lookups/google.rb +19 -7
- data/lib/geocoder/lookups/google_premier.rb +8 -7
- data/lib/geocoder/lookups/mapquest.rb +5 -23
- data/lib/geocoder/lookups/nominatim.rb +16 -13
- data/lib/geocoder/lookups/test.rb +8 -6
- data/lib/geocoder/lookups/yahoo.rb +49 -10
- data/lib/geocoder/lookups/yandex.rb +15 -8
- data/lib/geocoder/models/mongoid.rb +0 -1
- data/lib/geocoder/query.rb +88 -0
- data/lib/geocoder/results/mapquest.rb +2 -61
- data/lib/geocoder/results/nominatim.rb +24 -3
- data/lib/geocoder/sql.rb +104 -0
- data/lib/geocoder/stores/active_record.rb +68 -140
- data/lib/geocoder/stores/mongo_base.rb +2 -2
- data/lib/geocoder/version.rb +1 -1
- data/test/active_record_test.rb +15 -0
- data/test/calculations_test.rb +7 -0
- data/test/error_handling_test.rb +7 -7
- data/test/fixtures/yahoo_madison_square_garden.json +49 -43
- data/test/fixtures/yahoo_v1_madison_square_garden.json +46 -0
- data/test/fixtures/yahoo_v1_no_results.json +10 -0
- data/test/https_test.rb +2 -2
- data/test/integration/smoke_test.rb +6 -4
- data/test/lookup_test.rb +13 -6
- data/test/query_test.rb +34 -0
- data/test/result_test.rb +1 -1
- data/test/services_test.rb +48 -7
- data/test/test_helper.rb +64 -49
- data/test/test_mode_test.rb +0 -1
- metadata +13 -7
- data/README.rdoc +0 -552
- data/test/fixtures/yahoo_garbage.json +0 -50
- data/test/input_handling_test.rb +0 -43
@@ -0,0 +1,30 @@
|
|
1
|
+
# This class implements a cache with simple delegation to the Redis store, but
|
2
|
+
# when it creates a key/value pair, it also sends an EXPIRE command with a TTL.
|
3
|
+
# It should be fairly simple to do the same thing with Memcached.
|
4
|
+
class AutoexpireCache
|
5
|
+
def initialize(store)
|
6
|
+
@store = store
|
7
|
+
@ttl = 86400
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](url)
|
11
|
+
@store.[](url)
|
12
|
+
end
|
13
|
+
|
14
|
+
def []=(url, value)
|
15
|
+
@store.[]=(url, value)
|
16
|
+
@store.expire(url, @ttl)
|
17
|
+
end
|
18
|
+
|
19
|
+
def keys
|
20
|
+
@store.keys
|
21
|
+
end
|
22
|
+
|
23
|
+
def del(url)
|
24
|
+
@store.del(url)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Geocoder.configure do |config|
|
29
|
+
config.cache = AutoexpireCache.new(Redis.new)
|
30
|
+
end
|
data/lib/geocoder.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
require "geocoder/configuration"
|
2
|
+
require "geocoder/query"
|
2
3
|
require "geocoder/calculations"
|
3
4
|
require "geocoder/exceptions"
|
4
5
|
require "geocoder/cache"
|
5
6
|
require "geocoder/request"
|
7
|
+
require "geocoder/lookup"
|
6
8
|
require "geocoder/models/active_record" if defined?(::ActiveRecord)
|
7
9
|
require "geocoder/models/mongoid" if defined?(::Mongoid)
|
8
10
|
require "geocoder/models/mongo_mapper" if defined?(::MongoMapper)
|
@@ -13,15 +15,16 @@ module Geocoder
|
|
13
15
|
##
|
14
16
|
# Search for information about an address or a set of coordinates.
|
15
17
|
#
|
16
|
-
def search(query)
|
17
|
-
|
18
|
+
def search(query, options = {})
|
19
|
+
query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
|
20
|
+
query.blank? ? [] : query.execute
|
18
21
|
end
|
19
22
|
|
20
23
|
##
|
21
24
|
# Look up the coordinates of the given street or IP address.
|
22
25
|
#
|
23
|
-
def coordinates(address)
|
24
|
-
if (results = search(address)).size > 0
|
26
|
+
def coordinates(address, options = {})
|
27
|
+
if (results = search(address, options)).size > 0
|
25
28
|
results.first.coordinates
|
26
29
|
end
|
27
30
|
end
|
@@ -30,8 +33,8 @@ module Geocoder
|
|
30
33
|
# Look up the address of the given coordinates ([lat,lon])
|
31
34
|
# or IP address (string).
|
32
35
|
#
|
33
|
-
def address(query)
|
34
|
-
if (results = search(query)).size > 0
|
36
|
+
def address(query, options = {})
|
37
|
+
if (results = search(query, options)).size > 0
|
35
38
|
results.first.address
|
36
39
|
end
|
37
40
|
end
|
@@ -45,85 +48,6 @@ module Geocoder
|
|
45
48
|
end
|
46
49
|
@cache
|
47
50
|
end
|
48
|
-
|
49
|
-
##
|
50
|
-
# Array of valid Lookup names.
|
51
|
-
#
|
52
|
-
def valid_lookups
|
53
|
-
street_lookups + ip_lookups
|
54
|
-
end
|
55
|
-
|
56
|
-
##
|
57
|
-
# All street address lookups, default first.
|
58
|
-
#
|
59
|
-
def street_lookups
|
60
|
-
[:google, :google_premier, :yahoo, :bing, :geocoder_ca, :yandex, :nominatim, :mapquest, :test]
|
61
|
-
end
|
62
|
-
|
63
|
-
##
|
64
|
-
# All IP address lookups, default first.
|
65
|
-
#
|
66
|
-
def ip_lookups
|
67
|
-
[:freegeoip]
|
68
|
-
end
|
69
|
-
|
70
|
-
|
71
|
-
private # -----------------------------------------------------------------
|
72
|
-
|
73
|
-
##
|
74
|
-
# Get a Lookup object (which communicates with the remote geocoding API).
|
75
|
-
# Takes a search query and returns an IP or street address Lookup
|
76
|
-
# depending on the query contents.
|
77
|
-
#
|
78
|
-
def lookup(query)
|
79
|
-
if ip_address?(query)
|
80
|
-
get_lookup(ip_lookups.first)
|
81
|
-
else
|
82
|
-
get_lookup(Configuration.lookup || street_lookups.first)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
##
|
87
|
-
# Retrieve a Lookup object from the store.
|
88
|
-
#
|
89
|
-
def get_lookup(name)
|
90
|
-
@lookups = {} unless defined?(@lookups)
|
91
|
-
@lookups[name] = spawn_lookup(name) unless @lookups.include?(name)
|
92
|
-
@lookups[name]
|
93
|
-
end
|
94
|
-
|
95
|
-
##
|
96
|
-
# Spawn a Lookup of the given name.
|
97
|
-
#
|
98
|
-
def spawn_lookup(name)
|
99
|
-
if valid_lookups.include?(name)
|
100
|
-
name = name.to_s
|
101
|
-
require "geocoder/lookups/#{name}"
|
102
|
-
klass = name.split("_").map{ |i| i[0...1].upcase + i[1..-1] }.join
|
103
|
-
Geocoder::Lookup.const_get(klass).new
|
104
|
-
else
|
105
|
-
valids = valid_lookups.map(&:inspect).join(", ")
|
106
|
-
raise ConfigurationError, "Please specify a valid lookup for Geocoder " +
|
107
|
-
"(#{name.inspect} is not one of: #{valids})."
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
##
|
112
|
-
# Does the given value look like an IP address?
|
113
|
-
#
|
114
|
-
# Does not check for actual validity, just the appearance of four
|
115
|
-
# dot-delimited numbers.
|
116
|
-
#
|
117
|
-
def ip_address?(value)
|
118
|
-
!!value.to_s.match(/^(::ffff:)?(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
|
119
|
-
end
|
120
|
-
|
121
|
-
##
|
122
|
-
# Is the given search query blank? (ie, should we not bother searching?)
|
123
|
-
#
|
124
|
-
def blank_query?(value)
|
125
|
-
!!value.to_s.match(/^\s*$/)
|
126
|
-
end
|
127
51
|
end
|
128
52
|
|
129
53
|
# load Railtie if Rails exists
|
@@ -184,7 +184,7 @@ module Geocoder
|
|
184
184
|
end
|
185
185
|
|
186
186
|
##
|
187
|
-
# Returns coordinates of the
|
187
|
+
# Returns coordinates of the southwest and northeast corners of a box
|
188
188
|
# with the given point at its center. The radius is the shortest distance
|
189
189
|
# from the center point to any side of the box (the length of each side
|
190
190
|
# is twice the radius).
|
data/lib/geocoder/cli.rb
CHANGED
@@ -32,9 +32,10 @@ module Geocoder
|
|
32
32
|
Geocoder::Configuration.http_proxy = proxy
|
33
33
|
end
|
34
34
|
|
35
|
-
opts.on("-s <service>", Geocoder.
|
36
|
-
"Geocoding service: #{Geocoder.
|
35
|
+
opts.on("-s <service>", Geocoder::Lookup.all_services_except_test, "--service <service>",
|
36
|
+
"Geocoding service: #{Geocoder::Lookup.all_services_except_test * ', '}") do |service|
|
37
37
|
Geocoder::Configuration.lookup = service.to_sym
|
38
|
+
Geocoder::Configuration.ip_lookup = service.to_sym
|
38
39
|
end
|
39
40
|
|
40
41
|
opts.on("-t <seconds>", "--timeout <seconds>",
|
@@ -78,21 +79,19 @@ module Geocoder
|
|
78
79
|
end
|
79
80
|
|
80
81
|
if show_url
|
81
|
-
|
82
|
-
|
83
|
-
out << lookup.send(:query_url, query, reverse) + "\n"
|
82
|
+
q = Geocoder::Query.new(query)
|
83
|
+
out << q.lookup.send(:query_url, q) + "\n"
|
84
84
|
exit 0
|
85
85
|
end
|
86
86
|
|
87
87
|
if show_json
|
88
|
-
|
89
|
-
|
90
|
-
out << lookup.send(:fetch_raw_data, query, reverse) + "\n"
|
88
|
+
q = Geocoder::Query.new(query)
|
89
|
+
out << q.lookup.send(:fetch_raw_data, q) + "\n"
|
91
90
|
exit 0
|
92
91
|
end
|
93
92
|
|
94
93
|
if (result = Geocoder.search(query).first)
|
95
|
-
|
94
|
+
google = Geocoder::Lookup.get(:google)
|
96
95
|
lines = [
|
97
96
|
["Latitude", result.latitude],
|
98
97
|
["Longitude", result.longitude],
|
@@ -101,7 +100,7 @@ module Geocoder
|
|
101
100
|
["State/province", result.state],
|
102
101
|
["Postal code", result.postal_code],
|
103
102
|
["Country", result.country],
|
104
|
-
["Google map",
|
103
|
+
["Google map", google.map_link_url(result.coordinates)],
|
105
104
|
]
|
106
105
|
lines.each do |line|
|
107
106
|
out << (line[0] + ": ").ljust(18) + line[1].to_s + "\n"
|
@@ -40,6 +40,7 @@ module Geocoder
|
|
40
40
|
OPTIONS = [
|
41
41
|
:timeout,
|
42
42
|
:lookup,
|
43
|
+
:ip_lookup,
|
43
44
|
:language,
|
44
45
|
:http_headers,
|
45
46
|
:use_https,
|
@@ -61,7 +62,8 @@ module Geocoder
|
|
61
62
|
|
62
63
|
def set_defaults
|
63
64
|
@timeout = 3 # geocoding service timeout (secs)
|
64
|
-
@lookup = :google # name of geocoding service (symbol)
|
65
|
+
@lookup = :google # name of street address geocoding service (symbol)
|
66
|
+
@ip_lookup = :freegeoip # name of IP address geocoding service (symbol)
|
65
67
|
@language = :en # ISO-639 language code
|
66
68
|
@http_headers = {} # HTTP headers for lookup
|
67
69
|
@use_https = false # use HTTPS for lookup requests? (if supported)
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Geocoder
|
2
|
+
module Lookup
|
3
|
+
extend self
|
4
|
+
|
5
|
+
##
|
6
|
+
# Array of valid Lookup service names.
|
7
|
+
#
|
8
|
+
def all_services
|
9
|
+
street_services + ip_services
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Array of valid Lookup service names, excluding :test.
|
14
|
+
#
|
15
|
+
def all_services_except_test
|
16
|
+
all_services - [:test]
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# All street address lookup services, default first.
|
21
|
+
#
|
22
|
+
def street_services
|
23
|
+
[
|
24
|
+
:google,
|
25
|
+
:google_premier,
|
26
|
+
:yahoo,
|
27
|
+
:bing,
|
28
|
+
:geocoder_ca,
|
29
|
+
:yandex,
|
30
|
+
:nominatim,
|
31
|
+
:mapquest,
|
32
|
+
:test
|
33
|
+
]
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# All IP address lookup services, default first.
|
38
|
+
#
|
39
|
+
def ip_services
|
40
|
+
[:freegeoip]
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Retrieve a Lookup object from the store.
|
45
|
+
# Use this instead of Geocoder::Lookup::X.new to get an
|
46
|
+
# already-configured Lookup object.
|
47
|
+
#
|
48
|
+
def get(name)
|
49
|
+
@services = {} unless defined?(@services)
|
50
|
+
@services[name] = spawn(name) unless @services.include?(name)
|
51
|
+
@services[name]
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
private # -----------------------------------------------------------------
|
56
|
+
|
57
|
+
##
|
58
|
+
# Spawn a Lookup of the given name.
|
59
|
+
#
|
60
|
+
def spawn(name)
|
61
|
+
if all_services.include?(name)
|
62
|
+
Geocoder::Lookup.const_get(classify_name(name)).new
|
63
|
+
else
|
64
|
+
valids = all_services.map(&:inspect).join(", ")
|
65
|
+
raise ConfigurationError, "Please specify a valid lookup for Geocoder " +
|
66
|
+
"(#{name.inspect} is not one of: #{valids})."
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Convert an "underscore" version of a name into a "class" version.
|
72
|
+
#
|
73
|
+
def classify_name(filename)
|
74
|
+
filename.to_s.split("_").map{ |i| i[0...1].upcase + i[1..-1] }.join
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
Geocoder::Lookup.all_services.each do |name|
|
80
|
+
require "geocoder/lookups/#{name}"
|
81
|
+
end
|
@@ -13,6 +13,7 @@ end
|
|
13
13
|
|
14
14
|
module Geocoder
|
15
15
|
module Lookup
|
16
|
+
|
16
17
|
class Base
|
17
18
|
|
18
19
|
##
|
@@ -23,18 +24,9 @@ module Geocoder
|
|
23
24
|
# "205.128.54.202") for geocoding, or coordinates (latitude, longitude)
|
24
25
|
# for reverse geocoding. Returns an array of <tt>Geocoder::Result</tt>s.
|
25
26
|
#
|
26
|
-
def search(query)
|
27
|
-
|
28
|
-
|
29
|
-
query = query.split(/\s*,\s*/) if coordinates?(query)
|
30
|
-
|
31
|
-
if query.is_a?(Array)
|
32
|
-
reverse = true
|
33
|
-
query = query.join(',')
|
34
|
-
else
|
35
|
-
reverse = false
|
36
|
-
end
|
37
|
-
results(query, reverse).map{ |r|
|
27
|
+
def search(query, options = {})
|
28
|
+
query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
|
29
|
+
results(query).map{ |r|
|
38
30
|
result = result_class.new(r)
|
39
31
|
result.cache_hit = @cache_hit if cache
|
40
32
|
result
|
@@ -77,14 +69,24 @@ module Geocoder
|
|
77
69
|
##
|
78
70
|
# Geocoder::Result object or nil on timeout or other error.
|
79
71
|
#
|
80
|
-
def results(query
|
72
|
+
def results(query)
|
81
73
|
fail
|
82
74
|
end
|
83
75
|
|
76
|
+
def query_url_params(query)
|
77
|
+
query.options[:params] || {}
|
78
|
+
end
|
79
|
+
|
80
|
+
def url_query_string(query)
|
81
|
+
hash_to_query(
|
82
|
+
query_url_params(query).reject{ |key,value| value.nil? }
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
84
86
|
##
|
85
87
|
# URL to use for querying the geocoding engine.
|
86
88
|
#
|
87
|
-
def query_url(query
|
89
|
+
def query_url(query)
|
88
90
|
fail
|
89
91
|
end
|
90
92
|
|
@@ -111,8 +113,8 @@ module Geocoder
|
|
111
113
|
##
|
112
114
|
# Returns a parsed search result (Ruby hash).
|
113
115
|
#
|
114
|
-
def fetch_data(query
|
115
|
-
parse_raw_data fetch_raw_data(query
|
116
|
+
def fetch_data(query)
|
117
|
+
parse_raw_data fetch_raw_data(query)
|
116
118
|
rescue SocketError => err
|
117
119
|
raise_error(err) or warn "Geocoding API connection cannot be established."
|
118
120
|
rescue TimeoutError => err
|
@@ -144,9 +146,9 @@ module Geocoder
|
|
144
146
|
##
|
145
147
|
# Fetches a raw search result (JSON string).
|
146
148
|
#
|
147
|
-
def fetch_raw_data(query
|
149
|
+
def fetch_raw_data(query)
|
148
150
|
timeout(Geocoder::Configuration.timeout) do
|
149
|
-
url = query_url(query
|
151
|
+
url = query_url(query)
|
150
152
|
uri = URI.parse(url)
|
151
153
|
if cache and body = cache[url]
|
152
154
|
@cache_hit = true
|
@@ -171,20 +173,6 @@ module Geocoder
|
|
171
173
|
Geocoder.cache
|
172
174
|
end
|
173
175
|
|
174
|
-
##
|
175
|
-
# Is the given string a loopback IP address?
|
176
|
-
#
|
177
|
-
def loopback_address?(ip)
|
178
|
-
!!(ip == "0.0.0.0" or ip.to_s.match(/^127/))
|
179
|
-
end
|
180
|
-
|
181
|
-
##
|
182
|
-
# Does the given string look like latitude/longitude coordinates?
|
183
|
-
#
|
184
|
-
def coordinates?(value)
|
185
|
-
value.is_a?(String) and !!value.to_s.match(/^-?[0-9\.]+, *-?[0-9\.]+$/)
|
186
|
-
end
|
187
|
-
|
188
176
|
##
|
189
177
|
# Simulate ActiveSupport's Object#to_query.
|
190
178
|
# Removes any keys with nil value.
|
@@ -10,8 +10,8 @@ module Geocoder::Lookup
|
|
10
10
|
|
11
11
|
private # ---------------------------------------------------------------
|
12
12
|
|
13
|
-
def results(query
|
14
|
-
return [] unless doc = fetch_data(query
|
13
|
+
def results(query)
|
14
|
+
return [] unless doc = fetch_data(query)
|
15
15
|
|
16
16
|
if doc['statusDescription'] == "OK"
|
17
17
|
return doc['resourceSets'].first['estimatedTotal'] > 0 ? doc['resourceSets'].first['resources'] : []
|
@@ -21,13 +21,17 @@ module Geocoder::Lookup
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
24
|
+
def query_url_params(query)
|
25
|
+
super.merge(
|
26
|
+
:key => Geocoder::Configuration.api_key,
|
27
|
+
:query => query.reverse_geocode? ? nil : query.sanitized_text
|
28
|
+
)
|
29
|
+
end
|
27
30
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
+
def query_url(query)
|
32
|
+
"http://dev.virtualearth.net/REST/v1/Locations" +
|
33
|
+
(query.reverse_geocode? ? "/#{query.sanitized_text}?" : "?") +
|
34
|
+
url_query_string(query)
|
31
35
|
end
|
32
36
|
end
|
33
37
|
end
|