earth 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/CHANGELOG +6 -0
  2. data/Rakefile +0 -3
  3. data/TODO +1 -1
  4. data/ar17.html +2093 -0
  5. data/data/air/aircraft.csv +7 -0
  6. data/data/air/airports.csv +1 -0
  7. data/earth.gemspec +4 -3
  8. data/errata/airline/bts_carrier_codes_errata.csv +74 -10
  9. data/errata/airport/openflights_errata.csv +7 -0
  10. data/errata/flight_segment/bts_errata.csv +1 -0
  11. data/lib/earth/air/aircraft.rb +1 -1
  12. data/lib/earth/air/aircraft/data_miner.rb +1 -1
  13. data/lib/earth/air/airline.rb +1 -1
  14. data/lib/earth/air/airline/data_miner.rb +6 -10
  15. data/lib/earth/air/airport.rb +6 -6
  16. data/lib/earth/air/bts_aircraft.rb +1 -1
  17. data/lib/earth/air/flight_segment.rb +1 -1
  18. data/lib/earth/automobile/automobile_activity_year_type.rb +1 -1
  19. data/lib/earth/automobile/automobile_activity_year_type_fuel/data_miner.rb +3 -3
  20. data/lib/earth/automobile/automobile_make_model_year_variant.rb +2 -2
  21. data/lib/earth/automobile/automobile_make_model_year_variant/data_miner.rb +1 -1
  22. data/lib/earth/automobile/automobile_type_fuel_year.rb +1 -1
  23. data/lib/earth/automobile/automobile_type_fuel_year/data_miner.rb +2 -2
  24. data/lib/earth/automobile/automobile_type_fuel_year_control.rb +1 -1
  25. data/lib/earth/insolation_scopes.rb +10 -0
  26. data/lib/earth/loader.rb +0 -7
  27. data/lib/earth/locality/climate_division.rb +3 -3
  28. data/lib/earth/locality/climate_division_month.rb +28 -0
  29. data/lib/earth/locality/climate_division_month/data_miner.rb +140 -0
  30. data/lib/earth/locality/direct_normal_insolation.rb +37 -0
  31. data/lib/earth/locality/direct_normal_insolation/data_miner.rb +41 -0
  32. data/lib/earth/locality/egrid_subregion.rb +4 -1
  33. data/lib/earth/locality/electricity_mix.rb +9 -1
  34. data/lib/earth/locality/global_horizontal_insolation.rb +37 -0
  35. data/lib/earth/locality/global_horizontal_insolation/data_miner.rb +41 -0
  36. data/lib/earth/locality/photovoltaic_insolation.rb +37 -0
  37. data/lib/earth/locality/photovoltaic_insolation/data_miner.rb +41 -0
  38. data/lib/earth/locality/zip_code.rb +9 -15
  39. data/lib/earth/residence/residence_fuel_price.rb +1 -1
  40. data/lib/earth/tasks.rb +5 -0
  41. data/lib/earth/version.rb +1 -1
  42. data/spec/data_mining_spec.rb +1 -1
  43. data/spec/earth/air/aircraft_spec.rb +2 -2
  44. data/spec/earth/air/airline_spec.rb +1 -1
  45. data/spec/earth/air/airport_spec.rb +1 -1
  46. data/spec/earth/air/bts_aircraft_spec.rb +1 -1
  47. data/spec/earth/air/flight_segment_spec.rb +19 -21
  48. data/spec/earth/automobile/automobile_make_model_year_variant_spec.rb +1 -1
  49. data/spec/earth/locality/climate_division_month_spec.rb +23 -0
  50. data/spec/earth/locality/direct_normal_insolation_spec.rb +8 -0
  51. data/spec/earth/locality/global_horizontal_insolation_spec.rb +8 -0
  52. data/spec/earth/locality/photovoltaic_insolation_spec.rb +8 -0
  53. data/spec/earth/locality/zip_code_spec.rb +1 -10
  54. data/spec/earth_spec.rb +1 -1
  55. data/spec/spec_helper.rb +1 -1
  56. metadata +261 -410
  57. data/data/air/airlines.csv +0 -45
  58. data/vendor/geokit-rails/.gitignore +0 -2
  59. data/vendor/geokit-rails/CHANGELOG.rdoc +0 -49
  60. data/vendor/geokit-rails/MIT-LICENSE +0 -20
  61. data/vendor/geokit-rails/README.markdown +0 -569
  62. data/vendor/geokit-rails/Rakefile +0 -18
  63. data/vendor/geokit-rails/about.yml +0 -11
  64. data/vendor/geokit-rails/assets/api_keys_template +0 -61
  65. data/vendor/geokit-rails/init.rb +0 -2
  66. data/vendor/geokit-rails/install.rb +0 -14
  67. data/vendor/geokit-rails/lib/geokit-rails.rb +0 -28
  68. data/vendor/geokit-rails/lib/geokit-rails/acts_as_mappable.rb +0 -469
  69. data/vendor/geokit-rails/lib/geokit-rails/adapters/abstract.rb +0 -31
  70. data/vendor/geokit-rails/lib/geokit-rails/adapters/mysql.rb +0 -22
  71. data/vendor/geokit-rails/lib/geokit-rails/adapters/mysql2.rb +0 -22
  72. data/vendor/geokit-rails/lib/geokit-rails/adapters/postgresql.rb +0 -22
  73. data/vendor/geokit-rails/lib/geokit-rails/adapters/sqlserver.rb +0 -43
  74. data/vendor/geokit-rails/lib/geokit-rails/defaults.rb +0 -22
  75. data/vendor/geokit-rails/lib/geokit-rails/geocoder_control.rb +0 -16
  76. data/vendor/geokit-rails/lib/geokit-rails/ip_geocode_lookup.rb +0 -46
  77. data/vendor/geokit-rails/test/acts_as_mappable_test.rb +0 -474
  78. data/vendor/geokit-rails/test/boot.rb +0 -25
  79. data/vendor/geokit-rails/test/database.yml +0 -25
  80. data/vendor/geokit-rails/test/fixtures/companies.yml +0 -7
  81. data/vendor/geokit-rails/test/fixtures/custom_locations.yml +0 -54
  82. data/vendor/geokit-rails/test/fixtures/locations.yml +0 -54
  83. data/vendor/geokit-rails/test/fixtures/mock_addresses.yml +0 -17
  84. data/vendor/geokit-rails/test/fixtures/mock_families.yml +0 -2
  85. data/vendor/geokit-rails/test/fixtures/mock_houses.yml +0 -9
  86. data/vendor/geokit-rails/test/fixtures/mock_organizations.yml +0 -5
  87. data/vendor/geokit-rails/test/fixtures/mock_people.yml +0 -5
  88. data/vendor/geokit-rails/test/fixtures/stores.yml +0 -0
  89. data/vendor/geokit-rails/test/ip_geocode_lookup_test.rb +0 -77
  90. data/vendor/geokit-rails/test/models/company.rb +0 -3
  91. data/vendor/geokit-rails/test/models/custom_location.rb +0 -12
  92. data/vendor/geokit-rails/test/models/location.rb +0 -4
  93. data/vendor/geokit-rails/test/models/mock_address.rb +0 -4
  94. data/vendor/geokit-rails/test/models/mock_family.rb +0 -3
  95. data/vendor/geokit-rails/test/models/mock_house.rb +0 -3
  96. data/vendor/geokit-rails/test/models/mock_organization.rb +0 -4
  97. data/vendor/geokit-rails/test/models/mock_person.rb +0 -4
  98. data/vendor/geokit-rails/test/models/store.rb +0 -3
  99. data/vendor/geokit-rails/test/schema.rb +0 -60
  100. data/vendor/geokit-rails/test/tasks.rake +0 -31
  101. 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,11 +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
