geocoder 1.5.0 → 1.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +53 -2
  3. data/LICENSE +1 -1
  4. data/README.md +23 -46
  5. data/bin/console +13 -0
  6. data/examples/autoexpire_cache_redis.rb +2 -0
  7. data/lib/easting_northing.rb +171 -0
  8. data/lib/geocoder/cache.rb +4 -0
  9. data/lib/geocoder/calculations.rb +1 -1
  10. data/lib/geocoder/configuration.rb +2 -1
  11. data/lib/geocoder/configuration_hash.rb +4 -4
  12. data/lib/geocoder/ip_address.rb +15 -1
  13. data/lib/geocoder/lookup.rb +9 -4
  14. data/lib/geocoder/lookups/abstract_api.rb +46 -0
  15. data/lib/geocoder/lookups/baidu_ip.rb +1 -1
  16. data/lib/geocoder/lookups/ban_data_gouv_fr.rb +14 -1
  17. data/lib/geocoder/lookups/base.rb +7 -2
  18. data/lib/geocoder/lookups/bing.rb +2 -1
  19. data/lib/geocoder/lookups/esri.rb +44 -13
  20. data/lib/geocoder/lookups/freegeoip.rb +7 -7
  21. data/lib/geocoder/lookups/geocodio.rb +1 -1
  22. data/lib/geocoder/lookups/google.rb +7 -2
  23. data/lib/geocoder/lookups/google_places_details.rb +8 -14
  24. data/lib/geocoder/lookups/google_places_search.rb +28 -2
  25. data/lib/geocoder/lookups/google_premier.rb +4 -0
  26. data/lib/geocoder/lookups/here.rb +28 -22
  27. data/lib/geocoder/lookups/ip2location.rb +7 -14
  28. data/lib/geocoder/lookups/ipapi_com.rb +2 -1
  29. data/lib/geocoder/lookups/ipdata_co.rb +5 -4
  30. data/lib/geocoder/lookups/ipgeolocation.rb +51 -0
  31. data/lib/geocoder/lookups/ipinfo_io.rb +2 -11
  32. data/lib/geocoder/lookups/ipregistry.rb +68 -0
  33. data/lib/geocoder/lookups/ipstack.rb +2 -2
  34. data/lib/geocoder/lookups/latlon.rb +1 -2
  35. data/lib/geocoder/lookups/maxmind.rb +2 -2
  36. data/lib/geocoder/lookups/maxmind_geoip2.rb +4 -7
  37. data/lib/geocoder/lookups/maxmind_local.rb +7 -1
  38. data/lib/geocoder/lookups/nationaal_georegister_nl.rb +38 -0
  39. data/lib/geocoder/lookups/osmnames.rb +57 -0
  40. data/lib/geocoder/lookups/pelias.rb +2 -3
  41. data/lib/geocoder/lookups/pickpoint.rb +1 -1
  42. data/lib/geocoder/lookups/pointpin.rb +3 -3
  43. data/lib/geocoder/lookups/smarty_streets.rb +19 -4
  44. data/lib/geocoder/lookups/telize.rb +3 -3
  45. data/lib/geocoder/lookups/tencent.rb +59 -0
  46. data/lib/geocoder/lookups/test.rb +4 -0
  47. data/lib/geocoder/lookups/uk_ordnance_survey_names.rb +59 -0
  48. data/lib/geocoder/lookups/yandex.rb +3 -4
  49. data/lib/geocoder/query.rb +14 -0
  50. data/lib/geocoder/railtie.rb +1 -1
  51. data/lib/geocoder/results/abstract_api.rb +146 -0
  52. data/lib/geocoder/results/baidu.rb +0 -4
  53. data/lib/geocoder/results/ban_data_gouv_fr.rb +27 -2
  54. data/lib/geocoder/results/db_ip_com.rb +1 -1
  55. data/lib/geocoder/results/here.rb +4 -1
  56. data/lib/geocoder/results/ipgeolocation.rb +59 -0
  57. data/lib/geocoder/results/ipregistry.rb +304 -0
  58. data/lib/geocoder/results/nationaal_georegister_nl.rb +62 -0
  59. data/lib/geocoder/results/nominatim.rb +4 -0
  60. data/lib/geocoder/results/osmnames.rb +56 -0
  61. data/lib/geocoder/results/smarty_streets.rb +48 -18
  62. data/lib/geocoder/results/tencent.rb +72 -0
  63. data/lib/geocoder/results/uk_ordnance_survey_names.rb +59 -0
  64. data/lib/geocoder/results/yandex.rb +217 -59
  65. data/lib/geocoder/sql.rb +4 -4
  66. data/lib/geocoder/stores/active_record.rb +1 -1
  67. data/lib/geocoder/util.rb +29 -0
  68. data/lib/geocoder/version.rb +1 -1
  69. data/lib/maxmind_database.rb +3 -3
  70. metadata +24 -18
  71. data/lib/geocoder/lookups/geocoder_us.rb +0 -51
  72. data/lib/geocoder/lookups/mapzen.rb +0 -15
  73. data/lib/geocoder/results/geocoder_us.rb +0 -39
  74. data/lib/geocoder/results/mapzen.rb +0 -5
  75. data/lib/hash_recursive_merge.rb +0 -74
