mongoid_geospatial 1.0.0rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. data/.document +5 -0
  2. data/.gitignore +49 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +11 -0
  5. data/README.md +330 -0
  6. data/Rakefile +18 -0
  7. data/lib/mongoid_geospatial/contexts/mongo.rb +115 -0
  8. data/lib/mongoid_geospatial/criteria.rb +5 -0
  9. data/lib/mongoid_geospatial/criterion/complex.rb +19 -0
  10. data/lib/mongoid_geospatial/criterion/inclusion.rb +14 -0
  11. data/lib/mongoid_geospatial/criterion/near_spatial.rb +50 -0
  12. data/lib/mongoid_geospatial/criterion/within_spatial.rb +60 -0
  13. data/lib/mongoid_geospatial/criterion.rb +3 -0
  14. data/lib/mongoid_geospatial/extensions/hash.rb +22 -0
  15. data/lib/mongoid_geospatial/extensions/symbol.rb +46 -0
  16. data/lib/mongoid_geospatial/field_option.rb +16 -0
  17. data/lib/mongoid_geospatial/fields/line_string.rb +18 -0
  18. data/lib/mongoid_geospatial/fields/point.rb +51 -0
  19. data/lib/mongoid_geospatial/fields/polygon.rb +22 -0
  20. data/lib/mongoid_geospatial/finders.rb +5 -0
  21. data/lib/mongoid_geospatial/geospatial/core_ext.rb +27 -0
  22. data/lib/mongoid_geospatial/geospatial/geo_near_results.rb +140 -0
  23. data/lib/mongoid_geospatial/geospatial.rb +86 -0
  24. data/lib/mongoid_geospatial/version.rb +5 -0
  25. data/lib/mongoid_geospatial.rb +16 -0
  26. data/mongoid_geospatial.gemspec +28 -0
  27. data/spec/config/mongod.conf +3 -0
  28. data/spec/config/mongoid.yml +18 -0
  29. data/spec/functional/contexts/mongo_spec.rb +127 -0
  30. data/spec/functional/criterion/inclusion_spec.rb +356 -0
  31. data/spec/functional/mongoid_geospatial_spec.rb +54 -0
  32. data/spec/functional/spatial/geo_near_results_spec.rb +78 -0
  33. data/spec/models/account.rb +19 -0
  34. data/spec/models/acolyte.rb +9 -0
  35. data/spec/models/address.rb +62 -0
  36. data/spec/models/address_component.rb +5 -0
  37. data/spec/models/agent.rb +10 -0
  38. data/spec/models/alert.rb +5 -0
  39. data/spec/models/animal.rb +21 -0
  40. data/spec/models/answer.rb +4 -0
  41. data/spec/models/bar.rb +9 -0
  42. data/spec/models/birthday.rb +13 -0
  43. data/spec/models/book.rb +5 -0
  44. data/spec/models/business.rb +7 -0
  45. data/spec/models/callbacks.rb +57 -0
  46. data/spec/models/category.rb +13 -0
  47. data/spec/models/circus.rb +7 -0
  48. data/spec/models/comment.rb +13 -0
  49. data/spec/models/country_code.rb +6 -0
  50. data/spec/models/description.rb +11 -0
  51. data/spec/models/division.rb +5 -0
  52. data/spec/models/drug.rb +5 -0
  53. data/spec/models/employer.rb +5 -0
  54. data/spec/models/entry.rb +6 -0
  55. data/spec/models/event.rb +20 -0
  56. data/spec/models/farm.rb +10 -0
  57. data/spec/models/favorite.rb +6 -0
  58. data/spec/models/fruits.rb +11 -0
  59. data/spec/models/game.rb +18 -0
  60. data/spec/models/ghost.rb +7 -0
  61. data/spec/models/house.rb +4 -0
  62. data/spec/models/inheritance.rb +90 -0
  63. data/spec/models/league.rb +5 -0
  64. data/spec/models/location.rb +5 -0
  65. data/spec/models/login.rb +6 -0
  66. data/spec/models/membership.rb +4 -0
  67. data/spec/models/mixed_drink.rb +4 -0
  68. data/spec/models/name.rb +13 -0
  69. data/spec/models/namespacing.rb +11 -0
  70. data/spec/models/observed.rb +41 -0
  71. data/spec/models/override.rb +16 -0
  72. data/spec/models/owner.rb +6 -0
  73. data/spec/models/page.rb +5 -0
  74. data/spec/models/page_question.rb +4 -0
  75. data/spec/models/paranoid_post.rb +18 -0
  76. data/spec/models/parents.rb +32 -0
  77. data/spec/models/patient.rb +15 -0
  78. data/spec/models/person.rb +146 -0
  79. data/spec/models/pet.rb +7 -0
  80. data/spec/models/pet_owner.rb +6 -0
  81. data/spec/models/phone.rb +7 -0
  82. data/spec/models/player.rb +23 -0
  83. data/spec/models/post.rb +26 -0
  84. data/spec/models/preference.rb +9 -0
  85. data/spec/models/question.rb +8 -0
  86. data/spec/models/quiz.rb +6 -0
  87. data/spec/models/rating.rb +8 -0
  88. data/spec/models/river.rb +20 -0
  89. data/spec/models/role.rb +5 -0
  90. data/spec/models/service.rb +6 -0
  91. data/spec/models/shelf.rb +5 -0
  92. data/spec/models/slave_address_numbers.rb +14 -0
  93. data/spec/models/survey.rb +5 -0
  94. data/spec/models/tag.rb +6 -0
  95. data/spec/models/tracking_id_validation_history.rb +25 -0
  96. data/spec/models/translation.rb +5 -0
  97. data/spec/models/tree.rb +9 -0
  98. data/spec/models/user.rb +9 -0
  99. data/spec/models/user_account.rb +10 -0
  100. data/spec/models/vet_visit.rb +5 -0
  101. data/spec/models/video.rb +9 -0
  102. data/spec/models/wiki_page.rb +6 -0
  103. data/spec/spec_helper.rb +51 -0
  104. data/spec/support/authentication.rb +29 -0
  105. data/spec/unit/criterion/complex_spec.rb +15 -0
  106. data/spec/unit/criterion/inclusion_spec.rb +0 -0
  107. data/spec/unit/criterion/near_spatial_spec.rb +39 -0
  108. data/spec/unit/criterion/within_spatial_spec.rb +52 -0
  109. metadata +339 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.gitignore ADDED
