Mongoid geo
A Geo extension for Mongoid.
- Supports Mongo DB 1.7+ sphere distance calculations
- Extra geo calculation methods such as #near_sphere etc.
- Add concept of a special “geo” field (for setting the location point)
- Configure and create geo indexes
- Calculate locations near a given point and return as criteria, sorted by distance
Status Update (May 31, 2011)
Added support for using GeoPoint from geo_calc to pass around location points. Also makes it easy to use GeoVectors from geo_vectors
Please see new specs use_geo_calc and use_geo_vectors in the /spec folder to see how this is used. Please help make sure the functionality works in the right way! Thanks :)
Status Update (May 25, 2011)
- Fixed major bug which stored coordinate in [:lat, :lng]. This is incorrect. Coordinates must be stored with :lng as the first value in order for geospatial queries to work properly. - Excerpt from MongoDB GeoSpatial Indexing ‘The code assumes that you are using decimal degrees in (longitude, latitude) order. This is the same order used for the GeoJSON spec. Using (latitude, longitude) will result in very incorrect results, but is often the ordering used elsewhere, so it is good to double-check. The names you assign to a location object (if using an object and not an array) are completely ignored, only the ordering is detected.’
Status update (May 21, 2011)
- Created alias methods such as #near_sphere for #nearSphere etc. to make the DSL much more ‘rubyish’!
- Added #create_index! method (see ‘Geo index’ section below).
You need help?
Post questions in the mongoid-geo group. Here I (and other uses of mongoid-geo) will try to help out and answer any questions.
Suggestions, improvements and bugs?
Please raise issues or suggestions for improvements in the “Issues” section on github. I would recommend that you try to branch the project, try to fix it yourself and make a pull request.
Current integrations
Mongoid geo has now been integrated with Google-Maps-for-Rails, thanks to oli-g, see commit
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)
Calling geo_index also adds a #create_index! method to the class to enable construction of the index (on the instances/data in the DB).
class Address
...
geo_index :location
end
Address.create_index!
I also added a nice little Array macro so you can do this:
[Address, Location].create_geo_indexes!
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)
Customizing lat/lng attribute names:
# 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)
Reversing lat/lng for spherical calculations
# 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
geo_near
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.geo_near(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.geo_near(another_address.location, :pos)
Perform distance locations in Speherical mode inside Mongo DB (default is :plane)
nearest_positions = Position.geo_near(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.geo_near @center.location, :location, :unit => :feet, :dist_order => :desc
The geo_near 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.near_sphere(:location => [ 72, -44 ])
Mongoid Geo extra inflections
near_sphere
base.where(:location.near_sphere => [ 72, -44 ])
# => :location => { "$nearSphere" : [ 72, -44 ] }
near_max
Find points near a given point within a maximum distance
base.where(:location.near_max => [[ 72, -44 ], 5])
# => { $near: [50, 40] , $maxDistance: 3 }
base.where(:location.near_max(:sphere) => [[ 72, -44 ], 5])
# => { $nearSphere: [50, 40] , $maxDistanceSphere: 3 }
base.where(:location.near_max(:sphere, :flat) => [[ 72, -44 ], 5])
# => { $nearSphere: [50, 40] , $maxDistance: 3 }
You can also use a Hash to define the near_max
places.where(:location.near_max => => [ 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.near_max => 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.within_center => [center, radius])
# OR
places.where(:location.within_center => [=> 72, :lng => -44, radius])
within_box
box = [[50, 40], [30,55]]
base.where(:location.within_box => box)
# => locations: : {"$box" : [[50, 40], [30,55]]
base.where(:location.within_box(:sphere) => box)
# => locations: : {"$boxSphere" : [[50, 40], [30,55]]
You can also use a Hash to define the box
places.where(:location.within_box => => [50, 40], :upper_right => [30,55])
# or mix and match
places.where(:location.within_box => => {: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.within_box => box)
within_center
center = [50, 40]
radius = 4
places.where(:location.within_center => [center, radius])
# => places: : {"$center" : [[50, 40], 4]
places.where(:location.within_center(: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.within_center => => [50, 40], :radius => 4)
Or use an object (which must have the methods #center and #radius that return the center and radius of the circle))
circle = (Struct.new :center, :radius).new
circle.center = [50, 40]
circle.radius = 4
places.where(:location.within_center => circle)
Note on the specs
The specs still use the old “Javascript” like method convention, such as #nearSphere Don’t let that fool you ;)
Contribute
Please feel free to contribute to the project!
I aim to deliver a complete geo package for use with Mongoid_. This gem should work nicely with geo_calc and geo_vectorsvectors that I’m also working on.
Your assistance on any of these projects will be greatly appreciated :)