mongoid_location 0.3.1 → 0.3.2

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.
@@ -2,13 +2,13 @@ require 'rgeo'
2
2
  require 'mongoid'
3
3
  require 'active_support/core_ext/string/inflections'
4
4
  require 'active_support/concern'
5
+ require 'mongoid_location/version'
6
+ #require 'mongoid_location/contextual/mongo'
5
7
  if Mongoid::VERSION > '3'
6
8
  require 'mongoid_location/contextual/mongo'
7
9
  else
8
10
  require 'mongoid_location/contexts/mongo'
9
11
  end
10
- require 'mongoid_location/version'
11
- #require 'mongoid_location/contextual/mongo'
12
12
  require 'mongoid_location/criteria'
13
13
  require 'mongoid_location/extensions/symbol'
14
14
  require 'mongoid_location/extensions/rgeo_spherical_point_impl'
@@ -0,0 +1,117 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Contexts #:nodoc:
4
+ class Mongo #:nodoc:
5
+
6
+ # Fetches rows from the data base sorted by distance.
7
+ # In MongoDB versions 1.7 and above it returns a distance.
8
+ # Uses all criteria chains except without, only, asc, desc, order_by
9
+ #
10
+ # @example Minimal Query
11
+ #
12
+ # Address.geo_near([70,40])
13
+ #
14
+ # @example Chained Query
15
+ #
16
+ # Address.where(:state => 'ny').geo_near([70,40])
17
+ #
18
+ # @example Calc Distances Query
19
+ #
20
+ # Address.geo_near([70,40], :max_distance => 5, :unit => 5)
21
+ #
22
+ # @param [ Array, Hash, #to_xy ] center The center of where to calculate distance from
23
+ # @param [ Hash ] opts the options to query with
24
+ # @options opts [Integer] :num The number of rows to fetch
25
+ # @options opts [Hash] :query The query to filter the rows by, accepts
26
+ # @options opts [Numeric] :distance_multiplier this is multiplied against the calculated distance
27
+ # @options opts [Numeric] :max_distance The max distance of a row that should be returned in :unit(s)
28
+ # @options opts [Numeric, :km, :k, :mi, :ft] :unit automatically sets :distance_multiplier and converts :max_distance
29
+ # @options opts [true,false] :spherical Will determine the distance either by spherical calculation or flat calculation
30
+ # @options opts [TrueClass,Array<Symbol>] :calculate Which extra fields to calculate distance for in ruby, if set to TrueClass it will calculate all spatial fields
31
+ #
32
+ # @return [ Array ] Sorted Rows
33
+ def geo_near(center, opts = {})
34
+ opts = self.options.merge(opts)
35
+ # convert point
36
+ center = center.to_xy if center.respond_to?(:to_xy)
37
+ center = [center.x, center.y] if center.respond_to?(:x)
38
+
39
+ # set default opts
40
+ opts[:skip] ||= 0
41
+
42
+ if unit = Mongoid::Location.earth_radius[opts[:unit]]
43
+ opts[:unit] = (opts[:spherical]) ? unit : unit * Mongoid::Location::RAD_PER_DEG
44
+ end
45
+
46
+ if unit = Mongoid::Location.earth_radius[opts[:distance_multiplier]]
47
+ opts[:distance_multiplier] = (opts[:spherical]) ? unit : unit * Mongoid::Location::RAD_PER_DEG
48
+ end
49
+
50
+ opts[:distance_multiplier] = opts[:unit] if opts[:unit].kind_of?(Numeric)
51
+
52
+ # setup paging.
53
+ if opts.has_key?(:page)
54
+ opts[:page] ||= 1
55
+ opts[:paginator] ||= Mongoid::Location.paginator()
56
+
57
+ if opts[:per_page].blank?
58
+ opts[:per_page] = case opts[:paginator]
59
+ when :will_paginate
60
+ @document.per_page
61
+ when :kaminari
62
+ Kaminari.config.default_per_page
63
+ else
64
+ Mongoid::Location.default_per_page
65
+ end
66
+ opts[:per_page] = opts[:per_page].to_i
67
+ end
68
+
69
+ end
70
+ opts[:query] = create_geo_near_query(center,opts)
71
+ results = klass.db.command(opts[:query])
72
+ Mongoid::Location::GeoNearResults.new(klass,results,opts)
73
+ end
74
+
75
+ private
76
+
77
+ def create_geo_near_query(center,opts)
78
+ # minimum query
79
+ query = BSON::OrderedHash.new
80
+ query[:geoNear] = klass.collection_name
81
+ query[:near] = center
82
+
83
+ # create limit and use skip
84
+ if opts[:num]
85
+ query['num'] = opts[:skip].to_i + opts[:num].to_i
86
+ elsif opts[:limit]
87
+ query['num'] = opts[:skip].to_i + opts[:limit].to_i
88
+ elsif opts[:page]
89
+ query['num'] = opts[:skip].to_i + (opts[:page].to_i * opts[:per_page].to_i)
90
+ end
91
+
92
+ # allow the use of complex werieis
93
+ if opts[:query]
94
+ query['query'] = self.criteria.where(opts[:query]).selector
95
+ elsif self.selector != {}
96
+ query['query'] = self.selector
97
+ end
98
+
99
+ if opts[:max_distance]
100
+ query['maxDistance'] = opts[:max_distance].to_f
101
+ query['maxDistance'] = query['maxDistance']/opts[:unit].to_f if opts[:unit]
102
+ end
103
+
104
+ if opts[:unique_docs]
105
+ query['uniqueDocs'] = true
106
+ end
107
+
108
+ query['spherical'] = true if opts[:spherical]
109
+
110
+ # mongodb < 1.7 returns degrees but with earth flat. in Mongodb 1.7 you can set sphere and let mongodb calculate the distance in Miles or KM
111
+ # for mongodb < 1.7 we need to run Haversine first before calculating degrees to Km or Miles. See below.
112
+ query['distanceMultiplier'] = opts[:distance_multiplier].to_f if opts[:distance_multiplier]
113
+ query
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,118 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Contextual #:nodoc:
4
+ class Mongo #:nodoc:
5
+
6
+ # Fetches rows from the data base sorted by distance.
7
+ # In MongoDB versions 1.7 and above it returns a distance.
8
+ # Uses all criteria chains except without, only, asc, desc, order_by
9
+ #
10
+ # @example Minimal Query
11
+ #
12
+ # Address.geo_near([70,40])
13
+ #
14
+ # @example Chained Query
15
+ #
16
+ # Address.where(:state => 'ny').geo_near([70,40])
17
+ #
18
+ # @example Calc Distances Query
19
+ #
20
+ # Address.geo_near([70,40], :max_distance => 5, :unit => 5)
21
+ #
22
+ # @param [ Array, Hash, #to_xy ] center The center of where to calculate distance from
23
+ # @param [ Hash ] opts the options to query with
24
+ # @options opts [Integer] :num The number of rows to fetch
25
+ # @options opts [Hash] :query The query to filter the rows by, accepts
26
+ # @options opts [Numeric] :distance_multiplier this is multiplied against the calculated distance
27
+ # @options opts [Numeric] :max_distance The max distance of a row that should be returned in :unit(s)
28
+ # @options opts [Numeric, :km, :k, :mi, :ft] :unit automatically sets :distance_multiplier and converts :max_distance
29
+ # @options opts [true,false] :spherical Will determine the distance either by spherical calculation or flat calculation
30
+ # @options opts [TrueClass,Array<Symbol>] :calculate Which extra fields to calculate distance for in ruby, if set to TrueClass it will calculate all spatial fields
31
+ #
32
+ # @return [ Array ] Sorted Rows
33
+ def geo_near(center, opts = {})
34
+ opts = self.criteria.options.merge(opts)
35
+ # convert point
36
+ center = center.to_xy if center.respond_to?(:to_xy)
37
+ center = [center.x, center.y] if center.respond_to?(:x)
38
+
39
+ # set default opts
40
+ opts[:skip] ||= 0
41
+
42
+ if unit = Mongoid::Location.earth_radius[opts[:unit]]
43
+ opts[:unit] = (opts[:spherical]) ? unit : unit * Mongoid::Location::RAD_PER_DEG
44
+ end
45
+
46
+ if unit = Mongoid::Location.earth_radius[opts[:distance_multiplier]]
47
+ opts[:distance_multiplier] = (opts[:spherical]) ? unit : unit * Mongoid::Location::RAD_PER_DEG
48
+ end
49
+
50
+ opts[:distance_multiplier] = opts[:unit] if opts[:unit].kind_of?(Numeric)
51
+
52
+ # setup paging.
53
+ if opts.has_key?(:page)
54
+ opts[:page] ||= 1
55
+ opts[:paginator] ||= Mongoid::Location.paginator()
56
+
57
+ if opts[:per_page].blank?
58
+ opts[:per_page] = case opts[:paginator]
59
+ when :will_paginate
60
+ @document.per_page
61
+ when :kaminari
62
+ Kaminari.config.default_per_page
63
+ else
64
+ Mongoid::Location.default_per_page
65
+ end
66
+ opts[:per_page] = opts[:per_page].to_i
67
+ end
68
+
69
+ end
70
+ opts[:query] = create_geo_near_query(center,opts)
71
+ results = klass.mongo_session.command(opts[:query])
72
+
73
+ Mongoid::Location::GeoNearResults.new(klass,results,opts)
74
+ end
75
+
76
+ private
77
+
78
+ def create_geo_near_query(center,opts)
79
+ # minimum query
80
+ query = {}
81
+ query[:geoNear] = klass.collection_name
82
+ query[:near] = center
83
+
84
+ # create limit and use skip
85
+ if opts[:num]
86
+ query['num'] = opts[:skip].to_i + opts[:num].to_i
87
+ elsif opts[:limit]
88
+ query['num'] = opts[:skip].to_i + opts[:limit].to_i
89
+ elsif opts[:page]
90
+ query['num'] = opts[:skip].to_i + (opts[:page].to_i * opts[:per_page].to_i)
91
+ end
92
+
93
+ # allow the use of complex werieis
94
+ if opts[:query]
95
+ query['query'] = self.criteria.where(opts[:query]).selector
96
+ elsif self.criteria.selector != {}
97
+ query['query'] = self.criteria.selector
98
+ end
99
+
100
+ if opts[:max_distance]
101
+ query['maxDistance'] = opts[:max_distance].to_f
102
+ query['maxDistance'] = query['maxDistance']/opts[:unit].to_f if opts[:unit]
103
+ end
104
+
105
+ if opts[:unique_docs]
106
+ query['uniqueDocs'] = true
107
+ end
108
+
109
+ query['spherical'] = true if opts[:spherical]
110
+
111
+ # mongodb < 1.7 returns degrees but with earth flat. in Mongodb 1.7 you can set sphere and let mongodb calculate the distance in Miles or KM
112
+ # for mongodb < 1.7 we need to run Haversine first before calculating degrees to Km or Miles. See below.
113
+ query['distanceMultiplier'] = opts[:distance_multiplier].to_f if opts[:distance_multiplier]
114
+ query
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,10 @@
1
+ require 'mongoid_location/criterion/complex'
2
+ require 'mongoid_location/criterion/near_spatial'
3
+ require 'mongoid_location/criterion/within_spatial'
4
+
5
+
6
+ module Mongoid #:nodoc:
7
+ class Criteria
8
+ delegate :geo_near, :to => :context
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Criterion #:nodoc:
4
+ # Complex criterion are used when performing operations on symbols to get
5
+ # get a shorthand syntax for where clauses.
6
+ #
7
+ # Example:
8
+ #
9
+ # <tt>{ :field => { "$lt" => "value" } }</tt>
10
+ # becomes:
11
+ # <tt> { :field.lt => "value }</tt>
12
+ class Complex
13
+
14
+ attr_accessor :key, :operator
15
+
16
+ def initialize(opts = {})
17
+ @key, @operator = opts[:key], opts[:operator]
18
+ end
19
+
20
+ def to_mongo_query v
21
+ {"$#{operator}" => v}
22
+ end
23
+ end
24
+ end
25
+ end
@@ -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,52 @@
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 NearSpatial < Complex
13
+
14
+ # Coninputert input to query for near or nearSphere
15
+ #
16
+ # @example
17
+ # near = NearSpatial.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.respond_to?(:x)
23
+ {"$#{operator}" => [input.x, input.y]} #, '$maxDistance' => input[1] }
24
+ elsif input.kind_of?(Hash)
25
+ raise ':point required to make valid query' unless input[:point]
26
+ input[:point] = input[:point].to_xy if input[:point].respond_to?(:to_xy)
27
+ query = {"$#{operator}" => input[:point] }
28
+ if input[:max]
29
+ query['$maxDistance'] = input[:max].to_f
30
+
31
+ if unit = Mongoid::Location.earth_radius[input[:unit]]
32
+ unit *= Mongoid::Location::RAD_PER_DEG unless operator =~ /sphere/i
33
+ input[:unit] = unit
34
+ end
35
+
36
+ query['$maxDistance'] = query['$maxDistance']/input[:unit].to_f if input[:unit]
37
+ end
38
+ query
39
+ elsif input.kind_of? Array
40
+ if input.first.kind_of? Numeric
41
+ {"$#{operator}" => input }
42
+ else
43
+ input[0] = input[0].to_xy if input[0].respond_to?(:to_xy)
44
+ {"$#{operator}" => input[0], '$maxDistance' => input[1] }
45
+ end
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+
@@ -0,0 +1,62 @@
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 WithinSpatial < Complex
13
+
14
+ # Convert input to query for box, polygon, center, and centerSphere
15
+ #
16
+ # @example
17
+ # within = WithinSpatial.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'].include?(@operator)
23
+ input = input.values if input.kind_of?(Hash)
24
+ if input.respond_to?(:map)
25
+ input.map! do |v|
26
+ v.respond_to?(:to_xy) ? v.to_xy : v
27
+ end
28
+ else
29
+ input
30
+ end
31
+ elsif ['center','centerSphere'].include?(@operator)
32
+
33
+ if input.kind_of?(Hash) || input.kind_of?(ActiveSupport::OrderedHash)
34
+ raise ':point required to make valid query' unless input[:point]
35
+ input[:point] = input[:point].to_xy if input[:point].respond_to?(:to_xy)
36
+ if input[:max]
37
+ input[:max] = input[:max].to_f
38
+
39
+ if unit = Mongoid::Location.earth_radius[input[:unit]]
40
+ unit *= Mongoid::Location::RAD_PER_DEG unless operator =~ /sphere/i
41
+ input[:unit] = unit
42
+ end
43
+
44
+ input[:max] = input[:max]/input[:unit].to_f if input[:unit]
45
+
46
+ input = [input[:point],input[:max]]
47
+ else
48
+ input = input[:point]
49
+ end
50
+ end
51
+
52
+ if input.kind_of? Array
53
+ input[0] = input[0].to_xy if input[0].respond_to?(:to_xy)
54
+ end
55
+
56
+ end
57
+ {'$within' => {"$#{@operator}"=>input} }
58
+ end
59
+ end
60
+ end
61
+ end
62
+
@@ -0,0 +1,13 @@
1
+ module RGeo
2
+ module Geographic
3
+ class SphericalPointImpl
4
+ def to_a
5
+ [x, y, z]
6
+ end
7
+
8
+ def [] index
9
+ to_a[index]
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Extensions #:nodoc:
4
+ module Symbol #:nodoc:
5
+
6
+ # return a class that will accept a value to convert the query correctly for near
7
+ #
8
+ # @param [Symbol] calc This accepts :sphere
9
+ #
10
+ # @return [Criterion::NearSpatial]
11
+
12
+ def near(calc = :flat)
13
+ Criterion::NearSpatial.new(:operator => get_op('near',calc), :key => self)
14
+ end
15
+
16
+ # alias for self.near(:sphere)
17
+ #
18
+ # @return [Criterion::NearSpatial]
19
+ def near_sphere
20
+ self.near(:sphere)
21
+ end
22
+
23
+ # @param [Symbol] shape :box,:polygon,:center,:center_sphere
24
+ #
25
+ # @return [Criterion::WithinSpatial]
26
+ def within(shape)
27
+ shape = get_op(:center,:sphere) if shape == :center_sphere
28
+ Criterion::WithinSpatial.new(:operator => shape.to_s , :key => self)
29
+ end
30
+
31
+ private
32
+
33
+ def get_op operator, calc
34
+ if calc.to_sym == :sphere
35
+ "#{operator}Sphere"
36
+ else
37
+ operator.to_s
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+
45
+
46
+ ::Symbol.__send__(:include, Mongoid::Extensions::Symbol)
@@ -0,0 +1,16 @@
1
+ #require 'ostruct'
2
+
3
+ Mongoid::Fields.option :spatial do |model,field,options|
4
+ options = {} unless options.kind_of?(Hash)
5
+ lat_meth = options[:lat] || :lat
6
+ lng_meth = options[:lng] || :lng
7
+ model.class_eval do
8
+ self.spatial_fields ||= []
9
+ self.spatial_fields << field.name.to_sym if self.spatial_fields.kind_of? Array
10
+
11
+ define_method "distance_from_#{field.name}" do |*args|
12
+ self.distance_from(field.name, *args)
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ module Mongoid
2
+ module Location
3
+ class LineString
4
+ # See http://mongoid.org/en/mongoid/docs/upgrading.html
5
+ def mongoize
6
+ to_a
7
+ end
8
+
9
+ class << self
10
+ def demongoize(object)
11
+ RGeo::Geographic.spherical_factory.line_string *object
12
+ end
13
+ # def evolve(object)
14
+ # { "$gte" => object.first, "$lte" => object.last }
15
+ # end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Mongoid
2
+ module Location
3
+ class LineString
4
+ include Mongoid::Fields::Serializable
5
+
6
+ def self.instantiate name, options = {}
7
+ super
8
+ end
9
+
10
+ def serialize(object)
11
+ object.to_a
12
+ end
13
+
14
+ def deserialize(object)
15
+ return unless object && !object.empty?
16
+ RGeo::Geographic.spherical_factory.line_string(*object)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Mongoid
2
+ module Location
3
+ class Point
4
+ include Mongoid::Fields::Serializable
5
+
6
+ def self.instantiate name, options = {}
7
+ super
8
+ end
9
+
10
+ def serialize(object)
11
+ object.respond_to?(:x) ? [object.x, object.y] : object
12
+ end
13
+
14
+ def deserialize(object)
15
+ return unless object && !object.empty?
16
+ RGeo::Geographic.spherical_factory.point *object
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ module Mongoid
2
+ module Location
3
+ class Polygon
4
+ include Mongoid::Fields::Serializable
5
+
6
+ def self.instantiate name, options = {}
7
+ super
8
+ end
9
+
10
+ def serialize(object)
11
+ object
12
+ end
13
+
14
+ def deserialize(object)
15
+ points = object.map do |pair|
16
+ RGeo::Geographic.spherical_factory.point *pair
17
+ end
18
+ ring = RGeo::Geographic.spherical_factory.linear_ring points
19
+ RGeo::Geographic.spherical_factory.polygon ring
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,63 @@
1
+ module Mongoid
2
+ module Location
3
+ class Point
4
+ # See http://mongoid.org/en/mongoid/docs/upgrading.html
5
+
6
+ def mongoize
7
+ self.respond_to?(:x) ? [x, y] : self
8
+ # if object.respond_to? :x
9
+ # { "x" => object.x, "y" => object.y }
10
+ # else
11
+ # { "x" => object[0], "y" => object[1] }
12
+ # end
13
+ end
14
+
15
+ class << self
16
+
17
+ def demongoize(object)
18
+ return unless object && !object.empty?
19
+ RGeo::Geographic.spherical_factory.point *object
20
+ #["x"], object["y"]
21
+ end
22
+
23
+ def mongoize(object)
24
+ object.respond_to?(:x) ? [object.x, object.y] : object
25
+ end
26
+
27
+ # Converts the object that was supplied to a criteria and converts it
28
+ # into a database friendly form.
29
+ def evolve(object)
30
+ object.respond_to?(:x) ? [object.x, object.y] : object
31
+ end
32
+ end
33
+
34
+ # - self.spacial_fields ||= []
35
+ # - self.spacial_fields << field.name.to_sym if self.spacial_fields.kind_of? Array
36
+ # -
37
+ # - define_method "distance_from_#{field.name}" do |*args|
38
+ # - self.distance_from(field.name, *args)
39
+ # - end
40
+ # -
41
+ # - define_method field.name do
42
+ # - output = self[field.name] || [nil,nil]
43
+ # - output = {lng_meth => output[0], lat_meth => output[1]} unless options[:return_array]
44
+ # - return options[:class].new(output) if options[:class]
45
+ # - output
46
+ # - end
47
+ # -
48
+ # - define_method "#{field.name}=" do |arg|
49
+ # - if arg.kind_of?(Hash) && arg[lng_meth] && arg[lat_meth]
50
+ # - arg = [arg[lng_meth].to_f, arg[lat_meth].to_f]
51
+ # - elsif arg.respond_to?(:to_xy)
52
+ # - arg = arg.to_xy
53
+ # - end
54
+ # - self[field.name]=arg
55
+ # - arg = [nil,nil] if arg.nil?
56
+ # - return arg[0..1] if options[:return_array]
57
+ # - h = {lng_meth => arg[0], lat_meth => arg[1]}
58
+ # - return h if options[:class].blank?
59
+ # - options[:class].new(h)
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,25 @@
1
+ module Mongoid
2
+ module Location
3
+ class Polygon
4
+ # See http://mongoid.org/en/mongoid/docs/upgrading.html
5
+
6
+ def mongoize
7
+ self #.flatten
8
+ end
9
+
10
+ class << self
11
+ def demongoize(object)
12
+ points = object.map do |pair|
13
+ RGeo::Geographic.spherical_factory.point *pair
14
+ end
15
+ ring = RGeo::Geographic.spherical_factory.linear_ring points
16
+ RGeo::Geographic.spherical_factory.polygon ring
17
+ end
18
+ # def evolve(object)
19
+ # { "$gte" => object.first, "$lte" => object.last }
20
+ # end
21
+ end
22
+
23
+ end
24
+ end
25
+ 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,86 @@
1
+ require 'mongoid_location/location/core_ext'
2
+ require 'mongoid_location/location/geo_near_results'
3
+
4
+ module Mongoid
5
+ module Location
6
+ extend ActiveSupport::Concern
7
+
8
+ LNG_SYMBOLS = [:x, :lon, :long, :lng, :longitude]
9
+ LAT_SYMBOLS = [:y, :lat, :latitude]
10
+
11
+ EARTH_RADIUS_KM = 6371 # taken directly from mongodb
12
+
13
+ EARTH_RADIUS = {
14
+ :km => EARTH_RADIUS_KM,
15
+ :m => EARTH_RADIUS_KM*1000,
16
+ :mi => EARTH_RADIUS_KM*0.621371192, # taken directly from mongodb
17
+ :ft => EARTH_RADIUS_KM*5280*0.621371192,
18
+ :sm => EARTH_RADIUS_KM*0.53995680345572 # sea mile
19
+ }
20
+
21
+ GEO_FACTORY = RGeo::Geographic.spherical_factory
22
+ RAD_PER_DEG = Math::PI/180
23
+ mattr_accessor :lng_symbols
24
+ @@lng_symbols = LNG_SYMBOLS.dup
25
+
26
+ mattr_accessor :lat_symbols
27
+ @@lat_symbols = LAT_SYMBOLS.dup
28
+
29
+ mattr_accessor :earth_radius
30
+ @@earth_radius = EARTH_RADIUS.dup
31
+
32
+ mattr_accessor :paginator
33
+ @@paginator = :array
34
+
35
+ mattr_accessor :default_per_page
36
+ @@default_per_page = 25
37
+
38
+ # mattr_accessor :spherical_distance_formula
39
+ # @@spherical_distance_formula = :n_vector
40
+ mattr_accessor :geo_factory
41
+ @@geo_factory = GEO_FACTORY.dup
42
+
43
+ included do
44
+ attr_accessor :geo
45
+ cattr_accessor :spatial_fields, :spatial_fields_indexed
46
+ @@spatial_fields = []
47
+ @@spatial_fields_indexed = []
48
+ end
49
+
50
+ module ClassMethods #:nodoc:
51
+ # create spatial index for given field
52
+ # @param [String,Symbol] name
53
+ # @param [Hash] options options for spatial_index
54
+
55
+ def spatial_index name, options = {}
56
+ self.spatial_fields_indexed << name
57
+ if Mongoid::VERSION =~ /3.0/
58
+ index({name => '2d'}, options)
59
+ else
60
+ index [[name, '2d']], options
61
+ end
62
+ end
63
+ end
64
+
65
+ # def distance(p2, opts = {})
66
+ # p1 = self.send(key)
67
+ # Mongoid::Location.distance(p1, p2, opts)
68
+ # end
69
+
70
+ # def self.distance(p1,p2,opts = {})
71
+ # opts[:formula] ||= (opts[:spherical]) ? @@spherical_distance_formula : :pythagorean_theorem
72
+ # p1 = p1.to_lng_lat if p1.respond_to?(:to_lng_lat)
73
+ # p2 = p2.to_lng_lat if p2.respond_to?(:to_lng_lat)
74
+
75
+ # rads = Formulas.send(opts[:formula], p1, p2)
76
+
77
+ # if unit = earth_radius[opts[:unit]]
78
+ # opts[:unit] = (rads.instance_variable_get("@radian")) ? unit : unit * RAD_PER_DEG
79
+ # end
80
+
81
+ # rads *= opts[:unit].to_f if opts[:unit]
82
+ # rads
83
+ # end
84
+
85
+ end
86
+ end
@@ -0,0 +1,29 @@
1
+ class Array
2
+ def to_xy
3
+ self[0..1].map(&:to_f)
4
+ end
5
+ alias :to_lng_lat :to_xy
6
+ end
7
+
8
+ class Hash
9
+ def to_xy
10
+ raise "Hash must have at least 2 items" if self.size < 2
11
+ [to_x, to_y]
12
+ end
13
+ alias :to_lng_lat :to_xy
14
+
15
+ def to_y
16
+ v = (Mongoid::Location.lat_symbols & self.keys).first
17
+ return self[v].to_f if !v.nil? && self[v]
18
+ raise "Hash must contain #{Mongoid::Location.lat_symbols.inspect} if ruby version is less than 1.9" if RUBY_VERSION.to_f < 1.9
19
+ raise "Hash cannot contain #{Mongoid::Location.lng_symbols.inspect} as the second item if there is no #{Mongoid::Location.lat_symbols.inspect}" if Mongoid::Location.lng_symbols.index(self.keys[1])
20
+ self.values[1].to_f
21
+ end
22
+
23
+ def to_x
24
+ v = (Mongoid::Location.lng_symbols & self.keys).first
25
+ return self[v].to_f if !v.nil? && self[v]
26
+ raise "Hash cannot contain #{Mongoid::Location.lat_symbols.inspect} as the first item if there is no #{Mongoid::Location.lng_symbols.inspect}" if Mongoid::Location.lat_symbols.index(self.keys[0])
27
+ self.values[0].to_f
28
+ end
29
+ end
@@ -0,0 +1,140 @@
1
+ module Mongoid
2
+ module Location
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::Location::Document" unless document.respond_to?(:spatial_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.spatial_fields_indexed if @document.spatial_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.spatial_fields_indexed.kind_of?(Array) && @document.spatial_fields_indexed.size == 1
31
+ primary = @document.spatial_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::Location.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::Location.default_per_page
110
+ end
111
+ end
112
+ end
113
+ alias_method :per_page, :limit_value
114
+
115
+ def num_pages
116
+ (total_entries && @opts[:per_page]) ? (total_entries.to_f / @opts[:per_page]).ceil : 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
@@ -1,5 +1,5 @@
1
1
  module Mongoid
