soey-geokit 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,549 @@
1
+ require 'net/http'
2
+ require 'rexml/document'
3
+ require 'yaml'
4
+ require 'timeout'
5
+ require 'logger'
6
+
7
+ module Geokit
8
+ module Inflector
9
+
10
+ extend self
11
+
12
+ def titleize(word)
13
+ humanize(underscore(word)).gsub(/\b([a-z])/u) { $1.capitalize }
14
+ end
15
+
16
+ def underscore(camel_cased_word)
17
+ camel_cased_word.to_s.gsub(/::/, '/').
18
+ gsub(/([A-Z]+)([A-Z][a-z])/u,'\1_\2').
19
+ gsub(/([a-z\d])([A-Z])/u,'\1_\2').
20
+ tr("-", "_").
21
+ downcase
22
+ end
23
+
24
+ def humanize(lower_case_and_underscored_word)
25
+ lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
26
+ end
27
+
28
+ def snake_case(s)
29
+ return s.downcase if s =~ /^[A-Z]+$/u
30
+ s.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/u, '_\&') =~ /_*(.*)/
31
+ return $+.downcase
32
+
33
+ end
34
+
35
+ def url_escape(s)
36
+ s.gsub(/([^ a-zA-Z0-9_.-]+)/nu) do
37
+ '%' + $1.unpack('H2' * $1.size).join('%').upcase
38
+ end.tr(' ', '+')
39
+ end
40
+ end
41
+
42
+ # Contains a range of geocoders:
43
+ #
44
+ # ### "regular" address geocoders
45
+ # * Yahoo Geocoder - requires an API key.
46
+ # * Geocoder.us - may require authentication if performing more than the free request limit.
47
+ # * Geocoder.ca - for Canada; may require authentication as well.
48
+ # * Geonames - a free geocoder
49
+ #
50
+ # ### address geocoders that also provide reverse geocoding
51
+ # * Google Geocoder - requires an API key.
52
+ #
53
+ # ### IP address geocoders
54
+ # * IP Geocoder - geocodes an IP address using hostip.info's web service.
55
+ # * Geoplugin.net -- another IP address geocoder
56
+ #
57
+ # ### The Multigeocoder
58
+ # * Multi Geocoder - provides failover for the physical location geocoders.
59
+ #
60
+ # Some of these geocoders require configuration. You don't have to provide it here. See the README.
61
+ module Geocoders
62
+ @@proxy_addr = nil
63
+ @@proxy_port = nil
64
+ @@proxy_user = nil
65
+ @@proxy_pass = nil
66
+ @@timeout = nil
67
+ @@yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
68
+ @@google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
69
+ @@geocoder_us = false
70
+ @@geocoder_ca = false
71
+ @@geonames = false
72
+ @@provider_order = [:google,:us]
73
+ @@logger=Logger.new(STDOUT)
74
+ @@logger.level=Logger::INFO
75
+
76
+ [:yahoo, :google, :geocoder_us, :geocoder_ca, :geonames, :provider_order, :timeout,
77
+ :proxy_addr, :proxy_port, :proxy_user, :proxy_pass,:logger].each do |sym|
78
+ class_eval <<-EOS, __FILE__, __LINE__
79
+ def self.#{sym}
80
+ if defined?(#{sym.to_s.upcase})
81
+ #{sym.to_s.upcase}
82
+ else
83
+ @@#{sym}
84
+ end
85
+ end
86
+
87
+ def self.#{sym}=(obj)
88
+ @@#{sym} = obj
89
+ end
90
+ EOS
91
+ end
92
+
93
+ # Error which is thrown in the event a geocoding error occurs.
94
+ class GeocodeError < StandardError; end
95
+
96
+ # -------------------------------------------------------------------------------------------
97
+ # Geocoder Base class -- every geocoder should inherit from this
98
+ # -------------------------------------------------------------------------------------------
99
+
100
+ # The Geocoder base class which defines the interface to be used by all
101
+ # other geocoders.
102
+ class Geocoder
103
+ # Main method which calls the do_geocode template method which subclasses
104
+ # are responsible for implementing. Returns a populated GeoLoc or an
105
+ # empty one with a failed success code.
106
+ def self.geocode(address)
107
+ res = do_geocode(address)
108
+ return res.success ? res : GeoLoc.new
109
+ end
110
+
111
+ # Main method which calls the do_reverse_geocode template method which subclasses
112
+ # are responsible for implementing. Returns a populated GeoLoc or an
113
+ # empty one with a failed success code.
114
+ def self.reverse_geocode(latlng)
115
+ res = do_reverse_geocode(latlng)
116
+ return res.success ? res : GeoLoc.new
117
+ end
118
+
119
+ # Call the geocoder service using the timeout if configured.
120
+ def self.call_geocoder_service(url)
121
+ timeout(Geokit::Geocoders::timeout) { return self.do_get(url) } if Geokit::Geocoders::timeout
122
+ return self.do_get(url)
123
+ rescue TimeoutError
124
+ return nil
125
+ end
126
+
127
+ # Not all geocoders can do reverse geocoding. So, unless the subclass explicitly overrides this method,
128
+ # a call to reverse_geocode will return an empty GeoLoc. If you happen to be using MultiGeocoder,
129
+ # this will cause it to failover to the next geocoder, which will hopefully be one which supports reverse geocoding.
130
+ def self.do_reverse_geocode(latlng)
131
+ return GeoLoc.new
132
+ end
133
+
134
+ protected
135
+
136
+ def self.logger()
137
+ Geokit::Geocoders::logger
138
+ end
139
+
140
+ private
141
+
142
+ # Wraps the geocoder call around a proxy if necessary.
143
+ def self.do_get(url)
144
+ uri = URI.parse(url)
145
+ req = Net::HTTP::Get.new(url)
146
+ req.basic_auth(uri.user, uri.password) if uri.userinfo
147
+ res = Net::HTTP::Proxy(GeoKit::Geocoders::proxy_addr,
148
+ GeoKit::Geocoders::proxy_port,
149
+ GeoKit::Geocoders::proxy_user,
150
+ GeoKit::Geocoders::proxy_pass).start(uri.host, uri.port) { |http| http.request(req) }
151
+
152
+ return res
153
+ end
154
+
155
+ # Adds subclass' geocode method making it conveniently available through
156
+ # the base class.
157
+ def self.inherited(clazz)
158
+ class_name = clazz.name.split('::').last
159
+ src = <<-END_SRC
160
+ def self.#{Geokit::Inflector.underscore(class_name)}(address)
161
+ #{class_name}.geocode(address)
162
+ end
163
+ END_SRC
164
+ class_eval(src)
165
+ end
166
+ end
167
+
168
+ # -------------------------------------------------------------------------------------------
169
+ # "Regular" Address geocoders
170
+ # -------------------------------------------------------------------------------------------
171
+
172
+ # Geocoder CA geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_CA variable to
173
+ # contain true or false based upon whether authentication is to occur. Conforms to the
174
+ # interface set by the Geocoder class.
175
+ #
176
+ # Returns a response like:
177
+ # <?xml version="1.0" encoding="UTF-8" ?>
178
+ # <geodata>
179
+ # <latt>49.243086</latt>
180
+ # <longt>-123.153684</longt>
181
+ # </geodata>
182
+ class CaGeocoder < Geocoder
183
+
184
+ private
185
+
186
+ # Template method which does the geocode lookup.
187
+ def self.do_geocode(address)
188
+ raise ArgumentError('Geocoder.ca requires a GeoLoc argument') unless address.is_a?(GeoLoc)
189
+ url = construct_request(address)
190
+ res = self.call_geocoder_service(url)
191
+ return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
192
+ xml = res.body
193
+ logger.debug "Geocoder.ca geocoding. Address: #{address}. Result: #{xml}"
194
+ # Parse the document.
195
+ doc = REXML::Document.new(xml)
196
+ address.lat = doc.elements['//latt'].text
197
+ address.lng = doc.elements['//longt'].text
198
+ address.success = true
199
+ return address
200
+ rescue
201
+ logger.error "Caught an error during Geocoder.ca geocoding call: "+$!
202
+ return GeoLoc.new
203
+ end
204
+
205
+ # Formats the request in the format acceptable by the CA geocoder.
206
+ def self.construct_request(location)
207
+ url = ""
208
+ url += add_ampersand(url) + "stno=#{location.street_number}" if location.street_address
209
+ url += add_ampersand(url) + "addresst=#{Geokit::Inflector::url_escape(location.street_name)}" if location.street_address
210
+ url += add_ampersand(url) + "city=#{Geokit::Inflector::url_escape(location.city)}" if location.city
211
+ url += add_ampersand(url) + "prov=#{location.state}" if location.state
212
+ url += add_ampersand(url) + "postal=#{location.zip}" if location.zip
213
+ url += add_ampersand(url) + "auth=#{Geokit::Geocoders::geocoder_ca}" if Geokit::Geocoders::geocoder_ca
214
+ url += add_ampersand(url) + "geoit=xml"
215
+ 'http://geocoder.ca/?' + url
216
+ end
217
+
218
+ def self.add_ampersand(url)
219
+ url && url.length > 0 ? "&" : ""
220
+ end
221
+ end
222
+
223
+
224
+ # Geocoder Us geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_US variable to
225
+ # contain true or false based upon whether authentication is to occur. Conforms to the
226
+ # interface set by the Geocoder class.
227
+ class UsGeocoder < Geocoder
228
+
229
+ private
230
+ def self.do_geocode(address)
231
+ address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
232
+
233
+ query = (address_str =~ /^\d{5}(?:-\d{4})?$/ ? "zip" : "address") + "=#{Geokit::Inflector::url_escape(address_str)}"
234
+ url = if GeoKit::Geocoders::geocoder_us
235
+ "http://#{GeoKit::Geocoders::geocoder_us}@geocoder.us/member/service/csv/geocode"
236
+ else
237
+ "http://geocoder.us/service/csv/geocode"
238
+ end
239
+
240
+ url = "#{url}?#{query}"
241
+ res = self.call_geocoder_service(url)
242
+
243
+ return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
244
+ data = res.body
245
+ logger.debug "Geocoder.us geocoding. Address: #{address}. Result: #{data}"
246
+ array = data.chomp.split(',')
247
+
248
+ if array.length == 5
249
+ res=GeoLoc.new
250
+ res.lat,res.lng,res.city,res.state,res.zip=array
251
+ res.country_code='US'
252
+ res.success=true
253
+ return res
254
+ elsif array.length == 6
255
+ res=GeoLoc.new
256
+ res.lat,res.lng,res.street_address,res.city,res.state,res.zip=array
257
+ res.country_code='US'
258
+ res.success=true
259
+ return res
260
+ else
261
+ logger.info "geocoder.us was unable to geocode address: "+address
262
+ return GeoLoc.new
263
+ end
264
+ rescue
265
+ logger.error "Caught an error during geocoder.us geocoding call: "+$!
266
+ return GeoLoc.new
267
+
268
+ end
269
+ end
270
+
271
+ # Yahoo geocoder implementation. Requires the Geokit::Geocoders::YAHOO variable to
272
+ # contain a Yahoo API key. Conforms to the interface set by the Geocoder class.
273
+ class YahooGeocoder < Geocoder
274
+
275
+ private
276
+
277
+ # Template method which does the geocode lookup.
278
+ def self.do_geocode(address)
279
+ address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
280
+ url="http://api.local.yahoo.com/MapsService/V1/geocode?appid=#{Geokit::Geocoders::yahoo}&location=#{Geokit::Inflector::url_escape(address_str)}"
281
+ res = self.call_geocoder_service(url)
282
+ return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
283
+ xml = res.body
284
+ doc = REXML::Document.new(xml)
285
+ logger.debug "Yahoo geocoding. Address: #{address}. Result: #{xml}"
286
+
287
+ if doc.elements['//ResultSet']
288
+ res=GeoLoc.new
289
+
290
+ #basic
291
+ res.lat=doc.elements['//Latitude'].text
292
+ res.lng=doc.elements['//Longitude'].text
293
+ res.country_code=doc.elements['//Country'].text
294
+ res.provider='yahoo'
295
+
296
+ #extended - false if not available
297
+ res.city=doc.elements['//City'].text if doc.elements['//City'] && doc.elements['//City'].text != nil
298
+ res.state=doc.elements['//State'].text if doc.elements['//State'] && doc.elements['//State'].text != nil
299
+ res.zip=doc.elements['//Zip'].text if doc.elements['//Zip'] && doc.elements['//Zip'].text != nil
300
+ res.street_address=doc.elements['//Address'].text if doc.elements['//Address'] && doc.elements['//Address'].text != nil
301
+ res.precision=doc.elements['//Result'].attributes['precision'] if doc.elements['//Result']
302
+ res.success=true
303
+ return res
304
+ else
305
+ logger.info "Yahoo was unable to geocode address: "+address
306
+ return GeoLoc.new
307
+ end
308
+
309
+ rescue
310
+ logger.info "Caught an error during Yahoo geocoding call: "+$!
311
+ return GeoLoc.new
312
+ end
313
+ end
314
+
315
+ # Another geocoding web service
316
+ # http://www.geonames.org
317
+ class GeonamesGeocoder < Geocoder
318
+
319
+ private
320
+
321
+ # Template method which does the geocode lookup.
322
+ def self.do_geocode(address)
323
+ address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
324
+ # geonames need a space seperated search string
325
+ address_str.gsub!(/,/, " ")
326
+ params = "/postalCodeSearch?placename=#{Geokit::Inflector::url_escape(address_str)}&maxRows=10"
327
+
328
+ if(GeoKit::Geocoders::geonames)
329
+ url = "http://ws.geonames.net#{params}&username=#{GeoKit::Geocoders::geonames}"
330
+ else
331
+ url = "http://ws.geonames.org#{params}"
332
+ end
333
+
334
+ res = self.call_geocoder_service(url)
335
+
336
+ return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
337
+
338
+ xml=res.body
339
+ logger.debug "Geonames geocoding. Address: #{address}. Result: #{xml}"
340
+ doc=REXML::Document.new(xml)
341
+
342
+ if(doc.elements['//geonames/totalResultsCount'].text.to_i > 0)
343
+ res=GeoLoc.new
344
+
345
+ # only take the first result
346
+ res.lat=doc.elements['//code/lat'].text if doc.elements['//code/lat']
347
+ res.lng=doc.elements['//code/lng'].text if doc.elements['//code/lng']
348
+ res.country_code=doc.elements['//code/countryCode'].text if doc.elements['//code/countryCode']
349
+ res.provider='genomes'
350
+ res.city=doc.elements['//code/name'].text if doc.elements['//code/name']
351
+ res.state=doc.elements['//code/adminName1'].text if doc.elements['//code/adminName1']
352
+ res.zip=doc.elements['//code/postalcode'].text if doc.elements['//code/postalcode']
353
+ res.success=true
354
+ return res
355
+ else
356
+ logger.info "Geonames was unable to geocode address: "+address
357
+ return GeoLoc.new
358
+ end
359
+
360
+ rescue
361
+ logger.error "Caught an error during Geonames geocoding call: "+$!
362
+ end
363
+ end
364
+
365
+ # -------------------------------------------------------------------------------------------
366
+ # Address geocoders that also provide reverse geocoding
367
+ # -------------------------------------------------------------------------------------------
368
+
369
+ # Google geocoder implementation. Requires the Geokit::Geocoders::GOOGLE variable to
370
+ # contain a Google API key. Conforms to the interface set by the Geocoder class.
371
+ class GoogleGeocoder < Geocoder
372
+
373
+ private
374
+
375
+ # Template method which does the reverse-geocode lookup.
376
+ def self.do_reverse_geocode(latlng)
377
+ latlng=LatLng.normalize(latlng)
378
+ 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")
379
+ # 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"))
380
+ return GeoLoc.new unless (res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPOK))
381
+ xml = res.body
382
+ logger.debug "Google reverse-geocoding. LL: #{latlng}. Result: #{xml}"
383
+ return self.xml2GeoLoc(xml)
384
+ end
385
+
386
+ # Template method which does the geocode lookup.
387
+ def self.do_geocode(address)
388
+ address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
389
+ res = self.call_geocoder_service("http://maps.google.com/maps/geo?q=#{Geokit::Inflector::url_escape(address_str)}&output=xml&key=#{Geokit::Geocoders::google}&oe=utf-8")
390
+ return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
391
+ xml = res.body
392
+ logger.debug "Google geocoding. Address: #{address}. Result: #{xml}"
393
+ return self.xml2GeoLoc(xml)
394
+ end
395
+
396
+ def self.xml2GeoLoc(xml)
397
+ doc=REXML::Document.new(xml)
398
+
399
+ if doc.elements['//kml/Response/Status/code'].text == '200'
400
+ res = GeoLoc.new
401
+ coordinates=doc.elements['//coordinates'].text.to_s.split(',')
402
+
403
+ #basics
404
+ res.lat=coordinates[1]
405
+ res.lng=coordinates[0]
406
+ res.country_code=doc.elements['//CountryNameCode'].text if doc.elements['//CountryNameCode']
407
+ res.provider='google'
408
+
409
+ #extended -- false if not not available
410
+ res.city = doc.elements['//LocalityName'].text if doc.elements['//LocalityName']
411
+ res.state = doc.elements['//AdministrativeAreaName'].text if doc.elements['//AdministrativeAreaName']
412
+ res.full_address = doc.elements['//address'].text if doc.elements['//address'] # google provides it
413
+ res.zip = doc.elements['//PostalCodeNumber'].text if doc.elements['//PostalCodeNumber']
414
+ res.street_address = doc.elements['//ThoroughfareName'].text if doc.elements['//ThoroughfareName']
415
+ # Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country
416
+ # For Google, 1=low accuracy, 8=high accuracy
417
+ # old way -- address_details=doc.elements['//AddressDetails','urn:oasis:names:tc:ciq:xsdschema:xAL:2.0']
418
+ address_details=doc.elements['//*[local-name() = "AddressDetails"]']
419
+ accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0
420
+ res.precision=%w{unknown country state state city zip zip+4 street address}[accuracy]
421
+ if doc.elements['//LatLonBox']
422
+ box = doc.elements['//LatLonBox']
423
+ res.bounds = {:north=>box.attributes['north'],:south=>box.attributes['south'], :east=>box.attributes['east'], :west=>box.attributes['west'] }
424
+ end
425
+ res.success=true
426
+
427
+ return res
428
+ else
429
+ logger.info "Google was unable to geocode address: "+address
430
+ return GeoLoc.new
431
+ end
432
+
433
+ rescue
434
+ logger.error "Caught an error during Google geocoding call: "+$!
435
+ return GeoLoc.new
436
+ end
437
+ end
438
+
439
+
440
+ # -------------------------------------------------------------------------------------------
441
+ # IP Geocoders
442
+ # -------------------------------------------------------------------------------------------
443
+
444
+ # Provides geocoding based upon an IP address. The underlying web service is geoplugin.net
445
+ class GeoPluginGeocoder < Geocoder
446
+ private
447
+
448
+ def self.do_geocode(ip)
449
+ return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
450
+ response = self.call_geocoder_service("http://www.geoplugin.net/xml.gp?ip=#{ip}")
451
+ return response.is_a?(Net::HTTPSuccess) ? parse_xml(response.body) : GeoLoc.new
452
+ rescue
453
+ logger.error "Caught an error during GeloPluginGeocoder geocoding call: "+$!
454
+ return GeoLoc.new
455
+ end
456
+
457
+ def self.parse_xml(xml)
458
+ xml = REXML::Document.new(xml)
459
+ geo = GeoLoc.new
460
+ geo.provider='geoPlugin'
461
+ geo.city = xml.elements['//geoplugin_city'].text
462
+ geo.state = xml.elements['//geoplugin_region'].text
463
+ geo.country_code = xml.elements['//geoplugin_countryCode'].text
464
+ geo.lat = xml.elements['//geoplugin_latitude'].text.to_f
465
+ geo.lng = xml.elements['//geoplugin_longitude'].text.to_f
466
+ geo.success = !geo.city.empty?
467
+ return geo
468
+ end
469
+ end
470
+
471
+ # Provides geocoding based upon an IP address. The underlying web service is a hostip.info
472
+ # which sources their data through a combination of publicly available information as well
473
+ # as community contributions.
474
+ class IpGeocoder < Geocoder
475
+
476
+ private
477
+
478
+ # Given an IP address, returns a GeoLoc instance which contains latitude,
479
+ # longitude, city, and country code. Sets the success attribute to false if the ip
480
+ # parameter does not match an ip address.
481
+ def self.do_geocode(ip)
482
+ return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip)
483
+ url = "http://api.hostip.info/get_html.php?ip=#{ip}&position=true"
484
+ response = self.call_geocoder_service(url)
485
+ response.is_a?(Net::HTTPSuccess) ? parse_body(response.body) : GeoLoc.new
486
+ rescue
487
+ logger.error "Caught an error during HostIp geocoding call: "+$!
488
+ return GeoLoc.new
489
+ end
490
+
491
+ # Converts the body to YAML since its in the form of:
492
+ #
493
+ # Country: UNITED STATES (US)
494
+ # City: Sugar Grove, IL
495
+ # Latitude: 41.7696
496
+ # Longitude: -88.4588
497
+ #
498
+ # then instantiates a GeoLoc instance to populate with location data.
499
+ def self.parse_body(body) # :nodoc:
500
+ yaml = YAML.load(body)
501
+ res = GeoLoc.new
502
+ res.provider = 'hostip'
503
+ res.city, res.state = yaml['City'].split(', ')
504
+ country, res.country_code = yaml['Country'].split(' (')
505
+ res.lat = yaml['Latitude']
506
+ res.lng = yaml['Longitude']
507
+ res.country_code.chop!
508
+ res.success = !(res.city =~ /\(.+\)/)
509
+ res
510
+ end
511
+ end
512
+
513
+ # -------------------------------------------------------------------------------------------
514
+ # The Multi Geocoder
515
+ # -------------------------------------------------------------------------------------------
516
+
517
+ # Provides methods to geocode with a variety of geocoding service providers, plus failover
518
+ # among providers in the order you configure.
519
+ #
520
+ # Goal:
521
+ # - homogenize the results of multiple geocoders
522
+ #
523
+ # Limitations:
524
+ # - currently only provides the first result. Sometimes geocoders will return multiple results.
525
+ # - currently discards the "accuracy" component of the geocoding calls
526
+ class MultiGeocoder < Geocoder
527
+ private
528
+
529
+ # This method will call one or more geocoders in the order specified in the
530
+ # configuration until one of the geocoders work.
531
+ #
532
+ # The failover approach is crucial for production-grade apps, but is rarely used.
533
+ # 98% of your geocoding calls will be successful with the first call
534
+ def self.do_geocode(address)
535
+ Geokit::Geocoders::provider_order.each do |provider|
536
+ begin
537
+ klass = Geokit::Geocoders.const_get "#{provider.to_s.capitalize}Geocoder"
538
+ res = klass.send :geocode, address
539
+ return res if res.success
540
+ rescue
541
+ 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}")
542
+ end
543
+ end
544
+ # If we get here, we failed completely.
545
+ GeoLoc.new
546
+ end
547
+ end
548
+ end
549
+ end