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,16 +4,28 @@ require "geocoder/results/yandex"
4
4
  module Geocoder::Lookup
5
5
  class Yandex < Base
6
6
 
7
+ def name
8
+ "Yandex"
9
+ end
10
+
7
11
  def map_link_url(coordinates)
8
12
  "http://maps.yandex.ru/?ll=#{coordinates.reverse.join(',')}"
9
13
  end
10
14
 
15
+ def query_url(query)
16
+ "#{protocol}://geocode-maps.yandex.ru/1.x/?" + url_query_string(query)
17
+ end
18
+
11
19
  private # ---------------------------------------------------------------
12
20
 
13
21
  def results(query)
14
22
  return [] unless doc = fetch_data(query)
15
23
  if err = doc['error']
16
- warn "Yandex Geocoding API error: #{err['status']} (#{err['message']})."
24
+ if err["status"] == 401 and err["message"] == "invalid key"
25
+ raise_error(Geocoder::InvalidApiKey) || warn("Invalid API key.")
26
+ else
27
+ warn "Yandex Geocoding API error: #{err['status']} (#{err['message']})."
28
+ end
17
29
  return []
18
30
  end
19
31
  if doc = doc['response']['GeoObjectCollection']
@@ -31,16 +43,12 @@ module Geocoder::Lookup
31
43
  else
32
44
  q = query.sanitized_text
33
45
  end
34
- super.merge(
46
+ {
35
47
  :geocode => q,
36
48
  :format => "json",
37
- :plng => "#{Geocoder::Configuration.language}", # supports ru, uk, be
38
- :key => Geocoder::Configuration.api_key
39
- )
40
- end
41
-
42
- def query_url(query)
43
- "#{protocol}://geocode-maps.yandex.ru/1.x/?" + url_query_string(query)
49
+ :plng => "#{configuration.language}", # supports ru, uk, be
50
+ :key => configuration.api_key
51
+ }.merge(super)
44
52
  end
45
53
  end
46
54
  end
@@ -36,17 +36,20 @@ module Geocoder
36
36
  Lookup.get(name)
37
37
  end
38
38
 
39
+ def url
40
+ lookup.query_url(self)
41
+ end
42
+
39
43
  ##
40
- # Is the Query text blank? (ie, should we not bother searching?)
44
+ # Is the Query blank? (ie, should we not bother searching?)
45
+ # A query is considered blank if its text is nil or empty string AND
46
+ # no URL parameters are specified.
41
47
  #
42
48
  def blank?
43
- # check whether both coordinates given
44
- if text.is_a?(Array)
45
- text.compact.size < 2
46
- # else assume a string
47
- else
48
- !!text.to_s.match(/^\s*$/)
49
- end
49
+ !params_given? and (
50
+ (text.is_a?(Array) and text.compact.size < 2) or
51
+ text.to_s.match(/^\s*$/)
52
+ )
50
53
  end
51
54
 
52
55
  ##
@@ -90,5 +93,11 @@ module Geocoder
90
93
  def reverse_geocode?
91
94
  coordinates?
92
95
  end
96
+
97
+ private # ----------------------------------------------------------------
98
+
99
+ def params_given?
100
+ !!(options[:params].is_a?(Hash) and options[:params].keys.size > 0)
101
+ end
93
102
  end
94
103
  end
@@ -5,7 +5,13 @@ module Geocoder
5
5
 
6
6
  def location
7
7
  unless defined?(@location)
8
- @location = Geocoder.search(ip).first
8
+ if env.has_key?('HTTP_X_REAL_IP')
9
+ @location = Geocoder.search(env['HTTP_X_REAL_IP']).first
10
+ elsif env.has_key?('HTTP_X_FORWARDED_FOR')
11
+ @location = Geocoder.search(env['HTTP_X_FORWARDED_FOR']).first
12
+ else
13
+ @location = Geocoder.search(ip).first
14
+ end
9
15
  end
10
16
  @location
11
17
  end
@@ -53,6 +53,12 @@ module Geocoder::Result
53
53
  end
54
54
  end
55
55
 
56
+ def route
57
+ if route = address_components_of_type(:route).first
58
+ route['long_name']
59
+ end
60
+ end
61
+
56
62
  def types
57
63
  @data['types']
58
64
  end
