MongoDB is an open-source NoSQL database. It offers great features to build Location Based Services with build in support for Geospatial Indexes and Queries.
I’ve been using MongoDB with Node.js on Heroku so here a quick walk through on how to set it up and use it:
Setup
Local
Install and Run:
$ brew install mongo
$ mongod
On lesson I’ve learned is to always try to use MongoDB from the shell first before you start with actual code. After having the command right in the shell translate it into code and test your actual implementation. Here some shell commands to get you started:
$ mongo
> use mydbname
For this example we store locations in a collection called locations
Make sure you have set the geo spacial index:
> db.locations.ensureIndex({loc: '2d'})
> db.locations.getIndexKeys()
Should output [ { "_id" : 1 }, { "loc" : "2d" } ]
Heroku
On the Heroku side it’s pretty easy to set up as well:
$ heroku addons:add mongohq:sandbox
To make sure the location index is also working on Heroku, login, select your app and select the MongoHQ add-on. This brings you to the MongoHQ admin interface. Then select your locations collection and make sure the Indexes
are configured correctly. In my case it shows as { key: { loc: "2d" }, v: 1, ns: "appXXX.locations", name: "loc_2d" }
MongoDB Shell
Insert Location
When you insert a new location it’s essential to store the location in an array with [lon, lat] and not [lat, lon]. This was a mistake I made first and it took me a while to figure that out because all my queries were returning the wrong stuff.
Let’s insert some example locations (I’ve been using LATLONG.net):
> db.locations.insert({name: "Ferry Building", city: "San Francisco", loc:[-122.3937, 37.7955]})
> db.locations.insert({name: "Union Square", city: "San Francisco", loc:[-122.407437, 37.787994]})
> db.locations.insert({name: "Duboce Park", city: "San Francisco", loc:[-122.433453, 37.769422]})
> db.locations.insert({name: "Parque Ibirapuera", city: "São Paulo",loc:[-46.657490, -23.586996]})
> db.locations.insert({name: "Big Ben", city: "London",loc:[-0.124575, 51.500705]})
> db.locations.insert({name: "Fischmarkt", city: "Hamburg",loc:[9.937740, 53.544527]})
> db.locations.insert({name: "Opera House", city: "Sydney",loc:[151.214993, -33.857767]})
Now you can query all locations with:
> db.locations.find()
Just as a sanity check: Everything west of Greenwich has a longitude < 0 and east > 0. Everything in the northern hemisphere has a latitude > 0 and in the southern hemisphere < 0 or as Wikipedia puts it:
In geography, latitude (φ) is a geographic coordinate that specifies the north-south position of a point on the Earth’s surface. Latitude is an angle (defined below) which ranges from 0° at the Equator to 90° (North or South) at the poles.
Longitude (/ˈlɒndʒɨtjuːd/ or /ˈlɒŋɡɨtjuːd/),[1] is a geographic coordinate that specifies the east-west position of a point on the Earth’s surface. […] The longitude of other places is measured as an angle east or west from the Prime Meridian, ranging from 0° at the Prime Meridian to +180° eastward and −180° westward.
Query Locations
After we have inserted some test locations let’s try to make a geospatial query. In my case I wanted to find locations close to the current location so let’s pretend we are at the Ferry Building in San Francisco.
> db.runCommand({geoNear: "locations", near: [-122.3937, 37.7955], num: 10, spherical:true})
The result is ordered by distance and the Ferry Building should be on top with a dis
of 0. To have the distance in a human readable unit you can use the distanceMultiplier attribute and select for instance the earth radius in miles which is 3959. This will show all distances in miles:
> db.runCommand({geoNear: "locations", near: [-122.3937, 37.7955], num: 10, spherical:true,
distanceMultiplier: 3959})
If you want to restrict the search to a radius like 10 miles you’ll have to use the maxDistance attribute:
> db.runCommand({geoNear: "locations", near: [-122.3937, 37.7955], num: 10, spherical:true,
distanceMultiplier: 3959, maxDistance:10/3959})
If you want kilometers use 6371m as the radius of the earth.
Now that everything is working in the shell we can start with actual code.
Node.js Code
This example is written with CoffeeScript and has been extracted from an actual project and usually you would do more validation and parsing but you should get the idea on how the Express route is working:
1 2 3 4 5 6 7 8 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
|