Module: Mongoid::Geospatial::ClassMethods
- Defined in:
- lib/mongoid/geospatial.rb
Overview
Methods applied to Document's class
Instance Method Summary collapse
-
#geo_near(field_name, coordinates, options = {}) ⇒ Mongo::Collection::View::Aggregation
Performs a $geoNear aggregation pipeline stage to find documents near a point, returning them sorted by distance and including the distance.
-
#geom(name = :geom) ⇒ Object
A Point on a 2dsphere index.
-
#nearby(coordinates, km: nil, field: nil) ⇒ Mongoid::Criteria
Provides a convenient way to find documents near a given set of coordinates.
-
#nearest(geom, km, field: nil) ⇒ Object
The single closest document within
km, or nil. -
#remember_indexed(name) ⇒ Object
One field, one entry, always a Symbol — a field may carry both a 2d and a 2dsphere index, and
spatial: truecalls this on its way in too. -
#spatial_field(field = nil) ⇒ Array
The pin
.nearby,.withinand.nearestread, and whether it is on a sphere. -
#spatial_index(name, options = {}) ⇒ Object
Creates a 2d spatial index for the given field.
-
#spatial_scope(field_name, default_geo_near_options = {}) ⇒ Object
Defines a class method to find the closest document to a given point using the specified spatial field via the
geoNearcommand. -
#spherical_index(name, options = {}) ⇒ Object
(also: #sphere_index)
Creates a 2dsphere index for the given field, suitable for spherical geometry calculations.
-
#within(geom, km, field: nil) ⇒ Object
Documents within
kmofgeom, nearest first.
Instance Method Details
#geo_near(field_name, coordinates, options = {}) ⇒ Mongo::Collection::View::Aggregation
Performs a $geoNear aggregation pipeline stage to find documents near a point, returning them sorted by distance and including the distance.
This method is a wrapper around the MongoDB $geoNear aggregation stage,
which allows for more complex queries and options than the simple near or near_sphere methods.
- But it's not chainable like a standard Mongoid query *
Example:
# Find places near [10, 20], using spherical calculations, up to 5km away
Place.geo_near(:location, [10, 20],
spherical: true,
maxDistance: 5000, # 5 kilometers in meters
distanceField: 'dist.calculated',
query: { category: 'restaurant' },
limit: 10)
# Iterate over results — hashes, not documents
Place.geo_near(:location, [10, 20], spherical: true).each do |doc|
puts "#{doc['name']} is #{doc['distance']} meters away."
end
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
# File 'lib/mongoid/geospatial.rb', line 378 def geo_near(field_name, coordinates, = {}) mongoized_coords = Mongoid::Geospatial.mongoize_point!(coordinates) # User-provided options. Work with a copy. = .dup limit_value = .delete(:limit) # Handled by a separate pipeline stage # `km:` is metres on the wire, and metres only holds for a GeoJSON # `near` on a sphere. A legacy pair would read `maxDistance` in radians. if (km = .delete(:km)) [:maxDistance] = km.to_f * 1_000 [:spherical] = true mongoized_coords = { 'type' => 'Point', 'coordinates' => mongoized_coords } end # Core $geoNear parameters derived from method arguments, these are not overrideable by user_options. geo_near_core_params = { key: field_name.to_s, near: mongoized_coords } # Defaultable $geoNear parameters. User options will override these. geo_near_defaultable_params = { distanceField: 'distance', spherical: false # Default to planar (2d) calculations } # Merge user options over defaults, then ensure core parameters are set. = geo_near_defaultable_params.merge().merge(geo_near_core_params) # $geoNear wants a strict boolean, and honours the caller's choice. [:spherical] = [:spherical] ? true : false # Note on performance: # $geoNear is an aggregation pipeline stage. For simple proximity queries, # it might exhibit slightly higher "real" time (wall-clock time) in benchmarks # compared to direct query operators like $near or $nearSphere. This is often # due to the inherent overhead of the aggregation framework versus a direct query. # However, $geoNear offers more capabilities, such as returning the distance # (distanceField), distanceMultiplier, includeLocs, and integrating with other # aggregation stages, which are not available with $near/$nearSphere. pipeline = [{ '$geoNear' => }] # Add $limit stage if limit_value was provided pipeline << { '$limit' => limit_value.to_i } if limit_value collection.aggregate(pipeline) end |
#geom(name = :geom) ⇒ Object
A Point on a 2dsphere index. Default name is geom.
160 161 162 |
# File 'lib/mongoid/geospatial.rb', line 160 def geom(name = :geom) field name, type: Point, sphere: true end |
#nearby(coordinates, km: nil, field: nil) ⇒ Mongoid::Criteria
Provides a convenient way to find documents near a given set of coordinates.
It automatically uses the first spatial field defined in the model and
determines whether to use a planar (.near) or spherical (.near_sphere)
query based on the field's definition options (spatial: true vs sphere: true).
Example:
Bar.nearby([10, 20])
Alarm.nearby(my_point_object, km: 30)
280 281 282 283 284 285 |
# File 'lib/mongoid/geospatial.rb', line 280 def nearby(coordinates, km: nil, field: nil) # rubocop:disable Naming/MethodParameterName pin, sphere = spatial_field(field) return criteria.where(Mongoid::Geospatial.near_selector(pin, coordinates, km, sphere: sphere)) if km criteria.where(pin.send(sphere ? :near_sphere : :near) => coordinates) end |
#nearest(geom, km, field: nil) ⇒ Object
The single closest document within km, or nil.
`within(geom, km).first` NOT the nearest one
`nearest(geom, km)` the nearest one
Mongoid's #first and #last sort by _id when the criteria carries no
sort of its own (contextual/mongo.rb, view.sort || { _id: 1 }), and
that _id sort replaces the distance order $near put there. It reads
as working every time the closest document happens to be the oldest.
323 324 325 |
# File 'lib/mongoid/geospatial.rb', line 323 def nearest(geom, km, field: nil) # rubocop:disable Naming/MethodParameterName within(geom, km, field: field).limit(1).to_a.first end |
#remember_indexed(name) ⇒ Object
One field, one entry, always a Symbol — a field may carry both a 2d
and a 2dsphere index, and spatial: true calls this on its way in too.
152 153 154 155 |
# File 'lib/mongoid/geospatial.rb', line 152 def remember_indexed(name) sym = name.to_sym spatial_fields_indexed << sym unless spatial_fields_indexed.include?(sym) end |
#spatial_field(field = nil) ⇒ Array
The pin .nearby, .within and .nearest read, and whether it is on
a sphere. Handed nothing, the first spatial field — a model with two
pins (geom :pick_up, geom :drop_up) has to name the one it means.
294 295 296 297 298 299 300 301 302 |
# File 'lib/mongoid/geospatial.rb', line 294 def spatial_field(field = nil) sym = (field || spatial_fields.first)&.to_sym unless sym && spatial_fields.include?(sym) raise ArgumentError, "#{name} has no spatial field #{sym.inspect} — it has #{spatial_fields.inspect}. " \ "Mark one with 'spatial: true' or 'sphere: true'." end [sym, fields.fetch(sym.to_s).[:sphere] ? true : false] end |
#spatial_index(name, options = {}) ⇒ Object
Creates a 2d spatial index for the given field.
131 132 133 134 |
# File 'lib/mongoid/geospatial.rb', line 131 def spatial_index(name, = {}) remember_indexed(name) index({ name => '2d' }, ) end |
#spatial_scope(field_name, default_geo_near_options = {}) ⇒ Object
Defines a class method to find the closest document to a given point
using the specified spatial field via the geoNear command.
Example:
class Place
include Mongoid::Document
include Mongoid::Geospatial
field :location, type: Array
spherical_index :location # Assumes a 2dsphere index for spherical queries
spatial_scope :location, spherical: true # Default to spherical for this scope
end
Place.closest_to_location([lon, lat]) # Finds the single closest place
Place.closest_to_location([lon, lat], max_distance: 500) # Override/add options
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/mongoid/geospatial.rb', line 217 def spatial_scope(field_name, = {}) method_name = :"closest_to_#{field_name}" field_name_sym = field_name.to_sym # key_name = field_name.to_s # Original geoNear used 'key' for field name singleton_class.class_eval do define_method(method_name) do |coordinates, = {}| # `coordinates` should be [lon, lat] or a GeoJSON Point hash # `self` here is the class (e.g., Bar) = .merge() # Determine if spherical based on options or field definition is_spherical = if .key?(:spherical) [:spherical] else # self.fields uses string keys for field names field_def = fields[field_name.to_s] field_def && field_def.[:sphere] end query_operator = is_spherical ? :near_sphere : :near # Prepare the value for the geospatial operator # Mongoid::Geospatial::Point.mongoize ensures coordinates are in [lng, lat] array format # from various input types (Point object, array, string, hash). mongoized_coords = Mongoid::Geospatial::Point.mongoize(coordinates) geo_query_value = if [:max_distance] { # Using $geometry for clarity when $maxDistance is used, # which is standard for $near/$nearSphere operators. '$geometry' => { type: 'Point', coordinates: mongoized_coords }, '$maxDistance' => [:max_distance].to_f } else mongoized_coords # Simple array [lng, lat] for the operator end # Start with a base criteria, applying an optional filter query current_criteria = [:query] ? where([:query]) : all # Apply the geospatial query. $near and $nearSphere queries return sorted results. current_criteria.where(field_name_sym.send(query_operator) => geo_query_value) end end end |
#spherical_index(name, options = {}) ⇒ Object Also known as: sphere_index
Creates a 2dsphere index for the given field, suitable for spherical geometry calculations.
142 143 144 145 |
# File 'lib/mongoid/geospatial.rb', line 142 def spherical_index(name, = {}) remember_indexed(name) index({ name => '2dsphere' }, ) end |
#within(geom, km, field: nil) ⇒ Object
Documents within km of geom, nearest first. Spherical either way:
the field's index decides the dialect, see .near_selector.
308 309 310 |
# File 'lib/mongoid/geospatial.rb', line 308 def within(geom, km, field: nil) # rubocop:disable Naming/MethodParameterName nearby(geom, km: Mongoid::Geospatial.km!(km), field: field) end |