yannski-geokit 1.2.6

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