mongoid-geospatial 3.9.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.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +49 -0
- data/.travis.yml +19 -0
- data/Gemfile +30 -0
- data/Guardfile +16 -0
- data/MIT-LICENSE +20 -0
- data/README.md +519 -0
- data/Rakefile +17 -0
- data/lib/mongoid/geospatial.rb +106 -0
- data/lib/mongoid/geospatial/ext/rgeo_spherical_point_impl.rb +18 -0
- data/lib/mongoid/geospatial/fields/box.rb +6 -0
- data/lib/mongoid/geospatial/fields/circle.rb +18 -0
- data/lib/mongoid/geospatial/fields/geometry_field.rb +41 -0
- data/lib/mongoid/geospatial/fields/line.rb +6 -0
- data/lib/mongoid/geospatial/fields/point.rb +143 -0
- data/lib/mongoid/geospatial/fields/polygon.rb +6 -0
- data/lib/mongoid/geospatial/helpers/delegate.rb +30 -0
- data/lib/mongoid/geospatial/helpers/spatial.rb +19 -0
- data/lib/mongoid/geospatial/helpers/sphere.rb +18 -0
- data/lib/mongoid/geospatial/version.rb +6 -0
- data/lib/mongoid/geospatial/wrappers/georuby.rb +33 -0
- data/lib/mongoid/geospatial/wrappers/rgeo.rb +43 -0
- data/mongoid-geospatial.gemspec +22 -0
- data/spec/models/address.rb +69 -0
- data/spec/models/alarm.rb +12 -0
- data/spec/models/bar.rb +13 -0
- data/spec/models/bus.rb +12 -0
- data/spec/models/event.rb +17 -0
- data/spec/models/farm.rb +13 -0
- data/spec/models/person.rb +97 -0
- data/spec/models/phone.rb +8 -0
- data/spec/models/place.rb +13 -0
- data/spec/models/river.rb +22 -0
- data/spec/mongoid/geospatial/fields/box_spec.rb +10 -0
- data/spec/mongoid/geospatial/fields/circle_spec.rb +10 -0
- data/spec/mongoid/geospatial/fields/line_spec.rb +40 -0
- data/spec/mongoid/geospatial/fields/point_spec.rb +254 -0
- data/spec/mongoid/geospatial/fields/polygon_spec.rb +84 -0
- data/spec/mongoid/geospatial/geospatial_spec.rb +143 -0
- data/spec/mongoid/geospatial/helpers/core_spec.rb +27 -0
- data/spec/mongoid/geospatial/helpers/delegate_spec.rb +54 -0
- data/spec/mongoid/geospatial/helpers/spatial_spec.rb +36 -0
- data/spec/mongoid/geospatial/helpers/sphere_spec.rb +26 -0
- data/spec/mongoid/geospatial/wrappers/georuby_spec.rb +66 -0
- data/spec/mongoid/geospatial/wrappers/rgeo_spec.rb +121 -0
- data/spec/spec_helper.rb +64 -0
- data/spec/support/authentication.rb +29 -0
- data/spec/support/mongod.conf +3 -0
- data/spec/support/mongoid.yml +19 -0
- metadata +164 -0
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
|
4
|
+
require 'rspec/core'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
7
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
8
|
+
end
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
11
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
12
|
+
spec.rcov = true
|
13
|
+
end
|
14
|
+
|
15
|
+
task default: :spec
|
16
|
+
|
17
|
+
require 'yard'
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'mongoid'
|
2
|
+
require 'active_support/core_ext/string/inflections'
|
3
|
+
require 'active_support/concern'
|
4
|
+
require 'mongoid/geospatial/helpers/spatial'
|
5
|
+
require 'mongoid/geospatial/helpers/sphere'
|
6
|
+
require 'mongoid/geospatial/helpers/delegate'
|
7
|
+
|
8
|
+
require 'mongoid/geospatial/fields/geometry_field'
|
9
|
+
|
10
|
+
%w(point circle box line polygon).each do |type|
|
11
|
+
require "mongoid/geospatial/fields/#{type}"
|
12
|
+
end
|
13
|
+
|
14
|
+
module Mongoid
|
15
|
+
#
|
16
|
+
# Main Geospatial module
|
17
|
+
#
|
18
|
+
# include Mongoid::Geospatial
|
19
|
+
#
|
20
|
+
module Geospatial
|
21
|
+
extend ActiveSupport::Concern
|
22
|
+
|
23
|
+
LNG_SYMBOLS = [:x, :lon, :long, :lng, :longitude, 'x', 'lon', 'long', 'longitude']
|
24
|
+
LAT_SYMBOLS = [:y, :lat, :latitude, 'y', 'lat', 'latitude']
|
25
|
+
|
26
|
+
EARTH_RADIUS_KM = 6371 # taken directly from mongodb
|
27
|
+
RAD_PER_DEG = Math::PI / 180
|
28
|
+
|
29
|
+
EARTH_RADIUS = {
|
30
|
+
km: EARTH_RADIUS_KM,
|
31
|
+
m: EARTH_RADIUS_KM * 1000,
|
32
|
+
mi: EARTH_RADIUS_KM * 0.621371192, # taken directly from mongodb
|
33
|
+
ft: EARTH_RADIUS_KM * 5280 * 0.621371192,
|
34
|
+
sm: EARTH_RADIUS_KM * 0.53995680345572 # sea mile
|
35
|
+
}
|
36
|
+
|
37
|
+
mattr_accessor :lng_symbols
|
38
|
+
mattr_accessor :lat_symbols
|
39
|
+
mattr_accessor :earth_radius
|
40
|
+
mattr_accessor :factory
|
41
|
+
|
42
|
+
@@lng_symbols = LNG_SYMBOLS.dup
|
43
|
+
@@lat_symbols = LAT_SYMBOLS.dup
|
44
|
+
@@earth_radius = EARTH_RADIUS.dup
|
45
|
+
|
46
|
+
included do
|
47
|
+
# attr_accessor :geo
|
48
|
+
cattr_accessor :spatial_fields, :spatial_fields_indexed
|
49
|
+
self.spatial_fields = []
|
50
|
+
self.spatial_fields_indexed = []
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.with_rgeo!
|
54
|
+
require 'mongoid/geospatial/wrappers/rgeo'
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.with_georuby!
|
58
|
+
require 'mongoid/geospatial/wrappers/georuby'
|
59
|
+
end
|
60
|
+
|
61
|
+
module ClassMethods #:nodoc:
|
62
|
+
def geo_field(name, options = {})
|
63
|
+
field name, { type: Mongoid::Geospatial::Point, spatial: true }.merge(options)
|
64
|
+
end
|
65
|
+
|
66
|
+
# create spatial index for given field
|
67
|
+
# @param [String,Symbol] name
|
68
|
+
# @param [Hash] options options for spatial_index
|
69
|
+
# http://www.mongodb.org/display/DOCS/Geospatial+Indexing#GeospatialIndexing-geoNearCommand
|
70
|
+
def spatial_index(name, options = {})
|
71
|
+
spatial_fields_indexed << name
|
72
|
+
index({ name => '2d' }, options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def sphere_index(name, options = {})
|
76
|
+
spatial_fields_indexed << name
|
77
|
+
index({ name => '2dsphere' }, options)
|
78
|
+
end
|
79
|
+
|
80
|
+
def spatial_scope(field, _opts = {})
|
81
|
+
singleton_class.class_eval do
|
82
|
+
# define_method(:close) do |args|
|
83
|
+
define_method(:nearby) do |args|
|
84
|
+
queryable.where(field.near_sphere => args)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# model.instance_eval do # wont work
|
93
|
+
# # define_method "near_#{field.name}" do |*args|
|
94
|
+
# # self.where(field.name => args)
|
95
|
+
# # end
|
96
|
+
# end
|
97
|
+
|
98
|
+
# define_method "near_#{field.name}" do |*args|
|
99
|
+
# queryable.where(field.near_sphere => args)
|
100
|
+
# end
|
101
|
+
|
102
|
+
# model.class_eval do
|
103
|
+
# define_method "close_to" do |*args|
|
104
|
+
# queriable.where(field.name.near_sphere => *args)
|
105
|
+
# end
|
106
|
+
# end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Geospatial
|
3
|
+
# Main Geometry Array
|
4
|
+
# Holds Lines/Polygons....
|
5
|
+
class GeometryField < Array
|
6
|
+
def bounding_box
|
7
|
+
max_x, min_x = -Float::MAX, Float::MAX
|
8
|
+
max_y, min_y = -Float::MAX, Float::MAX
|
9
|
+
each do |point|
|
10
|
+
max_y = point[1] if point[1] > max_y
|
11
|
+
min_y = point[1] if point[1] < min_y
|
12
|
+
max_x = point[0] if point[0] > max_x
|
13
|
+
min_x = point[0] if point[0] < min_x
|
14
|
+
end
|
15
|
+
[[min_x, min_y], [max_x, max_y]]
|
16
|
+
end
|
17
|
+
alias_method :bbox, :bounding_box
|
18
|
+
|
19
|
+
def center_point
|
20
|
+
min, max = *bbox
|
21
|
+
[(min[0] + max[0]) / 2.0, (min[1] + max[1]) / 2.0]
|
22
|
+
end
|
23
|
+
alias_method :center, :center_point
|
24
|
+
|
25
|
+
def radius(r = 1)
|
26
|
+
[center, r]
|
27
|
+
end
|
28
|
+
|
29
|
+
def radius_sphere(r = 1, unit = :km)
|
30
|
+
radius r.to_f / Mongoid::Geospatial.earth_radius[unit]
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
# Database -> Object
|
35
|
+
def demongoize(o)
|
36
|
+
new(o)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Geospatial
|
3
|
+
# Point
|
4
|
+
#
|
5
|
+
class Point
|
6
|
+
include Enumerable
|
7
|
+
attr_reader :x, :y
|
8
|
+
|
9
|
+
def initialize(x = nil, y = nil)
|
10
|
+
return unless x && y
|
11
|
+
@x, @y = x, y
|
12
|
+
end
|
13
|
+
|
14
|
+
# Object -> Database
|
15
|
+
# Let's store NilClass if we are invalid.
|
16
|
+
def mongoize
|
17
|
+
return nil unless x && y
|
18
|
+
[x, y]
|
19
|
+
end
|
20
|
+
alias_method :to_a, :mongoize
|
21
|
+
alias_method :to_xy, :mongoize
|
22
|
+
|
23
|
+
def [](args)
|
24
|
+
mongoize[args]
|
25
|
+
end
|
26
|
+
|
27
|
+
def each
|
28
|
+
yield x
|
29
|
+
yield y
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
"#{x}, #{y}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_hsh(xl = :x, yl = :y)
|
37
|
+
{ xl => x, yl => y }
|
38
|
+
end
|
39
|
+
alias_method :to_hash, :to_hsh
|
40
|
+
|
41
|
+
def radius(r = 1)
|
42
|
+
[mongoize, r]
|
43
|
+
end
|
44
|
+
|
45
|
+
def radius_sphere(r = 1, unit = :km)
|
46
|
+
radius r.to_f / Mongoid::Geospatial.earth_radius[unit]
|
47
|
+
end
|
48
|
+
|
49
|
+
def valid?
|
50
|
+
x && y && x.is_a?(Numeric) && y.is_a?(Numeric)
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Distance calculation methods. Thinking about not using it
|
55
|
+
# One needs to choose and external lib. GeoRuby or RGeo
|
56
|
+
#
|
57
|
+
# Return the distance between the 2D points (ie taking care
|
58
|
+
# only of the x and y coordinates), assuming the points are
|
59
|
+
# in projected coordinates. Euclidian distance in whatever
|
60
|
+
# unit the x and y ordinates are.
|
61
|
+
# def euclidian_distance(point)
|
62
|
+
# Math.sqrt((point.x - x)**2 + (point.y - y)**2)
|
63
|
+
# end
|
64
|
+
|
65
|
+
# # Spherical distance in meters, using 'Haversine' formula.
|
66
|
+
# # with a radius of 6471000m
|
67
|
+
# # Assumes x is the lon and y the lat, in degrees (Changed
|
68
|
+
# in version 1.1).
|
69
|
+
# # The user has to make sure using this distance makes sense
|
70
|
+
# (ie she should be in latlon coordinates)
|
71
|
+
# def spherical_distance(point,r=6370997.0)
|
72
|
+
# dlat = (point.lat - lat) * DEG2RAD / 2
|
73
|
+
# dlon = (point.lon - lon) * DEG2RAD / 2
|
74
|
+
|
75
|
+
# a = Math.sin(dlat)**2 + Math.cos(lat * DEG2RAD) *
|
76
|
+
# Math.cos(point.lat * DEG2RAD) * Math.sin(dlon)**2
|
77
|
+
# c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
|
78
|
+
# r * c
|
79
|
+
# end
|
80
|
+
|
81
|
+
class << self
|
82
|
+
# Makes life easier:
|
83
|
+
# "" -> nil
|
84
|
+
def from_string(str)
|
85
|
+
return nil if str.empty?
|
86
|
+
str.split(/,|\s/).reject(&:empty?).map(&:to_f)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Also makes life easier:
|
90
|
+
# [] -> nil
|
91
|
+
def from_array(ary)
|
92
|
+
return nil if ary.empty?
|
93
|
+
ary[0..1].map(&:to_f)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Throw error on wrong hash, just for a change.
|
97
|
+
def from_hash(hsh)
|
98
|
+
fail 'Hash must have at least 2 items' if hsh.size < 2
|
99
|
+
[from_hash_x(hsh), from_hash_y(hsh)]
|
100
|
+
end
|
101
|
+
|
102
|
+
def from_hash_y(hsh)
|
103
|
+
v = (Mongoid::Geospatial.lat_symbols & hsh.keys).first
|
104
|
+
return hsh[v].to_f if !v.nil? && hsh[v]
|
105
|
+
fail "Hash must contain #{Mongoid::Geospatial.lat_symbols.inspect} if Ruby version is less than 1.9" if RUBY_VERSION.to_f < 1.9
|
106
|
+
fail "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(hsh.keys[1])
|
107
|
+
hsh.values[1].to_f
|
108
|
+
end
|
109
|
+
|
110
|
+
def from_hash_x(hsh)
|
111
|
+
v = (Mongoid::Geospatial.lng_symbols & hsh.keys).first
|
112
|
+
return hsh[v].to_f if !v.nil? && hsh[v]
|
113
|
+
fail "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(hsh.keys[0])
|
114
|
+
hsh.values[0].to_f
|
115
|
+
end
|
116
|
+
|
117
|
+
# Database -> Object
|
118
|
+
def demongoize(object)
|
119
|
+
Point.new(*object) if object
|
120
|
+
end
|
121
|
+
|
122
|
+
def mongoize(object)
|
123
|
+
case object
|
124
|
+
when Point then object.mongoize
|
125
|
+
when String then from_string(object)
|
126
|
+
when Array then from_array(object)
|
127
|
+
when Hash then from_hash(object)
|
128
|
+
when NilClass then nil
|
129
|
+
else
|
130
|
+
return object.to_xy if object.respond_to?(:to_xy)
|
131
|
+
fail 'Invalid Point'
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Converts the object that was supplied to a criteria
|
136
|
+
# into a database friendly form.
|
137
|
+
def evolve(object)
|
138
|
+
object.respond_to?(:x) ? object.mongoize : object
|
139
|
+
end
|
140
|
+
end # << self
|
141
|
+
end # Point
|
142
|
+
end # Geospatial
|
143
|
+
end # Mongoid
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#
|
2
|
+
# Mongoid fields extension
|
3
|
+
#
|
4
|
+
# field :foo, :delegate => {opts
|
5
|
+
#
|
6
|
+
Mongoid::Fields.option :delegate do |model, field, options|
|
7
|
+
options = {} unless options.is_a?(Hash)
|
8
|
+
x_meth = options[:x] || :x
|
9
|
+
y_meth = options[:y] || :y
|
10
|
+
|
11
|
+
model.instance_eval do
|
12
|
+
|
13
|
+
define_method x_meth do
|
14
|
+
self[field.name][0]
|
15
|
+
end
|
16
|
+
|
17
|
+
define_method y_meth do
|
18
|
+
self[field.name][1]
|
19
|
+
end
|
20
|
+
|
21
|
+
define_method "#{x_meth}=" do |arg|
|
22
|
+
self[field.name][0] = arg
|
23
|
+
end
|
24
|
+
|
25
|
+
define_method "#{y_meth}=" do |arg|
|
26
|
+
self[field.name][1] = arg
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#
|
2
|
+
# Mongoid fields extension
|
3
|
+
#
|
4
|
+
# field :foo, :spatial => true
|
5
|
+
#
|
6
|
+
Mongoid::Fields.option :spatial do |model, field, _options|
|
7
|
+
# options = {} unless options.kind_of?(Hash)
|
8
|
+
|
9
|
+
model.class_eval do
|
10
|
+
|
11
|
+
spatial_fields << field.name.to_sym
|
12
|
+
spatial_fields_indexed << field.name.to_sym
|
13
|
+
|
14
|
+
# Create 2D index
|
15
|
+
spatial_index field.name
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#
|
2
|
+
# Mongoid fields extension
|
3
|
+
#
|
4
|
+
# field :foo, :sphere => true
|
5
|
+
#
|
6
|
+
Mongoid::Fields.option :sphere do |model, field, _options|
|
7
|
+
# options = {} unless options.kind_of?(Hash)
|
8
|
+
|
9
|
+
model.class_eval do
|
10
|
+
|
11
|
+
spatial_fields << field.name.to_sym
|
12
|
+
spatial_fields_indexed << field.name.to_sym
|
13
|
+
|
14
|
+
# Create 2Dsphere index
|
15
|
+
sphere_index field.name
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|