geocoder2 0.1.0

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 (146) hide show
  1. data/.gitignore +5 -0
  2. data/.travis.yml +27 -0
  3. data/CHANGELOG.md +329 -0
  4. data/LICENSE +20 -0
  5. data/README.md +796 -0
  6. data/Rakefile +25 -0
  7. data/bin/geocode2 +5 -0
  8. data/examples/autoexpire_cache_dalli.rb +62 -0
  9. data/examples/autoexpire_cache_redis.rb +28 -0
  10. data/examples/cache_bypass.rb +48 -0
  11. data/gemfiles/Gemfile.mongoid-2.4.x +15 -0
  12. data/json?address=26+leonard+street%2C+Belmont&key=AIzaSyDoltU6YL8XeIQrSLFGk6ZfpKaWkPukwYQ&language=en +68 -0
  13. data/lib/generators/geocoder2/config/config_generator.rb +14 -0
  14. data/lib/generators/geocoder2/config/templates/initializer.rb +21 -0
  15. data/lib/geocoder2/cache.rb +89 -0
  16. data/lib/geocoder2/calculations.rb +389 -0
  17. data/lib/geocoder2/cli.rb +121 -0
  18. data/lib/geocoder2/configuration.rb +130 -0
  19. data/lib/geocoder2/configuration_hash.rb +11 -0
  20. data/lib/geocoder2/exceptions.rb +21 -0
  21. data/lib/geocoder2/lookup.rb +86 -0
  22. data/lib/geocoder2/lookups/baidu.rb +54 -0
  23. data/lib/geocoder2/lookups/base.rb +266 -0
  24. data/lib/geocoder2/lookups/bing.rb +47 -0
  25. data/lib/geocoder2/lookups/dstk.rb +20 -0
  26. data/lib/geocoder2/lookups/esri.rb +48 -0
  27. data/lib/geocoder2/lookups/freegeoip.rb +43 -0
  28. data/lib/geocoder2/lookups/geocoder_ca.rb +54 -0
  29. data/lib/geocoder2/lookups/geocoder_us.rb +39 -0
  30. data/lib/geocoder2/lookups/google.rb +69 -0
  31. data/lib/geocoder2/lookups/google_premier.rb +47 -0
  32. data/lib/geocoder2/lookups/mapquest.rb +59 -0
  33. data/lib/geocoder2/lookups/maxmind.rb +88 -0
  34. data/lib/geocoder2/lookups/nominatim.rb +44 -0
  35. data/lib/geocoder2/lookups/ovi.rb +62 -0
  36. data/lib/geocoder2/lookups/test.rb +44 -0
  37. data/lib/geocoder2/lookups/yahoo.rb +86 -0
  38. data/lib/geocoder2/lookups/yandex.rb +54 -0
  39. data/lib/geocoder2/models/active_record.rb +46 -0
  40. data/lib/geocoder2/models/base.rb +42 -0
  41. data/lib/geocoder2/models/mongo_base.rb +60 -0
  42. data/lib/geocoder2/models/mongo_mapper.rb +26 -0
  43. data/lib/geocoder2/models/mongoid.rb +32 -0
  44. data/lib/geocoder2/query.rb +107 -0
  45. data/lib/geocoder2/railtie.rb +26 -0
  46. data/lib/geocoder2/request.rb +23 -0
  47. data/lib/geocoder2/results/baidu.rb +79 -0
  48. data/lib/geocoder2/results/base.rb +67 -0
  49. data/lib/geocoder2/results/bing.rb +48 -0
  50. data/lib/geocoder2/results/dstk.rb +6 -0
  51. data/lib/geocoder2/results/esri.rb +51 -0
  52. data/lib/geocoder2/results/freegeoip.rb +45 -0
  53. data/lib/geocoder2/results/geocoder_ca.rb +60 -0
  54. data/lib/geocoder2/results/geocoder_us.rb +39 -0
  55. data/lib/geocoder2/results/google.rb +124 -0
  56. data/lib/geocoder2/results/google_premier.rb +6 -0
  57. data/lib/geocoder2/results/mapquest.rb +51 -0
  58. data/lib/geocoder2/results/maxmind.rb +135 -0
  59. data/lib/geocoder2/results/nominatim.rb +94 -0
  60. data/lib/geocoder2/results/ovi.rb +62 -0
  61. data/lib/geocoder2/results/test.rb +16 -0
  62. data/lib/geocoder2/results/yahoo.rb +55 -0
  63. data/lib/geocoder2/results/yandex.rb +80 -0
  64. data/lib/geocoder2/sql.rb +106 -0
  65. data/lib/geocoder2/stores/active_record.rb +272 -0
  66. data/lib/geocoder2/stores/base.rb +120 -0
  67. data/lib/geocoder2/stores/mongo_base.rb +89 -0
  68. data/lib/geocoder2/stores/mongo_mapper.rb +13 -0
  69. data/lib/geocoder2/stores/mongoid.rb +13 -0
  70. data/lib/geocoder2/version.rb +3 -0
  71. data/lib/geocoder2.rb +55 -0
  72. data/lib/hash_recursive_merge.rb +74 -0
  73. data/lib/oauth_util.rb +112 -0
  74. data/lib/tasks/geocoder2.rake +27 -0
  75. data/test/active_record_test.rb +15 -0
  76. data/test/cache_test.rb +35 -0
  77. data/test/calculations_test.rb +211 -0
  78. data/test/configuration_test.rb +78 -0
  79. data/test/custom_block_test.rb +32 -0
  80. data/test/error_handling_test.rb +43 -0
  81. data/test/fixtures/baidu_invalid_key +1 -0
  82. data/test/fixtures/baidu_no_results +1 -0
  83. data/test/fixtures/baidu_reverse +1 -0
  84. data/test/fixtures/baidu_shanghai_pearl_tower +12 -0
  85. data/test/fixtures/bing_invalid_key +1 -0
  86. data/test/fixtures/bing_madison_square_garden +40 -0
  87. data/test/fixtures/bing_no_results +16 -0
  88. data/test/fixtures/bing_reverse +42 -0
  89. data/test/fixtures/esri_madison_square_garden +59 -0
  90. data/test/fixtures/esri_no_results +8 -0
  91. data/test/fixtures/esri_reverse +21 -0
  92. data/test/fixtures/freegeoip_74_200_247_59 +12 -0
  93. data/test/fixtures/freegeoip_no_results +1 -0
  94. data/test/fixtures/geocoder_ca_madison_square_garden +1 -0
  95. data/test/fixtures/geocoder_ca_no_results +1 -0
  96. data/test/fixtures/geocoder_ca_reverse +34 -0
  97. data/test/fixtures/geocoder_us_madison_square_garden +1 -0
  98. data/test/fixtures/geocoder_us_no_results +1 -0
  99. data/test/fixtures/google_garbage +456 -0
  100. data/test/fixtures/google_madison_square_garden +57 -0
  101. data/test/fixtures/google_no_city_data +44 -0
  102. data/test/fixtures/google_no_locality +51 -0
  103. data/test/fixtures/google_no_results +4 -0
  104. data/test/fixtures/google_over_limit +4 -0
  105. data/test/fixtures/mapquest_error +16 -0
  106. data/test/fixtures/mapquest_invalid_api_key +16 -0
  107. data/test/fixtures/mapquest_invalid_request +16 -0
  108. data/test/fixtures/mapquest_madison_square_garden +52 -0
  109. data/test/fixtures/mapquest_no_results +16 -0
  110. data/test/fixtures/maxmind_24_24_24_21 +1 -0
  111. data/test/fixtures/maxmind_24_24_24_22 +1 -0
  112. data/test/fixtures/maxmind_24_24_24_23 +1 -0
  113. data/test/fixtures/maxmind_24_24_24_24 +1 -0
  114. data/test/fixtures/maxmind_74_200_247_59 +1 -0
  115. data/test/fixtures/maxmind_invalid_key +1 -0
  116. data/test/fixtures/maxmind_no_results +1 -0
  117. data/test/fixtures/nominatim_madison_square_garden +150 -0
  118. data/test/fixtures/nominatim_no_results +1 -0
  119. data/test/fixtures/ovi_madison_square_garden +72 -0
  120. data/test/fixtures/ovi_no_results +8 -0
  121. data/test/fixtures/yahoo_error +1 -0
  122. data/test/fixtures/yahoo_invalid_key +2 -0
  123. data/test/fixtures/yahoo_madison_square_garden +52 -0
  124. data/test/fixtures/yahoo_no_results +10 -0
  125. data/test/fixtures/yahoo_over_limit +2 -0
  126. data/test/fixtures/yandex_invalid_key +1 -0
  127. data/test/fixtures/yandex_kremlin +48 -0
  128. data/test/fixtures/yandex_no_city_and_town +112 -0
  129. data/test/fixtures/yandex_no_results +16 -0
  130. data/test/geocoder_test.rb +59 -0
  131. data/test/https_test.rb +16 -0
  132. data/test/integration/smoke_test.rb +26 -0
  133. data/test/lookup_test.rb +117 -0
  134. data/test/method_aliases_test.rb +25 -0
  135. data/test/mongoid_test.rb +46 -0
  136. data/test/mongoid_test_helper.rb +43 -0
  137. data/test/near_test.rb +61 -0
  138. data/test/oauth_util_test.rb +30 -0
  139. data/test/proxy_test.rb +36 -0
  140. data/test/query_test.rb +52 -0
  141. data/test/request_test.rb +29 -0
  142. data/test/result_test.rb +42 -0
  143. data/test/services_test.rb +393 -0
  144. data/test/test_helper.rb +289 -0
  145. data/test/test_mode_test.rb +59 -0
  146. metadata +213 -0
