geocoder 1.4.8 → 1.6.0

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 (92) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +31 -0
  3. data/LICENSE +1 -1
  4. data/README.md +355 -950
  5. data/examples/autoexpire_cache_redis.rb +2 -0
  6. data/lib/generators/geocoder/config/templates/initializer.rb +1 -1
  7. data/lib/geocoder/calculations.rb +1 -1
  8. data/lib/geocoder/cli.rb +2 -2
  9. data/lib/geocoder/configuration.rb +1 -1
  10. data/lib/geocoder/exceptions.rb +1 -1
  11. data/lib/geocoder/ip_address.rb +14 -1
  12. data/lib/geocoder/lookup.rb +7 -5
  13. data/lib/geocoder/lookups/amap.rb +7 -3
  14. data/lib/geocoder/lookups/baidu.rb +14 -10
  15. data/lib/geocoder/lookups/baidu_ip.rb +7 -36
  16. data/lib/geocoder/lookups/ban_data_gouv_fr.rb +17 -4
  17. data/lib/geocoder/lookups/base.rb +27 -4
  18. data/lib/geocoder/lookups/bing.rb +10 -13
  19. data/lib/geocoder/lookups/db_ip_com.rb +9 -6
  20. data/lib/geocoder/lookups/dstk.rb +4 -2
  21. data/lib/geocoder/lookups/esri.rb +39 -29
  22. data/lib/geocoder/lookups/freegeoip.rb +11 -7
  23. data/lib/geocoder/lookups/geocoder_ca.rb +4 -4
  24. data/lib/geocoder/lookups/geocodio.rb +5 -5
  25. data/lib/geocoder/lookups/geoportail_lu.rb +7 -7
  26. data/lib/geocoder/lookups/google.rb +8 -8
  27. data/lib/geocoder/lookups/google_places_details.rb +4 -4
  28. data/lib/geocoder/lookups/google_places_search.rb +4 -4
  29. data/lib/geocoder/lookups/google_premier.rb +10 -0
  30. data/lib/geocoder/lookups/here.rb +29 -23
  31. data/lib/geocoder/lookups/ip2location.rb +67 -0
  32. data/lib/geocoder/lookups/ipapi_com.rb +9 -13
  33. data/lib/geocoder/lookups/ipdata_co.rb +9 -4
  34. data/lib/geocoder/lookups/ipgeolocation.rb +63 -0
  35. data/lib/geocoder/lookups/ipinfo_io.rb +11 -29
  36. data/lib/geocoder/lookups/ipregistry.rb +68 -0
  37. data/lib/geocoder/lookups/ipstack.rb +11 -12
  38. data/lib/geocoder/lookups/latlon.rb +4 -4
  39. data/lib/geocoder/lookups/location_iq.rb +10 -4
  40. data/lib/geocoder/lookups/mapbox.rb +7 -6
  41. data/lib/geocoder/lookups/mapquest.rb +4 -5
  42. data/lib/geocoder/lookups/maxmind.rb +6 -6
  43. data/lib/geocoder/lookups/maxmind_geoip2.rb +8 -7
  44. data/lib/geocoder/lookups/nominatim.rb +4 -4
  45. data/lib/geocoder/lookups/opencagedata.rb +6 -5
  46. data/lib/geocoder/lookups/osmnames.rb +57 -0
  47. data/lib/geocoder/lookups/pelias.rb +8 -9
  48. data/lib/geocoder/lookups/pickpoint.rb +9 -3
  49. data/lib/geocoder/lookups/pointpin.rb +10 -9
  50. data/lib/geocoder/lookups/postcode_anywhere_uk.rb +4 -5
  51. data/lib/geocoder/lookups/postcodes_io.rb +31 -0
  52. data/lib/geocoder/lookups/smarty_streets.rb +20 -10
  53. data/lib/geocoder/lookups/telize.rb +23 -3
  54. data/lib/geocoder/lookups/tencent.rb +59 -0
  55. data/lib/geocoder/lookups/yandex.rb +6 -6
  56. data/lib/geocoder/query.rb +14 -0
  57. data/lib/geocoder/railtie.rb +1 -1
  58. data/lib/geocoder/results/baidu.rb +10 -14
  59. data/lib/geocoder/results/ban_data_gouv_fr.rb +1 -1
  60. data/lib/geocoder/results/base.rb +13 -1
  61. data/lib/geocoder/results/bing.rb +1 -1
  62. data/lib/geocoder/results/db_ip_com.rb +0 -5
  63. data/lib/geocoder/results/freegeoip.rb +0 -5
  64. data/lib/geocoder/results/geocoder_ca.rb +3 -3
  65. data/lib/geocoder/results/geoip2.rb +4 -4
  66. data/lib/geocoder/results/geoportail_lu.rb +5 -3
  67. data/lib/geocoder/results/here.rb +4 -1
  68. data/lib/geocoder/results/ip2location.rb +22 -0
  69. data/lib/geocoder/results/ipdata_co.rb +0 -5
  70. data/lib/geocoder/results/ipgeolocation.rb +59 -0
  71. data/lib/geocoder/results/ipregistry.rb +308 -0
  72. data/lib/geocoder/results/maxmind.rb +0 -5
  73. data/lib/geocoder/results/maxmind_local.rb +0 -5
  74. data/lib/geocoder/results/osmnames.rb +56 -0
  75. data/lib/geocoder/results/postcodes_io.rb +40 -0
  76. data/lib/geocoder/results/smarty_streets.rb +48 -18
  77. data/lib/geocoder/results/telize.rb +0 -5
  78. data/lib/geocoder/results/tencent.rb +72 -0
  79. data/lib/geocoder/results/test.rb +1 -1
  80. data/lib/geocoder/stores/active_record.rb +1 -3
  81. data/lib/geocoder/version.rb +1 -1
  82. data/lib/hash_recursive_merge.rb +1 -2
  83. data/lib/maxmind_database.rb +3 -3
  84. metadata +17 -18
  85. data/lib/geocoder/lookups/geocoder_us.rb +0 -43
  86. data/lib/geocoder/lookups/mapzen.rb +0 -15
  87. data/lib/geocoder/lookups/okf.rb +0 -44
  88. data/lib/geocoder/lookups/ovi.rb +0 -62
  89. data/lib/geocoder/results/geocoder_us.rb +0 -39
  90. data/lib/geocoder/results/mapzen.rb +0 -5
  91. data/lib/geocoder/results/okf.rb +0 -106
  92. data/lib/geocoder/results/ovi.rb +0 -71
