sig_mongoid_spacial 0.2.17
Sign up to get free protection for your applications and to get access to all the features.
- 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 +369 -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
|