geocoder 1.1.5 → 1.1.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of geocoder might be problematic. Click here for more details.

Files changed (84) hide show
  1. data/.travis.yml +4 -0
  2. data/{CHANGELOG.rdoc → CHANGELOG.md} +90 -37
  3. data/README.md +96 -45
  4. data/examples/autoexpire_cache.rb +1 -3
  5. data/lib/generators/geocoder/config/templates/initializer.rb +18 -22
  6. data/lib/geocoder.rb +2 -4
  7. data/lib/geocoder/cache.rb +2 -1
  8. data/lib/geocoder/calculations.rb +12 -12
  9. data/lib/geocoder/cli.rb +10 -11
  10. data/lib/geocoder/configuration.rb +67 -43
  11. data/lib/geocoder/configuration_hash.rb +11 -0
  12. data/lib/geocoder/exceptions.rb +3 -0
  13. data/lib/geocoder/lookup.rb +1 -1
  14. data/lib/geocoder/lookups/base.rb +59 -16
  15. data/lib/geocoder/lookups/bing.rb +21 -11
  16. data/lib/geocoder/lookups/freegeoip.rb +8 -4
  17. data/lib/geocoder/lookups/geocoder_ca.rb +11 -7
  18. data/lib/geocoder/lookups/google.rb +18 -9
  19. data/lib/geocoder/lookups/google_premier.rb +16 -8
  20. data/lib/geocoder/lookups/mapquest.rb +12 -5
  21. data/lib/geocoder/lookups/maxmind.rb +72 -0
  22. data/lib/geocoder/lookups/nominatim.rb +13 -8
  23. data/lib/geocoder/lookups/test.rb +4 -0
  24. data/lib/geocoder/lookups/yahoo.rb +38 -11
  25. data/lib/geocoder/lookups/yandex.rb +17 -9
  26. data/lib/geocoder/query.rb +17 -8
  27. data/lib/geocoder/request.rb +7 -1
  28. data/lib/geocoder/results/google.rb +6 -0
  29. data/lib/geocoder/results/maxmind.rb +136 -0
  30. data/lib/geocoder/results/nominatim.rb +0 -10
  31. data/lib/geocoder/sql.rb +6 -4
  32. data/lib/geocoder/stores/active_record.rb +5 -5
  33. data/lib/geocoder/stores/base.rb +3 -2
  34. data/lib/geocoder/version.rb +1 -1
  35. data/lib/hash_recursive_merge.rb +74 -0
  36. data/lib/oauth_util.rb +8 -2
  37. data/test/cache_test.rb +2 -2
  38. data/test/calculations_test.rb +4 -4
  39. data/test/configuration_test.rb +26 -51
  40. data/test/error_handling_test.rb +4 -4
  41. data/test/fixtures/bing_invalid_key +1 -0
  42. data/test/fixtures/{bing_madison_square_garden.json → bing_madison_square_garden} +0 -0
  43. data/test/fixtures/{bing_no_results.json → bing_no_results} +0 -0
  44. data/test/fixtures/{bing_reverse.json → bing_reverse} +0 -0
  45. data/test/fixtures/{freegeoip_74_200_247_59.json → freegeoip_74_200_247_59} +0 -0
  46. data/test/fixtures/{freegeoip_no_results.json → freegeoip_no_results} +0 -0
  47. data/test/fixtures/{geocoder_ca_madison_square_garden.json → geocoder_ca_madison_square_garden} +0 -0
  48. data/test/fixtures/{geocoder_ca_no_results.json → geocoder_ca_no_results} +0 -0
  49. data/test/fixtures/{geocoder_ca_reverse.json → geocoder_ca_reverse} +0 -0
  50. data/test/fixtures/{google_garbage.json → google_garbage} +0 -0
  51. data/test/fixtures/{google_madison_square_garden.json → google_madison_square_garden} +0 -0
  52. data/test/fixtures/{google_no_city_data.json → google_no_city_data} +0 -0
  53. data/test/fixtures/{google_no_locality.json → google_no_locality} +0 -0
  54. data/test/fixtures/{google_no_results.json → google_no_results} +0 -0
  55. data/test/fixtures/{mapquest_madison_square_garden.json → mapquest_madison_square_garden} +0 -0
  56. data/test/fixtures/{mapquest_no_results.json → mapquest_no_results} +0 -0
  57. data/test/fixtures/maxmind_24_24_24_21 +1 -0
  58. data/test/fixtures/maxmind_24_24_24_22 +1 -0
  59. data/test/fixtures/maxmind_24_24_24_23 +1 -0
  60. data/test/fixtures/maxmind_24_24_24_24 +1 -0
  61. data/test/fixtures/maxmind_74_200_247_59 +1 -0
  62. data/test/fixtures/maxmind_invalid_key +1 -0
  63. data/test/fixtures/maxmind_no_results +1 -0
  64. data/test/fixtures/{nominatim_madison_square_garden.json → nominatim_madison_square_garden} +0 -0
  65. data/test/fixtures/{nominatim_no_results.json → nominatim_no_results} +0 -0
  66. data/test/fixtures/{yahoo_error.json → yahoo_error} +0 -0
  67. data/test/fixtures/yahoo_invalid_key +2 -0
  68. data/test/fixtures/{yahoo_madison_square_garden.json → yahoo_madison_square_garden} +0 -0
  69. data/test/fixtures/{yahoo_no_results.json → yahoo_no_results} +0 -0
  70. data/test/fixtures/yahoo_over_limit +2 -0
  71. data/test/fixtures/{yandex_invalid_key.json → yandex_invalid_key} +0 -0
  72. data/test/fixtures/{yandex_kremlin.json → yandex_kremlin} +0 -0
  73. data/test/fixtures/{yandex_no_results.json → yandex_no_results} +0 -0
  74. data/test/https_test.rb +3 -3
  75. data/test/integration/smoke_test.rb +2 -2
  76. data/test/lookup_test.rb +82 -5
  77. data/test/oauth_util_test.rb +30 -0
  78. data/test/proxy_test.rb +2 -2
  79. data/test/query_test.rb +3 -0
  80. data/test/result_test.rb +2 -2
  81. data/test/services_test.rb +121 -44
  82. data/test/test_helper.rb +44 -109
  83. data/test/test_mode_test.rb +3 -3
  84. metadata +42 -33
