rails-geocoder 0.9.10 → 0.9.11

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.
@@ -6,10 +6,10 @@ module Geocoder::Lookup
6
6
 
7
7
  private # ---------------------------------------------------------------
8
8
 
9
- def result(query, reverse = false)
10
- doc = fetch_data(query, reverse)
9
+ def results(query, reverse = false)
10
+ return [] unless doc = fetch_data(query, reverse)
11
11
  case doc['status']; when "OK" # OK status implies >0 results
12
- doc['results'].first
12
+ return doc['results']
13
13
  when "OVER_QUERY_LIMIT"
14
14
  warn "Google Geocoding API error: over query limit."
15
15
  when "REQUEST_DENIED"
@@ -17,14 +17,17 @@ module Geocoder::Lookup
17
17
  when "INVALID_REQUEST"
18
18
  warn "Google Geocoding API error: invalid request."
19
19
  end
20
+ return []
20
21
  end
21
22
 
22
23
  def query_url(query, reverse = false)
23
24
  params = {
24
25
  (reverse ? :latlng : :address) => query,
25
- :sensor => "false"
26
+ :sensor => "false",
27
+ :language => Geocoder::Configuration.language,
28
+ :key => Geocoder::Configuration.api_key
26
29
  }
27
- "http://maps.google.com/maps/api/geocode/json?" + hash_to_query(params)
30
+ "#{protocol}://maps.google.com/maps/api/geocode/json?" + hash_to_query(params)
28
31
  end
29
32
  end
30
33
  end
@@ -6,12 +6,13 @@ module Geocoder::Lookup
6
6
 
7
7
  private # ---------------------------------------------------------------
8
8
 
9
- def result(query, reverse = false)
10
- doc = fetch_data(query, reverse)
9
+ def results(query, reverse = false)
10
+ return [] unless doc = fetch_data(query, reverse)
11
11
  if doc = doc['ResultSet'] and doc['Error'] == 0
12
- doc['Results'].first if doc['Found'] > 0
12
+ return doc['Found'] > 0 ? doc['Results'] : []
13
13
  else
14
14
  warn "Yahoo Geocoding API error: #{doc['Error']} (#{doc['ErrorMessage']})."
15
+ return []
15
16
  end
16
17
  end
17
18
 
@@ -20,7 +21,8 @@ module Geocoder::Lookup
20
21
  :location => query,
21
22
  :flags => "JXTSR",
22
23
  :gflags => "AC#{'R' if reverse}",
23
- :appid => Geocoder::Configuration.yahoo_appid
24
+ :locale => "#{Geocoder::Configuration.language}_US",
25
+ :appid => Geocoder::Configuration.api_key
24
26
  }
25
27
  "http://where.yahooapis.com/geocode?" + hash_to_query(params)
26
28
  end
@@ -27,9 +27,11 @@ module Geocoder::Orm
27
27
  "OR #{geocoder_options[:longitude]} IS NULL"}}
28
28
 
29
29
  ##
30
- # Find all objects within a radius (in miles) of the given location
31
- # (address string). Location (the first argument) may be either a string
32
- # to geocode or an array of coordinates (<tt>[lat,long]</tt>).
30
+ # Find all objects within a radius of the given location.
31
+ # Location may be either a string to geocode or an array of
32
+ # coordinates (<tt>[lat,lon]</tt>). Also takes an options hash
33
+ # (see Geocoder::Orm::ActiveRecord::ClassMethods.near_scope_options
34
+ # for details).
33
35
  #
