geocoder 1.6.6 → 1.7.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,7 +9,7 @@ Geocoder.configure(
9
9
  # https_proxy: nil, # HTTPS proxy server (user:pass@host:port)
10
10
  # api_key: nil, # API key for geocoding service
11
11
  # cache: nil, # cache object (must respond to #[], #[]=, and #del)
12
- # cache_prefix: 'geocoder:', # prefix (string) to use for all cache keys
12
+ # cache_prefix: 'geocoder:', # DEPRECATED, please use cache_options[:prefix] instead
13
13
 
14
14
  # Exceptions that should not be rescued by default
15
15
  # (if you want to implement custom error handling);
@@ -19,4 +19,10 @@ Geocoder.configure(
19
19
  # Calculation options
20
20
  # units: :mi, # :km for kilometers or :mi for miles
21
21
  # distances: :linear # :spherical or :linear
22
+
23
+ # Cache configuration
24
+ # cache_options: {
25
+ # expiration: 2.days,
26
+ # prefix: 'geocoder:'
27
+ # }
22
28
  )
@@ -1,23 +1,18 @@
1
+ Dir["#{__dir__}/cache_stores/*.rb"].each {|file| require file }
2
+
1
3
  module Geocoder
2
4
  class Cache
3
5
 
4
- def initialize(store, prefix)
5
- @store = store
6
- @prefix = prefix
6
+ def initialize(store, config)
7
+ @class = (Object.const_get("Geocoder::CacheStore::#{store.class}") rescue Geocoder::CacheStore::Generic)
8
+ @store_service = @class.new(store, config)
7
9
  end
8
10
 
9
11
  ##
10
12
  # Read from the Cache.
11
13
  #
12
14
  def [](url)
13
- interpret case
14
- when store.respond_to?(:[])
15
- store[key_for(url)]
16
- when store.respond_to?(:get)
17
- store.get key_for(url)
18
- when store.respond_to?(:read)
19
- store.read key_for(url)
20
- end
15
+ interpret store_service.read(url)
21
16
  rescue => e
22
17
  warn "Geocoder cache read error: #{e}"
23
18
  end
@@ -26,14 +21,7 @@ module Geocoder
26
21
  # Write to the Cache.
27
22
  #
28
23
  def []=(url, value)
29
- case
30
- when store.respond_to?(:[]=)
31
- store[key_for(url)] = value
32
- when store.respond_to?(:set)
33
- store.set key_for(url), value
34
- when store.respond_to?(:write)
35
- store.write key_for(url), value
36
- end
24
+ store_service.write(url, value)
37
25
  rescue => e
38
26
  warn "Geocoder cache write error: #{e}"
39
27
  end
@@ -44,7 +32,7 @@ module Geocoder
44
32
  #
45
33
  def expire(url)
46
34
  if url == :all
47
- if store.respond_to?(:keys)
35
+ if store_service.respond_to?(:keys)
48
36
  urls.each{ |u| expire(u) }
49
37
  else
50
38
  raise(NoMethodError, "The Geocoder cache store must implement `#keys` for `expire(:all)` to work")
@@ -57,29 +45,21 @@ module Geocoder
57
45
 
58
46
  private # ----------------------------------------------------------------
59
47
 
60
- def prefix; @prefix; end
61
- def store; @store; end
62
-
63
- ##
64
- # Cache key for a given URL.
65
- #
66
- def key_for(url)
67
- [prefix, url].join
68
- end
48
+ def store_service; @store_service; end
69
49
 
70
50
  ##
71
51
  # Array of keys with the currently configured prefix
72
52
  # that have non-nil values.
73
53
  #
74
54
  def keys
75
- store.keys.select{ |k| k.match(/^#{prefix}/) and self[k] }
55
+ store_service.keys
76
56
  end
77
57
 
78
58
  ##
79
59
  # Array of cached URLs.
80
60
  #
81
61
  def urls
82
- keys.map{ |k| k[/^#{prefix}(.*)/, 1] }
62
+ store_service.urls
83
63
  end
84
64
 
85
65
  ##
@@ -91,8 +71,7 @@ module Geocoder
91
71
  end
92
72
 
93
73
  def expire_single_url(url)
94
- key = key_for(url)
95
- store.respond_to?(:del) ? store.del(key) : store.delete(key)
74
+ store_service.remove(url)
96
75
  end
97
76
  end
98
77
  end
@@ -0,0 +1,40 @@
1
+ module Geocoder::CacheStore
2
+ class Base
3
+ def initialize(store, options)
4
+ @store = store
5
+ @config = options
6
+ @prefix = config[:prefix]
7
+ end
8
+
9
+ ##
10
+ # Array of keys with the currently configured prefix
11
+ # that have non-nil values.
12
+ def keys
13
+ store.keys.select { |k| k.match(/^#{prefix}/) and self[k] }
14
+ end
15
+
16
+ ##
17
+ # Array of cached URLs.
18
+ #
19
+ def urls
20
+ keys
21
+ end
22
+
23
+ protected # ----------------------------------------------------------------
24
+
25
+ def prefix; @prefix; end
26
+ def store; @store; end
27
+ def config; @config; end
28
+
29
+ ##
30
+ # Cache key for a given URL.
31
+ #
32
+ def key_for(url)
33
+ if url.match(/^#{prefix}/)
34
+ url
35
+ else
36
+ [prefix, url].join
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,35 @@
1
+ require 'geocoder/cache_stores/base'
2
+
3
+ module Geocoder::CacheStore
4
+ class Generic < Base
5
+ def write(url, value)
6
+ case
7
+ when store.respond_to?(:[]=)
8
+ store[key_for(url)] = value
9
+ when store.respond_to?(:set)
10
+ store.set key_for(url), value
11
+ when store.respond_to?(:write)
12
+ store.write key_for(url), value
13
+ end
14
+ end
15
+
16
+ def read(url)
17
+ case
18
+ when store.respond_to?(:[])
19
+ store[key_for(url)]
20
+ when store.respond_to?(:get)
21
+ store.get key_for(url)
22
+ when store.respond_to?(:read)
23
+ store.read key_for(url)
24
+ end
25
+ end
26
+
27
+ def keys
28
+ store.keys
29
+ end
30
+
31
+ def remove(key)
32
+ store.delete(key)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ require 'geocoder/cache_stores/base'
2
+
3
+ module Geocoder::CacheStore
4
+ class Redis < Base
5
+ def initialize(store, options)
6
+ super
7
+ @expiration = options[:expiration]
8
+ end
9
+
10
+ def write(url, value, expire = @expiration)
11
+ if expire.present?
12
+ store.set key_for(url), value, ex: expire
13
+ else
14
+ store.set key_for(url), value
15
+ end
16
+ end
17
+
18
+ def read(url)
19
+ store.get key_for(url)
20
+ end
21
+
22
+ def keys
23
+ store.keys("#{prefix}*")
24
+ end
25
+
26
+ def remove(key)
27
+ store.del(key)
28
+ end
29
+
30
+ private # ----------------------------------------------------------------
31
+
32
+ def expire; @expiration; end
33
+ end
34
+ end
@@ -55,6 +55,7 @@ module Geocoder
55
55
  :lookup,
56
56
  :ip_lookup,
57
57
  :language,
58
+ :host,
58
59
  :http_headers,
59
60
  :use_https,
60
61
  :http_proxy,
@@ -67,7 +68,8 @@ module Geocoder
67
68
  :distances,
68
69
  :basic_auth,
69
70
  :logger,
70
- :kernel_logger_level
71
+ :kernel_logger_level,
72
+ :cache_options
71
73
  ]
72
74
 
73
75
  attr_accessor :data
@@ -107,7 +109,7 @@ module Geocoder
107
109
  @data[:https_proxy] = nil # HTTPS proxy server (user:pass@host:port)
108
110
  @data[:api_key] = nil # API key for geocoding service
109
111
  @data[:cache] = nil # cache object (must respond to #[], #[]=, and #keys)
110
- @data[:cache_prefix] = "geocoder:" # prefix (string) to use for all cache keys
112
+ @data[:cache_prefix] = "geocoder:" # - DEPRECATED - prefix (string) to use for all cache keys
111
113
  @data[:basic_auth] = {} # user and password for basic auth ({:user => "user", :password => "password"})
112
114
  @data[:logger] = :kernel # :kernel or Logger instance
113
115
  @data[:kernel_logger_level] = ::Logger::WARN # log level, if kernel logger is used
@@ -120,6 +122,9 @@ module Geocoder
120
122
  # calculation options
121
123
  @data[:units] = :mi # :mi or :km
122
124
  @data[:distances] = :linear # :linear or :spherical
125
+
126
+ # explicit cache service options
127
+ @data[:cache_options] = {}
123
128
  end
124
129
 
125
130
  instance_eval(OPTIONS.map do |option|
@@ -7,6 +7,12 @@ module Geocoder
7
7
  '192.168.0.0/16',
8
8
  ].map { |ip| IPAddr.new(ip) }.freeze
9
9
 
10
+ def initialize(ip)
11
+ ip = ip.to_string if ip.is_a?(IPAddr)
12
+
13
+ super(ip)
14
+ end
15
+
10
16
  def internal?
11
17
  loopback? || private?
12
18
  end
@@ -18,6 +18,14 @@ module Geocoder
18
18
  all_services - [:test]
19
19
  end
20
20
 
21
+ ##
22
+ # Array of valid Lookup service names, excluding any that do not build their own HTTP requests.
23
+ # For example, Amazon Location Service uses the AWS gem, not HTTP REST requests, to fetch data.
24
+ #
25
+ def all_services_with_http_requests
26
+ all_services_except_test - [:amazon_location_service]
27
+ end
28
+
21
29
  ##
22
30
  # All street address lookup services, default first.
23
31
  #
@@ -53,7 +61,11 @@ module Geocoder
53
61
  :test,
54
62
  :latlon,
55
63
  :amap,
56
- :osmnames
64
+ :osmnames,
65
+ :melissa_street,
66
+ :amazon_location_service,
67
+ :geoapify,
68
+ :photon
57
69
  ]
58
70
  end
59
71
 
@@ -63,6 +75,7 @@ module Geocoder
63
75
  def ip_services
64
76
  @ip_services ||= [
65
77
  :baidu_ip,
78
+ :abstract_api,
66
79
  :freegeoip,
67
80
  :geoip2,
68
81
  :maxmind,
@@ -77,7 +90,8 @@ module Geocoder
77
90
  :db_ip_com,
78
91
  :ipstack,
79
92
  :ip2location,
80
- :ipgeolocation
93
+ :ipgeolocation,
94
+ :ipqualityscore
81
95
  ]
82
96
  end
83
97
 
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+
3
+ require 'geocoder/lookups/base'
4
+ require 'geocoder/results/abstract_api'
5
+
6
+ module Geocoder::Lookup
7
+ class AbstractApi < Base
8
+
9
+ def name
10
+ "Abstract API"
11
+ end
12
+
13
+ def required_api_key_parts
14
+ ['api_key']
15
+ end
16
+
17
+ def supported_protocols
18
+ [:https]
19
+ end
20
+
21
+ private # ---------------------------------------------------------------
22
+
23
+ def base_query_url(query)
24
+ "#{protocol}://ipgeolocation.abstractapi.com/v1/?"
25
+ end
26
+
27
+ def query_url_params(query)
28
+ params = {api_key: configuration.api_key}
29
+
30
+ ip_address = query.sanitized_text
31
+ if ip_address.is_a?(String) && ip_address.length > 0
32
+ params[:ip_address] = ip_address
33
+ end
34
+
35
+ params.merge(super)
36
+ end
37
+
38
+ def results(query, reverse = false)
39
+ if doc = fetch_data(query)
40
+ [doc]
41
+ else
42
+ []
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,54 @@
1
+ require 'geocoder/lookups/base'
2
+ require 'geocoder/results/amazon_location_service'
3
+
4
+ module Geocoder::Lookup
5
+ class AmazonLocationService < Base
6
+ def results(query)
7
+ params = query.options.dup
8
+
9
+ # index_name is required
10
+ # Aws::ParamValidator raises ArgumentError on missing required keys
11
+ params.merge!(index_name: configuration[:index_name])
12
+
13
+ # Aws::ParamValidator raises ArgumentError on unexpected keys
14
+ params.delete(:lookup)
15
+
16
+ resp = if query.reverse_geocode?
17
+ client.search_place_index_for_position(params.merge(position: query.coordinates.reverse))
18
+ else
19
+ client.search_place_index_for_text(params.merge(text: query.text))
20
+ end
21
+
22
+ resp.results.map(&:place)
23
+ end
24
+
25
+ private
26
+
27
+ def client
28
+ return @client if @client
29
+ require_sdk
30
+ keys = configuration.api_key
31
+ if keys
32
+ @client = Aws::LocationService::Client.new(
33
+ access_key_id: keys[:access_key_id],
34
+ secret_access_key: keys[:secret_access_key],
35
+ )
36
+ else
37
+ @client = Aws::LocationService::Client.new
38
+ end
39
+ end
40
+
41
+ def require_sdk
42
+ begin
43
+ require 'aws-sdk-locationservice'
44
+ rescue LoadError
45
+ raise_error(Geocoder::ConfigurationError) ||
46
+ Geocoder.log(
47
+ :error,
48
+ "Couldn't load the Amazon Location Service SDK. " +
49
+ "Install it with: gem install aws-sdk-locationservice -v '~> 1.4'"
50
+ )
51
+ end
52
+ end
53
+ end
54
+ end
@@ -83,8 +83,14 @@ module Geocoder
83
83
  # The working Cache object.
84
84
  #
85
85
  def cache
86
- if @cache.nil? and store = configuration.cache
87
- @cache = Cache.new(store, configuration.cache_prefix)
86
+ if @cache.nil? && (store = configuration.cache)
87
+ cache_options = configuration.cache_options
88
+ cache_prefix = (configuration.cache_prefix rescue false)
89
+ if cache_prefix
90
+ cache_options[:prefix] ||= configuration.cache_prefix
91
+ warn '[Geocoder] cache_prefix is deprecated, please change to cache_options.prefix instead'
92
+ end
93
+ @cache = Cache.new(store, cache_options)
88
94
  end
89
95
  @cache
90
96
  end
@@ -54,7 +54,7 @@ module Geocoder::Lookup
54
54
  def query_url_params(query)
55
55
  {
56
56
  key: configuration.api_key,
57
- language: (query.language || configuration.language)
57
+ culture: (query.language || configuration.language)
58
58
  }.merge(super)
59
59
  end
60
60
 
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'geocoder/lookups/base'
4
+ require 'geocoder/results/geoapify'
5
+
6
+ module Geocoder
7
+ module Lookup
8
+ # https://apidocs.geoapify.com/docs/geocoding/api
9
+ class Geoapify < Base
10
+ def name
11
+ 'Geoapify'
12
+ end
13
+
14
+ def required_api_key_parts
15
+ ['api_key']
16
+ end
17
+
18
+ def supported_protocols
19
+ [:https]
20
+ end
21
+
22
+ private
23
+
24
+ def base_query_url(query)
25
+ method = query.reverse_geocode? ? 'reverse' : 'search'
26
+ "https://api.geoapify.com/v1/geocode/#{method}?"
27
+ end
28
+
29
+ def results(query)
30
+ return [] unless (doc = fetch_data(query))
31
+
32
+ # The rest of the status codes should be already handled by the default
33
+ # functionality as the API returns correct HTTP response codes in most
34
+ # cases. There may be some unhandled cases still (such as over query
35
+ # limit reached) but there is not enough documentation to cover them.
36
+ case doc['statusCode']
37
+ when 500
38
+ raise_error(Geocoder::InvalidRequest) || Geocoder.log(:warn, doc['message'])
39
+ end
40
+
41
+ return [] unless doc['type'] == 'FeatureCollection'
42
+ return [] unless doc['features'] || doc['features'].present?
43
+
44
+ doc['features']
45
+ end
46
+
47
+ def query_url_params(query)
48
+ lang = query.language || configuration.language
49
+ params = { apiKey: configuration.api_key, lang: lang, limit: query.options[:limit] }
50
+
51
+ if query.reverse_geocode?
52
+ params.merge!(query_url_params_reverse(query))
53
+ else
54
+ params.merge!(query_url_params_coordinates(query))
55
+ end
56
+
57
+ params.merge!(super)
58
+ end
59
+
60
+ def query_url_params_coordinates(query)
61
+ { text: query.sanitized_text }
62
+ end
63
+
64
+ def query_url_params_reverse(query)
65
+ {
66
+ lat: query.coordinates[0],
67
+ lon: query.coordinates[1]
68
+ }
69
+ end
70
+ end
71
+ end
72
+ end
@@ -37,6 +37,10 @@ module Geocoder
37
37
  def results(query)
38
38
  return [] unless configuration[:file]
39
39
 
40
+ if @mmdb.respond_to?(:local_ip_alias) && !configuration[:local_ip_alias].nil?
41
+ @mmdb.local_ip_alias = configuration[:local_ip_alias]
42
+ end
43
+
40
44
  result = @mmdb.lookup(query.to_s)
41
45
  result.nil? ? [] : [result]
42
46
  end
@@ -8,6 +8,10 @@ module Geocoder::Lookup
8
8
  "IP2LocationApi"
9
9
  end
10
10
 
11
+ def required_api_key_parts
12
+ ['key']
13
+ end
14
+
11
15
  def supported_protocols
12
16
  [:http, :https]
13
17
  end
@@ -15,15 +19,15 @@ module Geocoder::Lookup
15
19
  private # ----------------------------------------------------------------
16
20
 
17
21
  def base_query_url(query)
18
- "#{protocol}://api.ip2location.com/?"
22
+ "#{protocol}://api.ip2location.com/v2/?"
19
23
  end
20
24
 
21
25
  def query_url_params(query)
22
- params = super
23
- if configuration.has_key?(:package)
24
- params.merge!(package: configuration[:package])
25
- end
26
- params
26
+ super.merge(
27
+ key: configuration.api_key,
28
+ ip: query.sanitized_text,
29
+ package: configuration[:package],
30
+ )
27
31
  end
28
32
 
29
33
  def results(query)
@@ -47,7 +47,7 @@ module Geocoder::Lookup
47
47
  end
48
48
 
49
49
  def host
50
- "api.ipdata.co"
50
+ configuration[:host] || "api.ipdata.co"
51
51
  end
52
52
 
53
53
  def check_response_for_errors!(response)
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ require 'geocoder/lookups/base'
4
+ require 'geocoder/results/ipqualityscore'
5
+
6
+ module Geocoder::Lookup
7
+ class Ipqualityscore < Base
8
+
9
+ def name
10
+ "IPQualityScore"
11
+ end
12
+
13
+ def required_api_key_parts
14
+ ['api_key']
15
+ end
16
+
17
+ private # ---------------------------------------------------------------
18
+
19
+ def base_query_url(query)
20
+ "#{protocol}://ipqualityscore.com/api/json/ip/#{configuration.api_key}/#{query.sanitized_text}?"
21
+ end
22
+
23
+ def valid_response?(response)
24
+ if (json = parse_json(response.body))
25
+ success = json['success']
26
+ end
27
+ super && success == true
28
+ end
29
+
30
+ def results(query, reverse = false)
31
+ return [] unless doc = fetch_data(query)
32
+
33
+ return [doc] if doc['success']
34
+
35
+ case doc['message']
36
+ when /invalid (.*) key/i
37
+ raise_error Geocoder::InvalidApiKey ||
38
+ Geocoder.log(:warn, "#{name} API error: invalid api key.")
39
+ when /insufficient credits/, /exceeded your request quota/
40
+ raise_error Geocoder::OverQueryLimitError ||
41
+ Geocoder.log(:warn, "#{name} API error: query limit exceeded.")
42
+ when /invalid (.*) address/i
43
+ raise_error Geocoder::InvalidRequest ||
44
+ Geocoder.log(:warn, "#{name} API error: invalid request.")
45
+ end
46
+
47
+ [doc]
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,41 @@
1
+ require 'geocoder/lookups/base'
2
+ require "geocoder/results/melissa_street"
3
+
4
+ module Geocoder::Lookup
5
+ class MelissaStreet < Base
6
+
7
+ def name
8
+ "MelissaStreet"
9
+ end
10
+
11
+ def results(query)
12
+ return [] unless doc = fetch_data(query)
13
+
14
+ if doc["TransmissionResults"] == "GE05"
15
+ raise_error(Geocoder::InvalidApiKey) ||
16
+ Geocoder.log(:warn, "Melissa service error: invalid API key.")
17
+ end
18
+
19
+ return doc["Records"]
20
+ end
21
+
22
+ private # ---------------------------------------------------------------
23
+
24
+ def base_query_url(query)
25
+ "#{protocol}://address.melissadata.net/v3/WEB/GlobalAddress/doGlobalAddress?"
26
+ end
27
+
28
+ def query_url_params(query)
29
+ params = {
30
+ id: configuration.api_key,
31
+ format: "JSON",
32
+ a1: query.sanitized_text,
33
+ loc: query.options[:city],
34
+ admarea: query.options[:state],
35
+ postal: query.options[:postal],
36
+ ctry: query.options[:country]
37
+ }
38
+ params.merge(super)
39
+ end
40
+ end
41
+ end