@@ -1,5 +1,6 @@
1
1
  require 'singleton'
2
2
  require 'geocoder/configuration_hash'
3
+ require 'geocoder/util'
3
4
 
4
5
  module Geocoder
5
6
 
@@ -85,7 +86,7 @@ module Geocoder
85
86
  end
86
87
 
87
88
  def configure(options)
88
- @data.rmerge!(options)
89
+ Util.recursive_hash_merge(@data, options)
89
90
  end
90
91
 
91
92
  def initialize # :nodoc
@@ -1,11 +1,11 @@
1
- require 'hash_recursive_merge'
2
-
3
1
  module Geocoder
4
2
  class ConfigurationHash < Hash
5
- include HashRecursiveMerge
6
-
7
3
  def method_missing(meth, *args, &block)
8
4
  has_key?(meth) ? self[meth] : super
9
5
  end
6
+
7
+ def respond_to_missing?(meth, include_private = false)
8
+ has_key?(meth) || super
9
+ end
10
10
  end
11
11
  end
@@ -1,13 +1,27 @@
1
1
  require 'resolv'
2
2
  module Geocoder
3
3
  class IpAddress < String
4
+ PRIVATE_IPS = [
5
+ '10.0.0.0/8',
6
+ '172.16.0.0/12',
7
+ '192.168.0.0/16',
8
+ ].map { |ip| IPAddr.new(ip) }.freeze
9
+
10
+ def internal?
11
+ loopback? || private?
12
+ end
4
13
 
5
14
  def loopback?
6
15
  valid? and !!(self == "0.0.0.0" or self.match(/\A127\./) or self == "::1")
7
16
  end
8
17
 
18
+ def private?
19
+ valid? && PRIVATE_IPS.any? { |ip| ip.include?(self) }
20
+ end
21
+
9
22
  def valid?
10
- !!((self =~ Resolv::IPv4::Regex) || (self =~ Resolv::IPv6::Regex))
23
+ ip = self[/(?<=\[)(.*?)(?=\])/] || self
24
+ !!((ip =~ Resolv::IPv4::Regex) || (ip =~ Resolv::IPv6::Regex))
11
25
  end
12
26
  end
13
27
  end
@@ -32,17 +32,18 @@ module Geocoder
32
32
  :google_places_search,
33
33
  :bing,
34
34
  :geocoder_ca,
35
- :geocoder_us,
36
35
  :yandex,
36
+ :nationaal_georegister_nl,
37
37
  :nominatim,
38
38
  :mapbox,
39
39
  :mapquest,
40
- :mapzen,
40
+ :uk_ordnance_survey_names,
41
41
  :opencagedata,
42
42
  :pelias,
43
43
  :pickpoint,
44
44
  :here,
45
45
  :baidu,
46
+ :tencent,
46
47
  :geocodio,
47
48
  :smarty_streets,
48
49
  :postcode_anywhere_uk,
@@ -51,7 +52,8 @@ module Geocoder
51
52
  :ban_data_gouv_fr,
52
53
  :test,
53
54
  :latlon,
54
- :amap
55
+ :amap,
56
+ :osmnames
55
57
  ]
56
58
  end