@@ -1,6 +1,8 @@
1
1
  # This class implements a cache with simple delegation to the Redis store, but
2
2
  # when it creates a key/value pair, it also sends an EXPIRE command with a TTL.
3
3
  # It should be fairly simple to do the same thing with Memcached.
4
+ # Alternatively, this class could inherit from Redis, which would make most
5
+ # of the below methods unnecessary.
4
6
  class AutoexpireCacheRedis
5
7
  def initialize(store, ttl = 86400)
6
8
  @store = store
@@ -1,7 +1,7 @@
1
1
  Geocoder.configure(
2
2
  # Geocoding options
3
3
  # timeout: 3, # geocoding service timeout (secs)
4
- # lookup: :google, # name of geocoding service (symbol)
4
+ # lookup: :nominatim, # name of geocoding service (symbol)
5
5
  # ip_lookup: :ipinfo_io, # name of IP address geocoding service (symbol)
6
6
  # language: :en, # ISO-639 language code
7
7
  # use_https: false, # use HTTPS for lookup requests? (if supported)
@@ -154,7 +154,7 @@ module Geocoder
154
154
  # Translate a bearing (float) into a compass direction (string, eg "North").
155
155
  #
156
156
  def compass_point(bearing, points = COMPASS_POINTS)
157
- seg_size = 360 / points.size
157
+ seg_size = 360.0 / points.size
158
158
  points[((bearing + (seg_size / 2)) % 360) / seg_size]
159
159
  end
160
160
 
@@ -97,7 +97,7 @@ module Geocoder
97
97
  end
98
98
 
99
99
  if (result = Geocoder.search(query).first)
100
- google = Geocoder::Lookup.get(:google)
100
+ nominatim = Geocoder::Lookup.get(:nominatim)
101
101
  lines = [
102
102
  ["Latitude", result.latitude],
103
103
  ["Longitude", result.longitude],
@@ -106,7 +106,7 @@ module Geocoder
106
106
  ["State/province", result.state],
107
107
  ["Postal code", result.postal_code],
108
108
  ["Country", result.country],
109
- ["Google map", google.map_link_url(result.coordinates)],
109
+ ["Map", nominatim.map_link_url(result.coordinates)],
110
110
  ]
111
111
  lines.each do |line|
112
112
  out << (line[0] + ": ").ljust(18) + line[1].to_s + "\n"
@@ -97,7 +97,7 @@ module Geocoder
97
97
 
98
98
  # geocoding options
99
99
  @data[:timeout] = 3 # geocoding service timeout (secs)
100
- @data[:lookup] = :google # name of street address geocoding service (symbol)
100
+ @data[:lookup] = :nominatim # name of street address geocoding service (symbol)
101
101
  @data[:ip_lookup] = :ipinfo_io # name of IP address geocoding service (symbol)
102
102
  @data[:language] = :en # ISO-639 language code
103
103
  @data[:http_headers] = {} # HTTP headers for lookup
@@ -1,4 +1,4 @@
1
- require 'timeout' # required for Ruby 1.9.3
1
+ require 'timeout'
2
2
 
3
3
  module Geocoder
4
4
 
@@ -1,9 +1,22 @@
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
- valid? and (self == "0.0.0.0" or self.match(/\A127\./) or self == "::1")
15
+ valid? and !!(self == "0.0.0.0" or self.match(/\A127\./) or self == "::1")
16
+ end
17
+
18
+ def private?
19
+ valid? && PRIVATE_IPS.any? { |ip| ip.include?(self) }
7
20
  end
8
21
 
9
22
  def valid?
@@ -32,27 +32,26 @@ module Geocoder
32
32
  :google_places_search,
33
33
  :bing,
34
34
  :geocoder_ca,
35
- :geocoder_us,
36
35
  :yandex,
37
36
  :nominatim,
38
37
  :mapbox,
39
38
  :mapquest,
40
- :mapzen,
41
39
  :opencagedata,
42
- :ovi,
43
40
  :pelias,
44
41
  :pickpoint,
45
42
  :here,
46
43
  :baidu,
44
+ :tencent,
47
45
  :geocodio,
48
46
  :smarty_streets,
49
- :okf,
50
47
  :postcode_anywhere_uk,
48
+ :postcodes_io,
51
49
  :geoportail_lu,
52
50
  :ban_data_gouv_fr,
53
51
  :test,
54
52
  :latlon,
55
- :amap
53
+ :amap,
54
+ :osmnames
56
55
  ]