@@ -0,0 +1,106 @@
1
+ module Geocoder2
2
+ module Sql
3
+ extend self
4
+
5
+ ##
6
+ # Distance calculation for use with a database that supports POWER(),
7
+ # SQRT(), PI(), and trigonometric functions SIN(), COS(), ASIN(),
8
+ # ATAN2(), DEGREES(), and RADIANS().
9
+ #
10
+ # Based on the excellent tutorial at:
11
+ # http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
12
+ #
13
+ def full_distance(latitude, longitude, lat_attr, lon_attr, options = {})
14
+ units = options[:units] || Geocoder2.config.units
15
+ earth = Geocoder2::Calculations.earth_radius(units)
16
+
17
+ "#{earth} * 2 * ASIN(SQRT(" +
18
+ "POWER(SIN((#{latitude.to_f} - #{lat_attr}) * PI() / 180 / 2), 2) + " +
19
+ "COS(#{latitude.to_f} * PI() / 180) * COS(#{lat_attr} * PI() / 180) * " +
20
+ "POWER(SIN((#{longitude.to_f} - #{lon_attr}) * PI() / 180 / 2), 2)" +
21
+ "))"
22
+ end
23
+
24
+ ##
25
+ # Distance calculation for use with a database without trigonometric
26
+ # functions, like SQLite. Approach is to find objects within a square
27
+ # rather than a circle, so results are very approximate (will include
28
+ # objects outside the given radius).
29
+ #
30
+ # Distance and bearing calculations are *extremely inaccurate*. To be
31
+ # clear: this only exists to provide interface consistency. Results
32
+ # are not intended for use in production!
33
+ #
34
+ def approx_distance(latitude, longitude, lat_attr, lon_attr, options = {})
35
+ units = options[:units] || Geocoder2.config.units
36
+ dx = Geocoder2::Calculations.longitude_degree_distance(30, units)
37
+ dy = Geocoder2::Calculations.latitude_degree_distance(units)
38
+
39
+ # sin of 45 degrees = average x or y component of vector
40
+ factor = Math.sin(Math::PI / 4)
41
+
42
+ "(#{dy} * ABS(#{lat_attr} - #{latitude.to_f}) * #{factor}) + " +
43
+ "(#{dx} * ABS(#{lon_attr} - #{longitude.to_f}) * #{factor})"
44
+ end
45
+
46
+ def within_bounding_box(sw_lat, sw_lng, ne_lat, ne_lng, lat_attr, lon_attr)
47
+ spans = "#{lat_attr} BETWEEN #{sw_lat} AND #{ne_lat} AND "
48
+ # handle box that spans 180 longitude
49
+ if sw_lng.to_f > ne_lng.to_f
50
+ spans + "#{lon_attr} BETWEEN #{sw_lng} AND 180 OR " +
51
+ "#{lon_attr} BETWEEN -180 AND #{ne_lng}"
52
+ else
53
+ spans + "#{lon_attr} BETWEEN #{sw_lng} AND #{ne_lng}"
54
+ end
55
+ end
56
+
57
+ ##
58
+ # Fairly accurate bearing calculation. Takes a latitude, longitude,
59
+ # and an options hash which must include a :bearing value
60
+ # (:linear or :spherical).
61
+ #
62
+ # Based on:
63
+ # http://www.beginningspatial.com/calculating_bearing_one_point_another
64
+ #
65
+ def full_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
66
+ case options[:bearing] || Geocoder2.config.distances
67
+ when :linear
68
+ "CAST(" +
69
+ "DEGREES(ATAN2( " +
70
+ "RADIANS(#{lon_attr} - #{longitude.to_f}), " +
71
+ "RADIANS(#{lat_attr} - #{latitude.to_f})" +
72
+ ")) + 360 " +
73
+ "AS decimal) % 360"
74
+ when :spherical
75
+ "CAST(" +
76
+ "DEGREES(ATAN2( " +
77
+ "SIN(RADIANS(#{lon_attr} - #{longitude.to_f})) * " +
78
+ "COS(RADIANS(#{lat_attr})), (" +
79
+ "COS(RADIANS(#{latitude.to_f})) * SIN(RADIANS(#{lat_attr}))" +
80
+ ") - (" +
81
+ "SIN(RADIANS(#{latitude.to_f})) * COS(RADIANS(#{lat_attr})) * " +
82
+ "COS(RADIANS(#{lon_attr} - #{longitude.to_f}))" +
83
+ ")" +
84
+ ")) + 360 " +
85
+ "AS decimal) % 360"
86
+ end
87
+ end
88
+
89
+ ##
90
+ # Totally lame bearing calculation. Basically useless except that it
91
+ # returns *something* in databases without trig functions.
92
+ #
93
+ def approx_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
94
+ "CASE " +
95
+ "WHEN (#{lat_attr} >= #{latitude.to_f} AND " +
96
+ "#{lon_attr} >= #{longitude.to_f}) THEN 45.0 " +
97
+ "WHEN (#{lat_attr} < #{latitude.to_f} AND " +
98
+ "#{lon_attr} >= #{longitude.to_f}) THEN 135.0 " +
99
+ "WHEN (#{lat_attr} < #{latitude.to_f} AND " +
100
+ "#{lon_attr} < #{longitude.to_f}) THEN 225.0 " +
101
+ "WHEN (#{lat_attr} >= #{latitude.to_f} AND " +
102
+ "#{lon_attr} < #{longitude.to_f}) THEN 315.0 " +
103
+ "END"
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,272 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'geocoder2/sql'
3
+ require 'geocoder2/stores/base'
4
+
5
+ ##
6
+ # Add geocoding functionality to any ActiveRecord object.
7
+ #
8
+ module Geocoder2::Store
9
+ module ActiveRecord
10
+ include Base
11
+
12
+ ##
13
+ # Implementation of 'included' hook method.
14
+ #
15
+ def self.included(base)
16
+ base.extend ClassMethods
17
+ base.class_eval do
18
+
19
+ # scope: geocoded objects
20
+ scope :geocoded, lambda {
21
+ where("#{geocoder2_options[:latitude]} IS NOT NULL " +
22
+ "AND #{geocoder2_options[:longitude]} IS NOT NULL")
23
+ }
24
+
25
+ # scope: not-geocoded objects
26
+ scope :not_geocoded, lambda {
27
+ where("#{geocoder2_options[:latitude]} IS NULL " +
28
+ "OR #{geocoder2_options[:longitude]} IS NULL")
29
+ }
30
+
31
+ ##
32
+ # Find all objects within a radius of the given location.
33
+ # Location may be either a string to geocode or an array of
34
+ # coordinates (<tt>[lat,lon]</tt>). Also takes an options hash
35
+ # (see Geocoder2::Store::ActiveRecord::ClassMethods.near_scope_options
36
+ # for details).
37
+ #
38
+ scope :near, lambda{ |location, *args|
39
+ latitude, longitude = Geocoder2::Calculations.extract_coordinates(location)
40
+ if Geocoder2::Calculations.coordinates_present?(latitude, longitude)
41
+ options = near_scope_options(latitude, longitude, *args)
42
+ select(options[:select]).where(options[:conditions]).
43
+ order(options[:order])
44
+ else
45
+ # If no lat/lon given we don't want any results, but we still
46
+ # need distance and bearing columns so you can add, for example:
47
+ # .order("distance")
48
+ select(select_clause(nil, "NULL", "NULL")).where(false_condition)
49
+ end
50
+ }
51
+
52
+ ##
53
+ # Find all objects within the area of a given bounding box.
54
+ # Bounds must be an array of locations specifying the southwest
55
+ # corner followed by the northeast corner of the box
56
+ # (<tt>[[sw_lat, sw_lon], [ne_lat, ne_lon]]</tt>).
57
+ #
58
+ scope :within_bounding_box, lambda{ |bounds|
59
+ sw_lat, sw_lng, ne_lat, ne_lng = bounds.flatten if bounds
60
+ if sw_lat && sw_lng && ne_lat && ne_lng
61
+ where(Geocoder2::Sql.within_bounding_box(
62
+ sw_lat, sw_lng, ne_lat, ne_lng,
63
+ full_column_name(geocoder2_options[:latitude]),
64
+ full_column_name(geocoder2_options[:longitude])
65
+ ))
66
+ else
67
+ select(select_clause(nil, "NULL", "NULL")).where(false_condition)
68
+ end
69
+ }
70
+ end
71
+ end
72
+
73
+ ##
74
+ # Methods which will be class methods of the including class.
75
+ #
76
+ module ClassMethods
77
+
78
+ def distance_from_sql(location, *args)
79
+ latitude, longitude = Geocoder2::Calculations.extract_coordinates(location)
80
+ if Geocoder2::Calculations.coordinates_present?(latitude, longitude)
81
+ distance_sql(latitude, longitude, *args)
82
+ end
83
+ end
84
+
85
+ private # ----------------------------------------------------------------
86
+
87
+ ##
88
+ # Get options hash suitable for passing to ActiveRecord.find to get
89
+ # records within a radius (in kilometers) of the given point.
90
+ # Options hash may include:
91
+ #
92
+ # * +:units+ - <tt>:mi</tt> or <tt>:km</tt>; to be used.
93
+ # for interpreting radius as well as the +distance+ attribute which
94
+ # is added to each found nearby object.
95
+ # Use Geocoder2.configure[:units] to configure default units.
96
+ # * +:bearing+ - <tt>:linear</tt> or <tt>:spherical</tt>.
97
+ # the method to be used for calculating the bearing (direction)
98
+ # between the given point and each found nearby point;
99
+ # set to false for no bearing calculation. Use
100
+ # Geocoder2.configure[:distances] to configure default calculation method.
101
+ # * +:select+ - string with the SELECT SQL fragment (e.g. “id, name”)
102
+ # * +:select_distance+ - whether to include the distance alias in the
103
+ # SELECT SQL fragment (e.g. <formula> AS distance)
104
+ # * +:select_bearing+ - like +:select_distance+ but for bearing.
105
+ # * +:order+ - column(s) for ORDER BY SQL clause; default is distance;
106
+ # set to false or nil to omit the ORDER BY clause
107
+ # * +:exclude+ - an object to exclude (used by the +nearbys+ method)
108
+ # * +:distance_column+ - used to set the column name of the calculated distance.
109
+ # * +:bearing_column+ - used to set the column name of the calculated bearing.
110
+ #
111
+ def near_scope_options(latitude, longitude, radius = 20, options = {})
112
+ if options[:units]
113
+ options[:units] = options[:units].to_sym
114
+ end
115
+ options[:units] ||= (geocoder2_options[:units] || Geocoder2.config.units)
116
+ select_distance = options.fetch(:select_distance, true)
117
+ options[:order] = "" if !select_distance && !options.include?(:order)
118
+ select_bearing = options.fetch(:select_bearing, true)
119
+ bearing = bearing_sql(latitude, longitude, options)
120
+ distance = distance_sql(latitude, longitude, options)
121
+ distance_column = options.fetch(:distance_column, 'distance')
122
+ bearing_column = options.fetch(:bearing_column, 'bearing')
123
+
124
+ b = Geocoder2::Calculations.bounding_box([latitude, longitude], radius, options)
125
+ args = b + [
126
+ full_column_name(geocoder2_options[:latitude]),
127
+ full_column_name(geocoder2_options[:longitude])
128
+ ]
129
+ bounding_box_conditions = Geocoder2::Sql.within_bounding_box(*args)
130
+
131
+ if using_sqlite?
132
+ conditions = bounding_box_conditions
133
+ else
134
+ conditions = [bounding_box_conditions + " AND #{distance} <= ?", radius]
135
+ end
136
+ {
137
+ :select => select_clause(options[:select],
138
+ select_distance ? distance : nil,
139
+ select_bearing ? bearing : nil,
140
+ distance_column,
141
+ bearing_column),
142
+ :conditions => add_exclude_condition(conditions, options[:exclude]),
143
+ :order => options.include?(:order) ? options[:order] : "#{distance_column} ASC"
144
+ }
145
+ end
146
+
147
+ ##
148
+ # SQL for calculating distance based on the current database's
149
+ # capabilities (trig functions?).
150
+ #
151
+ def distance_sql(latitude, longitude, options = {})
152
+ method_prefix = using_sqlite? ? "approx" : "full"
153
+ Geocoder2::Sql.send(
154
+ method_prefix + "_distance",
155
+ latitude, longitude,
156
+ full_column_name(geocoder2_options[:latitude]),
157
+ full_column_name(geocoder2_options[:longitude]),
158
+ options
159
+ )
160
+ end
161
+
162
+ ##
163
+ # SQL for calculating bearing based on the current database's
164
+ # capabilities (trig functions?).
165
+ #
166
+ def bearing_sql(latitude, longitude, options = {})
167
+ if !options.include?(:bearing)
168
+ options[:bearing] = Geocoder2.config.distances
169
+ end
170
+ if options[:bearing]
171
+ method_prefix = using_sqlite? ? "approx" : "full"
172
+ Geocoder2::Sql.send(
173
+ method_prefix + "_bearing",
174
+ latitude, longitude,
175
+ full_column_name(geocoder2_options[:latitude]),
176
+ full_column_name(geocoder2_options[:longitude]),
177
+ options
178
+ )
179
+ end
180
+ end
181
+
182
+ ##
183
+ # Generate the SELECT clause.
184
+ #
185
+ def select_clause(columns, distance = nil, bearing = nil, distance_column = 'distance', bearing_column = 'bearing')
186
+ if columns == :id_only
187
+ return full_column_name(primary_key)
188
+ elsif columns == :geo_only
189
+ clause = ""
190
+ else
191
+ clause = (columns || full_column_name("*"))
192
+ end
193
+ if distance
194
+ clause += ", " unless clause.empty?
195
+ clause += "#{distance} AS #{distance_column}"
196
+ end
197
+ if bearing
198
+ clause += ", " unless clause.empty?
199
+ clause += "#{bearing} AS #{bearing_column}"
200
+ end
201
+ clause
202
+ end
203
+
204
+ ##
205
+ # Adds a condition to exclude a given object by ID.
206
+ # Expects conditions as an array or string. Returns array.
207
+ #
208
+ def add_exclude_condition(conditions, exclude)
209
+ conditions = [conditions] if conditions.is_a?(String)
210
+ if exclude
211
+ conditions[0] << " AND #{full_column_name(primary_key)} != ?"
212
+ conditions << exclude.id
213
+ end
214
+ conditions
215
+ end
216
+
217
+ def using_sqlite?
218
+ connection.adapter_name.match /sqlite/i
219
+ end
220
+
221
+ ##
222
+ # Value which can be passed to where() to produce no results.
223
+ #
224
+ def false_condition
225
+ using_sqlite? ? 0 : "false"
226
+ end
227
+
228
+ ##
229
+ # Prepend table name if column name doesn't already contain one.
230
+ #
231
+ def full_column_name(column)
232
+ column = column.to_s
233
+ column.include?(".") ? column : [table_name, column].join(".")
234
+ end
235
+ end
236
+
237
+ ##
238
+ # Look up coordinates and assign to +latitude+ and +longitude+ attributes
239
+ # (or other as specified in +geocoded_by+). Returns coordinates (array).
240
+ #
241
+ def geocode
242
+ do_lookup(false) do |o,rs|
243
+ if r = rs.first
244
+ unless r.latitude.nil? or r.longitude.nil?
245
+ o.__send__ "#{self.class.geocoder2_options[:latitude]}=", r.latitude
246
+ o.__send__ "#{self.class.geocoder2_options[:longitude]}=", r.longitude
247
+ end
248
+ r.coordinates
249
+ end
250
+ end
251
+ end
252
+
253
+ alias_method :fetch_coordinates, :geocode
254
+
255
+ ##
256
+ # Look up address and assign to +address+ attribute (or other as specified
257
+ # in +reverse_geocoded_by+). Returns address (string).
258
+ #
259
+ def reverse_geocode
260
+ do_lookup(true) do |o,rs|
261
+ if r = rs.first
262
+ unless r.address.nil?
263
+ o.__send__ "#{self.class.geocoder2_options[:fetched_address]}=", r.address
264
+ end
265
+ r.address
266
+ end
267
+ end
268
+ end
269
+
270
+ alias_method :fetch_address, :reverse_geocode
271
+ end
272
+ end
@@ -0,0 +1,120 @@
1
+ module Geocoder2
2
+ module Store
3
+ module Base
4
+
5
+ ##
6
+ # Is this object geocoded? (Does it have latitude and longitude?)
7
+ #
8
+ def geocoded?
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.geocoder2_options[i] }
17
+ end
18
+
19
+ ##
20
+ # Calculate the distance from the object to an arbitrary point.
21
+ # See Geocoder2::Calculations.distance_between for ways of specifying
22
+ # the point. Also takes a symbol specifying the units
23
+ # (:mi or :km; can be specified in Geocoder2 configuration).
24
+ #
25
+ def distance_to(point, units = nil)
26
+ units ||= self.class.geocoder2_options[:units]
27
+ return nil unless geocoded?
28
+ Geocoder2::Calculations.distance_between(
29
+ to_coordinates, point, :units => units)
30
+ end
31
+
32
+ alias_method :distance_from, :distance_to
33
+
34
+ ##
35
+ # Calculate the bearing from the object to another point.
36
+ # See Geocoder2::Calculations.distance_between for
37
+ # ways of specifying the point.
38
+ #
39
+ def bearing_to(point, options = {})
40
+ options[:method] ||= self.class.geocoder2_options[:method]
41
+ return nil unless geocoded?
42
+ Geocoder2::Calculations.bearing_between(
43
+ to_coordinates, point, options)
44
+ end
45
+
46
+ ##
47
+ # Calculate the bearing from another point to the object.
48
+ # See Geocoder2::Calculations.distance_between for
49
+ # ways of specifying the point.
50
+ #
51
+ def bearing_from(point, options = {})
52
+ options[:method] ||= self.class.geocoder2_options[:method]
53
+ return nil unless geocoded?
54
+ Geocoder2::Calculations.bearing_between(
55
+ point, to_coordinates, options)
56
+ end
57
+
58
+ ##
59
+ # Get nearby geocoded objects.
60
+ # Takes the same options hash as the near class method (scope).
61
+ # Returns nil if the object is not geocoded.
62
+ #
63
+ def nearbys(radius = 20, options = {})
64
+ return nil unless geocoded?
65
+ options.merge!(:exclude => self) unless send(self.class.primary_key).nil?
66
+ self.class.near(self, radius, options)
67
+ end
68
+
69
+ ##
70
+ # Look up coordinates and assign to +latitude+ and +longitude+ attributes
71
+ # (or other as specified in +geocoded_by+). Returns coordinates (array).
72
+ #
73
+ def geocode
74
+ fail
75
+ end
76
+
77
+ ##
78
+ # Look up address and assign to +address+ attribute (or other as specified
79
+ # in +reverse_geocoded_by+). Returns address (string).
80
+ #
81
+ def reverse_geocode
82
+ fail
83
+ end
84
+
85
+ private # --------------------------------------------------------------
86
+
87
+ ##
88
+ # Look up geographic data based on object attributes (configured in
89
+ # geocoded_by or reverse_geocoded_by) and handle the results with the
90
+ # block (given to geocoded_by or reverse_geocoded_by). The block is
91
+ # given two-arguments: the object being geocoded and an array of
92
+ # Geocoder2::Result objects).
93
+ #
94
+ def do_lookup(reverse = false)
95
+ options = self.class.geocoder2_options
96
+ if reverse and options[:reverse_geocode]
97
+ query = to_coordinates
98
+ elsif !reverse and options[:geocode]
99
+ query = send(options[:user_address])
100
+ else
101
+ return
102
+ end
103
+
104
+ results = Geocoder2.search(query)
105
+
106
+ # execute custom block, if specified in configuration
107
+ block_key = reverse ? :reverse_block : :geocode_block
108
+ if custom_block = options[block_key]
109
+ custom_block.call(self, results)
110
+
111
+ # else execute block passed directly to this method,
112
+ # which generally performs the "auto-assigns"
113
+ elsif block_given?
114
+ yield(self, results)
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+
@@ -0,0 +1,89 @@
1
+ module Geocoder2::Store
2
+ module MongoBase
3
+
4
+ def self.included_by_model(base)
5
+ base.class_eval do
6
+
7
+ scope :geocoded, lambda {
8
+ where(geocoder2_options[:coordinates].ne => nil)
9
+ }
10
+
11
+ scope :not_geocoded, lambda {
12
+ where(geocoder2_options[:coordinates] => nil)
13
+ }
14
+
15
+ scope :near, lambda{ |location, *args|
16
+ coords = Geocoder2::Calculations.extract_coordinates(location)
17
+
18
+ # no results if no lat/lon given
19
+ return where(:id => false) unless coords.is_a?(Array)
20
+
21
+ radius = args.size > 0 ? args.shift : 20
22
+ options = args.size > 0 ? args.shift : {}
23
+ options[:units] ||= geocoder2_options[:units]
24
+
25
+ # Use BSON::OrderedHash if Ruby's hashes are unordered.
26
+ # Conditions must be in order required by indexes (see mongo gem).
27
+ version = RUBY_VERSION.split('.').map { |i| i.to_i }
28
+ empty = version[0] < 2 && version[1] < 9 ? BSON::OrderedHash.new : {}
29
+
30
+ conds = empty.clone
31
+ field = geocoder2_options[:coordinates]
32
+ conds[field] = empty.clone
33
+ conds[field]["$nearSphere"] = coords.reverse
34
+
35
+ if radius
36
+ conds[field]["$maxDistance"] = \
37
+ Geocoder2::Calculations.distance_to_radians(radius, options[:units])
38
+ end
39
+
40
+ if obj = options[:exclude]
41
+ conds[:_id.ne] = obj.id
42
+ end
43
+ where(conds)
44
+ }
45
+ end
46
+ end
47
+
48
+ ##
49
+ # Coordinates [lat,lon] of the object.
50
+ # This method always returns coordinates in lat,lon order,
51
+ # even though internally they are stored in the opposite order.
52
+ #
53
+ def to_coordinates
54
+ coords = send(self.class.geocoder2_options[:coordinates])
55
+ coords.is_a?(Array) ? coords.reverse : []
56
+ end
57
+
58
+ ##
59
+ # Look up coordinates and assign to +latitude+ and +longitude+ attributes
60
+ # (or other as specified in +geocoded_by+). Returns coordinates (array).
61
+ #
62
+ def geocode
63
+ do_lookup(false) do |o,rs|
64
+ if r = rs.first
65
+ unless r.coordinates.nil?
66
+ o.__send__ "#{self.class.geocoder2_options[:coordinates]}=", r.coordinates.reverse
67
+ end
68
+ r.coordinates
69
+ end
70
+ end
71
+ end
72
+
73
+ ##
74
+ # Look up address and assign to +address+ attribute (or other as specified
75
+ # in +reverse_geocoded_by+). Returns address (string).
76
+ #
77
+ def reverse_geocode
78
+ do_lookup(true) do |o,rs|
79
+ if r = rs.first
80
+ unless r.address.nil?
81
+ o.__send__ "#{self.class.geocoder2_options[:fetched_address]}=", r.address
82
+ end
83
+ r.address
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
@@ -0,0 +1,13 @@
1
+ require 'geocoder2/stores/base'
2
+ require 'geocoder2/stores/mongo_base'
3
+
4
+ module Geocoder2::Store
5
+ module MongoMapper
6
+ include Base
7
+ include MongoBase
8
+
9
+ def self.included(base)
10
+ MongoBase.included_by_model(base)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'geocoder2/stores/base'
2
+ require 'geocoder2/stores/mongo_base'
3
+
4
+ module Geocoder2::Store
5
+ module Mongoid
6
+ include Base
7
+ include MongoBase
8
+
9
+ def self.included(base)
10
+ MongoBase.included_by_model(base)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module Geocoder2
2
+ VERSION = "0.1.0"
3
+ end
data/lib/geocoder2.rb ADDED
@@ -0,0 +1,55 @@
1
+ require "geocoder2/configuration"
2
+ require "geocoder2/query"
3
+ require "geocoder2/calculations"
4
+ require "geocoder2/exceptions"
5
+ require "geocoder2/cache"
6
+ require "geocoder2/request"
7
+ require "geocoder2/lookup"
8
+ require "geocoder2/models/active_record" if defined?(::ActiveRecord)
9
+ require "geocoder2/models/mongoid" if defined?(::Mongoid)
10
+ require "geocoder2/models/mongo_mapper" if defined?(::MongoMapper)
11
+
12
+ module Geocoder2
13
+ extend self
14
+
15
+ ##
16
+ # Search for information about an address or a set of coordinates.
17
+ #
18
+ def search(query, options = {})
19
+ query = Geocoder2::Query.new(query, options) unless query.is_a?(Geocoder2::Query)
20
+ query.blank? ? [] : query.execute
21
+ end
22
+
23
+ ##
24
+ # Look up the coordinates of the given street or IP address.
25
+ #
26
+ def coordinates(address, options = {})
27
+ if (results = search(address, options)).size > 0
28
+ results.first.coordinates
29
+ end
30
+ end
31
+
32
+ ##
33
+ # Look up the address of the given coordinates ([lat,lon])
34
+ # or IP address (string).
35
+ #
36
+ def address(query, options = {})
37
+ if (results = search(query, options)).size > 0
38
+ results.first.address
39
+ end
40
+ end
41
+
42
+ ##
43
+ # The working Cache object, or +nil+ if none configured.
44
+ #
45
+ def cache
46
+ warn "WARNING: Calling Geocoder2.cache is DEPRECATED. The #cache method now belongs to the Geocoder2::Lookup object."
47
+ Geocoder2::Lookup.get(Geocoder2.config.lookup).cache
48
+ end
49
+ end
50
+
51
+ # load Railtie if Rails exists
52
+ if defined?(Rails)
53
+ require "geocoder2/railtie"
54
+ Geocoder2::Railtie.insert
55
+ end