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 = {}) ⇒ Array<Mongoid::Document>
Performs a $geoNear aggregation pipeline stage to find documents near a point, returning them sorted by distance and including the distance.
-
#nearby(coordinates, _options = {}) ⇒ Mongoid::Criteria
Provides a convenient way to find documents near a given set of coordinates.
-
#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
Creates a 2dsphere index for the given field, suitable for spherical geometry calculations.
Instance Method Details
#geo_near(field_name, coordinates, options = {}) ⇒ Array<Mongoid::Document>
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
Place.geo_near(:location, [10, 20], spherical: true).each do |place|
puts "#{place.name} is #{place.distance} meters away." # Assumes distanceField is 'distance'
end
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/mongoid/geospatial.rb', line 276 def geo_near(field_name, coordinates, = {}) mongoized_coords = Mongoid::Geospatial::Point.mongoize(coordinates) raise ArgumentError, "Invalid coordinates provided: #{coordinates.inspect}" unless mongoized_coords # User-provided options. Work with a copy. = .dup limit_value = .delete(:limit) # Handled by a separate pipeline stage # 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) # Ensure :spherical is a strict boolean (true/false). # If user_options provided :spherical, it's already set. If not, the default is used. # This line ensures the final value is strictly true or false, not just truthy/falsy. [:spherical] = ![:spherical].nil? # 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 # Execute the aggregation pipeline collection.aggregate(pipeline) # Don't instantiate results here. # aggregated_results = collection.aggregate(pipeline) # Map the raw Hash results from aggregation to Mongoid model instances. # Mongoid's #instantiate method correctly handles creating model objects # and assigning attributes, including dynamic ones like the distanceField. # aggregated_results.map { |attrs| instantiate(attrs) } end |
#nearby(coordinates, _options = {}) ⇒ 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)
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/mongoid/geospatial.rb', line 212 def nearby(coordinates, = {}) if spatial_fields.empty? raise "No spatial fields defined for #{name} to use with .nearby. " \ "Mark a field with 'spatial: true' or 'sphere: true'." end field_name_sym = spatial_fields.first.to_sym field_definition = fields[field_name_sym.to_s] raise "Could not find field definition for spatial field: #{field_name_sym}" unless field_definition query_operator = field_definition.[:sphere] ? :near_sphere : :near criteria.where(field_name_sym.send(query_operator) => coordinates) end |
#spatial_index(name, options = {}) ⇒ Object
Creates a 2d spatial index for the given field.
80 81 82 83 |
# File 'lib/mongoid/geospatial.rb', line 80 def spatial_index(name, = {}) spatial_fields_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
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/mongoid/geospatial.rb', line 149 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
Creates a 2dsphere index for the given field, suitable for spherical geometry calculations.
91 92 93 94 |
# File 'lib/mongoid/geospatial.rb', line 91 def spherical_index(name, = {}) spatial_fields_indexed << name index({ name => '2dsphere' }, ) end |