Mongoid geo

A Geo extension for Mongoid.

MongoDB Geospatial Indexing

  • Supports Mongoid 1.7 sphere distance calculations and
  • Adds nearSphere inclusion method
  • Adds a set of geo related inflections
  • Adds an exta option for defining a “geo” field, to have the generated attr_writer parse and convert strings etc. to float arrays.

Mongoid 2 geo features

Find addresses near a point


  Address.near(:latlng => [37.761523, -122.423575, 1])

Find locations within a circle


  base.where(:location.within => { "$center" => [ [ 50, -40 ], 1 ] })  

Create geo-spatial index


  class Person
    field :location, :type => Array
    index [[ :location, Mongo::GEO2D ]], :min => -180, :max => 180      
  end

  # to ensure indexes are created, either:
  Mongoid.autocreate_indexes = true

  # or in the mongoid.yml
  autocreate_indexes: true  

These are the only geo features I could find are currently built-in for Mongoid 2. Mongoid Geo implements the following extra features…

Mongoid Geo features

The following briefly demonstrates all the features that Mongoid Geo currently provides

Geo index

Old/Manual way: index [[ :location, Mongo::GEO2D ]], :min => -180, :max => 180

Using new geo_index class method : geo_index :location

Special geo-array attribute writer

When setting a geo-location array, the setter should try to convert the value to an array of floats

Old/Manual way:


  class Person
    field :locations, :type => Array

    def locations= args
      @locations = args.kind_of?(String) ? args.split(",").map(&:to_f) : args
    end
  end  

With the new :geo option supplied by mongoid-geo :


  class Person
    field :location, :type => Array, :geo => true
    
    geo_index :location
  end  
  
   p = Person.new

   # A Geo array can now be set via String or Strings, Hash or Object, here a few examples...
   # Please see geo_fields_spec.rb for more options!

   p.location = "45.1, -3.4"
   p.location = "45.1", "-3.4"   
   p.location = {:lat => 45.1, :lng => -3.4}   
   p.location = [{:lat => 45.1, :lng => -3.4}]
   p.location = {:latitude => 45.1, :longitude => -3.4}   
   
   my_location  = Location.new :latitude => 45.1, :longitude => -3.4
   p.location   = my_location

   # for each of the above, the following holds
   assert([45.1, -3.4], p.location)   

   # also by default adds #lat and #lng convenience methods (thanks to TeuF)
   
   assert(45.1 , p.lat)
   assert(-3.4 , p.lng)

   # the #lat and #lng convenience methods can also be customized with the :lat and :lng options
  field :location, :type => Array, :geo => true, :lat => :latitude, :lng => :longitude

  assert(45.1 , p.latitude)
  assert(-3.4 , p.longitude) 
  
  # or set the array attributes using symmetric setter convenience methods!
  p.latitude   = 44
  assert(44 , p.latitude)   
  
  # PURE MAGIC!!!

News update (March 2)

  • Added support for geoNear queries!!!

class Address
  include Mongoid::Document
  extend Mongoid::Geo::Near

  field :location, :type => Array, :geo => true
  ...
end  

# Find all addresses sorted nearest to a specific address loation 
nearest_addresses = Address.geoNear(another_address, :location)

class Position
  include Mongoid::Document

  field :pos, :type => Array, :geo => true
  ...
end  

# Find all positions sorted nearest to the address loation 
nearest_positions = Position.geoNear(another_address.location, :pos)

# perform distance locations in Speherical mode inside Mongo DB (default is :plane)
nearest_positions = Position.geoNear(another_address.location, :pos, :mode => :sphere)

# other options supported are: :num, :maxDistance, distanceMultiplier, :query

If you need to operate on the Mongoid models referenced by the query result, simply call #to_models on it Fx to get the city of the first model instance from the query result:

nearest_city = Position.geoNear(another_address.location, :pos).to_models.first.city

You can also use a #to_model method on an individual query result like this:

nearest_city = Position.geoNear(another_address.location, :pos).first.to_model.city

  1. the model returned also has a distance accessor populated with the distance calculated by running the geoNear query

nearest_distance = Position.geoNear(another_address.location, :pos).first.to_model.distance

Mongoid Geo extra inclusions

Find addresses near a point using spherical distance calculation


  Address.nearSphere(:location => [ 72, -44 ])

Mongoid Geo extra inflections

nearSphere


  base.where(:location.nearSphere => [ 72, -44 ])
  # => :location => { "$nearSphere" : [ 72, -44 ] }

nearMax

Find points near a given point within a maximum distance


  base.where(:location.nearMax => [[ 72, -44 ], 5])
  # => { $near: [50, 40] , $maxDistance: 3 }

  base.where(:location.nearMax(:sphere) => [[ 72, -44 ], 5])
  # => { $nearSphere: [50, 40] , $maxDistanceSphere: 3 }

  base.where(:location.nearMax(:sphere, :flat) => [[ 72, -44 ], 5])
  # => { $nearSphere: [50, 40] , $maxDistance: 3 }

You can also use a Hash to define the nearMax

 
  places.where(:location.nearMax => {:point => [ 72, -44 ], :distance => 5})  

Or use an Object (which must have the methods #point and #distance that return the point and max distance from that point)


  near_max_ = (Struct.new :point, :distance).new
  near_max.point = [50, 40]
  near_max.distance = [30,55]
    
  places.where(:location.nearMax => near_max)  

Note: For the points, you can also use a hash or an object with the methods/keys, either :lat, :lng or :latitude, :longitude

Example:


  center = (Struct.new :lat, :lng).new
  center.lat = 72
  center.lng = -44  
  places.where(:location.withinCenter => [center, radius])    
  
  # OR
  
  places.where(:location.withinCenter => [{:lat => 72, :lng => -44}, radius])      

withinBox


  box = [[50, 40], [30,55]]
  base.where(:location.withinBox => box)
  # => locations: {"$within" : {"$box" : [[50, 40], [30,55]]}

  base.where(:location.withinBox(:sphere) => box)
  # => locations: {"$within" : {"$boxSphere" : [[50, 40], [30,55]]}

You can also use a Hash to define the box

 
  places.where(:location.withinBox => {:lower_left => [50, 40], :upper_right => [30,55]})  
  
  # or mix and match
  
  places.where(:location.withinBox => {:lower_left => {:lat => 50, :lng => 40}, :upper_right => [30,55] } )      

Or use an object (which must have the methods #lower_left and #upper_right that return the points of the bounding box)


  box = (Struct.new :lower_left, :upper_right).new
  box.lower_left =  [50, 40]
  box.upper_right = [30, 55]
    
  places.where(:location.withinBox => box)  

withinCenter


  center = [50, 40]
  radius = 4

  places.where(:location.withinCenter => [center, radius])  
  # => places: {"$within" : {"$center" : [[50, 40], 4]}  

  places.where(:location.withinCenter(:sphere) => [center, radius])    
  # => places: {"$within" : {"$centerSphere" : [[50, 40], 4]}  

You can also use a hash to define the circle, with :center and :radius keys

 
  places.where(:location.withinCenter => {:center => [50, 40], :radius => 4})  

Or use an object (which must have the methods #lower_left and #upper_right that return the points of the bounding box)


  circle = (Struct.new :center, :radius).new
  circle.center = [50, 40]
  circle.radius = 4
  
  places.where(:location.withinCenter => circle)