@@ -4,34 +4,44 @@ require "geocoder/results/bing"
4
4
  module Geocoder::Lookup
5
5
  class Bing < Base
6
6
 
7
+ def name
8
+ "Bing"
9
+ end
10
+
7
11
  def map_link_url(coordinates)
8
12
  "http://www.bing.com/maps/default.aspx?cp=#{coordinates.join('~')}"
9
13
  end
10
14
 
15
+ def required_api_key_parts
16
+ ["key"]
17
+ end
18
+
19
+ def query_url(query)
20
+ "#{protocol}://dev.virtualearth.net/REST/v1/Locations" +
21
+ (query.reverse_geocode? ? "/#{query.sanitized_text}?" : "?") +
22
+ url_query_string(query)
23
+ end
24
+
11
25
  private # ---------------------------------------------------------------
12
26
 
13
27
  def results(query)
14
28
  return [] unless doc = fetch_data(query)
15
29
 
16
- if doc['statusDescription'] == "OK"
30
+ if doc['statusCode'] == 200
17
31
  return doc['resourceSets'].first['estimatedTotal'] > 0 ? doc['resourceSets'].first['resources'] : []
32
+ elsif doc['statusCode'] == 401 and doc["authenticationResultCode"] == "InvalidCredentials"
33
+ raise_error(Geocoder::InvalidApiKey) || warn("Invalid Bing API key.")
18
34
  else
19
35
  warn "Bing Geocoding API error: #{doc['statusCode']} (#{doc['statusDescription']})."
20
- return []
21
36
  end
37
+ return []
22
38
  end
23
39
 
24
40
  def query_url_params(query)
25
- super.merge(
26
- :key => Geocoder::Configuration.api_key,
41
+ {
42
+ :key => configuration.api_key,
27
43
  :query => query.reverse_geocode? ? nil : query.sanitized_text
28
- )
29
- end
30
-
31
- def query_url(query)
32
- "#{protocol}://dev.virtualearth.net/REST/v1/Locations" +
33
- (query.reverse_geocode? ? "/#{query.sanitized_text}?" : "?") +
34
- url_query_string(query)
44
+ }.merge(super)
35
45
  end
36
46
  end
37
47
  end
@@ -4,6 +4,14 @@ require 'geocoder/results/freegeoip'
4
4
  module Geocoder::Lookup
5
5
  class Freegeoip < Base
6
6
 
7
+ def name
8
+ "FreeGeoIP"
9
+ end
10
+
11
+ def query_url(query)
12
+ "#{protocol}://freegeoip.net/json/#{query.sanitized_text}"
13
+ end
14
+
7
15
  private # ---------------------------------------------------------------
8
16
 
9
17
  def parse_raw_data(raw_data)
@@ -35,9 +43,5 @@ module Geocoder::Lookup
35
43
  "country_code" => "RD"
36
44
  }
37
45
  end
