Location Filtering in Node Using MongoDB Geospatial Queries

Shivang Kar
Fasal Engineering
Published in
4 min readApr 29, 2021

In today’s fast-moving world, where most of the services are getting digitized; as a software engineer it is very likely to come across a scenario where we have a chunk of data in the format of address and we need to figure out which is the nearest one or to filter it out based on a specific field. For instance, working on a food delivery application where a user selects Pizza, and all the pizza stores within a particular radius lights up!

Image by Pexels from Pixabay

One might think of purely using Map APIs to implement this, but that would be very costly and not a scalable option! That’s where MongoDb’s GeoSpatial Queries comes into the picture. Geospatial queries are operations on geospatial data. Using it, we can filter out locations based on our query. But before we do that, we need to set up a few things.

Assuming we have a lot of addresses of different types of entities stored in the MongoDB, such as the below record which contains the address of a Pizza store:

The first step would be to assign them a proper Lat — Long.

Converting address strings to Lat - Long using node-geocoder

In order to convert locations stored in the form of strings, we can use node-geocoder package. It allows us to use different map providers to achieve that.

If there are a lot of addresses(which should be common for most of the use cases), it is a better option to divide them into smaller chunks of 100 or 200 records first; since most of the Map providers will fail your requests if you pass too many addresses in a single request!

const addressChunks = lodash.chunk(addresses, 200);

Configure geocoder by defining options according to your use case

const NodeGeocoder = require('node-geocoder');const options = {
provider: 'google',
httpAdapter: 'https',
apiKey: 'YOUR_KEY', // for Google Premier, OpenCage, Mapquest
formatter: null
};
const geocoder = NodeGeocoder(options);

Using batchGeocode method to get multiple locations at once for our chunks

geocoder.batchGeocode(addresses, function (error, response) {
if (error) {
// Handle Error
} else if (response) {
// Loop over each record of response, and add the following
// location object to your respective addresses:
const location = {
type: 'Point',
coordinates: [response[iterator].value[0].longitude,
response[iterator].value[0].latitude]
};
}
});

By using the above method in a loop we can get the location for all our chunks.

Note: If you are making multiple batchGeocode calls, and your above request fails, then you will need to add a delay of few seconds before each batchGeocode call is made!

Once this is finished, our record of Pizza store should look something like this:

Note: Make sure your coordinates array has longitude on 0th index and latitude on 1st!

Filtering location based on preferences

Since all our records have lat-long assigned to them, all there is left is to filter out the results. But before that, we need to create 2dsphere Indexes like this:

db.storeLocations.createIndex({ "location": "2dsphere" })

A 2dsphere Index supports queries that calculate geometries on an earth-like sphere. This is a one-time command, that you need to run before your first find query.

Now, let’s say we want to find all the pizza stores within a 5km radius of the user’s coordinates, that have a rating of 4 stars or more. We can simply achieve that like this:

storeLocations
.find({
foodType: 'PIZZA',
shopRating: {$gte: 4},
location: {
$near: {
$geometry: { type: "Point",
coordinates: [longitude,latitude] //In float
},
$minDistance: 0,
$maxDistance: 5000, //In meters
},
},
});

In the above query, $near specifies a point for which, the query returns the documents from nearest to farthest location. Using $minDistance & $maxDistance we can define the range of distance in which we want to search. You can refer to other useful GeoSpatial operators of MongoDB here.

Since we are now using MongoDB to filter our locations, this saves us from the cost of having to make calls to google or apple’s map APIs! Of course, we will need to make an initial call to convert addresses to lat-long, but that is just a one-time setup. Once you are done with that, you can simply modify your query based on your requirements and get the intended results.

Also read —

--

--

Shivang Kar
Fasal Engineering

I’m a Lead Product Engineer at Fasal, where I focus on building Tech/Product that would help farmers in increasing yield, optimize resource utilization etc.