geokit-rails 2.0.1 → 2.3.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of geokit-rails might be problematic. Click here for more details.

@@ -0,0 +1,17 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rdoc/task'
5
+ Rake::RDocTask.new do |rdoc|
6
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
7
+
8
+ rdoc.rdoc_dir = 'rdoc'
9
+ rdoc.title = "geokit-rails #{version}"
10
+ rdoc.rdoc_files.include('README*')
11
+ rdoc.rdoc_files.include('lib/**/*.rb')
12
+ end
13
+
14
+ load 'test/tasks.rake'
15
+
16
+ desc 'Default: run unit tests.'
17
+ task :default => :test
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development, :test do
4
+ gem 'activerecord', '~> 3.2.0'
5
+ end
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development, :test do
4
+ gem 'activerecord', '~> 4.0'
5
+ gem 'test-unit'
6
+ end
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development, :test do
4
+ gem 'activerecord', '~> 5.0'
5
+ gem 'test-unit'
6
+ end
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path("../lib/geokit-rails/version", __FILE__)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "geokit-rails"
6
+ spec.version = GeokitRails::VERSION
7
+ spec.authors = ["Michael Noack", "Andre Lewis", "Bill Eisenhauer", "Jeremy Lecour"]
8
+ spec.email = ["michael+geokit@noack.com.au", "andre@earthcode.com", "bill_eisenhauer@yahoo.com", "jeremy.lecour@gmail.com"]
9
+ spec.summary = "Integrate Geokit with Rails"
10
+ spec.description = "Official Geokit plugin for Rails/ActiveRecord. Provides location-based goodness for your Rails app. Requires the Geokit gem."
11
+ spec.summary = "Geokit helpers for rails apps."
12
+ spec.homepage = "http://github.com/geokit/geokit-rails"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency 'rails', '>= 3.0'
21
+ spec.add_dependency 'geokit', '~> 1.5'
22
+ spec.add_development_dependency "bundler", "> 1.0"
23
+ spec.add_development_dependency "simplecov", "~> 0.16.1"
24
+ spec.add_development_dependency "simplecov-rcov"
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_development_dependency 'test-unit'
27
+ spec.add_development_dependency "mocha", "~> 0.9"
28
+ spec.add_development_dependency 'coveralls'
29
+ spec.add_development_dependency "mysql2", "~> 0.2"
30
+ spec.add_development_dependency "activerecord-mysql2spatial-adapter"
31
+ spec.add_development_dependency "pg", "~> 0.10"
32
+ spec.add_development_dependency "sqlite3"
33
+ end
@@ -0,0 +1,13 @@
1
+ module GeokitRails
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path("../../templates", __FILE__)
5
+
6
+ desc "Creates a sample Geokit initializer."
7
+
8
+ def copy_initializer
9
+ copy_file "geokit_config.rb", "config/initializers/geokit_config.rb"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,100 @@
1
+ # These defaults are used in Geokit::Mappable.distance_to and acts_as_mappable
2
+ Geokit::default_units = :miles # others :kms, :nms, :meters
3
+ Geokit::default_formula = :sphere
4
+
5
+ # This is the timeout value in seconds to be used for calls to the geocoder web
6
+ # services. For no timeout at all, comment out the setting. The timeout unit
7
+ # is in seconds.
8
+ Geokit::Geocoders::request_timeout = 3
9
+
10
+ # This setting can be used if web service calls must be routed through a proxy.
11
+ # These setting can be nil if not needed, otherwise, a valid URI must be
12
+ # filled in at a minimum. If the proxy requires authentication, the username
13
+ # and password can be provided as well.
14
+ # Geokit::Geocoders::proxy = 'https://user:password@host:port'
15
+
16
+ # This is your yahoo application key for the Yahoo Geocoder.
17
+ # See http://developer.yahoo.com/faq/index.html#appid
18
+ # and http://developer.yahoo.com/maps/rest/V1/geocode.html
19
+ # Geokit::Geocoders::YahooGeocoder.key = 'REPLACE_WITH_YOUR_YAHOO_KEY'
20
+ # Geokit::Geocoders::YahooGeocoder.secret = 'REPLACE_WITH_YOUR_YAHOO_SECRET'
21
+
22
+ # This is your Google Maps geocoder keys (all optional).
23
+ # See http://www.google.com/apis/maps/signup.html
24
+ # and http://www.google.com/apis/maps/documentation/#Geocoding_Examples
25
+ # Geokit::Geocoders::GoogleGeocoder.client_id = ''
26
+ # Geokit::Geocoders::GoogleGeocoder.cryptographic_key = ''
27
+ # Geokit::Geocoders::GoogleGeocoder.channel = ''
28
+
29
+ # You can also use the free API key instead of signed requests
30
+ # See https://developers.google.com/maps/documentation/geocoding/#api_key
31
+ # Geokit::Geocoders::GoogleGeocoder.api_key = ''
32
+
33
+ # You can also set multiple API KEYS for different domains that may be directed
34
+ # to this same application.
35
+ # The domain from which the current user is being directed will automatically
36
+ # be updated for Geokit via
37
+ # the GeocoderControl class, which gets it's begin filter mixed
38
+ # into the ActionController.
39
+ # You define these keys with a Hash as follows:
40
+ # Geokit::Geocoders::google = {
41
+ # 'rubyonrails.org' => 'RUBY_ON_RAILS_API_KEY',
42
+ # ' ruby-docs.org' => 'RUBY_DOCS_API_KEY' }
43
+
44
+ # This is your username and password for geocoder.us.
45
+ # To use the free service, the value can be set to nil or false. For
46
+ # usage tied to an account, the value should be set to username:password.
47
+ # See http://geocoder.us
48
+ # and http://geocoder.us/user/signup
49
+ # Geokit::Geocoders::UsGeocoder.key = 'username:password'
50
+
51
+ # This is your authorization key for geocoder.ca.
52
+ # To use the free service, the value can be set to nil or false. For
53
+ # usage tied to an account, set the value to the key obtained from
54
+ # Geocoder.ca.
55
+ # See http://geocoder.ca
56
+ # and http://geocoder.ca/?register=1
57
+ # Geokit::Geocoders::CaGeocoder.key = 'KEY'
58
+
59
+ # This is your username key for geonames.
60
+ # To use this service either free or premium, you must register a key.
61
+ # See http://www.geonames.org
62
+ # Geokit::Geocoders::GeonamesGeocoder.key = 'KEY'
63
+
64
+ # Most other geocoders need either no setup or a key
65
+ # Geokit::Geocoders::BingGeocoder.key = ''
66
+ # Geokit::Geocoders::MapQuestGeocoder.key = ''
67
+ # Geokit::Geocoders::YandexGeocoder.key = ''
68
+ # Geokit::Geocoders::MapboxGeocoder.key = 'ACCESS_TOKEN'
69
+ # Geokit::Geocoders::OpencageGeocoder.key = 'some_api_key'
70
+
71
+ # Geonames has a free service and a premium service, each using a different URL
72
+ # GeonamesGeocoder.premium = true will use http://ws.geonames.net (premium)
73
+ # GeonamesGeocoder.premium = false will use http://api.geonames.org (free)
74
+ # Geokit::Geocoders::GeonamesGeocoder.premium = false
75
+
76
+ # require "external_geocoder.rb"
77
+ # Please see the section "writing your own geocoders" for more information.
78
+ # Geokit::Geocoders::external_key = 'REPLACE_WITH_YOUR_API_KEY'
79
+
80
+ # This is the order in which the geocoders are called in a failover scenario
81
+ # If you only want to use a single geocoder, put a single symbol in the array.
82
+ # Valid symbols are :google, :yahoo, :us, and :ca.
83
+ # Be aware that there are Terms of Use restrictions on how you can use the
84
+ # various geocoders. Make sure you read up on relevant Terms of Use for each
85
+ # geocoder you are going to use.
86
+ # Geokit::Geocoders::provider_order = [:google,:us]
87
+
88
+ # The IP provider order. Valid symbols are :ip,:geo_plugin.
89
+ # As before, make sure you read up on relevant Terms of Use for each.
90
+ # Geokit::Geocoders::ip_provider_order = [:external,:geo_plugin,:ip]
91
+
92
+ # Disable HTTPS globally. This option can also be set on individual
93
+ # geocoder classes.
94
+ # Geokit::Geocoders::secure = false
95
+
96
+ # Control verification of the server certificate for geocoders using HTTPS
97
+ # Geokit::Geocoders::ssl_verify_mode = OpenSSL::SSL::VERIFY_(PEER/NONE)
98
+ # Setting this to VERIFY_NONE may be needed on systems that don't have
99
+ # a complete or up to date root certificate store. Only applies to
100
+ # the Net::HTTP adapter.
@@ -3,7 +3,6 @@ require 'geokit'
3
3
  require 'geokit-rails/railtie'
