mongoid_geospatial 2.0.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/Gemfile +2 -1
  2. data/README.md +84 -60
  3. data/lib/mongoid_geospatial.rb +3 -7
  4. data/lib/mongoid_geospatial/{geospatial → extensions}/core_ext.rb +0 -0
  5. data/lib/mongoid_geospatial/field_option.rb +3 -4
  6. data/lib/mongoid_geospatial/fields/geometry_field.rb +34 -0
  7. data/lib/mongoid_geospatial/fields/line_string.rb +5 -7
  8. data/lib/mongoid_geospatial/fields/point.rb +18 -33
  9. data/lib/mongoid_geospatial/fields/polygon.rb +6 -14
  10. data/lib/mongoid_geospatial/geospatial.rb +20 -25
  11. data/lib/mongoid_geospatial/version.rb +1 -1
  12. data/lib/mongoid_geospatial/wrappers/georuby.rb +28 -0
  13. data/lib/mongoid_geospatial/wrappers/rgeo.rb +32 -0
  14. data/spec/models/farm.rb +5 -2
  15. data/spec/models/person.rb +4 -14
  16. data/spec/models/phone.rb +1 -3
  17. data/spec/mongoid_geospatial/extensions/core_ext_spec.rb +20 -0
  18. data/spec/mongoid_geospatial/field_option_spec.rb +11 -0
  19. data/spec/mongoid_geospatial/fields/line_string_spec.rb +40 -0
  20. data/spec/mongoid_geospatial/fields/point_spec.rb +136 -10
  21. data/spec/mongoid_geospatial/fields/polygon_spec.rb +75 -0
  22. data/spec/mongoid_geospatial/geospatial_spec.rb +88 -3
  23. data/spec/mongoid_geospatial/wrappers/rgeo_spec.rb +43 -0
  24. data/spec/spec_helper.rb +5 -21
  25. metadata +14 -26
  26. data/lib/mongoid_geospatial/contextual/mongo.rb +0 -118
  27. data/lib/mongoid_geospatial/criteria.rb +0 -10
  28. data/lib/mongoid_geospatial/criterion/complex.rb +0 -26
  29. data/lib/mongoid_geospatial/criterion/inclusion.rb +0 -14
  30. data/lib/mongoid_geospatial/criterion/near_spatial.rb +0 -52
  31. data/lib/mongoid_geospatial/criterion/within_spatial.rb +0 -62
  32. data/lib/mongoid_geospatial/extensions/symbol.rb +0 -46
  33. data/lib/mongoid_geospatial/finders.rb +0 -5
  34. data/lib/mongoid_geospatial/geospatial/geo_near_results.rb +0 -140
  35. data/spec/mongoid_geospatial/contextual/mongo_spec.rb +0 -135
  36. data/spec/mongoid_geospatial/criterion/complex_spec.rb +0 -15
  37. data/spec/mongoid_geospatial/criterion/inclusion_spec.rb +0 -375
  38. data/spec/mongoid_geospatial/criterion/near_spatial_spec.rb +0 -39
  39. data/spec/mongoid_geospatial/criterion/within_spatial_spec.rb +0 -54
  40. data/spec/mongoid_geospatial/geospatial/geo_near_results_spec.rb +0 -78
  41. data/spec/mongoid_geospatial/mongoid_geospatial_spec.rb +0 -83
data/Gemfile CHANGED
@@ -1,10 +1,11 @@
1
1
  source 'http://rubygems.org'
2
2
  gemspec # Specify gem's dependencies in mongoid_geospatial.gemspec
3
3
 
4
- gem 'rgeo'
5
4
  gem 'mongoid', '~> 3.0'
6
5
 
7
6
  group :development do
7
+ gem 'rgeo'
8
+ gem 'georuby'
8
9
  gem 'rspec'
9
10
  gem 'guard-rspec'
10
11
  gem 'pry'
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  Mongoid Geospatial
2
2
  ==================
3
3
 
4
- A Mongoid Extension that simplifies and adds support for MongoDB and
5
- RGeo Spatial Calculations.
4
+ A Mongoid Extension that simplifies the use of MongoDB spatial features.
5
+
6
6
 
7
7
  ** On beta again **
8
8
 
@@ -18,13 +18,80 @@ There are no plans to support Mongoid <= 2.0
18
18
 