34
36
  scope :near, lambda{ |location, *args|
35
37
  latitude, longitude = location.is_a?(Array) ?
@@ -53,16 +55,22 @@ module Geocoder::Orm
53
55
  # records within a radius (in miles) of the given point.
54
56
  # Options hash may include:
55
57
  #
56
- # +units+ :: <tt>:mi</tt> (default) or <tt>:km</tt>
57
- # +exclude+ :: an object to exclude (used by the #nearbys method)
58
- # +order+ :: column(s) for ORDER BY SQL clause
59
- # +limit+ :: number of records to return (for LIMIT SQL clause)
60
- # +offset+ :: number of records to skip (for OFFSET SQL clause)
61
- # +select+ :: string with the SELECT SQL fragment (e.g. “id, name”)
58
+ # * +:units+ - <tt>:mi</tt> (default) or <tt>:km</tt>; to be used
59
+ # for interpreting radius as well as the +distance+ attribute which
60
+ # is added to each found nearby object
61
+ # * +:bearing+ - <tt>:linear</tt> (default) or <tt>:spherical</tt>;
62
+ # the method to be used for calculating the bearing (direction)
63
+ # between the given point and each found nearby point;
64
+ # set to false for no bearing calculation
65
+ # * +:select+ - string with the SELECT SQL fragment (e.g. “id, name”)
66
+ # * +:order+ - column(s) for ORDER BY SQL clause
67
+ # * +:limit+ - number of records to return (for LIMIT SQL clause)
68
+ # * +:offset+ - number of records to skip (for OFFSET SQL clause)
69
+ # * +:exclude+ - an object to exclude (used by the +nearbys+ method)
62
70
  #
63
71
  def near_scope_options(latitude, longitude, radius = 20, options = {})
64
72
  radius *= Geocoder::Calculations.km_in_mi if options[:units] == :km
65
- if ::ActiveRecord::Base.connection.adapter_name == "SQLite"
73
+ if connection.adapter_name.match /sqlite/i
66
74
  approx_near_scope_options(latitude, longitude, radius, options)
67
75
  else
68
76
  full_near_scope_options(latitude, longitude, radius, options)
@@ -74,23 +82,50 @@ module Geocoder::Orm
74
82
 
75
83
  ##
76
84
  # Scope options hash for use with a database that supports POWER(),
77
- # SQRT(), PI(), and trigonometric functions (SIN(), COS(), and ASIN()).
85
+ # SQRT(), PI(), and trigonometric functions SIN(), COS(), ASIN(),
86
+ # ATAN2(), DEGREES(), and RADIANS().
78
87
  #
79
- # Taken from the excellent tutorial at:
88
+ # Distance calculations based on the excellent tutorial at:
80
89
  # http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
81
90
  #
91
+ # Bearing calculation based on:
92
+ # http://www.beginningspatial.com/calculating_bearing_one_point_another
93
+ #
82
94
  def full_near_scope_options(latitude, longitude, radius, options)
83
95
  lat_attr = geocoder_options[:latitude]
84
96
  lon_attr = geocoder_options[:longitude]
85
- distance = "3956 * 2 * ASIN(SQRT(" +
86
- "POWER(SIN((#{latitude} - #{lat_attr}) * " +
87
- "PI() / 180 / 2), 2) + COS(#{latitude} * PI()/180) * " +
88
- "COS(#{lat_attr} * PI() / 180) * " +
89
- "POWER(SIN((#{longitude} - #{lon_attr}) * " +
90
- "PI() / 180 / 2), 2) ))"
97
+ options[:bearing] = :linear unless options.include?(:bearing)
98
+ bearing = case options[:bearing]
99
+ when :linear
100
+ "CAST(" +
101
+ "DEGREES(ATAN2( " +
102
+ "RADIANS(#{lon_attr} - #{longitude}), " +
103
+ "RADIANS(#{lat_attr} - #{latitude})" +
104
+ ")) + 360 " +
105
+ "AS decimal) % 360"
106
+ when :spherical
107
+ "CAST(" +
108
+ "DEGREES(ATAN2( " +
109
+ "SIN(RADIANS(#{lon_attr} - #{longitude})) * " +
110
+ "COS(RADIANS(#{lat_attr})), (" +
111
+ "COS(RADIANS(#{latitude})) * SIN(RADIANS(#{lat_attr}))" +
112
+ ") - (" +
113
+ "SIN(RADIANS(#{latitude})) * COS(RADIANS(#{lat_attr})) * " +
114
+ "COS(RADIANS(#{lon_attr} - #{longitude}))" +
115
+ ")" +
116
+ ")) + 360 " +
117
+ "AS decimal) % 360"
118
+ end
119
+ earth = Geocoder::Calculations.earth_radius(options[:units] || :mi)
120
+ distance = "#{earth} * 2 * ASIN(SQRT(" +
121
+ "POWER(SIN((#{latitude} - #{lat_attr}) * PI() / 180 / 2), 2) + " +
122
+ "COS(#{latitude} * PI() / 180) * COS(#{lat_attr} * PI() / 180) * " +
123
+ "POWER(SIN((#{longitude} - #{lon_attr}) * PI() / 180 / 2), 2) ))"
91
124
  options[:order] ||= "#{distance} ASC"
92
125
  default_near_scope_options(latitude, longitude, radius, options).merge(
93
- :select => "#{options[:select] || '*'}, #{distance} AS distance",
126
+ :select => "#{options[:select] || '*'}, " +
127
+ "#{distance} AS distance" +
128
+ (bearing ? ", #{bearing} AS bearing" : ""),
94
129
  :having => "#{distance} <= #{radius}"
95
130
  )
96
131
  end
@@ -101,9 +136,33 @@ module Geocoder::Orm
101
136
  # rather than a circle, so results are very approximate (will include
102
137
  # objects outside the given radius).
103
138
  #
139
+ # Distance and bearing calculations are *extremely inaccurate*. They
140
+ # only exist for interface consistency--not intended for production!
141
+ #
104
142
  def approx_near_scope_options(latitude, longitude, radius, options)
143
+ lat_attr = geocoder_options[:latitude]
144
+ lon_attr = geocoder_options[:longitude]
145
+ options[:bearing] = :linear unless options.include?(:bearing)
146
+ if options[:bearing]
147
+ bearing = "CASE " +
148
+ "WHEN (#{lat_attr} >= #{latitude} AND #{lon_attr} >= #{longitude}) THEN 45.0 " +
149
+ "WHEN (#{lat_attr} < #{latitude} AND #{lon_attr} >= #{longitude}) THEN 135.0 " +
150
+ "WHEN (#{lat_attr} < #{latitude} AND #{lon_attr} < #{longitude}) THEN 225.0 " +
151
+ "WHEN (#{lat_attr} >= #{latitude} AND #{lon_attr} < #{longitude}) THEN 315.0 " +
152
+ "END"
153
+ else
154
+ bearing = false
155
+ end
156
+
157
+ dx = Geocoder::Calculations.longitude_degree_distance(30, options[:units] || :mi)
158
+ dy = Geocoder::Calculations.latitude_degree_distance(options[:units] || :mi)
159
+
160
+ distance = "(#{dy} * ABS(#{lat_attr} - #{latitude}) / 2) + " +
161
+ "(#{dx} * ABS(#{lon_attr} - #{longitude}) / 2)"
105
162
  default_near_scope_options(latitude, longitude, radius, options).merge(
106
- :select => options[:select] || nil
163
+ :select => "#{options[:select] || '*'}, " +
164
+ "#{distance} AS distance" +
165
+ (bearing ? ", #{bearing} AS bearing" : "")
107
166
  )
108
167
  end
109
168
 
@@ -113,9 +172,10 @@ module Geocoder::Orm
113
172
  def default_near_scope_options(latitude, longitude, radius, options)
114
173
  lat_attr = geocoder_options[:latitude]
115
174
  lon_attr = geocoder_options[:longitude]
175
+ b = Geocoder::Calculations.bounding_box(latitude, longitude, radius, options)
116
176
  conditions = \
117
177
  ["#{lat_attr} BETWEEN ? AND ? AND #{lon_attr} BETWEEN ? AND ?"] +
118
- coordinate_bounds(latitude, longitude, radius)
178
+ [b[0], b[2], b[1], b[3]]
119
179
  if obj = options[:exclude]
120
180
  conditions[0] << " AND #{table_name}.id != ?"
121
181
  conditions << obj.id
@@ -128,22 +188,6 @@ module Geocoder::Orm
128
188
  :conditions => conditions
129
189
  }