4
4
  require 'geokit-rails/core_extensions'
5
5
 
6
- require 'geokit-rails/defaults'
7
6
  require 'geokit-rails/adapters/abstract'
8
7
  require 'geokit-rails/acts_as_mappable'
9
8
  require 'geokit-rails/geocoder_control'
@@ -11,6 +11,8 @@ module Geokit
11
11
  extend ActiveSupport::Concern
12
12
 
13
13
  module ClassMethods # :nodoc:
14
+ OPTION_SYMBOLS = [ :distance_column_name, :default_units, :default_formula, :lat_column_name, :lng_column_name, :qualified_lat_column_name, :qualified_lng_column_name, :skip_loading ]
15
+
14
16
  def acts_as_mappable(options = {})
15
17
  metaclass = (class << self; self; end)
16
18
 
@@ -21,20 +23,21 @@ module Geokit
21
23
 
22
24
  if reflection = Geokit::ActsAsMappable.end_of_reflection_chain(self.through, self)
23
25
  metaclass.instance_eval do
24
- [ :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|
26
+ OPTION_SYMBOLS.each do |method_name|
25
27
  define_method method_name do
26
28
  reflection.klass.send(method_name)
27
29
  end
28
30
  end
29
31
  end
30
32
  else
31
- cattr_accessor :distance_column_name, :default_units, :default_formula, :lat_column_name, :lng_column_name, :qualified_lat_column_name, :qualified_lng_column_name
33
+ cattr_accessor *OPTION_SYMBOLS
32
34
 
33
35
  self.distance_column_name = options[:distance_column_name] || 'distance'
34
36
  self.default_units = options[:default_units] || Geokit::default_units
35
37
  self.default_formula = options[:default_formula] || Geokit::default_formula
36
38
  self.lat_column_name = options[:lat_column_name] || 'lat'
37
39
  self.lng_column_name = options[:lng_column_name] || 'lng'
40
+ self.skip_loading = options[:skip_loading]
38
41
  self.qualified_lat_column_name = "#{table_name}.#{lat_column_name}"
39
42
  self.qualified_lng_column_name = "#{table_name}.#{lng_column_name}"
40
43
 
@@ -91,11 +94,21 @@ module Geokit
91
94
  module ClassMethods
92
95
 
93
96
  # A proxy to an instance of a finder adapter, inferred from the connection's adapter.
94
- def adapter
95
- @adapter ||= begin
96
- require File.join(File.dirname(__FILE__), 'adapters', connection.adapter_name.downcase)
97
+ def geokit_finder_adapter
98
+ @geokit_finder_adapter ||= begin
99
+ unless Adapters.const_defined?(connection.adapter_name.camelcase)
100
+ filename = connection.adapter_name.downcase
101
+ require File.join("geokit-rails", "adapters", filename)
102
+ end
97
103
  klass = Adapters.const_get(connection.adapter_name.camelcase)
98
- klass.load(self) unless klass.loaded
104
+ if klass.class == Module
105
+ # For some reason Mysql2 adapter was defined in Adapters.constants but was Module instead of a Class
106
+ filename = connection.adapter_name.downcase
107
+ require File.join("geokit-rails", "adapters", filename)
108
+ # Re-init the klass after require
109
+ klass = Adapters.const_get(connection.adapter_name.camelcase)
110
+ end
111
+ klass.load(self) unless klass.loaded || skip_loading
99
112
  klass.new(self)
100
113
  rescue LoadError
101
114
  raise UnsupportedAdapter, "`#{connection.adapter_name.downcase}` is not a supported adapter."
@@ -104,8 +117,13 @@ module Geokit
104
117
 
105
118
  def within(distance, options = {})
106
119
  options[:within] = distance
107
- #geo_scope(options)
108
- where(distance_conditions(options))
120
+ # Add bounding box to speed up SQL request.
121
+ bounds = formulate_bounds_from_distance(
122
+ options,
123
+ normalize_point_to_lat_lng(options[:origin]),
124
+ options[:units] || default_units)
125
+ with_latlng.where(bound_conditions(bounds)).
126
+ where(distance_conditions(options))
109
127
  end
110
128
  alias inside within
111
129
 
@@ -123,30 +141,35 @@ module Geokit
123
141
  end
124
142
 
125
143
  def in_bounds(bounds, options = {})
144
+ inclusive = options.delete(:inclusive) || false
126
145
  options[:bounds] = bounds
127
146
  #geo_scope(options)
128
147
  #where(distance_conditions(options))
129
148
  bounds = extract_bounds_from_options(options)
130
- where(bound_conditions(bounds))
149
+ where(bound_conditions(bounds, inclusive))
131
150
  end
132
151
 
133
152
  def by_distance(options = {})
134
153
  origin = extract_origin_from_options(options)
135
154
  units = extract_units_from_options(options)
136
155
  formula = extract_formula_from_options(options)
137
- bounds = extract_bounds_from_options(options)
138
156
  distance_column_name = distance_sql(origin, units, formula)
139
- #geo_scope(options).order("#{distance_column_name} asc")
140
- order("#{distance_column_name} asc")
157
+ with_latlng.order(
158
+ Arel.sql(distance_column_name).send(options[:reverse] ? 'desc' : 'asc')
159
+ )
160
+ end
161
+
162
+ def with_latlng
163
+ where("#{qualified_lat_column_name} IS NOT NULL AND #{qualified_lng_column_name} IS NOT NULL")
141
164
  end
142
165
 
143
166
  def closest(options = {})
144
- by_distance(options).first(1)
167
+ by_distance(options).limit(1)
145
168
  end
146
169
  alias nearest closest
147
170
 
148
171
  def farthest(options = {})
149
- by_distance(options).last(1)
172
+ by_distance({:reverse => true}.merge(options)).limit(1)
150
173
  end
151
174
 
152
175
  #def geo_scope(options = {})
@@ -162,9 +185,9 @@ module Geokit
162
185
 
163
186
  # if origin
164
187
  # arel.distance_formula = distance_sql(origin, units, formula)
165
- #
188
+ #
166
189
  # if arel.select_values.blank?
167
- # star_select = Arel::SqlLiteral.new(arel.quoted_table_name + '.*')
190
+ # star_select = Arel::Nodes::SqlLiteral.new(arel.quoted_table_name + '.*')
168
191
  # arel = arel.select(star_select)
169
192
  # end
170
193
  # end
@@ -211,10 +234,10 @@ module Geokit
211
234
  # If it's a :within query, add a bounding box to improve performance.
212
235
  # This only gets called if a :bounds argument is not otherwise supplied.
213
236
  def formulate_bounds_from_distance(options, origin, units)
214
- distance = options[:within] if options.has_key?(:within)
237
+ distance = options[:within] if options.has_key?(:within) && options[:within].is_a?(Numeric)
215
238
  distance = options[:range].last-(options[:range].exclude_end?? 1 : 0) if options.has_key?(:range)
216
239
  if distance
217
- res=Geokit::Bounds.from_point_and_radius(origin,distance,:units=>units)
240
+ Geokit::Bounds.from_point_and_radius(origin,distance,:units=>units)
218
241
  else
219
242
  nil
220
243
  end
@@ -224,25 +247,41 @@ module Geokit
224
247
  origin = extract_origin_from_options(options)
225
248
  units = extract_units_from_options(options)
226
249
  formula = extract_formula_from_options(options)
227
- bounds = extract_bounds_from_options(options)
228
250
  distance_column_name = distance_sql(origin, units, formula)
229
251
 
230
- res = if options.has_key?(:within)
231
- "#{distance_column_name} <= #{options[:within]}"
252
+ if options.has_key?(:within)
253
+ Arel.sql(distance_column_name).lteq(options[:within])
232
254
  elsif options.has_key?(:beyond)
233
- "#{distance_column_name} > #{options[:beyond]}"
255
+ Arel.sql(distance_column_name).gt(options[:beyond])
234
256
  elsif options.has_key?(:range)
235
- "#{distance_column_name} >= #{options[:range].first} AND #{distance_column_name} <#{'=' unless options[:range].exclude_end?} #{options[:range].last}"
257
+ min_condition = Arel.sql(distance_column_name).gteq(options[:range].begin)
258
+ max_condition = if options[:range].exclude_end?
259
+ Arel.sql(distance_column_name).lt(options[:range].end)
260
+ else
261
+ Arel.sql(distance_column_name).lteq(options[:range].end)
262
+ end
263
+ min_condition.and(max_condition)
236
264
  end
237
- Arel::SqlLiteral.new("(#{res})") if res.present?
238
265
  end
239
266
 
240
- def bound_conditions(bounds)
267
+ def bound_conditions(bounds, inclusive = false)
268
+ return nil unless bounds
269
+ if inclusive
270
+ lt_operator = :lteq
271
+ gt_operator = :gteq
272
+ else
273
+ lt_operator = :lt
274
+ gt_operator = :gt
275
+ end
241
276
  sw,ne = bounds.sw, bounds.ne
242
- 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}"
243
- res = "#{qualified_lat_column_name}>#{sw.lat} AND #{qualified_lat_column_name}<#{ne.lat} AND #{lng_sql}"
244
- #Arel::SqlLiteral.new("(#{res})") if res.present?
245
- res if res.present?
277
+ lat, lng = Arel.sql(qualified_lat_column_name), Arel.sql(qualified_lng_column_name)
278
+ lat.send(gt_operator, sw.lat).and(lat.send(lt_operator, ne.lat)).and(
279
+ if bounds.crosses_meridian?
280
+ lng.send(lt_operator, ne.lng).or(lng.send(gt_operator, sw.lng))
281
+ else
282
+ lng.send(gt_operator, sw.lng).and(lng.send(lt_operator, ne.lng))
283
+ end
284
+ )
246
285
  end
