soey-geokit 1.2.4

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.
@@ -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