class Geocoder::Lookup::Base
def base_query_url(query)
produces the full query URL. Should include the "?" a the end.
String which, when concatenated with url_query_string(query)
#
def base_query_url(query) fail end
def cache
The working Cache object.
#
def cache if @cache.nil? and store = configuration.cache cache_options = configuration.cache_options @cache = Cache.new(store, cache_options) end @cache end
def cache_key(query)
something else (like the URL before OAuth encoding).
timestamp, etc varies from one request to another, we need to use
request URL, but in cases where OAuth is used and the nonce,
Key to use for caching a geocoding result. Usually this will be the
#
def cache_key(query) base_query_url(query) + hash_to_query(cache_key_params(query)) end
def cache_key_params(query)
def cache_key_params(query) # omit api_key and token because they may vary among requests query_url_params(query).reject do |key,value| key.to_s.match(/(key|token)/) end end
def check_api_key_configuration!(query)
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 end end
def check_response_for_errors!(response)
def check_response_for_errors!(response) if response.code.to_i == 400 raise_error(Geocoder::InvalidRequest) || Geocoder.log(:warn, "Geocoding API error: 400 Bad Request") elsif response.code.to_i == 401 raise_error(Geocoder::RequestDenied) || Geocoder.log(:warn, "Geocoding API error: 401 Unauthorized") elsif response.code.to_i == 402 raise_error(Geocoder::OverQueryLimitError) || Geocoder.log(:warn, "Geocoding API error: 402 Payment Required") elsif response.code.to_i == 429 raise_error(Geocoder::OverQueryLimitError) || Geocoder.log(:warn, "Geocoding API error: 429 Too Many Requests") elsif response.code.to_i == 503 raise_error(Geocoder::ServiceUnavailable) || Geocoder.log(:warn, "Geocoding API error: 503 Service Unavailable") end end
def configuration
An object with configuration data for this particular lookup.
#
def configuration Geocoder.config_for_lookup(handle) end
def configure_ssl!(client); end
def configure_ssl!(client); end
def fetch_data(query)
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 Geocoder.log(:warn, "Geocoding API connection cannot be established.") rescue Errno::ECONNREFUSED => err raise_error(err) or Geocoder.log(:warn, "Geocoding API connection refused.") rescue Geocoder::NetworkError => err raise_error(err) or Geocoder.log(:warn, "Geocoding API connection is either unreacheable or reset by the peer") rescue Timeout::Error => err raise_error(err) or Geocoder.log(:warn, "Geocoding API not responding fast enough " + "(use Geocoder.configure(:timeout => ...) to set limit).") end
def fetch_raw_data(query)
The result might or might not be cached.
Fetch a raw geocoding result (JSON string).
#
def fetch_raw_data(query) key = cache_key(query) if cache and body = cache[key] @cache_hit = true else check_api_key_configuration!(query) response = make_api_request(query) check_response_for_errors!(response) body = response.body # apply the charset from the Content-Type header, if possible ct = response['content-type'] if ct && ct['charset'] charset = ct.split(';').select do |s| s['charset'] end.first.to_s.split('=') if charset.length == 2 body.force_encoding(charset.last) rescue ArgumentError end end if cache and valid_response?(response) cache[key] = body end @cache_hit = false end body end
def handle
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
def hash_to_query(hash)
Removes any keys with nil value.
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 * '&' end
def http_client
Object used to make HTTP requests.
#
def http_client proxy_name = "#{protocol}_proxy" if proxy = configuration.send(proxy_name) proxy_url = !!(proxy =~ /^#{protocol}/) ? proxy : 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 end end
def initialize
def initialize @cache = nil end
def make_api_request(query)
return the response object.
Make an HTTP(S) request to a geocoding API and
#
def make_api_request(query) uri = URI.parse(query_url(query)) Geocoder.log(:debug, "Geocoder: HTTP request being made for #{uri.to_s}") http_client.start(uri.host, uri.port, use_ssl: use_ssl?, open_timeout: configuration.timeout, read_timeout: configuration.timeout) do |client| configure_ssl!(client) if use_ssl? req = Net::HTTP::Get.new(uri.request_uri, configuration.http_headers) if configuration.basic_auth[:user] and configuration.basic_auth[:password] req.basic_auth( configuration.basic_auth[:user], configuration.basic_auth[:password] ) end client.request(req) end rescue Timeout::Error raise Geocoder::LookupTimeout rescue Errno::EHOSTUNREACH, Errno::ETIMEDOUT, Errno::ENETUNREACH, Errno::ECONNRESET raise Geocoder::NetworkError end
def map_link_url(coordinates)
also provide maps.
Not necessarily implemented by all subclasses as only some lookups
Return the URL for a map of the given coordinates.
#
def map_link_url(coordinates) nil end
def name
Human-readable name of the geocoding API.
#
def name fail end
def parse_json(data)
def parse_json(data) if defined?(ActiveSupport::JSON) ActiveSupport::JSON.decode(data) else JSON.parse(data) end rescue unless raise_error(ResponseParseError.new(data)) Geocoder.log(:warn, "Geocoding API's response was not valid JSON") Geocoder.log(:debug, "Raw response: #{data}") end end
def parse_raw_data(raw_data)
Parses a raw search result (returns hash or array).
#
def parse_raw_data(raw_data) parse_json(raw_data) end
def protocol
Set in configuration but not available for every service.
Protocol to use for communication with geocoding services.
#
def protocol "http" + (use_ssl? ? "s" : "") end
def query_url(query)
subclss this method, they must also subclass #cache_key.
base_query_url and url_query_string. If absolutely necessary to
Subclasses should not modify this method. Instead they should define
URL to use for querying the geocoding engine.
#
def query_url(query) base_query_url(query) + url_query_string(query) end
def query_url_params(query)
def query_url_params(query) query.options[:params] || {} end
def raise_error(error, message = nil)
Return false if exception not raised.
Raise exception if configuration specifies it should be raised.
#
def raise_error(error, message = nil) exceptions = configuration.always_raise if exceptions == :all or exceptions.include?( error.is_a?(Class) ? error : error.class ) raise error, message else false end end
def required_api_key_parts
Empty array if keys are optional or not required.
Array containing string descriptions of keys required by the API.
#
def required_api_key_parts [] end
def result_class
Class of the result objects
#
def result_class Geocoder::Result.const_get(self.class.to_s.split(":").last) end
def results(query)
Geocoder::Result object or nil on timeout or other error.
#
def results(query) fail end
def search(query, options = {})
for reverse geocoding. Returns an array of Geocoder::Results.
"205.128.54.202") for geocoding, or coordinates (latitude, longitude)
Takes a search string (eg: "Mississippi Coast Coliseumf, Biloxi, MS",
Returns +nil+ on timeout or error.
Query the geocoding API and return a Geocoder::Result object.
#
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 } end
def supported_protocols
or [:https] if only HTTPS is supported.
Should be set to [:http] if only HTTP is supported
Array containing the protocols supported by the api.
#
def supported_protocols [:http, :https] end
def url_query_string(query)
def url_query_string(query) hash_to_query( query_url_params(query).reject{ |key,value| value.nil? } ) end
def use_ssl?
def use_ssl? if supported_protocols == [:https] true elsif supported_protocols == [:http] false else configuration.use_https end end
def valid_response?(response)
def valid_response?(response) (200..399).include?(response.code.to_i) end