@@ -0,0 +1,136 @@
1
+ require 'geocoder/results/base'
2
+
3
+ module Geocoder::Result
4
+ class Maxmind < Base
5
+
6
+ ##
7
+ # Hash mapping service names to names of returned fields.
8
+ #
9
+ def self.field_names
10
+ {
11
+ :country => [
12
+ :country_code,
13
+ :error
14
+ ],
15
+
16
+ :city => [
17
+ :country_code,
18
+ :region_code,
19
+ :city_name,
20
+ :latitude,
21
+ :longitude,
22
+ :error
23
+ ],
24
+
25
+ :city_isp_org => [
26
+ :country_code,
27
+ :region_code,
28
+ :city_name,
29
+ :postal_code,
30
+ :latitude,
31
+ :longitude,
32
+ :metro_code,
33
+ :area_code,
34
+ :isp_name,
35
+ :organization_name,
36
+ :error
37
+ ],
38
+
39
+ :omni => [
40
+ :country_code,
41
+ :country_name,
42
+ :region_code,
43
+ :region_name,
44
+ :city_name,
45
+ :latitude,
46
+ :longitude,
47
+ :metro_code,
48
+ :area_code,
49
+ :time_zone,
50
+ :continent_code,
51
+ :postal_code,
52
+ :isp_name,
53
+ :organization_name,
54
+ :domain,
55
+ :as_number,
56
+ :netspeed,
57
+ :user_type,
58
+ :accuracy_radius,
59
+ :country_confidence_factor,
60
+ :city_confidence_factor,
61
+ :region_confidence_factor,
62
+ :postal_confidence_factor,
63
+ :error
64
+ ]
65
+ }
66
+ end
67
+
68
+ ##
69
+ # Name of the MaxMind service being used.
70
+ # Inferred from format of @data.
71
+ #
72
+ def service_name
73
+ self.class.field_names.to_a.each do |n,f|
74
+ return n if f.size == @data.size
75
+ end
76
+ nil
77
+ end
78
+
79
+ def field_names
80
+ self.class.field_names[service_name]
81
+ end
82
+
83
+ def data_hash
84
+ @data_hash ||= Hash[*field_names.zip(@data).flatten]
85
+ end
86
+
87
+ def coordinates
88
+ [data_hash[:latitude].to_f, data_hash[:longitude].to_f]
89
+ end
90
+
91
+ def address(format = :full)
92
+ s = state_code.to_s == "" ? "" : ", #{state_code}"
93
+ "#{city}#{s} #{postal_code}, #{country_code}".sub(/^[ ,]*/, "")
94
+ end
95
+
96
+ def city
97
+ data_hash[:city_name]
98
+ end
99
+
100
+ def state # not given by MaxMind
101
+ data_hash[:region_name] || data_hash[:region_code]
102
+ end
103
+
104
+ def state_code
105
+ data_hash[:region_code]
106
+ end
107
+
108
+ def country #not given by MaxMind
109
+ data_hash[:country_name] || data_hash[:country_code]
110
+ end
111
+
112
+ def country_code
113
+ data_hash[:country_code]
114
+ end
115
+
116
+ def postal_code
117
+ data_hash[:postal_code]
118
+ end
119
+
120
+ def method_missing(method, *args, &block)
121
+ if field_names.include?(method)
122
+ data_hash[method]
123
+ else
124
+ super
125
+ end
126
+ end
127
+
128
+ def respond_to?(method)
129
+ if field_names.include?(method)
130
+ true
131
+ else
132
+ super
133
+ end
134
+ end
135
+ end
136
+ end
@@ -83,16 +83,6 @@ module Geocoder::Result
83
83
  polygonpoints display_name class type stadium]
84
84
  end
85
85
 
86
- def class
87
- warn "DEPRECATION WARNING: The 'class' method of Geocoder::Result::Nominatim objects is deprecated and will be removed in Geocoder version 1.2.0. Please use 'place_class' instead."
88
- @data['class']
89
- end
90
-
91
- def type
92
- warn "DEPRECATION WARNING: The 'type' method of Geocoder::Result::Nominatim objects is deprecated and will be removed in Geocoder version 1.2.0. Please use 'place_type' instead."
93
- @data['type']
94
- end
95
-
96
86
  response_attributes.each do |a|
97
87
  unless method_defined?(a)
98
88
  define_method a do
@@ -11,7 +11,8 @@ module Geocoder
11
11
  # http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
12
12
  #
