Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Geo-spatial extension for Mongoid 2
branch: master

This branch is 3 commits ahead, 42 commits behind kristianmandrup:master

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
lib
sandbox
spec
.document
.gitignore
.rspec
.rvmrc
Changelog.textile
Gemfile
MIT-LICENSE
README.textile
Rakefile
VERSION
mongoid_geo-0.6.0.gem
mongoid_geo.gemspec

README.textile

Mongoid geo

A Geo extension for Mongoid.

MongoDB Geospatial Indexing

  • 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 => {: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])

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)

within_center

  center = [50, 40]
  radius = 4

  places.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 = 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 :)

Something went wrong with that request. Please try again.