cb_mongoid_spacial 0.2.16
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.
- data/.document +5 -0
- data/.gitignore +49 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +20 -0
- data/README.md +180 -0
- data/Rakefile +18 -0
- data/lib/mongoid_spacial.rb +11 -0
- data/lib/mongoid_spacial/contexts/mongo.rb +115 -0
- data/lib/mongoid_spacial/criteria.rb +5 -0
- data/lib/mongoid_spacial/criterion.rb +3 -0
- data/lib/mongoid_spacial/criterion/complex.rb +19 -0
- data/lib/mongoid_spacial/criterion/inclusion.rb +14 -0
- data/lib/mongoid_spacial/criterion/near_spacial.rb +50 -0
- data/lib/mongoid_spacial/criterion/within_spacial.rb +60 -0
- data/lib/mongoid_spacial/extentions/hash/criteria_helpers.rb +22 -0
- data/lib/mongoid_spacial/extentions/symbol/inflections.rb +46 -0
- data/lib/mongoid_spacial/field_option.rb +38 -0
- data/lib/mongoid_spacial/finders.rb +5 -0
- data/lib/mongoid_spacial/spacial.rb +56 -0
- data/lib/mongoid_spacial/spacial/core_ext.rb +27 -0
- data/lib/mongoid_spacial/spacial/document.rb +29 -0
- data/lib/mongoid_spacial/spacial/formulas.rb +52 -0
- data/lib/mongoid_spacial/spacial/geo_near_results.rb +140 -0
- data/lib/mongoid_spacial/spacial/version.rb +5 -0
- data/mongoid_spacial.gemspec +27 -0
- data/spec/config/mongod.conf +3 -0
- data/spec/config/mongoid.yml +18 -0
- data/spec/functional/mongoid/contexts/mongo_spec.rb +125 -0
- data/spec/functional/mongoid/criterion/inclusion_spec.rb +356 -0
- data/spec/functional/mongoid/spacial/geo_near_results_spec.rb +73 -0
- data/spec/functional/mongoid/spacial_spec.rb +18 -0
- data/spec/models/account.rb +19 -0
- data/spec/models/acolyte.rb +9 -0
- data/spec/models/address.rb +62 -0
- data/spec/models/address_component.rb +5 -0
- data/spec/models/agent.rb +10 -0
- data/spec/models/alert.rb +5 -0
- data/spec/models/animal.rb +21 -0
- data/spec/models/answer.rb +4 -0
- data/spec/models/bar.rb +9 -0
- data/spec/models/birthday.rb +13 -0
- data/spec/models/book.rb +5 -0
- data/spec/models/business.rb +7 -0
- data/spec/models/callbacks.rb +57 -0
- data/spec/models/category.rb +13 -0
- data/spec/models/circus.rb +7 -0
- data/spec/models/comment.rb +13 -0
- data/spec/models/country_code.rb +6 -0
- data/spec/models/description.rb +11 -0
- data/spec/models/division.rb +5 -0
- data/spec/models/drug.rb +5 -0
- data/spec/models/employer.rb +5 -0
- data/spec/models/entry.rb +6 -0
- data/spec/models/event.rb +20 -0
- data/spec/models/favorite.rb +6 -0
- data/spec/models/fruits.rb +11 -0
- data/spec/models/game.rb +18 -0
- data/spec/models/ghost.rb +7 -0
- data/spec/models/house.rb +4 -0
- data/spec/models/inheritance.rb +90 -0
- data/spec/models/league.rb +5 -0
- data/spec/models/location.rb +5 -0
- data/spec/models/login.rb +6 -0
- data/spec/models/membership.rb +4 -0
- data/spec/models/mixed_drink.rb +4 -0
- data/spec/models/name.rb +13 -0
- data/spec/models/namespacing.rb +11 -0
- data/spec/models/observed.rb +41 -0
- data/spec/models/override.rb +16 -0
- data/spec/models/owner.rb +6 -0
- data/spec/models/page.rb +5 -0
- data/spec/models/page_question.rb +4 -0
- data/spec/models/paranoid_post.rb +18 -0
- data/spec/models/parents.rb +32 -0
- data/spec/models/patient.rb +15 -0
- data/spec/models/person.rb +146 -0
- data/spec/models/pet.rb +7 -0
- data/spec/models/pet_owner.rb +6 -0
- data/spec/models/phone.rb +7 -0
- data/spec/models/player.rb +23 -0
- data/spec/models/post.rb +26 -0
- data/spec/models/preference.rb +9 -0
- data/spec/models/question.rb +8 -0
- data/spec/models/quiz.rb +6 -0
- data/spec/models/rating.rb +8 -0
- data/spec/models/river.rb +20 -0
- data/spec/models/role.rb +5 -0
- data/spec/models/service.rb +6 -0
- data/spec/models/shelf.rb +5 -0
- data/spec/models/slave_address_numbers.rb +14 -0
- data/spec/models/survey.rb +5 -0
- data/spec/models/tag.rb +6 -0
- data/spec/models/tracking_id_validation_history.rb +25 -0
- data/spec/models/translation.rb +5 -0
- data/spec/models/tree.rb +9 -0
- data/spec/models/user.rb +9 -0
- data/spec/models/user_account.rb +10 -0
- data/spec/models/vet_visit.rb +5 -0
- data/spec/models/video.rb +9 -0
- data/spec/models/wiki_page.rb +6 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/support/authentication.rb +29 -0
- data/spec/unit/mongoid/criterion/complex_spec.rb +15 -0
- data/spec/unit/mongoid/criterion/inclusion_spec.rb +0 -0
- data/spec/unit/mongoid/criterion/near_spacial_spec.rb +39 -0
- data/spec/unit/mongoid/criterion/within_spacial_spec.rb +52 -0
- data/spec/unit/mongoid/spacial/formulas_spec.rb +37 -0
- data/spec/unit/mongoid/spacial_spec.rb +6 -0
- metadata +374 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Mongoid #:nodoc:
|
|
3
|
+
module Criterion #:nodoc:
|
|
4
|
+
module Inclusion
|
|
5
|
+
def near(attributes = {})
|
|
6
|
+
update_selector(attributes, "$near")
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def near_sphere(attributes = {})
|
|
10
|
+
update_selector(attributes, "$near")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Mongoid #:nodoc:
|
|
3
|
+
module Criterion #:nodoc:
|
|
4
|
+
|
|
5
|
+
# NearSpecial criterion is used when performing #near with symbols to get
|
|
6
|
+
# get a shorthand syntax for where clauses.
|
|
7
|
+
#
|
|
8
|
+
# @example Coninputersion of a simple to complex criterion.
|
|
9
|
+
# { :field => { "$nearSphere" => => [20,30]}, '$maxDistance' => 5 }
|
|
10
|
+
# becomes:
|
|
11
|
+
# { :field.near(:sphere) => {:point => [20,30], :max => 5, :unit => :km} }
|
|
12
|
+
class NearSpacial < Complex
|
|
13
|
+
|
|
14
|
+
# Coninputert input to query for near or nearSphere
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# near = NearSpacial.new(:key => :field, :operator => "near")
|
|
18
|
+
# near.to_mongo_query({:point => [:50,50], :max => 5, :unit => :km}) => { '$near : [50,50]' , '$maxDistance' : 5 }
|
|
19
|
+
#
|
|
20
|
+
# @param [Hash,Array] input input to coninputer to query
|
|
21
|
+
def to_mongo_query(input)
|
|
22
|
+
if input.kind_of?(Hash)
|
|
23
|
+
raise ':point required to make valid query' unless input[:point]
|
|
24
|
+
input[:point] = input[:point].to_lng_lat if input[:point].respond_to?(:to_lng_lat)
|
|
25
|
+
query = {"$#{operator}" => input[:point] }
|
|
26
|
+
if input[:max]
|
|
27
|
+
query['$maxDistance'] = input[:max].to_f
|
|
28
|
+
|
|
29
|
+
if unit = Mongoid::Spacial.earth_radius[input[:unit]]
|
|
30
|
+
unit *= Mongoid::Spacial::RAD_PER_DEG unless operator =~ /sphere/i
|
|
31
|
+
input[:unit] = unit
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
query['$maxDistance'] = query['$maxDistance']/input[:unit].to_f if input[:unit]
|
|
35
|
+
end
|
|
36
|
+
query
|
|
37
|
+
elsif input.kind_of? Array
|
|
38
|
+
if input.first.kind_of? Numeric
|
|
39
|
+
{"$#{operator}" => input }
|
|
40
|
+
else
|
|
41
|
+
input[0] = input[0].to_lng_lat if input[0].respond_to?(:to_lng_lat)
|
|
42
|
+
{"$#{operator}" => input[0], '$maxDistance' => input[1] }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Mongoid #:nodoc:
|
|
3
|
+
module Criterion #:nodoc:
|
|
4
|
+
|
|
5
|
+
# WithinSpecial criterion is used when performing #within with symbols to get
|
|
6
|
+
# get a shorthand syntax for where clauses.
|
|
7
|
+
#
|
|
8
|
+
# @example Conversion of a simple to complex criterion.
|
|
9
|
+
# { :field => { "$within" => {'$center' => [20,30]} } }
|
|
10
|
+
# becomes:
|
|
11
|
+
# { :field.within(:center) => [20,30] }
|
|
12
|
+
class WithinSpacial < Complex
|
|
13
|
+
|
|
14
|
+
# Convert input to query for box, polygon, center, and centerSphere
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# within = WithinSpacial.new(opts[:key] => 'point', :operator => 'center')
|
|
18
|
+
# within.to_mongo_query({:point => [20,30], :max => 5, :unit => :km}) #=>
|
|
19
|
+
#
|
|
20
|
+
# @param [Hash,Array] input Variable to conver to query
|
|
21
|
+
def to_mongo_query(input)
|
|
22
|
+
if ['box','polygon'].index(@operator)
|
|
23
|
+
input = input.values if input.kind_of?(Hash)
|
|
24
|
+
if input.respond_to?(:map)
|
|
25
|
+
input.map!{ |v| (v.respond_to?(:to_lng_lat)) ? v.to_lng_lat : v }
|
|
26
|
+
else
|
|
27
|
+
input
|
|
28
|
+
end
|
|
29
|
+
elsif ['center','centerSphere'].index(@operator)
|
|
30
|
+
|
|
31
|
+
if input.kind_of?(Hash) || input.kind_of?(ActiveSupport::OrderedHash)
|
|
32
|
+
raise ':point required to make valid query' unless input[:point]
|
|
33
|
+
input[:point] = input[:point].to_lng_lat if input[:point].respond_to?(:to_lng_lat)
|
|
34
|
+
if input[:max]
|
|
35
|
+
input[:max] = input[:max].to_f
|
|
36
|
+
|
|
37
|
+
if unit = Mongoid::Spacial.earth_radius[input[:unit]]
|
|
38
|
+
unit *= Mongoid::Spacial::RAD_PER_DEG unless operator =~ /sphere/i
|
|
39
|
+
input[:unit] = unit
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
input[:max] = input[:max]/input[:unit].to_f if input[:unit]
|
|
43
|
+
|
|
44
|
+
input = [input[:point],input[:max]]
|
|
45
|
+
else
|
|
46
|
+
input = input[:point]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if input.kind_of? Array
|
|
51
|
+
input[0] = input[0].to_lng_lat if input[0].respond_to?(:to_lng_lat)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
{'$within' => {"$#{@operator}"=>input} }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Mongoid #:nodoc:
|
|
3
|
+
module Extensions #:nodoc:
|
|
4
|
+
module Hash #:nodoc:
|
|
5
|
+
module CriteriaHelpers #:nodoc:
|
|
6
|
+
def expand_complex_criteria
|
|
7
|
+
hsh = {}
|
|
8
|
+
each_pair do |k,v|
|
|
9
|
+
if k.respond_to?(:key) && k.respond_to?(:to_mongo_query)
|
|
10
|
+
hsh[k.key] ||= {}
|
|
11
|
+
hsh[k.key].merge!(k.to_mongo_query(v))
|
|
12
|
+
else
|
|
13
|
+
hsh[k] = v
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
hsh
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module Mongoid #:nodoc:
|
|
3
|
+
module Extensions #:nodoc:
|
|
4
|
+
module Symbol #:nodoc:
|
|
5
|
+
module Inflections #:nodoc:
|
|
6
|
+
|
|
7
|
+
# return a class that will accept a value to convert the query correctly for near
|
|
8
|
+
#
|
|
9
|
+
# @param [Symbol] calc This accepts :sphere
|
|
10
|
+
#
|
|
11
|
+
# @return [Criterion::NearSpacial]
|
|
12
|
+
|
|
13
|
+
def near(calc = :flat)
|
|
14
|
+
Criterion::NearSpacial.new(:operator => get_op('near',calc), :key => self)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# alias for self.near(:sphere)
|
|
18
|
+
#
|
|
19
|
+
# @return [Criterion::NearSpacial]
|
|
20
|
+
def near_sphere
|
|
21
|
+
self.near(:sphere)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @param [Symbol] shape :box,:polygon,:center,:center_sphere
|
|
25
|
+
#
|
|
26
|
+
# @return [Criterion::WithinSpacial]
|
|
27
|
+
def within(shape)
|
|
28
|
+
shape = get_op(:center,:sphere) if shape == :center_sphere
|
|
29
|
+
Criterion::WithinSpacial.new(:operator => shape.to_s , :key => self)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def get_op operator, calc
|
|
35
|
+
if calc.to_sym == :sphere && Mongoid.master.connection.server_version >= '1.7'
|
|
36
|
+
"#{operator}Sphere"
|
|
37
|
+
elsif calc.to_sym == :sphere
|
|
38
|
+
raise "MongoDB Server version #{Mongoid.master.connection.server_version} does not have Spherical Calculation"
|
|
39
|
+
else
|
|
40
|
+
operator.to_s
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'ostruct'
|
|
2
|
+
# Field changes to Fields from mongoid 2.0 to mongoid 2.1
|
|
3
|
+
field = (defined?(Mongoid::Field)) ? Mongoid::Field : Mongoid::Fields
|
|
4
|
+
|
|
5
|
+
field.option :spacial do |model,field,options|
|
|
6
|
+
options = {} unless options.kind_of?(Hash)
|
|
7
|
+
lat_meth = options[:lat] || :lat
|
|
8
|
+
lng_meth = options[:lng] || :lng
|
|
9
|
+
model.class_eval do
|
|
10
|
+
self.spacial_fields ||= []
|
|
11
|
+
self.spacial_fields << field.name.to_sym if self.spacial_fields.kind_of? Array
|
|
12
|
+
|
|
13
|
+
define_method "distance_from_#{field.name}" do |*args|
|
|
14
|
+
self.distance_from(field.name, *args)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
define_method field.name do
|
|
18
|
+
output = self[field.name] || [nil,nil]
|
|
19
|
+
output = {lng_meth => output[0], lat_meth => output[1]} unless options[:return_array]
|
|
20
|
+
return options[:class].new(output) if options[:class]
|
|
21
|
+
output
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
define_method "#{field.name}=" do |arg|
|
|
25
|
+
if arg.kind_of?(Hash) && arg[lng_meth] && arg[lat_meth]
|
|
26
|
+
arg = [arg[lng_meth].to_f, arg[lat_meth].to_f]
|
|
27
|
+
elsif arg.respond_to?(:to_lng_lat)
|
|
28
|
+
arg = arg.to_lng_lat
|
|
29
|
+
end
|
|
30
|
+
self[field.name]=arg
|
|
31
|
+
arg = [nil,nil] if arg.nil?
|
|
32
|
+
return arg[0..1] if options[:return_array]
|
|
33
|
+
h = {lng_meth => arg[0], lat_meth => arg[1]}
|
|
34
|
+
return h if options[:class].blank?
|
|
35
|
+
options[:class].new(h)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require 'mongoid_spacial/spacial/core_ext'
|
|
2
|
+
require 'mongoid_spacial/spacial/formulas'
|
|
3
|
+
require 'mongoid_spacial/spacial/document'
|
|
4
|
+
require 'mongoid_spacial/spacial/geo_near_results'
|
|
5
|
+
module Mongoid
|
|
6
|
+
module Spacial
|
|
7
|
+
|
|
8
|
+
EARTH_RADIUS_KM = 6371 # taken directly from mongodb
|
|
9
|
+
|
|
10
|
+
EARTH_RADIUS = {
|
|
11
|
+
:km => EARTH_RADIUS_KM,
|
|
12
|
+
:m => EARTH_RADIUS_KM*1000,
|
|
13
|
+
:mi => EARTH_RADIUS_KM*0.621371192, # taken directly from mongodb
|
|
14
|
+
:ft => EARTH_RADIUS_KM*5280*0.621371192,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
RAD_PER_DEG = Math::PI/180
|
|
18
|
+
|
|
19
|
+
LNG_SYMBOLS = [:x, :lon, :long, :lng, :longitude]
|
|
20
|
+
LAT_SYMBOLS = [:y, :lat, :latitude]
|
|
21
|
+
|
|
22
|
+
def self.distance(p1,p2,opts = {})
|
|
23
|
+
opts[:formula] ||= (opts[:spherical]) ? @@spherical_distance_formula : :pythagorean_theorem
|
|
24
|
+
p1 = p1.to_lng_lat if p1.respond_to?(:to_lng_lat)
|
|
25
|
+
p2 = p2.to_lng_lat if p2.respond_to?(:to_lng_lat)
|
|
26
|
+
|
|
27
|
+
rads = Formulas.send(opts[:formula], p1, p2)
|
|
28
|
+
|
|
29
|
+
if unit = earth_radius[opts[:unit]]
|
|
30
|
+
opts[:unit] = (rads.instance_variable_get("@radian")) ? unit : unit * RAD_PER_DEG
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
rads *= opts[:unit].to_f if opts[:unit]
|
|
34
|
+
rads
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
mattr_accessor :lng_symbols
|
|
38
|
+
@@lng_symbols = LNG_SYMBOLS.dup
|
|
39
|
+
|
|
40
|
+
mattr_accessor :lat_symbols
|
|
41
|
+
@@lat_symbols = LAT_SYMBOLS.dup
|
|
42
|
+
|
|
43
|
+
mattr_accessor :earth_radius
|
|
44
|
+
@@earth_radius = EARTH_RADIUS.dup
|
|
45
|
+
|
|
46
|
+
mattr_accessor :paginator
|
|
47
|
+
@@paginator = :array
|
|
48
|
+
|
|
49
|
+
mattr_accessor :default_per_page
|
|
50
|
+
@@default_per_page = 25
|
|
51
|
+
|
|
52
|
+
mattr_accessor :spherical_distance_formula
|
|
53
|
+
@@spherical_distance_formula = :n_vector
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class Array
|
|
2
|
+
def to_lng_lat
|
|
3
|
+
self[0..1].map(&:to_f)
|
|
4
|
+
end
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class Hash
|
|
8
|
+
def to_lng_lat
|
|
9
|
+
raise "Hash must have at least 2 items" if self.size < 2
|
|
10
|
+
[to_lng, to_lat]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_lat
|
|
14
|
+
v = (Mongoid::Spacial.lat_symbols & self.keys).first
|
|
15
|
+
return self[v].to_f if !v.nil? && self[v]
|
|
16
|
+
raise "Hash must contain #{Mongoid::Spacial.lat_symbols.inspect} if ruby version is less than 1.9" if RUBY_VERSION.to_f < 1.9
|
|
17
|
+
raise "Hash cannot contain #{Mongoid::Spacial.lng_symbols.inspect} as the second item if there is no #{Mongoid::Spacial.lat_symbols.inspect}" if Mongoid::Spacial.lng_symbols.index(self.keys[1])
|
|
18
|
+
self.values[1].to_f
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_lng
|
|
22
|
+
v = (Mongoid::Spacial.lng_symbols & self.keys).first
|
|
23
|
+
return self[v].to_f if !v.nil? && self[v]
|
|
24
|
+
raise "Hash cannot contain #{Mongoid::Spacial.lat_symbols.inspect} as the first item if there is no #{Mongoid::Spacial.lng_symbols.inspect}" if Mongoid::Spacial.lat_symbols.index(self.keys[0])
|
|
25
|
+
self.values[0].to_f
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Mongoid
|
|
2
|
+
module Spacial
|
|
3
|
+
module Document
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
attr_accessor :geo
|
|
8
|
+
cattr_accessor :spacial_fields, :spacial_fields_indexed
|
|
9
|
+
@@spacial_fields = []
|
|
10
|
+
@@spacial_fields_indexed = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ClassMethods #:nodoc:
|
|
14
|
+
# create spacial index for given field
|
|
15
|
+
# @param [String,Symbol] name
|
|
16
|
+
# @param [Hash] options options for spacial_index
|
|
17
|
+
def spacial_index name, *options
|
|
18
|
+
self.spacial_fields_indexed << name
|
|
19
|
+
index [[ name, Mongo::GEO2D ]], *options
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def distance_from(key,p2, opts = {})
|
|
24
|
+
p1 = self.send(key)
|
|
25
|
+
Mongoid::Spacial.distance(p1, p2, opts)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Mongoid
|
|
2
|
+
module Spacial
|
|
3
|
+
module Formulas
|
|
4
|
+
class << self
|
|
5
|
+
def n_vector(point1,point2)
|
|
6
|
+
p1 = point1.map{|deg| deg * RAD_PER_DEG}
|
|
7
|
+
p2 = point2.map{|deg| deg * RAD_PER_DEG}
|
|
8
|
+
|
|
9
|
+
sin_x1 = Math.sin(p1[0])
|
|
10
|
+
cos_x1 = Math.cos(p1[0])
|
|
11
|
+
|
|
12
|
+
sin_y1 = Math.sin(p1[1])
|
|
13
|
+
cos_y1 = Math.cos(p1[1])
|
|
14
|
+
|
|
15
|
+
sin_x2 = Math.sin(p2[0])
|
|
16
|
+
cos_x2 = Math.cos(p2[0])
|
|
17
|
+
|
|
18
|
+
sin_y2 = Math.sin(p2[1])
|
|
19
|
+
cos_y2 = Math.cos(p2[1])
|
|
20
|
+
|
|
21
|
+
cross_prod = (cos_y1*cos_x1 * cos_y2*cos_x2) +
|
|
22
|
+
(cos_y1*sin_x1 * cos_y2*sin_x2) +
|
|
23
|
+
(sin_y1 * sin_y2)
|
|
24
|
+
|
|
25
|
+
return cross_prod > 0 ? 0 : Math::PI if (cross_prod >= 1 || cross_prod <= -1)
|
|
26
|
+
|
|
27
|
+
d = Math.acos(cross_prod)
|
|
28
|
+
d.instance_variable_set("@radian", true)
|
|
29
|
+
d
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def haversine(point1,point2)
|
|
33
|
+
p1 = point1.map{|deg| deg * RAD_PER_DEG}
|
|
34
|
+
p2 = point2.map{|deg| deg * RAD_PER_DEG}
|
|
35
|
+
|
|
36
|
+
dlon = p2[0] - p1[0]
|
|
37
|
+
dlat = p2[1] - p1[1]
|
|
38
|
+
|
|
39
|
+
a = (Math.sin(dlat/2))**2 + Math.cos(p1[1]) * Math.cos(p2[1]) * (Math.sin(dlon/2))**2
|
|
40
|
+
|
|
41
|
+
d = 2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a))
|
|
42
|
+
d.instance_variable_set("@radian", true)
|
|
43
|
+
d
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def pythagorean_theorem(p1, p2)
|
|
47
|
+
Math.sqrt(((p2[0] - p1[0]) ** 2) + ((p2[1] - p1[1]) ** 2))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
module Mongoid
|
|
2
|
+
module Spacial
|
|
3
|
+
class GeoNearResults < Array
|
|
4
|
+
attr_reader :stats, :document, :_original_array, :_original_opts
|
|
5
|
+
attr_accessor :opts
|
|
6
|
+
|
|
7
|
+
def initialize(document,results,opts = {})
|
|
8
|
+
raise "#{document.name} class must include Mongoid::Spacial::Document" unless document.respond_to?(:spacial_fields_indexed)
|
|
9
|
+
@document = document
|
|
10
|
+
@opts = opts
|
|
11
|
+
@_original_opts = opts.clone
|
|
12
|
+
@stats = results['stats'] || {}
|
|
13
|
+
@opts[:skip] ||= 0
|
|
14
|
+
|
|
15
|
+
@_original_array = results['results'].collect do |result|
|
|
16
|
+
res = Mongoid::Factory.from_db(@document, result.delete('obj'))
|
|
17
|
+
res.geo = {}
|
|
18
|
+
# camel case is awkward in ruby when using variables...
|
|
19
|
+
if result['dis']
|
|
20
|
+
res.geo[:distance] = result.delete('dis').to_f
|
|
21
|
+
end
|
|
22
|
+
result.each do |key,value|
|
|
23
|
+
res.geo[key.snakecase.to_sym] = value
|
|
24
|
+
end
|
|
25
|
+
# dist_options[:formula] = opts[:formula] if opts[:formula]
|
|
26
|
+
@opts[:calculate] = @document.spacial_fields_indexed if @document.spacial_fields_indexed.kind_of?(Array) && @opts[:calculate] == true
|
|
27
|
+
if @opts[:calculate]
|
|
28
|
+
@opts[:calculate] = [@opts[:calculate]] unless @opts[:calculate].kind_of? Array
|
|
29
|
+
@opts[:calculate] = @opts[:calculate].map(&:to_sym) & geo_fields
|
|
30
|
+
if @document.spacial_fields_indexed.kind_of?(Array) && @document.spacial_fields_indexed.size == 1
|
|
31
|
+
primary = @document.spacial_fields_indexed.first
|
|
32
|
+
end
|
|
33
|
+
@opts[:calculate].each do |key|
|
|
34
|
+
res.geo[(key.to_s+'_distance').to_sym] = res.distance_from(key,center,{:unit =>@opts[:unit] || @opts[:distance_multiplier], :spherical => @opts[:spherical]} )
|
|
35
|
+
res.geo[:distance] = res.geo[key] if primary && key == primary
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
res
|
|
39
|
+
end
|
|
40
|
+
if @opts[:page]
|
|
41
|
+
start = (@opts[:page]-1)*@opts[:per_page] # assuming current_page is 1 based.
|
|
42
|
+
@_paginated_array = @_original_array.clone
|
|
43
|
+
super(@_paginated_array[@opts[:skip]+start, @opts[:per_page]] || [])
|
|
44
|
+
else
|
|
45
|
+
super(@_original_array[@opts[:skip]..-1] || [])
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def page(*args)
|
|
50
|
+
new_collection = self.clone
|
|
51
|
+
new_collection.page!(*args)
|
|
52
|
+
new_collection
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def page!(page, options = {})
|
|
56
|
+
original = options.delete(:original)
|
|
57
|
+
self.opts.merge!(options)
|
|
58
|
+
self.opts[:paginator] ||= Mongoid::Spacial.paginator
|
|
59
|
+
self.opts[:page] = page
|
|
60
|
+
start = (self.current_page-1)*self.limit_value # assuming current_page is 1 based.
|
|
61
|
+
|
|
62
|
+
if original
|
|
63
|
+
@_paginated_array = @_original_array.clone
|
|
64
|
+
self.replace(@_paginated_array[self.opts[:skip]+start, self.limit_value] || [])
|
|
65
|
+
else
|
|
66
|
+
@_paginated_array ||= self.to_a
|
|
67
|
+
self.replace(@_paginated_array[self.opts[:skip]+start, self.limit_value])
|
|
68
|
+
end
|
|
69
|
+
true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def per(num)
|
|
73
|
+
self.page(current_page, :per_page => num)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def reset!
|
|
77
|
+
self.replace(@_original_array)
|
|
78
|
+
@opts = @_original_opts
|
|
79
|
+
@_paginated_array = nil
|
|
80
|
+
true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def reset
|
|
84
|
+
clone = self.clone
|
|
85
|
+
clone.reset!
|
|
86
|
+
clone
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def total_entries
|
|
90
|
+
(@_paginated_array) ? @_paginated_array.count : @_original_array.count
|
|
91
|
+
end
|
|
92
|
+
alias_method :total_count, :total_entries
|
|
93
|
+
|
|
94
|
+
def current_page
|
|
95
|
+
page = (@opts[:page]) ? @opts[:page].to_i.abs : 1
|
|
96
|
+
(page < 1) ? 1 : page
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def limit_value
|
|
100
|
+
if @opts[:per_page]
|
|
101
|
+
@opts[:per_page] = @opts[:per_page].to_i.abs
|
|
102
|
+
else
|
|
103
|
+
@opts[:per_page] = case self.opts[:paginator]
|
|
104
|
+
when :will_paginate
|
|
105
|
+
@document.per_page
|
|
106
|
+
when :kaminari
|
|
107
|
+
Kaminari.config.default_per_page
|
|
108
|
+
else
|
|
109
|
+
Mongoid::Spacial.default_per_page
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
alias_method :per_page, :limit_value
|
|
114
|
+
|
|
115
|
+
def num_pages
|
|
116
|
+
(self.total_entries && @opts[:per_page]) ? self.total_entries/@opts[:per_page] : nil
|
|
117
|
+
end
|
|
118
|
+
alias_method :total_pages, :num_pages
|
|
119
|
+
|
|
120
|
+
def out_of_bounds?
|
|
121
|
+
self.current_page > self.total_pages
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def offset
|
|
125
|
+
(self.current_page - 1) * self.per_page
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# current_page - 1 or nil if there is no previous page
|
|
129
|
+
def previous_page
|
|
130
|
+
self.current_page > 1 ? (self.current_page - 1) : nil
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# current_page + 1 or nil if there is no next page
|
|
134
|
+
def next_page
|
|
135
|
+
self.current_page < self.total_pages ? (self.current_page + 1) : nil
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|