57
59
 
@@ -61,6 +63,7 @@ module Geocoder
61
63
  def ip_services
62
64
  @ip_services ||= [
63
65
  :baidu_ip,
66
+ :abstract_api,
64
67
  :freegeoip,
65
68
  :geoip2,
66
69
  :maxmind,
@@ -69,11 +72,13 @@ module Geocoder
69
72
  :pointpin,
70
73
  :maxmind_geoip2,
71
74
  :ipinfo_io,
75
+ :ipregistry,
72
76
  :ipapi_com,
73
77
  :ipdata_co,
74
78
  :db_ip_com,
75
79
  :ipstack,
76
- :ip2location
80
+ :ip2location,
81
+ :ipgeolocation
77
82
  ]
78
83
  end
79
84
 
@@ -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
@@ -1,4 +1,4 @@
1
- require 'geocoder/lookups/base'
1
+ require 'geocoder/lookups/baidu'
2
2
  require 'geocoder/results/baidu_ip'
3
3
 
4
4
  module Geocoder::Lookup
@@ -22,7 +22,7 @@ module Geocoder::Lookup
22
22
  end
23
23
 
24
24
  def any_result?(doc)
25
- doc['features'].any?
25
+ doc['features'] and doc['features'].any?
26
26
  end
27
27
 
28
28
  def results(query)
@@ -86,6 +86,12 @@ module Geocoder::Lookup
86
86
  unless (citycode = query.options[:citycode]).nil? || !code_param_is_valid?(citycode)
87
87
  params[:citycode] = citycode.to_s
88
88
  end
89
+ unless (lat = query.options[:lat]).nil? || !latitude_is_valid?(lat)
90
+ params[:lat] = lat
91
+ end
92
+ unless (lon = query.options[:lon]).nil? || !longitude_is_valid?(lon)
93
+ params[:lon] = lon
94
+ end
89
95
  params
90
96
  end
91
97
 
@@ -126,5 +132,12 @@ module Geocoder::Lookup
126
132
  (1..99999).include?(param.to_i)
127
133
  end
128
134
 
135
+ def latitude_is_valid?(param)
136
+ param.to_f <= 90 && param.to_f >= -90
137
+ end
138
+
139
+ def longitude_is_valid?(param)
140
+ param.to_f <= 180 && param.to_f >= -180
141
+ end
129
142
  end
130
143
  end
@@ -164,7 +164,7 @@ module Geocoder
164
164
  def cache_key_params(query)
165
165
  # omit api_key and token because they may vary among requests
166
166
  query_url_params(query).reject do |key,value|
167
- key.to_s.match /(key|token)/
167
+ key.to_s.match(/(key|token)/)
168
168
  end
169
169
  end
170
170
 
@@ -197,6 +197,8 @@ module Geocoder
197
197
  raise_error(err) or Geocoder.log(:warn, "Geocoding API connection cannot be established.")
198
198
  rescue Errno::ECONNREFUSED => err
199
199
  raise_error(err) or Geocoder.log(:warn, "Geocoding API connection refused.")
200
+ rescue Geocoder::NetworkError => err
201
+ raise_error(err) or Geocoder.log(:warn, "Geocoding API connection is either unreacheable or reset by the peer")
200
202
  rescue Timeout::Error => err
201
203
  raise_error(err) or Geocoder.log(:warn, "Geocoding API not responding fast enough " +
202
204
  "(use Geocoder.configure(:timeout => ...) to set limit).")
@@ -209,7 +211,10 @@ module Geocoder
209
211
  JSON.parse(data)
210
212
  end
211
213
  rescue
212
- raise_error(ResponseParseError.new(data)) or Geocoder.log(:warn, "Geocoding API's response was not valid JSON: #{data}")
214
+ unless raise_error(ResponseParseError.new(data))
215
+ Geocoder.log(:warn, "Geocoding API's response was not valid JSON")
216
+ Geocoder.log(:debug, "Raw response: #{data}")
217
+ end
213
218
  end
214
219
 
215
220
  ##
@@ -53,7 +53,8 @@ module Geocoder::Lookup
53
53
 
54
54
  def query_url_params(query)
