geocoder 0.9.8 → 0.9.9

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.

@@ -1,8 +1,16 @@
1
1
  module Geocoder
2
2
  class Configuration
3
- cattr_accessor :timeout
3
+ def self.timeout; @@timeout; end
4
+ def self.timeout=(obj); @@timeout = obj; end
5
+
6
+ def self.lookup; @@lookup; end
7
+ def self.lookup=(obj); @@lookup = obj; end
8
+
9
+ def self.yahoo_appid; @@yahoo_appid; end
10
+ def self.yahoo_appid=(obj); @@yahoo_appid = obj; end
4
11
  end
5
12
  end
6
13
 
7
- Geocoder::Configuration.timeout = 3
8
-
14
+ Geocoder::Configuration.timeout = 3
15
+ Geocoder::Configuration.lookup = :google
16
+ Geocoder::Configuration.yahoo_appid = ""
@@ -0,0 +1,110 @@
1
+ require 'net/http'
2
+ unless defined? ActiveSupport::JSON
3
+ begin
4
+ require 'json'
5
+ rescue LoadError
6
+ raise LoadError, "Please install the json gem to parse geocoder results."
7
+ end
8
+ end
9
+
10
+ module Geocoder
11
+ module Lookup
12
+ class Base
13
+
14
+ ##
15
+ # Query the geocoding API and return a Geocoder::Result object.
16
+ # Returns +nil+ on timeout or error.
17
+ #
18
+ # Takes a search string (eg: "Mississippi Coast Coliseumf, Biloxi, MS",
19
+ # "205.128.54.202") for geocoding, or coordinates (latitude, longitude)
20
+ # for reverse geocoding.
21
+ #
22
+ def search(*args)
23
+ if res = result(args.join(","), args.size == 2)
24
+ result_class.new(res)
25
+ end
26
+ end
27
+
28
+
29
+ private # -------------------------------------------------------------
30
+
31
+ ##
32
+ # Geocoder::Result object or nil on timeout or other error.
33
+ #
34
+ def result(query, reverse = false)
35
+ fail
36
+ end
37
+
38
+ ##
39
+ # URL to use for querying the geocoding engine.
40
+ #
41
+ def query_url(query, reverse = false)
42
+ fail
43
+ end
44
+
45
+ ##
46
+ # Class of the result objects
47
+ #
48
+ def result_class
49
+ eval("Geocoder::Result::#{self.class.to_s.split(":").last}")
50
+ end
51
+
52
+ ##
53
+ # Returns a parsed search result (Ruby hash).
54
+ #
55
+ def fetch_data(query, reverse = false)
56
+ begin
57
+ parse_raw_data fetch_raw_data(query, reverse)
58
+ rescue SocketError
59
+ warn "Geocoding API connection cannot be established."
60
+ rescue TimeoutError
61
+ warn "Geocoding API not responding fast enough " +
62
+ "(see Geocoder::Configuration.timeout to set limit)."
63
+ end
64
+ end
65
+
66
+ ##
67
+ # Parses a raw search result (returns hash or array).
68
+ #
69
+ def parse_raw_data(raw_data)
70
+ if defined?(JSON) and defined?(JSON.parse)
71
+ begin
72
+ JSON.parse(raw_data)
73
+ rescue
74
+ warn "Geocoding API's response was not valid JSON."
75
+ end
76
+ elsif defined?(ActiveSupport::JSON)
77
+ ActiveSupport::JSON.decode(raw_data)
78
+ else
79
+ raise Geocoder::Error, "No JSON-parsing library found. " +
80
+ "Please install either the 'json' or 'activesupport' gem."
81
+ end
82
+ end
83
+
84
+ ##
85
+ # Fetches a raw search result (JSON string).
86
+ #
87
+ def fetch_raw_data(query, reverse = false)
88
+ url = query_url(query, reverse)
89
+ timeout(Geocoder::Configuration.timeout) do
90
+ Net::HTTP.get_response(URI.parse(url)).body
91
+ end
92
+ end
93
+
94
+ ##
95
+ # Is the given string a loopback IP address?
96
+ #
97
+ def loopback_address?(ip)
98
+ !!(ip == "0.0.0.0" or ip.match(/^127/))
99
+ end
100
+
101
+ ##
102
+ # Simulate ActiveSupport's Object#to_query.
103
+ #
104
+ def hash_to_query(hash)
105
+ require 'cgi' unless defined?(CGI) && defined?(CGI.escape)
106
+ hash.collect{ |p| p.map{ |i| CGI.escape i.to_s } * '=' }.sort * '&'
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,40 @@
1
+ require 'geocoder/lookups/base'
2
+ require 'geocoder/results/freegeoip'
3
+
4
+ module Geocoder::Lookup
5
+ class Freegeoip < Base
6
+
7
+ private # ---------------------------------------------------------------
8
+
9
+ def result(query, reverse = false)
10
+ # don't look up a loopback address, just return the stored result
11
+ return reserved_result(query) if loopback_address?(query)
12
+ begin
13
+ if doc = fetch_data(query, reverse)
14
+ doc
15
+ end
16
+ rescue StandardError # Freegeoip.net returns HTML on bad request
17
+ nil
18
+ end
19
+ end
20
+
21
+ def reserved_result(ip)
22
+ {
23
+ "ip" => ip,
24
+ "city" => "",
25
+ "region_code" => "",
26
+ "region_name" => "",
27
+ "metrocode" => "",
28
+ "zipcode" => "",
29
+ "latitude" => "0",
30
+ "longitude" => "0",
31
+ "country_name" => "Reserved",
32
+ "country_code" => "RD"
33
+ }
34
+ end
35
+
36
+ def query_url(query, reverse = false)
37
+ "http://freegeoip.net/json/#{query}"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,31 @@
1
+ require 'geocoder/lookups/base'
2
+ require "geocoder/results/google"
3
+
4
+ module Geocoder::Lookup
5
+ class Google < Base
6
+
7
+ private # ---------------------------------------------------------------
8
+
9
+ def result(query, reverse = false)
10
+ doc = fetch_data(query, reverse)
11
+ case doc['status']; when "OK" # OK status implies >0 results
12
+ doc['results'].first
13
+ when "OVER_QUERY_LIMIT"
14
+ warn "Google Geocoding API error: over query limit."
15
+ when "REQUEST_DENIED"
16
+ warn "Google Geocoding API error: request denied."
17
+ when "INVALID_REQUEST"
18
+ warn "Google Geocoding API error: invalid request."
19
+ end
20
+ end
21
+
22
+ def query_url(query, reverse = false)
23
+ params = {
24
+ (reverse ? :latlng : :address) => query,
25
+ :sensor => "false"
26
+ }
27
+ "http://maps.google.com/maps/api/geocode/json?" + hash_to_query(params)
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,29 @@
1
+ require 'geocoder/lookups/base'
2
+ require "geocoder/results/yahoo"
3
+
4
+ module Geocoder::Lookup
5
+ class Yahoo < Base
6
+
7
+ private # ---------------------------------------------------------------
8
+
9
+ def result(query, reverse = false)
10
+ doc = fetch_data(query, reverse)
11
+ if doc = doc['ResultSet'] and doc['Error'] == 0
12
+ doc['Results'].first if doc['Found'] > 0
13
+ else
14
+ warn "Yahoo Geocoding API error: #{doc['Error']} (#{doc['ErrorMessage']})."
15
+ end
16
+ end
17
+
18
+ def query_url(query, reverse = false)
19
+ params = {
20
+ :location => query,
21
+ :flags => "JXTSR",
22
+ :gflags => "AC#{'R' if reverse}",
23
+ :appid => Geocoder::Configuration.yahoo_appid
24
+ }
25
+ "http://where.yahooapis.com/geocode?" + hash_to_query(params)
26
+ end
27
+ end
28
+ end
29
+
@@ -1,8 +1,13 @@
1
+ require 'geocoder/orms/base'
2
+ require 'geocoder/orms/active_record_legacy'
3
+
1
4
  ##