130
190
  end
131
-
132
- ##
133
- # Get the rough high/low lat/long bounds for a geographic point and
134
- # radius. Returns an array: <tt>[lat_lo, lat_hi, lon_lo, lon_hi]</tt>.
135
- # Used to constrain search to a (radius x radius) square.
136
- #
137
- def coordinate_bounds(latitude, longitude, radius)
138
- radius = radius.to_f
139
- factor = (Math::cos(latitude * Math::PI / 180.0) * 69.0).abs
140
- [
141
- latitude - (radius / 69.0),
142
- latitude + (radius / 69.0),
143
- longitude - (radius / factor),
144
- longitude + (radius / factor)
145
- ]
146
- end
147
191
  end
148
192
 
149
193
  ##
@@ -151,7 +195,8 @@ module Geocoder::Orm
151
195
  # (or other as specified in +geocoded_by+). Returns coordinates (array).
152
196
  #
153
197
  def geocode
154
- do_lookup(false) do |o,r|
198
+ do_lookup(false) do |o,rs|
199
+ r = rs.first
155
200
  unless r.latitude.nil? or r.longitude.nil?
156
201
  o.send :write_attribute, self.class.geocoder_options[:latitude], r.latitude
157
202
  o.send :write_attribute, self.class.geocoder_options[:longitude], r.longitude
@@ -167,7 +212,8 @@ module Geocoder::Orm
167
212
  # in +reverse_geocoded_by+). Returns address (string).
168
213
  #
169
214
  def reverse_geocode
170
- do_lookup(true) do |o,r|
215
+ do_lookup(true) do |o,rs|
216
+ r = rs.first
171
217
  unless r.address.nil?
