geokit 1.6.5 → 1.6.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +9 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +92 -0
- data/Gemfile +4 -0
- data/LICENSE +25 -0
- data/Manifest.txt +21 -0
- data/README.markdown +179 -176
- data/Rakefile +8 -1
- data/ext/mkrf_conf.rb +15 -0
- data/geokit.gemspec +33 -0
- data/lib/geokit/geocoders.rb +7 -774
- data/lib/geokit/inflectors.rb +38 -0
- data/lib/geokit/mappable.rb +61 -3
- data/lib/geokit/multi_geocoder.rb +61 -0
- data/lib/geokit/services/ca_geocoder.rb +55 -0
- data/lib/geokit/services/fcc.rb +57 -0
- data/lib/geokit/services/geo_plugin.rb +31 -0
- data/lib/geokit/services/geonames.rb +53 -0
- data/lib/geokit/services/google.rb +158 -0
- data/lib/geokit/services/google3.rb +202 -0
- data/lib/geokit/services/ip.rb +103 -0
- data/lib/geokit/services/openstreetmap.rb +119 -0
- data/lib/geokit/services/us_geocoder.rb +50 -0
- data/lib/geokit/services/yahoo.rb +75 -0
- data/lib/geokit/version.rb +3 -0
- data/test/helper.rb +92 -0
- data/test/test_base_geocoder.rb +1 -15
- data/test/test_bounds.rb +1 -2
- data/test/test_ca_geocoder.rb +1 -1
- data/test/test_geoloc.rb +35 -5
- data/test/test_geoplugin_geocoder.rb +1 -2
- data/test/test_google_geocoder.rb +39 -2
- data/test/test_google_geocoder3.rb +55 -3
- data/test/test_google_reverse_geocoder.rb +1 -1
- data/test/test_inflector.rb +5 -3
- data/test/test_ipgeocoder.rb +25 -1
- data/test/test_latlng.rb +1 -3
- data/test/test_multi_geocoder.rb +1 -1
- data/test/test_multi_ip_geocoder.rb +1 -1
- data/test/test_openstreetmap_geocoder.rb +161 -0
- data/test/test_polygon_contains.rb +101 -0
- data/test/test_us_geocoder.rb +1 -1
- data/test/test_yahoo_geocoder.rb +18 -1
- metadata +164 -83
data/Rakefile
CHANGED
@@ -1,10 +1,17 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require 'rake/testtask'
|
3
3
|
|
4
|
-
task :default
|
4
|
+
task :default do
|
5
|
+
end
|
5
6
|
|
6
7
|
Rake::TestTask.new do |t|
|
7
8
|
t.libs << "test"
|
8
9
|
t.test_files = FileList['test/test*.rb']
|
9
10
|
t.verbose = true
|
10
11
|
end
|
12
|
+
|
13
|
+
desc "Generate SimpleCov test coverage and open in your browser"
|
14
|
+
task :coverage do
|
15
|
+
ENV['COVERAGE'] = 'true'
|
16
|
+
Rake::Task['test'].invoke
|
17
|
+
end
|
data/ext/mkrf_conf.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rubygems/command.rb'
|
3
|
+
require 'rubygems/dependency_installer.rb'
|
4
|
+
begin
|
5
|
+
Gem::Command.build_args = ARGV
|
6
|
+
rescue NoMethodError
|
7
|
+
end
|
8
|
+
inst = Gem::DependencyInstaller.new
|
9
|
+
begin
|
10
|
+
if RUBY_VERSION < "1.9"
|
11
|
+
inst.install "iconv"
|
12
|
+
end
|
13
|
+
rescue
|
14
|
+
exit(1)
|
15
|
+
end
|
data/geokit.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'geokit/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "geokit"
|
8
|
+
spec.version = Geokit::VERSION
|
9
|
+
spec.authors = ["Michael Noack, James Cox, Andre Lewis & Bill Eisenhauer"]
|
10
|
+
spec.email = ["michael+geokit@noack.com.au"]
|
11
|
+
spec.description = %q{Geokit provides geocoding and distance calculation in an easy-to-use API}
|
12
|
+
spec.summary = %q{Geokit: encoding and distance calculation gem}
|
13
|
+
spec.homepage = "http://github.com/geokit/geokit"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.has_rdoc = true
|
17
|
+
spec.rdoc_options = ["--main", "README.markdown"]
|
18
|
+
spec.extra_rdoc_files = ["README.markdown"]
|
19
|
+
spec.extensions = 'ext/mkrf_conf.rb'
|
20
|
+
|
21
|
+
spec.files = `git ls-files`.split($/)
|
22
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
23
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
24
|
+
spec.require_paths = ["lib"]
|
25
|
+
|
26
|
+
spec.add_dependency "multi_json", ">= 1.3.2"
|
27
|
+
spec.add_development_dependency "bundler", "> 1.0"
|
28
|
+
spec.add_development_dependency 'simplecov'
|
29
|
+
spec.add_development_dependency "simplecov-rcov"
|
30
|
+
spec.add_development_dependency 'rake'
|
31
|
+
spec.add_development_dependency 'mocha'
|
32
|
+
spec.add_development_dependency 'coveralls'
|
33
|
+
end
|
data/lib/geokit/geocoders.rb
CHANGED
@@ -8,46 +8,10 @@ require 'logger'
|
|
8
8
|
require 'multi_json'
|
9
9
|
|
10
10
|
module Geokit
|
11
|
+
require File.join(File.dirname(__FILE__), 'inflectors')
|
11
12
|
|
12
13
|
class TooManyQueriesError < StandardError; end
|
13
14
|
|
14
|
-
module Inflector
|
15
|
-
|
16
|
-
extend self
|
17
|
-
|
18
|
-
def titleize(word)
|
19
|
-
humanize(underscore(word)).gsub(/\b([a-z])/u) { $1.capitalize }
|
20
|
-
end
|
21
|
-
|
22
|
-
def underscore(camel_cased_word)
|
23
|
-
camel_cased_word.to_s.gsub(/::/, '/').
|
24
|
-
gsub(/([A-Z]+)([A-Z][a-z])/u,'\1_\2').
|
25
|
-
gsub(/([a-z\d])([A-Z])/u,'\1_\2').
|
26
|
-
tr("-", "_").
|
27
|
-
downcase
|
28
|
-
end
|
29
|
-
|
30
|
-
def humanize(lower_case_and_underscored_word)
|
31
|
-
lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
|
32
|
-
end
|
33
|
-
|
34
|
-
def snake_case(s)
|
35
|
-
return s.downcase if s =~ /^[A-Z]+$/u
|
36
|
-
s.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/u, '_\&') =~ /_*(.*)/
|
37
|
-
return $+.downcase
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
def url_escape(s)
|
42
|
-
s.gsub(/([^ a-zA-Z0-9_.-]+)/nu) do
|
43
|
-
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
44
|
-
end.tr(' ', '+')
|
45
|
-
end
|
46
|
-
|
47
|
-
def camelize(str)
|
48
|
-
str.split('_').map {|w| w.capitalize}.join
|
49
|
-
end
|
50
|
-
end
|
51
15
|
|
52
16
|
# Contains a range of geocoders:
|
53
17
|
#
|
@@ -76,6 +40,9 @@ module Geokit
|
|
76
40
|
@@request_timeout = nil
|
77
41
|
@@yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
|
78
42
|
@@google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
|
43
|
+
@@google_client_id = nil
|
44
|
+
@@google_cryptographic_key = nil
|
45
|
+
@@google_channel = nil
|
79
46
|
@@geocoder_us = false
|
80
47
|
@@geocoder_ca = false
|
81
48
|
@@geonames = false
|
@@ -84,6 +51,7 @@ module Geokit
|
|
84
51
|
@@logger=Logger.new(STDOUT)
|
85
52
|
@@logger.level=Logger::INFO
|
86
53
|
@@domain = nil
|
54
|
+
@@osm = 'REPLACE_WITH_YOUR_OSM_KEY' #if needed
|
87
55
|
|
88
56
|
def self.__define_accessors
|
89
57
|
class_variables.each do |v|
|
@@ -188,743 +156,8 @@ module Geokit
|
|
188
156
|
# -------------------------------------------------------------------------------------------
|
189
157
|
# "Regular" Address geocoders
|
190
158
|
# -------------------------------------------------------------------------------------------
|
159
|
+
Dir[File.join(File.dirname(__FILE__), "/services/*.rb")].each {|f| require f}
|
191
160
|
|
192
|
-
|
193
|
-
# contain true or false based upon whether authentication is to occur. Conforms to the
|
194
|
-
# interface set by the Geocoder class.
|
195
|
-
#
|
196
|
-
# Returns a response like:
|
197
|
-
# <?xml version="1.0" encoding="UTF-8" ?>
|
198
|
-
# <geodata>
|
199
|
-
# <latt>49.243086</latt>
|
200
|
-
# <longt>-123.153684</longt>
|
201
|
-
# </geodata>
|
202
|
-
class CaGeocoder < Geocoder
|
203
|
-
|
204
|
-
private
|
205
|
-
|
206
|
-
# Template method which does the geocode lookup.
|
207
|
-
def self.do_geocode(address, options = {})
|
208
|
-
raise ArgumentError('Geocoder.ca requires a GeoLoc argument') unless address.is_a?(GeoLoc)
|
209
|
-
url = construct_request(address)
|
210
|
-
res = self.call_geocoder_service(url)
|
211
|
-
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
212
|
-
xml = res.body
|
213
|
-
logger.debug "Geocoder.ca geocoding. Address: #{address}. Result: #{xml}"
|
214
|
-
# Parse the document.
|
215
|
-
doc = REXML::Document.new(xml)
|
216
|
-
address.lat = doc.elements['//latt'].text
|
217
|
-
address.lng = doc.elements['//longt'].text
|
218
|
-
address.success = true
|
219
|
-
return address
|
220
|
-
rescue
|
221
|
-
logger.error "Caught an error during Geocoder.ca geocoding call: "+$!
|
222
|
-
return GeoLoc.new
|
223
|
-
end
|
224
|
-
|
225
|
-
# Formats the request in the format acceptable by the CA geocoder.
|
226
|
-
def self.construct_request(location)
|
227
|
-
url = ""
|
228
|
-
url += add_ampersand(url) + "stno=#{location.street_number}" if location.street_address
|
229
|
-
url += add_ampersand(url) + "addresst=#{Geokit::Inflector::url_escape(location.street_name)}" if location.street_address
|
230
|
-
url += add_ampersand(url) + "city=#{Geokit::Inflector::url_escape(location.city)}" if location.city
|
231
|
-
url += add_ampersand(url) + "prov=#{location.state}" if location.state
|
232
|
-
url += add_ampersand(url) + "postal=#{location.zip}" if location.zip
|
233
|
-
url += add_ampersand(url) + "auth=#{Geokit::Geocoders::geocoder_ca}" if Geokit::Geocoders::geocoder_ca
|
234
|
-
url += add_ampersand(url) + "geoit=xml"
|
235
|
-
'http://geocoder.ca/?' + url
|
236
|
-
end
|
237
|
-
|
238
|
-
def self.add_ampersand(url)
|
239
|
-
url && url.length > 0 ? "&" : ""
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
|
-
# Geocoder Us geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_US variable to
|
244
|
-
# contain true or false based upon whether authentication is to occur. Conforms to the
|
245
|
-
# interface set by the Geocoder class.
|
246
|
-
class UsGeocoder < Geocoder
|
247
|
-
|
248
|
-
private
|
249
|
-
def self.do_geocode(address, options = {})
|
250
|
-
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
251
|
-
|
252
|
-
query = (address_str =~ /^\d{5}(?:-\d{4})?$/ ? "zip" : "address") + "=#{Geokit::Inflector::url_escape(address_str)}"
|
253
|
-
url = if GeoKit::Geocoders::geocoder_us
|
254
|
-
"http://#{GeoKit::Geocoders::geocoder_us}@geocoder.us/member/service/csv/geocode"
|
255
|
-
else
|
256
|
-
"http://geocoder.us/service/csv/geocode"
|
257
|
-
end
|
258
|
-
|
259
|
-
url = "#{url}?#{query}"
|
260
|
-
res = self.call_geocoder_service(url)
|
261
|
-
|
262
|
-
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
263
|
-
data = res.body
|
264
|
-
logger.debug "Geocoder.us geocoding. Address: #{address}. Result: #{data}"
|
265
|
-
array = data.chomp.split(',')
|
266
|
-
|
267
|
-
if array.length == 5
|
268
|
-
res=GeoLoc.new
|
269
|
-
res.lat,res.lng,res.city,res.state,res.zip=array
|
270
|
-
res.country_code='US'
|
271
|
-
res.success=true
|
272
|
-
return res
|
273
|
-
elsif array.length == 6
|
274
|
-
res=GeoLoc.new
|
275
|
-
res.lat,res.lng,res.street_address,res.city,res.state,res.zip=array
|
276
|
-
res.country_code='US'
|
277
|
-
res.success=true
|
278
|
-
return res
|
279
|
-
else
|
280
|
-
logger.info "geocoder.us was unable to geocode address: "+address
|
281
|
-
return GeoLoc.new
|
282
|
-
end
|
283
|
-
rescue
|
284
|
-
logger.error "Caught an error during geocoder.us geocoding call: "+$!
|
285
|
-
return GeoLoc.new
|
286
|
-
|
287
|
-
end
|
288
|
-
end
|
289
|
-
|
290
|
-
# Yahoo geocoder implementation. Requires the Geokit::Geocoders::YAHOO variable to
|
291
|
-
# contain a Yahoo API key. Conforms to the interface set by the Geocoder class.
|
292
|
-
class YahooGeocoder < Geocoder
|
293
|
-
|
294
|
-
private
|
295
|
-
|
296
|
-
# Template method which does the geocode lookup.
|
297
|
-
def self.do_geocode(address, options = {})
|
298
|
-
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
299
|
-
url="http://where.yahooapis.com/geocode?flags=J&appid=#{Geokit::Geocoders::yahoo}&q=#{Geokit::Inflector::url_escape(address_str)}"
|
300
|
-
res = self.call_geocoder_service(url)
|
301
|
-
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
302
|
-
json = res.body
|
303
|
-
logger.debug "Yahoo geocoding. Address: #{address}. Result: #{json}"
|
304
|
-
return self.json2GeoLoc(json, address)
|
305
|
-
end
|
306
|
-
|
307
|
-
def self.json2GeoLoc(json, address)
|
308
|
-
results = MultiJson.decode(json)
|
309
|
-
|
310
|
-
if results['ResultSet']['Error'] == 0
|
311
|
-
geoloc = nil
|
312
|
-
results['ResultSet']['Results'].each do |result|
|
313
|
-
extracted_geoloc = extract_geoloc(result)
|
314
|
-
if geoloc.nil?
|
315
|
-
geoloc = extracted_geoloc
|
316
|
-
else
|
317
|
-
geoloc.all.push(extracted_geoloc)
|
318
|
-
end
|
319
|
-
end
|
320
|
-
return geoloc
|
321
|
-
else
|
322
|
-
logger.info "Yahoo was unable to geocode address: " + address
|
323
|
-
return GeoLoc.new
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
def self.extract_geoloc(result_json)
|
328
|
-
geoloc = GeoLoc.new
|
329
|
-
|
330
|
-
# basic
|
331
|
-
geoloc.lat = result_json['latitude']
|
332
|
-
geoloc.lng = result_json['longitude']
|
333
|
-
geoloc.country_code = result_json['countrycode']
|
334
|
-
geoloc.provider = 'yahoo'
|
335
|
-
|
336
|
-
# extended
|
337
|
-
geoloc.street_address = result_json['line1'].to_s.empty? ? nil : result_json['line1']
|
338
|
-
geoloc.city = result_json['city']
|
339
|
-
geoloc.state = geoloc.is_us? ? result_json['statecode'] : result_json['state']
|
340
|
-
geoloc.zip = result_json['postal']
|
341
|
-
|
342
|
-
geoloc.precision = case result_json['quality']
|
343
|
-
when 9,10 then 'country'
|
344
|
-
when 19..30 then 'state'
|
345
|
-
when 39,40 then 'city'
|
346
|
-
when 49,50 then 'neighborhood'
|
347
|
-
when 59,60,64 then 'zip'
|
348
|
-
when 74,75 then 'zip+4'
|
349
|
-
when 70..72 then 'street'
|
350
|
-
when 80..87 then 'address'
|
351
|
-
when 62,63,90,99 then 'building'
|
352
|
-
else 'unknown'
|
353
|
-
end
|
354
|
-
|
355
|
-
geoloc.accuracy = %w{unknown country state state city zip zip+4 street address building}.index(geoloc.precision)
|
356
|
-
geoloc.success = true
|
357
|
-
|
358
|
-
return geoloc
|
359
|
-
end
|
360
|
-
end
|
361
|
-
|
362
|
-
# Another geocoding web service
|
363
|
-
# http://www.geonames.org
|
364
|
-
class GeonamesGeocoder < Geocoder
|
365
|
-
|
366
|
-
private
|
367
|
-
|
368
|
-
# Template method which does the geocode lookup.
|
369
|
-
def self.do_geocode(address, options = {})
|
370
|
-
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
371
|
-
# geonames need a space seperated search string
|
372
|
-
address_str.gsub!(/,/, " ")
|
373
|
-
params = "/postalCodeSearch?placename=#{Geokit::Inflector::url_escape(address_str)}&maxRows=10"
|
374
|
-
|
375
|
-
if(GeoKit::Geocoders::geonames)
|
376
|
-
url = "http://ws.geonames.net#{params}&username=#{GeoKit::Geocoders::geonames}"
|
377
|
-
else
|
378
|
-
url = "http://ws.geonames.org#{params}"
|
379
|
-
end
|
380
|
-
|
381
|
-
res = self.call_geocoder_service(url)
|
382
|
-
|
383
|
-
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
384
|
-
|
385
|
-
xml=res.body
|
386
|
-
logger.debug "Geonames geocoding. Address: #{address}. Result: #{xml}"
|
387
|
-
doc=REXML::Document.new(xml)
|
388
|
-
|
389
|
-
if(doc.elements['//geonames/totalResultsCount'].text.to_i > 0)
|
390
|
-
res=GeoLoc.new
|
391
|
-
|
392
|
-
# only take the first result
|
393
|
-
res.lat=doc.elements['//code/lat'].text if doc.elements['//code/lat']
|
394
|
-
res.lng=doc.elements['//code/lng'].text if doc.elements['//code/lng']
|
395
|
-
res.country_code=doc.elements['//code/countryCode'].text if doc.elements['//code/countryCode']
|
396
|
-
res.provider='genomes'
|
397
|
-
res.city=doc.elements['//code/name'].text if doc.elements['//code/name']
|
398
|
-
res.state=doc.elements['//code/adminName1'].text if doc.elements['//code/adminName1']
|
399
|
-
res.zip=doc.elements['//code/postalcode'].text if doc.elements['//code/postalcode']
|
400
|
-
res.success=true
|
401
|
-
return res
|
402
|
-
else
|
403
|
-
logger.info "Geonames was unable to geocode address: "+address
|
404
|
-
return GeoLoc.new
|
405
|
-
end
|
406
|
-
|
407
|
-
rescue
|
408
|
-
logger.error "Caught an error during Geonames geocoding call: "+$!
|
409
|
-
end
|
410
|
-
end
|
411
|
-
|
412
|
-
# -------------------------------------------------------------------------------------------
|
413
|
-
# Address geocoders that also provide reverse geocoding
|
414
|
-
# -------------------------------------------------------------------------------------------
|
415
|
-
|
416
|
-
# Google geocoder implementation. Requires the Geokit::Geocoders::GOOGLE variable to
|
417
|
-
# contain a Google API key. Conforms to the interface set by the Geocoder class.
|
418
|
-
class GoogleGeocoder < Geocoder
|
419
|
-
|
420
|
-
private
|
421
|
-
|
422
|
-
# Template method which does the reverse-geocode lookup.
|
423
|
-
def self.do_reverse_geocode(latlng)
|
424
|
-
latlng=LatLng.normalize(latlng)
|
425
|
-
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")
|
426
|
-
# 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"))
|
427
|
-
return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
|
428
|
-
xml = res.body
|
429
|
-
logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{xml}"
|
430
|
-
return self.xml2GeoLoc(xml)
|
431
|
-
end
|
432
|
-
|
433
|
-
# Template method which does the geocode lookup.
|
434
|
-
#
|
435
|
-
# Supports viewport/country code biasing
|
436
|
-
#
|
437
|
-
# ==== OPTIONS
|
438
|
-
# * :bias - This option makes the Google Geocoder return results biased to a particular
|
439
|
-
# country or viewport. Country code biasing is achieved by passing the ccTLD
|
440
|
-
# ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
|
441
|
-
# look here: http://en.wikipedia.org/wiki/CcTLD. By default, the geocoder
|
442
|
-
# will be biased to results within the US (ccTLD .com).
|
443
|
-
#
|
444
|
-
# If you'd like the Google Geocoder to prefer results within a given viewport,
|
445
|
-
# you can pass a Geokit::Bounds object as the :bias value.
|
446
|
-
#
|
447
|
-
# ==== EXAMPLES
|
448
|
-
# # By default, the geocoder will return Syracuse, NY
|
449
|
-
# Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse').country_code # => 'US'
|
450
|
-
# # With country code biasing, it returns Syracuse in Sicily, Italy
|
451
|
-
# Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse', :bias => :it).country_code # => 'IT'
|
452
|
-
#
|
453
|
-
# # By default, the geocoder will return Winnetka, IL
|
454
|
-
# Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka').state # => 'IL'
|
455
|
-
# # When biased to an bounding box around California, it will now return the Winnetka neighbourhood, CA
|
456
|
-
# bounds = Geokit::Bounds.normalize([34.074081, -118.694401], [34.321129, -118.399487])
|
457
|
-
# Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka', :bias => bounds).state # => 'CA'
|
458
|
-
def self.do_geocode(address, options = {})
|
459
|
-
bias_str = options[:bias] ? construct_bias_string_from_options(options[:bias]) : ''
|
460
|
-
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
461
|
-
res = self.call_geocoder_service("http://maps.google.com/maps/geo?q=#{Geokit::Inflector::url_escape(address_str)}&output=xml#{bias_str}&key=#{Geokit::Geocoders::google}&oe=utf-8")
|
462
|
-
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
463
|
-
xml = res.body
|
464
|
-
logger.debug "Google geocoding. Address: #{address}. Result: #{xml}"
|
465
|
-
return self.xml2GeoLoc(xml, address)
|
466
|
-
end
|
467
|
-
|
468
|
-
def self.construct_bias_string_from_options(bias)
|
469
|
-
if bias.is_a?(String) or bias.is_a?(Symbol)
|
470
|
-
# country code biasing
|
471
|
-
"&gl=#{bias.to_s.downcase}"
|
472
|
-
elsif bias.is_a?(Bounds)
|
473
|
-
# viewport biasing
|
474
|
-
"&ll=#{bias.center.ll}&spn=#{bias.to_span.ll}"
|
475
|
-
end
|
476
|
-
end
|
477
|
-
|
478
|
-
def self.xml2GeoLoc(xml, address="")
|
479
|
-
doc=REXML::Document.new(xml)
|
480
|
-
|
481
|
-
if doc.elements['//kml/Response/Status/code'].text == '200'
|
482
|
-
geoloc = nil
|
483
|
-
# Google can return multiple results as //Placemark elements.
|
484
|
-
# iterate through each and extract each placemark as a geoloc
|
485
|
-
doc.each_element('//Placemark') do |e|
|
486
|
-
extracted_geoloc = extract_placemark(e) # g is now an instance of GeoLoc
|
487
|
-
if geoloc.nil?
|
488
|
-
# first time through, geoloc is still nil, so we make it the geoloc we just extracted
|
489
|
-
geoloc = extracted_geoloc
|
490
|
-
else
|
491
|
-
# second (and subsequent) iterations, we push additional
|
492
|
-
# geolocs onto "geoloc.all"
|
493
|
-
geoloc.all.push(extracted_geoloc)
|
494
|
-
end
|
495
|
-
end
|
496
|
-
return geoloc
|
497
|
-
elsif doc.elements['//kml/Response/Status/code'].text == '620'
|
498
|
-
raise Geokit::TooManyQueriesError
|
499
|
-
else
|
500
|
-
logger.info "Google was unable to geocode address: "+address
|
501
|
-
return GeoLoc.new
|
502
|
-
end
|
503
|
-
|
504
|
-
rescue Geokit::TooManyQueriesError
|
505
|
-
# re-raise because of other rescue
|
506
|
-
raise Geokit::TooManyQueriesError, "Google returned a 620 status, too many queries. The given key has gone over the requests limit in the 24 hour period or has submitted too many requests in too short a period of time. If you're sending multiple requests in parallel or in a tight loop, use a timer or pause in your code to make sure you don't send the requests too quickly."
|
507
|
-
rescue
|
508
|
-
logger.error "Caught an error during Google geocoding call: "+$!
|
509
|
-
return GeoLoc.new
|
510
|
-
end
|
511
|
-
|
512
|
-
# extracts a single geoloc from a //placemark element in the google results xml
|
513
|
-
def self.extract_placemark(doc)
|
514
|
-
res = GeoLoc.new
|
515
|
-
coordinates=doc.elements['.//coordinates'].text.to_s.split(',')
|
516
|
-
|
517
|
-
#basics
|
518
|
-
res.lat=coordinates[1]
|
519
|
-
res.lng=coordinates[0]
|
520
|
-
res.country_code=doc.elements['.//CountryNameCode'].text if doc.elements['.//CountryNameCode']
|
521
|
-
res.provider='google'
|
522
|
-
|
523
|
-
#extended -- false if not not available
|
524
|
-
res.city = doc.elements['.//LocalityName'].text if doc.elements['.//LocalityName']
|
525
|
-
res.state = doc.elements['.//AdministrativeAreaName'].text if doc.elements['.//AdministrativeAreaName']
|
526
|
-
res.province = doc.elements['.//SubAdministrativeAreaName'].text if doc.elements['.//SubAdministrativeAreaName']
|
527
|
-
res.full_address = doc.elements['.//address'].text if doc.elements['.//address'] # google provides it
|
528
|
-
res.zip = doc.elements['.//PostalCodeNumber'].text if doc.elements['.//PostalCodeNumber']
|
529
|
-
res.street_address = doc.elements['.//ThoroughfareName'].text if doc.elements['.//ThoroughfareName']
|
530
|
-
res.country = doc.elements['.//CountryName'].text if doc.elements['.//CountryName']
|
531
|
-
res.district = doc.elements['.//DependentLocalityName'].text if doc.elements['.//DependentLocalityName']
|
532
|
-
# Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
|
533
|
-
# For Google, 1=low accuracy, 8=high accuracy
|
534
|
-
address_details=doc.elements['.//*[local-name() = "AddressDetails"]']
|
535
|
-
res.accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
|
536
|
-
res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy]
|
537
|
-
|
538
|
-
# google returns a set of suggested boundaries for the geocoded result
|
539
|
-
if suggested_bounds = doc.elements['//LatLonBox']
|
540
|
-
res.suggested_bounds = Bounds.normalize(
|
541
|
-
[suggested_bounds.attributes['south'], suggested_bounds.attributes['west']],
|
542
|
-
[suggested_bounds.attributes['north'], suggested_bounds.attributes['east']])
|
543
|
-
end
|
544
|
-
|
545
|
-
res.success=true
|
546
|
-
|
547
|
-
return res
|
548
|
-
end
|
549
|
-
end
|
550
|
-
|
551
|
-
class GoogleGeocoder3 < Geocoder
|
552
|
-
|
553
|
-
private
|
554
|
-
# Template method which does the reverse-geocode lookup.
|
555
|
-
def self.do_reverse_geocode(latlng)
|
556
|
-
latlng=LatLng.normalize(latlng)
|
557
|
-
res = self.call_geocoder_service("http://maps.google.com/maps/api/geocode/json?sensor=false&latlng=#{Geokit::Inflector::url_escape(latlng.ll)}")
|
558
|
-
return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
|
559
|
-
json = res.body
|
560
|
-
logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{json}"
|
561
|
-
return self.json2GeoLoc(json)
|
562
|
-
end
|
563
|
-
|
564
|
-
# Template method which does the geocode lookup.
|
565
|
-
#
|
566
|
-
# Supports viewport/country code biasing
|
567
|
-
#
|
568
|
-
# ==== OPTIONS
|
569
|
-
# * :bias - This option makes the Google Geocoder return results biased to a particular
|
570
|
-
# country or viewport. Country code biasing is achieved by passing the ccTLD
|
571
|
-
# ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
|
572
|
-
# look here: http://en.wikipedia.org/wiki/CcTLD. By default, the geocoder
|
573
|
-
# will be biased to results within the US (ccTLD .com).
|
574
|
-
#
|
575
|
-
# If you'd like the Google Geocoder to prefer results within a given viewport,
|
576
|
-
# you can pass a Geokit::Bounds object as the :bias value.
|
577
|
-
#
|
578
|
-
# ==== EXAMPLES
|
579
|
-
# # By default, the geocoder will return Syracuse, NY
|
580
|
-
# Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse').country_code # => 'US'
|
581
|
-
# # With country code biasing, it returns Syracuse in Sicily, Italy
|
582
|
-
# Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse', :bias => :it).country_code # => 'IT'
|
583
|
-
#
|
584
|
-
# # By default, the geocoder will return Winnetka, IL
|
585
|
-
# Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka').state # => 'IL'
|
586
|
-
# # When biased to an bounding box around California, it will now return the Winnetka neighbourhood, CA
|
587
|
-
# bounds = Geokit::Bounds.normalize([34.074081, -118.694401], [34.321129, -118.399487])
|
588
|
-
# Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka', :bias => bounds).state # => 'CA'
|
589
|
-
def self.do_geocode(address, options = {})
|
590
|
-
bias_str = options[:bias] ? construct_bias_string_from_options(options[:bias]) : ''
|
591
|
-
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
592
|
-
|
593
|
-
res = self.call_geocoder_service("http://maps.google.com/maps/api/geocode/json?sensor=false&address=#{Geokit::Inflector::url_escape(address_str)}#{bias_str}")
|
594
|
-
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
595
|
-
|
596
|
-
json = res.body
|
597
|
-
logger.debug "Google geocoding. Address: #{address}. Result: #{json}"
|
598
|
-
|
599
|
-
return self.json2GeoLoc(json, address)
|
600
|
-
end
|
601
|
-
|
602
|
-
def self.construct_bias_string_from_options(bias)
|
603
|
-
if bias.is_a?(String) or bias.is_a?(Symbol)
|
604
|
-
# country code biasing
|
605
|
-
"®ion=#{bias.to_s.downcase}"
|
606
|
-
elsif bias.is_a?(Bounds)
|
607
|
-
# viewport biasing
|
608
|
-
Geokit::Inflector::url_escape("&bounds=#{bias.sw.to_s}|#{bias.ne.to_s}")
|
609
|
-
end
|
610
|
-
end
|
611
|
-
|
612
|
-
def self.json2GeoLoc(json, address="")
|
613
|
-
ret=nil
|
614
|
-
results = MultiJson.decode(json)
|
615
|
-
|
616
|
-
if results['status'] == 'OVER_QUERY_LIMIT'
|
617
|
-
raise Geokit::TooManyQueriesError
|
618
|
-
end
|
619
|
-
if results['status'] == 'ZERO_RESULTS'
|
620
|
-
return GeoLoc.new
|
621
|
-
end
|
622
|
-
# this should probably be smarter.
|
623
|
-
if !results['status'] == 'OK'
|
624
|
-
raise Geokit::Geocoders::GeocodeError
|
625
|
-
end
|
626
|
-
# location_type stores additional data about the specified location.
|
627
|
-
# The following values are currently supported:
|
628
|
-
# "ROOFTOP" indicates that the returned result is a precise geocode
|
629
|
-
# for which we have location information accurate down to street
|
630
|
-
# address precision.
|
631
|
-
# "RANGE_INTERPOLATED" indicates that the returned result reflects an
|
632
|
-
# approximation (usually on a road) interpolated between two precise
|
633
|
-
# points (such as intersections). Interpolated results are generally
|
634
|
-
# returned when rooftop geocodes are unavailable for a street address.
|
635
|
-
# "GEOMETRIC_CENTER" indicates that the returned result is the
|
636
|
-
# geometric center of a result such as a polyline (for example, a
|
637
|
-
# street) or polygon (region).
|
638
|
-
# "APPROXIMATE" indicates that the returned result is approximate
|
639
|
-
|
640
|
-
# these do not map well. Perhaps we should guess better based on size
|
641
|
-
# of bounding box where it exists? Does it really matter?
|
642
|
-
accuracy = {
|
643
|
-
"ROOFTOP" => 9,
|
644
|
-
"RANGE_INTERPOLATED" => 8,
|
645
|
-
"GEOMETRIC_CENTER" => 5,
|
646
|
-
"APPROXIMATE" => 4
|
647
|
-
}
|
648
|
-
|
649
|
-
@unsorted = []
|
650
|
-
|
651
|
-
results['results'].each do |addr|
|
652
|
-
res = GeoLoc.new
|
653
|
-
res.provider = 'google3'
|
654
|
-
res.success = true
|
655
|
-
res.full_address = addr['formatted_address']
|
656
|
-
|
657
|
-
addr['address_components'].each do |comp|
|
658
|
-
case
|
659
|
-
when comp['types'].include?("subpremise")
|
660
|
-
res.sub_premise = comp['short_name']
|
661
|
-
when comp['types'].include?("street_number")
|
662
|
-
res.street_number = comp['short_name']
|
663
|
-
when comp['types'].include?("route")
|
664
|
-
res.street_name = comp['long_name']
|
665
|
-
when comp['types'].include?("locality")
|
666
|
-
res.city = comp['long_name']
|
667
|
-
when comp['types'].include?("administrative_area_level_1")
|
668
|
-
res.state = comp['short_name']
|
669
|
-
res.province = comp['short_name']
|
670
|
-
when comp['types'].include?("postal_code")
|
671
|
-
res.zip = comp['long_name']
|
672
|
-
when comp['types'].include?("country")
|
673
|
-
res.country_code = comp['short_name']
|
674
|
-
res.country = comp['long_name']
|
675
|
-
when comp['types'].include?("administrative_area_level_2")
|
676
|
-
res.district = comp['long_name']
|
677
|
-
end
|
678
|
-
end
|
679
|
-
if res.street_name
|
680
|
-
res.street_address=[res.street_number,res.street_name].join(' ').strip
|
681
|
-
end
|
682
|
-
res.accuracy = accuracy[addr['geometry']['location_type']]
|
683
|
-
res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy]
|
684
|
-
# try a few overrides where we can
|
685
|
-
if res.sub_premise
|
686
|
-
res.accuracy = 9
|
687
|
-
res.precision = 'building'
|
688
|
-
end
|
689
|
-
if res.street_name && res.precision=='city'
|
690
|
-
res.precision = 'street'
|
691
|
-
res.accuracy = 7
|
692
|
-
end
|
693
|
-
|
694
|
-
res.lat=addr['geometry']['location']['lat'].to_f
|
695
|
-
res.lng=addr['geometry']['location']['lng'].to_f
|
696
|
-
|
697
|
-
ne=Geokit::LatLng.new(
|
698
|
-
addr['geometry']['viewport']['northeast']['lat'].to_f,
|
699
|
-
addr['geometry']['viewport']['northeast']['lng'].to_f
|
700
|
-
)
|
701
|
-
sw=Geokit::LatLng.new(
|
702
|
-
addr['geometry']['viewport']['southwest']['lat'].to_f,
|
703
|
-
addr['geometry']['viewport']['southwest']['lng'].to_f
|
704
|
-
)
|
705
|
-
res.suggested_bounds = Geokit::Bounds.new(sw,ne)
|
706
|
-
|
707
|
-
@unsorted << res
|
708
|
-
end
|
709
|
-
|
710
|
-
all = @unsorted.sort_by { |a| a.accuracy }.reverse
|
711
|
-
encoded = all.first
|
712
|
-
encoded.all = all
|
713
|
-
return encoded
|
714
|
-
end
|
715
|
-
end
|
716
|
-
|
717
|
-
class FCCGeocoder < Geocoder
|
718
|
-
|
719
|
-
private
|
720
|
-
# Template method which does the reverse-geocode lookup.
|
721
|
-
def self.do_reverse_geocode(latlng)
|
722
|
-
latlng=LatLng.normalize(latlng)
|
723
|
-
res = self.call_geocoder_service("http://data.fcc.gov/api/block/find?format=json&latitude=#{Geokit::Inflector::url_escape(latlng.lat.to_s)}&longitude=#{Geokit::Inflector::url_escape(latlng.lng.to_s)}")
|
724
|
-
return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
|
725
|
-
json = res.body
|
726
|
-
logger.debug "FCC reverse-geocoding. LL: #{latlng}. Result: #{json}"
|
727
|
-
return self.json2GeoLoc(json)
|
728
|
-
end
|
729
|
-
|
730
|
-
# Template method which does the geocode lookup.
|
731
|
-
#
|
732
|
-
# ==== EXAMPLES
|
733
|
-
# ll=GeoKit::LatLng.new(40, -85)
|
734
|
-
# Geokit::Geocoders::FCCGeocoder.geocode(ll) #
|
735
|
-
|
736
|
-
# JSON result looks like this
|
737
|
-
# => {"County"=>{"name"=>"Wayne", "FIPS"=>"18177"},
|
738
|
-
# "Block"=>{"FIPS"=>"181770103002004"},
|
739
|
-
# "executionTime"=>"0.099",
|
740
|
-
# "State"=>{"name"=>"Indiana", "code"=>"IN", "FIPS"=>"18"},
|
741
|
-
# "status"=>"OK"}
|
742
|
-
|
743
|
-
def self.json2GeoLoc(json, address="")
|
744
|
-
ret = nil
|
745
|
-
results = MultiJson.decode(json)
|
746
|
-
|
747
|
-
if results.has_key?('Err') and results['Err']["msg"] == 'There are no results for this location'
|
748
|
-
return GeoLoc.new
|
749
|
-
end
|
750
|
-
# this should probably be smarter.
|
751
|
-
if !results['status'] == 'OK'
|
752
|
-
raise Geokit::Geocoders::GeocodeError
|
753
|
-
end
|
754
|
-
|
755
|
-
res = GeoLoc.new
|
756
|
-
res.provider = 'fcc'
|
757
|
-
res.success = true
|
758
|
-
res.precision = 'block'
|
759
|
-
res.country_code = 'US'
|
760
|
-
res.district = results['County']['name']
|
761
|
-
res.district_fips = results['County']['FIPS']
|
762
|
-
res.state = results['State']['code']
|
763
|
-
res.state_fips = results['State']['FIPS']
|
764
|
-
res.block_fips = results['Block']['FIPS']
|
765
|
-
|
766
|
-
res
|
767
|
-
end
|
768
|
-
end
|
769
|
-
# -------------------------------------------------------------------------------------------
|
770
|
-
# IP Geocoders
|
771
|
-
# -------------------------------------------------------------------------------------------
|
772
|
-
|
773
|
-
# Provides geocoding based upon an IP address. The underlying web service is geoplugin.net
|
774
|
-
class GeoPluginGeocoder < Geocoder
|
775
|
-
private
|
776
|
-
|
777
|
-
def self.do_geocode(ip, options = {})
|
778
|
-
return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
|
779
|
-
response = self.call_geocoder_service("http://www.geoplugin.net/xml.gp?ip=#{ip}")
|
780
|
-
return response.is_a?(Net::HTTPSuccess) ? parse_xml(response.body) : GeoLoc.new
|
781
|
-
rescue
|
782
|
-
logger.error "Caught an error during GeoPluginGeocoder geocoding call: "+$!
|
783
|
-
return GeoLoc.new
|
784
|
-
end
|
785
|
-
|
786
|
-
def self.parse_xml(xml)
|
787
|
-
xml = REXML::Document.new(xml)
|
788
|
-
geo = GeoLoc.new
|
789
|
-
geo.provider='geoPlugin'
|
790
|
-
geo.city = xml.elements['//geoplugin_city'].text
|
791
|
-
geo.state = xml.elements['//geoplugin_region'].text
|
792
|
-
geo.country_code = xml.elements['//geoplugin_countryCode'].text
|
793
|
-
geo.lat = xml.elements['//geoplugin_latitude'].text.to_f
|
794
|
-
geo.lng = xml.elements['//geoplugin_longitude'].text.to_f
|
795
|
-
geo.success = !!geo.city && !geo.city.empty?
|
796
|
-
return geo
|
797
|
-
end
|
798
|
-
end
|
799
|
-
|
800
|
-
# Provides geocoding based upon an IP address. The underlying web service is a hostip.info
|
801
|
-
# which sources their data through a combination of publicly available information as well
|
802
|
-
# as community contributions.
|
803
|
-
class IpGeocoder < Geocoder
|
804
|
-
|
805
|
-
# A number of non-routable IP ranges.
|
806
|
-
#
|
807
|
-
# --
|
808
|
-
# Sources for these:
|
809
|
-
# RFC 3330: Special-Use IPv4 Addresses
|
810
|
-
# The bogon list: http://www.cymru.com/Documents/bogon-list.html
|
811
|
-
|
812
|
-
NON_ROUTABLE_IP_RANGES = [
|
813
|
-
IPAddr.new('0.0.0.0/8'), # "This" Network
|
814
|
-
IPAddr.new('10.0.0.0/8'), # Private-Use Networks
|
815
|
-
IPAddr.new('14.0.0.0/8'), # Public-Data Networks
|
816
|
-
IPAddr.new('127.0.0.0/8'), # Loopback
|
817
|
-
IPAddr.new('169.254.0.0/16'), # Link local
|
818
|
-
IPAddr.new('172.16.0.0/12'), # Private-Use Networks
|
819
|
-
IPAddr.new('192.0.2.0/24'), # Test-Net
|
820
|
-
IPAddr.new('192.168.0.0/16'), # Private-Use Networks
|
821
|
-
IPAddr.new('198.18.0.0/15'), # Network Interconnect Device Benchmark Testing
|
822
|
-
IPAddr.new('224.0.0.0/4'), # Multicast
|
823
|
-
IPAddr.new('240.0.0.0/4') # Reserved for future use
|
824
|
-
].freeze
|
825
|
-
|
826
|
-
private
|
827
|
-
|
828
|
-
# Given an IP address, returns a GeoLoc instance which contains latitude,
|
829
|
-
# longitude, city, and country code. Sets the success attribute to false if the ip
|
830
|
-
# parameter does not match an ip address.
|
831
|
-
def self.do_geocode(ip, options = {})
|
832
|
-
return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
|
833
|
-
return GeoLoc.new if self.private_ip_address?(ip)
|
834
|
-
url = "http://api.hostip.info/get_html.php?ip=#{ip}&position=true"
|
835
|
-
response = self.call_geocoder_service(url)
|
836
|
-
response.is_a?(Net::HTTPSuccess) ? parse_body(response.body) : GeoLoc.new
|
837
|
-
rescue
|
838
|
-
logger.error "Caught an error during HostIp geocoding call: "+$!
|
839
|
-
return GeoLoc.new
|
840
|
-
end
|
841
|
-
|
842
|
-
# Converts the body to YAML since its in the form of:
|
843
|
-
#
|
844
|
-
# Country: UNITED STATES (US)
|
845
|
-
# City: Sugar Grove, IL
|
846
|
-
# Latitude: 41.7696
|
847
|
-
# Longitude: -88.4588
|
848
|
-
#
|
849
|
-
# then instantiates a GeoLoc instance to populate with location data.
|
850
|
-
def self.parse_body(body) # :nodoc:
|
851
|
-
yaml = YAML.load(body)
|
852
|
-
res = GeoLoc.new
|
853
|
-
res.provider = 'hostip'
|
854
|
-
res.city, res.state = yaml['City'].split(', ')
|
855
|
-
country, res.country_code = yaml['Country'].split(' (')
|
856
|
-
res.lat = yaml['Latitude']
|
857
|
-
res.lng = yaml['Longitude']
|
858
|
-
res.country_code.chop!
|
859
|
-
res.success = !(res.city =~ /\(.+\)/)
|
860
|
-
res
|
861
|
-
end
|
862
|
-
|
863
|
-
# Checks whether the IP address belongs to a private address range.
|
864
|
-
#
|
865
|
-
# This function is used to reduce the number of useless queries made to
|
866
|
-
# the geocoding service. Such queries can occur frequently during
|
867
|
-
# integration tests.
|
868
|
-
def self.private_ip_address?(ip)
|
869
|
-
return NON_ROUTABLE_IP_RANGES.any? { |range| range.include?(ip) }
|
870
|
-
end
|
871
|
-
end
|
872
|
-
|
873
|
-
# -------------------------------------------------------------------------------------------
|
874
|
-
# The Multi Geocoder
|
875
|
-
# -------------------------------------------------------------------------------------------
|
876
|
-
|
877
|
-
# Provides methods to geocode with a variety of geocoding service providers, plus failover
|
878
|
-
# among providers in the order you configure. When 2nd parameter is set 'true', perform
|
879
|
-
# ip location lookup with 'address' as the ip address.
|
880
|
-
#
|
881
|
-
# Goal:
|
882
|
-
# - homogenize the results of multiple geocoders
|
883
|
-
#
|
884
|
-
# Limitations:
|
885
|
-
# - currently only provides the first result. Sometimes geocoders will return multiple results.
|
886
|
-
# - currently discards the "accuracy" component of the geocoding calls
|
887
|
-
class MultiGeocoder < Geocoder
|
888
|
-
|
889
|
-
private
|
890
|
-
# This method will call one or more geocoders in the order specified in the
|
891
|
-
# configuration until one of the geocoders work.
|
892
|
-
#
|
893
|
-
# The failover approach is crucial for production-grade apps, but is rarely used.
|
894
|
-
# 98% of your geocoding calls will be successful with the first call
|
895
|
-
def self.do_geocode(address, options = {})
|
896
|
-
geocode_ip = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.match(address)
|
897
|
-
provider_order = geocode_ip ? Geokit::Geocoders::ip_provider_order : Geokit::Geocoders::provider_order
|
898
|
-
|
899
|
-
provider_order.each do |provider|
|
900
|
-
begin
|
901
|
-
klass = Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder"
|
902
|
-
res = klass.send :geocode, address, options
|
903
|
-
return res if res.success?
|
904
|
-
rescue
|
905
|
-
logger.error("Something has gone very wrong during geocoding, OR you have configured an invalid class name in Geokit::Geocoders::provider_order. Address: #{address}. Provider: #{provider}")
|
906
|
-
end
|
907
|
-
end
|
908
|
-
# If we get here, we failed completely.
|
909
|
-
GeoLoc.new
|
910
|
-
end
|
911
|
-
|
912
|
-
# This method will call one or more geocoders in the order specified in the
|
913
|
-
# configuration until one of the geocoders work, only this time it's going
|
914
|
-
# to try to reverse geocode a geographical point.
|
915
|
-
def self.do_reverse_geocode(latlng)
|
916
|
-
Geokit::Geocoders::provider_order.each do |provider|
|
917
|
-
begin
|
918
|
-
klass = Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder"
|
919
|
-
res = klass.send :reverse_geocode, latlng
|
920
|
-
return res if res.success?
|
921
|
-
rescue
|
922
|
-
logger.error("Something has gone very wrong during reverse geocoding, OR you have configured an invalid class name in Geokit::Geocoders::provider_order. LatLng: #{latlng}. Provider: #{provider}")
|
923
|
-
end
|
924
|
-
end
|
925
|
-
# If we get here, we failed completely.
|
926
|
-
GeoLoc.new
|
927
|
-
end
|
928
|
-
end
|
161
|
+
require File.join(File.dirname(__FILE__), 'multi_geocoder')
|
929
162
|
end
|
930
163
|
end
|