57
56
  end
58
57
 
@@ -70,10 +69,13 @@ module Geocoder
70
69
  :pointpin,
71
70
  :maxmind_geoip2,
72
71
  :ipinfo_io,
72
+ :ipregistry,
73
73
  :ipapi_com,
74
74
  :ipdata_co,
75
75
  :db_ip_com,
76
76
  :ipstack,
77
+ :ip2location,
78
+ :ipgeolocation
77
79
  ]
78
80
  end
79
81
 
@@ -12,13 +12,17 @@ module Geocoder::Lookup
12
12
  ["key"]
13
13
  end
14
14
 
15
- def query_url(query)
16
- path = query.reverse_geocode? ? 'regeo' : 'geo'
17
- "http://restapi.amap.com/v3/geocode/#{path}?" + url_query_string(query)
15
+ def supported_protocols
16
+ [:http]
18
17
  end
19
18
 
20
19
  private # ---------------------------------------------------------------
21
20
 
21
+ def base_query_url(query)
22
+ path = query.reverse_geocode? ? 'regeo' : 'geo'
23
+ "http://restapi.amap.com/v3/geocode/#{path}?"
24
+ end
25
+
22
26
  def results(query, reverse = false)
23
27
  return [] unless doc = fetch_data(query)
24
28
  case [doc['status'], doc['info']]
@@ -12,10 +12,6 @@ module Geocoder::Lookup
12
12
  ["key"]
13
13
  end
14
14
 
15
- def query_url(query)
16
- "#{protocol}://api.map.baidu.com/geocoder/v2/?" + url_query_string(query)
17
- end
18
-
19
15
  # HTTP only
20
16
  def supported_protocols
21
17
  [:http]
@@ -23,26 +19,34 @@ module Geocoder::Lookup
23
19
 
24
20
  private # ---------------------------------------------------------------
25
21
 
22
+ def base_query_url(query)
23
+ "#{protocol}://api.map.baidu.com/geocoder/v2/?"
24
+ end
25
+
26
+ def content_key
27
+ 'result'
28
+ end
29
+
26
30
  def results(query, reverse = false)
27
31
  return [] unless doc = fetch_data(query)
28
32
  case doc['status']
29
33
  when 0
30
- return [doc['result']] unless doc['result'].blank?
34
+ return [doc[content_key]] unless doc[content_key].blank?
31
35
  when 1, 3, 4
32
36
  raise_error(Geocoder::Error, "server error.") ||
33
- Geocoder.log(:warn, "Baidu Geocoding API error: server error.")
37
+ Geocoder.log(:warn, "#{name} Geocoding API error: server error.")
34
38
  when 2
35
39
  raise_error(Geocoder::InvalidRequest, "invalid request.") ||
36
- Geocoder.log(:warn, "Baidu Geocoding API error: invalid request.")
40
+ Geocoder.log(:warn, "#{name} Geocoding API error: invalid request.")
37
41
  when 5