13
13
  def full_distance(latitude, longitude, lat_attr, lon_attr, options = {})
14
- earth = Geocoder::Calculations.earth_radius(options[:units] || :mi)
14
+ units = options[:units] || Geocoder.config.units
15
+ earth = Geocoder::Calculations.earth_radius(units)
15
16
 
16
17
  "#{earth} * 2 * ASIN(SQRT(" +
17
18
  "POWER(SIN((#{latitude.to_f} - #{lat_attr}) * PI() / 180 / 2), 2) + " +
@@ -31,8 +32,9 @@ module Geocoder
31
32
  # are not intended for use in production!
32
33
  #
33
34
  def approx_distance(latitude, longitude, lat_attr, lon_attr, options = {})
34
- dx = Geocoder::Calculations.longitude_degree_distance(30, options[:units] || :mi)
35
- dy = Geocoder::Calculations.latitude_degree_distance(options[:units] || :mi)
35
+ units = options[:units] || Geocoder.config.units
36
+ dx = Geocoder::Calculations.longitude_degree_distance(30, units)
37
+ dy = Geocoder::Calculations.latitude_degree_distance(units)
36
38
 
37
39
  # sin of 45 degrees = average x or y component of vector
38
40
  factor = Math.sin(Math::PI / 4)
@@ -61,7 +63,7 @@ module Geocoder
61
63
  # http://www.beginningspatial.com/calculating_bearing_one_point_another
62
64
  #
63
65
  def full_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
64
- case options[:bearing]
66
+ case options[:bearing] || Geocoder.config.distances
65
67
  when :linear
66
68
  "CAST(" +
67
69
  "DEGREES(ATAN2( " +
@@ -87,19 +87,19 @@ module Geocoder::Store
87
87
  # * +:units+ - <tt>:mi</tt> or <tt>:km</tt>; to be used.
88
88
  # for interpreting radius as well as the +distance+ attribute which
89
89
  # is added to each found nearby object.
90
- # See Geocoder::Configuration to know how configure default units.
90
+ # Use Geocoder.configure[:units] to configure default units.
91
91
  # * +:bearing+ - <tt>:linear</tt> or <tt>:spherical</tt>.
92
92
  # the method to be used for calculating the bearing (direction)
93
93
  # between the given point and each found nearby point;
94
- # set to false for no bearing calculation.
95
- # See Geocoder::Configuration to know how configure default method.
94
+ # set to false for no bearing calculation. Use
95
+ # Geocoder.configure[:distances] to configure default calculation method.
96
96
  # * +:select+ - string with the SELECT SQL fragment (e.g. “id, name”)
97
97
  # * +:order+ - column(s) for ORDER BY SQL clause; default is distance;
98
98
  # set to false or nil to omit the ORDER BY clause
99
99
  # * +:exclude+ - an object to exclude (used by the +nearbys+ method)
100
100
  #
101
101
  def near_scope_options(latitude, longitude, radius = 20, options = {})
102
- options[:units] ||= (geocoder_options[:units] || Geocoder::Configuration.units)
102
+ options[:units] ||= (geocoder_options[:units] || Geocoder.config.units)
103
103
  bearing = bearing_sql(latitude, longitude, options)
104
104
  distance = distance_sql(latitude, longitude, options)
105
105
 
@@ -143,7 +143,7 @@ module Geocoder::Store
143
143
  #
144
144
  def bearing_sql(latitude, longitude, options = {})
145
145
  if !options.include?(:bearing)
146
- options[:bearing] = Geocoder::Configuration.distances
146
+ options[:bearing] = Geocoder.config.distances
147
147
  end
148
148
  if options[:bearing]
149
149
  method_prefix = using_sqlite? ? "approx" : "full"
@@ -58,10 +58,11 @@ module Geocoder
58
58
  ##
59
59
  # Get nearby geocoded objects.
60
60
  # Takes the same options hash as the near class method (scope).
61
+ # Returns nil if the object is not geocoded.
61
62
  #
62
63
  def nearbys(radius = 20, options = {})
63
- return [] unless geocoded? # TODO: return ActiveRecord::Relation, not Array
64
- options.merge!(:exclude => self)
64
+ return nil unless geocoded?
65
+ options.merge!(:exclude => self) unless send(self.class.primary_key).nil?
65
66
  self.class.near(self, radius, options)
66
67
  end
67
68
 