172
218
  o.send :write_attribute, self.class.geocoder_options[:fetched_address], r.address
173
219
  end
@@ -7,7 +7,8 @@ module Geocoder::Orm::ActiveRecord
7
7
  def fetch_coordinates!
8
8
  warn "DEPRECATION WARNING: The 'fetch_coordinates!' method is deprecated and will be removed in geocoder v1.0. " +
9
9
  "Please use 'geocode' instead and then save your objects manually."
10
- do_lookup(false) do |o,r|
10
+ do_lookup(false) do |o,rs|
11
+ r = rs.first
11
12
  unless r.latitude.nil? or r.longitude.nil?
12
13
  o.send :update_attribute, self.class.geocoder_options[:latitude], r.latitude
13
14
  o.send :update_attribute, self.class.geocoder_options[:longitude], r.longitude
@@ -19,7 +20,8 @@ module Geocoder::Orm::ActiveRecord
19
20
  def fetch_coordinates(*args)
20
21
  warn "DEPRECATION WARNING: The 'fetch_coordinates' method will cease taking " +
21
22
  "an argument in geocoder v1.0. Please save your objects manually." if args.size > 0
22
- do_lookup(false) do |o,r|
23
+ do_lookup(false) do |o,rs|
24
+ r = rs.first
23
25
  unless r.latitude.nil? or r.longitude.nil?
24
26
  method = ((args.size > 0 && args.first) ? "update" : "write" ) + "_attribute"
25
27
  o.send method, self.class.geocoder_options[:latitude], r.latitude
@@ -35,7 +37,8 @@ module Geocoder::Orm::ActiveRecord
35
37
  def fetch_address!
36
38
  warn "DEPRECATION WARNING: The 'fetch_address!' method is deprecated and will be removed in geocoder v1.0. " +
37
39
  "Please use 'reverse_geocode' instead and then save your objects manually."
38
- do_lookup(true) do |o,r|
40
+ do_lookup(true) do |o,rs|
41
+ r = rs.first
39
42
  unless r.address.nil?
40
43
  o.send :update_attribute, self.class.geocoder_options[:fetched_address], r.address
41
44
  end
@@ -46,7 +49,8 @@ module Geocoder::Orm::ActiveRecord
46
49
  def fetch_address(*args)
47
50
  warn "DEPRECATION WARNING: The 'fetch_address' method will cease taking " +
48
51
  "an argument in geocoder v1.0. Please save your objects manually." if args.size > 0
49
- do_lookup(true) do |o,r|
52
+ do_lookup(true) do |o,rs|
53
+ r = rs.first
50
54
  unless r.latitude.nil? or r.longitude.nil?
51
55
  method = ((args.size > 0 && args.first) ? "update" : "write" ) + "_attribute"
52
56
  o.send method, self.class.geocoder_options[:fetched_address], r.address
@@ -6,7 +6,14 @@ module Geocoder
6
6
  # Is this object geocoded? (Does it have latitude and longitude?)
7
7
  #
8
8
  def geocoded?
9
- read_coordinates.compact.size > 0
9
+ to_coordinates.compact.size > 0
10
+ end
11
+
12
+ ##
13
+ # Coordinates [lat,lon] of the object.
14
+ #
15
+ def to_coordinates
16
+ [:latitude, :longitude].map{ |i| send self.class.geocoder_options[i] }
10
17
  end
11
18
 
12
19
  ##
@@ -16,20 +23,24 @@ module Geocoder
16
23
  #
17
24
  def distance_to(lat, lon, units = :mi)
18
25
  return nil unless geocoded?
19
- mylat,mylon = read_coordinates
26
+ mylat,mylon = to_coordinates
20
27
  Geocoder::Calculations.distance_between(mylat, mylon, lat, lon, :units => units)
21
28
  end
22
29
 
23
30
  alias_method :distance_from, :distance_to
24
31
 
25
32
  ##
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).
33
+ # Get nearby geocoded objects.
34
+ # Takes the same options hash as the near class method (scope).
28
35
  #
29
- def nearbys(radius = 20, units = :mi)
36
+ def nearbys(radius = 20, options = {})
30
37
  return [] unless geocoded?
31
- options = {:exclude => self, :units => units}
32
- self.class.near(read_coordinates, radius, options)
38
+ if options.is_a?(Symbol)
39
+ options = {:units => options}
40
+ warn "DEPRECATION WARNING: The units argument to the nearbys method has been replaced with an options hash (same options hash as the near scope). You should instead call: obj.nearbys(#{radius}, :units => #{options[:units]}). The old syntax will not be supported in Geocoder v1.0."
41
+ end
42
+ options.merge!(:exclude => self)
43
+ self.class.near(to_coordinates, radius, options)
33
44
  end
