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.
Files changed (110) hide show
  1. data/.document +5 -0
  2. data/.gitignore +49 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +180 -0
  7. data/Rakefile +18 -0
  8. data/lib/mongoid_spacial.rb +11 -0
  9. data/lib/mongoid_spacial/contexts/mongo.rb +115 -0
  10. data/lib/mongoid_spacial/criteria.rb +5 -0
  11. data/lib/mongoid_spacial/criterion.rb +3 -0
  12. data/lib/mongoid_spacial/criterion/complex.rb +19 -0
  13. data/lib/mongoid_spacial/criterion/inclusion.rb +14 -0
  14. data/lib/mongoid_spacial/criterion/near_spacial.rb +50 -0
  15. data/lib/mongoid_spacial/criterion/within_spacial.rb +60 -0
  16. data/lib/mongoid_spacial/extentions/hash/criteria_helpers.rb +22 -0
  17. data/lib/mongoid_spacial/extentions/symbol/inflections.rb +46 -0
  18. data/lib/mongoid_spacial/field_option.rb +38 -0
  19. data/lib/mongoid_spacial/finders.rb +5 -0
  20. data/lib/mongoid_spacial/spacial.rb +56 -0
  21. data/lib/mongoid_spacial/spacial/core_ext.rb +27 -0
  22. data/lib/mongoid_spacial/spacial/document.rb +29 -0
  23. data/lib/mongoid_spacial/spacial/formulas.rb +52 -0
  24. data/lib/mongoid_spacial/spacial/geo_near_results.rb +140 -0
  25. data/lib/mongoid_spacial/spacial/version.rb +5 -0
  26. data/mongoid_spacial.gemspec +27 -0
  27. data/spec/config/mongod.conf +3 -0
  28. data/spec/config/mongoid.yml +18 -0
  29. data/spec/functional/mongoid/contexts/mongo_spec.rb +125 -0
  30. data/spec/functional/mongoid/criterion/inclusion_spec.rb +356 -0
  31. data/spec/functional/mongoid/spacial/geo_near_results_spec.rb +73 -0
  32. data/spec/functional/mongoid/spacial_spec.rb +18 -0
  33. data/spec/models/account.rb +19 -0
  34. data/spec/models/acolyte.rb +9 -0
  35. data/spec/models/address.rb +62 -0
  36. data/spec/models/address_component.rb +5 -0
  37. data/spec/models/agent.rb +10 -0
  38. data/spec/models/alert.rb +5 -0
  39. data/spec/models/animal.rb +21 -0
  40. data/spec/models/answer.rb +4 -0
  41. data/spec/models/bar.rb +9 -0
  42. data/spec/models/birthday.rb +13 -0
  43. data/spec/models/book.rb +5 -0
  44. data/spec/models/business.rb +7 -0
  45. data/spec/models/callbacks.rb +57 -0
  46. data/spec/models/category.rb +13 -0
  47. data/spec/models/circus.rb +7 -0
  48. data/spec/models/comment.rb +13 -0
  49. data/spec/models/country_code.rb +6 -0
  50. data/spec/models/description.rb +11 -0
  51. data/spec/models/division.rb +5 -0
  52. data/spec/models/drug.rb +5 -0
  53. data/spec/models/employer.rb +5 -0
  54. data/spec/models/entry.rb +6 -0
  55. data/spec/models/event.rb +20 -0
  56. data/spec/models/favorite.rb +6 -0
  57. data/spec/models/fruits.rb +11 -0
  58. data/spec/models/game.rb +18 -0
  59. data/spec/models/ghost.rb +7 -0
  60. data/spec/models/house.rb +4 -0
  61. data/spec/models/inheritance.rb +90 -0
  62. data/spec/models/league.rb +5 -0
  63. data/spec/models/location.rb +5 -0
  64. data/spec/models/login.rb +6 -0
  65. data/spec/models/membership.rb +4 -0
  66. data/spec/models/mixed_drink.rb +4 -0
  67. data/spec/models/name.rb +13 -0
  68. data/spec/models/namespacing.rb +11 -0
  69. data/spec/models/observed.rb +41 -0
  70. data/spec/models/override.rb +16 -0
  71. data/spec/models/owner.rb +6 -0
  72. data/spec/models/page.rb +5 -0
  73. data/spec/models/page_question.rb +4 -0
  74. data/spec/models/paranoid_post.rb +18 -0
  75. data/spec/models/parents.rb +32 -0
  76. data/spec/models/patient.rb +15 -0
  77. data/spec/models/person.rb +146 -0
  78. data/spec/models/pet.rb +7 -0
  79. data/spec/models/pet_owner.rb +6 -0
  80. data/spec/models/phone.rb +7 -0
  81. data/spec/models/player.rb +23 -0
  82. data/spec/models/post.rb +26 -0
  83. data/spec/models/preference.rb +9 -0
  84. data/spec/models/question.rb +8 -0
  85. data/spec/models/quiz.rb +6 -0
  86. data/spec/models/rating.rb +8 -0
  87. data/spec/models/river.rb +20 -0
  88. data/spec/models/role.rb +5 -0
  89. data/spec/models/service.rb +6 -0
  90. data/spec/models/shelf.rb +5 -0
  91. data/spec/models/slave_address_numbers.rb +14 -0
  92. data/spec/models/survey.rb +5 -0
  93. data/spec/models/tag.rb +6 -0
  94. data/spec/models/tracking_id_validation_history.rb +25 -0
  95. data/spec/models/translation.rb +5 -0
  96. data/spec/models/tree.rb +9 -0
  97. data/spec/models/user.rb +9 -0
  98. data/spec/models/user_account.rb +10 -0
  99. data/spec/models/vet_visit.rb +5 -0
  100. data/spec/models/video.rb +9 -0
  101. data/spec/models/wiki_page.rb +6 -0
  102. data/spec/spec_helper.rb +43 -0
  103. data/spec/support/authentication.rb +29 -0
  104. data/spec/unit/mongoid/criterion/complex_spec.rb +15 -0
  105. data/spec/unit/mongoid/criterion/inclusion_spec.rb +0 -0
  106. data/spec/unit/mongoid/criterion/near_spacial_spec.rb +39 -0
  107. data/spec/unit/mongoid/criterion/within_spacial_spec.rb +52 -0
  108. data/spec/unit/mongoid/spacial/formulas_spec.rb +37 -0
  109. data/spec/unit/mongoid/spacial_spec.rb +6 -0
  110. 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,5 @@
1
+ module Mongoid #:nodoc:
2
+ module Finders
3
+ delegate :geo_near, :to => :criteria
4
+ end
5
+ 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