geocoder 1.1.3 → 1.1.6

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 (98) hide show
  1. data/.travis.yml +4 -0
  2. data/{CHANGELOG.rdoc → CHANGELOG.md} +109 -35
  3. data/README.md +645 -0
  4. data/examples/autoexpire_cache.rb +28 -0
  5. data/lib/generators/geocoder/config/templates/initializer.rb +18 -22
  6. data/lib/geocoder/cache.rb +2 -1
  7. data/lib/geocoder/calculations.rb +13 -13
  8. data/lib/geocoder/cli.rb +17 -19
  9. data/lib/geocoder/configuration.rb +68 -42
  10. data/lib/geocoder/configuration_hash.rb +11 -0
  11. data/lib/geocoder/exceptions.rb +3 -0
  12. data/lib/geocoder/lookup.rb +81 -0
  13. data/lib/geocoder/lookups/base.rb +106 -56
  14. data/lib/geocoder/lookups/bing.rb +25 -11
  15. data/lib/geocoder/lookups/freegeoip.rb +11 -7
  16. data/lib/geocoder/lookups/geocoder_ca.rb +17 -9
  17. data/lib/geocoder/lookups/google.rb +29 -8
  18. data/lib/geocoder/lookups/google_premier.rb +20 -11
  19. data/lib/geocoder/lookups/mapquest.rb +29 -19
  20. data/lib/geocoder/lookups/maxmind.rb +72 -0
  21. data/lib/geocoder/lookups/nominatim.rb +21 -13
  22. data/lib/geocoder/lookups/test.rb +12 -6
  23. data/lib/geocoder/lookups/yahoo.rb +63 -13
  24. data/lib/geocoder/lookups/yandex.rb +26 -11
  25. data/lib/geocoder/models/mongoid.rb +1 -2
  26. data/lib/geocoder/query.rb +103 -0
  27. data/lib/geocoder/request.rb +7 -1
  28. data/lib/geocoder/results/base.rb +8 -2
  29. data/lib/geocoder/results/google.rb +6 -0
  30. data/lib/geocoder/results/mapquest.rb +19 -34
  31. data/lib/geocoder/results/maxmind.rb +136 -0
  32. data/lib/geocoder/results/nominatim.rb +14 -3
  33. data/lib/geocoder/results/yahoo.rb +9 -2
  34. data/lib/geocoder/sql.rb +106 -0
  35. data/lib/geocoder/stores/active_record.rb +75 -142
  36. data/lib/geocoder/stores/base.rb +3 -2
  37. data/lib/geocoder/stores/mongo_base.rb +2 -2
  38. data/lib/geocoder/version.rb +1 -1
  39. data/lib/geocoder.rb +11 -89
  40. data/lib/hash_recursive_merge.rb +74 -0
  41. data/lib/oauth_util.rb +112 -0
  42. data/test/active_record_test.rb +15 -0
  43. data/test/cache_test.rb +19 -0
  44. data/test/calculations_test.rb +11 -4
  45. data/test/configuration_test.rb +26 -51
  46. data/test/error_handling_test.rb +15 -11
  47. data/test/fixtures/bing_invalid_key +1 -0
  48. data/test/fixtures/mapquest_madison_square_garden +52 -0
  49. data/test/fixtures/mapquest_no_results +7 -0
  50. data/test/fixtures/maxmind_24_24_24_21 +1 -0
  51. data/test/fixtures/maxmind_24_24_24_22 +1 -0
  52. data/test/fixtures/maxmind_24_24_24_23 +1 -0
  53. data/test/fixtures/maxmind_24_24_24_24 +1 -0
  54. data/test/fixtures/maxmind_74_200_247_59 +1 -0
  55. data/test/fixtures/maxmind_invalid_key +1 -0
  56. data/test/fixtures/maxmind_no_results +1 -0
  57. data/test/fixtures/yahoo_error +1 -0
  58. data/test/fixtures/yahoo_invalid_key +2 -0
  59. data/test/fixtures/yahoo_madison_square_garden +52 -0
  60. data/test/fixtures/yahoo_no_results +10 -0
  61. data/test/fixtures/yahoo_over_limit +2 -0
  62. data/test/https_test.rb +3 -3
  63. data/test/integration/smoke_test.rb +8 -6
  64. data/test/lookup_test.rb +92 -13
  65. data/test/near_test.rb +11 -0
  66. data/test/oauth_util_test.rb +30 -0
  67. data/test/proxy_test.rb +2 -2
  68. data/test/query_test.rb +42 -0
  69. data/test/result_test.rb +4 -3
  70. data/test/services_test.rb +164 -26
  71. data/test/test_helper.rb +93 -112
  72. data/test/test_mode_test.rb +3 -4
  73. metadata +52 -35
  74. data/README.rdoc +0 -552
  75. data/test/fixtures/mapquest_madison_square_garden.json +0 -27
  76. data/test/fixtures/mapquest_no_results.json +0 -1
  77. data/test/fixtures/yahoo_garbage.json +0 -50
  78. data/test/fixtures/yahoo_madison_square_garden.json +0 -46
  79. data/test/fixtures/yahoo_no_results.json +0 -10
  80. data/test/input_handling_test.rb +0 -43
  81. /data/test/fixtures/{bing_madison_square_garden.json → bing_madison_square_garden} +0 -0
  82. /data/test/fixtures/{bing_no_results.json → bing_no_results} +0 -0
  83. /data/test/fixtures/{bing_reverse.json → bing_reverse} +0 -0
  84. /data/test/fixtures/{freegeoip_74_200_247_59.json → freegeoip_74_200_247_59} +0 -0
  85. /data/test/fixtures/{freegeoip_no_results.json → freegeoip_no_results} +0 -0
  86. /data/test/fixtures/{geocoder_ca_madison_square_garden.json → geocoder_ca_madison_square_garden} +0 -0
  87. /data/test/fixtures/{geocoder_ca_no_results.json → geocoder_ca_no_results} +0 -0
  88. /data/test/fixtures/{geocoder_ca_reverse.json → geocoder_ca_reverse} +0 -0
  89. /data/test/fixtures/{google_garbage.json → google_garbage} +0 -0
  90. /data/test/fixtures/{google_madison_square_garden.json → google_madison_square_garden} +0 -0
  91. /data/test/fixtures/{google_no_city_data.json → google_no_city_data} +0 -0
  92. /data/test/fixtures/{google_no_locality.json → google_no_locality} +0 -0
  93. /data/test/fixtures/{google_no_results.json → google_no_results} +0 -0
  94. /data/test/fixtures/{nominatim_madison_square_garden.json → nominatim_madison_square_garden} +0 -0
  95. /data/test/fixtures/{nominatim_no_results.json → nominatim_no_results} +0 -0
  96. /data/test/fixtures/{yandex_invalid_key.json → yandex_invalid_key} +0 -0
  97. /data/test/fixtures/{yandex_kremlin.json → yandex_kremlin} +0 -0
  98. /data/test/fixtures/{yandex_no_results.json → yandex_no_results} +0 -0
