ridematcher
Version:
Find carpool/vanpool matches
183 lines (145 loc) • 6.28 kB
JavaScript
;
var _Promise = require('babel-runtime/core-js/promise')['default'];
var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
Object.defineProperty(exports, '__esModule', {
value: true
});
exports.findMatches = findMatches;
exports.findRidepoolMatches = findRidepoolMatches;
var _rbush = require('rbush');
var _rbush2 = _interopRequireDefault(_rbush);
var _turfDestination = require('turf-destination');
var _turfDestination2 = _interopRequireDefault(_turfDestination);
var _turfDistance = require('turf-distance');
var _turfDistance2 = _interopRequireDefault(_turfDistance);
var _turfPoint = require('turf-point');
var _turfPoint2 = _interopRequireDefault(_turfPoint);
/**
* Compute the number of potential carpool matches within a commuter population.
*
* @param {Array} commuters Array of commuters to match to each other
* @param {Object} opts Options object
* @returns {Promise} promise
* @example
* import {findMatches} from 'ridematcher'
* findMatches({
* commuters: [{
* _id: 1,
* from: [-77.4875, 39.0436],
* to: [..]
* }], {
* radius: .5,
* units: 'miles'
* }}).then((matches) => {
* console.log(matches) // map of commuter id's to matching commuter id's
* }, handleError)
*/
function findMatches(commuters) {
var opts = arguments[1] === undefined ? {} : arguments[1];
return new _Promise(function (resolve, reject) {
if (!commuters) return reject('No commuters.');
var tree = (0, _rbush2['default'])();
tree.load(commuters.map(function (c) {
var to = c.to || c.from;
return [c.from[0], c.from[1], to[0], to[1], c];
}));
var responses = {};
var RADIUS = opts.radius || 0.25;
var DIST = RADIUS * Math.sqrt(2);
var UNITS = opts.units || 'miles';
commuters.forEach(function (commuter) {
var fromPoint = (0, _turfPoint2['default'])(commuter.from);
// construct bbox
var bottomLeft = (0, _turfDestination2['default'])(fromPoint, DIST, -135, UNITS);
var topRight = (0, _turfDestination2['default'])(fromPoint, DIST, 45, UNITS);
// do the initial bbox search
var results = tree.search(bottomLeft.geometry.coordinates.concat(topRight.geometry.coordinates));
// filter the matches
var matches = results.reduce(function (matches, result) {
var match = result[4]._id;
var matchPoint = (0, _turfPoint2['default'])([result[0], result[1]]);
// ignore self match
if (match === commuter._id) return matches;
// ignore matches where distance exceeds search radius
var distance = (0, _turfDistance2['default'])(fromPoint, matchPoint, UNITS);
if (distance > RADIUS) return matches;
matches.push({
_id: match,
distance: distance
});
return matches;
}, []);
responses[commuter._id] = matches;
});
resolve(responses);
});
}
/**
* Find matches for a single commute against a set of car/vanpools
*
* @param {Array} from Search origin lng/lat
* @param {Array} to Search destination lng/lat
* @param {Array} ridepools Array of car/vanpools to match against
* @param {Object} opts Options object
* @returns {Promise} promise
* @example
* import {findRidepoolMatches} from 'ridematcher'
* findMatches(
* from: [-77.4875, 39.0436],
* to: [..],
* ridepools: [{
* _id: 1,
* from: [-77.4875, 39.0436],
* to: [..]
* }], {
* radius: .5,
* units: 'miles'
* }}).then((matches) => {
* console.log(matches) // map of commuter id's to matching commuter id's
* }, handleError)
*/
function findRidepoolMatches(from, to, ridepools) {
var opts = arguments[3] === undefined ? {} : arguments[3];
return new _Promise(function (resolve, reject) {
if (!ridepools) return reject('No ridepools.');
var fromTree = (0, _rbush2['default'])();
fromTree.load(ridepools.map(function (r) {
return [r.from[0], r.from[1], r.from[0], r.from[1], r];
}));
var toTree = (0, _rbush2['default'])();
toTree.load(ridepools.map(function (r) {
return [r.to[0], r.to[1], r.to[0], r.to[1], r];
}));
var responses = {};
var RADIUS = opts.radius || 0.25;
var DIST = RADIUS * Math.sqrt(2);
var UNITS = opts.units || 'miles';
var fromPoint = (0, _turfPoint2['default'])(from);
var toPoint = (0, _turfPoint2['default'])(to);
// find the pools matching the 'from' search point
var fromBottomLeft = (0, _turfDestination2['default'])(fromPoint, DIST, -135, UNITS);
var fromTopRight = (0, _turfDestination2['default'])(fromPoint, DIST, 45, UNITS);
var s = fromBottomLeft.geometry.coordinates.concat(fromTopRight.geometry.coordinates);
var fromMatches = fromTree.search(s);
fromMatches = fromMatches.filter(function (match) {
var distance = (0, _turfDistance2['default'])(fromPoint, (0, _turfPoint2['default'])([match[0], match[1]]), UNITS);
return distance <= RADIUS;
}).map(function (match) {
return match[4];
});
// find the pools matching the 'to' search point
var toBottomLeft = (0, _turfDestination2['default'])(toPoint, DIST, -135, UNITS);
var toTopRight = (0, _turfDestination2['default'])(toPoint, DIST, 45, UNITS);
var toMatches = toTree.search(toBottomLeft.geometry.coordinates.concat(toTopRight.geometry.coordinates));
toMatches = toMatches.filter(function (match) {
var distance = (0, _turfDistance2['default'])(toPoint, (0, _turfPoint2['default'])([match[0], match[1]]), UNITS);
return distance <= RADIUS;
}).map(function (match) {
return match[4];
});
// return the intersection of the two match sets
resolve(fromMatches.filter(function (n) {
return toMatches.indexOf(n) != -1;
}));
});
}