require 'open-uri' require 'rexml/document' ## # Library for looking up coordinates with Google's Geocoding API. # # http://www.google.com/apis/maps/documentation/#Geocoding_HTTP_Request class GoogleGeocode ## # Base error class class Error < RuntimeError; end ## # Raised when you try to locate an invalid address. class AddressError < Error; end ## # Raised when you use an invalid key. class KeyError < Error; end ## # Location struct Location = Struct.new :address, :latitude, :longitude ## # Creates a new GoogleGeocode that will use Google Maps API key +key+. You # can sign up for an API key here: # # http://www.google.com/apis/maps/signup.html def initialize(key) @key = key @url = URI.parse 'http://maps.google.com/maps/geo' end ## # Locates +address+ returning a Location struct. def locate(address) get :q => address end private def parse_response(xml) l = Location.new l.address = xml.elements['/kml/Response/Placemark/address'].text coordinates = xml.elements['/kml/Response/Placemark/Point/coordinates'].text l.longitude, l.latitude, = coordinates.split(',').map { |v| v.to_f } return l end def check_error(obj) obj = REXML::Document.new obj.read unless REXML::Document === obj status = obj.elements['/kml/Response/Status/code'].text.to_i case status when 200 then # ignore, ok when 500 then raise Error, 'server error' when 601 then raise AddressError, 'missing address' when 602 then raise AddressError, 'unknown address' when 603 then raise AddressError, 'unavailable address' when 610 then raise KeyError, 'invalid key' when 620 then raise KeyError, 'too many queries' else raise Error, "unknown error #{status}" end end def get(params) url = make_url params url.open do |xml| res = REXML::Document.new xml.read check_error res return parse_response(res) end rescue OpenURI::HTTPError => e check_error e.io raise end def make_url(params) params[:key] = @key params[:output] = 'xml' escaped_params = params.sort_by { |k,v| k.to_s }.map do |k,v| "#{URI.escape k.to_s}=#{URI.escape v.to_s}" end url = @url.dup url.query = escaped_params.join '&' return url end end ## # A Location contains the following fields: # # +latitude+:: Latitude of the location # +longitude+:: Longitude of the location # +address+:: Street address of the result. class GoogleGeocode::Location ## # The coordinates for this location. def coordinates [latitude, longitude] end end