- name_3: Jonathan Owens
7
- name_4: Jérémy Lecour
8
- summary: Geo distance calculations, distance calculation query support, geocoding for physical and ip addresses.
9
- version: 1.1.4
10
- rails_version: 1.0+
11
- 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,2 +0,0 @@
1
- # rather than unvendor this, let's try to get rid of the acts_as_mappable dependency (?)
2
- require File.expand_path('../lib/geokit-rails', __FILE__)
@@ -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,28 +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
- require 'geokit'
9
- if defined? Geokit
10
- # rather than unvendor this, let's try to get rid of the acts_as_mappable dependency (?)
11
- require File.expand_path('../geokit-rails/defaults', __FILE__)
12
- require File.expand_path('../geokit-rails/adapters/abstract', __FILE__)
13
- require File.expand_path('../geokit-rails/acts_as_mappable', __FILE__)
14
- require File.expand_path('../geokit-rails/ip_geocode_lookup', __FILE__)
15
-
16
- # Automatically mix in distance finder support into ActiveRecord classes.
17
- ActiveRecord::Base.send :include, GeoKit::ActsAsMappable
18
-
19
- # # Automatically mix in ip geocoding helpers into ActionController classes.
20
- # ActionController::Base.send :include, GeoKit::IpGeocodeLookup
21
- else
22
- message=%q(WARNING: geokit-rails requires the Geokit gem. You either don't have the gem installed,
23
- or you haven't told Rails to require it. If you're using a recent version of Rails:
24
- config.gem "geokit" # in config/environment.rb
25
- and of course install the gem: sudo gem install geokit)
26
- puts message
27
- Rails.logger.error message
28
- end
@@ -1,469 +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] = my_merge_conditions(options[:conditions], distance_condition)
341
- end
342
- end
343
-
344
- # http://stackoverflow.com/questions/6409398/undefined-method-merge-conditions-for-geokit-rails
345
- def my_merge_conditions(*conditions)
346
- segments = []
347
- conditions.each do |condition|
348
- unless condition.blank?
349
- sql = sanitize_sql(condition)
350
- segments << sql unless sql.blank?
351
- end
352
- end
353
- "(#{segments.join(') AND (')})" unless segments.empty?
354
- end
355
- # enough hacking
356
-
357
- # Alters the conditions to include rectangular bounds conditions.
358
- def apply_bounds_conditions(options,bounds)
359
- sw,ne = bounds.sw, bounds.ne
360
- 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}"
361
- bounds_sql = "#{qualified_lat_column_name}>#{sw.lat} AND #{qualified_lat_column_name}<#{ne.lat} AND #{lng_sql}"
362
- options[:conditions] = my_merge_conditions(options[:conditions], bounds_sql)
363
- end
364
-
365
- # Extracts the origin instance out of the options if it exists and returns
366
- # it. If there is no origin, looks for latitude and longitude values to
367
- # create an origin. The side-effect of the method is to remove these
368
- # option keys from the hash.
369
- def extract_origin_from_options(options)
370
- origin = options.delete(:origin)
371
- res = normalize_point_to_lat_lng(origin) if origin
372
- res
373
- end
374
-
375
- # Extract the units out of the options if it exists and returns it. If
376
- # there is no :units key, it uses the default. The side effect of the
377
- # method is to remove the :units key from the options hash.
378
- def extract_units_from_options(options)
379
- units = options[:units] || default_units
380
- options.delete(:units)
381
- units
382
- end
383
-
384
- # Extract the formula out of the options if it exists and returns it. If
385
- # there is no :formula key, it uses the default. The side effect of the
386
- # method is to remove the :formula key from the options hash.
387
- def extract_formula_from_options(options)
388
- formula = options[:formula] || default_formula
389
- options.delete(:formula)
390
- formula
391
- end
392
-
393
- def extract_bounds_from_options(options)
394
- bounds = options.delete(:bounds)
395
- bounds = Geokit::Bounds.normalize(bounds) if bounds
396
- end
397
-
398
- # Geocode IP address.
399
- def geocode_ip_address(origin)
400
- geo_location = Geokit::Geocoders::MultiGeocoder.geocode(origin)
401
- return geo_location if geo_location.success
402
- raise Geokit::Geocoders::GeocodeError
403
- end
404
-
405
- # Given a point in a variety of (an address to geocode,
406
- # an array of [lat,lng], or an object with appropriate lat/lng methods, an IP addres)
407
- # this method will normalize it into a Geokit::LatLng instance. The only thing this
408
- # method adds on top of LatLng#normalize is handling of IP addresses
409
- def normalize_point_to_lat_lng(point)
410
- res = geocode_ip_address(point) if point.is_a?(String) && /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(point)
411
- res = Geokit::LatLng.normalize(point) unless res
412
- res
413
- end
414
-
415
- # Augments the select with the distance SQL.
416
- def add_distance_to_select(options, origin, units=default_units, formula=default_formula)
417
- if origin
418
- distance_selector = distance_sql(origin, units, formula) + " AS #{distance_column_name}"
419
- selector = options.has_key?(:select) && options[:select] ? options[:select] : "*"
420
- options[:select] = "#{selector}, #{distance_selector}"
421
- end
422
- end
423
-
424
- # Looks for the distance column and replaces it with the distance sql. If an origin was not
425
- # passed in and the distance column exists, we leave it to be flagged as bad SQL by the database.
426
- # Conditions are either a string or an array. In the case of an array, the first entry contains
427
- # the condition.
428
- def substitute_distance_in_conditions(options, origin, units=default_units, formula=default_formula)
429
- condition = options[:conditions].is_a?(String) ? options[:conditions] : options[:conditions].first
430
- pattern = Regexp.new("\\b#{distance_column_name}\\b")
431
- condition.gsub!(pattern, distance_sql(origin, units, formula))
432
- end
433
-
434
- # Returns the distance SQL using the spherical world formula (Haversine). The SQL is tuned
435
- # to the database in use.
436
- def sphere_distance_sql(origin, units)
437
- lat = deg2rad(origin.lat)
438
- lng = deg2rad(origin.lng)
439
- multiplier = units_sphere_multiplier(units)
440
-
441
- adapter.sphere_distance_sql(lat, lng, multiplier) if adapter
442
- end
443
-
444
- # Returns the distance SQL using the flat-world formula (Phythagorean Theory). The SQL is tuned
445
- # to the database in use.
446
- def flat_distance_sql(origin, units)
447
- lat_degree_units = units_per_latitude_degree(units)
448
- lng_degree_units = units_per_longitude_degree(origin.lat, units)
449
-
450
- adapter.flat_distance_sql(origin, lat_degree_units, lng_degree_units)
451
- end
452
- end
453
- end
454
- end
455
-
456
- # Extend Array with a sort_by_distance method.
457
- class Array
458
- # This method creates a "distance" attribute on each object, calculates the
459
- # distance from the passed origin, and finally sorts the array by the
460
- # resulting distance.
461
- def sort_by_distance_from(origin, opts={})
462
- distance_attribute_name = opts.delete(:distance_attribute_name) || 'distance'
463
- self.each do |e|
464
- e.class.send(:attr_accessor, distance_attribute_name) if !e.respond_to?("#{distance_attribute_name}=")
465
- e.send("#{distance_attribute_name}=", e.distance_to(origin,opts))
466
- end
467
- self.sort!{|a,b|a.send(distance_attribute_name) <=> b.send(distance_attribute_name)}
468
- end
469
- end