UNPKG

georedis

Version:
350 lines (279 loc) 9.25 kB
var geohash = require('ngeohash'); var geolib = require('geolib'); var helper = require('./util/helper'); var query = require('./query'); var range = require('./range'); var convertUnitsFromMeters = helper.convertUnitsFromMeters; var convertUnitsToMeters = helper.convertUnitsToMeters; var buildObjectFromKeyedArray = helper.buildObjectFromKeyedArray; var buildObjectFromStringArray = helper.buildObjectFromStringArray; /* * Emulated interface constructor * This interface emulates the native redis geo commands */ function EmulatedInterface(client) { this.client = client; } // Emulated Geo Commands EmulatedInterface.prototype.geoadd = function(locationName, point, zSetName, callBack) { this.client.zadd(zSetName, geohash.encode_int(point.latitude, point.longitude, 52), locationName, callBack); }; EmulatedInterface.prototype.geoadd_multi = function(locationSet, zSetName, callBack) { var argArray = [zSetName]; var locationName; var location; for (locationName in locationSet) { location = locationSet[locationName]; argArray.push(geohash.encode_int(location.latitude, location.longitude, 52), locationName); } this.client.zadd(argArray, callBack); }; EmulatedInterface.prototype.geodist = function(locationA, locationB, units, zSetName, callBack) { var client = this.client; var multi = client.multi(); var pointA; var pointB; var distance; multi.zscore(zSetName, locationA); multi.zscore(zSetName, locationB); multi.exec(function(err, replies) { if (err) { callBack(err, null); } else { replies = helper.normalizeMultiResponse(client, replies); pointA = geohashDecode(replies[0]); pointB = geohashDecode(replies[1]); distance = geolib.getDistance(pointA, pointB, 1); distance = convertUnitsFromMeters(units, distance); callBack(null, distance); } }); }; EmulatedInterface.prototype.geohash = function(member, zSetName, callBack) { var point; var hash; this.client.zscore(zSetName, member, function(err, result) { if (err) { callBack(err, null); } else { if (result !== null) { point = geohashDecode(result); hash = geohash.encode(point.latitude, point.longitude); } else { hash = null; } callBack(null, hash); } }); }; EmulatedInterface.prototype.geohashes = function(members, zSetName, callBack) { var client = this.client; var multi = client.multi(); var locationSet = {}; var point; var i; for (i = 0; i < members.length; i++) { multi.zscore(zSetName, members[i]); } multi.exec(function(err, replies) { if (err) { callBack(err, null); } else { replies = helper.normalizeMultiResponse(client, replies); for (i = 0; i < replies.length; i++) { if (replies[i] !== null) { point = geohashDecode(replies[i]); locationSet[members[i]] = geohash.encode(point.latitude, point.longitude); } else { locationSet[members[i]] = null; } } callBack(null, locationSet); } }); }; EmulatedInterface.prototype.geopos = function(member, zSetName, callBack) { var location; this.client.zscore(zSetName, member, function(err, result) { if (err) { callBack(err, null); } else { if (result !== null) { location = geohashDecode(result); } else { location = null; } callBack(null, location); } }); }; EmulatedInterface.prototype.geopos_multi = function(members, zSetName, callBack) { var client = this.client; var multi = client.multi(); var locationSet = {}; var i; for (i = 0; i < members.length; i++) { multi.zscore(zSetName, members[i]); } multi.exec(function(err, replies) { if (err) { callBack(err, null); } else { replies = helper.normalizeMultiResponse(client, replies); for (i = 0; i < replies.length; i++) { if (replies[i] !== null) { locationSet[members[i]] = geohashDecode(replies[i]); } else { locationSet[members[i]] = null; } } callBack(null, locationSet); } }); }; EmulatedInterface.prototype.georadius = function(point, radius, options, zSetName, callBack) { options.accurate = true; this.nearby(point, radius, options, zSetName, callBack); }; EmulatedInterface.prototype.georadiusbymember = function(member, radius, options, zSetName, callBack) { options.accurate = true; this.nearbymember(member, radius, options, zSetName, callBack); }; EmulatedInterface.prototype.nearby = function(point, distance, options, zSetName, callBack) { var ranges; var count = options.count; var units = options.units || 'm'; var accurate = options.accurate; var withValues = (accurate || options.withDistances || options.withCoordinates || options.withHashes || options.order); distance = convertUnitsToMeters(units, distance); ranges = range(point.latitude, point.longitude, distance, accurate); query(this.client, zSetName, ranges, withValues, function(err, locations) { if (!err) { if (withValues === true) { locations = processLocations(locations, point, distance, options, callBack); } else { if (typeof count === 'number') { locations = locations.slice(0, count); } Object.defineProperty(locations, 'locationSet', { get: function() { return buildObjectFromStringArray(this); } }); } callBack(null, locations); } else { callBack(err, null); } }); }; EmulatedInterface.prototype.nearbymember = function(locationName, distance, options, zSetName, callBack) { var self = this; this.geopos([locationName], zSetName, function(err, location) { if (!err) { if (location === null) { callBack(new Error('ERR could not decode requested zset member'), null); } else { self.nearby(location, distance, options, zSetName, callBack); } } else { callBack(err, null); } }); }; // Native Ordered Set Commands EmulatedInterface.prototype.del = function(zSetName, callBack) { this.client.del(zSetName, callBack); }; EmulatedInterface.prototype.zrem = function(locations, zSetName, callBack) { var argsArray = [zSetName].concat(locations); this.client.zrem(argsArray, callBack); }; function processLocations(locationSetOriginal, point, queryDistance, options, callBack) { var locations = []; var units = options.units || 'm'; var withDistances = options.withDistances; var withCoordinates = options.withCoordinates; var withHashes = options.withHashes; var order = options.order; var count = options.count; var accurate = options.accurate; var locationName; var location; var distance; if (order) { locations = orderResults(locationSetOriginal, point, queryDistance, withDistances, withCoordinates, withHashes, order, accurate, units); } else { for (locationName in locationSetOriginal) { location = locationSetOriginal[locationName]; if (accurate === true) { distance = geolib.getDistance(point, location, 1); if (distance > queryDistance) { continue; } location.distance = convertUnitsFromMeters(units, distance); } else if (withDistances === true) { distance = geolib.getDistance(point, location, 1); location.distance = convertUnitsFromMeters(units, distance); } if (withHashes === true) { location.hash = geohash.encode_int(location.latitude, location.longitude, 52); } location.key = locationName; locations.push(location); } } if (typeof count === 'number') { locations = locations.slice(0, count); } Object.defineProperty(locations, 'locationSet', { get: function() { return buildObjectFromKeyedArray(this); } }); return locations; } function orderResults(locationSetOriginal, point, queryDistance, withDistances, withCoordinates, withHashes, order, accurate, units) { var orderedLocations = geolib.orderByDistance(point, locationSetOriginal); var location; var i; if (accurate) { for (i = orderedLocations.length - 1; i > -1; i--) { if (orderedLocations[i].distance > queryDistance) { orderedLocations = orderedLocations.slice(0, i); break; } } } if (withHashes) { for (i = 0; i < orderedLocations.length; i++) { location = orderedLocations[i]; location.hash = geohash.encode_int(location.latitude, location.longitude, 52); } } if (units !== 'm') { for (i = 0; i < orderedLocations.length; i++) { location = orderedLocations[i]; location.distance = convertUnitsFromMeters(units, location.distance); } } if (order === 'DESC') { orderedLocations = orderedLocations.reverse(); } return orderedLocations; } function geohashDecode(hash) { var decoded; var latlon; if (typeof hash === 'string' || typeof hash === 'number') { latlon = geohash.decode_int(hash, 52); decoded = { latitude: latlon.latitude, longitude: latlon.longitude }; } else { decoded = null; } return decoded; } module.exports = EmulatedInterface;