lib/geokit/multi_geocoder.rb
module Geokit
module Geocoders
# --------------------------------------------------------------------------
# The Multi Geocoder
# --------------------------------------------------------------------------
# Provides methods to geocode with a variety of geocoding service providers,
# plus failover among providers in the order you configure. When 2nd
# parameter is set 'true', perform ip location lookup with 'address' as the
# ip address.
#
# Goal:
# - homogenize the results of multiple geocoders
#
# Limitations:
# - currently only provides the first result. Sometimes geocoders will
# return multiple results.
# - currently discards the "accuracy" component of the geocoding calls
class MultiGeocoder < Geocoder
private
# This method will call one or more geocoders in the order specified in
# the configuration until one of the geocoders work.
#
# The failover approach is crucial for production-grade apps, but is
# rarely used.
# 98% of your geocoding calls will be successful with the first call
def self.do_geocode(address, *args)
provider_order = provider_order_for(address, args)
provider_order.each do |provider|
klass = geocoder(provider)
begin
res = klass.send :geocode, address, *args
return res if res.success?
rescue => e
logger.error("An error has occurred during geocoding: #{e}\n" \
"Address: #{address}. Provider: #{provider}")
end
end
# If we get here, we failed completely.
GeoLoc.new
end
# This method will call one or more geocoders in the order specified in
# the configuration until one of the geocoders work, only this time it's
# going to try to reverse geocode a geographical point.
def self.do_reverse_geocode(latlng, *args)
provider_order = provider_order_for(latlng, args)
provider_order.each do |provider|
klass = geocoder(provider)
begin
res = klass.send :reverse_geocode, latlng
return res if res.success?
rescue => e
logger.error("An error has occurred during geocoding: #{e}\n" \
"Latlng: #{latlng}. Provider: #{provider}")
end
end
# If we get here, we failed completely.
GeoLoc.new
end
def self.geocoder(provider)
class_name = "#{Geokit::Inflector.camelize(provider.to_s)}Geocoder"
raise Geokit::Geocoders::NoSuchGeocoderError unless Geokit::Geocoders.const_defined? class_name
Geokit::Geocoders.const_get class_name
end
def self.provider_order_for(address, args)
if args.last.is_a?(Hash) && args.last.key?(:provider_order)
args.last.delete(:provider_order)
else
if address.is_a?(String) && /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.match(address)
Geokit::Geocoders.ip_provider_order
else
Geokit::Geocoders.provider_order
end
end
end
end
end
end