@@ -1,3 +1,3 @@
1
1
  module Geocoder
2
- VERSION = "1.1.5"
2
+ VERSION = "1.1.6"
3
3
  end
@@ -0,0 +1,74 @@
1
+ #
2
+ # = Hash Recursive Merge
3
+ #
4
+ # Merges a Ruby Hash recursively, Also known as deep merge.
5
+ # Recursive version of Hash#merge and Hash#merge!.
6
+ #
7
+ # Category:: Ruby
8
+ # Package:: Hash
9
+ # Author:: Simone Carletti <weppos@weppos.net>
10
+ # Copyright:: 2007-2008 The Authors
11
+ # License:: MIT License
12
+ # Link:: http://www.simonecarletti.com/
13
+ # Source:: http://gist.github.com/gists/6391/
14
+ #
15
+ module HashRecursiveMerge
16
+
17
+ #
18
+ # Recursive version of Hash#merge!
19
+ #
20
+ # Adds the contents of +other_hash+ to +hsh+,
21
+ # merging entries in +hsh+ with duplicate keys with those from +other_hash+.
22
+ #
23
+ # Compared with Hash#merge!, this method supports nested hashes.
24
+ # When both +hsh+ and +other_hash+ contains an entry with the same key,
25
+ # it merges and returns the values from both arrays.
26
+ #
27
+ # h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
28
+ # h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
29
+ # h1.rmerge!(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
30
+ #
31
+ # Simply using Hash#merge! would return
32
+ #
33
+ # h1.merge!(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
34
+ #
35
+ def rmerge!(other_hash)
36
+ merge!(other_hash) do |key, oldval, newval|
37
+ oldval.class == self.class ? oldval.rmerge!(newval) : newval
38
+ end
39
+ end
40
+
41
+ #
42
+ # Recursive version of Hash#merge
43
+ #
44
+ # Compared with Hash#merge!, this method supports nested hashes.
45
+ # When both +hsh+ and +other_hash+ contains an entry with the same key,
46
+ # it merges and returns the values from both arrays.
47
+ #
48
+ # Compared with Hash#merge, this method provides a different approch
49
+ # for merging nasted hashes.
50
+ # If the value of a given key is an Hash and both +other_hash+ abd +hsh
51
+ # includes the same key, the value is merged instead replaced with
52
+ # +other_hash+ value.
53
+ #
54
+ # h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
55
+ # h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
56
+ # h1.rmerge(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
57
+ #
58
+ # Simply using Hash#merge would return
59
+ #
60
+ # h1.merge(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
61
+ #
62
+ def rmerge(other_hash)
63
+ r = {}
64
+ merge(other_hash) do |key, oldval, newval|
65
+ r[key] = oldval.class == self.class ? oldval.rmerge(newval) : newval
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+
72
+ class Hash
73
+ include HashRecursiveMerge
74
+ end
@@ -3,6 +3,13 @@
3
3
  # Source: http://gist.github.com/383159
4
4
  # License: http://gist.github.com/375593
5
5
  # Usage: see example.rb below
6
+ #
7
+ # NOTE: This file has been modified from the original Gist:
8
+ #
9
+ # 1. Fix to prevent param-array conversion, as mentioned in Gist comment.
10
+ # 2. Query string escaping has been changed. See:
11
+ # https://github.com/alexreisner/geocoder/pull/360
12
+ #
6
13
 
7
14
  require 'uri'
8
15
  require 'cgi'
@@ -56,7 +63,7 @@ class OauthUtil
56
63
  def query_string
57
64
  pairs = []
58
65
  @params.sort.each { | key, val |
59
- pairs.push( "#{ percent_encode( key ) }=#{ percent_encode( val.to_s ) }" )
66
+ pairs.push( "#{ CGI.escape(key.to_s).gsub(/%(5B|5D)/n) { [$1].pack('H*') } }=#{ CGI.escape(val.to_s) }" )
60
67
  }
61
68
  pairs.join '&'
62
69
  end
@@ -74,7 +81,6 @@ class OauthUtil
74
81
 
75
82
  # if url has query, merge key/values into params obj overwriting defaults
76
83
  if parsed_url.query
77
- #@params.merge! CGI.parse( parsed_url.query )
78
84
  CGI.parse( parsed_url.query ).each do |k,v|
79
85
  if v.is_a?(Array) && v.count == 1
80
86
  @params[k] = v.first