lib/geokit/geocoders.rb in andre-geokit-1.2.6 vs lib/geokit/geocoders.rb in andre-geokit-1.3.1

- old
+ new

@@ -35,10 +35,14 @@ def url_escape(s) s.gsub(/([^ a-zA-Z0-9_.-]+)/nu) do '%' + $1.unpack('H2' * $1.size).join('%').upcase end.tr(' ', '+') end + + def camelize(str) + str.split('_').map {|w| w.capitalize}.join + end end # Contains a range of geocoders: # # ### "regular" address geocoders @@ -68,29 +72,41 @@ @@google = 'REPLACE_WITH_YOUR_GOOGLE_KEY' @@geocoder_us = false @@geocoder_ca = false @@geonames = false @@provider_order = [:google,:us] + @@ip_provider_order = [:geo_plugin,:ip] @@logger=Logger.new(STDOUT) @@logger.level=Logger::INFO + @@domain = nil - [:yahoo, :google, :geocoder_us, :geocoder_ca, :geonames, :provider_order, :timeout, - :proxy_addr, :proxy_port, :proxy_user, :proxy_pass,:logger].each do |sym| - class_eval <<-EOS, __FILE__, __LINE__ - def self.#{sym} - if defined?(#{sym.to_s.upcase}) - #{sym.to_s.upcase} - else - @@#{sym} - end + def self.__define_accessors + class_variables.each do |v| + sym = v.delete("@").to_sym + unless self.respond_to? sym + module_eval <<-EOS, __FILE__, __LINE__ + def self.#{sym} + value = if defined?(#{sym.to_s.upcase}) + #{sym.to_s.upcase} + else + @@#{sym} + end + if value.is_a?(Hash) + value = (self.domain.nil? ? nil : value[self.domain]) || value.values.first + end + value + end + + def self.#{sym}=(obj) + @@#{sym} = obj + end + EOS end - - def self.#{sym}=(obj) - @@#{sym} = obj - end - EOS + end end + + __define_accessors # Error which is thrown in the event a geocoding error occurs. class GeocodeError < StandardError; end # ------------------------------------------------------------------------------------------- @@ -103,13 +119,12 @@ # Main method which calls the do_geocode template method which subclasses # are responsible for implementing. Returns a populated GeoLoc or an # empty one with a failed success code. def self.geocode(address) res = do_geocode(address) - return res.success? ? res : GeoLoc.new + return res.nil? ? GeoLoc.new : res end - # Main method which calls the do_reverse_geocode template method which subclasses # are responsible for implementing. Returns a populated GeoLoc or an # empty one with a failed success code. def self.reverse_geocode(latlng) res = do_reverse_geocode(latlng) @@ -296,10 +311,12 @@ res.city=doc.elements['//City'].text if doc.elements['//City'] && doc.elements['//City'].text != nil res.state=doc.elements['//State'].text if doc.elements['//State'] && doc.elements['//State'].text != nil res.zip=doc.elements['//Zip'].text if doc.elements['//Zip'] && doc.elements['//Zip'].text != nil res.street_address=doc.elements['//Address'].text if doc.elements['//Address'] && doc.elements['//Address'].text != nil res.precision=doc.elements['//Result'].attributes['precision'] if doc.elements['//Result'] + # set the accuracy as google does (added by Andruby) + res.accuracy=%w{unknown country state state city zip zip+4 street address building}.index(res.precision) res.success=true return res else logger.info "Yahoo was unable to geocode address: "+address return GeoLoc.new @@ -439,12 +456,12 @@ res.zip = doc.elements['.//PostalCodeNumber'].text if doc.elements['.//PostalCodeNumber'] res.street_address = doc.elements['.//ThoroughfareName'].text if doc.elements['.//ThoroughfareName'] # Translate accuracy into Yahoo-style token address, street, zip, zip+4, city, state, country # For Google, 1=low accuracy, 8=high accuracy address_details=doc.elements['.//*[local-name() = "AddressDetails"]'] - accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0 - res.precision=%w{unknown country state state city zip zip+4 street address building}[accuracy] + res.accuracy = address_details ? address_details.attributes['Accuracy'].to_i : 0 + res.precision=%w{unknown country state state city zip zip+4 street address building}[res.accuracy] res.success=true return res end end @@ -461,11 +478,11 @@ def self.do_geocode(ip) return GeoLoc.new unless /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(ip) response = self.call_geocoder_service("http://www.geoplugin.net/xml.gp?ip=#{ip}") return response.is_a?(Net::HTTPSuccess) ? parse_xml(response.body) : GeoLoc.new rescue - logger.error "Caught an error during GeloPluginGeocoder geocoding call: "+$! + logger.error "Caught an error during GeoPluginGeocoder geocoding call: "+$! return GeoLoc.new end def self.parse_xml(xml) xml = REXML::Document.new(xml) @@ -474,11 +491,11 @@ geo.city = xml.elements['//geoplugin_city'].text geo.state = xml.elements['//geoplugin_region'].text geo.country_code = xml.elements['//geoplugin_countryCode'].text geo.lat = xml.elements['//geoplugin_latitude'].text.to_f geo.lng = xml.elements['//geoplugin_longitude'].text.to_f - geo.success = !geo.city.empty? + geo.success = !!geo.city && !geo.city.empty? return geo end end # Provides geocoding based upon an IP address. The underlying web service is a hostip.info @@ -527,29 +544,33 @@ # ------------------------------------------------------------------------------------------- # The Multi Geocoder # ------------------------------------------------------------------------------------------- # Provides methods to geocode with a variety of geocoding service providers, plus failover - # among providers in the order you configure. + # 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 + 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) - Geokit::Geocoders::provider_order.each do |provider| + geocode_ip = !!/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(address) + provider_order = geocode_ip ? Geokit::Geocoders::ip_provider_order : Geokit::Geocoders::provider_order + + provider_order.each do |provider| begin - klass = Geokit::Geocoders.const_get "#{provider.to_s.capitalize}Geocoder" + klass = Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder" res = klass.send :geocode, address return res if res.success? rescue 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}") end