geocoder 1.1.3 → 1.1.4

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 (45) hide show
  1. data/CHANGELOG.rdoc +12 -0
  2. data/README.md +580 -0
  3. data/examples/autoexpire_cache.rb +30 -0
  4. data/lib/geocoder.rb +9 -85
  5. data/lib/geocoder/calculations.rb +1 -1
  6. data/lib/geocoder/cli.rb +9 -10
  7. data/lib/geocoder/configuration.rb +3 -1
  8. data/lib/geocoder/lookup.rb +81 -0
  9. data/lib/geocoder/lookups/base.rb +20 -32
  10. data/lib/geocoder/lookups/bing.rb +12 -8
  11. data/lib/geocoder/lookups/freegeoip.rb +5 -5
  12. data/lib/geocoder/lookups/geocoder_ca.rb +13 -9
  13. data/lib/geocoder/lookups/google.rb +19 -7
  14. data/lib/geocoder/lookups/google_premier.rb +8 -7
  15. data/lib/geocoder/lookups/mapquest.rb +5 -23
  16. data/lib/geocoder/lookups/nominatim.rb +16 -13
  17. data/lib/geocoder/lookups/test.rb +8 -6
  18. data/lib/geocoder/lookups/yahoo.rb +49 -10
  19. data/lib/geocoder/lookups/yandex.rb +15 -8
  20. data/lib/geocoder/models/mongoid.rb +0 -1
  21. data/lib/geocoder/query.rb +88 -0
  22. data/lib/geocoder/results/mapquest.rb +2 -61
  23. data/lib/geocoder/results/nominatim.rb +24 -3
  24. data/lib/geocoder/sql.rb +104 -0
  25. data/lib/geocoder/stores/active_record.rb +68 -140
  26. data/lib/geocoder/stores/mongo_base.rb +2 -2
  27. data/lib/geocoder/version.rb +1 -1
  28. data/test/active_record_test.rb +15 -0
  29. data/test/calculations_test.rb +7 -0
  30. data/test/error_handling_test.rb +7 -7
  31. data/test/fixtures/yahoo_madison_square_garden.json +49 -43
  32. data/test/fixtures/yahoo_v1_madison_square_garden.json +46 -0
  33. data/test/fixtures/yahoo_v1_no_results.json +10 -0
  34. data/test/https_test.rb +2 -2
  35. data/test/integration/smoke_test.rb +6 -4
  36. data/test/lookup_test.rb +13 -6
  37. data/test/query_test.rb +34 -0
  38. data/test/result_test.rb +1 -1
  39. data/test/services_test.rb +48 -7
  40. data/test/test_helper.rb +64 -49
  41. data/test/test_mode_test.rb +0 -1
  42. metadata +13 -7
  43. data/README.rdoc +0 -552
  44. data/test/fixtures/yahoo_garbage.json +0 -50
  45. data/test/input_handling_test.rb +0 -43
@@ -0,0 +1,30 @@
1
+ # This class implements a cache with simple delegation to the Redis store, but
2
+ # when it creates a key/value pair, it also sends an EXPIRE command with a TTL.
3
+ # It should be fairly simple to do the same thing with Memcached.
4
+ class AutoexpireCache
5
+ def initialize(store)
6
+ @store = store
7
+ @ttl = 86400
8
+ end
9
+
10
+ def [](url)
11
+ @store.[](url)
12
+ end
13
+
14
+ def []=(url, value)
15
+ @store.[]=(url, value)
16
+ @store.expire(url, @ttl)
17
+ end
18
+
19
+ def keys
20
+ @store.keys
21
+ end
22
+
23
+ def del(url)
24
+ @store.del(url)
25
+ end
26
+ end
27
+
28
+ Geocoder.configure do |config|
29
+ config.cache = AutoexpireCache.new(Redis.new)
30
+ end
@@ -1,8 +1,10 @@
1
1
  require "geocoder/configuration"
