geokit-premier 0.0.4 → 0.0.5
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 +6 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +58 -0
- data/History.txt +77 -0
- data/Rakefile +2 -21
- data/autotest/discover.rb +2 -0
- data/geokit-premier-0.0.4.gem +0 -0
- data/geokit-premier.gemspec +54 -0
- data/lib/geokit.rb +0 -1
- data/lib/geokit/geocoders_mine.rb +728 -0
- data/lib/geokit/version.rb +3 -0
- metadata +129 -24
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
geokit-premier (0.0.5)
|
|
5
|
+
hoe
|
|
6
|
+
json_pure
|
|
7
|
+
|
|
8
|
+
GEM
|
|
9
|
+
remote: http://rubygems.org/
|
|
10
|
+
specs:
|
|
11
|
+
ZenTest (4.4.2)
|
|
12
|
+
activemodel (3.0.3)
|
|
13
|
+
activesupport (= 3.0.3)
|
|
14
|
+
builder (~> 2.1.2)
|
|
15
|
+
i18n (~> 0.4)
|
|
16
|
+
activerecord (3.0.3)
|
|
17
|
+
activemodel (= 3.0.3)
|
|
18
|
+
activesupport (= 3.0.3)
|
|
19
|
+
arel (~> 2.0.2)
|
|
20
|
+
tzinfo (~> 0.3.23)
|
|
21
|
+
activesupport (3.0.3)
|
|
22
|
+
arel (2.0.7)
|
|
23
|
+
autotest (4.4.2)
|
|
24
|
+
builder (2.1.2)
|
|
25
|
+
diff-lcs (1.1.2)
|
|
26
|
+
hoe (2.8.0)
|
|
27
|
+
rake (>= 0.8.7)
|
|
28
|
+
i18n (0.5.0)
|
|
29
|
+
json_pure (1.4.6)
|
|
30
|
+
mysql (2.8.1)
|
|
31
|
+
rake (0.8.7)
|
|
32
|
+
rspec (2.0.1)
|
|
33
|
+
rspec-core (~> 2.0.1)
|
|
34
|
+
rspec-expectations (~> 2.0.1)
|
|
35
|
+
rspec-mocks (~> 2.0.1)
|
|
36
|
+
rspec-core (2.0.1)
|
|
37
|
+
rspec-expectations (2.0.1)
|
|
38
|
+
diff-lcs (>= 1.1.2)
|
|
39
|
+
rspec-mocks (2.0.1)
|
|
40
|
+
rspec-core (~> 2.0.1)
|
|
41
|
+
rspec-expectations (~> 2.0.1)
|
|
42
|
+
standalone_migrations (0.3.0)
|
|
43
|
+
activerecord
|
|
44
|
+
rake
|
|
45
|
+
tzinfo (0.3.24)
|
|
46
|
+
|
|
47
|
+
PLATFORMS
|
|
48
|
+
ruby
|
|
49
|
+
|
|
50
|
+
DEPENDENCIES
|
|
51
|
+
ZenTest
|
|
52
|
+
autotest
|
|
53
|
+
geokit-premier!
|
|
54
|
+
hoe
|
|
55
|
+
json_pure
|
|
56
|
+
mysql
|
|
57
|
+
rspec
|
|
58
|
+
standalone_migrations
|
data/History.txt
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
=== 1.5.0 / 2009-09-21
|
|
2
|
+
* fixed jruby compatibility (thanks manalang)
|
|
3
|
+
* added country name to Google reverse geocoder (thanks joahking)
|
|
4
|
+
* added DependentLocalityName as district, and SubAdministrativeAreaName as province (google geocoder only)
|
|
5
|
+
* Google geocoder throws an error if you exceed geocoding rates (thanks drogus)
|
|
6
|
+
|
|
7
|
+
=== 1.4.1 / 2009-06-15
|
|
8
|
+
* Fixed Ruby 1.9.1 compat and load order (thanks Niels Ganser)
|
|
9
|
+
|
|
10
|
+
=== 1.4.0 / 2009-05-27
|
|
11
|
+
* Added country code/viewport biasing to GoogleGeocoder. Added Bounds#to_span method
|
|
12
|
+
* Added suggested_bounds (Geokit::Bounds) property to GeoLoc. (Google geocoder only)
|
|
13
|
+
* Added LatLng#reverse_geocode convenience method (thanks Tisho Georgiev for all three)
|
|
14
|
+
|
|
15
|
+
=== 1.3.2 / 2009-05-27
|
|
16
|
+
* Fixed blank address geocoding bug
|
|
17
|
+
|
|
18
|
+
=== 1.3.1 / 2009-05-21
|
|
19
|
+
* Support for External geocoders file (thanks dreamcat4)
|
|
20
|
+
* Support multiple ip geocoders, including new setting for ip_provider_order (thanks dreamcat4)
|
|
21
|
+
|
|
22
|
+
=== 1.3.0 / 2009-04-11
|
|
23
|
+
* Added capability to define multiple API keys for different domains that may be pointing to the same application (thanks Glenn Powell)
|
|
24
|
+
* Added numeric accuracy accessor for Yahoo and Google geocoders (thanks Andrew Fecheyr Lippens)
|
|
25
|
+
* Implement #hash and #eql? on LatLng to allow for using it as a hash key (thanks Luke Melia and Ross Kaffenberger)
|
|
26
|
+
*
|
|
27
|
+
|
|
28
|
+
=== 1.2.6 / 2009-03-19
|
|
29
|
+
* misc minor fixes
|
|
30
|
+
|
|
31
|
+
=== 1.2.5 / 2009-02-25
|
|
32
|
+
|
|
33
|
+
* fixed GeoLoc.to_yaml
|
|
34
|
+
* fixed minor google geocoding bug
|
|
35
|
+
* now periodically publishing the Geokit gem to Rubyforge. Still maintaining development and managing contributions at Github
|
|
36
|
+
|
|
37
|
+
=== 1.2.4 / 2009-02-25
|
|
38
|
+
|
|
39
|
+
* Improved Google geocoder in the Gem: Support for multiple geocoding results from the Google geocoder. (thanks github/pic)
|
|
40
|
+
|
|
41
|
+
=== 1.2.3 / 2009-02-01
|
|
42
|
+
|
|
43
|
+
* Adding GeoPluginGeocoder for IP geocoding (thanks github/xjunior)
|
|
44
|
+
* Ruby 1.9.1 compatibility and Unicode fixes (thanks github/Nielsomat)
|
|
45
|
+
* various bug fixes
|
|
46
|
+
|
|
47
|
+
=== 1.2.1 / 2009-01-05
|
|
48
|
+
|
|
49
|
+
* minor bug fixes
|
|
50
|
+
* reverse geocoding added (Google only): res=Geokit::Geocoders::GoogleGeocoder.reverse_geocode "37.791821,-122.394679"
|
|
51
|
+
* nautical miles added (in addition to miles and KM)
|
|
52
|
+
|
|
53
|
+
=== 1.2.0 / 2008-12-01
|
|
54
|
+
|
|
55
|
+
* Improved Geocoder.us support -- respects authentication, and can geocode city names or zipcodes alone
|
|
56
|
+
* cross-meridian finds work correctly with bounds conditions
|
|
57
|
+
* fixed a problem with columns with "distance" in their name
|
|
58
|
+
* added Geonames geocoder
|
|
59
|
+
* the gem and plugin are now hosted at Github.
|
|
60
|
+
|
|
61
|
+
=== 1.1.1 / 2008-01-20
|
|
62
|
+
* fixes for distance calculation (in-memory and database) when distances are either very small or 0.
|
|
63
|
+
* NOTE: older versions of MySQL/Postgres may not work. See readme for more info.
|
|
64
|
+
|
|
65
|
+
=== 1.1.0 / 2007-12-07
|
|
66
|
+
* Geokit is now Rails 2.0 / Edge friendly.
|
|
67
|
+
|
|
68
|
+
=== 1.0.0 / 2007-07-22
|
|
69
|
+
* see http://earthcode.com/blog/2007/07/new_geokit_release.html
|
|
70
|
+
* auto geocoding: an option to automatically geocode a model's address field on create
|
|
71
|
+
* in-memory sort-by-distance for arrays of location objects
|
|
72
|
+
* bounding box queries: `Location.find :all, :bounds=>[sw,ne]`
|
|
73
|
+
* improved performance by automatically adding a bounding box condition to radial queries
|
|
74
|
+
* new Bounds class for in-memory bounds-related operations
|
|
75
|
+
* ability to calculate heading and midpoint between two points
|
|
76
|
+
* ability to calculate endpoint given a point, heading, and distance
|
|
77
|
+
|
data/Rakefile
CHANGED
|
@@ -1,21 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require 'rubygems'
|
|
4
|
-
require 'hoe'
|
|
5
|
-
require './lib/geokit.rb'
|
|
6
|
-
|
|
7
|
-
# undefined method `empty?' for nil:NilClass
|
|
8
|
-
# /Library/Ruby/Site/1.8/rubygems/specification.rb:886:in `validate'
|
|
9
|
-
class NilClass
|
|
10
|
-
def empty?
|
|
11
|
-
true
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
project=Hoe.new('geokit-premier', Geokit::VERSION) do |p|
|
|
16
|
-
p.developer('Andrew Forward', 'aforward@gmail.com')
|
|
17
|
-
p.summary="Fork of Geokit to provide for Google Premier users"
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
# vim: syntax=Ruby
|
|
1
|
+
require 'bundler'
|
|
2
|
+
Bundler::GemHelper.install_tasks
|
|
Binary file
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "geokit/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "geokit-premier"
|
|
7
|
+
s.version = Geokit::VERSION
|
|
8
|
+
s.platform = Gem::Platform::RUBY
|
|
9
|
+
s.authors = ["Andrew Forward (forked project from Andre Lewis and Bill Eisenhauer)"]
|
|
10
|
+
s.email = ["aforward@gmail.com"]
|
|
11
|
+
s.homepage = "https://github.com/aforward/geokit-premier-gem"
|
|
12
|
+
s.summary = %q{Enables google geocoding using a premier account}
|
|
13
|
+
s.description = %q{Enhanced the google geocoder to take advantage of the premier account offering}
|
|
14
|
+
|
|
15
|
+
s.add_dependency('json_pure')
|
|
16
|
+
s.add_dependency('hoe')
|
|
17
|
+
|
|
18
|
+
s.add_development_dependency('rspec')
|
|
19
|
+
s.add_development_dependency('autotest')
|
|
20
|
+
s.add_development_dependency('ZenTest')
|
|
21
|
+
s.add_development_dependency('standalone_migrations')
|
|
22
|
+
s.add_development_dependency('mysql')
|
|
23
|
+
|
|
24
|
+
s.files = `git ls-files`.split("\n")
|
|
25
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
26
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
27
|
+
s.require_paths = ["lib"]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# -*- encoding: utf-8 -*-
|
|
31
|
+
# Gem::Specification.new do |s|
|
|
32
|
+
# # s.name = %q{geokit-premier}
|
|
33
|
+
# s.version = "0.0.4"
|
|
34
|
+
#
|
|
35
|
+
# # s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
36
|
+
# # s.date = %q{2009-08-02}
|
|
37
|
+
# # s.extra_rdoc_files = ["Manifest.txt", "README.markdown"]
|
|
38
|
+
# # s.files = ["Manifest.txt", "README.markdown", "Rakefile", "lib/geokit/geocoders.rb", "lib/geokit.rb", "lib/geokit/mappable.rb", "spec/geocoder_spec.rb", "spec/spec_helper.rb", "test/test_base_geocoder.rb", "test/test_bounds.rb", "test/test_ca_geocoder.rb", "test/test_geoloc.rb", "test/test_google_geocoder.rb", "test/test_latlng.rb", "test/test_multi_geocoder.rb", "test/test_us_geocoder.rb", "test/test_yahoo_geocoder.rb"]
|
|
39
|
+
# # s.has_rdoc = true
|
|
40
|
+
# # s.rdoc_options = ["--main", "README.markdown"]
|
|
41
|
+
# # s.rubygems_version = %q{1.3.5}
|
|
42
|
+
# # s.summary = %q{none}
|
|
43
|
+
# # s.test_files = ["spec/geocoder_spec.rb", "test/test_base_geocoder.rb", "test/test_bounds.rb", "test/test_ca_geocoder.rb", "test/test_geoloc.rb",
|
|
44
|
+
# # "test/test_geoplugin_geocoder.rb", "test/test_google_geocoder.rb", "test/test_google_reverse_geocoder.rb",
|
|
45
|
+
# # "test/test_inflector.rb", "test/test_ipgeocoder.rb", "test/test_latlng.rb", "test/test_multi_geocoder.rb",
|
|
46
|
+
# # "test/test_multi_ip_geocoder.rb", "test/test_us_geocoder.rb", "test/test_yahoo_geocoder.rb"]
|
|
47
|
+
#
|
|
48
|
+
# if s.respond_to? :specification_version then
|
|
49
|
+
# current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
|
50
|
+
# s.specification_version = 2
|
|
51
|
+
# end
|
|
52
|
+
# end
|
|
53
|
+
|
|
54
|
+
|
data/lib/geokit.rb
CHANGED
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'ipaddr'
|
|
3
|
+
require 'rexml/document'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
require 'timeout'
|
|
6
|
+
require 'logger'
|
|
7
|
+
require 'base64'
|
|
8
|
+
|
|
9
|
+
module Geokit
|
|
10
|
+
|
|
11
|
+
class TooManyQueriesError < StandardError; end
|
|
12
|
+
|
|
13
|
+
module Inflector
|
|
14
|
+
|
|
15
|
+
extend self
|
|
16
|
+
|
|
17
|
+
def titleize(word)
|
|
18
|
+
humanize(underscore(word)).gsub(/\b([a-z])/u) { $1.capitalize }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def underscore(camel_cased_word)
|
|
22
|
+
camel_cased_word.to_s.gsub(/::/, '/').
|
|
23
|
+
gsub(/([A-Z]+)([A-Z][a-z])/u,'\1_\2').
|
|
24
|
+
gsub(/([a-z\d])([A-Z])/u,'\1_\2').
|
|
25
|
+
tr("-", "_").
|
|
26
|
+
downcase
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def humanize(lower_case_and_underscored_word)
|
|
30
|
+
lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def snake_case(s)
|
|
34
|
+
return s.downcase if s =~ /^[A-Z]+$/u
|
|
35
|
+
s.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/u, '_\&') =~ /_*(.*)/
|
|
36
|
+
return $+.downcase
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def url_escape(s)
|
|
41
|
+
s.gsub(/([^ a-zA-Z0-9_.-]+)/nu) do
|
|
42
|
+
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
|
43
|
+
end.tr(' ', '+')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def camelize(str)
|
|
47
|
+
str.split('_').map {|w| w.capitalize}.join
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Contains a range of geocoders:
|
|
52
|
+
#
|
|
53
|
+
# ### "regular" address geocoders
|
|
54
|
+
# * Yahoo Geocoder - requires an API key.
|
|
55
|
+
# * Geocoder.us - may require authentication if performing more than the free request limit.
|
|
56
|
+
# * Geocoder.ca - for Canada; may require authentication as well.
|
|
57
|
+
# * Geonames - a free geocoder
|
|
58
|
+
#
|
|
59
|
+
# ### address geocoders that also provide reverse geocoding
|
|
60
|
+
# * Google Geocoder - requires an API key.
|
|
61
|
+
#
|
|
62
|
+
# ### IP address geocoders
|
|
63
|
+
# * IP Geocoder - geocodes an IP address using hostip.info's web service.
|
|
64
|
+
# * Geoplugin.net -- another IP address geocoder
|
|
65
|
+
#
|
|
66
|
+
# ### The Multigeocoder
|
|
67
|
+
# * Multi Geocoder - provides failover for the physical location geocoders.
|
|
68
|
+
#
|
|
69
|
+
# Some of these geocoders require configuration. You don't have to provide it here. See the README.
|
|
70
|
+
module Geocoders
|
|
71
|
+
@@proxy_addr = nil
|
|
72
|
+
@@proxy_port = nil
|
|
73
|
+
@@proxy_user = nil
|
|
74
|
+
@@proxy_pass = nil
|
|
75
|
+
@@request_timeout = nil
|
|
76
|
+
@@yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
|
|
77
|
+
@@google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
|
|
78
|
+
@@google_client_id = nil #only used for premier accounts
|
|
79
|
+
@@google_premier_secret_key = nil
|
|
80
|
+
@@geocoder_us = false
|
|
81
|
+
@@geocoder_ca = false
|
|
82
|
+
@@geonames = false
|
|
83
|
+
@@provider_order = [:google,:us]
|
|
84
|
+
@@ip_provider_order = [:geo_plugin,:ip]
|
|
85
|
+
@@logger=Logger.new(STDOUT)
|
|
86
|
+
@@logger.level=Logger::INFO
|
|
87
|
+
@@domain = nil
|
|
88
|
+
|
|
89
|
+
def self.__define_accessors
|
|
90
|
+
class_variables.each do |v|
|
|
91
|
+
sym = v.to_s.delete("@").to_sym
|
|
92
|
+
unless self.respond_to? sym
|
|
93
|
+
module_eval <<-EOS, __FILE__, __LINE__
|
|
94
|
+
def self.#{sym}
|
|
95
|
+
value = if defined?(#{sym.to_s.upcase})
|
|
96
|
+
#{sym.to_s.upcase}
|
|
97
|
+
else
|
|
98
|
+
@@#{sym}
|
|
99
|
+
end
|
|
100
|
+
if value.is_a?(Hash)
|
|
101
|
+
value = (self.domain.nil? ? nil : value[self.domain]) || value.values.first
|
|
102
|
+
end
|
|
103
|
+
value
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.#{sym}=(obj)
|
|
107
|
+
@@#{sym} = obj
|
|
108
|
+
end
|
|
109
|
+
EOS
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
__define_accessors
|
|
115
|
+
|
|
116
|
+
# Error which is thrown in the event a geocoding error occurs.
|
|
117
|
+
class GeocodeError < StandardError; end
|
|
118
|
+
|
|
119
|
+
# -------------------------------------------------------------------------------------------
|
|
120
|
+
# Geocoder Base class -- every geocoder should inherit from this
|
|
121
|
+
# -------------------------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
# The Geocoder base class which defines the interface to be used by all
|
|
124
|
+
# other geocoders.
|
|
125
|
+
class Geocoder
|
|
126
|
+
# Main method which calls the do_geocode template method which subclasses
|
|
127
|
+
# are responsible for implementing. Returns a populated GeoLoc or an
|
|
128
|
+
# empty one with a failed success code.
|
|
129
|
+
def self.geocode(address, options = {})
|
|
130
|
+
res = do_geocode(address, options)
|
|
131
|
+
return res.nil? ? GeoLoc.new : res
|
|
132
|
+
end
|
|
133
|
+
# Main method which calls the do_reverse_geocode template method which subclasses
|
|
134
|
+
# are responsible for implementing. Returns a populated GeoLoc or an
|
|
135
|
+
# empty one with a failed success code.
|
|
136
|
+
def self.reverse_geocode(latlng)
|
|
137
|
+
res = do_reverse_geocode(latlng)
|
|
138
|
+
return res.success? ? res : GeoLoc.new
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Call the geocoder service using the timeout if configured.
|
|
142
|
+
def self.call_geocoder_service(url)
|
|
143
|
+
Timeout::timeout(Geokit::Geocoders::request_timeout) { return self.do_get(url) } if Geokit::Geocoders::request_timeout
|
|
144
|
+
return self.do_get(url)
|
|
145
|
+
rescue TimeoutError
|
|
146
|
+
return nil
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Not all geocoders can do reverse geocoding. So, unless the subclass explicitly overrides this method,
|
|
150
|
+
# a call to reverse_geocode will return an empty GeoLoc. If you happen to be using MultiGeocoder,
|
|
151
|
+
# this will cause it to failover to the next geocoder, which will hopefully be one which supports reverse geocoding.
|
|
152
|
+
def self.do_reverse_geocode(latlng)
|
|
153
|
+
return GeoLoc.new
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# This will sign a raw url with a private key
|
|
157
|
+
def self.sign_url(raw_url,private_key)
|
|
158
|
+
uri = URI.parse(raw_url)
|
|
159
|
+
url_to_sign = uri.path + "?" + uri.query
|
|
160
|
+
decoded_key = Geocoder.urlsafe_decode64(private_key)
|
|
161
|
+
|
|
162
|
+
sha1_digest = OpenSSL::Digest::Digest.new('sha1')
|
|
163
|
+
signature = OpenSSL::HMAC.digest(sha1_digest,decoded_key,url_to_sign)
|
|
164
|
+
encoded_signature = Geocoder.urlsafe_encode64(signature)
|
|
165
|
+
signed_url = "#{uri.scheme}://#{uri.host}#{uri.path}?#{uri.query}&signature=#{encoded_signature}".strip!
|
|
166
|
+
signed_url
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# This will provide url safe base64 decoding
|
|
170
|
+
def self.urlsafe_decode64(raw_text)
|
|
171
|
+
decoded_text = raw_text.gsub('-','+').gsub('_', '/')
|
|
172
|
+
decoded_text = Base64.decode64(decoded_text)
|
|
173
|
+
decoded_text
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# This will provide url safe base64 encoding
|
|
177
|
+
def self.urlsafe_encode64(raw_text)
|
|
178
|
+
encoded_text = Base64.encode64(raw_text)
|
|
179
|
+
encoded_text = encoded_text.gsub('+','-').gsub('/', '_')
|
|
180
|
+
encoded_text
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
protected
|
|
185
|
+
|
|
186
|
+
def self.logger()
|
|
187
|
+
Geokit::Geocoders::logger
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
private
|
|
191
|
+
|
|
192
|
+
# Wraps the geocoder call around a proxy if necessary.
|
|
193
|
+
def self.do_get(url)
|
|
194
|
+
uri = URI.parse(url)
|
|
195
|
+
req = Net::HTTP::Get.new(url)
|
|
196
|
+
req.basic_auth(uri.user, uri.password) if uri.userinfo
|
|
197
|
+
res = Net::HTTP::Proxy(GeoKit::Geocoders::proxy_addr,
|
|
198
|
+
GeoKit::Geocoders::proxy_port,
|
|
199
|
+
GeoKit::Geocoders::proxy_user,
|
|
200
|
+
GeoKit::Geocoders::proxy_pass).start(uri.host, uri.port) { |http| http.get(uri.path + "?" + uri.query) }
|
|
201
|
+
return res
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Adds subclass' geocode method making it conveniently available through
|
|
205
|
+
# the base class.
|
|
206
|
+
def self.inherited(clazz)
|
|
207
|
+
class_name = clazz.name.split('::').last
|
|
208
|
+
src = <<-END_SRC
|
|
209
|
+
def self.#{Geokit::Inflector.underscore(class_name)}(address, options = {})
|
|
210
|
+
#{class_name}.geocode(address, options)
|
|
211
|
+
end
|
|
212
|
+
END_SRC
|
|
213
|
+
class_eval(src)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# -------------------------------------------------------------------------------------------
|
|
218
|
+
# "Regular" Address geocoders
|
|
219
|
+
# -------------------------------------------------------------------------------------------
|
|
220
|
+
|
|
221
|
+
# Geocoder CA geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_CA variable to
|
|
222
|
+
# contain true or false based upon whether authentication is to occur. Conforms to the
|
|
223
|
+
# interface set by the Geocoder class.
|
|
224
|
+
#
|
|
225
|
+
# Returns a response like:
|
|
226
|
+
# <?xml version="1.0" encoding="UTF-8" ?>
|
|
227
|
+
# <geodata>
|
|
228
|
+
# <latt>49.243086</latt>
|
|
229
|
+
# <longt>-123.153684</longt>
|
|
230
|
+
# </geodata>
|
|
231
|
+
class CaGeocoder < Geocoder
|
|
232
|
+
|
|
233
|
+
private
|
|
234
|
+
|
|
235
|
+
# Template method which does the geocode lookup.
|
|
236
|
+
def self.do_geocode(address, options = {})
|
|
237
|
+
raise ArgumentError('Geocoder.ca requires a GeoLoc argument') unless address.is_a?(GeoLoc)
|
|
238
|
+
url = construct_request(address)
|
|
239
|
+
res = self.call_geocoder_service(url)
|
|
240
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
|
241
|
+
xml = res.body
|
|
242
|
+
logger.debug "Geocoder.ca geocoding. Address: #{address}. Result: #{xml}"
|
|
243
|
+
# Parse the document.
|
|
244
|
+
doc = REXML::Document.new(xml)
|
|
245
|
+
address.lat = doc.elements['//latt'].text
|
|
246
|
+
address.lng = doc.elements['//longt'].text
|
|
247
|
+
address.success = true
|
|
248
|
+
return address
|
|
249
|
+
rescue
|
|
250
|
+
logger.error "Caught an error during Geocoder.ca geocoding call: "+$!
|
|
251
|
+
return GeoLoc.new
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Formats the request in the format acceptable by the CA geocoder.
|
|
255
|
+
def self.construct_request(location)
|
|
256
|
+
url = ""
|
|
257
|
+
url += add_ampersand(url) + "stno=#{location.street_number}" if location.street_address
|
|
258
|
+
url += add_ampersand(url) + "addresst=#{Geokit::Inflector::url_escape(location.street_name)}" if location.street_address
|
|
259
|
+
url += add_ampersand(url) + "city=#{Geokit::Inflector::url_escape(location.city)}" if location.city
|
|
260
|
+
url += add_ampersand(url) + "prov=#{location.state}" if location.state
|
|
261
|
+
url += add_ampersand(url) + "postal=#{location.zip}" if location.zip
|
|
262
|
+
url += add_ampersand(url) + "auth=#{Geokit::Geocoders::geocoder_ca}" if Geokit::Geocoders::geocoder_ca
|
|
263
|
+
url += add_ampersand(url) + "geoit=xml"
|
|
264
|
+
'http://geocoder.ca/?' + url
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def self.add_ampersand(url)
|
|
268
|
+
url && url.length > 0 ? "&" : ""
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Geocoder Us geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_US variable to
|
|
273
|
+
# contain true or false based upon whether authentication is to occur. Conforms to the
|
|
274
|
+
# interface set by the Geocoder class.
|
|
275
|
+
class UsGeocoder < Geocoder
|
|
276
|
+
|
|
277
|
+
private
|
|
278
|
+
def self.do_geocode(address, options = {})
|
|
279
|
+
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
|
280
|
+
|
|
281
|
+
query = (address_str =~ /^\d{5}(?:-\d{4})?$/ ? "zip" : "address") + "=#{Geokit::Inflector::url_escape(address_str)}"
|
|
282
|
+
url = if GeoKit::Geocoders::geocoder_us
|
|
283
|
+
"http://#{GeoKit::Geocoders::geocoder_us}@geocoder.us/member/service/csv/geocode"
|
|
284
|
+
else
|
|
285
|
+
"http://geocoder.us/service/csv/geocode"
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
url = "#{url}?#{query}"
|
|
289
|
+
res = self.call_geocoder_service(url)
|
|
290
|
+
|
|
291
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
|
292
|
+
data = res.body
|
|
293
|
+
logger.debug "Geocoder.us geocoding. Address: #{address}. Result: #{data}"
|
|
294
|
+
array = data.chomp.split(',')
|
|
295
|
+
|
|
296
|
+
if array.length == 5
|
|
297
|
+
res=GeoLoc.new
|
|
298
|
+
res.lat,res.lng,res.city,res.state,res.zip=array
|
|
299
|
+
res.country_code='US'
|
|
300
|
+
res.success=true
|
|
301
|
+
return res
|
|
302
|
+
elsif array.length == 6
|
|
303
|
+
res=GeoLoc.new
|
|
304
|
+
res.lat,res.lng,res.street_address,res.city,res.state,res.zip=array
|
|
305
|
+
res.country_code='US'
|
|
306
|
+
res.success=true
|
|
307
|
+
return res
|
|
308
|
+
else
|
|
309
|
+
logger.info "geocoder.us was unable to geocode address: "+address
|
|
310
|
+
return GeoLoc.new
|
|
311
|
+
end
|
|
312
|
+
rescue
|
|
313
|
+
logger.error "Caught an error during geocoder.us geocoding call: "+$!
|
|
314
|
+
return GeoLoc.new
|
|
315
|
+
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Yahoo geocoder implementation. Requires the Geokit::Geocoders::YAHOO variable to
|
|
320
|
+
# contain a Yahoo API key. Conforms to the interface set by the Geocoder class.
|
|
321
|
+
class YahooGeocoder < Geocoder
|
|
322
|
+
|
|
323
|
+
private
|
|
324
|
+
|
|
325
|
+
# Template method which does the geocode lookup.
|
|
326
|
+
def self.do_geocode(address, options = {})
|
|
327
|
+
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
|
328
|
+
url="http://api.local.yahoo.com/MapsService/V1/geocode?appid=#{Geokit::Geocoders::yahoo}&location=#{Geokit::Inflector::url_escape(address_str)}"
|
|
329
|
+
res = self.call_geocoder_service(url)
|
|
330
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
|
331
|
+
xml = res.body
|
|
332
|
+
doc = REXML::Document.new(xml)
|
|
333
|
+
logger.debug "Yahoo geocoding. Address: #{address}. Result: #{xml}"
|
|
334
|
+
|
|
335
|
+
if doc.elements['//ResultSet']
|
|
336
|
+
res=GeoLoc.new
|
|
337
|
+
|
|
338
|
+
#basic
|
|
339
|
+
res.lat=doc.elements['//Latitude'].text
|
|
340
|
+
res.lng=doc.elements['//Longitude'].text
|
|
341
|
+
res.country_code=doc.elements['//Country'].text
|
|
342
|
+
res.provider='yahoo'
|
|
343
|
+
|
|
344
|
+
#extended - false if not available
|
|
345
|
+
res.city=doc.elements['//City'].text if doc.elements['//City'] && doc.elements['//City'].text != nil
|
|
346
|
+
res.state=doc.elements['//State'].text if doc.elements['//State'] && doc.elements['//State'].text != nil
|
|
347
|
+
res.zip=doc.elements['//Zip'].text if doc.elements['//Zip'] && doc.elements['//Zip'].text != nil
|
|
348
|
+
res.street_address=doc.elements['//Address'].text if doc.elements['//Address'] && doc.elements['//Address'].text != nil
|
|
349
|
+
res.precision=doc.elements['//Result'].attributes['precision'] if doc.elements['//Result']
|
|
350
|
+
# set the accuracy as google does (added by Andruby)
|
|
351
|
+
res.accuracy=%w{unknown country state state city zip zip+4 street address building}.index(res.precision)
|
|
352
|
+
res.success=true
|
|
353
|
+
return res
|
|
354
|
+
else
|
|
355
|
+
logger.info "Yahoo was unable to geocode address: "+address
|
|
356
|
+
return GeoLoc.new
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
rescue
|
|
360
|
+
logger.info "Caught an error during Yahoo geocoding call: "+$!
|
|
361
|
+
return GeoLoc.new
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Another geocoding web service
|
|
366
|
+
# http://www.geonames.org
|
|
367
|
+
class GeonamesGeocoder < Geocoder
|
|
368
|
+
|
|
369
|
+
private
|
|
370
|
+
|
|
371
|
+
# Template method which does the geocode lookup.
|
|
372
|
+
def self.do_geocode(address, options = {})
|
|
373
|
+
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
|
374
|
+
# geonames need a space seperated search string
|
|
375
|
+
address_str.gsub!(/,/, " ")
|
|
376
|
+
params = "/postalCodeSearch?placename=#{Geokit::Inflector::url_escape(address_str)}&maxRows=10"
|
|
377
|
+
|
|
378
|
+
if(GeoKit::Geocoders::geonames)
|
|
379
|
+
url = "http://ws.geonames.net#{params}&username=#{GeoKit::Geocoders::geonames}"
|
|
380
|
+
else
|
|
381
|
+
url = "http://ws.geonames.org#{params}"
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
res = self.call_geocoder_service(url)
|
|
385
|
+
|
|
386
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
|
387
|
+
|
|
388
|
+
xml=res.body
|
|
389
|
+
logger.debug "Geonames geocoding. Address: #{address}. Result: #{xml}"
|
|
390
|
+
doc=REXML::Document.new(xml)
|
|
391
|
+
|
|
392
|
+
if(doc.elements['//geonames/totalResultsCount'].text.to_i > 0)
|
|
393
|
+
res=GeoLoc.new
|
|
394
|
+
|
|
395
|
+
# only take the first result
|
|
396
|
+
res.lat=doc.elements['//code/lat'].text if doc.elements['//code/lat']
|
|
397
|
+
res.lng=doc.elements['//code/lng'].text if doc.elements['//code/lng']
|
|
398
|
+
res.country_code=doc.elements['//code/countryCode'].text if doc.elements['//code/countryCode']
|
|
399
|
+
res.provider='genomes'
|
|
400
|
+
res.city=doc.elements['//code/name'].text if doc.elements['//code/name']
|
|
401
|
+
res.state=doc.elements['//code/adminName1'].text if doc.elements['//code/adminName1']
|
|
402
|
+
res.zip=doc.elements['//code/postalcode'].text if doc.elements['//code/postalcode']
|
|
403
|
+
res.success=true
|
|
404
|
+
return res
|
|
405
|
+
else
|
|
406
|
+
logger.info "Geonames was unable to geocode address: "+address
|
|
407
|
+
return GeoLoc.new
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
rescue
|
|
411
|
+
logger.error "Caught an error during Geonames geocoding call: "+$!
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# -------------------------------------------------------------------------------------------
|
|
416
|
+
# Address geocoders that also provide reverse geocoding
|
|
417
|
+
# -------------------------------------------------------------------------------------------
|
|
418
|
+
|
|
419
|
+
# Google geocoder implementation. Requires the Geokit::Geocoders::GOOGLE variable to
|
|
420
|
+
# contain a Google API key. Conforms to the interface set by the Geocoder class.
|
|
421
|
+
class GoogleGeocoder < Geocoder
|
|
422
|
+
|
|
423
|
+
private
|
|
424
|
+
|
|
425
|
+
# Template method which does the reverse-geocode lookup.
|
|
426
|
+
def self.do_reverse_geocode(latlng)
|
|
427
|
+
latlng=LatLng.normalize(latlng)
|
|
428
|
+
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")
|
|
429
|
+
# 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"))
|
|
430
|
+
return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
|
|
431
|
+
xml = res.body
|
|
432
|
+
logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{xml}"
|
|
433
|
+
return self.xml2GeoLoc(xml)
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# Template method which does the geocode lookup.
|
|
437
|
+
#
|
|
438
|
+
# Supports viewport/country code biasing
|
|
439
|
+
#
|
|
440
|
+
# ==== OPTIONS
|
|
441
|
+
# * :bias - This option makes the Google Geocoder return results biased to a particular
|
|
442
|
+
# country or viewport. Country code biasing is achieved by passing the ccTLD
|
|
443
|
+
# ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
|
|
444
|
+
# look here: http://en.wikipedia.org/wiki/CcTLD. By default, the geocoder
|
|
445
|
+
# will be biased to results within the US (ccTLD .com).
|
|
446
|
+
#
|
|
447
|
+
# If you'd like the Google Geocoder to prefer results within a given viewport,
|
|
448
|
+
# you can pass a Geokit::Bounds object as the :bias value.
|
|
449
|
+
#
|
|
450
|
+
# ==== EXAMPLES
|
|
451
|
+
# # By default, the geocoder will return Syracuse, NY
|
|
452
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse').country_code # => 'US'
|
|
453
|
+
# # With country code biasing, it returns Syracuse in Sicily, Italy
|
|
454
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse', :bias => :it).country_code # => 'IT'
|
|
455
|
+
#
|
|
456
|
+
# # By default, the geocoder will return Winnetka, IL
|
|
457
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka').state # => 'IL'
|
|
458
|
+
# # When biased to an bounding box around California, it will now return the Winnetka neighbourhood, CA
|
|
459
|
+
# bounds = Geokit::Bounds.normalize([34.074081, -118.694401], [34.321129, -118.399487])
|
|
460
|
+
# Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka', :bias => bounds).state # => 'CA'
|
|
461
|
+
def self.do_geocode(address, options = {})
|
|
462
|
+
res = self.call_geocoder_service(self.geocode_url(address,options))
|
|
463
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
|
464
|
+
xml = res.body
|
|
465
|
+
logger.debug "Google geocoding. Address: #{address}. Result: #{xml}"
|
|
466
|
+
return self.xml2GeoLoc(xml, address)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# Determine the Google API url based on the google api key, or based on the client / private key for premier users
|
|
470
|
+
def self.geocode_url(address,options = {})
|
|
471
|
+
bias_str = options[:bias] ? construct_bias_string_from_options(options[:bias]) : ''
|
|
472
|
+
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
|
473
|
+
|
|
474
|
+
if !Geokit::Geocoders::google_client_id.nil? && !Geokit::Geocoders::google_premier_secret_key.nil?
|
|
475
|
+
url = "http://maps.googleapis.com/maps/api/geocode/xml?address=#{Geokit::Inflector::url_escape(address_str)}&client=#{Geokit::Geocoders::google_client_id}&sensor=false&oe=utf-8"
|
|
476
|
+
Geokit::Geocoders::Geocoder.sign_url(url,Geokit::Geocoders::google_premier_secret_key)
|
|
477
|
+
else
|
|
478
|
+
"http://maps.google.com/maps/geo?q=#{Geokit::Inflector::url_escape(address_str)}&output=xml#{bias_str}&key=#{Geokit::Geocoders::google}&oe=utf-8"
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def self.construct_bias_string_from_options(bias)
|
|
484
|
+
if bias.is_a?(String) or bias.is_a?(Symbol)
|
|
485
|
+
# country code biasing
|
|
486
|
+
"&gl=#{bias.to_s.downcase}"
|
|
487
|
+
elsif bias.is_a?(Bounds)
|
|
488
|
+
# viewport biasing
|
|
489
|
+
"&ll=#{bias.center.ll}&spn=#{bias.to_span.ll}"
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def self.xml2GeoLoc(xml, address="")
|
|
494
|
+
doc=REXML::Document.new(xml)
|
|
495
|
+
|
|
496
|
+
if doc.elements['//kml/Response/Status/code'].text == '200'
|
|
497
|
+
geoloc = nil
|
|
498
|
+
# Google can return multiple results as //Placemark elements.
|
|
499
|
+
# iterate through each and extract each placemark as a geoloc
|
|
500
|
+
doc.each_element('//Placemark') do |e|
|
|
501
|
+
extracted_geoloc = extract_placemark(e) # g is now an instance of GeoLoc
|
|
502
|
+
if geoloc.nil?
|
|
503
|
+
# first time through, geoloc is still nil, so we make it the geoloc we just extracted
|
|
504
|
+
geoloc = extracted_geoloc
|
|
505
|
+
else
|
|
506
|
+
# second (and subsequent) iterations, we push additional
|
|
507
|
+
# geolocs onto "geoloc.all"
|
|
508
|
+
geoloc.all.push(extracted_geoloc)
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
return geoloc
|
|
512
|
+
elsif doc.elements['//kml/Response/Status/code'].text == '620'
|
|
513
|
+
raise Geokit::TooManyQueriesError
|
|
514
|
+
else
|
|
515
|
+
logger.info "Google was unable to geocode address: "+address
|
|
516
|
+
return GeoLoc.new
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
rescue Geokit::TooManyQueriesError
|
|
520
|
+
# re-raise because of other rescue
|
|
521
|
+
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."
|
|
522
|
+
rescue
|
|
523
|
+
logger.error "Caught an error during Google geocoding call: "+$!
|
|
524
|
+
return GeoLoc.new
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
# extracts a single geoloc from a //placemark element in the google results xml
|
|
528
|
+
def self.extract_placemark(doc)
|
|
529
|
+
res = GeoLoc.new
|
|
530
|
+
coordinates=doc.elements['.//coordinates'].text.to_s.split(',')
|
|
531
|
+
|
|
532
|
+
#basics
|
|
533
|
+
res.lat=coordinates[1]
|
|
534
|
+
res.lng=coordinates[0]
|
|
535
|
+
res.country_code=doc.elements['.//CountryNameCode'].text if doc.elements['.//CountryNameCode']
|
|
536
|
+
res.provider='google'
|
|
537
|
+
|
|
538
|
+
#extended -- false if not not available
|
|
539
|
+
res.city = doc.elements['.//LocalityName'].text if doc.elements['.//LocalityName']
|
|
540
|
+
res.state = doc.elements['.//AdministrativeAreaName'].text if doc.elements['.//AdministrativeAreaName']
|
|
541
|
+
res.province = doc.elements['.//SubAdministrativeAreaName'].text if doc.elements['.//SubAdministrativeAreaName']
|
|
542
|
+
res.full_address = doc.elements['.//address'].text if doc.elements['.//address'] # google provides it
|
|
543
|
+
res.zip = doc.elements['.//PostalCodeNumber'].text if doc.elements['.//PostalCodeNumber']
|
|
544
|
+
res.street_address = doc.elements['.//ThoroughfareName'].text if doc.elements['.//ThoroughfareName']
|
|
545
|
+
res.country = doc.elements['.//CountryName'].text if doc.elements['.//CountryName']
|
|
546
|
+
res.district = doc.elements['.//DependentLocalityName'].text if doc.elements['.//DependentLocalityName']
|
|
547
|
+
# Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
|
|
548
|
+
# For Google, 1=low accuracy, 8=high accuracy
|
|
549
|
+
address_details=doc.elements['.//*[local-name() = "AddressDetails"]']
|
|
550
|
+
res.accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
|
|
551
|
+
res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy]
|
|
552
|
+
|
|
553
|
+
# google returns a set of suggested boundaries for the geocoded result
|
|
554
|
+
if suggested_bounds = doc.elements['//LatLonBox']
|
|
555
|
+
res.suggested_bounds = Bounds.normalize(
|
|
556
|
+
[suggested_bounds.attributes['south'], suggested_bounds.attributes['west']],
|
|
557
|
+
[suggested_bounds.attributes['north'], suggested_bounds.attributes['east']])
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
res.success=true
|
|
561
|
+
|
|
562
|
+
return res
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
# -------------------------------------------------------------------------------------------
|
|
568
|
+
# IP Geocoders
|
|
569
|
+
# -------------------------------------------------------------------------------------------
|
|
570
|
+
|
|
571
|
+
# Provides geocoding based upon an IP address. The underlying web service is geoplugin.net
|
|
572
|
+
class GeoPluginGeocoder < Geocoder
|
|
573
|
+
private
|
|
574
|
+
|
|
575
|
+
def self.do_geocode(ip, options = {})
|
|
576
|
+
return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
|
|
577
|
+
response = self.call_geocoder_service("http://www.geoplugin.net/xml.gp?ip=#{ip}")
|
|
578
|
+
return response.is_a?(Net::HTTPSuccess) ? parse_xml(response.body) : GeoLoc.new
|
|
579
|
+
rescue
|
|
580
|
+
logger.error "Caught an error during GeoPluginGeocoder geocoding call: "+$!
|
|
581
|
+
return GeoLoc.new
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
def self.parse_xml(xml)
|
|
585
|
+
xml = REXML::Document.new(xml)
|
|
586
|
+
geo = GeoLoc.new
|
|
587
|
+
geo.provider='geoPlugin'
|
|
588
|
+
geo.city = xml.elements['//geoplugin_city'].text
|
|
589
|
+
geo.state = xml.elements['//geoplugin_region'].text
|
|
590
|
+
geo.country_code = xml.elements['//geoplugin_countryCode'].text
|
|
591
|
+
geo.lat = xml.elements['//geoplugin_latitude'].text.to_f
|
|
592
|
+
geo.lng = xml.elements['//geoplugin_longitude'].text.to_f
|
|
593
|
+
geo.success = !!geo.city && !geo.city.empty?
|
|
594
|
+
return geo
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
# Provides geocoding based upon an IP address. The underlying web service is a hostip.info
|
|
599
|
+
# which sources their data through a combination of publicly available information as well
|
|
600
|
+
# as community contributions.
|
|
601
|
+
class IpGeocoder < Geocoder
|
|
602
|
+
|
|
603
|
+
# A number of non-routable IP ranges.
|
|
604
|
+
#
|
|
605
|
+
# --
|
|
606
|
+
# Sources for these:
|
|
607
|
+
# RFC 3330: Special-Use IPv4 Addresses
|
|
608
|
+
# The bogon list: http://www.cymru.com/Documents/bogon-list.html
|
|
609
|
+
|
|
610
|
+
NON_ROUTABLE_IP_RANGES = [
|
|
611
|
+
IPAddr.new('0.0.0.0/8'), # "This" Network
|
|
612
|
+
IPAddr.new('10.0.0.0/8'), # Private-Use Networks
|
|
613
|
+
IPAddr.new('14.0.0.0/8'), # Public-Data Networks
|
|
614
|
+
IPAddr.new('127.0.0.0/8'), # Loopback
|
|
615
|
+
IPAddr.new('169.254.0.0/16'), # Link local
|
|
616
|
+
IPAddr.new('172.16.0.0/12'), # Private-Use Networks
|
|
617
|
+
IPAddr.new('192.0.2.0/24'), # Test-Net
|
|
618
|
+
IPAddr.new('192.168.0.0/16'), # Private-Use Networks
|
|
619
|
+
IPAddr.new('198.18.0.0/15'), # Network Interconnect Device Benchmark Testing
|
|
620
|
+
IPAddr.new('224.0.0.0/4'), # Multicast
|
|
621
|
+
IPAddr.new('240.0.0.0/4') # Reserved for future use
|
|
622
|
+
].freeze
|
|
623
|
+
|
|
624
|
+
private
|
|
625
|
+
|
|
626
|
+
# Given an IP address, returns a GeoLoc instance which contains latitude,
|
|
627
|
+
# longitude, city, and country code. Sets the success attribute to false if the ip
|
|
628
|
+
# parameter does not match an ip address.
|
|
629
|
+
def self.do_geocode(ip, options = {})
|
|
630
|
+
return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
|
|
631
|
+
return GeoLoc.new if self.private_ip_address?(ip)
|
|
632
|
+
url = "http://api.hostip.info/get_html.php?ip=#{ip}&position=true"
|
|
633
|
+
response = self.call_geocoder_service(url)
|
|
634
|
+
response.is_a?(Net::HTTPSuccess) ? parse_body(response.body) : GeoLoc.new
|
|
635
|
+
rescue
|
|
636
|
+
logger.error "Caught an error during HostIp geocoding call: "+$!
|
|
637
|
+
return GeoLoc.new
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
# Converts the body to YAML since its in the form of:
|
|
641
|
+
#
|
|
642
|
+
# Country: UNITED STATES (US)
|
|
643
|
+
# City: Sugar Grove, IL
|
|
644
|
+
# Latitude: 41.7696
|
|
645
|
+
# Longitude: -88.4588
|
|
646
|
+
#
|
|
647
|
+
# then instantiates a GeoLoc instance to populate with location data.
|
|
648
|
+
def self.parse_body(body) # :nodoc:
|
|
649
|
+
yaml = YAML.load(body)
|
|
650
|
+
res = GeoLoc.new
|
|
651
|
+
res.provider = 'hostip'
|
|
652
|
+
res.city, res.state = yaml['City'].split(', ')
|
|
653
|
+
country, res.country_code = yaml['Country'].split(' (')
|
|
654
|
+
res.lat = yaml['Latitude']
|
|
655
|
+
res.lng = yaml['Longitude']
|
|
656
|
+
res.country_code.chop!
|
|
657
|
+
res.success = !(res.city =~ /\(.+\)/)
|
|
658
|
+
res
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
# Checks whether the IP address belongs to a private address range.
|
|
662
|
+
#
|
|
663
|
+
# This function is used to reduce the number of useless queries made to
|
|
664
|
+
# the geocoding service. Such queries can occur frequently during
|
|
665
|
+
# integration tests.
|
|
666
|
+
def self.private_ip_address?(ip)
|
|
667
|
+
return NON_ROUTABLE_IP_RANGES.any? { |range| range.include?(ip) }
|
|
668
|
+
end
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
# -------------------------------------------------------------------------------------------
|
|
672
|
+
# The Multi Geocoder
|
|
673
|
+
# -------------------------------------------------------------------------------------------
|
|
674
|
+
|
|
675
|
+
# Provides methods to geocode with a variety of geocoding service providers, plus failover
|
|
676
|
+
# among providers in the order you configure. When 2nd parameter is set 'true', perform
|
|
677
|
+
# ip location lookup with 'address' as the ip address.
|
|
678
|
+
#
|
|
679
|
+
# Goal:
|
|
680
|
+
# - homogenize the results of multiple geocoders
|
|
681
|
+
#
|
|
682
|
+
# Limitations:
|
|
683
|
+
# - currently only provides the first result. Sometimes geocoders will return multiple results.
|
|
684
|
+
# - currently discards the "accuracy" component of the geocoding calls
|
|
685
|
+
class MultiGeocoder < Geocoder
|
|
686
|
+
|
|
687
|
+
private
|
|
688
|
+
# This method will call one or more geocoders in the order specified in the
|
|
689
|
+
# configuration until one of the geocoders work.
|
|
690
|
+
#
|
|
691
|
+
# The failover approach is crucial for production-grade apps, but is rarely used.
|
|
692
|
+
# 98% of your geocoding calls will be successful with the first call
|
|
693
|
+
def self.do_geocode(address, options = {})
|
|
694
|
+
geocode_ip = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.match(address)
|
|
695
|
+
provider_order = geocode_ip ? Geokit::Geocoders::ip_provider_order : Geokit::Geocoders::provider_order
|
|
696
|
+
|
|
697
|
+
provider_order.each do |provider|
|
|
698
|
+
begin
|
|
699
|
+
klass = Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder"
|
|
700
|
+
res = klass.send :geocode, address, options
|
|
701
|
+
return res if res.success?
|
|
702
|
+
rescue
|
|
703
|
+
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}")
|
|
704
|
+
end
|
|
705
|
+
end
|
|
706
|
+
# If we get here, we failed completely.
|
|
707
|
+
GeoLoc.new
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
# This method will call one or more geocoders in the order specified in the
|
|
711
|
+
# configuration until one of the geocoders work, only this time it's going
|
|
712
|
+
# to try to reverse geocode a geographical point.
|
|
713
|
+
def self.do_reverse_geocode(latlng)
|
|
714
|
+
Geokit::Geocoders::provider_order.each do |provider|
|
|
715
|
+
begin
|
|
716
|
+
klass = Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder"
|
|
717
|
+
res = klass.send :reverse_geocode, latlng
|
|
718
|
+
return res if res.success?
|
|
719
|
+
rescue
|
|
720
|
+
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}")
|
|
721
|
+
end
|
|
722
|
+
end
|
|
723
|
+
# If we get here, we failed completely.
|
|
724
|
+
GeoLoc.new
|
|
725
|
+
end
|
|
726
|
+
end
|
|
727
|
+
end
|
|
728
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: geokit-premier
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
5
|
-
prerelease:
|
|
4
|
+
hash: 21
|
|
5
|
+
prerelease:
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
8
|
- 0
|
|
9
|
-
-
|
|
10
|
-
version: 0.0.
|
|
9
|
+
- 5
|
|
10
|
+
version: 0.0.5
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Andrew Forward (forked project from Andre Lewis and Bill Eisenhauer)
|
|
@@ -15,51 +15,155 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date:
|
|
18
|
+
date: 2011-01-19 00:00:00 -05:00
|
|
19
19
|
default_executable:
|
|
20
|
-
dependencies:
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
dependencies:
|
|
21
|
+
- !ruby/object:Gem::Dependency
|
|
22
|
+
name: json_pure
|
|
23
|
+
prerelease: false
|
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
hash: 3
|
|
30
|
+
segments:
|
|
31
|
+
- 0
|
|
32
|
+
version: "0"
|
|
33
|
+
type: :runtime
|
|
34
|
+
version_requirements: *id001
|
|
35
|
+
- !ruby/object:Gem::Dependency
|
|
36
|
+
name: hoe
|
|
37
|
+
prerelease: false
|
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
39
|
+
none: false
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
hash: 3
|
|
44
|
+
segments:
|
|
45
|
+
- 0
|
|
46
|
+
version: "0"
|
|
47
|
+
type: :runtime
|
|
48
|
+
version_requirements: *id002
|
|
49
|
+
- !ruby/object:Gem::Dependency
|
|
50
|
+
name: rspec
|
|
51
|
+
prerelease: false
|
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
|
53
|
+
none: false
|
|
54
|
+
requirements:
|
|
55
|
+
- - ">="
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
hash: 3
|
|
58
|
+
segments:
|
|
59
|
+
- 0
|
|
60
|
+
version: "0"
|
|
61
|
+
type: :development
|
|
62
|
+
version_requirements: *id003
|
|
63
|
+
- !ruby/object:Gem::Dependency
|
|
64
|
+
name: autotest
|
|
65
|
+
prerelease: false
|
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
|
67
|
+
none: false
|
|
68
|
+
requirements:
|
|
69
|
+
- - ">="
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
hash: 3
|
|
72
|
+
segments:
|
|
73
|
+
- 0
|
|
74
|
+
version: "0"
|
|
75
|
+
type: :development
|
|
76
|
+
version_requirements: *id004
|
|
77
|
+
- !ruby/object:Gem::Dependency
|
|
78
|
+
name: ZenTest
|
|
79
|
+
prerelease: false
|
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
|
81
|
+
none: false
|
|
82
|
+
requirements:
|
|
83
|
+
- - ">="
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
hash: 3
|
|
86
|
+
segments:
|
|
87
|
+
- 0
|
|
88
|
+
version: "0"
|
|
89
|
+
type: :development
|
|
90
|
+
version_requirements: *id005
|
|
91
|
+
- !ruby/object:Gem::Dependency
|
|
92
|
+
name: standalone_migrations
|
|
93
|
+
prerelease: false
|
|
94
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
|
95
|
+
none: false
|
|
96
|
+
requirements:
|
|
97
|
+
- - ">="
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
hash: 3
|
|
100
|
+
segments:
|
|
101
|
+
- 0
|
|
102
|
+
version: "0"
|
|
103
|
+
type: :development
|
|
104
|
+
version_requirements: *id006
|
|
105
|
+
- !ruby/object:Gem::Dependency
|
|
106
|
+
name: mysql
|
|
107
|
+
prerelease: false
|
|
108
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
|
109
|
+
none: false
|
|
110
|
+
requirements:
|
|
111
|
+
- - ">="
|
|
112
|
+
- !ruby/object:Gem::Version
|
|
113
|
+
hash: 3
|
|
114
|
+
segments:
|
|
115
|
+
- 0
|
|
116
|
+
version: "0"
|
|
117
|
+
type: :development
|
|
118
|
+
version_requirements: *id007
|
|
119
|
+
description: Enhanced the google geocoder to take advantage of the premier account offering
|
|
23
120
|
email:
|
|
24
121
|
- aforward@gmail.com
|
|
25
122
|
executables: []
|
|
26
123
|
|
|
27
124
|
extensions: []
|
|
28
125
|
|
|
29
|
-
extra_rdoc_files:
|
|
30
|
-
|
|
31
|
-
- README.markdown
|
|
126
|
+
extra_rdoc_files: []
|
|
127
|
+
|
|
32
128
|
files:
|
|
129
|
+
- .gitignore
|
|
130
|
+
- Gemfile
|
|
131
|
+
- Gemfile.lock
|
|
132
|
+
- History.txt
|
|
33
133
|
- Manifest.txt
|
|
34
134
|
- README.markdown
|
|
35
135
|
- Rakefile
|
|
36
|
-
-
|
|
136
|
+
- autotest/discover.rb
|
|
137
|
+
- geokit-premier-0.0.4.gem
|
|
138
|
+
- geokit-premier.gemspec
|
|
37
139
|
- lib/geokit.rb
|
|
140
|
+
- lib/geokit/geocoders.rb
|
|
141
|
+
- lib/geokit/geocoders_mine.rb
|
|
38
142
|
- lib/geokit/mappable.rb
|
|
143
|
+
- lib/geokit/version.rb
|
|
39
144
|
- spec/geocoder_spec.rb
|
|
40
145
|
- spec/spec_helper.rb
|
|
41
146
|
- test/test_base_geocoder.rb
|
|
42
147
|
- test/test_bounds.rb
|
|
43
148
|
- test/test_ca_geocoder.rb
|
|
44
149
|
- test/test_geoloc.rb
|
|
45
|
-
- test/test_google_geocoder.rb
|
|
46
|
-
- test/test_latlng.rb
|
|
47
|
-
- test/test_multi_geocoder.rb
|
|
48
|
-
- test/test_us_geocoder.rb
|
|
49
|
-
- test/test_yahoo_geocoder.rb
|
|
50
150
|
- test/test_geoplugin_geocoder.rb
|
|
151
|
+
- test/test_google_geocoder.rb
|
|
51
152
|
- test/test_google_reverse_geocoder.rb
|
|
52
153
|
- test/test_inflector.rb
|
|
53
154
|
- test/test_ipgeocoder.rb
|
|
155
|
+
- test/test_latlng.rb
|
|
156
|
+
- test/test_multi_geocoder.rb
|
|
54
157
|
- test/test_multi_ip_geocoder.rb
|
|
158
|
+
- test/test_us_geocoder.rb
|
|
159
|
+
- test/test_yahoo_geocoder.rb
|
|
55
160
|
has_rdoc: true
|
|
56
|
-
homepage:
|
|
161
|
+
homepage: https://github.com/aforward/geokit-premier-gem
|
|
57
162
|
licenses: []
|
|
58
163
|
|
|
59
164
|
post_install_message:
|
|
60
|
-
rdoc_options:
|
|
61
|
-
|
|
62
|
-
- README.markdown
|
|
165
|
+
rdoc_options: []
|
|
166
|
+
|
|
63
167
|
require_paths:
|
|
64
168
|
- lib
|
|
65
169
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
@@ -83,12 +187,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
83
187
|
requirements: []
|
|
84
188
|
|
|
85
189
|
rubyforge_project:
|
|
86
|
-
rubygems_version: 1.
|
|
190
|
+
rubygems_version: 1.4.1
|
|
87
191
|
signing_key:
|
|
88
|
-
specification_version:
|
|
89
|
-
summary:
|
|
192
|
+
specification_version: 3
|
|
193
|
+
summary: Enables google geocoding using a premier account
|
|
90
194
|
test_files:
|
|
91
195
|
- spec/geocoder_spec.rb
|
|
196
|
+
- spec/spec_helper.rb
|
|
92
197
|
- test/test_base_geocoder.rb
|
|
93
198
|
- test/test_bounds.rb
|
|
94
199
|
- test/test_ca_geocoder.rb
|