rails-geocoder 0.9.8 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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