2
+ require "geocoder/query"
2
3
  require "geocoder/calculations"
3
4
  require "geocoder/exceptions"
4
5
  require "geocoder/cache"
5
6
  require "geocoder/request"
7
+ require "geocoder/lookup"
6
8
  require "geocoder/models/active_record" if defined?(::ActiveRecord)
7
9
  require "geocoder/models/mongoid" if defined?(::Mongoid)
8
10
  require "geocoder/models/mongo_mapper" if defined?(::MongoMapper)
@@ -13,15 +15,16 @@ module Geocoder
13
15
  ##
14
16
  # Search for information about an address or a set of coordinates.
15
17
  #
16
- def search(query)
17
- blank_query?(query) ? [] : lookup(query).search(query)
18
+ def search(query, options = {})
19
+ query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
20
+ query.blank? ? [] : query.execute
18
21
  end
19
22
 
20
23
  ##
21
24
  # Look up the coordinates of the given street or IP address.
22
25
  #
23
- def coordinates(address)
24
- if (results = search(address)).size > 0
26
+ def coordinates(address, options = {})
27
+ if (results = search(address, options)).size > 0
25
28
  results.first.coordinates
26
29
  end
27
30
  end
@@ -30,8 +33,8 @@ module Geocoder
30
33
  # Look up the address of the given coordinates ([lat,lon])
31
34
  # or IP address (string).
32
35
  #
33
- def address(query)
34
- if (results = search(query)).size > 0
36
+ def address(query, options = {})
37
+ if (results = search(query, options)).size > 0
35
38
  results.first.address
36
39
  end
37
40
  end
@@ -45,85 +48,6 @@ module Geocoder
45
48
  end
46
49
  @cache
47
50
  end
