geocoder 1.6.4 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -18,6 +18,8 @@ module Geocoder
18
18
  when store.respond_to?(:read)
19
19
  store.read key_for(url)
20
20
  end
21
+ rescue => e
22
+ warn "Geocoder cache read error: #{e}"
21
23
  end
22
24
 
23
25
  ##
@@ -32,6 +34,8 @@ module Geocoder
32
34
  when store.respond_to?(:write)
33
35
  store.write key_for(url), value
34
36
  end
37
+ rescue => e
38
+ warn "Geocoder cache write error: #{e}"
35
39
  end
36
40
 
37
41
  ##
@@ -60,7 +64,11 @@ module Geocoder
60
64
  # Cache key for a given URL.
61
65
  #
62
66
  def key_for(url)
63
- [prefix, url].join
67
+ if url.match(/^#{prefix}/)
68
+ url
69
+ else
70
+ [prefix, url].join
71
+ end
64
72
  end
65
73
 
66
74
  ##
@@ -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,
@@ -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,53 @@
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 = { **global_index_name, **query.options }
8
+ if query.reverse_geocode?
9
+ resp = client.search_place_index_for_position(**{ **params, position: query.coordinates.reverse })
10
+ else
11
+ resp = client.search_place_index_for_text(**{ **params, text: query.text })
12
+ end
13
+ resp.results.map(&:place)
14
+ end
15
+
16
+ private
17
+
18
+ def client
19
+ return @client if @client
20
+ require_sdk
21
+ keys = configuration.api_key
22
+ if keys
23
+ @client = Aws::LocationService::Client.new(
24
+ access_key_id: keys[:access_key_id],
25
+ secret_access_key: keys[:secret_access_key],
26
+ )
27
+ else
28
+ @client = Aws::LocationService::Client.new
29
+ end
30
+ end
31
+
32
+ def require_sdk
33
+ begin
34
+ require 'aws-sdk-locationservice'
35
+ rescue LoadError
36
+ raise_error(Geocoder::ConfigurationError) ||
37
+ Geocoder.log(
38
+ :error,
39
+ "Couldn't load the Amazon Location Service SDK. " +
40
+ "Install it with: gem install aws-sdk-locationservice -v '~> 1.4'"
41
+ )
42
+ end
43
+ end
44
+
45
+ def global_index_name
46
+ if configuration[:index_name]
47
+ { index_name: configuration[:index_name] }
48
+ else
49
+ {}
50
+ end
51
+ end
52
+ end
53
+ 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)
@@ -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
@@ -30,7 +30,13 @@ module Geocoder::Lookup
30
30
  def results(query)
31
31
  if configuration[:file]
32
32
  geoip_class = RUBY_PLATFORM == "java" ? JGeoIP : GeoIP
33
- result = geoip_class.new(configuration[:file]).city(query.to_s)
33
+ geoip_instance = geoip_class.new(configuration[:file])
34
+ result =
35
+ if configuration[:package] == :country
36
+ geoip_instance.country(query.to_s)
37
+ else
38
+ geoip_instance.city(query.to_s)
39
+ end
34
40
  result.nil? ? [] : [encode_hash(result.to_hash)]
35
41
  elsif configuration[:package] == :city
36
42
  addr = IPAddr.new(query.text).to_i
@@ -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
@@ -0,0 +1,89 @@
1
+ require 'geocoder/lookups/base'
2
+ require 'geocoder/results/photon'
3
+
4
+ module Geocoder::Lookup
5
+ class Photon < Base
6
+ def name
7
+ 'Photon'
8
+ end
9
+
10
+ private # ---------------------------------------------------------------
11
+
12
+ def supported_protocols
13
+ [:https]
14
+ end
15
+
16
+ def base_query_url(query)
17
+ host = configuration[:host] || 'photon.komoot.io'
18
+ method = query.reverse_geocode? ? 'reverse' : 'api'
19
+ "#{protocol}://#{host}/#{method}?"
20
+ end
21
+
22
+ def results(query)
23
+ return [] unless (doc = fetch_data(query))
24
+ return [] unless doc['type'] == 'FeatureCollection'
25
+ return [] unless doc['features'] || doc['features'].present?
26
+
27
+ doc['features']
28
+ end
29
+
30
+ def query_url_params(query)
31
+ lang = query.language || configuration.language
32
+ params = { lang: lang, limit: query.options[:limit] }
33
+
34
+ if query.reverse_geocode?
35
+ params.merge!(query_url_params_reverse(query))
36
+ else
37
+ params.merge!(query_url_params_coordinates(query))
38
+ end
39
+
40
+ params.merge!(super)
41
+ end
42
+
43
+ def query_url_params_coordinates(query)
44
+ params = { q: query.sanitized_text }
45
+
46
+ if (bias = query.options[:bias])
47
+ params.merge!(lat: bias[:latitude], lon: bias[:longitude], location_bias_scale: bias[:scale])
48
+ end
49
+
50
+ if (filter = query_url_params_coordinates_filter(query))
51
+ params.merge!(filter)
52
+ end
53
+
54
+ params
55
+ end
56
+
57
+ def query_url_params_coordinates_filter(query)
58
+ filter = query.options[:filter]
59
+ return unless filter
60
+
61
+ bbox = filter[:bbox]
62
+ {
63
+ bbox: bbox.is_a?(Array) ? bbox.join(',') : bbox,
64
+ osm_tag: filter[:osm_tag]
65
+ }
66
+ end
67
+
68
+ def query_url_params_reverse(query)
69
+ params = { lat: query.coordinates[0], lon: query.coordinates[1], radius: query.options[:radius] }
70
+
71
+ if (dsort = query.options[:distance_sort])
72
+ params[:distance_sort] = dsort ? 'true' : 'false'
73
+ end
74
+
75
+ if (filter = query_url_params_reverse_filter(query))
76
+ params.merge!(filter)
77
+ end
78
+
79
+ params
80
+ end
81
+
82
+ def query_url_params_reverse_filter(query)
83
+ filter = query.options[:filter]
84
+ return unless filter
85
+
86
+ { query_string_filter: filter[:string] }
87
+ end
88
+ end
89
+ end
@@ -28,6 +28,10 @@ module Geocoder
28
28
  @stubs ||= {}
