module Geokit module Geocoders # Provides geocoding based upon an IP address. The underlying web service is a hostip.info # which sources their data through a combination of publicly available information as well # as community contributions. class IpGeocoder < BaseIpGeocoder private # Given an IP address, returns a GeoLoc instance which contains latitude, # longitude, city, and country code. Sets the success attribute to false if the ip # parameter does not match an ip address. def self.do_geocode(ip, _=nil) return GeoLoc.new unless valid_ip?(ip) url = submit_url(ip) res = call_geocoder_service(url) return GeoLoc.new unless net_adapter.success?(res) ensure_utf8_encoding(res) body = res.body body = body.encode('UTF-8') if body.respond_to? :encode parse :yaml, body end def self.submit_url(ip) "http://api.hostip.info/get_html.php?ip=#{ip}&position=true" end # Converts the body to YAML since its in the form of: # # Country: UNITED STATES (US) # City: Sugar Grove, IL # Latitude: 41.7696 # Longitude: -88.4588 # # then instantiates a GeoLoc instance to populate with location data. def self.parse_yaml(yaml) # :nodoc: loc = new_loc loc.city, loc.state_code = yaml['City'].split(', ') loc.country, loc.country_code = yaml['Country'].split(' (') loc.lat = yaml['Latitude'] loc.lng = yaml['Longitude'] loc.country_code.chop! loc.success = !(loc.city =~ /\(.+\)/) loc end # Forces UTF-8 encoding on the body # Rails expects string input to be UTF-8 # hostip.info specifies the charset encoding in the headers # thus extract encoding from headers and tell Rails about it by forcing it def self.ensure_utf8_encoding(res) if (enc_string = extract_charset(res)) res.body.force_encoding(enc_string.upcase) if res.body.respond_to?(:force_encoding) res.body.encode('UTF-8') end end # Extracts charset out of the response headers def self.extract_charset(res) if (content_type = res['content-type']) capture = content_type.match(/charset=(.+)/) capture && capture[1] end end end end end