Newer
Older
Olivier Garcia
committed
require 'uri'
require 'rubygems' # for Ruby 1.8
require 'json'
rescue LoadError
raise LoadError, "Please install the 'json' or 'json_pure' gem to parse geocoder results."
##
# Human-readable name of the geocoding API.
#
def name
fail
end
##
# Symbol which is used in configuration to refer to this Lookup.
#
def handle
str = self.class.to_s
str[str.rindex(':')+1..-1].gsub(/([a-z\d]+)([A-Z])/,'\1_\2').downcase.to_sym
end
# Query the geocoding API and return a Geocoder::Result object.
# Returns +nil+ on timeout or error.
#
# Takes a search string (eg: "Mississippi Coast Coliseumf, Biloxi, MS",
# "205.128.54.202") for geocoding, or coordinates (latitude, longitude)
# for reverse geocoding. Returns an array of <tt>Geocoder::Result</tt>s.
def search(query, options = {})
query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
results(query).map{ |r|
result = result_class.new(r)
result.cache_hit = @cache_hit if cache
result
}
##
# Return the URL for a map of the given coordinates.
#
# Not necessarily implemented by all subclasses as only some lookups
# also provide maps.
#
def map_link_url(coordinates)
nil
end
##
# Array containing string descriptions of keys required by the API.
# Empty array if keys are optional or not required.
#
def required_api_key_parts
[]
end
##
# URL to use for querying the geocoding engine.
#
def query_url(query)
fail
end
robdiciuccio
committed
##
# The working Cache object.
#
def cache
if @cache.nil? and store = configuration.cache
@cache = Cache.new(store, configuration.cache_prefix)
end
@cache
end
private # -------------------------------------------------------------
##
# An object with configuration data for this particular lookup.
#
def configuration
Olivier Garcia
committed
##
# Object used to make HTTP requests.
Olivier Garcia
committed
#
def http_client
protocol = "http#{'s' if configuration.use_https}"
if proxy = configuration.send(proxy_name)
proxy_url = protocol + '://' + proxy
begin
uri = URI.parse(proxy_url)
rescue URI::InvalidURIError
raise ConfigurationError,
"Error parsing #{protocol.upcase} proxy URL: '#{proxy_url}'"
end
Net::HTTP::Proxy(uri.host, uri.port, uri.user, uri.password)
else
Net::HTTP
Olivier Garcia
committed
end
end
# Geocoder::Result object or nil on timeout or other error.
def query_url_params(query)
query.options[:params] || {}
end
def url_query_string(query)
hash_to_query(
query_url_params(query).reject{ |key,value| value.nil? }
)
end
##
# Key to use for caching a geocoding result. Usually this will be the
# request URL, but in cases where OAuth is used and the nonce,
# timestamp, etc varies from one request to another, we need to use
# something else (like the URL before OAuth encoding).
#
def cache_key(query)
query_url(query)
end
##
# Class of the result objects
#
def result_class
Geocoder::Result.const_get(self.class.to_s.split(":").last)
# Raise exception if configuration specifies it should be raised.
# Return false if exception not raised.
def raise_error(error, message = nil)
exceptions = configuration.always_raise
Alex Reisner
committed
if exceptions == :all or exceptions.include?( error.is_a?(Class) ? error : error.class )
##
# Returns a parsed search result (Ruby hash).
#
def fetch_data(query)
parse_raw_data fetch_raw_data(query)
rescue SocketError => err
raise_error(err) or warn "Geocoding API connection cannot be established."
rescue TimeoutError => err
raise_error(err) or warn "Geocoding API not responding fast enough " +
"(use Geocoder.configure(:timeout => ...) to set limit)."
def parse_json(data)
if defined?(ActiveSupport::JSON)
ActiveSupport::JSON.decode(data)
else
JSON.parse(data)
end
end
##
# Parses a raw search result (returns hash or array).
#
def parse_raw_data(raw_data)
rescue
warn "Geocoding API's response was not valid JSON."
##
# Protocol to use for communication with geocoding services.
# Set in configuration but not available for every service.
"http" + (configuration.use_https ? "s" : "")
def valid_response(response)
(200..399).include?(response.code.to_i)
end
# Fetch a raw geocoding result (JSON string).
# The result might or might not be cached.
key = cache_key(query)
if cache and body = cache[key]
@cache_hit = true
else
check_api_key_configuration!(query)
response = make_api_request(query)
body = response.body
if cache and valid_response(response)
@cache_hit = false
end
body
end
##
# Make an HTTP(S) request to a geocoding API and
# return the response object.
#
def make_api_request(query)
uri = URI.parse(query_url(query))
client = http_client.new(uri.host, uri.port)
client.use_ssl = true if configuration.use_https
client.get(uri.request_uri, configuration.http_headers)
def check_api_key_configuration!(query)
key_parts = query.lookup.required_api_key_parts
if key_parts.size > Array(configuration.api_key).size
parts_string = key_parts.size == 1 ? key_parts.first : key_parts
raise Geocoder::ConfigurationError,
"The #{query.lookup.name} API requires a key to be configured: " +
parts_string.inspect
##
# Simulate ActiveSupport's Object#to_query.
#
def hash_to_query(hash)
require 'cgi' unless defined?(CGI) && defined?(CGI.escape)
hash.collect{ |p|
p[1].nil? ? nil : p.map{ |i| CGI.escape i.to_s } * '='
}.compact.sort * '&'