247
286
 
248
287
  # Extracts the origin instance out of the options if it exists and returns
@@ -315,20 +354,31 @@ module Geokit
315
354
  # Returns the distance SQL using the spherical world formula (Haversine). The SQL is tuned
316
355
  # to the database in use.
317
356
  def sphere_distance_sql(origin, units)
318
- lat = deg2rad(origin.lat)
319
- lng = deg2rad(origin.lng)
357
+ # "origin" can be a Geokit::LatLng (with :lat and :lng methods), e.g.
358
+ # when using geo_scope or it can be an ActsAsMappable with customized
359
+ # latitude and longitude methods, e.g. when using distance_sql.
360
+ lat = deg2rad(get_lat(origin))
361
+ lng = deg2rad(get_lng(origin))
320
362
  multiplier = units_sphere_multiplier(units)
321
-
322
- adapter.sphere_distance_sql(lat, lng, multiplier) if adapter
363
+ geokit_finder_adapter.sphere_distance_sql(lat, lng, multiplier) if geokit_finder_adapter
323
364
  end
324
365
 
325
366
  # Returns the distance SQL using the flat-world formula (Phythagorean Theory). The SQL is tuned
326
367
  # to the database in use.
327
368
  def flat_distance_sql(origin, units)
328
369
  lat_degree_units = units_per_latitude_degree(units)