2
2
  module Location
3
- VERSION = "0.3.1"
3
+ VERSION = "0.3.2"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongoid_location
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -153,6 +153,26 @@ files:
153
153
  - README.md
154
154
  - Rakefile
155
155
  - lib/mongoid_location.rb
156
+ - lib/mongoid_location/contexts/mongo.rb
157
+ - lib/mongoid_location/contextual/mongo.rb
158
+ - lib/mongoid_location/criteria.rb
159
+ - lib/mongoid_location/criterion/complex.rb
160
+ - lib/mongoid_location/criterion/inclusion.rb
161
+ - lib/mongoid_location/criterion/near_spatial.rb
162
+ - lib/mongoid_location/criterion/within_spatial.rb
163
+ - lib/mongoid_location/extensions/rgeo_spherical_point_impl.rb
164
+ - lib/mongoid_location/extensions/symbol.rb
165
+ - lib/mongoid_location/field_option.rb
166
+ - lib/mongoid_location/fields/line_string.rb
167
+ - lib/mongoid_location/fields/mongoid2/line_string.rb
168
+ - lib/mongoid_location/fields/mongoid2/point.rb
169
+ - lib/mongoid_location/fields/mongoid2/polygon.rb
170
+ - lib/mongoid_location/fields/point.rb
171
+ - lib/mongoid_location/fields/polygon.rb
172
+ - lib/mongoid_location/finders.rb
173
+ - lib/mongoid_location/location.rb
174
+ - lib/mongoid_location/location/core_ext.rb
175
+ - lib/mongoid_location/location/geo_near_results.rb
156
176
  - lib/mongoid_location/version.rb
157
177
  - mongoid_location.gemspec
158
178
  homepage: http://www.openxid.com