38
42
  raise_error(Geocoder::InvalidApiKey, "invalid api key") ||
39
- Geocoder.log(:warn, "Baidu Geocoding API error: invalid api key.")
43
+ Geocoder.log(:warn, "#{name} Geocoding API error: invalid api key.")
40
44
  when 101, 102, 200..299
41
45
  raise_error(Geocoder::RequestDenied, "request denied") ||
42
- Geocoder.log(:warn, "Baidu Geocoding API error: request denied.")
46
+ Geocoder.log(:warn, "#{name} Geocoding API error: request denied.")
43
47
  when 300..399
44
48
  raise_error(Geocoder::OverQueryLimitError, "over query limit.") ||
45
- Geocoder.log(:warn, "Baidu Geocoding API error: over query limit.")
49
+ Geocoder.log(:warn, "#{name} Geocoding API error: over query limit.")
46
50
  end
47
51
  return []
48
52
  end
@@ -1,50 +1,21 @@
1
- require 'geocoder/lookups/base'
1
+ require 'geocoder/lookups/baidu'
2
2
  require 'geocoder/results/baidu_ip'
3
3
 
4
4
  module Geocoder::Lookup
5
- class BaiduIp < Base
5
+ class BaiduIp < Baidu
6
6
 
7
7
  def name
8
8
  "Baidu IP"
9
9
  end
10
10
 
11
- def required_api_key_parts
12
- ["key"]
13
- end
14
-
15
- def query_url(query)
16
- "#{protocol}://api.map.baidu.com/location/ip?" + url_query_string(query)
17
- end
11
+ private # ---------------------------------------------------------------
18
12
 
19
- # HTTP only
20
- def supported_protocols
21
- [:http]
13
+ def base_query_url(query)
14
+ "#{protocol}://api.map.baidu.com/location/ip?"
22
15
  end
23
16
 
24
- private # ---------------------------------------------------------------
25
-
26
- def results(query, reverse = false)
27
- return [] unless doc = fetch_data(query)
28
- case doc['status']
29
- when 0
30
- return [doc['content']] unless doc['content'].blank?
31
- when 1, 3, 4
32
- raise_error(Geocoder::Error, "server error.") ||
33
- Geocoder.log(:warn, "Baidu IP Geocoding API error: server error.")
34
- when 2
35
- raise_error(Geocoder::InvalidRequest, "invalid request.") ||
36
- Geocoder.log(:warn, "Baidu IP Geocoding API error: invalid request.")
37
- when 5
38
- raise_error(Geocoder::InvalidApiKey, "invalid api key.") ||
39
- Geocoder.log(:warn, "Baidu IP Geocoding API error: invalid api key.")
40
- when 101, 102, 200..299
41
- raise_error(Geocoder::RequestDenied, "request denied.") ||
42
- Geocoder.log(:warn, "Baidu IP Geocoding API error: request denied.")
43
- when 300..399
44
- raise_error(Geocoder::OverQueryLimitError, "over query limit") ||
45
- Geocoder.log(:warn, "Baidu IP Geocoding API error: over query limit.")
46
- end
47
- return []
17
+ def content_key
18
+ 'content'
48
19
  end
49
20
 
50
21
  def query_url_params(query)
@@ -14,13 +14,13 @@ module Geocoder::Lookup
14
14
  "https://www.openstreetmap.org/#map=19/#{coordinates.join('/')}"
15
15
  end
16
16
 
17
- def query_url(query)
17
+ private # ---------------------------------------------------------------
18
+
19
+ def base_query_url(query)
18
20
  method = query.reverse_geocode? ? "reverse" : "search"
19
- "#{protocol}://api-adresse.data.gouv.fr/#{method}/?" + url_query_string(query)
21
+ "#{protocol}://api-adresse.data.gouv.fr/#{method}/?"
20
22
  end
21
23
 
22
- private # ---------------------------------------------------------------
23
-
24
24
  def any_result?(doc)
25
25
  doc['features'].any?
26
26
  end
@@ -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
@@ -4,7 +4,6 @@ require 'uri'
4
4
 
5
5
  unless defined?(ActiveSupport::JSON)
6
6
  begin
7
- require 'rubygems' # for Ruby 1.8
8
7
  require 'json'
9
8
  rescue LoadError
10
9
  raise LoadError, "Please install the 'json' or 'json_pure' gem to parse geocoder results."
@@ -72,8 +71,12 @@ module Geocoder
72
71
  ##
73
72
  # URL to use for querying the geocoding engine.
74
73
  #