2
5
  # Add geocoding functionality to any ActiveRecord object.
3
6
  #
4
- module Geocoder
7
+ module Geocoder::Orm
5
8
  module ActiveRecord
9
+ include Base
10
+ include ActiveRecord::Legacy
6
11
 
7
12
  ##
8
13
  # Implementation of 'included' hook method.
@@ -28,7 +33,7 @@ module Geocoder
28
33
  #
29
34
  scope :near, lambda{ |location, *args|
30
35
  latitude, longitude = location.is_a?(Array) ?
31
- location : Geocoder::Lookup.coordinates(location)
36
+ location : Geocoder.coordinates(location)
32
37
  if latitude and longitude
33
38
  near_scope_options(latitude, longitude, *args)
34
39
  else
@@ -112,7 +117,7 @@ module Geocoder
112
117
  ["#{lat_attr} BETWEEN ? AND ? AND #{lon_attr} BETWEEN ? AND ?"] +
113
118
  coordinate_bounds(latitude, longitude, radius)
114
119
  if obj = options[:exclude]
115
- conditions[0] << " AND id != ?"
120
+ conditions[0] << " AND #{table_name}.id != ?"
116
121
  conditions << obj.id
117
122
  end
118
123
  {
@@ -142,94 +147,34 @@ module Geocoder
142
147
  end
143
148
 
144
149
  ##
145
- # Read the coordinates [lat,lon] of an object. This is not great but it
146
- # seems cleaner than polluting the instance method namespace.
147
- #
148
- def read_coordinates
149
- [:latitude, :longitude].map{ |i| send self.class.geocoder_options[i] }
150
- end
151
-
152
- ##
153
- # Is this object geocoded? (Does it have latitude and longitude?)
154
- #
155
- def geocoded?
156
- read_coordinates.compact.size > 0
157
- end
158
-
159
- ##
160
- # Calculate the distance from the object to a point (lat,lon).
161
- #
162
- # <tt>:units</tt> :: <tt>:mi</tt> (default) or <tt>:km</tt>
163
- #
164
- def distance_to(lat, lon, units = :mi)
165
- return nil unless geocoded?
166
- mylat,mylon = read_coordinates
167
- Geocoder::Calculations.distance_between(mylat, mylon, lat, lon, :units => units)
168
- end
169
-
170
- ##
171
- # Get other geocoded objects within a given radius.
172
- #
173
- # <tt>:units</tt> :: <tt>:mi</tt> (default) or <tt>:km</tt>
150
+ # Look up coordinates and assign to +latitude+ and +longitude+ attributes
151
+ # (or other as specified in +geocoded_by+). Returns coordinates (array).
174
152
  #
175
- def nearbys(radius = 20, units = :mi)
176
- return [] unless geocoded?
177
- options = {:exclude => self, :units => units}
178
- self.class.near(read_coordinates, radius, options)
179
- end
180
-
181
- ##
182
- # Fetch coordinates and assign +latitude+ and +longitude+. Also returns
183
- # coordinates as an array: <tt>[lat, lon]</tt>.
184
- #
185
- def fetch_coordinates(save = false)
186
- address_method = self.class.geocoder_options[:user_address]
187
- unless address_method.is_a? Symbol
188
- raise Geocoder::ConfigurationError,
189
- "You are attempting to fetch coordinates but have not specified " +
190
- "a method which provides an address for the object."
191
- end
192
- coords = Geocoder::Lookup.coordinates(send(address_method))
193
- unless coords.blank?
194
- method = (save ? "update" : "write") + "_attribute"
195
- send method, self.class.geocoder_options[:latitude], coords[0]
196
- send method, self.class.geocoder_options[:longitude], coords[1]
153
+ def geocode
154
+ do_lookup(false) do |o,r|
155
+ unless r.latitude.nil? or r.longitude.nil?
156
+ o.send :write_attribute, self.class.geocoder_options[:latitude], r.latitude
157
+ o.send :write_attribute, self.class.geocoder_options[:longitude], r.longitude
158
+ end
159
+ r.coordinates
197
160
  end
198
- coords
199
161
  end
200
162
 
201
- ##
202
- # Fetch coordinates and update (save) +latitude+ and +longitude+ data.
203
- #
204
- def fetch_coordinates!
205
- fetch_coordinates(true)
206
- end
163
+ #alias_method :fetch_coordinates, :geocode
207
164
 
208
165
  ##
209
- # Fetch address and assign +address+ attribute. Also returns
210
- # address as a string.
166
+ # Look up address and assign to +address+ attribute (or other as specified
167
+ # in +reverse_geocoded_by+). Returns address (string).
211
168
  #
212
- def fetch_address(save = false)
213
- lat_attr = self.class.geocoder_options[:latitude]
214
- lon_attr = self.class.geocoder_options[:longitude]
215
- unless lat_attr.is_a?(Symbol) and lon_attr.is_a?(Symbol)
216
- raise Geocoder::ConfigurationError,
217
- "You are attempting to fetch an address but have not specified " +
218
- "attributes which provide coordinates for the object."
219
- end
220
- address = Geocoder::Lookup.address(send(lat_attr), send(lon_attr))
221
- unless address.blank?
222
- method = (save ? "update" : "write") + "_attribute"
223
- send method, self.class.geocoder_options[:fetched_address], address
169
+ def reverse_geocode
170
+ do_lookup(true) do |o,r|
171
+ unless r.address.nil?
172
+ o.send :write_attribute, self.class.geocoder_options[:fetched_address], r.address
173
+ end
174
+ r.address
224
175
  end
225
- address
226
176
  end
227
177
 
228
- ##
229
- # Fetch address and update (save) +address+ data.
230
- #
231
- def fetch_address!
232
- fetch_address(true)
233
- end
178
+ #alias_method :fetch_address, :reverse_geocode
234
179
  end
235
180
  end
@@ -0,0 +1,58 @@
1
+ module Geocoder::Orm::ActiveRecord
2
+ module Legacy
3
+
4
+ ##
5
+ # Fetch coordinates and update (save) +latitude+ and +longitude+ data.
6
+ #
7
+ def fetch_coordinates!
8
+ warn "DEPRECATION WARNING: The 'fetch_coordinates!' method is deprecated and will be removed in geocoder v1.0. " +
9
+ "Please use 'geocode' instead and then save your objects manually."
10
+ do_lookup(false) do |o,r|
11
+ unless r.latitude.nil? or r.longitude.nil?
12
+ o.send :update_attribute, self.class.geocoder_options[:latitude], r.latitude
13
+ o.send :update_attribute, self.class.geocoder_options[:longitude], r.longitude
14
+ end
15
+ r.coordinates
16
+ end
17
+ end
18
+
19
+ def fetch_coordinates(*args)
20
+ warn "DEPRECATION WARNING: The 'fetch_coordinates' method will cease taking " +
21
+ "an argument in geocoder v1.0. Please save your objects manually." if args.size > 0
22
+ do_lookup(false) do |o,r|
23
+ unless r.latitude.nil? or r.longitude.nil?
24
+ method = ((args.size > 0 && args.first) ? "update" : "write" ) + "_attribute"
25
+ o.send method, self.class.geocoder_options[:latitude], r.latitude
26
+ o.send method, self.class.geocoder_options[:longitude], r.longitude
27
+ end
28
+ r.coordinates
29
+ end
30
+ end
31
+
32
+ ##
33
+ # Fetch address and update (save) +address+ data.
34
+ #
35
+ def fetch_address!
36
+ warn "DEPRECATION WARNING: The 'fetch_address!' method is deprecated and will be removed in geocoder v1.0. " +
37
+ "Please use 'reverse_geocode' instead and then save your objects manually."
38
+ do_lookup(true) do |o,r|
39
+ unless r.address.nil?
40
+ o.send :update_attribute, self.class.geocoder_options[:fetched_address], r.address
41
+ end
42
+ r.address
43
+ end
44
+ end
45
+
46
+ def fetch_address(*args)
47
+ warn "DEPRECATION WARNING: The 'fetch_address' method will cease taking " +
48
+ "an argument in geocoder v1.0. Please save your objects manually." if args.size > 0
49
+ do_lookup(true) do |o,r|
50
+ unless r.latitude.nil? or r.longitude.nil?
51
+ method = ((args.size > 0 && args.first) ? "update" : "write" ) + "_attribute"
52
+ o.send method, self.class.geocoder_options[:fetched_address], r.address
53
+ end
54
+ r.address
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,96 @@
1
+ module Geocoder
2
+ module Orm
3
+ module Base
4
+
5
+ ##
6
+ # Is this object geocoded? (Does it have latitude and longitude?)
7
+ #
8
+ def geocoded?
9
+ read_coordinates.compact.size > 0
10
+ end
11
+
12
+ ##
13
+ # Calculate the distance from the object to an arbitrary point.
14
+ # Takes two floats (latitude, longitude) and a symbol specifying the
15
+ # units to be used (:mi or :km; default is :mi).
16
+ #
17
+ def distance_to(lat, lon, units = :mi)
18
+ return nil unless geocoded?
19
+ mylat,mylon = read_coordinates
20
+ Geocoder::Calculations.distance_between(mylat, mylon, lat, lon, :units => units)
21
+ end
22
+
23
+ alias_method :distance_from, :distance_to
24
+
25
+ ##
26
+ # Get nearby geocoded objects. Takes a radius (integer) and a symbol
27
+ # representing the units of the ratius (:mi or :km; default is :mi).
28
+ #
29
+ def nearbys(radius = 20, units = :mi)
30
+ return [] unless geocoded?
31
+ options = {:exclude => self, :units => units}
32
+ self.class.near(read_coordinates, radius, options)
33
+ end
34
+
35
+ ##
36
+ # Look up coordinates and assign to +latitude+ and +longitude+ attributes
37
+ # (or other as specified in +geocoded_by+). Returns coordinates (array).
38
+ #
39
+ def geocode
40
+ fail
41
+ end
42
+
43
+ ##
44
+ # Look up address and assign to +address+ attribute (or other as specified
45
+ # in +reverse_geocoded_by+). Returns address (string).
46
+ #
47
+ def reverse_geocode
48
+ fail
49
+ end
50
+
51
+
52
+ private # --------------------------------------------------------------
53
+
54
+ ##
55
+ # Look up geographic data based on object attributes (configured in
56
+ # geocoded_by or reverse_geocoded_by) and handle the result with the
57
+ # block (given to geocoded_by or reverse_geocoded_by). The block is
58
+ # given two-arguments: the object being geocoded and a
59
+ # Geocoder::Result object with the geocoding results).
60
+ #
61
+ def do_lookup(reverse = false)
62
+ options = self.class.geocoder_options
63
+ if reverse and options[:reverse_geocode]
64
+ args = [:latitude, :longitude]
65
+ elsif !reverse and options[:geocode]
66
+ args = [:user_address]
67
+ else
68
+ return
69
+ end
70
+ args.map!{ |a| send(options[a]) }
71
+
72
+ if result = Geocoder.search(*args)
73
+
74
+ # execute custom block, if specified in configuration
75
+ block_key = reverse ? :reverse_block : :geocode_block
76
+ if custom_block = options[block_key]
77
+ custom_block.call(self, result)
78
+
79
+ # else execute block passed directly to this method,
80
+ # which generally performs the "auto-assigns"
81
+ elsif block_given?
82
+ yield(self, result)
83
+ end
84
+ end
85
+ end
86
+
87
+ ##
88
+ # Read the coordinates [lat,lon] of the object.
89
+ # Looks at user config to determine attributes.
90
+ #
91
+ def read_coordinates
92
+ [:latitude, :longitude].map{ |i| send self.class.geocoder_options[i] }
93
+ end
94
+ end
95
+ end
96
+ end