329
- lng_degree_units = units_per_longitude_degree(origin.lat, units)
370
+ lng_degree_units = units_per_longitude_degree(get_lat(origin), units)
371
+ geokit_finder_adapter.flat_distance_sql(origin, lat_degree_units, lng_degree_units)
372
+ end
373
+
374
+ def get_lat(origin)
375
+ origin.respond_to?(:lat) ? origin.lat \
376
+ : origin.send(:"#{lat_column_name}")
377
+ end
330
378
 
331
- adapter.flat_distance_sql(origin, lat_degree_units, lng_degree_units)
379
+ def get_lng(origin)
380
+ origin.respond_to?(:lng) ? origin.lng \
381
+ : origin.send(:"#{lng_column_name}")
332
382
  end
333
383
 
334
384
  end # ClassMethods
@@ -339,8 +389,8 @@ module Geokit
339
389
  geo=Geokit::Geocoders::MultiGeocoder.geocode(address)
340
390
 
341
391
  if geo.success
342
- self.send("#{lat_column_name}=", geo.lat)
343
- self.send("#{lng_column_name}=", geo.lng)
392
+ self.send("#{lat_column_name}=", geo.send(:"#{lat_column_name}"))
393
+ self.send("#{lng_column_name}=", geo.send(:"#{lng_column_name}"))
344
394
  else
