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.
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