geokit-premier 0.0.7 → 0.1.0
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/.gitignore +1 -0
- data/geokit-premier.gemspec +1 -1
- data/lib/geokit/geocoders.rb +170 -157
- data/lib/geokit/geocoders_mine.rb +119 -119
- data/lib/geokit/version.rb +1 -1
- data/spec/geocoder_spec.rb +61 -1
- data/spec/spec_helper.rb +1 -1
- metadata +114 -118
- data/Gemfile.lock +0 -56
data/.gitignore
CHANGED
data/geokit-premier.gemspec
CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.add_dependency('json_pure')
|
16
16
|
s.add_dependency('hoe')
|
17
17
|
|
18
|
-
s.add_development_dependency('rspec')
|
18
|
+
s.add_development_dependency('rspec', '>= 2.14.1')
|
19
19
|
s.add_development_dependency('autotest')
|
20
20
|
s.add_development_dependency('ZenTest')
|
21
21
|
s.add_development_dependency('standalone_migrations')
|
data/lib/geokit/geocoders.rb
CHANGED
@@ -6,8 +6,8 @@ require 'timeout'
|
|
6
6
|
require 'logger'
|
7
7
|
require 'base64'
|
8
8
|
|
9
|
-
# do this just in case
|
10
|
-
begin
|
9
|
+
# do this just in case
|
10
|
+
begin
|
11
11
|
ActiveSupport.nil?
|
12
12
|
rescue NameError
|
13
13
|
require 'json/pure'
|
@@ -18,13 +18,13 @@ module Geokit
|
|
18
18
|
class TooManyQueriesError < StandardError; end
|
19
19
|
|
20
20
|
module Inflector
|
21
|
-
|
21
|
+
|
22
22
|
extend self
|
23
|
-
|
23
|
+
|
24
24
|
def titleize(word)
|
25
25
|
humanize(underscore(word)).gsub(/\b([a-z])/u) { $1.capitalize }
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
def underscore(camel_cased_word)
|
29
29
|
camel_cased_word.to_s.gsub(/::/, '/').
|
30
30
|
gsub(/([A-Z]+)([A-Z][a-z])/u,'\1_\2').
|
@@ -32,52 +32,52 @@ module Geokit
|
|
32
32
|
tr("-", "_").
|
33
33
|
downcase
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
def humanize(lower_case_and_underscored_word)
|
37
37
|
lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
def snake_case(s)
|
41
41
|
return s.downcase if s =~ /^[A-Z]+$/u
|
42
42
|
s.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/u, '_\&') =~ /_*(.*)/
|
43
43
|
return $+.downcase
|
44
|
-
|
44
|
+
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
def url_escape(s)
|
48
48
|
URI.escape(s)
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
def camelize(str)
|
52
52
|
str.split('_').map {|w| w.capitalize}.join
|
53
53
|
end
|
54
|
-
end
|
55
|
-
|
54
|
+
end
|
55
|
+
|
56
56
|
# Contains a range of geocoders:
|
57
|
-
#
|
58
|
-
# ### "regular" address geocoders
|
57
|
+
#
|
58
|
+
# ### "regular" address geocoders
|
59
59
|
# * Yahoo Geocoder - requires an API key.
|
60
60
|
# * Geocoder.us - may require authentication if performing more than the free request limit.
|
61
61
|
# * Geocoder.ca - for Canada; may require authentication as well.
|
62
62
|
# * Geonames - a free geocoder
|
63
63
|
#
|
64
|
-
# ### address geocoders that also provide reverse geocoding
|
64
|
+
# ### address geocoders that also provide reverse geocoding
|
65
65
|
# * Google Geocoder - requires an API key.
|
66
|
-
#
|
67
|
-
# ### IP address geocoders
|
66
|
+
#
|
67
|
+
# ### IP address geocoders
|
68
68
|
# * IP Geocoder - geocodes an IP address using hostip.info's web service.
|
69
69
|
# * Geoplugin.net -- another IP address geocoder
|
70
70
|
#
|
71
71
|
# ### The Multigeocoder
|
72
72
|
# * Multi Geocoder - provides failover for the physical location geocoders.
|
73
|
-
#
|
73
|
+
#
|
74
74
|
# Some of these geocoders require configuration. You don't have to provide it here. See the README.
|
75
75
|
module Geocoders
|
76
76
|
@@proxy_addr = nil
|
77
77
|
@@proxy_port = nil
|
78
78
|
@@proxy_user = nil
|
79
79
|
@@proxy_pass = nil
|
80
|
-
@@request_timeout = nil
|
80
|
+
@@request_timeout = nil
|
81
81
|
@@yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
|
82
82
|
@@google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
|
83
83
|
@@google_client_id = nil #only used for premier accounts
|
@@ -90,9 +90,9 @@ module Geokit
|
|
90
90
|
@@logger=Logger.new(STDOUT)
|
91
91
|
@@logger.level=Logger::INFO
|
92
92
|
@@domain = nil
|
93
|
-
|
93
|
+
|
94
94
|
def self.__define_accessors
|
95
|
-
class_variables.each do |v|
|
95
|
+
class_variables.each do |v|
|
96
96
|
sym = v.to_s.delete("@").to_sym
|
97
97
|
unless self.respond_to? sym
|
98
98
|
module_eval <<-EOS, __FILE__, __LINE__
|
@@ -107,7 +107,7 @@ module Geokit
|
|
107
107
|
end
|
108
108
|
value
|
109
109
|
end
|
110
|
-
|
110
|
+
|
111
111
|
def self.#{sym}=(obj)
|
112
112
|
@@#{sym} = obj
|
113
113
|
end
|
@@ -117,38 +117,38 @@ module Geokit
|
|
117
117
|
end
|
118
118
|
|
119
119
|
__define_accessors
|
120
|
-
|
120
|
+
|
121
121
|
# Error which is thrown in the event a geocoding error occurs.
|
122
122
|
class GeocodeError < StandardError; end
|
123
123
|
|
124
124
|
# -------------------------------------------------------------------------------------------
|
125
125
|
# Geocoder Base class -- every geocoder should inherit from this
|
126
|
-
# -------------------------------------------------------------------------------------------
|
127
|
-
|
126
|
+
# -------------------------------------------------------------------------------------------
|
127
|
+
|
128
128
|
# The Geocoder base class which defines the interface to be used by all
|
129
129
|
# other geocoders.
|
130
|
-
class Geocoder
|
130
|
+
class Geocoder
|
131
131
|
# Main method which calls the do_geocode template method which subclasses
|
132
132
|
# are responsible for implementing. Returns a populated GeoLoc or an
|
133
133
|
# empty one with a failed success code.
|
134
|
-
def self.geocode(address, options = {})
|
134
|
+
def self.geocode(address, options = {})
|
135
135
|
res = do_geocode(address, options)
|
136
136
|
return res.nil? ? GeoLoc.new : res
|
137
|
-
end
|
137
|
+
end
|
138
138
|
# Main method which calls the do_reverse_geocode template method which subclasses
|
139
139
|
# are responsible for implementing. Returns a populated GeoLoc or an
|
140
140
|
# empty one with a failed success code.
|
141
141
|
def self.reverse_geocode(latlng)
|
142
142
|
res = do_reverse_geocode(latlng)
|
143
|
-
return res.success? ? res : GeoLoc.new
|
143
|
+
return res.success? ? res : GeoLoc.new
|
144
144
|
end
|
145
|
-
|
145
|
+
|
146
146
|
# Call the geocoder service using the timeout if configured.
|
147
147
|
def self.call_geocoder_service(url)
|
148
|
-
Timeout::timeout(Geokit::Geocoders::request_timeout) { return self.do_get(url) } if Geokit::Geocoders::request_timeout
|
148
|
+
Timeout::timeout(Geokit::Geocoders::request_timeout) { return self.do_get(url) } if Geokit::Geocoders::request_timeout
|
149
149
|
return self.do_get(url)
|
150
150
|
rescue TimeoutError
|
151
|
-
return nil
|
151
|
+
return nil
|
152
152
|
end
|
153
153
|
|
154
154
|
# Not all geocoders can do reverse geocoding. So, unless the subclass explicitly overrides this method,
|
@@ -157,14 +157,14 @@ module Geokit
|
|
157
157
|
def self.do_reverse_geocode(latlng)
|
158
158
|
return GeoLoc.new
|
159
159
|
end
|
160
|
-
|
160
|
+
|
161
161
|
# This will sign a raw url with a private key
|
162
162
|
def self.sign_url(raw_url,private_key)
|
163
163
|
uri = URI.parse(raw_url)
|
164
164
|
url_to_sign = uri.path + "?" + uri.query
|
165
165
|
decoded_key = Geocoder.urlsafe_decode64(private_key)
|
166
166
|
|
167
|
-
sha1_digest = OpenSSL::Digest
|
167
|
+
sha1_digest = OpenSSL::Digest.new('sha1')
|
168
168
|
signature = OpenSSL::HMAC.digest(sha1_digest,decoded_key,url_to_sign)
|
169
169
|
encoded_signature = Geocoder.urlsafe_encode64(signature)
|
170
170
|
signed_url = "#{uri.scheme}://#{uri.host}#{uri.path}?#{uri.query}&signature=#{encoded_signature}".strip!
|
@@ -183,18 +183,18 @@ module Geokit
|
|
183
183
|
encoded_text = Base64.encode64(raw_text)
|
184
184
|
encoded_text = encoded_text.gsub('+','-').gsub('/', '_')
|
185
185
|
encoded_text
|
186
|
-
end
|
186
|
+
end
|
187
187
|
|
188
188
|
protected
|
189
189
|
|
190
|
-
def self.logger()
|
190
|
+
def self.logger()
|
191
191
|
Geokit::Geocoders::logger
|
192
192
|
end
|
193
|
-
|
193
|
+
|
194
194
|
private
|
195
|
-
|
195
|
+
|
196
196
|
# Wraps the geocoder call around a proxy if necessary.
|
197
|
-
def self.do_get(url)
|
197
|
+
def self.do_get(url)
|
198
198
|
uri = URI.parse(url)
|
199
199
|
req = Net::HTTP::Get.new(url)
|
200
200
|
req.basic_auth(uri.user, uri.password) if uri.userinfo
|
@@ -204,8 +204,8 @@ module Geokit
|
|
204
204
|
GeoKit::Geocoders::proxy_pass).start(uri.host, uri.port) { |http| http.get(uri.path + "?" + uri.query) }
|
205
205
|
return res
|
206
206
|
end
|
207
|
-
|
208
|
-
# Adds subclass' geocode method making it conveniently available through
|
207
|
+
|
208
|
+
# Adds subclass' geocode method making it conveniently available through
|
209
209
|
# the base class.
|
210
210
|
def self.inherited(clazz)
|
211
211
|
class_name = clazz.name.split('::').last
|
@@ -220,10 +220,10 @@ module Geokit
|
|
220
220
|
|
221
221
|
# -------------------------------------------------------------------------------------------
|
222
222
|
# "Regular" Address geocoders
|
223
|
-
# -------------------------------------------------------------------------------------------
|
224
|
-
|
223
|
+
# -------------------------------------------------------------------------------------------
|
224
|
+
|
225
225
|
# Geocoder CA geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_CA variable to
|
226
|
-
# contain true or false based upon whether authentication is to occur. Conforms to the
|
226
|
+
# contain true or false based upon whether authentication is to occur. Conforms to the
|
227
227
|
# interface set by the Geocoder class.
|
228
228
|
#
|
229
229
|
# Returns a response like:
|
@@ -245,15 +245,15 @@ module Geokit
|
|
245
245
|
xml = res.body
|
246
246
|
logger.debug "Geocoder.ca geocoding. Address: #{address}. Result: #{xml}"
|
247
247
|
# Parse the document.
|
248
|
-
doc = REXML::Document.new(xml)
|
248
|
+
doc = REXML::Document.new(xml)
|
249
249
|
address.lat = doc.elements['//latt'].text
|
250
250
|
address.lng = doc.elements['//longt'].text
|
251
251
|
address.success = true
|
252
252
|
return address
|
253
253
|
rescue
|
254
254
|
logger.error "Caught an error during Geocoder.ca geocoding call: "+$!
|
255
|
-
return GeoLoc.new
|
256
|
-
end
|
255
|
+
return GeoLoc.new
|
256
|
+
end
|
257
257
|
|
258
258
|
# Formats the request in the format acceptable by the CA geocoder.
|
259
259
|
def self.construct_request(location)
|
@@ -271,60 +271,60 @@ module Geokit
|
|
271
271
|
def self.add_ampersand(url)
|
272
272
|
url && url.length > 0 ? "&" : ""
|
273
273
|
end
|
274
|
-
end
|
275
|
-
|
274
|
+
end
|
275
|
+
|
276
276
|
# Geocoder Us geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_US variable to
|
277
|
-
# contain true or false based upon whether authentication is to occur. Conforms to the
|
277
|
+
# contain true or false based upon whether authentication is to occur. Conforms to the
|
278
278
|
# interface set by the Geocoder class.
|
279
279
|
class UsGeocoder < Geocoder
|
280
280
|
|
281
281
|
private
|
282
282
|
def self.do_geocode(address, options = {})
|
283
283
|
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
284
|
-
|
284
|
+
|
285
285
|
query = (address_str =~ /^\d{5}(?:-\d{4})?$/ ? "zip" : "address") + "=#{Geokit::Inflector::url_escape(address_str)}"
|
286
|
-
url = if GeoKit::Geocoders::geocoder_us
|
286
|
+
url = if GeoKit::Geocoders::geocoder_us
|
287
287
|
"http://#{GeoKit::Geocoders::geocoder_us}@geocoder.us/member/service/csv/geocode"
|
288
288
|
else
|
289
289
|
"http://geocoder.us/service/csv/geocode"
|
290
290
|
end
|
291
|
-
|
292
|
-
url = "#{url}?#{query}"
|
291
|
+
|
292
|
+
url = "#{url}?#{query}"
|
293
293
|
res = self.call_geocoder_service(url)
|
294
|
-
|
294
|
+
|
295
295
|
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
296
296
|
data = res.body
|
297
297
|
logger.debug "Geocoder.us geocoding. Address: #{address}. Result: #{data}"
|
298
298
|
array = data.chomp.split(',')
|
299
|
-
|
299
|
+
|
300
300
|
if array.length == 5
|
301
301
|
res=GeoLoc.new
|
302
302
|
res.lat,res.lng,res.city,res.state,res.zip=array
|
303
303
|
res.country_code='US'
|
304
304
|
res.success=true
|
305
305
|
return res
|
306
|
-
elsif array.length == 6
|
307
|
-
res=GeoLoc.new
|
306
|
+
elsif array.length == 6
|
307
|
+
res=GeoLoc.new
|
308
308
|
res.lat,res.lng,res.street_address,res.city,res.state,res.zip=array
|
309
309
|
res.country_code='US'
|
310
|
-
res.success=true
|
310
|
+
res.success=true
|
311
311
|
return res
|
312
|
-
else
|
312
|
+
else
|
313
313
|
logger.info "geocoder.us was unable to geocode address: "+address
|
314
|
-
return GeoLoc.new
|
314
|
+
return GeoLoc.new
|
315
315
|
end
|
316
|
-
rescue
|
316
|
+
rescue
|
317
317
|
logger.error "Caught an error during geocoder.us geocoding call: "+$!
|
318
318
|
return GeoLoc.new
|
319
319
|
|
320
320
|
end
|
321
321
|
end
|
322
|
-
|
322
|
+
|
323
323
|
# Yahoo geocoder implementation. Requires the Geokit::Geocoders::YAHOO variable to
|
324
324
|
# contain a Yahoo API key. Conforms to the interface set by the Geocoder class.
|
325
325
|
class YahooGeocoder < Geocoder
|
326
326
|
|
327
|
-
private
|
327
|
+
private
|
328
328
|
|
329
329
|
# Template method which does the geocode lookup.
|
330
330
|
def self.do_geocode(address, options = {})
|
@@ -339,11 +339,11 @@ module Geokit
|
|
339
339
|
if doc.elements['//ResultSet']
|
340
340
|
res=GeoLoc.new
|
341
341
|
|
342
|
-
#basic
|
342
|
+
#basic
|
343
343
|
res.lat=doc.elements['//Latitude'].text
|
344
344
|
res.lng=doc.elements['//Longitude'].text
|
345
345
|
res.country_code=doc.elements['//Country'].text
|
346
|
-
res.provider='yahoo'
|
346
|
+
res.provider='yahoo'
|
347
347
|
|
348
348
|
#extended - false if not available
|
349
349
|
res.city=doc.elements['//City'].text if doc.elements['//City'] && doc.elements['//City'].text != nil
|
@@ -355,12 +355,12 @@ module Geokit
|
|
355
355
|
res.accuracy=%w{unknown country state state city zip zip+4 street address building}.index(res.precision)
|
356
356
|
res.success=true
|
357
357
|
return res
|
358
|
-
else
|
358
|
+
else
|
359
359
|
logger.info "Yahoo was unable to geocode address: "+address
|
360
360
|
return GeoLoc.new
|
361
|
-
end
|
361
|
+
end
|
362
362
|
|
363
|
-
rescue
|
363
|
+
rescue
|
364
364
|
logger.info "Caught an error during Yahoo geocoding call: "+$!
|
365
365
|
return GeoLoc.new
|
366
366
|
end
|
@@ -370,47 +370,47 @@ module Geokit
|
|
370
370
|
# http://www.geonames.org
|
371
371
|
class GeonamesGeocoder < Geocoder
|
372
372
|
|
373
|
-
private
|
374
|
-
|
373
|
+
private
|
374
|
+
|
375
375
|
# Template method which does the geocode lookup.
|
376
376
|
def self.do_geocode(address, options = {})
|
377
377
|
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
378
378
|
# geonames need a space seperated search string
|
379
379
|
address_str.gsub!(/,/, " ")
|
380
380
|
params = "/postalCodeSearch?placename=#{Geokit::Inflector::url_escape(address_str)}&maxRows=10"
|
381
|
-
|
381
|
+
|
382
382
|
if(GeoKit::Geocoders::geonames)
|
383
383
|
url = "http://ws.geonames.net#{params}&username=#{GeoKit::Geocoders::geonames}"
|
384
384
|
else
|
385
385
|
url = "http://ws.geonames.org#{params}"
|
386
386
|
end
|
387
|
-
|
387
|
+
|
388
388
|
res = self.call_geocoder_service(url)
|
389
|
-
|
389
|
+
|
390
390
|
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
391
|
-
|
391
|
+
|
392
392
|
xml=res.body
|
393
393
|
logger.debug "Geonames geocoding. Address: #{address}. Result: #{xml}"
|
394
394
|
doc=REXML::Document.new(xml)
|
395
|
-
|
395
|
+
|
396
396
|
if(doc.elements['//geonames/totalResultsCount'].text.to_i > 0)
|
397
397
|
res=GeoLoc.new
|
398
|
-
|
398
|
+
|
399
399
|
# only take the first result
|
400
400
|
res.lat=doc.elements['//code/lat'].text if doc.elements['//code/lat']
|
401
401
|
res.lng=doc.elements['//code/lng'].text if doc.elements['//code/lng']
|
402
402
|
res.country_code=doc.elements['//code/countryCode'].text if doc.elements['//code/countryCode']
|
403
|
-
res.provider='genomes'
|
403
|
+
res.provider='genomes'
|
404
404
|
res.city=doc.elements['//code/name'].text if doc.elements['//code/name']
|
405
405
|
res.state=doc.elements['//code/adminName1'].text if doc.elements['//code/adminName1']
|
406
406
|
res.zip=doc.elements['//code/postalcode'].text if doc.elements['//code/postalcode']
|
407
407
|
res.success=true
|
408
408
|
return res
|
409
|
-
else
|
409
|
+
else
|
410
410
|
logger.info "Geonames was unable to geocode address: "+address
|
411
411
|
return GeoLoc.new
|
412
412
|
end
|
413
|
-
|
413
|
+
|
414
414
|
rescue
|
415
415
|
logger.error "Caught an error during Geonames geocoding call: "+$!
|
416
416
|
end
|
@@ -424,18 +424,18 @@ module Geokit
|
|
424
424
|
# contain a Google API key. Conforms to the interface set by the Geocoder class.
|
425
425
|
class GoogleGeocoder < Geocoder
|
426
426
|
|
427
|
-
private
|
428
|
-
|
427
|
+
private
|
428
|
+
|
429
429
|
# Template method which does the reverse-geocode lookup.
|
430
|
-
def self.do_reverse_geocode(latlng)
|
430
|
+
def self.do_reverse_geocode(latlng)
|
431
431
|
latlng=LatLng.normalize(latlng)
|
432
432
|
res = self.call_geocoder_service("http://maps.google.com/maps/geo?ll=#{Geokit::Inflector::url_escape(latlng.ll)}&output=xml&key=#{Geokit::Geocoders::google}&oe=utf-8")
|
433
433
|
# res = Net::HTTP.get_response(URI.parse("http://maps.google.com/maps/geo?ll=#{Geokit::Inflector::url_escape(address_str)}&output=xml&key=#{Geokit::Geocoders::google}&oe=utf-8"))
|
434
434
|
return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
|
435
435
|
xml = res.body
|
436
436
|
logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{xml}"
|
437
|
-
return self.xml2GeoLoc(xml)
|
438
|
-
end
|
437
|
+
return self.xml2GeoLoc(xml)
|
438
|
+
end
|
439
439
|
|
440
440
|
# Template method which does the geocode lookup.
|
441
441
|
#
|
@@ -444,7 +444,7 @@ module Geokit
|
|
444
444
|
# ==== OPTIONS
|
445
445
|
# * :bias - This option makes the Google Geocoder return results biased to a particular
|
446
446
|
# country or viewport. Country code biasing is achieved by passing the ccTLD
|
447
|
-
# ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
|
447
|
+
# ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
|
448
448
|
# look here: http://en.wikipedia.org/wiki/CcTLD. By default, the geocoder
|
449
449
|
# will be biased to results within the US (ccTLD .com).
|
450
450
|
#
|
@@ -469,9 +469,9 @@ module Geokit
|
|
469
469
|
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
470
470
|
xml = res.body
|
471
471
|
logger.debug "Google geocoding. Address: #{address}. Result: #{xml}"
|
472
|
-
return self.xml2GeoLoc(xml, address)
|
472
|
+
return self.xml2GeoLoc(xml, address)
|
473
473
|
end
|
474
|
-
|
474
|
+
|
475
475
|
def self.construct_bias_string_from_options(bias)
|
476
476
|
if bias.is_a?(String) or bias.is_a?(Symbol)
|
477
477
|
# country code biasing
|
@@ -481,24 +481,24 @@ module Geokit
|
|
481
481
|
"&ll=#{bias.center.ll}&spn=#{bias.to_span.ll}"
|
482
482
|
end
|
483
483
|
end
|
484
|
-
|
484
|
+
|
485
485
|
def self.xml2GeoLoc(xml, address="")
|
486
486
|
doc=REXML::Document.new(xml)
|
487
487
|
|
488
488
|
if doc.elements['//kml/Response/Status/code'].text == '200'
|
489
489
|
geoloc = nil
|
490
|
-
# Google can return multiple results as //Placemark elements.
|
490
|
+
# Google can return multiple results as //Placemark elements.
|
491
491
|
# iterate through each and extract each placemark as a geoloc
|
492
492
|
doc.each_element('//Placemark') do |e|
|
493
493
|
extracted_geoloc = extract_placemark(e) # g is now an instance of GeoLoc
|
494
|
-
if geoloc.nil?
|
494
|
+
if geoloc.nil?
|
495
495
|
# first time through, geoloc is still nil, so we make it the geoloc we just extracted
|
496
|
-
geoloc = extracted_geoloc
|
496
|
+
geoloc = extracted_geoloc
|
497
497
|
else
|
498
|
-
# second (and subsequent) iterations, we push additional
|
499
|
-
# geolocs onto "geoloc.all"
|
500
|
-
geoloc.all.push(extracted_geoloc)
|
501
|
-
end
|
498
|
+
# second (and subsequent) iterations, we push additional
|
499
|
+
# geolocs onto "geoloc.all"
|
500
|
+
geoloc.all.push(extracted_geoloc)
|
501
|
+
end
|
502
502
|
end
|
503
503
|
return geoloc
|
504
504
|
elsif doc.elements['//kml/Response/Status/code'].text == '620'
|
@@ -514,7 +514,7 @@ module Geokit
|
|
514
514
|
rescue
|
515
515
|
logger.error "Caught an error during Google geocoding call: "+$!
|
516
516
|
return GeoLoc.new
|
517
|
-
end
|
517
|
+
end
|
518
518
|
|
519
519
|
# extracts a single geoloc from a //placemark element in the google results xml
|
520
520
|
def self.extract_placemark(doc)
|
@@ -541,14 +541,14 @@ module Geokit
|
|
541
541
|
address_details=doc.elements['.//*[local-name() = "AddressDetails"]']
|
542
542
|
res.accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
|
543
543
|
res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy]
|
544
|
-
|
544
|
+
|
545
545
|
# google returns a set of suggested boundaries for the geocoded result
|
546
|
-
if suggested_bounds = doc.elements['//LatLonBox']
|
546
|
+
if suggested_bounds = doc.elements['//LatLonBox']
|
547
547
|
res.suggested_bounds = Bounds.normalize(
|
548
|
-
[suggested_bounds.attributes['south'], suggested_bounds.attributes['west']],
|
548
|
+
[suggested_bounds.attributes['south'], suggested_bounds.attributes['west']],
|
549
549
|
[suggested_bounds.attributes['north'], suggested_bounds.attributes['east']])
|
550
550
|
end
|
551
|
-
|
551
|
+
|
552
552
|
res.success=true
|
553
553
|
|
554
554
|
return res
|
@@ -557,16 +557,16 @@ module Geokit
|
|
557
557
|
|
558
558
|
class GoogleGeocoder3 < Geocoder
|
559
559
|
|
560
|
-
private
|
560
|
+
private
|
561
561
|
# Template method which does the reverse-geocode lookup.
|
562
|
-
def self.do_reverse_geocode(latlng)
|
562
|
+
def self.do_reverse_geocode(latlng)
|
563
563
|
latlng=LatLng.normalize(latlng)
|
564
564
|
res = self.call_geocoder_service("http://maps.google.com/maps/api/geocode/json?sensor=false&latlng=#{Geokit::Inflector::url_escape(latlng.ll)}")
|
565
565
|
return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
|
566
566
|
json = res.body
|
567
567
|
logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{json}"
|
568
|
-
return self.json2GeoLoc(json)
|
569
|
-
end
|
568
|
+
return self.json2GeoLoc(json)
|
569
|
+
end
|
570
570
|
|
571
571
|
# Template method which does the geocode lookup.
|
572
572
|
#
|
@@ -575,7 +575,7 @@ module Geokit
|
|
575
575
|
# ==== OPTIONS
|
576
576
|
# * :bias - This option makes the Google Geocoder return results biased to a particular
|
577
577
|
# country or viewport. Country code biasing is achieved by passing the ccTLD
|
578
|
-
# ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
|
578
|
+
# ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
|
579
579
|
# look here: http://en.wikipedia.org/wiki/CcTLD. By default, the geocoder
|
580
580
|
# will be biased to results within the US (ccTLD .com).
|
581
581
|
#
|
@@ -598,9 +598,9 @@ module Geokit
|
|
598
598
|
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
599
599
|
json = res.body
|
600
600
|
# logger.debug "Google geocoding. Address: #{address}. Result: #{json}"
|
601
|
-
return self.json2GeoLoc(json, address)
|
601
|
+
return self.json2GeoLoc(json, address)
|
602
602
|
end
|
603
|
-
|
603
|
+
|
604
604
|
# Determine the Google API url based on the google api key, or based on the client / private key for premier users
|
605
605
|
def self.geocode_url(address,options = {})
|
606
606
|
bias_str = options[:bias] ? construct_bias_string_from_options(options[:bias]) : ''
|
@@ -613,8 +613,8 @@ module Geokit
|
|
613
613
|
"http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(address_str)}#{bias_str}"
|
614
614
|
end
|
615
615
|
end
|
616
|
-
|
617
|
-
|
616
|
+
|
617
|
+
|
618
618
|
def self.construct_bias_string_from_options(bias)
|
619
619
|
if bias.is_a?(String) or bias.is_a?(Symbol)
|
620
620
|
# country code biasing
|
@@ -625,6 +625,40 @@ module Geokit
|
|
625
625
|
end
|
626
626
|
end
|
627
627
|
|
628
|
+
# location_type stores additional data about the specified location.
|
629
|
+
# The following values are currently supported:
|
630
|
+
# "ROOFTOP" indicates that the returned result is a precise geocode
|
631
|
+
# for which we have location information accurate down to street
|
632
|
+
# address precision.
|
633
|
+
# "RANGE_INTERPOLATED" indicates that the returned result reflects an
|
634
|
+
# approximation (usually on a road) interpolated between two precise
|
635
|
+
# points (such as intersections). Interpolated results are generally
|
636
|
+
# returned when rooftop geocodes are unavailable for a street address.
|
637
|
+
# "GEOMETRIC_CENTER" indicates that the returned result is the
|
638
|
+
# geometric center of a result such as a polyline (for example, a
|
639
|
+
# street) or polygon (region).
|
640
|
+
# "APPROXIMATE" indicates that the returned result is approximate
|
641
|
+
|
642
|
+
# these do not map well. Perhaps we should guess better based on size
|
643
|
+
# of bounding box where it exists? Does it really matter?
|
644
|
+
def self.accuracy
|
645
|
+
{
|
646
|
+
"ROOFTOP" => 9,
|
647
|
+
"RANGE_INTERPOLATED" => 8,
|
648
|
+
"GEOMETRIC_CENTER" => 5,
|
649
|
+
"APPROXIMATE" => 4
|
650
|
+
}
|
651
|
+
end
|
652
|
+
|
653
|
+
# Grouped by accuracy, then sorts the groups by accuracy, and
|
654
|
+
# joins the results. This means that if google returns 5 results,
|
655
|
+
# and of those 5, 3 of them are the same accuracy, we will maintain
|
656
|
+
# the order of those three as we've received them, within our sorted
|
657
|
+
# results that are returned.
|
658
|
+
def self.grouped_and_sorted_by_accuracy(results)
|
659
|
+
results.group_by{|a| accuracy[ a['geometry']['location_type'] ]}.sort_by {|accuracy,x| accuracy }.reverse.map {|x,locations| locations}.flatten
|
660
|
+
end
|
661
|
+
|
628
662
|
def self.json2GeoLoc(json, address="")
|
629
663
|
ret=nil
|
630
664
|
begin
|
@@ -632,8 +666,8 @@ module Geokit
|
|
632
666
|
rescue NameError => e
|
633
667
|
results=JSON.parse(json)
|
634
668
|
end
|
635
|
-
|
636
|
-
|
669
|
+
|
670
|
+
|
637
671
|
if results['status'] == 'OVER_QUERY_LIMIT'
|
638
672
|
raise Geokit::TooManyQueriesError
|
639
673
|
end
|
@@ -644,29 +678,8 @@ module Geokit
|
|
644
678
|
if !results['status'] == 'OK'
|
645
679
|
raise Geokit::Geocoders::GeocodeError
|
646
680
|
end
|
647
|
-
|
648
|
-
|
649
|
-
# "ROOFTOP" indicates that the returned result is a precise geocode
|
650
|
-
# for which we have location information accurate down to street
|
651
|
-
# address precision.
|
652
|
-
# "RANGE_INTERPOLATED" indicates that the returned result reflects an
|
653
|
-
# approximation (usually on a road) interpolated between two precise
|
654
|
-
# points (such as intersections). Interpolated results are generally
|
655
|
-
# returned when rooftop geocodes are unavailable for a street address.
|
656
|
-
# "GEOMETRIC_CENTER" indicates that the returned result is the
|
657
|
-
# geometric center of a result such as a polyline (for example, a
|
658
|
-
# street) or polygon (region).
|
659
|
-
# "APPROXIMATE" indicates that the returned result is approximate
|
660
|
-
|
661
|
-
# these do not map well. Perhaps we should guess better based on size
|
662
|
-
# of bounding box where it exists? Does it really matter?
|
663
|
-
accuracy = {
|
664
|
-
"ROOFTOP" => 9,
|
665
|
-
"RANGE_INTERPOLATED" => 8,
|
666
|
-
"GEOMETRIC_CENTER" => 5,
|
667
|
-
"APPROXIMATE" => 4
|
668
|
-
}
|
669
|
-
results['results'].sort_by{|a|accuracy[a['geometry']['location_type']]}.reverse.each do |addr|
|
681
|
+
|
682
|
+
grouped_and_sorted_by_accuracy(results['results']).each do |addr|
|
670
683
|
res=GeoLoc.new
|
671
684
|
res.provider = 'google3'
|
672
685
|
res.success = true
|
@@ -701,13 +714,13 @@ module Geokit
|
|
701
714
|
res.precision = 'street'
|
702
715
|
res.accuracy = 7
|
703
716
|
end
|
704
|
-
|
717
|
+
|
705
718
|
res.lat=addr['geometry']['location']['lat'].to_f
|
706
719
|
res.lng=addr['geometry']['location']['lng'].to_f
|
707
720
|
|
708
721
|
if addr['geometry'].include?('viewport')
|
709
722
|
ne=Geokit::LatLng.new(
|
710
|
-
addr['geometry']['viewport']['northeast']['lat'].to_f,
|
723
|
+
addr['geometry']['viewport']['northeast']['lat'].to_f,
|
711
724
|
addr['geometry']['viewport']['northeast']['lng'].to_f
|
712
725
|
)
|
713
726
|
sw=Geokit::LatLng.new(
|
@@ -729,11 +742,11 @@ module Geokit
|
|
729
742
|
# -------------------------------------------------------------------------------------------
|
730
743
|
# IP Geocoders
|
731
744
|
# -------------------------------------------------------------------------------------------
|
732
|
-
|
745
|
+
|
733
746
|
# Provides geocoding based upon an IP address. The underlying web service is geoplugin.net
|
734
747
|
class GeoPluginGeocoder < Geocoder
|
735
748
|
private
|
736
|
-
|
749
|
+
|
737
750
|
def self.do_geocode(ip, options = {})
|
738
751
|
return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
|
739
752
|
response = self.call_geocoder_service("http://www.geoplugin.net/xml.gp?ip=#{ip}")
|
@@ -760,7 +773,7 @@ module Geokit
|
|
760
773
|
# Provides geocoding based upon an IP address. The underlying web service is a hostip.info
|
761
774
|
# which sources their data through a combination of publicly available information as well
|
762
775
|
# as community contributions.
|
763
|
-
class IpGeocoder < Geocoder
|
776
|
+
class IpGeocoder < Geocoder
|
764
777
|
|
765
778
|
# A number of non-routable IP ranges.
|
766
779
|
#
|
@@ -783,11 +796,11 @@ module Geokit
|
|
783
796
|
IPAddr.new('240.0.0.0/4') # Reserved for future use
|
784
797
|
].freeze
|
785
798
|
|
786
|
-
private
|
799
|
+
private
|
787
800
|
|
788
801
|
# Given an IP address, returns a GeoLoc instance which contains latitude,
|
789
|
-
# longitude, city, and country code. Sets the success attribute to false if the ip
|
790
|
-
# parameter does not match an ip address.
|
802
|
+
# longitude, city, and country code. Sets the success attribute to false if the ip
|
803
|
+
# parameter does not match an ip address.
|
791
804
|
def self.do_geocode(ip, options = {})
|
792
805
|
return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
|
793
806
|
return GeoLoc.new if self.private_ip_address?(ip)
|
@@ -813,7 +826,7 @@ module Geokit
|
|
813
826
|
res.provider = 'hostip'
|
814
827
|
res.city, res.state = yaml['City'].split(', ')
|
815
828
|
country, res.country_code = yaml['Country'].split(' (')
|
816
|
-
res.lat = yaml['Latitude']
|
829
|
+
res.lat = yaml['Latitude']
|
817
830
|
res.lng = yaml['Longitude']
|
818
831
|
res.country_code.chop!
|
819
832
|
res.success = !(res.city =~ /\(.+\)/)
|
@@ -829,33 +842,33 @@ module Geokit
|
|
829
842
|
return NON_ROUTABLE_IP_RANGES.any? { |range| range.include?(ip) }
|
830
843
|
end
|
831
844
|
end
|
832
|
-
|
845
|
+
|
833
846
|
# -------------------------------------------------------------------------------------------
|
834
847
|
# The Multi Geocoder
|
835
|
-
# -------------------------------------------------------------------------------------------
|
836
|
-
|
848
|
+
# -------------------------------------------------------------------------------------------
|
849
|
+
|
837
850
|
# Provides methods to geocode with a variety of geocoding service providers, plus failover
|
838
851
|
# among providers in the order you configure. When 2nd parameter is set 'true', perform
|
839
852
|
# ip location lookup with 'address' as the ip address.
|
840
|
-
#
|
853
|
+
#
|
841
854
|
# Goal:
|
842
855
|
# - homogenize the results of multiple geocoders
|
843
|
-
#
|
856
|
+
#
|
844
857
|
# Limitations:
|
845
858
|
# - currently only provides the first result. Sometimes geocoders will return multiple results.
|
846
859
|
# - currently discards the "accuracy" component of the geocoding calls
|
847
|
-
class MultiGeocoder < Geocoder
|
860
|
+
class MultiGeocoder < Geocoder
|
848
861
|
|
849
862
|
private
|
850
|
-
# This method will call one or more geocoders in the order specified in the
|
863
|
+
# This method will call one or more geocoders in the order specified in the
|
851
864
|
# configuration until one of the geocoders work.
|
852
|
-
#
|
865
|
+
#
|
853
866
|
# The failover approach is crucial for production-grade apps, but is rarely used.
|
854
|
-
# 98% of your geocoding calls will be successful with the first call
|
867
|
+
# 98% of your geocoding calls will be successful with the first call
|
855
868
|
def self.do_geocode(address, options = {})
|
856
869
|
geocode_ip = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.match(address)
|
857
870
|
provider_order = geocode_ip ? Geokit::Geocoders::ip_provider_order : Geokit::Geocoders::provider_order
|
858
|
-
|
871
|
+
|
859
872
|
provider_order.each do |provider|
|
860
873
|
begin
|
861
874
|
klass = Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder"
|
@@ -868,8 +881,8 @@ module Geokit
|
|
868
881
|
# If we get here, we failed completely.
|
869
882
|
GeoLoc.new
|
870
883
|
end
|
871
|
-
|
872
|
-
# This method will call one or more geocoders in the order specified in the
|
884
|
+
|
885
|
+
# This method will call one or more geocoders in the order specified in the
|
873
886
|
# configuration until one of the geocoders work, only this time it's going
|
874
887
|
# to try to reverse geocode a geographical point.
|
875
888
|
def self.do_reverse_geocode(latlng)
|
@@ -885,6 +898,6 @@ module Geokit
|
|
885
898
|
# If we get here, we failed completely.
|
886
899
|
GeoLoc.new
|
887
900
|
end
|
888
|
-
end
|
901
|
+
end
|
889
902
|
end
|
890
903
|
end
|