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.
- data/lib/mongoid_location.rb +2 -2
- data/lib/mongoid_location/contexts/mongo.rb +117 -0
- data/lib/mongoid_location/contextual/mongo.rb +118 -0
- data/lib/mongoid_location/criteria.rb +10 -0
- data/lib/mongoid_location/criterion/complex.rb +25 -0
- data/lib/mongoid_location/criterion/inclusion.rb +14 -0
- data/lib/mongoid_location/criterion/near_spatial.rb +52 -0
- data/lib/mongoid_location/criterion/within_spatial.rb +62 -0
- data/lib/mongoid_location/extensions/rgeo_spherical_point_impl.rb +13 -0
- data/lib/mongoid_location/extensions/symbol.rb +46 -0
- data/lib/mongoid_location/field_option.rb +16 -0
- data/lib/mongoid_location/fields/line_string.rb +20 -0
- data/lib/mongoid_location/fields/mongoid2/line_string.rb +20 -0
- data/lib/mongoid_location/fields/mongoid2/point.rb +20 -0
- data/lib/mongoid_location/fields/mongoid2/polygon.rb +23 -0
- data/lib/mongoid_location/fields/point.rb +63 -0
- data/lib/mongoid_location/fields/polygon.rb +25 -0
- data/lib/mongoid_location/finders.rb +5 -0
- data/lib/mongoid_location/location.rb +86 -0
- data/lib/mongoid_location/location/core_ext.rb +29 -0
- data/lib/mongoid_location/location/geo_near_results.rb +140 -0
- data/lib/mongoid_location/version.rb +1 -1
- metadata +21 -1
data/lib/mongoid_location.rb
CHANGED
@@ -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,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,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,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
|
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.
|
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
|