@@ -0,0 +1,49 @@
1
+ Gemfile.lock
2
+ # rcov generated
3
+ coverage
4
+
5
+ # rdoc generated
6
+ rdoc
7
+
8
+ # yard generated
9
+ doc
10
+ .yardoc
11
+
12
+ # bundler
13
+ .bundle
14
+
15
+ # jeweler generated
16
+ pkg
17
+
18
+ # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
19
+ #
20
+ # * Create a file at ~/.gitignore
21
+ # * Include files you want ignored
22
+ # * Run: git config --global core.excludesfile ~/.gitignore
23
+ #
24
+ # After doing this, these files will be ignored in all your git projects,
25
+ # saving you from having to 'pollute' every project you touch with them
26
+ #
27
+ # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
28
+ #
29
+ # For MacOS:
30
+ #
31
+ .DS_Store
32
+
33
+ # For TextMate
34
+ #*.tmproj
35
+ #tmtags
36
+
37
+ # For emacs:
38
+ #*~
39
+ #\#*
40
+ #.\#*
41
+
42
+ # For vim:
43
+ *.swp
44
+
45
+ # For redcar:
46
+ #.redcar
47
+
48
+ # For rubinius:
49
+ #*.rbc
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mongoid_spacial.gemspec
4
+ gemspec
5
+
6
+ gem 'rgeo'
7
+
8
+ group :development do
9
+ gem 'rspec'
10
+ gem 'fuubar'
11
+ end
data/README.md ADDED
@@ -0,0 +1,330 @@
1
+ Mongoid Geospatial
2
+ ==================
3
+
4
+ A Mongoid Extention that simplifies and adds support for MongoDB and
5
+ RGeo Spatial Calculations.
6
+
7
+ Quick Start
8
+ -----------
9
+ Add mongoid_geospatial to your Gemfile:
10
+
11
+ ```ruby
12
+ gem 'mongoid_geospatial'
13
+ ```
14
+
15
+ Set up some slugs:
16
+
17
+ ```ruby
18
+ class River
19
+ include Mongoid::Document
20
+ include Mongoid::Geospatial
21
+
22
+ field :name, type: String
23
+ field :length, type: Integer
24
+ field :average_discharge, type: Integer
25
+ field :source, type: Point, spatial: true
26
+
27
+ # set return_array to true if you do not want a hash returned all the time
28
+ field :mouth, type: Point, spatial: {lat: :latitude, lng: :longitude, return_array: true }
29
+ field :course, type: Polygon
30
+
31
+ # simplified spatial indexing
32
+ # you can only index one point in mongodb version below 1.9
33
+ # if you want something besides the defaults {bit: 24, min: -180, max: 180} just set index to the options on the index
34
+ spatial_index :source
35
+
36
+ end
37
+ ```
38
+
39
+ Avaiable data types:
40
+
41
+ * Point
42
+ * LineString
43
+ * Polygon
44
+
45
+
46
+ Generate indexes on MongoDB:
47
+
48
+ ```
49
+ rake db:mongoid:create_indexes
50
+ ```
51
+
52
+
53
+ Before we manipulate the data mongoid_spatial handles is what we call points.
54
+
55
+ Points can be:
56
+
57
+ * an unordered hash with the lat long string keys defined when setting the field (only applies for setting the field)
58
+ * longitude latitude array in that order - [long,lat]
59
+ * an unordered hash with latitude key(:lat, :latitude) and a longitude key(:lon, :long, :lng, :longitude)
60
+ * an ordered hash with longitude as the first item and latitude as the second item
61
+ This hash does not have include the latitude and longitude keys
62
+ \*only works in ruby 1.9 and up because hashes below ruby 1.9 because they are not ordered
63
+ * anything with the method to_lng_lat that converts it to a [long,lat]
64
+ We store data in the DB as a [lng,lat] array then reformat when it is returned to you
65
+
66
+ ```ruby
67
+ hudson = River.create(
68
+ name: 'Hudson',
69
+ length: 315,
70
+ average_discharge: 21_400,
71
+ # when setting array LONGITUDE MUST BE FIRST LATITUDE MUST BE SECOND
72
+ # source: [-73.935833,44.106667],
73
+ # but we can use hash in any order,
74
+ # the default keys for latitude and longitude are :lat and :lng respectively
75
+ source: {:lat => 44.106667, :lng => -73.935833},
76
+ mouth: {:latitude => 40.703056, :longitude => -74.026667}
77
+ )
78
+
79
+ # now to access this spatial information we can now do this
80
+ hudson.source #=> {:lng => -73.935833, :lat => 44.106667}
81
+ hudson.mouth #=> [-74.026667, 40.703056] # notice how this returned as a lng,lat array because return_array was true
82
+ # notice how the order of lng and lat were switched. it will always come out like this when using spatial.
83
+ # Also adds a handy distance function
84
+ hudson.distance_from(:source, [-74,40], {:unit=>:mi})
85
+
86
+ ```
87
+ Mongoid Geo has extended all built in spatial symbol extentions
88
+
89
+ * near
90
+ * River.where(:source.near => [-73.98, 40.77])
91
+ * River.where(:source.near => [[-73.98, 40.77],5]) # sets max distance of 5
92
+ * River.where(:source.near => {:point => [-73.98, 40.77], :max => 5}) # sets max distance of 5
93
+ * River.where(:source.near(:sphere) => [[-73.98, 40.77],5]) # sets max distance of 5 radians
94
+ * River.where(:source.near(:sphere) => {:point => [-73.98, 40.77], :max => 5, :unit => :km}) # sets max distance of 5 km
95
+ * River.where(:source.near(:sphere) => [-73.98, 40.77])
96
+ * within
97
+ * River.where(:source.within(:box) => [[-73.99756,40.73083], [-73.988135,40.741404]])
98
+ * River.where(:source.within(:box) => [ {:lat => 40.73083, :lng => -73.99756}, [-73.988135,40.741404]])
99
+ * River.where(:source.within(:polygon) => [ [ 10, 20 ], [ 10, 40 ], [ 30, 40 ], [ 30, 20 ] ]
100
+ * River.where(:source.within(:polygon) => { a : { x : 10, y : 20 }, b : { x : 15, y : 25 }, c : { x : 20, y : 20 } })
101
+ * River.where(:source.within(:center) => [[-73.98, 40.77],5]) # same format as near
102
+ * River.where(:source.within(:center_sphere) => [[-73.98, 40.77],5]) # same format as near(:sphere)
103
+
104
+ One of the most handy features we have added is geo_near finder
105
+
106
+ ```ruby
107
+ # accepts all criteria chains except without, only, asc, desc, order\_by
108
+ River.where(:name=>'hudson').geo_near({:lat => 40.73083, :lng => -73.99756})
109
+
110
+ # geo\_near accepts a few parameters besides a point
111
+ # :num = limit
112
+ # :query = where
113
+ # :unit - [:km, :m, :mi, :ft] - converts :max\_distance to appropriate values and automatically sets :distance\_multiplier. accepts
114
+ # :max\_distance - Integer
115
+ # :distance\_multiplier - Integer
116
+ # :spherical - true - To enable spherical calculations
117
+ River.geo_near([-73.99756,40.73083], :max_distance => 4, :unit => :mi, :spherical => true)
118
+ ```
119
+
120
+ There are two types of pagination!
121
+
122
+ * MongoDB Pagination - Stores start of rows to page limit in memory
123
+ * Post Query Pagination - Stores all rows in memory
124
+
125
+ 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.
126
+
127
+ Post-Result has some advantages that are listed below.
128
+
129
+ ```ruby
130
+ # MongoDB pagination
131
+ # overwrites #skip chain method
132
+ # :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
133
+ # :per\_page
134
+ # :paginator - Choose which paginator to use. [default :arrary]
135
+ # Prefered method to set is Mongoid::Geospatial.paginator=:array
136
+ # Available Paginators [:kaminari, :will\_paginate, :array]
137
+ # The only thing this does really is configure default per\_page so it is only kind of useful
138
+ River.geo_near([-73.99756,40.73083], :page => 1)
139
+ ```
140
+
141
+ ```ruby
142
+ # Post Query Pagination
143
+ # At carzen we use Post Query Pagination because we need to re-sort our rows after fetching. Pagination is not friendly with re-sorting.
144
+ # You can jump pages continously without querying the database again.
145
+ # listens to #limit/:num & #skip before geo\_near
146
+ # #page(page\_number, opts = {})
147
+ # opts:
148
+ # :per\_page
149
+ # :paginator
150
+ # #per(per\_page\_number, opts = {})
151
+ # opts:
152
+ # :page
153
+ # :paginator
154
+ #
155
+ # both return a GeoNearResults, which is really just a modified Array
156
+ # #per really just #page but just moves the options around
157
+ rivers = River.geo_near([-73.99756,40.73083]).sort_by!{|r| r.geo[:distance] * r.multiplier }
158
+ rivers = rivers.per(25).page(1)
159
+ rivers.reset! # resets the object to it is original state right after query.
160
+ ```
161
+
162
+ Mongo DB 1.9+ New Geo features
163
+ ---------
164
+
165
+ Multi-location Documents v.1.9+
166
+
167
+ MongoDB now also supports indexing documents by multiple locations. These locations can be specified in arrays of sub-objects, for example:
168
+
169
+ ```
170
+ > db.places.insert({ addresses : [ { name : "Home", loc : [55.5, 42.3] }, { name : "Work", loc : [32.3, 44.2] } ] })
171
+ > db.places.ensureIndex({ "addresses.loc" : "2d" })
172
+ ```
173
+
174
+ Multiple locations may also be specified in a single field:
175
+
176
+ ```
177
+ > db.places.insert({ lastSeenAt : [ { x : 45.3, y : 32.2 }, [54.2, 32.3], { lon : 44.2, lat : 38.2 } ] })
178
+ > db.places.ensureIndex({ "lastSeenAt" : "2d" })
179
+ ```
180
+
181
+ By default, when performing geoNear or $near-type queries on collections containing multi-location documents, the same document may be returned multiple times, since $near queries return ordered results by distance. Queries using the $within operator by default do not return duplicate documents.
182
+
183
+ v2.0
184
+ In v2.0, this default can be overridden by the use of a $uniqueDocs parameter for geoNear and $within queries, like so:
185
+
186
+ ```
187
+ > db.runCommand( { geoNear : "places" , near : [50,50], num : 10, uniqueDocs : false } )
188
+ > db.places.find( { loc : { $within : { $center : [[0.5, 0.5], 20], $uniqueDocs : true } } } )
189
+ ```
190
+
191
+ Currently it is not possible to specify $uniqueDocs for $near queries
192
+ Whether or not uniqueDocs is true, when using a limit the limit is applied (as is normally the case) to the number of results returned (and not to the docs or locations). If running a geoNear query with uniqueDocs : true, the closest location in a document to the center of the search region will always be returned - this is not true for $within queries.
193
+
194
+ In addition, when using geoNear queries and multi-location documents, often it is useful to return not only distances, but also the location in the document which was used to generate the distance. In v2.0, to return the location alongside the distance in the geoNear results (in the field loc), specify includeLocs : true in the geoNear query. The location returned will be a copy of the location in the document used.
195
+
196
+ If the location was an array, the location returned will be an object with "0" and "1" fields in v2.0.0 and v2.0.1.
197
+
198
+ ```
199
+ > db.runCommand({ geoNear : "places", near : [ 0, 0 ], maxDistance : 20, includeLocs : true })
200
+ {
201
+ "ns" : "test.places",
202
+ "near" : "1100000000000000000000000000000000000000000000000000",
203
+ "results" : [
204
+ {
205
+ "dis" : 5.830951894845301,
206
+ "loc" : {
207
+ "x" : 3,
208
+ "y" : 5
209
+ },
210
+ "obj" : {
211
+ "_id" : ObjectId("4e52672c15f59224bdb2544d"),
212
+ "name" : "Final Place",
213
+ "loc" : {
214
+ "x" : 3,
215
+ "y" : 5
216
+ }
217
+ }
218
+ },
219
+ {
220
+ "dis" : 14.142135623730951,
221
+ "loc" : {
222
+ "0" : 10,
223
+ "1" : 10
224
+ },
225
+ "obj" : {
226
+ "_id" : ObjectId("4e5266a915f59224bdb2544b"),
227
+ "name" : "Some Place",
228
+ "loc" : [
229
+ [
230
+ 10,
231
+ 10
232
+ ],
233
+ [
234
+ 50,
235
+ 50
236
+ ]
237
+ ]
238
+ }
239
+ },
240
+ {
241
+ "dis" : 14.142135623730951,
242
+ "loc" : {
243
+ "0" : -10,
244
+ "1" : -10
245
+ },
246
+ "obj" : {
247
+ "_id" : ObjectId("4e5266ba15f59224bdb2544c"),
248
+ "name" : "Another Place",
249
+ "loc" : [
250
+ [
251
+ -10,
252
+ -10
253
+ ],
254
+ [
255
+ -50,
256
+ -50
257
+ ]
258
+ ]
259
+ }
260
+ }
261
+ ],
262
+ "stats" : {
263
+ "time" : 0,
264
+ "btreelocs" : 0,
265
+ "nscanned" : 5,
266
+ "objectsLoaded" : 3,
267
+ "avgDistance" : 11.371741047435734,
268
+ "maxDistance" : 14.142157540259815
269
+ },
270
+ "ok" : 1
271
+ }
272
+ ```
273
+
274
+ The plan is to include this functionality in a future release. Please help out ;)
275
+
276
+ This Fork
277
+ ---------
278
+
279
+ This fork is not backwards compatible with 'mongoid_spatial'.
280
+ This fork delegates all the calculation to the nice RGeo.
281
+ As a result, all the GEOS/Proj features are available in Ruby/Mongoid.
282
+
283
+ Change in your models:
284
+
285
+ include Mongoid::Spacial::Document
286
+
287
+ to
288
+
289
+ include Mongoid::Geospatial
290
+
291
+
292
+ And for the fields:
293
+
294
+
295
+ field :source, type: Array, spacial: true
296
+
297
+ to
298
+
299
+ field :source, type: Point, spatial: true
300
+
301
+
302
+
303
+ Troubleshooting
304
+ ---------------
305
+
306
+ **Mongo::OperationFailure: can't find special index: 2d**
307
+
308
+ Indexes need to be created. Execute command: <code>rake db:mongoid:create_indexes</code>
309
+
310
+
311
+ Thanks
312
+ -----------
313
+ * Thanks to Kristian Mandrup for creating the base of the gem and a few of the tests
314
+ * Thanks to CarZen LLC. for letting me release the code we are using
315
+
316
+ Contributing to mongoid_spatial
317
+ -----------
318
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
319
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
320
+ * Fork the project
321
+ * Start a feature/bugfix branch
322
+ * Commit and push until you are happy with your contribution
323
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
324
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
325
+
326
+ Copyright
327
+ -----------
328
+ Copyright (c) 2011 Ryan Ong. See LICENSE.txt for
329
+ further details.
330
+
data/Rakefile ADDED
@@ -0,0 +1,18 @@
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'
18
+
@@ -0,0 +1,115 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Contexts #:nodoc:
4
+ class Mongo #:nodoc:
5
+
6
+ # Fetches rows from the data base sorted by distance.
7
+ # In MongoDB versions 1.7 and above it returns a distance.
8
+ # Uses all criteria chains except without, only, asc, desc, order_by
9
+ #
10
+ # @example Minimal Query
11
+ #
12
+ # Address.geo_near([70,40])
13
+ #
14
+ # @example Chained Query
15
+ #
16
+ # Address.where(:state => 'ny').geo_near([70,40])
17
+ #
18
+ # @example Calc Distances Query
19
+ #
20
+ # Address.geo_near([70,40], :max_distance => 5, :unit => 5)
21
+ #
22
+ # @param [ Array, Hash, #to_lng_lat ] center The center of where to calculate distance from
23
+ # @param [ Hash ] opts the options to query with
24
+ # @options opts [Integer] :num The number of rows to fetch
25
+ # @options opts [Hash] :query The query to filter the rows by, accepts
26
+ # @options opts [Numeric] :distance_multiplier this is multiplied against the calculated distance
27
+ # @options opts [Numeric] :max_distance The max distance of a row that should be returned in :unit(s)
28
+ # @options opts [Numeric, :km, :k, :mi, :ft] :unit automatically sets :distance_multiplier and converts :max_distance
29
+ # @options opts [true,false] :spherical Will determine the distance either by spherical calculation or flat calculation
30
+ # @options opts [TrueClass,Array<Symbol>] :calculate Which extra fields to calculate distance for in ruby, if set to TrueClass it will calculate all spatial fields
31
+ #
32
+ # @return [ Array ] Sorted Rows
33
+ def geo_near(center, opts = {})
34
+ opts = self.options.merge(opts)
35
+ # convert point
36
+ center = center.to_lng_lat if center.respond_to?(:to_lng_lat)
37
+ center = [center.x, center.y] if center.respond_to?(:x)
38
+
39
+ # set default opts
40
+ opts[:skip] ||= 0
41
+
42
+ if unit = Mongoid::Geospatial.earth_radius[opts[:unit]]
43
+ opts[:unit] = (opts[:spherical]) ? unit : unit * Mongoid::Geospatial::RAD_PER_DEG
44
+ end
45
+
46
+ if unit = Mongoid::Geospatial.earth_radius[opts[:distance_multiplier]]
47
+ opts[:distance_multiplier] = (opts[:spherical]) ? unit : unit * Mongoid::Geospatial::RAD_PER_DEG
48
+ end
49
+
50
+ opts[:distance_multiplier] = opts[:unit] if opts[:unit].kind_of?(Numeric)
51
+
52
+ # setup paging.
53
+ if opts.has_key?(:page)
54
+ opts[:page] ||= 1
55
+ opts[:paginator] ||= Mongoid::Geospatial.paginator()
56
+
57
+ if opts[:per_page].blank?
58
+ opts[:per_page] = case opts[:paginator]
59
+ when :will_paginate
60
+ @document.per_page
61
+ when :kaminari
62
+ Kaminari.config.default_per_page
63
+ else
64
+ Mongoid::Geospatial.default_per_page
65
+ end
66
+ opts[:per_page] = opts[:per_page].to_i
67
+ end
68
+
69
+ end
70
+ opts[:query] = create_geo_near_query(center,opts)
71
+ results = klass.db.command(opts[:query])
72
+ Mongoid::Geospatial::GeoNearResults.new(klass,results,opts)
73
+ end
74
+
75
+ private
76
+
77
+ def create_geo_near_query(center,opts)
78
+ # minimum query
79
+ query = BSON::OrderedHash.new
80
+ query[:geoNear] = klass.collection_name
81
+ query[:near] = center
82
+
83
+ # create limit and use skip
84
+ if opts[:num]
85
+ query['num'] = opts[:skip].to_i + opts[:num].to_i
86
+ elsif opts[:limit]
87
+ query['num'] = opts[:skip].to_i + opts[:limit].to_i
88
+ elsif opts[:page]
89
+ query['num'] = opts[:skip].to_i + (opts[:page].to_i * opts[:per_page].to_i)
90
+ end
91
+
92
+ # allow the use of complex werieis
93
+ if opts[:query]
94
+ query['query'] = self.criteria.where(opts[:query]).selector
95
+ elsif self.selector != {}
96
+ query['query'] = self.selector
97
+ end
98
+
99
+ if opts[:max_distance]
100
+ query['maxDistance'] = opts[:max_distance].to_f
101
+ query['maxDistance'] = query['maxDistance']/opts[:unit].to_f if opts[:unit]
102
+ end
103
+
104
+ if klass.db.connection.server_version >= '1.7'
105
+ query['spherical'] = true if opts[:spherical]
106
+
107
+ # mongodb < 1.7 returns degrees but with earth flat. in Mongodb 1.7 you can set sphere and let mongodb calculate the distance in Miles or KM
108
+ # for mongodb < 1.7 we need to run Haversine first before calculating degrees to Km or Miles. See below.
109
+ query['distanceMultiplier'] = opts[:distance_multiplier].to_f if opts[:distance_multiplier]
110
+ end
111
+ query
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,5 @@
1
+ module Mongoid #:nodoc:
2
+ class Criteria
3
+ delegate :geo_near, :to => :context
4
+ end
5
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Criterion #:nodoc:
4
+ # Complex criterion are used when performing operations on symbols to get
5
+ # get a shorthand syntax for where clauses.
6
+ #
7
+ # Example:
8
+ #
9
+ # <tt>{ :field => { "$lt" => "value" } }</tt>
10
+ # becomes:
11
+ # <tt> { :field.lt => "value }</tt>
12
+ class Complex
13
+
14
+ def to_mongo_query v
15
+ {"$#{operator}" => v}
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Criterion #:nodoc:
4
+ module Inclusion
5
+ def near(attributes = {})
6
+ update_selector(attributes, "$near")
7
+ end
8
+
9
+ def near_sphere(attributes = {})
10
+ update_selector(attributes, "$near")
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Criterion #:nodoc:
4
+
5
+ # NearSpecial criterion is used when performing #near with symbols to get
6
+ # get a shorthand syntax for where clauses.
7
+ #
8
+ # @example Coninputersion of a simple to complex criterion.
9
+ # { :field => { "$nearSphere" => => [20,30]}, '$maxDistance' => 5 }
10
+ # becomes:
11
+ # { :field.near(:sphere) => {:point => [20,30], :max => 5, :unit => :km} }
12
+ class NearSpatial < Complex
13
+
14
+ # Coninputert input to query for near or nearSphere
15
+ #
16
+ # @example
17
+ # near = NearSpatial.new(:key => :field, :operator => "near")
18
+ # near.to_mongo_query({:point => [:50,50], :max => 5, :unit => :km}) => { '$near : [50,50]' , '$maxDistance' : 5 }
19
+ #
20
+ # @param [Hash,Array] input input to coninputer to query
21
+ def to_mongo_query(input)
22
+ if input.kind_of?(Hash)
23
+ raise ':point required to make valid query' unless input[:point]
24
+ input[:point] = input[:point].to_lng_lat if input[:point].respond_to?(:to_lng_lat)
25
+ query = {"$#{operator}" => input[:point] }
26
+ if input[:max]
27
+ query['$maxDistance'] = input[:max].to_f
28
+
29
+ if unit = Mongoid::Geospatial.earth_radius[input[:unit]]
30
+ unit *= Mongoid::Geospatial::RAD_PER_DEG unless operator =~ /sphere/i
31
+ input[:unit] = unit
32
+ end
33
+
34
+ query['$maxDistance'] = query['$maxDistance']/input[:unit].to_f if input[:unit]
35
+ end
36
+ query
37
+ elsif input.kind_of? Array
38
+ if input.first.kind_of? Numeric
39
+ {"$#{operator}" => input }
40
+ else
41
+ input[0] = input[0].to_lng_lat if input[0].respond_to?(:to_lng_lat)
42
+ {"$#{operator}" => input[0], '$maxDistance' => input[1] }
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Criterion #:nodoc:
4
+
5
+ # WithinSpecial criterion is used when performing #within with symbols to get
6
+ # get a shorthand syntax for where clauses.
7
+ #
8
+ # @example Conversion of a simple to complex criterion.
9
+ # { :field => { "$within" => {'$center' => [20,30]} } }
10
+ # becomes:
11
+ # { :field.within(:center) => [20,30] }
12
+ class WithinSpatial < Complex
13
+
14
+ # Convert input to query for box, polygon, center, and centerSphere
15
+ #
16
+ # @example
17
+ # within = WithinSpatial.new(opts[:key] => 'point', :operator => 'center')
18
+ # within.to_mongo_query({:point => [20,30], :max => 5, :unit => :km}) #=>
19
+ #
20
+ # @param [Hash,Array] input Variable to conver to query
21
+ def to_mongo_query(input)
22
+ if ['box','polygon'].index(@operator)
23
+ input = input.values if input.kind_of?(Hash)
24
+ if input.respond_to?(:map)
25
+ input.map!{ |v| (v.respond_to?(:to_lng_lat)) ? v.to_lng_lat : v }
26
+ else
27
+ input
28
+ end
29
+ elsif ['center','centerSphere'].index(@operator)
30
+
31
+ if input.kind_of?(Hash) || input.kind_of?(ActiveSupport::OrderedHash)
32
+ raise ':point required to make valid query' unless input[:point]
33
+ input[:point] = input[:point].to_lng_lat if input[:point].respond_to?(:to_lng_lat)
34
+ if input[:max]
35
+ input[:max] = input[:max].to_f
36
+
37
+ if unit = Mongoid::Geospatial.earth_radius[input[:unit]]
38
+ unit *= Mongoid::Geospatial::RAD_PER_DEG unless operator =~ /sphere/i
39
+ input[:unit] = unit
40
+ end
41
+
42
+ input[:max] = input[:max]/input[:unit].to_f if input[:unit]
43
+
44
+ input = [input[:point],input[:max]]
45
+ else
46
+ input = input[:point]
47
+ end
48
+ end
49
+
50
+ if input.kind_of? Array
51
+ input[0] = input[0].to_lng_lat if input[0].respond_to?(:to_lng_lat)
52
+ end
53
+
54
+ end
55
+ {'$within' => {"$#{@operator}"=>input} }
56
+ end
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,3 @@
1
+ require 'mongoid_geospatial/criterion/complex'
2
+ require 'mongoid_geospatial/criterion/near_spatial'
3
+ require 'mongoid_geospatial/criterion/within_spatial'