345
395
  errors.add(auto_geocode_field, auto_geocode_error_message)
346
396
  end
@@ -0,0 +1,32 @@
1
+ module Geokit
2
+ module Adapters
3
+ class OracleEnhanced < Abstract
4
+ TO_DEGREES = Math::PI / 180
5
+ def sphere_distance_sql(lat, lng, multiplier)
6
+ %{
7
+ (
8
+ ACOS(
9
+ COS(#{lat}) * COS(#{lng}) *
10
+ COS(#{TO_DEGREES} * #{qualified_lat_column_name}) *
11
+ COS(#{TO_DEGREES} * #{qualified_lng_column_name}) +
12
+ COS(#{lat}) * SIN(#{lng}) *
13
+ COS(#{TO_DEGREES} * #{qualified_lat_column_name}) *
14
+ SIN(#{TO_DEGREES} * #{qualified_lng_column_name}) +
15
+ SIN(#{lat}) *
16
+ SIN(#{TO_DEGREES} * #{qualified_lat_column_name})
17
+ ) *
18
+ #{multiplier})
19
+ }
20
+ end
21
+
22
+ def flat_distance_sql(origin, lat_degree_units, lng_degree_units)
23
+ %{
24
+ SQRT(
25
+ POWER(#{lat_degree_units}*(#{origin.lat}-#{qualified_lat_column_name}), 2)
26
+ POWER(#{lng_degree_units}*(#{origin.lng}-#{qualified_lng_column_name}), 2)
27
+ )
28
+ }
29
+ end
30
+ end
31
+ end
32
+ end