@@ -5,12 +5,18 @@ module Geocoder
5
5
  module Lookup
6
6
  class Test < Base
7
7
 
8
- def self.add_stub(query, results)
9
- stubs[query] = results
8
+ def name
9
+ "Test"
10
10
  end
11
11
 
12
- def self.read_stub(query)
13
- stubs.fetch(query) { raise ArgumentError, "unknown stub request #{query}" }
12
+ def self.add_stub(query_text, results)
13
+ stubs[query_text] = results
14
+ end
15
+
16
+ def self.read_stub(query_text)
17
+ stubs.fetch(query_text) {
18
+ raise ArgumentError, "unknown stub request #{query_text}"
19
+ }
14
20
  end
15
21
 
16
22
  def self.stubs
@@ -23,8 +29,8 @@ module Geocoder
23
29
 
24
30
  private
25
31
 
26
- def results(query, reverse = false)
27
- Geocoder::Lookup::Test.read_stub(query)
32
+ def results(query)
33
+ Geocoder::Lookup::Test.read_stub(query.text)
28
34
  end
29
35
 
30
36
  end
@@ -1,34 +1,84 @@
1
1
  require 'geocoder/lookups/base'
2
2
  require "geocoder/results/yahoo"
3
+ require 'oauth_util'
3
4
 
