module Geocoder::Lookup
Attributes
Public Instance Methods
# File lib/geocoder/lookups/pc_miler.rb, line 45 def add_metadata_to_locations!(data) confidence = data['QueryConfidence'] data['Locations'].each do |location| location['QueryConfidence'] = confidence end end
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
Array of valid Lookup
service names, excluding any that do not build their own HTTP requests. For example, Amazon Location Service uses the AWS gem, not HTTP REST requests, to fetch data.
# File lib/geocoder/lookup.rb, line 25 def all_services_with_http_requests all_services_except_test - [:amazon_location_service, :maxmind_local, :geoip2, :ip2location_lite] end
# File lib/geocoder/lookups/ban_data_gouv_fr.rb, line 24 def any_result?(doc) doc['features'] and doc['features'].any? end
# File lib/geocoder/lookups/telize.rb, line 70 def api_key configuration.api_key end
# File lib/geocoder/lookups/here.rb, line 64 def api_key_not_string! msg = <<~MSG API key for HERE Geocoding and Search API should be a string. For more info on how to obtain it, please see https://developer.here.com/documentation/identity-access-management/dev_guide/topics/plat-using-apikeys.html MSG raise_error(Geocoder::ConfigurationError, msg) || Geocoder.log(:warn, msg) end
# File lib/geocoder/lookups/ban_data_gouv_fr.rb, line 123 def autocomplete_param_is_valid?(param) [0,1].include?(param.to_i) end
# File lib/geocoder/lookups/nationaal_georegister_nl.rb, line 17 def base_query_url(query) "#{protocol}://geodata.nationaalgeoregister.nl/locatieserver/v3/free?" 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 161 def cache_key(query) base_query_url(query) + hash_to_query(cache_key_params(query)) end
# File lib/geocoder/lookups/base.rb, line 165 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 329 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 274 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 131 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 115 def configuration Geocoder.config_for_lookup(handle) end
# File lib/geocoder/lookups/base.rb, line 327 def configure_ssl!(client); end
# File lib/geocoder/lookups/location_iq.rb, line 31 def configured_host configuration[:host] || "us1.locationiq.com" 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 98 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 110 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 195 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
Fetch a raw geocoding result (JSON string). The result might or might not be cached.
# File lib/geocoder/lookups/base.rb, line 244 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/uk_ordnance_survey_names.rb, line 54 def filter local_types.map { |t| "local_type:#{t}" }.join(' ') end
# File lib/geocoder/lookups/esri.rb, line 72 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 110 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 343 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 58 def host configuration[:host] || "freegeoip.app" end
Object used to make HTTP requests.
# File lib/geocoder/lookups/base.rb, line 122 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 77 def ip_services @ip_services ||= [ :baidu_ip, :abstract_api, :freegeoip, :geoip2, :maxmind, :maxmind_local, :telize, :pointpin, :maxmind_geoip2, :ipinfo_io, :ipregistry, :ipapi_com, :ipdata_co, :db_ip_com, :ipstack, :ip2location, :ipgeolocation, :ipqualityscore, :ipbase, :ip2location_io, :ip2location_lite ] end
# File lib/geocoder/lookups/ban_data_gouv_fr.rb, line 135 def latitude_is_valid?(param) param.to_f <= 90 && param.to_f >= -90 end
# File lib/geocoder/lookups/ban_data_gouv_fr.rb, line 119 def limit_param_is_valid?(param) param.to_i.positive? end
# File lib/geocoder/lookups/uk_ordnance_survey_names.rb, line 43 def local_types %w[ City Hamlet Other_Settlement Town Village Postcode ] end
# File lib/geocoder/lookups/ban_data_gouv_fr.rb, line 139 def longitude_is_valid?(param) param.to_f <= 180 && param.to_f >= -180 end
Make an HTTP(S) request to a geocoding API and return the response object.
# File lib/geocoder/lookups/base.rb, line 297 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 208 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 224 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 232 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 69 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 38 def query_url_here_options(query, reverse_geocode) options = { apiKey: configuration.api_key, lang: (query.language || configuration.language) } return options if reverse_geocode unless (country = query.options[:country]).nil? options[:in] = "countryCode:#{country}" end options end
# File lib/geocoder/lookups/abstract_api.rb, line 27 def query_url_params(query) params = {api_key: configuration.api_key} ip_address = query.sanitized_text if ip_address.is_a?(String) && ip_address.length > 0 params[:ip_address] = ip_address end params.merge(super) end
# File lib/geocoder/lookups/photon.rb, line 43 def query_url_params_coordinates(query) params = { q: query.sanitized_text } if (bias = query.options[:bias]) params.merge!(lat: bias[:latitude], lon: bias[:longitude], location_bias_scale: bias[:scale]) end if (filter = query_url_params_coordinates_filter(query)) params.merge!(filter) end params end
# File lib/geocoder/lookups/photon.rb, line 57 def query_url_params_coordinates_filter(query) filter = query.options[:filter] return unless filter bbox = filter[:bbox] { bbox: bbox.is_a?(Array) ? bbox.join(',') : bbox, osm_tag: filter[:osm_tag] } end
# File lib/geocoder/lookups/photon.rb, line 68 def query_url_params_reverse(query) params = { lat: query.coordinates[0], lon: query.coordinates[1], radius: query.options[:radius] } if (dsort = query.options[:distance_sort]) params[:distance_sort] = dsort ? 'true' : 'false' end if (filter = query_url_params_reverse_filter(query)) params.merge!(filter) end params end
# File lib/geocoder/lookups/photon.rb, line 82 def query_url_params_reverse_filter(query) filter = query.options[:filter] return unless filter { query_string_filter: filter[:string] } end
Raise exception if configuration specifies it should be raised. Return false if exception not raised.
# File lib/geocoder/lookups/base.rb, line 183 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/pc_miler.rb, line 68 def region(query) query.options[:region] || query.options['region'] || configuration[:region] || "NA" end
# File lib/geocoder/lookups/freegeoip.rb, line 43 def reserved_result(ip) { "ip" => ip, "city" => "", "region_code" => "", "region_name" => "", "metro_code" => "", "zip_code" => "", "latitude" => "0", "longitude" => "0", "country_name" => "Reserved", "country_code" => "RD" } end
Class of the result objects
# File lib/geocoder/lookups/base.rb, line 175 def result_class Geocoder::Result.const_get(self.class.to_s.split(":").last) end
# File lib/geocoder/lookups/google.rb, line 47 def result_root_attr 'results' end
# File lib/geocoder/lookups/abstract_api.rb, line 38 def results(query, reverse = false) if doc = fetch_data(query) [doc] else [] end 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 107 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 unless (lat = query.options[:lat]).nil? || !latitude_is_valid?(lat) params[:lat] = lat end unless (lon = query.options[:lon]).nil? || !longitude_is_valid?(lon) params[:lon] = lon 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 46 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 32 def street_services @street_services ||= [ :location_iq, :esri, :google, :google_premier, :google_places_details, :google_places_search, :bing, :geocoder_ca, :yandex, :nationaal_georegister_nl, :nominatim, :mapbox, :mapquest, :uk_ordnance_survey_names, :opencagedata, :pelias, :pdok_nl, :pickpoint, :here, :baidu, :tencent, :geocodio, :smarty_streets, :postcode_anywhere_uk, :postcodes_io, :geoportail_lu, :ban_data_gouv_fr, :test, :latlon, :amap, :osmnames, :melissa_street, :amazon_location_service, :geoapify, :photon, :twogis, :pc_miler ] end
# File lib/geocoder/lookups/latlon.rb, line 36 def supported_protocols [:https] end
# File lib/geocoder/lookups/esri.rb, line 80 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 127 def type_param_is_valid?(param) %w(housenumber street locality municipality).include?(param.downcase) end
# File lib/geocoder/lookups/base.rb, line 149 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 53 def url_safe_base64_decode(base64_string) Base64.decode64(base64_string.tr('-_', '+/')) end
# File lib/geocoder/lookups/google_premier.rb, line 57 def url_safe_base64_encode(raw) Base64.encode64(raw).tr('+/', '-_').strip end
# File lib/geocoder/lookups/base.rb, line 317 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 236 def valid_response?(response) (200..399).include?(response.code.to_i) end
# File lib/geocoder/lookups/esri.rb, line 94 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