38
-
39
- def query_url(query)
40
- "#{protocol}://freegeoip.net/json/#{query.sanitized_text}"
41
- end
42
46
  end
43
47
  end
@@ -4,6 +4,14 @@ require "geocoder/results/geocoder_ca"
4
4
  module Geocoder::Lookup
5
5
  class GeocoderCa < Base
6
6
 
7
+ def name
8
+ "Geocoder.ca"
9
+ end
10
+
11
+ def query_url(query)
12
+ "#{protocol}://geocoder.ca/?" + url_query_string(query)
13
+ end
14
+
7
15
  private # ---------------------------------------------------------------
8
16
 
9
17
  def results(query)
@@ -19,12 +27,12 @@ module Geocoder::Lookup
19
27
  end
20
28
 
21
29
  def query_url_params(query)
22
- params = super.merge(
30
+ params = {
23
31
  :geoit => "xml",
24
32
  :jsonp => 1,
25
33
  :callback => "test",
26
- :auth => Geocoder::Configuration.api_key
27
- )
34
+ :auth => configuration.api_key
35
+ }.merge(super)
28
36
  if query.reverse_geocode?
29
37
  lat,lon = query.coordinates
30
38
  params[:latt] = lat
@@ -38,10 +46,6 @@ module Geocoder::Lookup
38
46
  params
39
47
  end
40
48
 
41
- def query_url(query)
42
- "#{protocol}://geocoder.ca/?" + url_query_string(query)
43
- end
44
-
45
49
  def parse_raw_data(raw_data)
46
50
  super raw_data[/^test\((.*)\)\;\s*$/, 1]
47
51
  end
@@ -4,10 +4,18 @@ require "geocoder/results/google"
4
4
  module Geocoder::Lookup
5
5
  class Google < Base
6
6
 
7
+ def name
8
+ "Google"
9
+ end
10
+
7
11
  def map_link_url(coordinates)
8
12
  "http://maps.google.com/maps?q=#{coordinates.join(',')}"
9
13
  end
10
14
 
15
+ def query_url(query)
16
+ "#{protocol}://maps.googleapis.com/maps/api/geocode/json?" + url_query_string(query)
17
+ end
18
+
11
19
  private # ---------------------------------------------------------------
12
20
 
13
21
  def results(query)
@@ -31,23 +39,24 @@ module Geocoder::Lookup
31
39
  params = {
32
40
  (query.reverse_geocode? ? :latlng : :address) => query.sanitized_text,
33
41
  :sensor => "false",
34
- :language => Geocoder::Configuration.language
42
+ :language => configuration.language
35
43
  }
36
44
  unless (bounds = query.options[:bounds]).nil?
37
45
  params[:bounds] = bounds.map{ |point| "%f,%f" % point }.join('|')
38
46
  end
47
+ unless (region = query.options[:region]).nil?
48
+ params[:region] = region
49
+ end
50
+ unless (components = query.options[:components]).nil?
51
+ params[:components] = components.is_a?(Array) ? components.join("|") : components
52
+ end
39
53
  params
40
54
  end
41
55
 
42
56
  def query_url_params(query)
43
- super.merge(query_url_google_params(query)).merge(
44
- :key => Geocoder::Configuration.api_key
45
- )
46
- end
47
-
48
- def query_url(query)
49
- "#{protocol}://maps.googleapis.com/maps/api/geocode/json?" + url_query_string(query)
57
+ query_url_google_params(query).merge(
58
+ :key => configuration.api_key
59
+ ).merge(super)
50
60
  end
51
61
  end
52
62
  end
53
-
@@ -6,14 +6,12 @@ require 'geocoder/results/google_premier'
6
6
  module Geocoder::Lookup
7
7
  class GooglePremier < Google
8
8
 
9
- private # ---------------------------------------------------------------
9
+ def name
10
+ "Google Premier"
11
+ end
10
12
 
11
- def query_url_params(query)
12
- super.merge(query_url_google_params(query)).merge(
13
- :key => nil, # don't use param inherited from Google lookup
14
- :client => Geocoder::Configuration.api_key[1],
15
- :channel => Geocoder::Configuration.api_key[2]
16
- )
13
+ def required_api_key_parts
14
+ ["private key", "client", "channel"]
17
15
  end
18
16
 
19
17
  def query_url(query)
@@ -21,8 +19,18 @@ module Geocoder::Lookup
21
19
  "#{protocol}://maps.googleapis.com#{path}&signature=#{sign(path)}"
22
20
  end
23
21
 