19
19
  Quick Start
20
20
  -----------
21
- Add mongoid_geospatial to your Gemfile:
22
21
 
23
- ```ruby
24
- gem 'mongoid_geospatial'
25
- ```
22
+ This gem focus on (making helpers for) spatial features MongoDB has.
23
+ You can also use an external Geometric/Spatial alongside.
24
+
25
+ # Gemfile
26
+ gem 'mongoid_geospatial'
27
+
28
+
29
+ # A place to illustrate Point, LineString and Polygon
30
+ class Place
31
+ include Mongoid::Document
32
+ include Mongoid::Geospatial
33
+
34
+ field :name, type: String
35
+ field :location, type: Point, :spatial => true
36
+ field :route, type: Linestring
37
+ field :area, type: Polygon
38
+ end
39
+
40
+
41
+ Geometry Helpers
42
+ ----------------
43
+
44
+ We currently support GeoRuby and RGeo.
45
+ If you require one of those, a #to_geo method will be available to all
46
+ spatial fields, returning the external library corresponding object.
47
+ To illustrate:
48
+
49
+ class Person
50
+ include Mongoid::Document
51
+ include Mongoid::Geospatial
52
+
53
+ field :location, type: Point
54
+ end
55
+
56
+ me = Person.new(location: [8, 8])
57
+
58
+ # Example with GeoRuby
59
+ point.class # Mongoid::Geospatial::Point
60
+ point.to_geo.class # GeoRuby::SimpleFeatures::Point
61
+
62
+ # Example with RGeo
63
+ point.class # Mongoid::Geospatial::Point
64
+ point.to_geo.class # RGeo::Geographic::SphericalPointImpl
65
+
66
+
67
+ Configure
68
+ ----------------
69
+
70
+ Assemble it as you need:
71
+
72
+ With RGeo
73
+
74
+ Mongoid::Geospatial.use_rgeo
75
+ # Optional
76
+ # Mongoid::Geospatial.factory = RGeo::Geographic.spherical_factory
77
+
78
+ With GeoRuby
79
+
80
+ Mongoid::Geospatial.use_georuby
81
+
82
+ Defaults (change if you know what you're doing)
83
+
84
+ Mongoid::Geospatial.lng_symbol = :x
85
+ Mongoid::Geospatial.lat_symbol = :y
86
+ Mongoid::Geospatial.earth_radius = EARTH_RADIUS
87
+
88
+
89
+
90
+ Model Setup
91
+ -----------
92
+
93
+ You can create Point, LineString and Polygon on your models:
26
94
 
27
- Set up some slugs:
28
95
 
29
96
  ```ruby
30
97
  class River
@@ -48,12 +115,6 @@ class River
48
115
  end
49
116
  ```
50
117
 
51
- Avaiable data types:
52
-
53
- * Point
54
- * LineString
55
- * Polygon
56
-
57
118
 
58
119
  Generate indexes on MongoDB:
59
120
 
@@ -129,47 +190,6 @@ River.where(:name=>'hudson').geo_near({:lat => 40.73083, :lng => -73.99756})
129
190
  River.geo_near([-73.99756,40.73083], :max_distance => 4, :unit => :mi, :spherical => true)
130
191
  ```
131
192
 
132
- There are two types of pagination!
133
-
134
- * MongoDB Pagination - Stores start of rows to page limit in memory
135
- * Post Query Pagination - Stores all rows in memory
136
-
137
- Post-Result is only minutely slower than MongoDB because MongoDB has to calculate distance for all of the rows anyway. The slow up is in the transfer of data from the database to ruby.
138
-
139
- Post-Result has some advantages that are listed below.
140
-
141
- ```ruby
142
- # MongoDB pagination
143
- # overwrites #skip chain method
144
- # :page - pagination will be enabled if set to any variable including nil, pagination will not be enabled if either :per\_page or :paginator is set
145
- # :per\_page
146
- # :paginator - Choose which paginator to use. [default :arrary]
147
- # Prefered method to set is Mongoid::Geospatial.paginator=:array
148
- # Available Paginators [:kaminari, :will\_paginate, :array]
149
- # The only thing this does really is configure default per\_page so it is only kind of useful
150
- River.geo_near([-73.99756,40.73083], :page => 1)
151
- ```
152
-
153
- ```ruby
154
- # Post Query Pagination
155
- # At carzen we use Post Query Pagination because we need to re-sort our rows after fetching. Pagination is not friendly with re-sorting.
156
- # You can jump pages continously without querying the database again.
157
- # listens to #limit/:num & #skip before geo\_near
158
- # #page(page\_number, opts = {})
159
- # opts:
160
- # :per\_page
161
- # :paginator
162
- # #per(per\_page\_number, opts = {})
163
- # opts:
164
- # :page
165
- # :paginator
166
- #
167
- # both return a GeoNearResults, which is really just a modified Array
168
- # #per really just #page but just moves the options around
169
- rivers = River.geo_near([-73.99756,40.73083]).sort_by!{|r| r.geo[:distance] * r.multiplier }
170
- rivers = rivers.per(25).page(1)
171
- rivers.reset! # resets the object to it is original state right after query.
172
- ```
173
193
 
174
194
  Mongo DB 1.9+ New Geo features
175
195
  ---------
@@ -289,8 +309,7 @@ This Fork
289
309
  ---------
290
310
 
291
311
  This fork is not backwards compatible with 'mongoid_spatial'.
292
- This fork delegates all the calculation to the nice RGeo.
293
- As a result, all the GEOS/Proj features are available in Ruby/Mongoid.
312
+ This fork delegates calculations to the external libs and use Moped.
294
313
 
295
314
  Change in your models:
296
315
 
@@ -320,16 +339,20 @@ Troubleshooting
320
339
 
321
340
  **Mongo::OperationFailure: can't find special index: 2d**
322
341
 
323
- Indexes need to be created. Execute command: <code>rake db:mongoid:create_indexes</code>
342
+ Indexes need to be created. Execute command:
343
+
344
+ rake db:mongoid:create_indexes
324
345
 
325
346
 
326
347
  Thanks
327
- -----------
348
+ ------
349
+
328
350
  * Thanks to Kristian Mandrup for creating the base of the gem and a few of the tests
329
351
  * Thanks to CarZen LLC. for letting me release the code we are using
330
352
 
331
- Contributing to mongoid_spatial
332
- -----------
353
+ Contributing
354
+ ------------
355
+
333
356
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
334
357
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
335
358
  * Fork the project
@@ -340,5 +363,6 @@ Contributing to mongoid_spatial
340
363
 
341
364
  Copyright
342
365
  -----------
366
+
343
367
  Copyright (c) 2011 Ryan Ong. See LICENSE.txt for
344
368
  further details.
@@ -1,18 +1,14 @@
1
- require 'rgeo'
2
1
  require 'mongoid'
3
2
  require 'active_support/core_ext/string/inflections'
4
3
  require 'active_support/concern'
5
- require 'mongoid_geospatial/contextual/mongo'
6
- require 'mongoid_geospatial/criteria'
7
- require 'mongoid_geospatial/extensions/symbol'
4
+ require 'mongoid_geospatial/extensions/core_ext'
8
5
  require 'mongoid_geospatial/extensions/rgeo_spherical_point_impl'
9
6
  require 'mongoid_geospatial/field_option'
10
7
 
11
- fields_path = 'mongoid_geospatial/fields'
8
+ require 'mongoid_geospatial/fields/geometry_field'
12
9
 
13
10
  %w{point polygon line_string}.each do |type|
14
- require "#{fields_path}/#{type}"
11
+ require "mongoid_geospatial/fields/#{type}"
15
12
  end
16
13
 
17
- require 'mongoid_geospatial/finders'
18
14
  require 'mongoid_geospatial/geospatial'
@@ -2,11 +2,10 @@
2
2
 
3
3
  Mongoid::Fields.option :spatial do |model,field,options|
4
4
  options = {} unless options.kind_of?(Hash)
5
- lat_meth = options[:lat] || :lat
6
- lng_meth = options[:lng] || :lng
5
+ # x_meth = options[:x] || :x
6
+ # y_meth = options[:y] || :y
7
7
  model.class_eval do
8
- self.spatial_fields ||= []
9
- self.spatial_fields << field.name.to_sym if self.spatial_fields.kind_of? Array
8
+ (self.spatial_fields ||= []) << field.name.to_sym
10
9
 
11
10
  define_method "distance_from_#{field.name}" do |*args|
12
11
  self.distance_from(field.name, *args)
@@ -0,0 +1,34 @@
1
+ module Mongoid
2
+ module Geospatial
3
+ class GeometryField < Array
4
+
5
+ def bounding_box
6
+ max_x, min_x = -Float::MAX, Float::MAX
7
+ max_y, min_y = -Float::MAX, Float::MAX
8
+ each do |point|
9
+ max_y = point[1] if point[1] > max_y
10
+ min_y = point[1] if point[1] < min_y
11
+ max_x = point[0] if point[0] > max_x
12
+ min_x = point[0] if point[0] < min_x
13
+ end
14
+ [[min_x, min_y], [max_x, max_y]]
15
+ end
16
+ alias :bbox :bounding_box
17
+
18
+ def center_point
19
+ min, max = *bbox
20
+ [min[0] + max[0] / 2.0, min[1] + max[1] / 2.0]
21
+ end
22
+ alias :center :center_point
23
+
24
+ def radius r = 1
25
+ [center, r]
26
+ end
27
+
28
+ def radius_sphere r = 1
29
+ [center, r.to_f/Mongoid::Geospatial.earth_radius[:km]]
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -1,14 +1,12 @@
1
1
  module Mongoid
2
2
  module Geospatial
3
- class LineString
4
-
5
- def mongoize
6
- to_a
7
- end
3
+ class LineString < GeometryField
8
4
 
9
5
  class << self
10
- def demongoize(object)
11
- RGeo::Geographic.spherical_factory.line_string object
6
+
7
+ # Database -> Object
8
+ def demongoize(o)
9
+ LineString.new(o)
12
10
  end
13
11
 
14
12
  end
@@ -1,62 +1,47 @@
1
1
  module Mongoid
2
2
  module Geospatial
3
3
  class Point
4
+ attr_accessor :x, :y
4
5
 
6
+ def initialize(x, y)
7
+ @x, @y = x, y
8
+ end
9
+
10
+ # Object -> Database
5
11
  def mongoize
6
12
  [x, y]
7
13
  end
14
+ alias :to_a :mongoize
15
+ alias :to_xy :mongoize
16
+
17
+ def [](args)
18
+ mongoize[args]
19
+ end
8
20
 
9
21
  class << self
10
22
 
23
+ # Database -> Object
11
24
  def demongoize(object)
12
- return unless object && !object.empty?
13
- RGeo::Geographic.spherical_factory.point *object
14
- #["x"], object["y"]
25
+ # return unless object && !object.empty?
26
+ Point.new(object[0], object[1])
15
27
  end
16
28
 
17
29
  def mongoize(object)
18
- #return new.mongoize if object.respond_to?(:x)
19
30
  case object
20
31
  when Point then object.mongoize
21
- when Hash then [object[:x], object[:y]]
32
+ when Array then object.to_xy
33
+ when Hash then object.to_xy
22
34
  else object
23
35
  end
24
36
  end
25
37
 
26
- # Converts the object that was supplied to a criteria and converts it
38
+ # Converts the object that was supplied to a criteria
27
39
  # into a database friendly form.
28
40
  def evolve(object)
29
41
  object.respond_to?(:x) ? object.mongoize : object
30
42
  end
31
43
  end
32
44
 
33
- # - self.spacial_fields ||= []
34
- # - self.spacial_fields << field.name.to_sym if self.spacial_fields.kind_of? Array
35
- # -
36
- # - define_method "distance_from_#{field.name}" do |*args|
37
- # - self.distance_from(field.name, *args)
38
- # - end
39
- # -
40
- # - define_method field.name do
41
- # - output = self[field.name] || [nil,nil]
42
- # - output = {lng_meth => output[0], lat_meth => output[1]} unless options[:return_array]
43
- # - return options[:class].new(output) if options[:class]
44
- # - output
45
- # - end
46
- # -
47
- # - define_method "#{field.name}=" do |arg|
48
- # - if arg.kind_of?(Hash) && arg[lng_meth] && arg[lat_meth]
49
- # - arg = [arg[lng_meth].to_f, arg[lat_meth].to_f]
50
- # - elsif arg.respond_to?(:to_xy)
51
- # - arg = arg.to_xy
52
- # - end
53
- # - self[field.name]=arg
54
- # - arg = [nil,nil] if arg.nil?
55
- # - return arg[0..1] if options[:return_array]
56
- # - h = {lng_meth => arg[0], lat_meth => arg[1]}
57
- # - return h if options[:class].blank?
58
- # - options[:class].new(h)
59
-
60
45
  end
61
46
  end
62
47
  end
@@ -1,24 +1,16 @@
1
1
  module Mongoid
2
2
  module Geospatial
3
- class Polygon
4
-
5
- def mongoize
6
- self #.flatten
7
- end
3
+ class Polygon < GeometryField
8
4
 
9
5
  class << self
10
- def demongoize(object)
11
- points = object.map do |pair|
12
- RGeo::Geographic.spherical_factory.point *pair
13
- end
14
- ring = RGeo::Geographic.spherical_factory.linear_ring points
15
- RGeo::Geographic.spherical_factory.polygon ring
6
+
7
+ # Database -> Object
8
+ def demongoize(o)
9
+ Polygon.new(o)
16
10
  end
17
11
 
18
- # def evolve(object)
19
- # { "$gte" => object.first, "$lte" => object.last }
20
- # end
21
12
  end
13
+
22
14
  end
23
15
  end
24
16
  end
@@ -1,6 +1,3 @@
1
- require 'mongoid_geospatial/geospatial/core_ext'
2
- require 'mongoid_geospatial/geospatial/geo_near_results'
3
-
4
1
  module Mongoid
5
2
  module Geospatial
6
3
  extend ActiveSupport::Concern
@@ -9,36 +6,24 @@ module Mongoid
9
6
  LAT_SYMBOLS = [:y, :lat, :latitude]
10
7
 
11
8
  EARTH_RADIUS_KM = 6371 # taken directly from mongodb
9
+ RAD_PER_DEG = Math::PI / 180
12
10
 
13
11
  EARTH_RADIUS = {
14
12
  :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
13
+ :m => EARTH_RADIUS_KM * 1000,
14
+ :mi => EARTH_RADIUS_KM * 0.621371192, # taken directly from mongodb
15
+ :ft => EARTH_RADIUS_KM * 5280*0.621371192,
16
+ :sm => EARTH_RADIUS_KM * 0.53995680345572 # sea mile
19
17
  }
20
18
 
21
- GEO_FACTORY = RGeo::Geographic.spherical_factory
22
- RAD_PER_DEG = Math::PI/180
23
19
  mattr_accessor :lng_symbols
24
- @@lng_symbols = LNG_SYMBOLS.dup
25
-
26
20
  mattr_accessor :lat_symbols
27
- @@lat_symbols = LAT_SYMBOLS.dup
28
-
29
21
  mattr_accessor :earth_radius
30
- @@earth_radius = EARTH_RADIUS.dup
22
+ mattr_accessor :factory
31
23
 
32
- mattr_accessor :paginator
33
- @@paginator = :array
34
-
35
- mattr_accessor :default_per_page
36
- @@default_per_page = 25
37
-
38
- # mattr_accessor :spherical_distance_formula
39
- # @@spherical_distance_formula = :n_vector
40
- mattr_accessor :geo_factory
41
- @@geo_factory = GEO_FACTORY.dup
24
+ @@lng_symbols = LNG_SYMBOLS.dup
25
+ @@lat_symbols = LAT_SYMBOLS.dup
26
+ @@earth_radius = EARTH_RADIUS.dup
42
27
 
43
28
  included do
44
29
  attr_accessor :geo
@@ -47,15 +32,25 @@ module Mongoid
47
32
  @@spatial_fields_indexed = []
48
33
  end
49
34
 
35
+ def self.use_rgeo
36
+ require 'mongoid_geospatial/wrappers/rgeo'
37
+ end
38
+
39
+ def self.use_georuby
40
+ require 'mongoid_geospatial/wrappers/georuby'
41
+ end
42
+
50
43
  module ClassMethods #:nodoc:
44
+
51
45
  # create spatial index for given field
52
46
  # @param [String,Symbol] name
53
47
  # @param [Hash] options options for spatial_index
54
-
48
+ # http://www.mongodb.org/display/DOCS/Geospatial+Indexing#GeospatialIndexing-geoNearCommand
55
49
  def spatial_index name, options = {}
56
50
  self.spatial_fields_indexed << name
57
51
  index({name => '2d'}, options)
58
52
  end
53
+
59
54
  end
60
55
 
61
56
  end