4
5
  module Geocoder::Lookup
5
6
  class Yahoo < Base
6
7
 
8
+ def name
9
+ "Yahoo BOSS"
10
+ end
11
+
7
12
  def map_link_url(coordinates)
8
13
  "http://maps.yahoo.com/#lat=#{coordinates[0]}&lon=#{coordinates[1]}"
9
14
  end
10
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
+
11
28
  private # ---------------------------------------------------------------
12
29
 
13
- def results(query, reverse = false)
14
- return [] unless doc = fetch_data(query, reverse)
15
- if doc = doc['ResultSet'] and doc['Error'] == 0
16
- return doc['Found'] > 0 ? doc['Results'] : []
30
+ def results(query)
31
+ return [] unless doc = fetch_data(query)
32
+ doc = doc['bossresponse']
33
+ if doc['responsecode'].to_i == 200
34
+ if doc['placefinder']['count'].to_i > 0
35
+ return doc['placefinder']['results']
36
+ else
37
+ return []
38
+ end
17
39
  else
18
- warn "Yahoo Geocoding API error: #{doc['Error']} (#{doc['ErrorMessage']})."
40
+ warn "Yahoo Geocoding API error: #{doc['responsecode']} (#{doc['reason']})."
19
41
  return []
20
42
  end
21
43
  end
22
44
 
23
- def query_url(query, reverse = false)
24
- params = {
25
- :location => query,
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
+
62
+ def query_url_params(query)
63
+ {
64
+ :location => query.sanitized_text,
26
65
  :flags => "JXTSR",
27
- :gflags => "AC#{'R' if reverse}",
28
- :locale => "#{Geocoder::Configuration.language}_US",
29
- :appid => Geocoder::Configuration.api_key
30
- }
31
- "http://where.yahooapis.com/geocode?" + hash_to_query(params)
66
+ :gflags => "AC#{'R' if query.reverse_geocode?}",
67
+ :locale => "#{configuration.language}_US",
68
+ :appid => configuration.api_key
69
+ }.merge(super)
70
+ end
71
+
72
+ def cache_key(query)
73
+ raw_url(query)
74
+ end
75
+
76
+ def base_url
77
+ "#{protocol}://yboss.yahooapis.com/geo/placefinder?"
78
+ end
79
+
80
+ def raw_url(query)
81
+ base_url + url_query_string(query)
32
82
  end
33
83
  end
34
84
  end
@@ -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
- def results(query, reverse = false)
14
- return [] unless doc = fetch_data(query, reverse)
21
+ def results(query)
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']
@@ -25,15 +37,18 @@ module Geocoder::Lookup
25
37
  end
26
38
  end
27
39
 
28
- def query_url(query, reverse = false)
29
- query = query.split(",").reverse.join(",") if reverse
30
- params = {
31
- :geocode => query,
40
+ def query_url_params(query)
41
+ if query.reverse_geocode?
42
+ q = query.coordinates.reverse.join(",")
43
+ else
44
+ q = query.sanitized_text
45
+ end
46
+ {
47
+ :geocode => q,
32
48
  :format => "json",
33
- :plng => "#{Geocoder::Configuration.language}", # supports ru, uk, be
34
- :key => Geocoder::Configuration.api_key
35
- }
36
- "http://geocode-maps.yandex.ru/1.x/?" + hash_to_query(params)
49
+ :plng => "#{configuration.language}", # supports ru, uk, be
50
+ :key => configuration.api_key
51
+ }.merge(super)
37
52
  end
38
53
  end
39
54
  end
@@ -1,4 +1,3 @@
1
- require 'mongoid/version'
2
1
  require 'geocoder/models/base'
3
2
  require 'geocoder/models/mongo_base'
4
3
 
@@ -19,7 +18,7 @@ module Geocoder
19
18
  super(options)
20
19
  if options[:skip_index] == false