22
+ private # ---------------------------------------------------------------
23
+
24
+ def query_url_params(query)
25
+ query_url_google_params(query).merge(super).merge(
26
+ :key => nil, # don't use param inherited from Google lookup
27
+ :client => configuration.api_key[1],
28
+ :channel => configuration.api_key[2]
29
+ )
30
+ end
31
+
24
32
  def sign(string)
25
- raw_private_key = url_safe_base64_decode(Geocoder::Configuration.api_key[0])
33
+ raw_private_key = url_safe_base64_decode(configuration.api_key[0])
26
34
  digest = OpenSSL::Digest::Digest.new('sha1')
27
35
  raw_signature = OpenSSL::HMAC.digest(digest, raw_private_key, string)
28
36
  url_safe_base64_encode(raw_signature)
@@ -5,26 +5,33 @@ require "geocoder/results/mapquest"
5
5
  module Geocoder::Lookup
6
6
  class Mapquest < Base
7
7
 
8
- private # ---------------------------------------------------------------
8
+ def name
9
+ "Mapquest"
10
+ end
11
+
12
+ def required_api_key_parts
13
+ ["key"]
14
+ end
9
15
 
10
16
  def query_url(query)
11
- key = Geocoder::Configuration.api_key
17
+ key = configuration.api_key
12
18
  domain = key ? "www" : "open"
13
19
  url = "#{protocol}://#{domain}.mapquestapi.com/geocoding/v1/#{search_type(query)}?"
14
20
  url + url_query_string(query)
15
21
  end
16
22
 
23
+ private # ---------------------------------------------------------------
24
+
17
25
  def search_type(query)
18
26
  query.reverse_geocode? ? "reverse" : "address"
19
27
  end
20
28
 
21
29
  def query_url_params(query)
22
- key = Geocoder::Configuration.api_key
23
30
  params = { :location => query.sanitized_text }
24
- if key
31
+ if key = configuration.api_key
25
32
  params[:key] = CGI.unescape(key)
26
33
  end
27
- super.merge(params)
34
+ params.merge(super)
28
35
  end
29
36
 
30
37
  def results(query)
@@ -0,0 +1,72 @@
1
+ require 'geocoder/lookups/base'
2
+ require 'geocoder/results/maxmind'
3
+ require 'csv'
4
+
5
+ module Geocoder::Lookup
6
+ class Maxmind < Base
7
+
8
+ def name
9
+ "MaxMind"
10
+ end
11
+
12
+ def query_url(query)
13
+ "#{protocol}://geoip.maxmind.com/#{service_code}?" + url_query_string(query)
14
+ end
15
+
16
+ private # ---------------------------------------------------------------
17
+
18
+ def service_code
19
+ if s = configuration[:service] and services.keys.include?(s)
20
+ services[s]
21
+ else
22
+ raise(
23
+ Geocoder::ConfigurationError,
24
+ "When using MaxMind you MUST specify a service name: " +
25
+ "Geocoder.configure(:maxmind => {:service => ...}), " +
26
+ "where '...' is one of: #{services.keys.inspect}"
27
+ )
28
+ end
29
+ end
30
+
31
+ ##
32
+ # Service names mapped to code used in URL.
33
+ #
34
+ def services
35
+ {
36
+ :country => "a",
37
+ :city => "b",
38
+ :city_isp_org => "f",
39
+ :omni => "e"
40
+ }
41
+ end
42
+
43
+ def results(query)
44
+ # don't look up a loopback address, just return the stored result
45
+ return [reserved_result] if query.loopback_ip_address?
46
+ doc = fetch_data(query)
47
+ if doc and doc.is_a?(Array)
48
+ if doc.last.nil?
49
+ return [doc]
50
+ elsif doc.last == "INVALID_LICENSE_KEY"
51
+ raise_error(Geocoder::InvalidApiKey) || warn("Invalid MaxMind API key.")
52
+ end
53
+ end
54
+ return []
55
+ end
56
+
57
+ def parse_raw_data(raw_data)
58
+ CSV.parse_line raw_data
59
+ end
60
+
61
+ def reserved_result
62
+ ",,,,0,0,0,0,,,"
63
+ end
64
+
65
+ def query_url_params(query)
66
+ {
67
+ :l => configuration.api_key,
68
+ :i => query.sanitized_text
69
+ }.merge(super)
70
+ end
71
+ end
72
+ end
@@ -4,10 +4,20 @@ require "geocoder/results/nominatim"
4
4
  module Geocoder::Lookup
5
5
  class Nominatim < Base
6
6
 
7
+ def name
8
+ "Nominatim"
9
+ end
10
+
7
11
  def map_link_url(coordinates)
8
12
  "http://www.openstreetmap.org/?lat=#{coordinates[0]}&lon=#{coordinates[1]}&zoom=15&layers=M"
