dreamcat4-geokit 1.3.0

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