34
45
 
35
46
  ##
@@ -53,10 +64,10 @@ module Geocoder
53
64
 
54
65
  ##
55
66
  # Look up geographic data based on object attributes (configured in
56
- # geocoded_by or reverse_geocoded_by) and handle the result with the
67
+ # geocoded_by or reverse_geocoded_by) and handle the results with the
57
68
  # 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).
69
+ # given two-arguments: the object being geocoded and an array of
70
+ # Geocoder::Result objects).
60
71
  #
61
72
  def do_lookup(reverse = false)
62
73
  options = self.class.geocoder_options
@@ -69,28 +80,20 @@ module Geocoder
69
80
  end
70
81
  args.map!{ |a| send(options[a]) }
71
82
 
72
- if result = Geocoder.search(*args)
83
+ if (results = Geocoder.search(*args)).size > 0
73
84
 
74
85
  # execute custom block, if specified in configuration
75
86
  block_key = reverse ? :reverse_block : :geocode_block
76
87
  if custom_block = options[block_key]
77
- custom_block.call(self, result)
88
+ custom_block.call(self, results)
78
89
 
79
90
  # else execute block passed directly to this method,
80
91
  # which generally performs the "auto-assigns"
81
92
  elsif block_given?
82
- yield(self, result)
93
+ yield(self, results)
83
94
  end
84
95
  end
85
96
  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
97
  end
95
98
  end
96
99
  end
@@ -5,7 +5,7 @@ module Geocoder
5
5
 
6
6
  def location
7
7
  unless defined?(@location)
8
- @location = Geocoder.search(ip)
8
+ @location = Geocoder.search(ip).first
9
9
  end
10
10
  @location
11
11
  end
@@ -39,20 +39,6 @@ module Geocoder
39
39
  def country_code
40
40
  fail
41
41
  end
42
-
43
- def [](i)
44
- if i == 0
45
- warn "DEPRECATION WARNING: You called '[0]' on a Geocoder::Result object. Geocoder.search(...) now returns a single result instead of an array so this is no longer necessary. This warning will be removed and an error will result in geocoder 1.0."
46
- elsif i.is_a?(Fixnum)
47
- warn "DEPRECATION WARNING: You tried to access a Geocoder result but Geocoder.search(...) now returns a single result instead of an array. This warning will be removed and an error will result in geocoder 1.0."
48
- end
49
- self
50
- end
51
-
52
- def first
53
- warn "DEPRECATION WARNING: You called '.first' on a Geocoder::Result object. Geocoder.search(...) now returns a single result instead of an array so this is no longer necessary. This warning will be removed and an error will result in geocoder 1.0."
54
- self
55
- end
56
42
  end
57
43
  end
58
44
  end
@@ -0,0 +1,58 @@
1
+ require 'geocoder/results/base'
2
+
3
+ module Geocoder::Result
4
+ class GeocoderCa < Base
5
+
6
+ def coordinates
7
+ [@data['latt'].to_f, @data['longt'].to_f]
8
+ end
9
+
10
+ def address(format = :full)
11
+ "#{street_address}, #{city}, #{state} #{postal_code}, #{country}"
12
+ end
13
+
14
+ def street_address
15
+ "#{@data['stnumber']} #{@data['staddress']}"
16
+ end
17
+
18
+ def city
19
+ @data['city']
20
+ end
21
+
22
+ def state
23
+ @data['prov']
24
+ end
25
+
26
+ alias_method :province, :state
27
+
28
+ def postal_code
29
+ @data['postal']
30
+ end
31
+
32
+ def country
33
+ country_code == 'CA' ? 'Canada' : 'United States'
34
+ end
35
+
36
+ def country_code
37
+ prov = @data['prov']
38
+ return nil if prov.nil? || prov == ""
39
+ canadian_province_abbreviations.include?(@data['prov']) ? "CA" : "US"
40
+ end
41
+
42
+ def canadian_province_abbreviations
43
+ %w[ON QC NS NB MB BC PE SK AB NL]
44
+ end
45
+
46
+ def self.response_attributes
47
+ %w[latt longt inlatt inlongt betweenRoad1 betweenRoad2 distance
48
+ stnumber staddress city prov postal
49
+ NearRoad NearRoadDistance intersection major_intersection]
50
+ end
51
+
52
+ response_attributes.each do |a|
53
+ define_method a do
54
+ @data[a]
55
+ end
56
+ end
57
+ end
58
+ end