sniff 0.0.3 → 0.0.4

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 (65) hide show
  1. data/README.markdown +5 -1
  2. data/lib/sniff.rb +4 -14
  3. data/lib/sniff/database.rb +20 -57
  4. data/lib/sniff/emitter.rb +12 -5
  5. data/spec/lib/sniff/database_spec.rb +8 -9
  6. metadata +56 -119
  7. data/lib/sniff/active_record_ext.rb +0 -12
  8. data/lib/sniff/conversions_ext.rb +0 -45
  9. data/lib/test_support/data_models/census_division.rb +0 -9
  10. data/lib/test_support/data_models/census_region.rb +0 -9
  11. data/lib/test_support/data_models/climate_division.rb +0 -8
  12. data/lib/test_support/data_models/country.rb +0 -9
  13. data/lib/test_support/data_models/egrid_region.rb +0 -7
  14. data/lib/test_support/data_models/egrid_subregion.rb +0 -8
  15. data/lib/test_support/data_models/gender.rb +0 -6
  16. data/lib/test_support/data_models/petroleum_administration_for_defense_district.rb +0 -9
  17. data/lib/test_support/data_models/state.rb +0 -13
  18. data/lib/test_support/data_models/urbanity.rb +0 -6
  19. data/lib/test_support/data_models/zip_code.rb +0 -14
  20. data/lib/test_support/db/fixtures/yearly_anonymous_emissions.csv +0 -0
  21. data/lib/test_support/db/fixtures/yearly_typical_emissions.csv +0 -0
  22. data/lib/test_support/db/schema.rb +0 -117
  23. data/spec/spec.opts +0 -2
  24. data/vendor/geokit-rails/CHANGELOG.rdoc +0 -46
  25. data/vendor/geokit-rails/MIT-LICENSE +0 -20
  26. data/vendor/geokit-rails/README.markdown +0 -561
  27. data/vendor/geokit-rails/Rakefile +0 -18
  28. data/vendor/geokit-rails/about.yml +0 -9
  29. data/vendor/geokit-rails/assets/api_keys_template +0 -61
  30. data/vendor/geokit-rails/init.rb +0 -1
  31. data/vendor/geokit-rails/install.rb +0 -14
  32. data/vendor/geokit-rails/lib/geokit-rails.rb +0 -24
  33. data/vendor/geokit-rails/lib/geokit-rails/acts_as_mappable.rb +0 -456
  34. data/vendor/geokit-rails/lib/geokit-rails/adapters/abstract.rb +0 -31
  35. data/vendor/geokit-rails/lib/geokit-rails/adapters/mysql.rb +0 -22
  36. data/vendor/geokit-rails/lib/geokit-rails/adapters/postgresql.rb +0 -22
  37. data/vendor/geokit-rails/lib/geokit-rails/adapters/sqlserver.rb +0 -43
  38. data/vendor/geokit-rails/lib/geokit-rails/defaults.rb +0 -22
  39. data/vendor/geokit-rails/lib/geokit-rails/geocoder_control.rb +0 -16
  40. data/vendor/geokit-rails/lib/geokit-rails/ip_geocode_lookup.rb +0 -46
  41. data/vendor/geokit-rails/test/acts_as_mappable_test.rb +0 -474
  42. data/vendor/geokit-rails/test/boot.rb +0 -25
  43. data/vendor/geokit-rails/test/database.yml +0 -20
  44. data/vendor/geokit-rails/test/fixtures/companies.yml +0 -7
  45. data/vendor/geokit-rails/test/fixtures/custom_locations.yml +0 -54
  46. data/vendor/geokit-rails/test/fixtures/locations.yml +0 -54
  47. data/vendor/geokit-rails/test/fixtures/mock_addresses.yml +0 -17
  48. data/vendor/geokit-rails/test/fixtures/mock_families.yml +0 -2
  49. data/vendor/geokit-rails/test/fixtures/mock_houses.yml +0 -9
  50. data/vendor/geokit-rails/test/fixtures/mock_organizations.yml +0 -5
  51. data/vendor/geokit-rails/test/fixtures/mock_people.yml +0 -5
  52. data/vendor/geokit-rails/test/fixtures/stores.yml +0 -0
  53. data/vendor/geokit-rails/test/ip_geocode_lookup_test.rb +0 -77
  54. data/vendor/geokit-rails/test/models/company.rb +0 -3
  55. data/vendor/geokit-rails/test/models/custom_location.rb +0 -12
  56. data/vendor/geokit-rails/test/models/location.rb +0 -4
  57. data/vendor/geokit-rails/test/models/mock_address.rb +0 -4
  58. data/vendor/geokit-rails/test/models/mock_family.rb +0 -3
  59. data/vendor/geokit-rails/test/models/mock_house.rb +0 -3
  60. data/vendor/geokit-rails/test/models/mock_organization.rb +0 -4
  61. data/vendor/geokit-rails/test/models/mock_person.rb +0 -4
  62. data/vendor/geokit-rails/test/models/store.rb +0 -3
  63. data/vendor/geokit-rails/test/schema.rb +0 -60
  64. data/vendor/geokit-rails/test/tasks.rake +0 -31
  65. data/vendor/geokit-rails/test/test_helper.rb +0 -23