74
+ # Subclasses should not modify this method. Instead they should define
75
+ # base_query_url and url_query_string. If absolutely necessary to
76
+ # subclss this method, they must also subclass #cache_key.
77
+ #
75
78
  def query_url(query)
76
- fail
79
+ base_query_url(query) + url_query_string(query)
77
80
  end
78
81
 
79
82
  ##
@@ -97,6 +100,14 @@ module Geocoder
97
100
 
98
101
  private # -------------------------------------------------------------
99
102
 
103
+ ##
104
+ # String which, when concatenated with url_query_string(query)
105
+ # produces the full query URL. Should include the "?" a the end.
106
+ #
107
+ def base_query_url(query)
108
+ fail
109
+ end
110
+
100
111
  ##
101
112
  # An object with configuration data for this particular lookup.
102
113
  #
@@ -147,7 +158,14 @@ module Geocoder
147
158
  # something else (like the URL before OAuth encoding).
148
159
  #
149
160
  def cache_key(query)
150
- query_url(query)
161
+ base_query_url(query) + hash_to_query(cache_key_params(query))
162
+ end
163
+
164
+ def cache_key_params(query)
165
+ # omit api_key and token because they may vary among requests
166
+ query_url_params(query).reject do |key,value|
167
+ key.to_s.match(/(key|token)/)
168
+ end
151
169
  end
152
170
 
153
171
  ##
@@ -179,6 +197,8 @@ module Geocoder
179
197
  raise_error(err) or Geocoder.log(:warn, "Geocoding API connection cannot be established.")
180
198
  rescue Errno::ECONNREFUSED => err
181
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")
182
202
  rescue Timeout::Error => err
183
203
  raise_error(err) or Geocoder.log(:warn, "Geocoding API not responding fast enough " +
184
204
  "(use Geocoder.configure(:timeout => ...) to set limit).")
@@ -191,7 +211,10 @@ module Geocoder
191
211
  JSON.parse(data)
192
212
  end
193
213
  rescue
194
- 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
195
218
  end
196
219
 
197
220
  ##
@@ -16,24 +16,20 @@ module Geocoder::Lookup
16
16
  ["key"]
17
17
  end
18
18
 
19
- def query_url(query)
20
- base_url(query) + url_query_string(query)
21
- end
22
-
23
19
  private # ---------------------------------------------------------------
24
20
 
25
- def base_url(query)
26
- url = "#{protocol}://dev.virtualearth.net/REST/v1/Locations"
27
-
28
- if !query.reverse_geocode?
21
+ def base_query_url(query)
22
+ text = CGI.escape(query.sanitized_text.strip)
23
+ url = "#{protocol}://dev.virtualearth.net/REST/v1/Locations/"
24
+ if query.reverse_geocode?
25
+ url + "#{text}?"
26
+ else
29
27
  if r = query.options[:region]
30
- url << "/#{r}"
28
+ url << "#{r}/"
31
29
  end
32
30
  # use the more forgiving 'unstructured' query format to allow special
33
31
  # chars, newlines, brackets, typos.
34
- url + "?q=" + URI.escape(query.sanitized_text.strip) + "&"
35
- else
36
- url + "/#{URI.escape(query.sanitized_text.strip)}?"
32
+ url + "?q=#{text}&"
37
33
  end
38
34
  end
39
35
 
@@ -57,7 +53,8 @@ module Geocoder::Lookup
57
53
 
58
54
  def query_url_params(query)
59
55
  {
60
- key: configuration.api_key
56
+ key: configuration.api_key,
57
+ language: (query.language || configuration.language)
61
58
  }.merge(super)
62
59
  end
63
60
 
@@ -16,15 +16,18 @@ module Geocoder::Lookup
16
16
  ['api_key']
17
17
  end
18
18
 
19
- def query_url(query)
20
- query_params = if query.options[:params]
21
- "?#{url_query_string(query)}"
22
- end
19
+ private # ----------------------------------------------------------------
23
20
 
24
- "#{protocol}://api.db-ip.com/v2/#{configuration.api_key}/#{query.sanitized_text}#{query_params}"
21
+ def base_query_url(query)
22
+ "#{protocol}://api.db-ip.com/v2/#{configuration.api_key}/#{query.sanitized_text}?"
25
23
  end
26
24
 
27
- private
25
+ ##
26
+ # Same as query_url but without the api key.
27
+ #
28
+ def cache_key(query)
29
+ "#{protocol}://api.db-ip.com/v2/#{query.sanitized_text}?" + hash_to_query(cache_key_params(query))
30
+ end
28
31
 
29
32
  def results(query)
30
33
  return [] unless (doc = fetch_data(query))