mongoid_geospatial 2.0.0 → 2.2.0
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/Gemfile +2 -1
- data/README.md +84 -60
- data/lib/mongoid_geospatial.rb +3 -7
- data/lib/mongoid_geospatial/{geospatial → extensions}/core_ext.rb +0 -0
- data/lib/mongoid_geospatial/field_option.rb +3 -4
- data/lib/mongoid_geospatial/fields/geometry_field.rb +34 -0
- data/lib/mongoid_geospatial/fields/line_string.rb +5 -7
- data/lib/mongoid_geospatial/fields/point.rb +18 -33
- data/lib/mongoid_geospatial/fields/polygon.rb +6 -14
- data/lib/mongoid_geospatial/geospatial.rb +20 -25
- data/lib/mongoid_geospatial/version.rb +1 -1
- data/lib/mongoid_geospatial/wrappers/georuby.rb +28 -0
- data/lib/mongoid_geospatial/wrappers/rgeo.rb +32 -0
- data/spec/models/farm.rb +5 -2
- data/spec/models/person.rb +4 -14
- data/spec/models/phone.rb +1 -3
- data/spec/mongoid_geospatial/extensions/core_ext_spec.rb +20 -0
- data/spec/mongoid_geospatial/field_option_spec.rb +11 -0
- data/spec/mongoid_geospatial/fields/line_string_spec.rb +40 -0
- data/spec/mongoid_geospatial/fields/point_spec.rb +136 -10
- data/spec/mongoid_geospatial/fields/polygon_spec.rb +75 -0
- data/spec/mongoid_geospatial/geospatial_spec.rb +88 -3
- data/spec/mongoid_geospatial/wrappers/rgeo_spec.rb +43 -0
- data/spec/spec_helper.rb +5 -21
- metadata +14 -26
- data/lib/mongoid_geospatial/contextual/mongo.rb +0 -118
- data/lib/mongoid_geospatial/criteria.rb +0 -10
- data/lib/mongoid_geospatial/criterion/complex.rb +0 -26
- data/lib/mongoid_geospatial/criterion/inclusion.rb +0 -14
- data/lib/mongoid_geospatial/criterion/near_spatial.rb +0 -52
- data/lib/mongoid_geospatial/criterion/within_spatial.rb +0 -62
- data/lib/mongoid_geospatial/extensions/symbol.rb +0 -46
- data/lib/mongoid_geospatial/finders.rb +0 -5
- data/lib/mongoid_geospatial/geospatial/geo_near_results.rb +0 -140
- data/spec/mongoid_geospatial/contextual/mongo_spec.rb +0 -135
- data/spec/mongoid_geospatial/criterion/complex_spec.rb +0 -15
- data/spec/mongoid_geospatial/criterion/inclusion_spec.rb +0 -375
- data/spec/mongoid_geospatial/criterion/near_spatial_spec.rb +0 -39
- data/spec/mongoid_geospatial/criterion/within_spatial_spec.rb +0 -54
- data/spec/mongoid_geospatial/geospatial/geo_near_results_spec.rb +0 -78
- data/spec/mongoid_geospatial/mongoid_geospatial_spec.rb +0 -83
@@ -1,52 +0,0 @@
|
|
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::Geospatial.earth_radius[input[:unit]]
|
32
|
-
unit *= Mongoid::Geospatial::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
|
-
|
@@ -1,62 +0,0 @@
|
|
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::Geospatial.earth_radius[input[:unit]]
|
40
|
-
unit *= Mongoid::Geospatial::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
|
-
|
@@ -1,46 +0,0 @@
|
|
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)
|
@@ -1,140 +0,0 @@
|
|
1
|
-
module Mongoid
|
2
|
-
module Geospatial
|
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::Geospatial::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::Geospatial.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::Geospatial.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,135 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
describe (Mongoid::VERSION > '3' ? Mongoid::Contextual::Mongo : Mongoid::Contexts::Mongo) do
|
4
|
-
describe "#geo_near" do
|
5
|
-
|
6
|
-
before do
|
7
|
-
Bar.delete_all
|
8
|
-
Bar.create_indexes
|
9
|
-
end
|
10
|
-
|
11
|
-
let!(:jfk) do
|
12
|
-
Bar.create(:name => 'jfk', :location => [-73.77694444, 40.63861111 ])
|
13
|
-
end
|
14
|
-
|
15
|
-
let!(:lax) do
|
16
|
-
Bar.create(:name => 'lax', :location => [-118.40, 33.94])
|
17
|
-
end
|
18
|
-
|
19
|
-
it "should work with specifying specific center and different location attribute on collction" do
|
20
|
-
Bar.geo_near(lax.location, :spherical => true).should == [lax, jfk]
|
21
|
-
Bar.geo_near(jfk.location, :spherical => true).should == [jfk, lax]
|
22
|
-
end
|
23
|
-
context 'option' do
|
24
|
-
context ':num' do
|
25
|
-
it "should limit number of results to 1" do
|
26
|
-
Bar.geo_near(jfk.location, :num => 1).size.should == 1
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
context ':maxDistance' do
|
31
|
-
it "should get 1 item" do
|
32
|
-
Bar.geo_near(lax.location, :spherical => true, :max_distance => 2465/Mongoid::Geospatial.earth_radius[:mi]).size.should == 1
|
33
|
-
end
|
34
|
-
it "should get 2 items" do
|
35
|
-
Bar.geo_near(lax.location, :spherical => true, :max_distance => 2480/Mongoid::Geospatial.earth_radius[:mi]).size.should == 2
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
context ':distance_multiplier' do
|
41
|
-
it "should multiply returned distance with multiplier" do
|
42
|
-
Bar.geo_near(lax.location, :spherical => true, :distance_multiplier=> Mongoid::Geospatial.earth_radius[:mi]).second.geo[:distance].to_i.should be_within(1).of(2469)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
context ':unit' do
|
47
|
-
it "should multiply returned distance with multiplier" do
|
48
|
-
pending
|
49
|
-
Bar.geo_near(lax.location, :spherical => true, :unit => :mi).second.geo[:distance].to_i.should be_within(1).of(2469)
|
50
|
-
end
|
51
|
-
|
52
|
-
it "should convert max_distance to radians with unit" do
|
53
|
-
Bar.geo_near(lax.location, :spherical => true, :max_distance => 2465, :unit => :mi).size.should == 1
|
54
|
-
end
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
context ':query' do
|
59
|
-
it "should filter using extra query option" do
|
60
|
-
# two record in the collection, only one's name is Munich
|
61
|
-
Bar.geo_near(jfk.location, :query => {:name => jfk.name}).should == [jfk]
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
context 'criteria chaining' do
|
68
|
-
it "should filter by where" do
|
69
|
-
Bar.where(:name => jfk.name).geo_near(jfk.location).should == [jfk]
|
70
|
-
Bar.any_of({:name => jfk.name},{:name => lax.name}).geo_near(jfk.location).should == [jfk,lax]
|
71
|
-
end
|
72
|
-
|
73
|
-
it 'should skip 1' do
|
74
|
-
Bar.skip(1).geo_near(jfk.location).size.should == 1
|
75
|
-
end
|
76
|
-
|
77
|
-
it 'should limit 1' do
|
78
|
-
Bar.limit(1).geo_near(jfk.location).size.should == 1
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
context ':page' do
|
84
|
-
before do
|
85
|
-
Bar.delete_all
|
86
|
-
Bar.create_indexes
|
87
|
-
|
88
|
-
50.times do
|
89
|
-
Bar.create({:location => [rand(360)-180,rand(360)-180]})
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
context ":paginator :array" do
|
94
|
-
[nil,1,2].each do |page|
|
95
|
-
it "page=#{page} should have 25" do
|
96
|
-
Bar.geo_near([1,1], :page => page).size.should == 25
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
it "page=3 should have 0" do
|
101
|
-
Bar.geo_near([1,1], :page => 20).size.should == 0
|
102
|
-
end
|
103
|
-
|
104
|
-
it "per_page=5" do
|
105
|
-
Bar.geo_near([1,1], :page => 1, :per_page => 5).size.should == 5
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
context ":paginator :kaminari" do
|
110
|
-
let(:near) {Bar.geo_near([1,1], :page => 1)}
|
111
|
-
|
112
|
-
it 'should have 50 Bars' do
|
113
|
-
Bar.all.count.should == 50
|
114
|
-
end
|
115
|
-
|
116
|
-
it "should have limit_value" do
|
117
|
-
near.limit_value.should == 25
|
118
|
-
end
|
119
|
-
|
120
|
-
# check results['results'] in GeoNearResults
|
121
|
-
it 'should find 25 items' do
|
122
|
-
near.size.should == 25
|
123
|
-
end
|
124
|
-
|
125
|
-
it "should have current_page" do
|
126
|
-
near.current_page.should == 1
|
127
|
-
end
|
128
|
-
|
129
|
-
it "should have num_pages" do
|
130
|
-
near.num_pages.should == 1
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
end
|