@@ -1,18 +0,0 @@
1
- require 'rake'
2
- require 'rake/testtask'
3
- require 'rake/rdoctask'
4
-
5
- load 'test/tasks.rake'
6
-
7
- desc 'Default: run unit tests.'
8
- task :default => :test
9
-
10
- desc 'Generate documentation for the GeoKit plugin.'
11
- Rake::RDocTask.new(:rdoc) do |rdoc|
12
- rdoc.rdoc_dir = 'rdoc'
13
- rdoc.title = 'GeoKit'
14
- rdoc.options << '--line-numbers' << '--inline-source'
15
- rdoc.rdoc_files.include('README')
16
- rdoc.rdoc_files.include('lib/**/*.rb')
17
- end
18
-
@@ -1,9 +0,0 @@
1
- author:
2
- name_1: Bill Eisenhauer
3
- homepage_1: http://blog.billeisenhauer.com
4
- name_2: Andre Lewis
5
- homepage_2: http://www.earthcode.com
6
- summary: Geo distance calculations, distance calculation query support, geocoding for physical and ip addresses.
7
- version: 1.1.4
8
- rails_version: 1.0+
9
- license: MIT
@@ -1,61 +0,0 @@
1
- if defined? Geokit
2
-
3
- # These defaults are used in Geokit::Mappable.distance_to and in acts_as_mappable
4
- Geokit::default_units = :miles
5
- Geokit::default_formula = :sphere
6
-
7
- # This is the timeout value in seconds to be used for calls to the geocoder web
8
- # services. For no timeout at all, comment out the setting. The timeout unit
9
- # is in seconds.
10
- Geokit::Geocoders::request_timeout = 3
11
-
12
- # These settings are used if web service calls must be routed through a proxy.
13
- # These setting can be nil if not needed, otherwise, addr and port must be
14
- # filled in at a minimum. If the proxy requires authentication, the username
15
- # and password can be provided as well.
16
- Geokit::Geocoders::proxy_addr = nil
17
- Geokit::Geocoders::proxy_port = nil
18
- Geokit::Geocoders::proxy_user = nil
19
- Geokit::Geocoders::proxy_pass = nil
20
-
21
- # This is your yahoo application key for the Yahoo Geocoder.
22
- # See http://developer.yahoo.com/faq/index.html#appid
23
- # and http://developer.yahoo.com/maps/rest/V1/geocode.html
24
- Geokit::Geocoders::yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
25
-
26
- # This is your Google Maps geocoder key.
27
- # See http://www.google.com/apis/maps/signup.html
28
- # and http://www.google.com/apis/maps/documentation/#Geocoding_Examples
29
- Geokit::Geocoders::google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
30
-
31
- # This is your username and password for geocoder.us.
32
- # To use the free service, the value can be set to nil or false. For
33
- # usage tied to an account, the value should be set to username:password.
34
- # See http://geocoder.us
35
- # and http://geocoder.us/user/signup
36
- Geokit::Geocoders::geocoder_us = false
37
-
38
- # This is your authorization key for geocoder.ca.
39
- # To use the free service, the value can be set to nil or false. For
40
- # usage tied to an account, set the value to the key obtained from
41
- # Geocoder.ca.
42
- # See http://geocoder.ca
43
- # and http://geocoder.ca/?register=1
44
- Geokit::Geocoders::geocoder_ca = false
45
-
46
- # Uncomment to use a username with the Geonames geocoder
47
- #Geokit::Geocoders::geonames="REPLACE_WITH_YOUR_GEONAMES_USERNAME"
48
-
49
- # This is the order in which the geocoders are called in a failover scenario
50
- # If you only want to use a single geocoder, put a single symbol in the array.
51
- # Valid symbols are :google, :yahoo, :us, and :ca.
52
- # Be aware that there are Terms of Use restrictions on how you can use the
53
- # various geocoders. Make sure you read up on relevant Terms of Use for each
54
- # geocoder you are going to use.
55
- Geokit::Geocoders::provider_order = [:google,:us]
56
-
57
- # The IP provider order. Valid symbols are :ip,:geo_plugin.
58
- # As before, make sure you read up on relevant Terms of Use for each
59
- # Geokit::Geocoders::ip_provider_order = [:geo_plugin,:ip]
60
-
61
- end
@@ -1 +0,0 @@
1
- require 'geokit-rails'
@@ -1,14 +0,0 @@
1
- # Display to the console the contents of the README file.
2
- puts IO.read(File.join(File.dirname(__FILE__), 'README.markdown'))
3
-
4
- # place the api_keys_template in the application's /config/initializers/geokit_config.rb
5
- path=File.expand_path(File.join(File.dirname(__FILE__), '../../../config/initializers/geokit_config.rb'))
6
- template_path=File.join(File.dirname(__FILE__), '/assets/api_keys_template')
7
- if File.exists?(path)
8
- puts "It looks like you already have a configuration file at #{path}. We've left it as-is. Recommended: check #{template_path} to see if anything has changed, and update config file accordingly."
9
- else
10
- File.open(path, "w") do |f|
11
- f.puts IO.read(template_path)
12
- puts "We created a configuration file for you in config/initializers/geokit_config.rb. Add your Google API keys, etc there."
13
- end
14
- end
@@ -1,24 +0,0 @@
1
- # Load modules and classes needed to automatically mix in ActiveRecord and
2
- # ActionController helpers. All other functionality must be explicitly
3
- # required.
4
- #
5
- # Note that we don't explicitly require the geokit gem.
6
- # You should specify gem dependencies in your config/environment.rb: config.gem "geokit"
7
- #
8
- begin
9
- require 'geokit'
10
- require 'geokit-rails/defaults'
11
- require 'geokit-rails/adapters/abstract'
12
- require 'geokit-rails/acts_as_mappable'
13
- require 'geokit-rails/ip_geocode_lookup'
14
-
15
- # Automatically mix in distance finder support into ActiveRecord classes.
16
- ActiveRecord::Base.send :include, GeoKit::ActsAsMappable
17
- rescue LoadError
18
- message=%q(WARNING: geokit-rails requires the Geokit gem. You either don't have the gem installed,
19
- or you haven't told Rails to require it. If you're using a recent version of Rails:
20
- config.gem "geokit" # in config/environment.rb
21
- and of course install the gem: sudo gem install geokit)
22
- puts message
23
- Rails.logger.error message
24
- end
@@ -1,456 +0,0 @@
1
- module Geokit
2
- # Contains the class method acts_as_mappable targeted to be mixed into ActiveRecord.
3
- # When mixed in, augments find services such that they provide distance calculation
4
- # query services. The find method accepts additional options:
5
- #
6
- # * :origin - can be
7
- # 1. a two-element array of latititude/longitude -- :origin=>[37.792,-122.393]
8
- # 2. a geocodeable string -- :origin=>'100 Spear st, San Francisco, CA'
9
- # 3. an object which responds to lat and lng methods, or latitude and longitude methods,
10
- # or whatever methods you have specified for lng_column_name and lat_column_name
11
- #
12
- # Other finder methods are provided for specific queries. These are:
13
- #
14
- # * find_within (alias: find_inside)
15
- # * find_beyond (alias: find_outside)
16
- # * find_closest (alias: find_nearest)
17
- # * find_farthest
18
- #
19
- # Counter methods are available and work similarly to finders.
20
- #
21
- # If raw SQL is desired, the distance_sql method can be used to obtain SQL appropriate
22
- # to use in a find_by_sql call.
23
- module ActsAsMappable
24
- class UnsupportedAdapter < StandardError ; end
25
-
26
- # Mix below class methods into ActiveRecord.
27
- def self.included(base) # :nodoc:
28
- base.extend ClassMethods
29
- end
30
-
31
- # Class method to mix into active record.
32
- module ClassMethods # :nodoc:
33
-
34
- # Class method to bring distance query support into ActiveRecord models. By default
35
- # uses :miles for distance units and performs calculations based upon the Haversine
36
- # (sphere) formula. These can be changed by setting Geokit::default_units and
37
- # Geokit::default_formula. Also, by default, uses lat, lng, and distance for respective
38
- # column names. All of these can be overridden using the :default_units, :default_formula,
39
- # :lat_column_name, :lng_column_name, and :distance_column_name hash keys.
40
- #
41
- # Can also use to auto-geocode a specific column on create. Syntax;
42
- #
43
- # acts_as_mappable :auto_geocode=>true
44
- #
45
- # By default, it tries to geocode the "address" field. Or, for more customized behavior:
46
- #
47
- # acts_as_mappable :auto_geocode=>{:field=>:address,:error_message=>'bad address'}
48
- #
49
- # In both cases, it creates a before_validation_on_create callback to geocode the given column.
50
- # For anything more customized, we recommend you forgo the auto_geocode option
51
- # and create your own AR callback to handle geocoding.
52
- def acts_as_mappable(options = {})
53
- metaclass = (class << self; self; end)
54
-
55
- # Mix in the module, but ensure to do so just once.
56
- return if !defined?(Geokit::Mappable) || metaclass.included_modules.include?(Geokit::ActsAsMappable::SingletonMethods)
57
-
58
- send :extend, Geokit::ActsAsMappable::SingletonMethods
59
- send :include, Geokit::Mappable
60
-
61
- cattr_accessor :through
62
- self.through = options[:through]
63
-
64
- if reflection = Geokit::ActsAsMappable.end_of_reflection_chain(self.through, self)
65
- metaclass.instance_eval do
66
- [ :distance_column_name, :default_units, :default_formula, :lat_column_name, :lng_column_name, :qualified_lat_column_name, :qualified_lng_column_name ].each do |method_name|
67
- define_method method_name do
68
- reflection.klass.send(method_name)
69
- end
70
- end
71
- end
72
- else
73
- cattr_accessor :distance_column_name, :default_units, :default_formula, :lat_column_name, :lng_column_name, :qualified_lat_column_name, :qualified_lng_column_name
74
-
75
- self.distance_column_name = options[:distance_column_name] || 'distance'
76
- self.default_units = options[:default_units] || Geokit::default_units
77
- self.default_formula = options[:default_formula] || Geokit::default_formula
78
- self.lat_column_name = options[:lat_column_name] || 'lat'
79
- self.lng_column_name = options[:lng_column_name] || 'lng'
80
- self.qualified_lat_column_name = "#{table_name}.#{lat_column_name}"
81
- self.qualified_lng_column_name = "#{table_name}.#{lng_column_name}"
82
-
83
- if options.include?(:auto_geocode) && options[:auto_geocode]
84
- # if the form auto_geocode=>true is used, let the defaults take over by suppling an empty hash
85
- options[:auto_geocode] = {} if options[:auto_geocode] == true
86
- cattr_accessor :auto_geocode_field, :auto_geocode_error_message
87
- self.auto_geocode_field = options[:auto_geocode][:field] || 'address'
88
- self.auto_geocode_error_message = options[:auto_geocode][:error_message] || 'could not locate address'
89
-
90
- # set the actual callback here
91
- before_validation_on_create :auto_geocode_address
92
- end
93
- end
94
- end
95
- end
96
-
97
- # this is the callback for auto_geocoding
98
- def auto_geocode_address
99
- address=self.send(auto_geocode_field).to_s
100
- geo=Geokit::Geocoders::MultiGeocoder.geocode(address)
101
-
102
- if geo.success
103
- self.send("#{lat_column_name}=", geo.lat)
104
- self.send("#{lng_column_name}=", geo.lng)
105
- else
106
- errors.add(auto_geocode_field, auto_geocode_error_message)
107
- end
108
-
109
- geo.success
110
- end
111
-
112
- def self.end_of_reflection_chain(through, klass)
113
- while through
114
- reflection = nil
115
- if through.is_a?(Hash)
116
- association, through = through.to_a.first
117
- else
118
- association, through = through, nil
119
- end
120
-
121
- if reflection = klass.reflect_on_association(association)
122
- klass = reflection.klass
123
- else
124
- raise ArgumentError, "You gave #{association} in :through, but I could not find it on #{klass}."
125
- end
126
- end
127
-
128
- reflection
129
- end
130
-
131
- # Instance methods to mix into ActiveRecord.
132
- module SingletonMethods #:nodoc:
133
-
134
- # A proxy to an instance of a finder adapter, inferred from the connection's adapter.
135
- def adapter
136
- @adapter ||= begin
137
- require File.join(File.dirname(__FILE__), 'adapters', connection.adapter_name.downcase)
138
- klass = Adapters.const_get(connection.adapter_name.camelcase)
139
- klass.load(self) unless klass.loaded
140
- klass.new(self)
141
- rescue LoadError
142
- raise UnsupportedAdapter, "`#{connection.adapter_name.downcase}` is not a supported adapter."
143
- end
144
- end
145
-
146
- # Extends the existing find method in potentially two ways:
147
- # - If a mappable instance exists in the options, adds a distance column.
148
- # - If a mappable instance exists in the options and the distance column exists in the
149
- # conditions, substitutes the distance sql for the distance column -- this saves
150
- # having to write the gory SQL.
151
- def find(*args)
152
- prepare_for_find_or_count(:find, args)
153
- super(*args)
154
- end
155
-
156
- # Extends the existing count method by:
157
- # - If a mappable instance exists in the options and the distance column exists in the
158
- # conditions, substitutes the distance sql for the distance column -- this saves
159
- # having to write the gory SQL.
160
- def count(*args)
161
- prepare_for_find_or_count(:count, args)
162
- super(*args)
163
- end
164
-
165
- # Finds within a distance radius.
166
- def find_within(distance, options={})
167
- options[:within] = distance
168
- find(:all, options)
169
- end
170
- alias find_inside find_within
171
-
172
- # Finds beyond a distance radius.
173
- def find_beyond(distance, options={})
174
- options[:beyond] = distance
175
- find(:all, options)
176
- end
177
- alias find_outside find_beyond
178
-
179
- # Finds according to a range. Accepts inclusive or exclusive ranges.
180
- def find_by_range(range, options={})
181
- options[:range] = range
182
- find(:all, options)
183
- end
184
-
185
- # Finds the closest to the origin.
186
- def find_closest(options={})
187
- find(:nearest, options)
188
- end
189
- alias find_nearest find_closest
190
-
191
- # Finds the farthest from the origin.
192
- def find_farthest(options={})
193
- find(:farthest, options)
194
- end
195
-
196
- # Finds within rectangular bounds (sw,ne).
197
- def find_within_bounds(bounds, options={})
198
- options[:bounds] = bounds
199
- find(:all, options)
200
- end
201
-
202
- # counts within a distance radius.
203
- def count_within(distance, options={})
204
- options[:within] = distance
205
- count(options)
206
- end
207
- alias count_inside count_within
208
-
209
- # Counts beyond a distance radius.
210
- def count_beyond(distance, options={})
211
- options[:beyond] = distance
212
- count(options)
213
- end
214
- alias count_outside count_beyond
215
-
216
- # Counts according to a range. Accepts inclusive or exclusive ranges.
217
- def count_by_range(range, options={})
218
- options[:range] = range
219
- count(options)
220
- end
221
-
222
- # Finds within rectangular bounds (sw,ne).
223
- def count_within_bounds(bounds, options={})
224
- options[:bounds] = bounds
225
- count(options)
226
- end
227
-
228
- # Returns the distance calculation to be used as a display column or a condition. This
229
- # is provide for anyone wanting access to the raw SQL.
230
- def distance_sql(origin, units=default_units, formula=default_formula)
231
- case formula
232
- when :sphere
233
- sql = sphere_distance_sql(origin, units)
234
- when :flat
235
- sql = flat_distance_sql(origin, units)
236
- end
237
- sql
238
- end
239
-
240
- private
241
-
242
- # Prepares either a find or a count action by parsing through the options and
243
- # conditionally adding to the select clause for finders.
244
- def prepare_for_find_or_count(action, args)
245
- options = args.extract_options!
246
- #options = defined?(args.extract_options!) ? args.extract_options! : extract_options_from_args!(args)
247
- # Obtain items affecting distance condition.
248
- origin = extract_origin_from_options(options)
249
- units = extract_units_from_options(options)
250
- formula = extract_formula_from_options(options)
251
- bounds = extract_bounds_from_options(options)
252
-
253
- # Only proceed if this is a geokit-related query
254
- if origin || bounds
255
- # if no explicit bounds were given, try formulating them from the point and distance given
256
- bounds = formulate_bounds_from_distance(options, origin, units) unless bounds
257
- # Apply select adjustments based upon action.
258
- add_distance_to_select(options, origin, units, formula) if origin && action == :find
259
- # Apply the conditions for a bounding rectangle if applicable
260
- apply_bounds_conditions(options,bounds) if bounds
261
- # Apply distance scoping and perform substitutions.
262
- apply_distance_scope(options)
263
- substitute_distance_in_conditions(options, origin, units, formula) if origin && options.has_key?(:conditions)
264
- # Order by scoping for find action.
265
- apply_find_scope(args, options) if action == :find
266
- # Handle :through
267
- apply_include_for_through(options)
268
- # Unfortunatley, we need to do extra work if you use an :include. See the method for more info.
269
- handle_order_with_include(options,origin,units,formula) if options.include?(:include) && options.include?(:order) && origin
270
- end
271
-
272
- # Restore options minus the extra options that we used for the
273
- # Geokit API.
274
- args.push(options)
275
- end
276
-
277
- def apply_include_for_through(options)
278
- if self.through
279
- case options[:include]
280
- when Array
281
- options[:include] << self.through
282
- when Hash, String, Symbol
283
- options[:include] = [ self.through, options[:include] ]
284
- else
285
- options[:include] = [ self.through ]
286
- end
287
- end
288
- end
289
-
290
- # If we're here, it means that 1) an origin argument, 2) an :include, 3) an :order clause were supplied.
291
- # Now we have to sub some SQL into the :order clause. The reason is that when you do an :include,
292
- # ActiveRecord drops the psuedo-column (specificically, distance) which we supplied for :select.
293
- # So, the 'distance' column isn't available for the :order clause to reference when we use :include.
294
- def handle_order_with_include(options, origin, units, formula)
295
- # replace the distance_column_name with the distance sql in order clause
296
- options[:order].sub!(distance_column_name, distance_sql(origin, units, formula))
297
- end
298
-
299
- # Looks for mapping-specific tokens and makes appropriate translations so that the
300
- # original finder has its expected arguments. Resets the the scope argument to
301
- # :first and ensures the limit is set to one.
302
- def apply_find_scope(args, options)
303
- case args.first
304
- when :nearest, :closest
305
- args[0] = :first
306
- options[:limit] = 1
307
- options[:order] = "#{distance_column_name} ASC"
308
- when :farthest
309
- args[0] = :first
310
- options[:limit] = 1
311
- options[:order] = "#{distance_column_name} DESC"
312
- end
313
- end
314
-
315
- # If it's a :within query, add a bounding box to improve performance.
316
- # This only gets called if a :bounds argument is not otherwise supplied.
317
- def formulate_bounds_from_distance(options, origin, units)
318
- distance = options[:within] if options.has_key?(:within)
319
- distance = options[:range].last-(options[:range].exclude_end?? 1 : 0) if options.has_key?(:range)
320
- if distance
321
- res=Geokit::Bounds.from_point_and_radius(origin,distance,:units=>units)
322
- else
323
- nil
324
- end
325
- end
326
-
327
- # Replace :within, :beyond and :range distance tokens with the appropriate distance
328
- # where clauses. Removes these tokens from the options hash.
329
- def apply_distance_scope(options)
330
- distance_condition = if options.has_key?(:within)
331
- "#{distance_column_name} <= #{options[:within]}"
332
- elsif options.has_key?(:beyond)
333
- "#{distance_column_name} > #{options[:beyond]}"
334
- elsif options.has_key?(:range)
335
- "#{distance_column_name} >= #{options[:range].first} AND #{distance_column_name} <#{'=' unless options[:range].exclude_end?} #{options[:range].last}"
336
- end
337
-
338
- if distance_condition
339
- [:within, :beyond, :range].each { |option| options.delete(option) }
340
- options[:conditions] = merge_conditions(options[:conditions], distance_condition)
341
- end
342
- end
343
-
344
- # Alters the conditions to include rectangular bounds conditions.
345
- def apply_bounds_conditions(options,bounds)
346
- sw,ne = bounds.sw, bounds.ne
347
- lng_sql = bounds.crosses_meridian? ? "(#{qualified_lng_column_name}<#{ne.lng} OR #{qualified_lng_column_name}>#{sw.lng})" : "#{qualified_lng_column_name}>#{sw.lng} AND #{qualified_lng_column_name}<#{ne.lng}"
348
- bounds_sql = "#{qualified_lat_column_name}>#{sw.lat} AND #{qualified_lat_column_name}<#{ne.lat} AND #{lng_sql}"
349
- options[:conditions] = merge_conditions(options[:conditions], bounds_sql)
350
- end
351
-
352
- # Extracts the origin instance out of the options if it exists and returns
353
- # it. If there is no origin, looks for latitude and longitude values to
354
- # create an origin. The side-effect of the method is to remove these
355
- # option keys from the hash.
356
- def extract_origin_from_options(options)
357
- origin = options.delete(:origin)
358
- res = normalize_point_to_lat_lng(origin) if origin
359
- res
360
- end
361
-
362
- # Extract the units out of the options if it exists and returns it. If
363
- # there is no :units key, it uses the default. The side effect of the
364
- # method is to remove the :units key from the options hash.
365
- def extract_units_from_options(options)
366
- units = options[:units] || default_units
367
- options.delete(:units)
368
- units
369
- end
370
-
371
- # Extract the formula out of the options if it exists and returns it. If
372
- # there is no :formula key, it uses the default. The side effect of the
373
- # method is to remove the :formula key from the options hash.
374
- def extract_formula_from_options(options)
375
- formula = options[:formula] || default_formula
376
- options.delete(:formula)
377
- formula
378
- end
379
-
380
- def extract_bounds_from_options(options)
381
- bounds = options.delete(:bounds)
382
- bounds = Geokit::Bounds.normalize(bounds) if bounds
383
- end
384
-
385
- # Geocode IP address.
386
- def geocode_ip_address(origin)
387
- geo_location = Geokit::Geocoders::MultiGeocoder.geocode(origin)
388
- return geo_location if geo_location.success
389
- raise Geokit::Geocoders::GeocodeError
390
- end
391
-
392
- # Given a point in a variety of (an address to geocode,
393
- # an array of [lat,lng], or an object with appropriate lat/lng methods, an IP addres)
394
- # this method will normalize it into a Geokit::LatLng instance. The only thing this
395
- # method adds on top of LatLng#normalize is handling of IP addresses
396
- def normalize_point_to_lat_lng(point)
397
- res = geocode_ip_address(point) if point.is_a?(String) && /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(point)
398
- res = Geokit::LatLng.normalize(point) unless res
399
- res
400
- end
401
-
402
- # Augments the select with the distance SQL.
403
- def add_distance_to_select(options, origin, units=default_units, formula=default_formula)
404
- if origin
405
- distance_selector = distance_sql(origin, units, formula) + " AS #{distance_column_name}"
406
- selector = options.has_key?(:select) && options[:select] ? options[:select] : "*"
407
- options[:select] = "#{selector}, #{distance_selector}"
408
- end
409
- end
410
-
411
- # Looks for the distance column and replaces it with the distance sql. If an origin was not
412
- # passed in and the distance column exists, we leave it to be flagged as bad SQL by the database.
413
- # Conditions are either a string or an array. In the case of an array, the first entry contains
414
- # the condition.
415
- def substitute_distance_in_conditions(options, origin, units=default_units, formula=default_formula)
416
- condition = options[:conditions].is_a?(String) ? options[:conditions] : options[:conditions].first
417
- pattern = Regexp.new("\\b#{distance_column_name}\\b")
418
- condition.gsub!(pattern, distance_sql(origin, units, formula))
419
- end
420
-
421
- # Returns the distance SQL using the spherical world formula (Haversine). The SQL is tuned
422
- # to the database in use.
423
- def sphere_distance_sql(origin, units)
424
- lat = deg2rad(origin.lat)
425
- lng = deg2rad(origin.lng)
426
- multiplier = units_sphere_multiplier(units)
427
-
428
- adapter.sphere_distance_sql(lat, lng, multiplier) if adapter
429
- end
430
-
431
- # Returns the distance SQL using the flat-world formula (Phythagorean Theory). The SQL is tuned
432
- # to the database in use.
433
- def flat_distance_sql(origin, units)
434
- lat_degree_units = units_per_latitude_degree(units)
435
- lng_degree_units = units_per_longitude_degree(origin.lat, units)
436
-
437
- adapter.flat_distance_sql(origin, lat_degree_units, lng_degree_units)
438
- end
439
- end
440
- end
441
- end
442
-
443
- # Extend Array with a sort_by_distance method.
444
- class Array
445
- # This method creates a "distance" attribute on each object, calculates the
446
- # distance from the passed origin, and finally sorts the array by the
447
- # resulting distance.
448
- def sort_by_distance_from(origin, opts={})
449
- distance_attribute_name = opts.delete(:distance_attribute_name) || 'distance'
450
- self.each do |e|
451
- e.class.send(:attr_accessor, distance_attribute_name) if !e.respond_to?("#{distance_attribute_name}=")
452
- e.send("#{distance_attribute_name}=", e.distance_to(origin,opts))
453
- end
454
- self.sort!{|a,b|a.send(distance_attribute_name) <=> b.send(distance_attribute_name)}
455
- end
456
- end