48
-
49
- ##
50
- # Array of valid Lookup names.
51
- #
52
- def valid_lookups
53
- street_lookups + ip_lookups
54
- end
55
-
56
- ##
57
- # All street address lookups, default first.
58
- #
59
- def street_lookups
60
- [:google, :google_premier, :yahoo, :bing, :geocoder_ca, :yandex, :nominatim, :mapquest, :test]
61
- end
62
-
63
- ##
64
- # All IP address lookups, default first.
65
- #
66
- def ip_lookups
67
- [:freegeoip]
68
- end
69
-
70
-
71
- private # -----------------------------------------------------------------
72
-
73
- ##
74
- # Get a Lookup object (which communicates with the remote geocoding API).
75
- # Takes a search query and returns an IP or street address Lookup
76
- # depending on the query contents.
77
- #
78
- def lookup(query)
79
- if ip_address?(query)
80
- get_lookup(ip_lookups.first)
81
- else
82
- get_lookup(Configuration.lookup || street_lookups.first)
83
- end
84
- end
85
-
86
- ##
87
- # Retrieve a Lookup object from the store.
88
- #
89
- def get_lookup(name)
90
- @lookups = {} unless defined?(@lookups)
91
- @lookups[name] = spawn_lookup(name) unless @lookups.include?(name)
92
- @lookups[name]
93
- end
94
-
95
- ##
96
- # Spawn a Lookup of the given name.
97
- #
98
- def spawn_lookup(name)
99
- if valid_lookups.include?(name)
100
- name = name.to_s
101
- require "geocoder/lookups/#{name}"
102
- klass = name.split("_").map{ |i| i[0...1].upcase + i[1..-1] }.join
103
- Geocoder::Lookup.const_get(klass).new
104
- else
105
- valids = valid_lookups.map(&:inspect).join(", ")
106
- raise ConfigurationError, "Please specify a valid lookup for Geocoder " +
107
- "(#{name.inspect} is not one of: #{valids})."
108
- end
109
- end
110
-
111
- ##
112
- # Does the given value look like an IP address?
113
- #
114
- # Does not check for actual validity, just the appearance of four
115
- # dot-delimited numbers.
116
- #
117
- def ip_address?(value)
118
- !!value.to_s.match(/^(::ffff:)?(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
119
- end
120
-
121
- ##
122
- # Is the given search query blank? (ie, should we not bother searching?)
123
- #
124
- def blank_query?(value)
125
- !!value.to_s.match(/^\s*$/)
126
- end
127
51
  end
128
52
 
129
53
  # load Railtie if Rails exists
@@ -184,7 +184,7 @@ module Geocoder
184
184
  end
185
185
 
186
186
  ##
187
- # Returns coordinates of the lower-left and upper-right corners of a box
187
+ # Returns coordinates of the southwest and northeast corners of a box
188
188
  # with the given point at its center. The radius is the shortest distance
189
189
  # from the center point to any side of the box (the length of each side
190
190
  # is twice the radius).
@@ -32,9 +32,10 @@ module Geocoder
32
32
  Geocoder::Configuration.http_proxy = proxy
33
33
  end
34
34
 
35
- opts.on("-s <service>", Geocoder.street_lookups, "--service <service>",
36
- "Geocoding service: #{Geocoder.street_lookups * ', '}") do |service|
35
+ opts.on("-s <service>", Geocoder::Lookup.all_services_except_test, "--service <service>",
36
+ "Geocoding service: #{Geocoder::Lookup.all_services_except_test * ', '}") do |service|
37
37
  Geocoder::Configuration.lookup = service.to_sym
38
+ Geocoder::Configuration.ip_lookup = service.to_sym
38
39
  end
39
40
 
40
41
  opts.on("-t <seconds>", "--timeout <seconds>",
@@ -78,21 +79,19 @@ module Geocoder
78
79
  end
79
80
 
80
81
  if show_url
81
- lookup = Geocoder.send(:lookup, query)
82
- reverse = lookup.send(:coordinates?, query)
83
- out << lookup.send(:query_url, query, reverse) + "\n"
82
+ q = Geocoder::Query.new(query)
83
+ out << q.lookup.send(:query_url, q) + "\n"
84
84
  exit 0
85
85
  end
86
86
 
87
87
  if show_json
88
- lookup = Geocoder.send(:lookup, query)
89
- reverse = lookup.send(:coordinates?, query)
90
- out << lookup.send(:fetch_raw_data, query, reverse) + "\n"
88
+ q = Geocoder::Query.new(query)
89
+ out << q.lookup.send(:fetch_raw_data, q) + "\n"
91
90
  exit 0
92
91
  end
93
92
 
94
93
  if (result = Geocoder.search(query).first)
95
- lookup = Geocoder.send(:get_lookup, :google)
94
+ google = Geocoder::Lookup.get(:google)
96
95
  lines = [
97
96
  ["Latitude", result.latitude],
98
97
  ["Longitude", result.longitude],
@@ -101,7 +100,7 @@ module Geocoder
101
100
  ["State/province", result.state],
102
101
  ["Postal code", result.postal_code],
103
102
  ["Country", result.country],
104
- ["Google map", lookup.map_link_url(result.coordinates)],
103
+ ["Google map", google.map_link_url(result.coordinates)],
105
104
  ]
106
105
  lines.each do |line|
107
106
  out << (line[0] + ": ").ljust(18) + line[1].to_s + "\n"
@@ -40,6 +40,7 @@ module Geocoder
40
40
  OPTIONS = [
41
41
  :timeout,
42
42
  :lookup,
43
+ :ip_lookup,
43
44
  :language,
44
45
  :http_headers,
45
46
  :use_https,
@@ -61,7 +62,8 @@ module Geocoder
61
62
 
62
63
  def set_defaults
63
64
  @timeout = 3 # geocoding service timeout (secs)
64
- @lookup = :google # name of geocoding service (symbol)
65
+ @lookup = :google # name of street address geocoding service (symbol)
66
+ @ip_lookup = :freegeoip # name of IP address geocoding service (symbol)
65
67
  @language = :en # ISO-639 language code
66
68
  @http_headers = {} # HTTP headers for lookup
67
69
  @use_https = false # use HTTPS for lookup requests? (if supported)
@@ -0,0 +1,81 @@
1
+ module Geocoder
2
+ module Lookup
3
+ extend self
4
+
5
+ ##
6
+ # Array of valid Lookup service names.
7
+ #
8
+ def all_services
9
+ street_services + ip_services
10
+ end
11
+
12
+ ##
13
+ # Array of valid Lookup service names, excluding :test.
14
+ #
15
+ def all_services_except_test
16
+ all_services - [:test]
17
+ end
18
+
19
+ ##
20
+ # All street address lookup services, default first.
21
+ #
22
+ def street_services
23
+ [
24
+ :google,
25
+ :google_premier,
26
+ :yahoo,
27
+ :bing,
28
+ :geocoder_ca,
29
+ :yandex,
30
+ :nominatim,
31
+ :mapquest,
32
+ :test
33
+ ]
34
+ end
35
+
36
+ ##
37
+ # All IP address lookup services, default first.
38
+ #
39
+ def ip_services
40
+ [:freegeoip]
41
+ end
42
+
43
+ ##
44
+ # Retrieve a Lookup object from the store.
45
+ # Use this instead of Geocoder::Lookup::X.new to get an
46
+ # already-configured Lookup object.
47
+ #
48
+ def get(name)
49
+ @services = {} unless defined?(@services)
50
+ @services[name] = spawn(name) unless @services.include?(name)
51
+ @services[name]
52
+ end
53
+
54
+
55
+ private # -----------------------------------------------------------------
56
+
57
+ ##
58
+ # Spawn a Lookup of the given name.
59
+ #
60
+ def spawn(name)
61
+ if all_services.include?(name)
62
+ Geocoder::Lookup.const_get(classify_name(name)).new
63
+ else
64
+ valids = all_services.map(&:inspect).join(", ")
65
+ raise ConfigurationError, "Please specify a valid lookup for Geocoder " +
66
+ "(#{name.inspect} is not one of: #{valids})."
67
+ end
68
+ end
69
+
70
+ ##
71
+ # Convert an "underscore" version of a name into a "class" version.
72
+ #
73
+ def classify_name(filename)
74
+ filename.to_s.split("_").map{ |i| i[0...1].upcase + i[1..-1] }.join
75
+ end
76
+ end
77
+ end
78
+
79
+ Geocoder::Lookup.all_services.each do |name|
80
+ require "geocoder/lookups/#{name}"
81
+ end
@@ -13,6 +13,7 @@ end
13
13
 
14
14
  module Geocoder
15
15
  module Lookup
16
+
16
17
  class Base
17
18
 
18
19
  ##
@@ -23,18 +24,9 @@ module Geocoder
23
24
  # "205.128.54.202") for geocoding, or coordinates (latitude, longitude)
24
25
  # for reverse geocoding. Returns an array of <tt>Geocoder::Result</tt>s.
25
26
  #
26
- def search(query)
27
-
28
- # if coordinates given as string, turn into array
29
- query = query.split(/\s*,\s*/) if coordinates?(query)
30
-
31
- if query.is_a?(Array)
32
- reverse = true
33
- query = query.join(',')
34
- else
35
- reverse = false
36
- end
37
- results(query, reverse).map{ |r|
27
+ def search(query, options = {})
28
+ query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
29
+ results(query).map{ |r|
38
30
  result = result_class.new(r)
39
31
  result.cache_hit = @cache_hit if cache
40
32
  result
@@ -77,14 +69,24 @@ module Geocoder
77
69
  ##
78
70
  # Geocoder::Result object or nil on timeout or other error.
79
71
  #
80
- def results(query, reverse = false)
72
+ def results(query)
81
73
  fail
82
74
  end
83
75
 
76
+ def query_url_params(query)
77
+ query.options[:params] || {}
78
+ end
79
+
80
+ def url_query_string(query)
81
+ hash_to_query(
82
+ query_url_params(query).reject{ |key,value| value.nil? }
83
+ )
84
+ end
85
+
84
86
  ##
85
87
  # URL to use for querying the geocoding engine.
86
88
  #
87
- def query_url(query, reverse = false)
89
+ def query_url(query)
88
90
  fail
89
91
  end
90
92
 
@@ -111,8 +113,8 @@ module Geocoder
111
113
  ##
112
114
  # Returns a parsed search result (Ruby hash).
113
115
  #
114
- def fetch_data(query, reverse = false)
115
- parse_raw_data fetch_raw_data(query, reverse)
116
+ def fetch_data(query)
117
+ parse_raw_data fetch_raw_data(query)
116
118
  rescue SocketError => err
117
119
  raise_error(err) or warn "Geocoding API connection cannot be established."
118
120
  rescue TimeoutError => err
@@ -144,9 +146,9 @@ module Geocoder
144
146
  ##
145
147
  # Fetches a raw search result (JSON string).
146
148
  #
147
- def fetch_raw_data(query, reverse = false)
149
+ def fetch_raw_data(query)
148
150
  timeout(Geocoder::Configuration.timeout) do
149
- url = query_url(query, reverse)
151
+ url = query_url(query)
150
152
  uri = URI.parse(url)
151
153
  if cache and body = cache[url]
152
154
  @cache_hit = true
@@ -171,20 +173,6 @@ module Geocoder
171
173
  Geocoder.cache
172
174
  end
173
175
 
174
- ##
175
- # Is the given string a loopback IP address?
176
- #
177
- def loopback_address?(ip)
178
- !!(ip == "0.0.0.0" or ip.to_s.match(/^127/))
179
- end
180
-
181
- ##
182
- # Does the given string look like latitude/longitude coordinates?
183
- #
184
- def coordinates?(value)
185
- value.is_a?(String) and !!value.to_s.match(/^-?[0-9\.]+, *-?[0-9\.]+$/)
186
- end
187
-
188
176
  ##
189
177
  # Simulate ActiveSupport's Object#to_query.
190
178
  # Removes any keys with nil value.
@@ -10,8 +10,8 @@ module Geocoder::Lookup
10
10
 
11
11
  private # ---------------------------------------------------------------
12
12
 
13
- def results(query, reverse = false)
14
- return [] unless doc = fetch_data(query, reverse)
13
+ def results(query)
14
+ return [] unless doc = fetch_data(query)
15
15
 
16
16
  if doc['statusDescription'] == "OK"
17
17
  return doc['resourceSets'].first['estimatedTotal'] > 0 ? doc['resourceSets'].first['resources'] : []
@@ -21,13 +21,17 @@ module Geocoder::Lookup
21
21
  end
22
22
  end
23
23
 
24
- def query_url(query, reverse = false)
25
- params = {:key => Geocoder::Configuration.api_key}
26
- params[:query] = query unless reverse
24
+ def query_url_params(query)
25
+ super.merge(
26
+ :key => Geocoder::Configuration.api_key,
27
+ :query => query.reverse_geocode? ? nil : query.sanitized_text
28
+ )
29
+ end
27
30
 
28
- base_url = "http://dev.virtualearth.net/REST/v1/Locations"
29
- url_tail = reverse ? "/#{query}?" : "?"
30
- base_url + url_tail + hash_to_query(params)
31
+ def query_url(query)
32
+ "http://dev.virtualearth.net/REST/v1/Locations" +
33
+ (query.reverse_geocode? ? "/#{query.sanitized_text}?" : "?") +
34
+ url_query_string(query)
31
35
  end
32
36
  end
33
37
  end