9
13
  end
10
14
 
15
+ def query_url(query)
16
+ method = query.reverse_geocode? ? "reverse" : "search"
17
+ host = configuration[:host] || "nominatim.openstreetmap.org"
18
+ "#{protocol}://#{host}/#{method}?" + url_query_string(query)
19
+ end
20
+
11
21
  private # ---------------------------------------------------------------
12
22
 
13
23
  def results(query)
@@ -16,12 +26,12 @@ module Geocoder::Lookup
16
26
  end
17
27
 
18
28
  def query_url_params(query)
19
- params = super.merge(
29
+ params = {
20
30
  :format => "json",
21
31
  :polygon => "1",
22
32
  :addressdetails => "1",
23
- :"accept-language" => Geocoder::Configuration.language
24
- )
33
+ :"accept-language" => configuration.language
34
+ }.merge(super)
25
35
  if query.reverse_geocode?
26
36
  lat,lon = query.coordinates
27
37
  params[:lat] = lat
@@ -31,10 +41,5 @@ module Geocoder::Lookup
31
41
  end
32
42
  params
33
43
  end
34
-
35
- def query_url(query)
36
- method = query.reverse_geocode? ? "reverse" : "search"
37
- "#{protocol}://nominatim.openstreetmap.org/#{method}?" + url_query_string(query)
38
- end
39
44
  end
40
45
  end
@@ -5,6 +5,10 @@ module Geocoder
5
5
  module Lookup
6
6
  class Test < Base
7
7
 
8
+ def name
9
+ "Test"
10
+ end
11
+
8
12
  def self.add_stub(query_text, results)
9
13
  stubs[query_text] = results
10
14
  end
@@ -5,10 +5,26 @@ require 'oauth_util'
5
5
  module Geocoder::Lookup
6
6
  class Yahoo < Base
7
7
 
8
+ def name
9
+ "Yahoo BOSS"
10
+ end
11
+
8
12
  def map_link_url(coordinates)
9
13
  "http://maps.yahoo.com/#lat=#{coordinates[0]}&lon=#{coordinates[1]}"
10
14
  end
11
15
 
16
+ def required_api_key_parts
17
+ ["consumer key", "consumer secret"]
18
+ end
19
+
20
+ def query_url(query)
21
+ parsed_url = URI.parse(raw_url(query))
22
+ o = OauthUtil.new
23
+ o.consumer_key = configuration.api_key[0]
24
+ o.consumer_secret = configuration.api_key[1]
25
+ base_url + o.sign(parsed_url).query_string
26
+ end
27
+
12
28
  private # ---------------------------------------------------------------
13
29
 
14
30
  def results(query)
@@ -26,12 +42,31 @@ module Geocoder::Lookup
26
42
  end
27
43
  end
28
44
 
45
+ ##
46
+ # Yahoo returns errors as XML even when JSON format is specified.
47
+ # Handle that here, without parsing the XML
48
+ # (which would add unnecessary complexity).
49
+ #
50
+ def parse_raw_data(raw_data)
51
+ if raw_data.match /^<\?xml/
52
+ if raw_data.include?("Rate Limit Exceeded")
53
+ raise_error(Geocoder::OverQueryLimitError) || warn("Over API query limit.")
54
+ elsif raw_data.include?("Please provide valid credentials")
55
+ raise_error(Geocoder::InvalidApiKey) || warn("Invalid API key.")
56
+ end
57
+ else
58
+ super(raw_data)
59
+ end
60
+ end
61
+
29
62
  def query_url_params(query)
30
- super.merge(
63
+ {
31
64
  :location => query.sanitized_text,
32
65
  :flags => "JXTSR",
33
- :gflags => "AC#{'R' if query.reverse_geocode?}"
34
- )
66
+ :gflags => "AC#{'R' if query.reverse_geocode?}",
67
+ :locale => "#{configuration.language}_US",
68
+ :appid => configuration.api_key
69
+ }.merge(super)
35
70
  end
36
71
 
37
72
  def cache_key(query)
@@ -45,13 +80,5 @@ module Geocoder::Lookup
45
80
  def raw_url(query)
46
81
  base_url + url_query_string(query)
47
82
  end
48
-
49
- def query_url(query)
50
- parsed_url = URI.parse(raw_url(query))
51
- o = OauthUtil.new
52
- o.consumer_key = Geocoder::Configuration.api_key[0]
53
- o.consumer_secret = Geocoder::Configuration.api_key[1]
54
- base_url + o.sign(parsed_url).query_string
55
- end
56
83
  end
57
84
  end