mongoid_geospatial 1.0.0rc0
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/.document +5 -0
- data/.gitignore +49 -0
- data/.rspec +1 -0
- data/Gemfile +11 -0
- data/README.md +330 -0
- data/Rakefile +18 -0
- data/lib/mongoid_geospatial/contexts/mongo.rb +115 -0
- data/lib/mongoid_geospatial/criteria.rb +5 -0
- data/lib/mongoid_geospatial/criterion/complex.rb +19 -0
- data/lib/mongoid_geospatial/criterion/inclusion.rb +14 -0
- data/lib/mongoid_geospatial/criterion/near_spatial.rb +50 -0
- data/lib/mongoid_geospatial/criterion/within_spatial.rb +60 -0
- data/lib/mongoid_geospatial/criterion.rb +3 -0
- data/lib/mongoid_geospatial/extensions/hash.rb +22 -0
- data/lib/mongoid_geospatial/extensions/symbol.rb +46 -0
- data/lib/mongoid_geospatial/field_option.rb +16 -0
- data/lib/mongoid_geospatial/fields/line_string.rb +18 -0
- data/lib/mongoid_geospatial/fields/point.rb +51 -0
- data/lib/mongoid_geospatial/fields/polygon.rb +22 -0
- data/lib/mongoid_geospatial/finders.rb +5 -0
- data/lib/mongoid_geospatial/geospatial/core_ext.rb +27 -0
- data/lib/mongoid_geospatial/geospatial/geo_near_results.rb +140 -0
- data/lib/mongoid_geospatial/geospatial.rb +86 -0
- data/lib/mongoid_geospatial/version.rb +5 -0
- data/lib/mongoid_geospatial.rb +16 -0
- data/mongoid_geospatial.gemspec +28 -0
- data/spec/config/mongod.conf +3 -0
- data/spec/config/mongoid.yml +18 -0
- data/spec/functional/contexts/mongo_spec.rb +127 -0
- data/spec/functional/criterion/inclusion_spec.rb +356 -0
- data/spec/functional/mongoid_geospatial_spec.rb +54 -0
- data/spec/functional/spatial/geo_near_results_spec.rb +78 -0
- data/spec/models/account.rb +19 -0
- data/spec/models/acolyte.rb +9 -0
- data/spec/models/address.rb +62 -0
- data/spec/models/address_component.rb +5 -0
- data/spec/models/agent.rb +10 -0
- data/spec/models/alert.rb +5 -0
- data/spec/models/animal.rb +21 -0
- data/spec/models/answer.rb +4 -0
- data/spec/models/bar.rb +9 -0
- data/spec/models/birthday.rb +13 -0
- data/spec/models/book.rb +5 -0
- data/spec/models/business.rb +7 -0
- data/spec/models/callbacks.rb +57 -0
- data/spec/models/category.rb +13 -0
- data/spec/models/circus.rb +7 -0
- data/spec/models/comment.rb +13 -0
- data/spec/models/country_code.rb +6 -0
- data/spec/models/description.rb +11 -0
- data/spec/models/division.rb +5 -0
- data/spec/models/drug.rb +5 -0
- data/spec/models/employer.rb +5 -0
- data/spec/models/entry.rb +6 -0
- data/spec/models/event.rb +20 -0
- data/spec/models/farm.rb +10 -0
- data/spec/models/favorite.rb +6 -0
- data/spec/models/fruits.rb +11 -0
- data/spec/models/game.rb +18 -0
- data/spec/models/ghost.rb +7 -0
- data/spec/models/house.rb +4 -0
- data/spec/models/inheritance.rb +90 -0
- data/spec/models/league.rb +5 -0
- data/spec/models/location.rb +5 -0
- data/spec/models/login.rb +6 -0
- data/spec/models/membership.rb +4 -0
- data/spec/models/mixed_drink.rb +4 -0
- data/spec/models/name.rb +13 -0
- data/spec/models/namespacing.rb +11 -0
- data/spec/models/observed.rb +41 -0
- data/spec/models/override.rb +16 -0
- data/spec/models/owner.rb +6 -0
- data/spec/models/page.rb +5 -0
- data/spec/models/page_question.rb +4 -0
- data/spec/models/paranoid_post.rb +18 -0
- data/spec/models/parents.rb +32 -0
- data/spec/models/patient.rb +15 -0
- data/spec/models/person.rb +146 -0
- data/spec/models/pet.rb +7 -0
- data/spec/models/pet_owner.rb +6 -0
- data/spec/models/phone.rb +7 -0
- data/spec/models/player.rb +23 -0
- data/spec/models/post.rb +26 -0
- data/spec/models/preference.rb +9 -0
- data/spec/models/question.rb +8 -0
- data/spec/models/quiz.rb +6 -0
- data/spec/models/rating.rb +8 -0
- data/spec/models/river.rb +20 -0
- data/spec/models/role.rb +5 -0
- data/spec/models/service.rb +6 -0
- data/spec/models/shelf.rb +5 -0
- data/spec/models/slave_address_numbers.rb +14 -0
- data/spec/models/survey.rb +5 -0
- data/spec/models/tag.rb +6 -0
- data/spec/models/tracking_id_validation_history.rb +25 -0
- data/spec/models/translation.rb +5 -0
- data/spec/models/tree.rb +9 -0
- data/spec/models/user.rb +9 -0
- data/spec/models/user_account.rb +10 -0
- data/spec/models/vet_visit.rb +5 -0
- data/spec/models/video.rb +9 -0
- data/spec/models/wiki_page.rb +6 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/support/authentication.rb +29 -0
- data/spec/unit/criterion/complex_spec.rb +15 -0
- data/spec/unit/criterion/inclusion_spec.rb +0 -0
- data/spec/unit/criterion/near_spatial_spec.rb +39 -0
- data/spec/unit/criterion/within_spatial_spec.rb +52 -0
- metadata +339 -0
|
@@ -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::NearSpatial]
|
|
12
|
+
|
|
13
|
+
def near(calc = :flat)
|
|
14
|
+
Criterion::NearSpatial.new(:operator => get_op('near',calc), :key => self)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# alias for self.near(:sphere)
|
|
18
|
+
#
|
|
19
|
+
# @return [Criterion::NearSpatial]
|
|
20
|
+
def near_sphere
|
|
21
|
+
self.near(:sphere)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @param [Symbol] shape :box,:polygon,:center,:center_sphere
|
|
25
|
+
#
|
|
26
|
+
# @return [Criterion::WithinSpatial]
|
|
27
|
+
def within(shape)
|
|
28
|
+
shape = get_op(:center,:sphere) if shape == :center_sphere
|
|
29
|
+
Criterion::WithinSpatial.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,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,18 @@
|
|
|
1
|
+
module Mongoid
|
|
2
|
+
module Geospatial
|
|
3
|
+
class LineString
|
|
4
|
+
|
|
5
|
+
include Mongoid::Fields::Serializable
|
|
6
|
+
|
|
7
|
+
def deserialize(object)
|
|
8
|
+
RGeo::Geographic.spherical_factory.line_string *object
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def serialize(object)
|
|
12
|
+
object.to_a
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Mongoid
|
|
2
|
+
module Geospatial
|
|
3
|
+
class Point
|
|
4
|
+
|
|
5
|
+
include Mongoid::Fields::Serializable
|
|
6
|
+
|
|
7
|
+
def deserialize(object)
|
|
8
|
+
return unless object && !object.empty?
|
|
9
|
+
RGeo::Geographic.spherical_factory.point *object
|
|
10
|
+
#["x"], object["y"]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def serialize(object)
|
|
14
|
+
object.respond_to?(:x) ? [object.x, object.y] : object
|
|
15
|
+
# if object.respond_to? :x
|
|
16
|
+
# { "x" => object.x, "y" => object.y }
|
|
17
|
+
# else
|
|
18
|
+
# { "x" => object[0], "y" => object[1] }
|
|
19
|
+
# end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# - self.spacial_fields ||= []
|
|
23
|
+
# - self.spacial_fields << field.name.to_sym if self.spacial_fields.kind_of? Array
|
|
24
|
+
# -
|
|
25
|
+
# - define_method "distance_from_#{field.name}" do |*args|
|
|
26
|
+
# - self.distance_from(field.name, *args)
|
|
27
|
+
# - end
|
|
28
|
+
# -
|
|
29
|
+
# - define_method field.name do
|
|
30
|
+
# - output = self[field.name] || [nil,nil]
|
|
31
|
+
# - output = {lng_meth => output[0], lat_meth => output[1]} unless options[:return_array]
|
|
32
|
+
# - return options[:class].new(output) if options[:class]
|
|
33
|
+
# - output
|
|
34
|
+
# - end
|
|
35
|
+
# -
|
|
36
|
+
# - define_method "#{field.name}=" do |arg|
|
|
37
|
+
# - if arg.kind_of?(Hash) && arg[lng_meth] && arg[lat_meth]
|
|
38
|
+
# - arg = [arg[lng_meth].to_f, arg[lat_meth].to_f]
|
|
39
|
+
# - elsif arg.respond_to?(:to_lng_lat)
|
|
40
|
+
# - arg = arg.to_lng_lat
|
|
41
|
+
# - end
|
|
42
|
+
# - self[field.name]=arg
|
|
43
|
+
# - arg = [nil,nil] if arg.nil?
|
|
44
|
+
# - return arg[0..1] if options[:return_array]
|
|
45
|
+
# - h = {lng_meth => arg[0], lat_meth => arg[1]}
|
|
46
|
+
# - return h if options[:class].blank?
|
|
47
|
+
# - options[:class].new(h)
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Mongoid
|
|
2
|
+
module Geospatial
|
|
3
|
+
class Polygon
|
|
4
|
+
|
|
5
|
+
include Mongoid::Fields::Serializable
|
|
6
|
+
|
|
7
|
+
def deserialize(object)
|
|
8
|
+
points = object.map do |pair|
|
|
9
|
+
RGeo::Geographic.spherical_factory.point *pair
|
|
10
|
+
end
|
|
11
|
+
ring = RGeo::Geographic.spherical_factory.linear_ring points
|
|
12
|
+
RGeo::Geographic.spherical_factory.polygon ring
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def serialize(object)
|
|
16
|
+
object #.flatten
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
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::Geospatial.lat_symbols & self.keys).first
|
|
15
|
+
return self[v].to_f if !v.nil? && self[v]
|
|
16
|
+
raise "Hash must contain #{Mongoid::Geospatial.lat_symbols.inspect} if ruby version is less than 1.9" if RUBY_VERSION.to_f < 1.9
|
|
17
|
+
raise "Hash cannot contain #{Mongoid::Geospatial.lng_symbols.inspect} as the second item if there is no #{Mongoid::Geospatial.lat_symbols.inspect}" if Mongoid::Geospatial.lng_symbols.index(self.keys[1])
|
|
18
|
+
self.values[1].to_f
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_lng
|
|
22
|
+
v = (Mongoid::Geospatial.lng_symbols & self.keys).first
|
|
23
|
+
return self[v].to_f if !v.nil? && self[v]
|
|
24
|
+
raise "Hash cannot contain #{Mongoid::Geospatial.lat_symbols.inspect} as the first item if there is no #{Mongoid::Geospatial.lng_symbols.inspect}" if Mongoid::Geospatial.lat_symbols.index(self.keys[0])
|
|
25
|
+
self.values[0].to_f
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
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
|
+
(self.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
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
require 'mongoid_geospatial/geospatial/core_ext'
|
|
2
|
+
require 'mongoid_geospatial/geospatial/geo_near_results'
|
|
3
|
+
|
|
4
|
+
module Mongoid
|
|
5
|
+
module Geospatial
|
|
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
|
+
|
|
23
|
+
included do
|
|
24
|
+
attr_accessor :geo
|
|
25
|
+
cattr_accessor :spatial_fields, :spatial_fields_indexed
|
|
26
|
+
@@spatial_fields = []
|
|
27
|
+
@@spatial_fields_indexed = []
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
module ClassMethods #:nodoc:
|
|
31
|
+
# create spatial index for given field
|
|
32
|
+
# @param [String,Symbol] name
|
|
33
|
+
# @param [Hash] options options for spatial_index
|
|
34
|
+
|
|
35
|
+
def spatial_index name, *options
|
|
36
|
+
self.spatial_fields_indexed << name
|
|
37
|
+
index [[ name, Mongo::GEO2D ]], *options
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# def distance(p2, opts = {})
|
|
42
|
+
# p1 = self.send(key)
|
|
43
|
+
# Mongoid::Geospatial.distance(p1, p2, opts)
|
|
44
|
+
# end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# def self.distance(p1,p2,opts = {})
|
|
49
|
+
# opts[:formula] ||= (opts[:spherical]) ? @@spherical_distance_formula : :pythagorean_theorem
|
|
50
|
+
# p1 = p1.to_lng_lat if p1.respond_to?(:to_lng_lat)
|
|
51
|
+
# p2 = p2.to_lng_lat if p2.respond_to?(:to_lng_lat)
|
|
52
|
+
|
|
53
|
+
# rads = Formulas.send(opts[:formula], p1, p2)
|
|
54
|
+
|
|
55
|
+
# if unit = earth_radius[opts[:unit]]
|
|
56
|
+
# opts[:unit] = (rads.instance_variable_get("@radian")) ? unit : unit * RAD_PER_DEG
|
|
57
|
+
# end
|
|
58
|
+
|
|
59
|
+
# rads *= opts[:unit].to_f if opts[:unit]
|
|
60
|
+
# rads
|
|
61
|
+
|
|
62
|
+
# end
|
|
63
|
+
RAD_PER_DEG = Math::PI/180
|
|
64
|
+
mattr_accessor :lng_symbols
|
|
65
|
+
@@lng_symbols = LNG_SYMBOLS.dup
|
|
66
|
+
|
|
67
|
+
mattr_accessor :lat_symbols
|
|
68
|
+
@@lat_symbols = LAT_SYMBOLS.dup
|
|
69
|
+
|
|
70
|
+
mattr_accessor :earth_radius
|
|
71
|
+
@@earth_radius = EARTH_RADIUS.dup
|
|
72
|
+
|
|
73
|
+
mattr_accessor :paginator
|
|
74
|
+
@@paginator = :array
|
|
75
|
+
|
|
76
|
+
mattr_accessor :default_per_page
|
|
77
|
+
@@default_per_page = 25
|
|
78
|
+
|
|
79
|
+
# mattr_accessor :spherical_distance_formula
|
|
80
|
+
# @@spherical_distance_formula = :n_vector
|
|
81
|
+
mattr_accessor :geo_factory
|
|
82
|
+
@@lng_symbols = GEO_FACTORY.dup
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'rgeo'
|
|
2
|
+
require 'mongoid'
|
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
|
4
|
+
require 'active_support/concern'
|
|
5
|
+
require 'mongoid_geospatial/contexts/mongo'
|
|
6
|
+
require 'mongoid_geospatial/criteria'
|
|
7
|
+
require 'mongoid_geospatial/criterion'
|
|
8
|
+
require 'mongoid_geospatial/extensions/hash'
|
|
9
|
+
require 'mongoid_geospatial/extensions/symbol'
|
|
10
|
+
require 'mongoid_geospatial/field_option'
|
|
11
|
+
require 'mongoid_geospatial/fields/point'
|
|
12
|
+
require 'mongoid_geospatial/fields/polygon'
|
|
13
|
+
require 'mongoid_geospatial/fields/line_string'
|
|
14
|
+
require 'mongoid_geospatial/finders'
|
|
15
|
+
require 'mongoid_geospatial/geospatial'
|
|
16
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
require File.expand_path('../lib/mongoid_geospatial/version', __FILE__)
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |gem|
|
|
5
|
+
gem.authors = ["Ryan Ong", "Marcos Piccinini"]
|
|
6
|
+
gem.email = ["use@git.hub.com"]
|
|
7
|
+
gem.description = %q{mongoid_geospatial simplifies spatial calculations. Adds integration into mongoid so pagination and other function continue to work. It adds symbol extentions to simplify query creation.}
|
|
8
|
+
gem.summary = %q{A Mongoid Extention that simplifies and adds support for MongoDB Geo Spatial Calculations.}
|
|
9
|
+
gem.homepage = "https://github.com/nofxx/mongoid_geospatial"
|
|
10
|
+
|
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
14
|
+
gem.name = "mongoid_geospatial"
|
|
15
|
+
gem.require_paths = ["lib"]
|
|
16
|
+
gem.version = Mongoid::Geospatial::VERSION
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
gem.add_dependency('rgeo', ['>= 0.3.5'])
|
|
20
|
+
gem.add_dependency('mongoid', ['>= 2.1.0'])
|
|
21
|
+
gem.add_dependency('activesupport', ["~> 3.0"])
|
|
22
|
+
gem.add_development_dependency('yard', ["~>0.6.0"])
|
|
23
|
+
gem.add_development_dependency('rspec', ['~>2.3'])
|
|
24
|
+
gem.add_development_dependency('rcov', ['>= 0'])
|
|
25
|
+
gem.add_development_dependency('mocha', ['>= 0'])
|
|
26
|
+
gem.add_development_dependency('will_paginate', ['>= 0'])
|
|
27
|
+
gem.add_development_dependency('kaminari', ['>= 0'])
|
|
28
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
test:
|
|
2
|
+
database: mongoid_config_test
|
|
3
|
+
host: localhost
|
|
4
|
+
slaves:
|
|
5
|
+
# - host: localhost
|
|
6
|
+
# port: 27018
|
|
7
|
+
# - host: localhost
|
|
8
|
+
# port: 27019
|
|
9
|
+
allow_dynamic_fields: false
|
|
10
|
+
include_root_in_json: true
|
|
11
|
+
parameterize_keys: false
|
|
12
|
+
persist_in_safe_mode: false
|
|
13
|
+
raise_not_found_error: false
|
|
14
|
+
reconnect_time: 5
|
|
15
|
+
autocreate_indexes: false
|
|
16
|
+
persist_types: false
|
|
17
|
+
option_no_exist: false
|
|
18
|
+
skip_version_check: false
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe 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
|
+
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
context ':page' do
|
|
86
|
+
before do
|
|
87
|
+
Bar.delete_all
|
|
88
|
+
Bar.create_indexes
|
|
89
|
+
|
|
90
|
+
50.times do
|
|
91
|
+
Bar.create({:location => [rand(360)-180,rand(360)-180]})
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
context ":paginator :array" do
|
|
96
|
+
[nil,1,2].each do |page|
|
|
97
|
+
it "page=#{page} should have 25" do
|
|
98
|
+
Bar.geo_near([1,1], :page => page).size.should == 25
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "page=3 should have 0" do
|
|
103
|
+
Bar.geo_near([1,1], :page => 20).size.should == 0
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "per_page=5" do
|
|
107
|
+
Bar.geo_near([1,1], :page => 1, :per_page => 5).size.should == 5
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
context ":paginator :kaminari" do
|
|
112
|
+
let(:near) {Bar.geo_near([1,1], :page => 1)}
|
|
113
|
+
it "should have current_page" do
|
|
114
|
+
near.current_page.should == 1
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "should have num_pages" do
|
|
118
|
+
near.num_pages.should == 2
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "should have limit_value" do
|
|
122
|
+
near.limit_value.should == 25
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
end
|