mongoid-geospatial 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +49 -0
  4. data/.travis.yml +19 -0
  5. data/Gemfile +30 -0
  6. data/Guardfile +16 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +519 -0
  9. data/Rakefile +17 -0
  10. data/lib/mongoid/geospatial.rb +106 -0
  11. data/lib/mongoid/geospatial/ext/rgeo_spherical_point_impl.rb +18 -0
  12. data/lib/mongoid/geospatial/fields/box.rb +6 -0
  13. data/lib/mongoid/geospatial/fields/circle.rb +18 -0
  14. data/lib/mongoid/geospatial/fields/geometry_field.rb +41 -0
  15. data/lib/mongoid/geospatial/fields/line.rb +6 -0
  16. data/lib/mongoid/geospatial/fields/point.rb +143 -0
  17. data/lib/mongoid/geospatial/fields/polygon.rb +6 -0
  18. data/lib/mongoid/geospatial/helpers/delegate.rb +30 -0
  19. data/lib/mongoid/geospatial/helpers/spatial.rb +19 -0
  20. data/lib/mongoid/geospatial/helpers/sphere.rb +18 -0
  21. data/lib/mongoid/geospatial/version.rb +6 -0
  22. data/lib/mongoid/geospatial/wrappers/georuby.rb +33 -0
  23. data/lib/mongoid/geospatial/wrappers/rgeo.rb +43 -0
  24. data/mongoid-geospatial.gemspec +22 -0
  25. data/spec/models/address.rb +69 -0
  26. data/spec/models/alarm.rb +12 -0
  27. data/spec/models/bar.rb +13 -0
  28. data/spec/models/bus.rb +12 -0
  29. data/spec/models/event.rb +17 -0
  30. data/spec/models/farm.rb +13 -0
  31. data/spec/models/person.rb +97 -0
  32. data/spec/models/phone.rb +8 -0
  33. data/spec/models/place.rb +13 -0
  34. data/spec/models/river.rb +22 -0
  35. data/spec/mongoid/geospatial/fields/box_spec.rb +10 -0
  36. data/spec/mongoid/geospatial/fields/circle_spec.rb +10 -0
  37. data/spec/mongoid/geospatial/fields/line_spec.rb +40 -0
  38. data/spec/mongoid/geospatial/fields/point_spec.rb +254 -0
  39. data/spec/mongoid/geospatial/fields/polygon_spec.rb +84 -0
  40. data/spec/mongoid/geospatial/geospatial_spec.rb +143 -0
  41. data/spec/mongoid/geospatial/helpers/core_spec.rb +27 -0
  42. data/spec/mongoid/geospatial/helpers/delegate_spec.rb +54 -0
  43. data/spec/mongoid/geospatial/helpers/spatial_spec.rb +36 -0
  44. data/spec/mongoid/geospatial/helpers/sphere_spec.rb +26 -0
  45. data/spec/mongoid/geospatial/wrappers/georuby_spec.rb +66 -0
  46. data/spec/mongoid/geospatial/wrappers/rgeo_spec.rb +121 -0
  47. data/spec/spec_helper.rb +64 -0
  48. data/spec/support/authentication.rb +29 -0
  49. data/spec/support/mongod.conf +3 -0
  50. data/spec/support/mongoid.yml +19 -0
  51. metadata +164 -0
@@ -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,18 @@
1
+ module RGeo
2
+ module Geographic
3
+ # RGeo Point
4
+ class SphericalPointImpl
5
+ def to_xy
6
+ [x, y]
7
+ end
8
+
9
+ def to_a
10
+ [x, y, z]
11
+ end
12
+
13
+ def [](index)
14
+ to_a[index]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ module Mongoid
2
+ module Geospatial
3
+ class Box < GeometryField
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,18 @@
1
+ module Mongoid
2
+ module Geospatial
3
+ # Circle
4
+ #
5
+ class Circle < GeometryField
6
+ attr_accessor :center, :radius
7
+
8
+ def point
9
+ Point.new(self[0])
10
+ end
11
+ alias_method :point, :center
12
+
13
+ def radius
14
+ self[1]
15
+ end
16
+ end
17
+ end
18
+ 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,6 @@
1
+ module Mongoid
2
+ module Geospatial
3
+ class Line < GeometryField
4
+ end
5
+ end
6
+ 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,6 @@
1
+ module Mongoid
2
+ module Geospatial
3
+ class Polygon < GeometryField
4
+ end
5
+ end
6
+ end
@@ -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
@@ -0,0 +1,6 @@
1
+ module Mongoid
2
+ # Gem version
3
+ module Geospatial
4
+ VERSION = '3.9.0'
5
+ end
6
+ end