module Geocoder::Lookup
Attributes
Public Instance Methods
Array of valid Lookup
service names.
# File lib/geocoder/lookup.rb, line 10 def all_services street_services + ip_services end
Array of valid Lookup
service names, excluding :test.
# File lib/geocoder/lookup.rb, line 17 def all_services_except_test all_services - [:test] end
# File lib/geocoder/lookups/ban_data_gouv_fr.rb, line 24 def any_result?(doc) doc['features'].any? end
# File lib/geocoder/lookups/here.rb, line 71 def api_code if (a = configuration.api_key) return a.last if a.is_a?(Array) end end
# File lib/geocoder/lookups/here.rb, line 65 def api_key if (a = configuration.api_key) return a.first if a.is_a?(Array) end end
# File lib/geocoder/lookups/ban_data_gouv_fr.rb, line 117 def autocomplete_param_is_valid?(param) [0,1].include?(param.to_i) end
# File lib/geocoder/lookups/geocoder_us.rb, line 25 def base_query_url_with_optional_key(key = nil) base = "#{protocol}://" if configuration.api_key base << "#{configuration.api_key}@" end base + "geocoder.us/member/service/csv/geocode?" 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).
# File lib/geocoder/lookups/base.rb, line 160 def cache_key(query) base_query_url(query) + hash_to_query(cache_key_params(query)) end
# File lib/geocoder/lookups/base.rb, line 164 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
# File lib/geocoder/lookups/base.rb, line 326 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
# File lib/geocoder/lookups/base.rb, line 271 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
# File lib/geocoder/lookups/ban_data_gouv_fr.rb, line 125 def code_param_is_valid?(param) (1..99999).include?(param.to_i) end
An object with configuration data for this particular lookup.
# File lib/geocoder/lookups/base.rb, line 114 def configuration Geocoder.config_for_lookup(handle) end
# File lib/geocoder/lookups/base.rb, line 324 def configure_ssl!(client); end
# File lib/geocoder/lookups/location_iq.rb, line 27 def configured_host configuration[:host] || "locationiq.org" end
Return the name of the configured service, or raise an exception.
# File lib/geocoder/lookups/maxmind.rb, line 21 def configured_service! if s = configuration[:service] and services.keys.include?(s) return s else raise( Geocoder::ConfigurationError, "When using MaxMind you MUST specify a service name: " + "Geocoder.configure(:maxmind => {:service => ...}), " + "where '...' is one of: #{services.keys.inspect}" ) end end
# File lib/geocoder/lookups/baidu.rb, line 26 def content_key 'result' end
# File lib/geocoder/lookups/esri.rb, line 79 def create_and_save_token!(query) token_instance = create_token if query.options[:token] query.options[:token] = token_instance else Geocoder.merge_into_lookup_config(:esri, token: token_instance) end token_instance end
# File lib/geocoder/lookups/esri.rb, line 91 def create_token Geocoder::EsriToken.generate_token(*configuration.api_key) end
# File lib/geocoder/lookups/maxmind.rb, line 42 def data_contains_error?(parsed_data) # if all fields given then there is an error parsed_data.size == service_response_fields_count and !parsed_data.last.nil? end
# File lib/geocoder/lookups/mapbox.rb, line 44 def dataset configuration[:dataset] || "mapbox.places" end
# File lib/geocoder/lookups/telize.rb, line 46 def empty_result?(doc) !doc.is_a?(Hash) or doc.keys == ["ip"] end
Returns a parsed search result (Ruby hash).
# File lib/geocoder/lookups/base.rb, line 194 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 Timeout::Error => err raise_error(err) or Geocoder.log(:warn, "Geocoding API not responding fast enough " + "(use Geocoder.configure(:timeout => ...) to set limit).") end
Fetch a raw geocoding result (JSON string). The result might or might not be cached.
# File lib/geocoder/lookups/base.rb, line 241 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
# File lib/geocoder/lookups/esri.rb, line 53 def for_storage(query) if query.options.has_key?(:for_storage) query.options[:for_storage] else configuration[:for_storage] end end
Retrieve a Lookup
object from the store. Use this instead of Geocoder::Lookup::X.new to get an already-configured Lookup
object.
# File lib/geocoder/lookup.rb, line 87 def get(name) @services = {} unless defined?(@services) @services[name] = spawn(name) unless @services.include?(name) @services[name] end
Simulate ActiveSupport's Object#to_query. Removes any keys with nil value.
# File lib/geocoder/lookups/base.rb, line 340 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
# File lib/geocoder/lookups/freegeoip.rb, line 56 def host configuration[:host] || "freegeoip.net" end
Object used to make HTTP requests.
# File lib/geocoder/lookups/base.rb, line 121 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
# File lib/geocoder/lookups/smarty_streets.rb, line 35 def international?(query) !query.options[:country].nil? end
# File lib/geocoder/lookups/ipapi_com.rb, line 68 def invalid_key_result { "message" => "invalid key" } end
All IP address lookup services, default first.
# File lib/geocoder/lookup.rb, line 61 def ip_services @ip_services ||= [ :baidu_ip, :freegeoip, :geoip2, :maxmind, :maxmind_local, :telize, :pointpin, :maxmind_geoip2, :ipinfo_io, :ipapi_com, :ipdata_co, :db_ip_com, :ipstack, :ip2location ] end
# File lib/geocoder/lookups/ban_data_gouv_fr.rb, line 113 def limit_param_is_valid?(param) param.to_i.positive? end
Make an HTTP(S) request to a geocoding API and return the response object.
# File lib/geocoder/lookups/base.rb, line 294 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
# File lib/geocoder/lookups/mapbox.rb, line 32 def mapbox_search_term(query) require 'cgi' unless defined?(CGI) && defined?(CGI.escape) if query.reverse_geocode? lat,lon = query.coordinates "#{CGI.escape lon},#{CGI.escape lat}" else # truncate at first semicolon so Mapbox doesn't go into batch mode # (see Github issue #1299) CGI.escape query.text.to_s.split(';').first.to_s end end
# File lib/geocoder/lookups/base.rb, line 205 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
Parses a raw search result (returns hash or array).
# File lib/geocoder/lookups/base.rb, line 221 def parse_raw_data(raw_data) parse_json(raw_data) end
Protocol to use for communication with geocoding services. Set in configuration but not available for every service.
# File lib/geocoder/lookups/base.rb, line 229 def protocol "http" + (use_ssl? ? "s" : "") end
# File lib/geocoder/lookups/ban_data_gouv_fr.rb, line 42 def query_ban_datagouv_fr_params(query) query.reverse_geocode? ? reverse_geocode_ban_fr_params(query) : search_geocode_ban_fr_params(query) end
# File lib/geocoder/lookups/geoportail_lu.rb, line 30 def query_url_geoportail_lu_params(query) query.reverse_geocode? ? reverse_geocode_params(query) : search_params(query) end
# File lib/geocoder/lookups/google.rb, line 64 def query_url_google_params(query) params = { :sensor => "false", :language => (query.language || configuration.language) } if query.options[:google_place_id] params[:place_id] = query.sanitized_text else params[(query.reverse_geocode? ? :latlng : :address)] = query.sanitized_text end unless (bounds = query.options[:bounds]).nil? params[:bounds] = bounds.map{ |point| "%f,%f" % point }.join('|') end unless (region = query.options[:region]).nil? params[:region] = region end unless (components = query.options[:components]).nil? params[:components] = components.is_a?(Array) ? components.join("|") : components end unless (result_type = query.options[:result_type]).nil? params[:result_type] = result_type.is_a?(Array) ? result_type.join("|") : result_type end params end
# File lib/geocoder/lookups/here.rb, line 31 def query_url_here_options(query, reverse_geocode) options = { gen: 9, app_id: api_key, app_code: api_code, language: (query.language || configuration.language) } if reverse_geocode options[:mode] = :retrieveAddresses return options end unless (country = query.options[:country]).nil? options[:country] = country end unless (mapview = query.options[:bounds]).nil? options[:mapview] = mapview.map{ |point| "%f,%f" % point }.join(';') end options end
# File lib/geocoder/lookups/amap.rb, line 43 def query_url_params(query) params = { :key => configuration.api_key, :output => "json" } if query.reverse_geocode? params[:location] = revert_coordinates(query.text) params[:extensions] = "all" params[:coordsys] = "gps" else params[:address] = query.sanitized_text end params.merge(super) end
Raise exception if configuration specifies it should be raised. Return false if exception not raised.
# File lib/geocoder/lookups/base.rb, line 182 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
# File lib/geocoder/lookups/postcode_anywhere_uk.rb, line 32 def raise_exception_for_response(response) case response['Error'] when *DAILY_LIMIT_EXEEDED_ERROR_CODES raise_error(Geocoder::OverQueryLimitError, response['Cause']) || Geocoder.log(:warn, response['Cause']) when INVALID_API_KEY_ERROR_CODE raise_error(Geocoder::InvalidApiKey, response['Cause']) || Geocoder.log(:warn, response['Cause']) else # anything else just raise general error with the api cause raise_error(Geocoder::Error, response['Cause']) || Geocoder.log(:warn, response['Cause']) end end
# File lib/geocoder/lookups/freegeoip.rb, line 41 def reserved_result(ip) { "ip" => ip, "city" => "", "region_code" => "", "region_name" => "", "metrocode" => "", "zipcode" => "", "latitude" => "0", "longitude" => "0", "country_name" => "Reserved", "country_code" => "RD" } end
Class of the result objects
# File lib/geocoder/lookups/base.rb, line 174 def result_class Geocoder::Result.const_get(self.class.to_s.split(":").last) end
# File lib/geocoder/lookups/amap.rb, line 26 def results(query, reverse = false) return [] unless doc = fetch_data(query) case [doc['status'], doc['info']] when ['1', 'OK'] return doc['regeocodes'] unless doc['regeocodes'].blank? return [doc['regeocode']] unless doc['regeocode'].blank? return doc['geocodes'] unless doc['geocodes'].blank? when ['0', 'INVALID_USER_KEY'] raise_error(Geocoder::InvalidApiKey, "invalid api key") || warn("#{self.name} Geocoding API error: invalid api key.") else raise_error(Geocoder::Error, "server error.") || warn("#{self.name} Geocoding API error: server error - [#{doc['info']}]") end return [] end
REVERSE GEOCODING PARAMS ####
:lat => required
:lon => required
:type => force returned results type
(check results for a list of accepted types)
# File lib/geocoder/lookups/ban_data_gouv_fr.rb, line 101 def reverse_geocode_ban_fr_params(query) lat_lon = query.coordinates params = { lat: lat_lon.first, lon: lat_lon.last } unless (type = query.options[:type]).nil? || !type_param_is_valid?(type) params[:type] = type.downcase end params end
# File lib/geocoder/lookups/geoportail_lu.rb, line 40 def reverse_geocode_params(query) lat_lon = query.coordinates { lat: lat_lon.first, lon: lat_lon.last } end
# File lib/geocoder/lookups/geoportail_lu.rb, line 26 def reverse_geocode_url_base_path "#{protocol}://api.geoportail.lu/geocoder/reverseGeocode?" end
# File lib/geocoder/lookups/amap.rb, line 58 def revert_coordinates(text) [text[1],text[0]].join(",") end
:limit => force limit number of results returned by raw API
(default = 5) note : only first result is taken in account in geocoder
:autocomplete => pass 0 to disable autocomplete treatment of :q
(default = 1)
:lat => force filter results around specific lat/lon
:lon => force filter results around specific lat/lon
:type => force filter the returned result type
(check results for a list of accepted types)
:postcode => force filter results on a specific city post code
:citycode => force filter results on a specific city UUID INSEE code
For up to date doc (in french only) : adresse.data.gouv.fr/api/
# File lib/geocoder/lookups/ban_data_gouv_fr.rb, line 70 def search_geocode_ban_fr_params(query) params = { q: query.sanitized_text } unless (limit = query.options[:limit]).nil? || !limit_param_is_valid?(limit) params[:limit] = limit.to_i end unless (autocomplete = query.options[:autocomplete]).nil? || !autocomplete_param_is_valid?(autocomplete) params[:autocomplete] = autocomplete.to_s end unless (type = query.options[:type]).nil? || !type_param_is_valid?(type) params[:type] = type.downcase end unless (postcode = query.options[:postcode]).nil? || !code_param_is_valid?(postcode) params[:postcode] = postcode.to_s end unless (citycode = query.options[:citycode]).nil? || !code_param_is_valid?(citycode) params[:citycode] = citycode.to_s end params end
# File lib/geocoder/lookups/geoportail_lu.rb, line 34 def search_params(query) { queryString: query.sanitized_text } end
# File lib/geocoder/lookups/mapquest.rb, line 23 def search_type(query) query.reverse_geocode? ? "reverse" : "address" end
# File lib/geocoder/lookups/geoportail_lu.rb, line 22 def search_url_base_path "#{protocol}://api.geoportail.lu/geocoder/search?" end
# File lib/geocoder/lookups/bing.rb, line 73 def server_overloaded?(response) # Occasionally, the servers processing service requests can be overloaded, # and you may receive some responses that contain no results for queries that # you would normally receive a result. To identify this situation, # check the HTTP headers of the response. If the HTTP header X-MS-BM-WS-INFO is set to 1, # it is best to wait a few seconds and try again. response['x-ms-bm-ws-info'].to_i == 1 end
# File lib/geocoder/lookups/maxmind.rb, line 34 def service_code services[configured_service!] end
# File lib/geocoder/lookups/maxmind.rb, line 38 def service_response_fields_count Geocoder::Result::Maxmind.field_names[configured_service!].size end
Service names mapped to code used in URL.
# File lib/geocoder/lookups/maxmind.rb, line 50 def services { :country => "a", :city => "b", :city_isp_org => "f", :omni => "e" } end
# File lib/geocoder/lookups/google_premier.rb, line 42 def sign(string) raw_private_key = url_safe_base64_decode(configuration.api_key[0]) digest = OpenSSL::Digest.new('sha1') raw_signature = OpenSSL::HMAC.digest(digest, raw_private_key, string) url_safe_base64_encode(raw_signature) end
# File lib/geocoder/lookups/mapbox.rb, line 52 def sort_relevant_feature(features) # Sort by descending relevance; Favor original order for equal relevance (eg occurs for reverse geocoding) features.sort_by do |feature| [feature["relevance"],-features.index(feature)] end.reverse end
All street address lookup services, default first.
# File lib/geocoder/lookup.rb, line 24 def street_services @street_services ||= [ :location_iq, :dstk, :esri, :google, :google_premier, :google_places_details, :google_places_search, :bing, :geocoder_ca, :geocoder_us, :yandex, :nominatim, :mapbox, :mapquest, :opencagedata, :pelias, :pickpoint, :here, :baidu, :tencent, :geocodio, :smarty_streets, :postcode_anywhere_uk, :postcodes_io, :geoportail_lu, :ban_data_gouv_fr, :test, :latlon, :amap ] end
# File lib/geocoder/lookups/latlon.rb, line 37 def supported_protocols [:https] end
# File lib/geocoder/lookups/esri.rb, line 61 def token(query) token_instance = if query.options[:token] query.options[:token] else configuration[:token] end if !valid_token_configured?(token_instance) && configuration.api_key token_instance = create_and_save_token!(query) end token_instance.to_s unless token_instance.nil? end
# File lib/geocoder/lookups/ban_data_gouv_fr.rb, line 121 def type_param_is_valid?(param) %w(housenumber street locality village town city).include?(param.downcase) end
# File lib/geocoder/lookups/base.rb, line 148 def url_query_string(query) hash_to_query( query_url_params(query).reject{ |key,value| value.nil? } ) end
# File lib/geocoder/lookups/google_premier.rb, line 49 def url_safe_base64_decode(base64_string) Base64.decode64(base64_string.tr('-_', '+/')) end
# File lib/geocoder/lookups/google_premier.rb, line 53 def url_safe_base64_encode(raw) Base64.encode64(raw).tr('+/', '-_').strip end
# File lib/geocoder/lookups/base.rb, line 314 def use_ssl? if supported_protocols == [:https] true elsif supported_protocols == [:http] false else configuration.use_https end end
# File lib/geocoder/lookups/base.rb, line 233 def valid_response?(response) (200..399).include?(response.code.to_i) end
# File lib/geocoder/lookups/esri.rb, line 75 def valid_token_configured?(token_instance) !token_instance.nil? && token_instance.active? end
# File lib/geocoder/lookups/smarty_streets.rb, line 31 def zipcode_only?(query) !query.text.is_a?(Array) and query.to_s.strip =~ /\A\d{5}(-\d{4})?\Z/ end