21
20
  # create 2d index
22
- if (::Mongoid::VERSION >= "3")
21
+ if defined?(::Mongoid::VERSION) && ::Mongoid::VERSION >= "3"
23
22
  index({ geocoder_options[:coordinates].to_sym => '2d' },
24
23
  {:min => -180, :max => 180})
25
24
  else
@@ -0,0 +1,103 @@
1
+ module Geocoder
2
+ class Query
3
+ attr_accessor :text, :options
4
+
5
+ def initialize(text, options = {})
6
+ self.text = text
7
+ self.options = options
8
+ end
9
+
10
+ def execute
11
+ lookup.search(text, options)
12
+ end
13
+
14
+ def to_s
15
+ text
16
+ end
17
+
18
+ def sanitized_text
19
+ if coordinates?
20
+ text.split(/\s*,\s*/).join(',')
21
+ else
22
+ text
23
+ end
24
+ end
25
+
26
+ ##
27
+ # Get a Lookup object (which communicates with the remote geocoding API)
28
+ # appropriate to the Query text.
29
+ #
30
+ def lookup
31
+ if ip_address?
32
+ name = Configuration.ip_lookup || Geocoder::Lookup.ip_services.first
33
+ else
34
+ name = Configuration.lookup || Geocoder::Lookup.street_services.first
35
+ end
36
+ Lookup.get(name)
37
+ end
38
+
39
+ def url
40
+ lookup.query_url(self)
41
+ end
42
+
43
+ ##
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.
47
+ #
48
+ def blank?
49
+ !params_given? and (
50
+ (text.is_a?(Array) and text.compact.size < 2) or
51
+ text.to_s.match(/^\s*$/)
52
+ )
53
+ end
54
+
55
+ ##
56
+ # Does the Query text look like an IP address?
57
+ #
58
+ # Does not check for actual validity, just the appearance of four
59
+ # dot-delimited numbers.
60
+ #
61
+ def ip_address?
62
+ !!text.to_s.match(/^(::ffff:)?(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
63
+ end
64
+
65
+ ##
66
+ # Is the Query text a loopback IP address?
67
+ #
68
+ def loopback_ip_address?
69
+ !!(text == "0.0.0.0" or text.to_s.match(/^127/))
70
+ end
71
+
72
+ ##
73
+ # Does the given string look like latitude/longitude coordinates?
74
+ #
75
+ def coordinates?
76
+ text.is_a?(Array) or (
77
+ text.is_a?(String) and
78
+ !!text.to_s.match(/^-?[0-9\.]+, *-?[0-9\.]+$/)
79
+ )
80
+ end
81
+
82
+ ##
83
+ # Return the latitude/longitude coordinates specified in the query,
84
+ # or nil if none.
85
+ #
86
+ def coordinates
87
+ sanitized_text.split(',') if coordinates?
88
+ end
89
+
90
+ ##
91
+ # Should reverse geocoding be performed for this query?
92
+ #
93
+ def reverse_geocode?
94
+ coordinates?
95
+ end
96
+
97
+ private # ----------------------------------------------------------------
98
+
99
+ def params_given?
100
+ !!(options[:params].is_a?(Hash) and options[:params].keys.size > 0)
101
+ end
102
+ end
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
@@ -1,10 +1,16 @@
1
1
  module Geocoder
2
2
  module Result
3
3
  class Base
4
- attr_accessor :data, :cache_hit
4
+
5
+ # data (hash) fetched from geocoding service
6
+ attr_accessor :data
7
+
8
+ # true if result came from cache, false if from request to geocoding
9
+ # service; nil if cache is not configured
10
+ attr_accessor :cache_hit
5
11
 
6
12
  ##
7
- # Takes a hash of result data from a parsed Google result document.
13
+ # Takes a hash of data from a parsed geocoding service response.
8
14
  #
9
15
  def initialize(data)
10
16
  @data = data
@@ -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
@@ -2,65 +2,50 @@ require 'geocoder/results/base'
2
2
 
3
3
  module Geocoder::Result
4
4
  class Mapquest < Base
5
-
6
- def house_number
7
- @data['address']['house_number']
5
+ def latitude
6
+ @data["latLng"]["lat"]
8
7
  end
9
8
 
10
- def address
11
- @data['display_name']
9
+ def longitude
10
+ @data["latLng"]["lng"]
12
11
  end
13
12
 
14
- def street
15
- @data['address']['road']
13
+ def coordinates
14
+ [latitude, longitude]
16
15
  end
17
16
 
18
17
  def city
19
- @data['address']['city']
18
+ @data['adminArea5']
20
19
  end
21
20
 
22
- def village
23
- @data['address']['villiage']
24
- end
25
-
26
- def town
27
- @data['address']['town']
21
+ def street
22
+ @data['street']
28
23
  end
29
24
 
30
25
  def state
31
- @data['address']['state']
26
+ @data['adminArea3']
32
27
  end
33
28
 
34
29
  alias_method :state_code, :state
35
30
 
36
- def postal_code
37
- @data['address']['postcode']
38
- end
31
+ #FIXME: these might not be right, unclear with MQ documentation
32
+ alias_method :provinice, :state
33
+ alias_method :province_code, :state
39
34
 
40
- def county
41
- @data['address']['county']
35
+ def postal_code
36
+ @data['postalCode'].to_s
42
37
  end
43
38
 
44
39
  def country
45
- @data['address']['country']
40
+ @data['adminArea1']
46
41
  end
47
42
 
48
43
  def country_code
49
- @data['address']['country_code']
50
- end
51
-
52
- def coordinates
53
- [@data['lat'].to_f, @data['lon'].to_f]
54
- end
55
-
56
- def self.response_attributes
57
- %w[place_id boundingbox license polygonpoints display_name class type stadium suburb]
44
+ country
58
45
  end
59
46
 
60
- response_attributes.each do |a|
61
- define_method a do
62
- @data[a]
63
- end
47
+ def address
48
+ [street, city, state, postal_code, country].reject{|s| s.length == 0 }.join(", ")
64
49
  end
65
50
  end
66
51
  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
@@ -5,8 +5,9 @@ module Geocoder::Result
5
5
 
6
6
  def poi
7
7
  %w[stadium bus_stop tram_stop].each do |key|
8
- @data['address'][key] if @data['address'].key?(key)
8
+ return @data['address'][key] if @data['address'].key?(key)
9
9
  end
10
+ return nil
10
11
  end
11
12
 
12
13
  def house_number
@@ -69,14 +70,24 @@ module Geocoder::Result
69
70
  [@data['lat'].to_f, @data['lon'].to_f]
70
71
  end
71
72
 
73
+ def place_class
74
+ @data['class']
75
+ end
76
+
77
+ def place_type
78
+ @data['type']
79
+ end
80
+
72
81
  def self.response_attributes
73
82
  %w[place_id osm_type osm_id boundingbox license
74
83
  polygonpoints display_name class type stadium]
75
84
  end
76
85
 
77
86
  response_attributes.each do |a|
78
- define_method a do
79
- @data[a]
87
+ unless method_defined?(a)
88
+ define_method a do
89
+ @data[a]
90
+ end
80
91
  end
81
92
  end
82
93
  end
@@ -31,17 +31,24 @@ module Geocoder::Result
31
31
  @data['postal']
32
32
  end
33
33
 
34
+ def address_hash
35
+ @data['hash']
36
+ end
37
+
34
38
  def self.response_attributes
35
39
  %w[quality offsetlat offsetlon radius boundingbox name
36
40
  line1 line2 line3 line4 cross house street xstreet unittype unit
41
+ city state statecode country countrycode postal
37
42
  neighborhood county countycode
38
43
  level0 level1 level2 level3 level4 level0code level1code level2code
39
44
  timezone areacode uzip hash woeid woetype]
40
45
  end
41
46
 
42
47
  response_attributes.each do |a|
43
- define_method a do
44
- @data[a]
48
+ unless method_defined?(a)
49
+ define_method a do
50
+ @data[a]
51
+ end
45
52
  end
46
53
  end
47
54
  end