geokit-with-premier-support 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest.txt ADDED
@@ -0,0 +1,21 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.markdown
4
+ Rakefile
5
+ geokit.gemspec
6
+ lib/geokit.rb
7
+ lib/geokit/geocoders.rb
8
+ lib/geokit/mappable.rb
9
+ test/test_base_geocoder.rb
10
+ test/test_bounds.rb
11
+ test/test_ca_geocoder.rb
12
+ test/test_geoloc.rb
13
+ test/test_geoplugin_geocoder.rb
14
+ test/test_google_geocoder.rb
15
+ test/test_google_reverse_geocoder.rb
16
+ test/test_inflector.rb
17
+ test/test_ipgeocoder.rb
18
+ test/test_latlng.rb
19
+ test/test_multi_geocoder.rb
20
+ test/test_us_geocoder.rb
21
+ test/test_yahoo_geocoder.rb
data/README.markdown ADDED
@@ -0,0 +1,277 @@
1
+ ## GEOKIT GEM DESCRIPTION
2
+
3
+ For Premier instructions visit:
4
+
5
+ http://groups.google.com/group/geokit/browse_thread/thread/14813f74bc0da41f/cd9d30f79dfa7088?lnk=gst&q=premier#cd9d30f79dfa7088
6
+
7
+ The Geokit gem provides:
8
+
9
+ * Distance calculations between two points on the earth. Calculate the distance in miles, kilometers, or nautical miles, with all the trigonometry abstracted away by GeoKit.
10
+ * Geocoding from multiple providers. It supports Google, Yahoo, Geocoder.us, and Geocoder.ca geocoders, and others. It provides a uniform response structure from all of them.
11
+ It also provides a fail-over mechanism, in case your input fails to geocode in one service.
12
+ * Rectangular bounds calculations: is a point within a given rectangular bounds?
13
+ * Heading and midpoint calculations
14
+
15
+ Combine this gem with the [geokit-rails plugin](http://github.com/andre/geokit-rails/tree/master) to get location-based finders for your Rails app.
16
+
17
+ * Geokit Documentation at Rubyforge [http://geokit.rubyforge.org](http://geokit.rubyforge.org).
18
+ * Repository at Github: [http://github.com/andre/geokit-gem/tree/master](http://github.com/andre/geokit-gem/tree/master).
19
+ * Follow the Google Group for updates and discussion on Geokit: [http://groups.google.com/group/geokit](http://groups.google.com/group/geokit)
20
+
21
+ ## INSTALL
22
+
23
+ sudo gem install geokit
24
+
25
+ ## QUICK START
26
+
27
+ irb> require 'rubygems'
28
+ irb> require 'geokit'
29
+ irb> a=Geokit::Geocoders::YahooGeocoder.geocode '140 Market St, San Francisco, CA'
30
+ irb> a.ll
31
+ => 37.79363,-122.396116
32
+ irb> b=Geokit::Geocoders::YahooGeocoder.geocode '789 Geary St, San Francisco, CA'
33
+ irb> b.ll
34
+ => 37.786217,-122.41619
35
+ irb> a.distance_to(b)
36
+ => 1.21120007413626
37
+ irb> a.heading_to(b)
38
+ => 244.959832435678
39
+ irb(main):006:0> c=a.midpoint_to(b) # what's halfway from a to b?
40
+ irb> c.ll
41
+ => "37.7899239257175,-122.406153503469"
42
+ irb(main):008:0> d=c.endpoint(90,10) # what's 10 miles to the east of c?
43
+ irb> d.ll
44
+ => "37.7897825005142,-122.223214776155"
45
+
46
+ FYI, that `.ll` method means "latitude longitude".
47
+
48
+ See the RDOC more more ... there are also operations on rectangular bounds (e.g., determining if a point is within bounds, find the center, etc).
49
+
50
+ ## CONFIGURATION
51
+
52
+ If you're using this gem by itself, here are the configuration options:
53
+
54
+ # These defaults are used in Geokit::Mappable.distance_to and in acts_as_mappable
55
+ Geokit::default_units = :miles
56
+ Geokit::default_formula = :sphere
57
+
58
+ # This is the timeout value in seconds to be used for calls to the geocoder web
59
+ # services. For no timeout at all, comment out the setting. The timeout unit
60
+ # is in seconds.
61
+ Geokit::Geocoders::timeout = 3
62
+
63
+ # These settings are used if web service calls must be routed through a proxy.
64
+ # These setting can be nil if not needed, otherwise, addr and port must be
65
+ # filled in at a minimum. If the proxy requires authentication, the username
66
+ # and password can be provided as well.
67
+ Geokit::Geocoders::proxy_addr = nil
68
+ Geokit::Geocoders::proxy_port = nil
69
+ Geokit::Geocoders::proxy_user = nil
70
+ Geokit::Geocoders::proxy_pass = nil
71
+
72
+ # This is your yahoo application key for the Yahoo Geocoder.
73
+ # See http://developer.yahoo.com/faq/index.html#appid
74
+ # and http://developer.yahoo.com/maps/rest/V1/geocode.html
75
+ Geokit::Geocoders::yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
76
+
77
+ # This is your Google Maps geocoder key.
78
+ # See http://www.google.com/apis/maps/signup.html
79
+ # and http://www.google.com/apis/maps/documentation/#Geocoding_Examples
80
+ Geokit::Geocoders::google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
81
+
82
+ # You can also set multiple API KEYS for different domains that may be directed to this same application.
83
+ # The domain from which the current user is being directed will automatically be updated for Geokit via
84
+ # the GeocoderControl class, which gets it's begin filter mixed into the ActionController.
85
+ # You define these keys with a Hash as follows:
86
+ #Geokit::Geocoders::google = { 'rubyonrails.org' => 'RUBY_ON_RAILS_API_KEY', 'ruby-docs.org' => 'RUBY_DOCS_API_KEY' }
87
+
88
+ # This is your username and password for geocoder.us.
89
+ # To use the free service, the value can be set to nil or false. For
90
+ # usage tied to an account, the value should be set to username:password.
91
+ # See http://geocoder.us
92
+ # and http://geocoder.us/user/signup
93
+ Geokit::Geocoders::geocoder_us = false
94
+
95
+ # This is your authorization key for geocoder.ca.
96
+ # To use the free service, the value can be set to nil or false. For
97
+ # usage tied to an account, set the value to the key obtained from
98
+ # Geocoder.ca.
99
+ # See http://geocoder.ca
100
+ # and http://geocoder.ca/?register=1
101
+ Geokit::Geocoders::geocoder_ca = false
102
+
103
+ # require "external_geocoder.rb"
104
+ # Please see the section "writing your own geocoders" for more information.
105
+ # Geokit::Geocoders::external_key = 'REPLACE_WITH_YOUR_API_KEY'
106
+
107
+ # This is the order in which the geocoders are called in a failover scenario
108
+ # If you only want to use a single geocoder, put a single symbol in the array.
109
+ # Valid symbols are :google, :yahoo, :us, and :ca.
110
+ # Be aware that there are Terms of Use restrictions on how you can use the
111
+ # various geocoders. Make sure you read up on relevant Terms of Use for each
112
+ # geocoder you are going to use.
113
+ Geokit::Geocoders::provider_order = [:google,:us]
114
+
115
+ # The IP provider order. Valid symbols are :ip,:geo_plugin.
116
+ # As before, make sure you read up on relevant Terms of Use for each.
117
+ # Geokit::Geocoders::ip_provider_order = [:external,:geo_plugin,:ip]
118
+
119
+ If you're using this gem with the [geokit-rails plugin](http://github.com/andre/geokit-rails/tree/master), the plugin
120
+ creates a template with these settings and places it in `config/initializers/geokit_config.rb`.
121
+
122
+ ## SUPPORTED GEOCODERS
123
+
124
+ ### "regular" address geocoders
125
+ * Yahoo Geocoder - requires an API key.
126
+ * Geocoder.us - may require authentication if performing more than the free request limit.
127
+ * Geocoder.ca - for Canada; may require authentication as well.
128
+ * Geonames - a free geocoder
129
+
130
+ ### address geocoders that also provide reverse geocoding
131
+ * Google Geocoder - requires an API key. Also supports multiple results and bounding box/country code biasing.
132
+
133
+ ### IP address geocoders
134
+ * IP Geocoder - geocodes an IP address using hostip.info's web service.
135
+ * Geoplugin.net -- another IP address geocoder
136
+
137
+ ### Google Geocoder Tricks
138
+
139
+ The Google Geocoder sports a number of useful tricks that elevate it a little bit above the rest of the currently supported geocoders. For starters, it returns a `suggested_bounds` property for all your geocoded results, so you can more easily decide where and how to center a map on the places you geocode. Here's a quick example:
140
+
141
+ irb> res = Geokit::Geocoders::GoogleGeocoder.geocode('140 Market St, San Francisco, CA')
142
+ irb> pp res.suggested_bounds
143
+ #<Geokit::Bounds:0x53b36c
144
+ @ne=#<Geokit::LatLng:0x53b204 @lat=37.7968528, @lng=-122.3926933>,
145
+ @sw=#<Geokit::LatLng:0x53b2b8 @lat=37.7905576, @lng=-122.3989885>>
146
+
147
+ In addition, you can use viewport or country code biasing to make sure the geocoders prefers results within a specific area. Say we wanted to geocode the city of Syracuse in Italy. A normal geocoding query would look like this:
148
+
149
+ irb> res = Geokit::Geocoder::GoogleGeocoder.geocode('Syracuse')
150
+ irb> res.full_address
151
+ => "Syracuse, NY, USA"
152
+
153
+ Not exactly what we were looking for. We know that Syracuse is in Italy, so we can tell the Google Geocoder to prefer results from Italy first, and then wander the Syracuses of the world. To do that, we have to pass Italy's ccTLD (country code top-level domain) to the `:bias` option of the `geocode` method. You can find a comprehensive list of all ccTLDs here: http://en.wikipedia.org/wiki/CcTLD.
154
+
155
+ irb> res = Geokit::Geocoder::GoogleGeocoder.geocode('Syracuse', :bias => 'it')
156
+ irb> res.full_address
157
+ => "Syracuse, Italy"
158
+
159
+ Alternatively, we can speficy the geocoding bias as a bounding box object. Say we wanted to geocode the Winnetka district in Los Angeles.
160
+
161
+ irb> res = Geokit::Geocoder::GoogleGeocoder.geocode('Winnetka')
162
+ irb> res.full_address
163
+ => "Winnetka, IL, USA"
164
+
165
+ Not it. What we can do is tell the geocoder to return results only from in and around LA.
166
+
167
+ irb> la_bounds = Geokit::Geocoder::GoogleGeocoder.geocode('Los Angeles').suggested_bounds
168
+ irb> res = Geokit::Geocoder::GoogleGeocoder.geocode('Winnetka', :bias => la_bounds)
169
+ irb> res.full_address
170
+ => "Winnetka, California, USA"
171
+
172
+
173
+ ### The Multigeocoder
174
+ Multi Geocoder - provides failover for the physical location geocoders, and also IP address geocoders. Its configured by setting Geokit::Geocoders::provider_order, and Geokit::Geocoders::ip_provider_order. You should call the Multi-Geocoder with its :geocode method, supplying one address parameter which is either a real street address, or an ip address. For example:
175
+
176
+ Geokit::Geocoders::MultiGeocoder.geocode("900 Sycamore Drive")
177
+
178
+ Geokit::Geocoders::MultiGeocoder.geocode("12.12.12.12")
179
+
180
+ ## MULTIPLE RESULTS
181
+ Some geocoding services will return multple results if the there isn't one clear result.
182
+ Geoloc can capture multiple results through its "all" method. Currently only the Google geocoder
183
+ supports multiple results:
184
+
185
+ irb> geo=Geokit::Geocoders::GoogleGeocoder.geocode("900 Sycamore Drive")
186
+ irb> geo.full_address
187
+ => "900 Sycamore Dr, Arkadelphia, AR 71923, USA"
188
+ irb> geo.all.size
189
+ irb> geo.all.each { |e| puts e.full_address }
190
+ 900 Sycamore Dr, Arkadelphia, AR 71923, USA
191
+ 900 Sycamore Dr, Burkburnett, TX 76354, USA
192
+ 900 Sycamore Dr, TN 38361, USA
193
+ ....
194
+
195
+ geo.all is just an array of additional Geolocs, so do what you want with it. If you call .all on a
196
+ geoloc that doesn't have any additional results, you will get an array of one.
197
+
198
+
199
+ ## NOTES ON WHAT'S WHERE
200
+
201
+ mappable.rb contains the Mappable module, which provides basic
202
+ distance calculation methods, i.e., calculating the distance
203
+ between two points.
204
+
205
+ mappable.rb also contains LatLng, GeoLoc, and Bounds.
206
+ LatLng is a simple container for latitude and longitude, but
207
+ it's made more powerful by mixing in the above-mentioned Mappable
208
+ module -- therefore, you can calculate easily the distance between two
209
+ LatLng ojbects with `distance = first.distance_to(other)`
210
+
211
+ GeoLoc (also in mappable.rb) represents an address or location which
212
+ has been geocoded. You can get the city, zipcode, street address, etc.
213
+ from a GeoLoc object. GeoLoc extends LatLng, so you also get lat/lng
214
+ AND the Mappable modeule goodness for free.
215
+
216
+ geocoders.rb contains all the geocoder implemenations. All the gercoders
217
+ inherit from a common base (class Geocoder) and implement the private method
218
+ do_geocode.
219
+
220
+ ## WRITING YOUR OWN GEOCODERS
221
+
222
+ If you would like to write your own geocoders, you can do so by requiring 'geokit' or 'geokit/geocoders.rb' in a new file and subclassing the base class (which is class "Geocoder").
223
+ You must then also require such extenal file back in your main geokit configuration.
224
+
225
+ require "geokit"
226
+
227
+ module Geokit
228
+ module Geocoders
229
+
230
+ # Should be overriden as Geokit::Geocoders::external_key in your configuration file
231
+ @@external_key = 'REPLACE_WITH_YOUR_API_KEY'
232
+ __define_accessors
233
+
234
+ # Replace name 'External' (below) with the name of your custom geocoder class
235
+ # and use :external to specify this geocoder in your list of geocoders.
236
+ class ExternalGeocoder < Geocoder
237
+ private
238
+ def self.do_geocode(address, options = {})
239
+ # Main geocoding method
240
+ end
241
+
242
+ def self.parse_http_resp(body) # :nodoc:
243
+ # Helper method to parse http response. See geokit/geocoders.rb.
244
+ end
245
+ end
246
+
247
+ end
248
+ end
249
+
250
+ ## GOOGLE GROUP
251
+
252
+ Follow the Google Group for updates and discussion on Geokit: http://groups.google.com/group/geokit
253
+
254
+ ## LICENSE
255
+
256
+ (The MIT License)
257
+
258
+ Copyright (c) 2007-2009 Andre Lewis and Bill Eisenhauer
259
+
260
+ Permission is hereby granted, free of charge, to any person obtaining
261
+ a copy of this software and associated documentation files (the
262
+ 'Software'), to deal in the Software without restriction, including
263
+ without limitation the rights to use, copy, modify, merge, publish,
264
+ distribute, sublicense, and/or sell copies of the Software, and to
265
+ permit persons to whom the Software is furnished to do so, subject to
266
+ the following conditions:
267
+
268
+ The above copyright notice and this permission notice shall be
269
+ included in all copies or substantial portions of the Software.
270
+
271
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
272
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
273
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
274
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
275
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
276
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
277
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ # -*- ruby -*-
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', Geokit::VERSION) do |p|
16
+ #p.rubyforge_name = 'geokit' # if different than lowercase project name
17
+ p.developer('Andre Lewis', 'andre@earthcode.com')
18
+ p.summary="Geokit provides geocoding and distance calculation in an easy-to-use API"
19
+ end
20
+
21
+
22
+ # vim: syntax=Ruby
data/lib/geokit.rb ADDED
@@ -0,0 +1,30 @@
1
+ module Geokit
2
+ VERSION = '1.5.0'
3
+ # These defaults are used in Geokit::Mappable.distance_to and in acts_as_mappable
4
+ @@default_units = :miles
5
+ @@default_formula = :sphere
6
+
7
+ [:default_units, :default_formula].each do |sym|
8
+ class_eval <<-EOS, __FILE__, __LINE__
9
+ def self.#{sym}
10
+ if defined?(#{sym.to_s.upcase})
11
+ #{sym.to_s.upcase}
12
+ else
13
+ @@#{sym}
14
+ end
15
+ end
16
+
17
+ def self.#{sym}=(obj)
18
+ @@#{sym} = obj
19
+ end
20
+ EOS
21
+ end
22
+ end
23
+
24
+ path = File.expand_path(File.dirname(__FILE__))
25
+ $:.unshift path unless $:.include?(path)
26
+ require 'geokit/geocoders'
27
+ require 'geokit/mappable'
28
+
29
+ # make old-style module name "GeoKit" equivalent to new-style "Geokit"
30
+ GeoKit=Geokit
@@ -0,0 +1,663 @@
1
+ require 'net/http'
2
+ require 'rexml/document'
3
+ require 'yaml'
4
+ require 'timeout'
5
+ require 'logger'
6
+
7
+ module Geokit
8
+
9
+ class TooManyQueriesError < StandardError; end
10
+
11
+ module Inflector
12
+
13
+ extend self
14
+
15
+ def titleize(word)
16
+ humanize(underscore(word)).gsub(/\b([a-z])/u) { $1.capitalize }
17
+ end
18
+
19
+ def underscore(camel_cased_word)
20
+ camel_cased_word.to_s.gsub(/::/, '/').
21
+ gsub(/([A-Z]+)([A-Z][a-z])/u,'\1_\2').
22
+ gsub(/([a-z\d])([A-Z])/u,'\1_\2').
23
+ tr("-", "_").
24
+ downcase
25
+ end
26
+
27
+ def humanize(lower_case_and_underscored_word)
28
+ lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
29
+ end
30
+
31
+ def snake_case(s)
32
+ return s.downcase if s =~ /^[A-Z]+$/u
33
+ s.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/u, '_\&') =~ /_*(.*)/
34
+ return $+.downcase
35
+
36
+ end
37
+
38
+ def url_escape(s)
39
+ s.gsub(/([^ a-zA-Z0-9_.-]+)/nu) do
40
+ '%' + $1.unpack('H2' * $1.size).join('%').upcase
41
+ end.tr(' ', '+')
42
+ end
43
+
44
+ def camelize(str)
45
+ str.split('_').map {|w| w.capitalize}.join
46
+ end
47
+ end
48
+
49
+ # Contains a range of geocoders:
50
+ #
51
+ # ### "regular" address geocoders
52
+ # * Yahoo Geocoder - requires an API key.
53
+ # * Geocoder.us - may require authentication if performing more than the free request limit.
54
+ # * Geocoder.ca - for Canada; may require authentication as well.
55
+ # * Geonames - a free geocoder
56
+ #
57
+ # ### address geocoders that also provide reverse geocoding
58
+ # * Google Geocoder - requires an API key.
59
+ #
60
+ # ### IP address geocoders
61
+ # * IP Geocoder - geocodes an IP address using hostip.info's web service.
62
+ # * Geoplugin.net -- another IP address geocoder
63
+ #
64
+ # ### The Multigeocoder
65
+ # * Multi Geocoder - provides failover for the physical location geocoders.
66
+ #
67
+ # Some of these geocoders require configuration. You don't have to provide it here. See the README.
68
+ module Geocoders
69
+ @@proxy_addr = nil
70
+ @@proxy_port = nil
71
+ @@proxy_user = nil
72
+ @@proxy_pass = nil
73
+ @@request_timeout = nil
74
+ @@yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
75
+ @@google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
76
+ @@google_client_id = nil
77
+ @@geocoder_us = false
78
+ @@geocoder_ca = false
79
+ @@geonames = false
80
+ @@provider_order = [:google,:us]
81
+ @@ip_provider_order = [:geo_plugin,:ip]
82
+ @@logger=Logger.new(STDOUT)
83
+ @@logger.level=Logger::INFO
84
+ @@domain = nil
85
+
86
+ def self.__define_accessors
87
+ class_variables.each do |v|
88
+ sym = v.to_s.delete("@").to_sym
89
+ unless self.respond_to? sym
90
+ module_eval <<-EOS, __FILE__, __LINE__
91
+ def self.#{sym}
92
+ value = if defined?(#{sym.to_s.upcase})
93
+ #{sym.to_s.upcase}
94
+ else
95
+ @@#{sym}
96
+ end
97
+ if value.is_a?(Hash)
98
+ value = (self.domain.nil? ? nil : value[self.domain]) || value.values.first
99
+ end
100
+ value
101
+ end
102
+
103
+ def self.#{sym}=(obj)
104
+ @@#{sym} = obj
105
+ end
106
+ EOS
107
+ end
108
+ end
109
+ end
110
+
111
+ __define_accessors
112
+
113
+ # Error which is thrown in the event a geocoding error occurs.
114
+ class GeocodeError < StandardError; end
115
+
116
+ # -------------------------------------------------------------------------------------------
117
+ # Geocoder Base class -- every geocoder should inherit from this
118
+ # -------------------------------------------------------------------------------------------
119
+
120
+ # The Geocoder base class which defines the interface to be used by all
121
+ # other geocoders.
122
+ class Geocoder
123
+ # Main method which calls the do_geocode template method which subclasses
124
+ # are responsible for implementing. Returns a populated GeoLoc or an
125
+ # empty one with a failed success code.
126
+ def self.geocode(address, options = {})
127
+ res = do_geocode(address, options)
128
+ return res.nil? ? GeoLoc.new : res
129
+ end
130
+ # Main method which calls the do_reverse_geocode template method which subclasses
131
+ # are responsible for implementing. Returns a populated GeoLoc or an
132
+ # empty one with a failed success code.
133
+ def self.reverse_geocode(latlng)
134
+ res = do_reverse_geocode(latlng)
135
+ return res.success? ? res : GeoLoc.new
136
+ end
137
+
138
+ # Call the geocoder service using the timeout if configured.
139
+ def self.call_geocoder_service(url)
140
+ Timeout::timeout(Geokit::Geocoders::request_timeout) { return self.do_get(url) } if Geokit::Geocoders::request_timeout
141
+ return self.do_get(url)
142
+ rescue TimeoutError
143
+ return nil
144
+ end
145
+
146
+ # Not all geocoders can do reverse geocoding. So, unless the subclass explicitly overrides this method,
147
+ # a call to reverse_geocode will return an empty GeoLoc. If you happen to be using MultiGeocoder,
148
+ # this will cause it to failover to the next geocoder, which will hopefully be one which supports reverse geocoding.
149
+ def self.do_reverse_geocode(latlng)
150
+ return GeoLoc.new
151
+ end
152
+
153
+ protected
154
+
155
+ def self.logger()
156
+ Geokit::Geocoders::logger
157
+ end
158
+
159
+ private
160
+
161
+ # Wraps the geocoder call around a proxy if necessary.
162
+ def self.do_get(url)
163
+ uri = URI.parse(url)
164
+ req = Net::HTTP::Get.new(url)
165
+ req.basic_auth(uri.user, uri.password) if uri.userinfo
166
+ res = Net::HTTP::Proxy(GeoKit::Geocoders::proxy_addr,
167
+ GeoKit::Geocoders::proxy_port,
168
+ GeoKit::Geocoders::proxy_user,
169
+ GeoKit::Geocoders::proxy_pass).start(uri.host, uri.port) { |http| http.get(uri.path + "?" + uri.query) }
170
+ return res
171
+ end
172
+
173
+ # Adds subclass' geocode method making it conveniently available through
174
+ # the base class.
175
+ def self.inherited(clazz)
176
+ class_name = clazz.name.split('::').last
177
+ src = <<-END_SRC
178
+ def self.#{Geokit::Inflector.underscore(class_name)}(address, options = {})
179
+ #{class_name}.geocode(address, options)
180
+ end
181
+ END_SRC
182
+ class_eval(src)
183
+ end
184
+ end
185
+
186
+ # -------------------------------------------------------------------------------------------
187
+ # "Regular" Address geocoders
188
+ # -------------------------------------------------------------------------------------------
189
+
190
+ # Geocoder CA geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_CA variable to
191
+ # contain true or false based upon whether authentication is to occur. Conforms to the
192
+ # interface set by the Geocoder class.
193
+ #
194
+ # Returns a response like:
195
+ # <?xml version="1.0" encoding="UTF-8" ?>
196
+ # <geodata>
197
+ # <latt>49.243086</latt>
198
+ # <longt>-123.153684</longt>
199
+ # </geodata>
200
+ class CaGeocoder < Geocoder
201
+
202
+ private
203
+
204
+ # Template method which does the geocode lookup.
205
+ def self.do_geocode(address, options = {})
206
+ raise ArgumentError('Geocoder.ca requires a GeoLoc argument') unless address.is_a?(GeoLoc)
207
+ url = construct_request(address)
208
+ res = self.call_geocoder_service(url)
209
+ return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
210
+ xml = res.body
211
+ logger.debug "Geocoder.ca geocoding. Address: #{address}. Result: #{xml}"
212
+ # Parse the document.
213
+ doc = REXML::Document.new(xml)
214
+ address.lat = doc.elements['//latt'].text
215
+ address.lng = doc.elements['//longt'].text
216
+ address.success = true
217
+ return address
218
+ rescue
219
+ logger.error "Caught an error during Geocoder.ca geocoding call: "+$!
220
+ return GeoLoc.new
221
+ end
222
+
223
+ # Formats the request in the format acceptable by the CA geocoder.
224
+ def self.construct_request(location)
225
+ url = ""
226
+ url += add_ampersand(url) + "stno=#{location.street_number}" if location.street_address
227
+ url += add_ampersand(url) + "addresst=#{Geokit::Inflector::url_escape(location.street_name)}" if location.street_address
228
+ url += add_ampersand(url) + "city=#{Geokit::Inflector::url_escape(location.city)}" if location.city
229
+ url += add_ampersand(url) + "prov=#{location.state}" if location.state
230
+ url += add_ampersand(url) + "postal=#{location.zip}" if location.zip
231
+ url += add_ampersand(url) + "auth=#{Geokit::Geocoders::geocoder_ca}" if Geokit::Geocoders::geocoder_ca
232
+ url += add_ampersand(url) + "geoit=xml"
233
+ 'http://geocoder.ca/?' + url
234
+ end
235
+
236
+ def self.add_ampersand(url)
237
+ url && url.length > 0 ? "&" : ""
238
+ end
239
+ end
240
+
241
+ # Geocoder Us geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_US variable to
242
+ # contain true or false based upon whether authentication is to occur. Conforms to the
243
+ # interface set by the Geocoder class.
244
+ class UsGeocoder < Geocoder
245
+
246
+ private
247
+ def self.do_geocode(address, options = {})
248
+ address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
249
+
250
+ query = (address_str =~ /^\d{5}(?:-\d{4})?$/ ? "zip" : "address") + "=#{Geokit::Inflector::url_escape(address_str)}"
251
+ url = if GeoKit::Geocoders::geocoder_us
252
+ "http://#{GeoKit::Geocoders::geocoder_us}@geocoder.us/member/service/csv/geocode"
253
+ else
254
+ "http://geocoder.us/service/csv/geocode"
255
+ end
256
+
257
+ url = "#{url}?#{query}"
258
+ res = self.call_geocoder_service(url)
259
+
260
+ return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
261
+ data = res.body
262
+ logger.debug "Geocoder.us geocoding. Address: #{address}. Result: #{data}"
263
+ array = data.chomp.split(',')
264
+
265
+ if array.length == 5
266
+ res=GeoLoc.new
267
+ res.lat,res.lng,res.city,res.state,res.zip=array
268
+ res.country_code='US'
269
+ res.success=true
270
+ return res
271
+ elsif array.length == 6
272
+ res=GeoLoc.new
273
+ res.lat,res.lng,res.street_address,res.city,res.state,res.zip=array
274
+ res.country_code='US'
275
+ res.success=true
276
+ return res
277
+ else
278
+ logger.info "geocoder.us was unable to geocode address: "+address
279
+ return GeoLoc.new
280
+ end
281
+ rescue
282
+ logger.error "Caught an error during geocoder.us geocoding call: "+$!
283
+ return GeoLoc.new
284
+
285
+ end
286
+ end
287
+
288
+ # Yahoo geocoder implementation. Requires the Geokit::Geocoders::YAHOO variable to
289
+ # contain a Yahoo API key. Conforms to the interface set by the Geocoder class.
290
+ class YahooGeocoder < Geocoder
291
+
292
+ private
293
+
294
+ # Template method which does the geocode lookup.
295
+ def self.do_geocode(address, options = {})
296
+ address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
297
+ url="http://api.local.yahoo.com/MapsService/V1/geocode?appid=#{Geokit::Geocoders::yahoo}&location=#{Geokit::Inflector::url_escape(address_str)}"
298
+ res = self.call_geocoder_service(url)
299
+ return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
300
+ xml = res.body
301
+ doc = REXML::Document.new(xml)
302
+ logger.debug "Yahoo geocoding. Address: #{address}. Result: #{xml}"
303
+
304
+ if doc.elements['//ResultSet']
305
+ res=GeoLoc.new
306
+
307
+ #basic
308
+ res.lat=doc.elements['//Latitude'].text
309
+ res.lng=doc.elements['//Longitude'].text
310
+ res.country_code=doc.elements['//Country'].text
311
+ res.provider='yahoo'
312
+
313
+ #extended - false if not available
314
+ res.city=doc.elements['//City'].text if doc.elements['//City'] && doc.elements['//City'].text != nil
315
+ res.state=doc.elements['//State'].text if doc.elements['//State'] && doc.elements['//State'].text != nil
316
+ res.zip=doc.elements['//Zip'].text if doc.elements['//Zip'] && doc.elements['//Zip'].text != nil
317
+ res.street_address=doc.elements['//Address'].text if doc.elements['//Address'] && doc.elements['//Address'].text != nil
318
+ res.precision=doc.elements['//Result'].attributes['precision'] if doc.elements['//Result']
319
+ # set the accuracy as google does (added by Andruby)
320
+ res.accuracy=%w{unknown country state state city zip zip+4 street address building}.index(res.precision)
321
+ res.success=true
322
+ return res
323
+ else
324
+ logger.info "Yahoo was unable to geocode address: "+address
325
+ return GeoLoc.new
326
+ end
327
+
328
+ rescue
329
+ logger.info "Caught an error during Yahoo geocoding call: "+$!
330
+ return GeoLoc.new
331
+ end
332
+ end
333
+
334
+ # Another geocoding web service
335
+ # http://www.geonames.org
336
+ class GeonamesGeocoder < Geocoder
337
+
338
+ private
339
+
340
+ # Template method which does the geocode lookup.
341
+ def self.do_geocode(address, options = {})
342
+ address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
343
+ # geonames need a space seperated search string
344
+ address_str.gsub!(/,/, " ")
345
+ params = "/postalCodeSearch?placename=#{Geokit::Inflector::url_escape(address_str)}&maxRows=10"
346
+
347
+ if(GeoKit::Geocoders::geonames)
348
+ url = "http://ws.geonames.net#{params}&username=#{GeoKit::Geocoders::geonames}"
349
+ else
350
+ url = "http://ws.geonames.org#{params}"
351
+ end
352
+
353
+ res = self.call_geocoder_service(url)
354
+
355
+ return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
356
+
357
+ xml=res.body
358
+ logger.debug "Geonames geocoding. Address: #{address}. Result: #{xml}"
359
+ doc=REXML::Document.new(xml)
360
+
361
+ if(doc.elements['//geonames/totalResultsCount'].text.to_i > 0)
362
+ res=GeoLoc.new
363
+
364
+ # only take the first result
365
+ res.lat=doc.elements['//code/lat'].text if doc.elements['//code/lat']
366
+ res.lng=doc.elements['//code/lng'].text if doc.elements['//code/lng']
367
+ res.country_code=doc.elements['//code/countryCode'].text if doc.elements['//code/countryCode']
368
+ res.provider='genomes'
369
+ res.city=doc.elements['//code/name'].text if doc.elements['//code/name']
370
+ res.state=doc.elements['//code/adminName1'].text if doc.elements['//code/adminName1']
371
+ res.zip=doc.elements['//code/postalcode'].text if doc.elements['//code/postalcode']
372
+ res.success=true
373
+ return res
374
+ else
375
+ logger.info "Geonames was unable to geocode address: "+address
376
+ return GeoLoc.new
377
+ end
378
+
379
+ rescue
380
+ logger.error "Caught an error during Geonames geocoding call: "+$!
381
+ end
382
+ end
383
+
384
+ # -------------------------------------------------------------------------------------------
385
+ # Address geocoders that also provide reverse geocoding
386
+ # -------------------------------------------------------------------------------------------
387
+
388
+ # Google geocoder implementation. Requires the Geokit::Geocoders::GOOGLE variable to
389
+ # contain a Google API key. Conforms to the interface set by the Geocoder class.
390
+ class GoogleGeocoder < Geocoder
391
+
392
+ private
393
+
394
+ # Template method which does the reverse-geocode lookup.
395
+ def self.do_reverse_geocode(latlng)
396
+ latlng=LatLng.normalize(latlng)
397
+ 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")
398
+ # 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"))
399
+ return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
400
+ xml = res.body
401
+ logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{xml}"
402
+ return self.xml2GeoLoc(xml)
403
+ end
404
+
405
+ # Template method which does the geocode lookup.
406
+ #
407
+ # Supports viewport/country code biasing
408
+ #
409
+ # ==== OPTIONS
410
+ # * :bias - This option makes the Google Geocoder return results biased to a particular
411
+ # country or viewport. Country code biasing is achieved by passing the ccTLD
412
+ # ('uk' for .co.uk, for example) as a :bias value. For a list of ccTLD's,
413
+ # look here: http://en.wikipedia.org/wiki/CcTLD. By default, the geocoder
414
+ # will be biased to results within the US (ccTLD .com).
415
+ #
416
+ # If you'd like the Google Geocoder to prefer results within a given viewport,
417
+ # you can pass a Geokit::Bounds object as the :bias value.
418
+ #
419
+ # ==== EXAMPLES
420
+ # # By default, the geocoder will return Syracuse, NY
421
+ # Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse').country_code # => 'US'
422
+ # # With country code biasing, it returns Syracuse in Sicily, Italy
423
+ # Geokit::Geocoders::GoogleGeocoder.geocode('Syracuse', :bias => :it).country_code # => 'IT'
424
+ #
425
+ # # By default, the geocoder will return Winnetka, IL
426
+ # Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka').state # => 'IL'
427
+ # # When biased to an bounding box around California, it will now return the Winnetka neighbourhood, CA
428
+ # bounds = Geokit::Bounds.normalize([34.074081, -118.694401], [34.321129, -118.399487])
429
+ # Geokit::Geocoders::GoogleGeocoder.geocode('Winnetka', :bias => bounds).state # => 'CA'
430
+ def self.do_geocode(address, options = {})
431
+ bias_str = options[:bias] ? construct_bias_string_from_options(options[:bias]) : ''
432
+ address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
433
+
434
+ if Geokit::Geocoders::google_client_id.nil?
435
+ # Using google maps normal
436
+ 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")
437
+ else
438
+ # Using google maps premier
439
+ res = self.call_geocoder_service("http://maps.google.com/maps/geo?q=#{Geokit::Inflector::url_escape(address_str)}&output=xml#{bias_str}&client=#{Geokit::Geocoders::google_client_id}&oe=utf-8")
440
+ end
441
+
442
+ return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
443
+ xml = res.body
444
+ logger.debug "Google geocoding. Address: #{address}. Result: #{xml}"
445
+ return self.xml2GeoLoc(xml, address)
446
+ end
447
+
448
+ def self.construct_bias_string_from_options(bias)
449
+ if bias.is_a?(String) or bias.is_a?(Symbol)
450
+ # country code biasing
451
+ "&gl=#{bias.to_s.downcase}"
452
+ elsif bias.is_a?(Bounds)
453
+ # viewport biasing
454
+ "&ll=#{bias.center.ll}&spn=#{bias.to_span.ll}"
455
+ end
456
+ end
457
+
458
+ def self.xml2GeoLoc(xml, address="")
459
+ doc=REXML::Document.new(xml)
460
+
461
+ if doc.elements['//kml/Response/Status/code'].text == '200'
462
+ geoloc = nil
463
+ # Google can return multiple results as //Placemark elements.
464
+ # iterate through each and extract each placemark as a geoloc
465
+ doc.each_element('//Placemark') do |e|
466
+ extracted_geoloc = extract_placemark(e) # g is now an instance of GeoLoc
467
+ if geoloc.nil?
468
+ # first time through, geoloc is still nil, so we make it the geoloc we just extracted
469
+ geoloc = extracted_geoloc
470
+ else
471
+ # second (and subsequent) iterations, we push additional
472
+ # geolocs onto "geoloc.all"
473
+ geoloc.all.push(extracted_geoloc)
474
+ end
475
+ end
476
+ return geoloc
477
+ elsif doc.elements['//kml/Response/Status/code'].text == '620'
478
+ raise Geokit::TooManyQueriesError
479
+ else
480
+ logger.info "Google was unable to geocode address: "+address
481
+ return GeoLoc.new
482
+ end
483
+
484
+ rescue Geokit::TooManyQueriesError
485
+ # re-raise because of other rescue
486
+ 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."
487
+ rescue
488
+ logger.error "Caught an error during Google geocoding call: "+$!
489
+ return GeoLoc.new
490
+ end
491
+
492
+ # extracts a single geoloc from a //placemark element in the google results xml
493
+ def self.extract_placemark(doc)
494
+ res = GeoLoc.new
495
+ coordinates=doc.elements['.//coordinates'].text.to_s.split(',')
496
+
497
+ #basics
498
+ res.lat=coordinates[1]
499
+ res.lng=coordinates[0]
500
+ res.country_code=doc.elements['.//CountryNameCode'].text if doc.elements['.//CountryNameCode']
501
+ res.provider='google'
502
+
503
+ #extended -- false if not not available
504
+ res.city = doc.elements['.//LocalityName'].text if doc.elements['.//LocalityName']
505
+ res.state = doc.elements['.//AdministrativeAreaName'].text if doc.elements['.//AdministrativeAreaName']
506
+ res.province = doc.elements['.//SubAdministrativeAreaName'].text if doc.elements['.//SubAdministrativeAreaName']
507
+ res.full_address = doc.elements['.//address'].text if doc.elements['.//address'] # google provides it
508
+ res.zip = doc.elements['.//PostalCodeNumber'].text if doc.elements['.//PostalCodeNumber']
509
+ res.street_address = doc.elements['.//ThoroughfareName'].text if doc.elements['.//ThoroughfareName']
510
+ res.country = doc.elements['.//CountryName'].text if doc.elements['.//CountryName']
511
+ res.district = doc.elements['.//DependentLocalityName'].text if doc.elements['.//DependentLocalityName']
512
+ # Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
513
+ # For Google, 1=low accuracy, 8=high accuracy
514
+ address_details=doc.elements['.//*[local-name() = "AddressDetails"]']
515
+ res.accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
516
+ res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy]
517
+
518
+ # google returns a set of suggested boundaries for the geocoded result
519
+ if suggested_bounds = doc.elements['//LatLonBox']
520
+ res.suggested_bounds = Bounds.normalize(
521
+ [suggested_bounds.attributes['south'], suggested_bounds.attributes['west']],
522
+ [suggested_bounds.attributes['north'], suggested_bounds.attributes['east']])
523
+ end
524
+
525
+ res.success=true
526
+
527
+ return res
528
+ end
529
+ end
530
+
531
+
532
+ # -------------------------------------------------------------------------------------------
533
+ # IP Geocoders
534
+ # -------------------------------------------------------------------------------------------
535
+
536
+ # Provides geocoding based upon an IP address. The underlying web service is geoplugin.net
537
+ class GeoPluginGeocoder < Geocoder
538
+ private
539
+
540
+ def self.do_geocode(ip, options = {})
541
+ return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
542
+ response = self.call_geocoder_service("http://www.geoplugin.net/xml.gp?ip=#{ip}")
543
+ return response.is_a?(Net::HTTPSuccess) ? parse_xml(response.body) : GeoLoc.new
544
+ rescue
545
+ logger.error "Caught an error during GeoPluginGeocoder geocoding call: "+$!
546
+ return GeoLoc.new
547
+ end
548
+
549
+ def self.parse_xml(xml)
550
+ xml = REXML::Document.new(xml)
551
+ geo = GeoLoc.new
552
+ geo.provider='geoPlugin'
553
+ geo.city = xml.elements['//geoplugin_city'].text
554
+ geo.state = xml.elements['//geoplugin_region'].text
555
+ geo.country_code = xml.elements['//geoplugin_countryCode'].text
556
+ geo.lat = xml.elements['//geoplugin_latitude'].text.to_f
557
+ geo.lng = xml.elements['//geoplugin_longitude'].text.to_f
558
+ geo.success = !!geo.city && !geo.city.empty?
559
+ return geo
560
+ end
561
+ end
562
+
563
+ # Provides geocoding based upon an IP address. The underlying web service is a hostip.info
564
+ # which sources their data through a combination of publicly available information as well
565
+ # as community contributions.
566
+ class IpGeocoder < Geocoder
567
+
568
+ private
569
+
570
+ # Given an IP address, returns a GeoLoc instance which contains latitude,
571
+ # longitude, city, and country code. Sets the success attribute to false if the ip
572
+ # parameter does not match an ip address.
573
+ def self.do_geocode(ip, options = {})
574
+ return GeoLoc.new if '0.0.0.0' == ip
575
+ return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
576
+ url = "http://api.hostip.info/get_html.php?ip=#{ip}&position=true"
577
+ response = self.call_geocoder_service(url)
578
+ response.is_a?(Net::HTTPSuccess) ? parse_body(response.body) : GeoLoc.new
579
+ rescue
580
+ logger.error "Caught an error during HostIp geocoding call: "+$!
581
+ return GeoLoc.new
582
+ end
583
+
584
+ # Converts the body to YAML since its in the form of:
585
+ #
586
+ # Country: UNITED STATES (US)
587
+ # City: Sugar Grove, IL
588
+ # Latitude: 41.7696
589
+ # Longitude: -88.4588
590
+ #
591
+ # then instantiates a GeoLoc instance to populate with location data.
592
+ def self.parse_body(body) # :nodoc:
593
+ yaml = YAML.load(body)
594
+ res = GeoLoc.new
595
+ res.provider = 'hostip'
596
+ res.city, res.state = yaml['City'].split(', ')
597
+ country, res.country_code = yaml['Country'].split(' (')
598
+ res.lat = yaml['Latitude']
599
+ res.lng = yaml['Longitude']
600
+ res.country_code.chop!
601
+ res.success = !(res.city =~ /\(.+\)/)
602
+ res
603
+ end
604
+ end
605
+
606
+ # -------------------------------------------------------------------------------------------
607
+ # The Multi Geocoder
608
+ # -------------------------------------------------------------------------------------------
609
+
610
+ # Provides methods to geocode with a variety of geocoding service providers, plus failover
611
+ # among providers in the order you configure. When 2nd parameter is set 'true', perform
612
+ # ip location lookup with 'address' as the ip address.
613
+ #
614
+ # Goal:
615
+ # - homogenize the results of multiple geocoders
616
+ #
617
+ # Limitations:
618
+ # - currently only provides the first result. Sometimes geocoders will return multiple results.
619
+ # - currently discards the "accuracy" component of the geocoding calls
620
+ class MultiGeocoder < Geocoder
621
+
622
+ private
623
+ # This method will call one or more geocoders in the order specified in the
624
+ # configuration until one of the geocoders work.
625
+ #
626
+ # The failover approach is crucial for production-grade apps, but is rarely used.
627
+ # 98% of your geocoding calls will be successful with the first call
628
+ def self.do_geocode(address, options = {})
629
+ geocode_ip = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.match(address)
630
+ provider_order = geocode_ip ? Geokit::Geocoders::ip_provider_order : Geokit::Geocoders::provider_order
631
+
632
+ provider_order.each do |provider|
633
+ begin
634
+ klass = Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder"
635
+ res = klass.send :geocode, address, options
636
+ return res if res.success?
637
+ rescue
638
+ 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}")
639
+ end
640
+ end
641
+ # If we get here, we failed completely.
642
+ GeoLoc.new
643
+ end
644
+
645
+ # This method will call one or more geocoders in the order specified in the
646
+ # configuration until one of the geocoders work, only this time it's going
647
+ # to try to reverse geocode a geographical point.
648
+ def self.do_reverse_geocode(latlng)
649
+ Geokit::Geocoders::provider_order.each do |provider|
650
+ begin
651
+ klass = Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder"
652
+ res = klass.send :reverse_geocode, latlng
653
+ return res if res.success?
654
+ rescue
655
+ 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}")
656
+ end
657
+ end
658
+ # If we get here, we failed completely.
659
+ GeoLoc.new
660
+ end
661
+ end
662
+ end
663
+ end