Note: This module requires a Redis server to be accessible to your Node environment.
This Node module provides everything you need to get proximity information for geo locations. More specifically:
It should be noted that the method used here is not the most precise, but the query is very fast, and should be appropriate for most consumer applications looking for this basic function. Read more about how this module works.
npm install geo-proximity
Usage of this module should be extremely simple. Just make sure that your Redis server is accessible to your Node environment. Because this module uses Redis as a store, almost all methods have integrated error handling for queries.
Include and initialize this module with a node-redis client instance.
var redis = require('redis'),
client = redis.createClient()
var proximity = require('geo-proximity').initialize(client)
Add locations individually:
proximity.addLocation(43.6667, -79.4167, 'Toronto', function(err, reply){
if(err) console.error(err)
else console.log('added location:', reply)
})
If you have a large set you'd like to add in bulk, there's a much quicker way:
var locations = [[43.6667, -79.4167, 'Toronto'],
[39.9523, -75.1638, 'Philadelphia'],
[37.4688, -122.1411, 'Palo Alto'],
[37.7691, -122.4449, 'San Francisco'],
[47.5500, -52.6667, 'St. John\'s'],
[40.7143, -74.0060, 'New York'],
[49.6500, -54.7500, 'Twillingate'],
[45.4167, -75.7000, 'Ottawa'],
[51.0833, -114.0833, 'Calgary'],
[18.9750, 72.8258, 'Mumbai']]
proximity.addLocations(locations, function(err, reply){
if(err) console.error(err)
else console.log('added locations:', reply)
})
proximity.location('Toronto', function(err, location){
if(err) console.error(err)
else console.log(location.name + "'s location is:", location.latitude, location.longitude)
})
Or for multiple locations:
proximity.locations(['Toronto', 'Philadelphia', 'Palo Alto', 'San Francisco', 'Ottawa'], function(err, locations){
if(err) console.error(err)
else {
for(var i = 0; i < locations.length; i++)
console.log(locations[i].name + "'s location is:", locations[i].latitude, locations[i].longitude)
}
})
Now you can look for locations that exist approximately within a certain distance of any particular coordinate in the system.
// look for all points within ~5000m of Toronto.
proximity.nearby(43.646838, -79.403723, 5000, function(err, locations){
if(err) console.error(err)
else console.log('nearby locations:', locations)
})
Of course you may need to remove some points from your set as users/temporary events/whatever no longer are part of the set.
proximity.removeLocation('New York', function(err, reply){
if(err) console.error(err)
else console.log('removed location:', reply)
})
// OR Quicker for Bulk Removals
proximity.removeLocations(['New York', 'St. John\'s', 'San Francisco'], function(err, reply){
if(err) console.error(err)
else console.log('removed locations', reply)
})
You can initialize geo-proximity
with a specific redis client instance, but you can also specify a ZSET name to use when storing/querying locations instead of the default geo:locations
. You may also enable an experimental caching feature that should help with performance, but will use additional memory.
var redis = require('redis'),
client = redis.createClient()
var proximity = require('geo-proximity').initialize(client, {
zset: 'mySpecialLocationsSet',
cache: true
})
If you have different sets of coordinates, you can store and query them separately by creating adding a new set.
var people = proximity.addSet('people')
var places = proximity.addSet('places')
var peopleLocations = [[43.6667,-79.4167, 'John'],
[39.9523, -75.1638, 'Shankar'],
[37.4688, -122.1411, 'Cynthia'],
[37.7691, -122.4449, 'Chen']]
var placeLocations = [[43.6667,-79.4167, 'Toronto'],
[39.9523, -75.1638, 'Philadelphia'],
[37.4688, -122.1411, 'Palo Alto'],
[37.7691, -122.4449, 'San Francisco'],
[47.5500, -52.6667, 'St. John\'s']]
people.addLocations(peopleLocations, function(err, reply){
if(err) console.error(err)
else console.log('added people:', reply)
})
places.addLocations(placeLocations, function(err, reply){
if(err) console.error(err)
else console.log('added places:', reply)
})
// will find all PEOPLE ~5000m from the passed in coordinate
people.nearby(43.646838, -79.403723, 5000, function(err, people){
if(err) console.error(err)
else console.log('people nearby:', people)
})
// will find all PLACES ~5000m from the passed in coordinate
places.nearby(43.646838, -79.403723, 5000, function(err, places){
if(err) console.error(err)
else console.log('places nearby:', places)
})
If you no longer need one of your newly created sets, you can just delete it. Either of the following methods will remove the set from redis and destroy its contents. If you add locations to that set again it will recreate the set on redis and you can use as usual.
// will delete the people set and its contents
people.delete()
// OR
proximity.deleteSet('people')
If you intend on performing the same query over and over again with the same initial coordinate and the same distance, you can cache the geohash ranges that are used to search for nearby locations. Use the proximity.getQueryCache and proximity.nearbyWithQueryCache methods together in order to do this.
The geohash ranges are what the proximity.nearby method ultimately searches within to find nearby points. So keeping these stored in a variable some place and passing them into a more basic search function will save some cycles (at least 5ms on a basic machine). This will save you quite a bit of processing time if you expect to refresh your searches often, and especially if you expect to have empty results often. Your processor is probably best used for other things.
var cachedQuery = proximity.getQueryCache(37.4688, -122.1411, 5000)
proximity.nearbyWithQueryCache(cachedQuery, function(err, replies){
console.log('results to the query:', replies)
})
Similar to the above method of increasing performance, you can use browserify and use this module in clients. The only method a client will have access to is the getQueryCache
method. This way, your clients can take on the computational load of generating the geohash ranges to query within.
No need to initialize the module to use it on the browser/client side, just do a regular require.
var proximity = require('geo-proximity')
var cachedQuery = proximity.getQueryCache(37.4688, -122.1411, 5000)
Pass the cachedQuery
along to the server (using http or socket.io or anything) to use with the nearbyWithQueryCache
method and send back the results.
Initialize the module with a redis client.
zset
String: Default geo:locations
. Set this option to specify a zset name to use to store location values.cache
Boolean: Default false
. The module can cache queries to increase the speed of future queries that are similar. However, this can end up taking a bit of memory, and might not be necessary if you don't need to repeat queries.var proximity = require('geo-proximity').initialize(client, {
zset: 'locations',
cache: false
})
This method will return a subset that can be queried and hold a unique set of locations from the main set. It will store these new locations in a new redis zset with a unique name related to the parent set (eg. geo:locations:people
).
This method will delete a subset and its contents. You should use the callBack to check for errors or to wait for confirmation that the set is deleted, but this is probably not necessary.
Add a new coordinate to your set.
Adds an array of new coordinates to your set. The coordinateArray
must be in the form [[lat, lon, name],[lat, lon, name],...,[lat, lon, name]]
. Use this method for bulk additions, as it is much faster than individual adds.
Update a coordinate to your set.
Same syntax as addLocations
. Updates all locations passed.
Retrieve the latitude and longitude of a specific named location. Returns an object with name
, latitude
and longitude
properties. latitude
and longitude
will be null if the location does not exist.
Retrieve the latitude and longitude of a list of specific named locations. Returns an array of objects with name
, latitude
and longitude
properties. latitude
and longitude
will be null if the location does not exist.
Remove the specified coordinate by name.
Remove a set of coordinates by name. coordinateNameArray
must be of the form [nameA,nameB,nameC,...,nameN]
.
Removes all locations and deletes the zSet from Redis. You should use the callBack to check for errors or to wait for confirmation that the set is deleted, but this is probably not necessary.
Use this function for a basic search by proximity within the given latitude and longitude and approximate distance (in meters). It is not ideal to use this method if you intend on making the same query multiple times. If performance is important and you'll be making the same query over and over again, it is recommended you instead have a look at proximity.nearbyWithQueryCache and promixity.getQueryCache. Otherwise this is an easy method to use.
values
Boolean: Default false
. Instead of returning a flat array of key names, it will instead return a full set of keynames with coordinates in the form of [[name, lat, lon], [name, lat, lon]...]
.This will be a slower query compared to just returning the keynames because the coordinates need to be calculated from the stored geohashes.Get the query ranges to use with proximity.nearbyWithQueryCache. This returns an array of geohash ranges to search your set for. bitDepth
is optional and defaults to 52, set it if you have chosen to store your coordinates at a different bit depth. Store the return value of this function for making the same query often.
Pass in query ranges returned by proximity.getQueryRangesFromRadius to find points that fall within your range value.
values
Boolean: Default false
. Instead of returning a flat array of key names, it will instead return a full set of keynames with coordinates in the form of [[name, lat, lon], [name, lat, lon]...]
.This will be a slower query compared to just returning the keynames because the coordinates need to be calculated from the stored geohashes.The MIT License (MIT)
Copyright (c) 2015 Arjun Mehta