Mongoid-geo is a project mainly written in RUBY and JAVASCRIPT, based on the MIT license.
Geo-spatial extension for Mongoid 2
h1. Mongoid geo
h2. Status update: August, 2011
This project is currently dead, as I can find no time to keep it alive for the time being. The project mostly works, except for
monogid/contexts/geo_mongo.rb
, which needs some loove ;)
The project "mongoid_spacial":https://github.com/ryanong/mongoid_spacial by @ryanong has taken many of the ideas and core design of mongoid_geo, improved, reworked and optimized it and added many new features such as pagination intgration etc.
Please use the mongoid_spacial gem instead!!
Note: I have created several other "geo" projects in various states, some currently with a "refactor" branch. Would be nice to finish those and have it all integrated some time :)
Cheers!
h3. Running specs
The goal of this branch is to define a good specs structure and build up a good design of this 'geo' add-on this way.
$ bundle $ bundle exec rspec spec/mongoid/geo/config_spec.rb
h3. Status update: June, 2011
h2. Example Usage 1.0 (goal)
class Address field :location, :type => Array, :geo => true endmy_home = Address.new :location => {:lat => 10, :lng => 12}.to_lng_lat my_job = Address.new :location => [10.2, "12.1"].to_lng_lat my_club = Address.new :location => [10.2, 12.1] my_club.location_lat == 12.1 # true my_club.location_lng == 10.2 # true
nearest_locations = Address.geo_near my_club nearest_locations.first
find near point, max distance
Address.where :location.near_max => [{:latitude => 10, :longitude => 12 }, 5] # 5 km? Address.where :location.near_max => [my_club, 5]
Address.where(:location.within_box => [my_job, my_club]) Address.where(:location.within_center(:sphere) => [my_home, 5])
geo_point integration (using GeoPoint class)
Mongoid::Geo::Config.enable_extension! :geo_point
p1 = "58 38 38N, 003 04 12W".geo_point # DMS format support! my_home = Address.new :location => p1.to_lng_lat
a GeoPoint includes some nice geo calculation methods, that are calculated relative to itself !
p1.bearing_to my_club.geo_point # => 60 deg ? p1.distance_to my_home.geo_point # => 2341 kms ?
geo_vectors integration
Mongoid::Geo::Config.enable_extension! :geo_vectors
my_home = Address.new :location => p1.to_lng_lat my_home.add [2, 4].vector # add lng/lat vector my_home.add [20, 4.km].vector # add vector of 4km at 20 degrees
h2. Example Usage 1.1 (goal)
Would be nice with a simple API that acts as a location repository and can be implemented for any key/value store. Would also be nice to use unit distances as Numeric macros (currently available in _geomagic gem).
Address.where(:location.within_box => [:my_job, :my_club]) # each point should be looked up in the repo using the symbol as key Address.where :location.near_max => [:my_home, 5000.meters] # use geo_magic distance here!
h2. Intro
This is a Geo extension for Mongoid 2.
"MongoDB Geospatial Indexing":http://www.mongodb.org/display/DOCS/Geospatial+Indexing
h2. You need help?
Post questions in the "mongoid-geo":http://groups.google.com/group/mongoid-geo group. Here I (and other uses of mongoid-geo) will try to help out and answer any questions.
h2. 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.
h3. Current integrations
Mongoid geo has now been integrated with "Google-Maps-for-Rails":https://github.com/apneadiving/Google-Maps-for-Rails, thanks to "oli-g":https://github.com/oli-g, see "commit":https://github.com/oli-g/Google-Maps-for-Rails/commit/9ff5c82d33e3fa625e441baa516fee05e9ae979c
h2. 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)
h3. Find addresses near a point
Address.near(:latlng => [37.761523, -122.423575, 1])
h3. Find locations within a circle
base.where(:location.within => { "$center" => [ [ 50, -40 ], 1 ] })
h3. Create geo-spatial index
class Person field :location, :type => Array index [[ :location, Mongo::GEO2D ]], :min => -180, :max => 180 endto 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:
h1. Mongoid Geo features
The following briefly demonstrates all the features that mongoid-geo provides:
h2. Geo index
A new _geoindex 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 endAddress.create_index!
I also added a nice little Array macro so you can do this:
[Address, Location].create_geo_indexes!
h2. 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 => Arraydef 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 => truegeo_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 => :longitudeassert(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 calculationsMongoid::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
h2. geo_near
class Address include Mongoid::Document extend Mongoid::Geo::Nearfield :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 _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 _geonear specs for more details/info on this.
@Mongoid::Geo.mongo_db_version = 1.7@
h2. Mongoid Geo extra inclusions
Find addresses near a point using spherical distance calculation
Address.near_sphere(:location => [ 72, -44 ])
h2. Mongoid Geo extra inflections
h3. near_sphere
base.where(:location.near_sphere => [ 72, -44 ]) # => :location => { "$nearSphere" : [ 72, -44 ] }
h3. 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 => {: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.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 => [{:lat => 72, :lng => -44}, radius])
h3. within_box
box = [[50, 40], [30,55]] base.where(:location.within_box => box) # => locations: {"$within" : {"$box" : [[50, 40], [30,55]]}base.where(:location.within_box(:sphere) => box)
=> locations: {"$within" : {"$boxSphere" : [[50, 40], [30,55]]}
You can also use a Hash to define the box
places.where(:location.within_box => {:lower_left => [50, 40], :upper_right => [30,55]})or mix and match
places.where(:location.within_box => {: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.within_box => box)
h3. within_center
center = [50, 40] radius = 4places.where(:location.within_center => [center, radius])
=> places: {"$within" : {"$center" : [[50, 40], 4]}
places.where(:location.within_center(: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.within_center => {: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 = 4places.where(:location.within_center => circle)
h2. Note on the specs
The specs still use the old "Javascript" like method convention, such as #nearSphere Don't let that fool you ;)
h2. 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":https://github.com/kristianmandrup/geo_calc and "geo_vectors":https://github.com/kristianmandrup/geo_vectors that I'm also working on.
Your assistance on any of these projects will be greatly appreciated :)