Query locations within a radius in MongoDB
It's a 3 step process.
- Step 1) Embed a GeoJSON Point within your documents.
- Step 2) Index the path to the points, using
2dsphere
. - Step 3) Query the points in the documents using
$geoWithin
and$centerSphere
.
To perform geospatial queries, you need to change the document structure to match a GeoJSON Point. Which looks like this.
loc : {
type : "Point",
coordinates : [lng, lat]
}
Sample code for translating your collection to the Point format.
// sample setup code.
// use test;
// db.positions.drop();
// db.positions.insert({
// pos : {
// lat : 0,
// lon : 30
// }
// });
db.positions.find().forEach(function (doc) {
var point = {
_id : doc._id,
loc : {
type : "Point",
coordinates : [doc.pos.lon, doc.pos.lat]
}
};
db.positions.update(doc, point);
});
db.positions.find().pretty();
Afterwards, you can use $geoWithin
and $near
operators in your queries like the example below.
Example
Setup
var createLandmarkDoc = function (name, lng, lat) {
return {
name : name,
loc : {
type : "Point",
coordinates : [lng, lat]
}
};
};
var addNewLandmark = function(name, lng, lat){
db.landmarks.insert(createLandmarkDoc(name, lng, lat));
};
db.landmarks.drop();
// Step 1: Add points.
addNewLandmark("Washington DC", 38.8993487, -77.0145665);
addNewLandmark("White House", 38.9024593, -77.0388266);
addNewLandmark("Library of Congress", 38.888684, -77.0047189);
addNewLandmark("Patuxent Research Refuge", 39.0391718, -76.8216182);
addNewLandmark("The Pentagon", 38.871857, -77.056267);
addNewLandmark("Massachusetts Institute of Technology", 42.360091, -71.09416);
// Step 2: Create index
db.landmarks.ensureIndex({
loc : "2dsphere"
});
Query: Find landmarks within 5 miles.
var milesToRadian = function(miles){
var earthRadiusInMiles = 3959;
return miles / earthRadiusInMiles;
};
var landmark = db.landmarks.findOne({name: "Washington DC"});
var query = {
"loc" : {
$geoWithin : {
$centerSphere : [landmark.loc.coordinates, milesToRadian(5) ]
}
}
};
// Step 3: Query points.
db.landmarks.find(query).pretty();
Output
{
"_id" : ObjectId("540e70c96033ed0d2d9694fa"),
"name" : "Washington DC",
"loc" : {
"type" : "Point",
"coordinates" : [
38.8993487,
-77.0145665
]
}
}
{
"_id" : ObjectId("540e70c96033ed0d2d9694fc"),
"name" : "Library of Congress",
"loc" : {
"type" : "Point",
"coordinates" : [
38.888684,
-77.0047189
]
}
}
{
"_id" : ObjectId("540e70c96033ed0d2d9694fb"),
"name" : "White House",
"loc" : {
"type" : "Point",
"coordinates" : [
38.9024593,
-77.0388266
]
}
}
{
"_id" : ObjectId("540e70c96033ed0d2d9694fe"),
"name" : "The Pentagon",
"loc" : {
"type" : "Point",
"coordinates" : [
38.871857,
-77.056267
]
}
}
More Info:
- 2dsphere
- 2dsphere Index
- $centerSphere
- Geospatial Query Operators
Step by step radial location querying with MongoDB
MongoDB is great for computing geolocation using geospatial data
Basicaly you use `$geoWithin:` with `$centerSphere:` on collection with **2dsphere** index. But lets go step by step.Scenario: Lets pretend a user is browsing hotels in his area... He want to display hotels that are in 50 mile range from his current location and your database app is full of hotels data. You would probably would use some MAP API to display them on the map (if that's what you after be sure to check out links at the bottom) but for this example we just just want to fetch hotels data with in 50 miles range from hotels location in your MongoDB database.
First create test document collection 'hotels'
* You might want to edit
coordinates
to those matching your nearby locations if you want this code to work without any change in radius distance.
// Your 'hotels' collection could look something like this:
db.hotels.insertMany([
{
'name': 'Golebiewski',
'address': 'Poland, Mikolajki',
'location': { type: "Point", coordinates: [ 40, 5.000001 ] },
'tel': '000 000 000'
},
{
'name': 'Baltazar',
'address': 'Poland, Pultusk 36',
'location': { type: "Point", coordinates: [ 40, 5.000002 ] },
'tel': '000 000 000'
},
{
'name': 'Zamek',
'address': 'Poland, Pultusk',
'location': { type: "Point", coordinates: [ 40, 5.000003 ] },
'tel': '000 000 000'
},
])
Then index location
with 2dsphere
db.places.createIndex( { location : "2dsphere" } )
Now query based on miles range and your location.
Get your tools ready first:// custom function - convert miles to radian
let milesToRadian = function(miles){
var earthRadiusInMiles = 3963;
return miles / earthRadiusInMiles;
};
// or custom function - convert km to radian
let kmToRadian = function(miles){
var earthRadiusInMiles = 6378;
return miles / earthRadiusInMiles;
};
// custom function - returns array with current user geolocation points - [latitude, longitude]
function getUserLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (position) {
let geoPoints = [position.coords.latitude, position.coords.longitude];
console.log(geoPoints);
return geoPoints;
});
} else {
console.log("Geolocation is not supported by this browser.");
}
}
Now use your new tools to create query for MongoDB geospatial data.
var query = {'location' : {$geoWithin: { $centerSphere: getUserLocation(), milesToRadian(50) ]}}}
// MongoDB example query could look something like:
// {'location': {$geoWithin: { $centerSphere: [ [ 40.000000001, 5.000000001 ], 0.012616704516780217 ]}}}
db.hotels.find(query);
MongoDB returns list of documents (hotels) with in 50 miles range from user location. Just remember that your document need to implement GeoJSON structure for storing location and it needs to be index 2dsphere. Result will be sorted by distance from user - ASC.
Output
{ "_id" : ObjectId("5d3130f810050424d26836d6"), "name" : "Golebiewski", "address" : "Poland, Mikolajki", "location" : { "type" : "Point", "coordinates" : [ 40, 5.000001 ] }, "tel" : "000 000 000" }
{ "_id" : ObjectId("5d3130f810050424d26836d7"), "name" : "Baltazar", "address" : "Poland, Pultusk 36", "location" : { "type" : "Point", "coordinates" : [ 40, 5.000002 ] }, "tel" : "000 000 000" }
{ "_id" : ObjectId("5d3130f810050424d26836d8"), "name" : "Zamek", "address" : "Poland, Pultusk", "location" : { "type" : "Point", "coordinates" : [ 40, 5.000003 ] }, "tel" : "000 000 000" }
Watch / Read more
MongoDB - geospatial queries: https://docs.mongodb.com/manual/geospatial-queries/ https://www.youtube.com/watch?v=3xP-gzh2ck0Go further!
Get your data on to the map. Here you have some really cool tutorials for that.Open Street Map with MapBox API: https://docs.mapbox.com/help/tutorials/building-a-store-locator/
Google Maps with Google JavaScript API: https://developers.google.com/maps/documentation/javascript/marker-clustering
If you want to convert address to geocode points, you can use Nominatim gui or Nominatim API
For example search for "Poland Pultusk" in browser or request a json response by adding to a string parameter &format=json
https://nominatim.openstreetmap.org/search.php?q=Poland+Pultusk https://nominatim.openstreetmap.org/search.php?q=Poland+Pultusk&format=json
Ps. MapBox is free up to 50k loads per month then $5 / 1000 API calls. Google gives you $200 MONTHLY CREDIT FREE USAGE which comes to 28k free API calls then 7$ / 1000 api calls. Pick your weapon and have fun :>.
Tools: MongoDB Compass (*not the community edition) https://www.mongodb.com/blog/post/visualizing-your-data-with-mongodb-compass