Mongoid geo
A Geo extension for Mongoid.
- Supports Mongo DB 1.7+ sphere distance calculations
- 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.
- Calculate locations near a given point (geoNear)
Mongoid 2 geo features
The following summarized what geo functionality is already provided by Mongoid 2.0 (as far as I could tell, May 9th, 2011)
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 that are currently built-in for Mongoid 2.
Mongoid Geo implements some nice extra geo features:
Mongoid Geo features
The following briefly demonstrates all the features that mongoid-geo provides:
Geo index
A new geo_index class method
Usage example:
geo_index :location
Note: For embedded documents, you must define the index in the root collection class. (davemitchell)
Geo option for Array GPS location field
Objective: When setting a geo GPS location array, the setter should try to convert the value to an array of floats
The “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
mongoid-geo provides a new :geo
option that can be used with any Array field:
Usage example:
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)
# You can also reverse the lat/lng positioning of the array storage - this is fx useful for spherical calculations
Mongoid::Geo.spherical_mode do
# Mongoid::Geo.lat_index.should == 1
# Mongoid::Geo.lng_index.should == 0
address.location = "23.5, -47"
address.location.should == [23.5, -47].reverse
end
# or alternatively
Mongoid::Geo.spherical = true
address.location = "23.5, -47"
address.location.should == [23.5, -47].reverse
geoNear
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
GeoNear returns each distance calculated in degrees. Use the :distanceMultiplier or :unit option to return in the unit of your choice (see unit.rb).
Set :distanceMultiplier = 6371
to get distance in KMs
Set @:distanceMultiplier = @3963.19
to get distance in Miles
You can also use the :unit option instead like this (supports :feet, :meters, :kms, :miles):
results = Address.geoNear @center.location, :location, :unit => :feet, :dist_order => :desc
The geoNear query result is returned as a Mongoid::Criteria
results.desc(:distance).map(&:distance)
Note that the :fromLocation
field, stores the location the distance was last calculated as a Hash of the GPS location point it was calculated from:
[23.5, -47].hash
This hash can be retrieved (and used for comparison?) using the fromHash
field
from = results.first.fromHash
You can also at any time get the GPS location point which the distance of any result instance was calculated from, using the @fromPoint field
from = results.first.fromPoint
You can now explicitly set/configure the Mongo DB version used. This will affect whether built-in Mongo DB distance calculation will be used or using standalone Ruby Haversine algorithm. By default the Mongo DB version is set to 1.8 (as of May 9, 2011) . See geo_near specs for more details/info on this.
Mongoid::Geo.mongo_db_version = 1.7
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 => => [ 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 => [=> 72, :lng => -44, radius])
withinBox
box = [[50, 40], [30,55]]
base.where(:location.withinBox => box)
# => locations: : {"$box" : [[50, 40], [30,55]]
base.where(:location.withinBox(:sphere) => box)
# => locations: : {"$boxSphere" : [[50, 40], [30,55]]
You can also use a Hash to define the box
places.where(:location.withinBox => => [50, 40], :upper_right => [30,55])
# or mix and match
places.where(:location.withinBox => => {: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: : {"$center" : [[50, 40], 4]
places.where(:location.withinCenter(:sphere) => [center, radius])
# => places: : {"$centerSphere" : [[50, 40], 4]
You can also use a hash to define the circle, with :center
and :radius
keys
places.where(:location.withinCenter => => [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)