55
55
  {
56
- key: configuration.api_key
56
+ key: configuration.api_key,
57
+ language: (query.language || configuration.language)
57
58
  }.merge(super)
58
59
  end
59
60
 
@@ -9,6 +9,10 @@ module Geocoder::Lookup
9
9
  "Esri"
10
10
  end
11
11
 
12
+ def supported_protocols
13
+ [:https]
14
+ end
15
+
12
16
  private # ---------------------------------------------------------------
13
17
 
14
18
  def base_query_url(query)
@@ -40,31 +44,58 @@ module Geocoder::Lookup
40
44
  else
41
45
  params[:text] = query.sanitized_text
42
46
  end
43
- params[:token] = token
44
- params[:forStorage] = configuration[:for_storage] if configuration[:for_storage]
47
+
48
+ params[:token] = token(query)
49
+
50
+ if for_storage_value = for_storage(query)
51
+ params[:forStorage] = for_storage_value
52
+ end
45
53
  params[:sourceCountry] = configuration[:source_country] if configuration[:source_country]
54
+ params[:preferredLabelValues] = configuration[:preferred_label_values] if configuration[:preferred_label_values]
55
+
46
56
  params.merge(super)
47
57
  end
48
58
 
49
- def token
50
- create_and_save_token! if !valid_token_configured? and configuration.api_key
51
- configuration[:token].to_s unless configuration[:token].nil?
59
+ def for_storage(query)
60
+ if query.options.has_key?(:for_storage)
61
+ query.options[:for_storage]
62
+ else
63
+ configuration[:for_storage]
64
+ end
52
65
  end
53
66
 
54
- def valid_token_configured?
55
- !configuration[:token].nil? and configuration[:token].active?
67
+ def token(query)
68
+ token_instance = if query.options[:token]
69
+ query.options[:token]
70
+ else
71
+ configuration[:token]
72
+ end
73
+
74
+ if !valid_token_configured?(token_instance) && configuration.api_key
75
+ token_instance = create_and_save_token!(query)
76
+ end
77
+
78
+ token_instance.to_s unless token_instance.nil?
56
79
  end
57
80
 
58
- def create_and_save_token!
59
- save_token!(create_token)
81
+ def valid_token_configured?(token_instance)
82
+ !token_instance.nil? && token_instance.active?
60
83
  end
61
84
 
62
- def create_token
63
- Geocoder::EsriToken.generate_token(*configuration.api_key)
85
+ def create_and_save_token!(query)
86
+ token_instance = create_token
87
+
88
+ if query.options[:token]
89
+ query.options[:token] = token_instance
90
+ else
91
+ Geocoder.merge_into_lookup_config(:esri, token: token_instance)
92
+ end
93
+
94
+ token_instance
64
95
  end
65
96
 
66
- def save_token!(token_instance)
67
- Geocoder.merge_into_lookup_config(:esri, token: token_instance)
97
+ def create_token
98
+ Geocoder::EsriToken.generate_token(*configuration.api_key)
68
99
  end
69
100
  end
70
101
  end
@@ -7,10 +7,10 @@ module Geocoder::Lookup
7
7
  def name
8
8
  "FreeGeoIP"
9
9
  end
10
-
10
+
11
11
  def supported_protocols
12
12
  if configuration[:host]
13
- [:http, :https]
13
+ [:https]
14
14
  else
15
15
  # use https for default host
16
16
  [:https]
@@ -32,8 +32,8 @@ module Geocoder::Lookup
32
32
  end
33
33
 
34
34
  def results(query)
35
- # don't look up a loopback address, just return the stored result
36
- return [reserved_result(query.text)] if query.loopback_ip_address?
35
+ # don't look up a loopback or private address, just return the stored result
36
+ return [reserved_result(query.text)] if query.internal_ip_address?
37
37
  # note: Freegeoip.net returns plain text "Not Found" on bad request
38
38
  (doc = fetch_data(query)) ? [doc] : []
39
39
  end
@@ -44,8 +44,8 @@ module Geocoder::Lookup
44
44
  "city" => "",
45
45
  "region_code" => "",
46
46
  "region_name" => "",
47
- "metrocode" => "",
48
- "zipcode" => "",
47
+ "metro_code" => "",
48
+ "zip_code" => "",
49
49
  "latitude" => "0",