29
29
  end
30
30
 
31
+ def self.delete_stub(query_text)
32
+ stubs.delete(query_text)
33
+ end
34
+
31
35
  def self.reset
32
36
  @stubs = {}
33
37
  @default_stub = nil
@@ -13,7 +13,7 @@ module Geocoder::Lookup
13
13
  end
14
14
 
15
15
  def base_query_url(query)
16
- "#{protocol}://api.ordnancesurvey.co.uk/opennames/v1/find?"
16
+ "#{protocol}://api.os.uk/search/names/v1/find?"
17
17
  end
18
18
 
19
19
  def required_api_key_parts
@@ -0,0 +1,146 @@
1
+ require 'geocoder/results/base'
2
+
3
+ module Geocoder
4
+ module Result
5
+ class AbstractApi < Base
6
+
7
+ ##
8
+ # Geolocation
9
+
10
+ def state
11
+ @data['region']
12
+ end
13
+
14
+ def state_code
15
+ @data['region_iso_code']
16
+ end
17
+
18
+ def city
19
+ @data["city"]
20
+ end
21
+
22
+ def city_geoname_id
23
+ @data["city_geoname_id"]
24
+ end
25
+
26
+ def region_geoname_id
27
+ @data["region_geoname_id"]
28
+ end
29
+
30
+ def postal_code
31
+ @data["postal_code"]
32
+ end
33
+
34
+ def country
35
+ @data["country"]
36
+ end
37
+
38
+ def country_code
39
+ @data["country_code"]
40
+ end
41
+
42
+ def country_geoname_id
43
+ @data["country_geoname_id"]
44
+ end
45
+
46
+ def country_is_eu
47
+ @data["country_is_eu"]
48
+ end
49
+
50
+ def continent
51
+ @data["continent"]
52
+ end
53
+
54
+ def continent_code
55
+ @data["continent_code"]
56
+ end
57
+
58
+ def continent_geoname_id
59
+ @data["continent_geoname_id"]
60
+ end
61
+
62
+ ##
63
+ # Security
64
+
65
+ def is_vpn?
66
+ @data.dig "security", "is_vpn"
67
+ end
68
+
69
+ ##
70
+ # Timezone
71
+
72
+ def timezone_name
73
+ @data.dig "timezone", "name"
74
+ end
75
+
76
+ def timezone_abbreviation
77
+ @data.dig "timezone", "abbreviation"
78
+ end
79
+
80
+ def timezone_gmt_offset
81
+ @data.dig "timezone", "gmt_offset"
82
+ end
83
+
84
+ def timezone_current_time
85
+ @data.dig "timezone", "current_time"
86
+ end
87
+
88
+ def timezone_is_dst
89
+ @data.dig "timezone", "is_dst"
90
+ end
91
+
92
+ ##
93
+ # Flag
94
+
95
+ def flag_emoji
96
+ @data.dig "flag", "emoji"
97
+ end
98
+
99
+ def flag_unicode
100
+ @data.dig "flag", "unicode"
101
+ end
102
+
103
+ def flag_png
104
+ @data.dig "flag", "png"
105
+ end
106
+
107
+ def flag_svg
108
+ @data.dig "flag", "svg"
109
+ end
110
+
111
+ ##
112
+ # Currency
113
+
114
+ def currency_currency_name
115
+ @data.dig "currency", "currency_name"
116
+ end
117
+
118
+ def currency_currency_code
119
+ @data.dig "currency", "currency_code"
120
+ end
121
+
122
+ ##
123
+ # Connection
124
+
125
+ def connection_autonomous_system_number
126
+ @data.dig "connection", "autonomous_system_number"
127
+ end
128
+
129
+ def connection_autonomous_system_organization
130
+ @data.dig "connection", "autonomous_system_organization"
131
+ end
132
+
133
+ def connection_connection_type
134
+ @data.dig "connection", "connection_type"
135
+ end
136
+
137
+ def connection_isp_name
138
+ @data.dig "connection", "isp_name"
139
+ end
140
+
141
+ def connection_organization_name
142
+ @data.dig "connection", "organization_name"
143
+ end
144
+ end
145
+ end
146
+ end