50
50
  "longitude" => "0",
51
51
  "country_name" => "Reserved",
@@ -54,7 +54,7 @@ module Geocoder::Lookup
54
54
  end
55
55
 
56
56
  def host
57
- configuration[:host] || "freegeoip.net"
57
+ configuration[:host] || "freegeoip.app"
58
58
  end
59
59
  end
60
60
  end
@@ -29,7 +29,7 @@ module Geocoder::Lookup
29
29
 
30
30
  def base_query_url(query)
31
31
  path = query.reverse_geocode? ? "reverse" : "geocode"
32
- "#{protocol}://api.geocod.io/v1.3/#{path}?"
32
+ "#{protocol}://api.geocod.io/v1.6/#{path}?"
33
33
  end
34
34
 
35
35
  def query_url_params(query)
@@ -44,10 +44,15 @@ module Geocoder::Lookup
44
44
  super(response) and ['OK', 'ZERO_RESULTS'].include?(status)
45
45
  end
46
46
 
47
+ def result_root_attr
48
+ 'results'
49
+ end
50
+
47
51
  def results(query)
48
52
  return [] unless doc = fetch_data(query)
49
- case doc['status']; when "OK" # OK status implies >0 results
50
- return doc['results']
53
+ case doc['status']
54
+ when "OK" # OK status implies >0 results
55
+ return doc[result_root_attr]
51
56
  when "OVER_QUERY_LIMIT"
52
57
  raise_error(Geocoder::OverQueryLimitError) ||
53
58
  Geocoder.log(:warn, "#{name} API error: over query limit.")
@@ -22,21 +22,15 @@ module Geocoder
22
22
  "#{protocol}://maps.googleapis.com/maps/api/place/details/json?"
23
23
  end
24
24
 
25
+ def result_root_attr
26
+ 'result'
27
+ end
28
+
25
29
  def results(query)
26
- return [] unless doc = fetch_data(query)
27
-
28
- case doc["status"]
29
- when "OK"
30
- return [doc["result"]]
31
- when "OVER_QUERY_LIMIT"
32
- raise_error(Geocoder::OverQueryLimitError) || Geocoder.log(:warn, "Google Places Details API error: over query limit.")
33
- when "REQUEST_DENIED"
34
- raise_error(Geocoder::RequestDenied) || Geocoder.log(:warn, "Google Places Details API error: request denied.")
35
- when "INVALID_REQUEST"
36
- raise_error(Geocoder::InvalidRequest) || Geocoder.log(:warn, "Google Places Details API error: invalid request.")
37
- end
38
-
39
- []
30
+ result = super(query)
31
+ return [result] unless result.is_a? Array
32
+
33
+ result
40
34
  end
41
35
 
42
36
  def query_url_google_params(query)
@@ -18,16 +18,42 @@ module Geocoder
18
18
 
19
19
  private
20
20
 
21
+ def result_root_attr
22
+ 'candidates'
23
+ end
24
+
21
25
  def base_query_url(query)
22
- "#{protocol}://maps.googleapis.com/maps/api/place/textsearch/json?"
26
+ "#{protocol}://maps.googleapis.com/maps/api/place/findplacefromtext/json?"
23
27
  end
24
28
 
25
29
  def query_url_google_params(query)
26
30
  {
27
- query: query.text,
31
+ input: query.text,
32
+ inputtype: 'textquery',
33
+ fields: fields(query),
28
34
  language: query.language || configuration.language
29
35
  }
30
36
  end
37
+
38
+ def fields(query)
39
+ query_fields = query.options[:fields]
40
+ return format_fields(query_fields) if query_fields
41
+
42
+ default_fields
43
+ end
44
+
45
+ def default_fields
46
+ legacy = %w[id reference]
47
+ basic = %w[business_status formatted_address geometry icon name
48
+ photos place_id plus_code types]
49
+ contact = %w[opening_hours]
50
+ atmosphere = %W[price_level rating user_ratings_total]
51
+ format_fields(legacy, basic, contact, atmosphere)
52
+ end
53
+
54
+ def format_fields(*fields)
55
+ fields.flatten.join(',')
56
+ end
31
57
  end
32
58
  end
33
59
  end