|
|
|
@ -35,19 +35,42 @@ var Server = grpc.buildServer([examples.RouteGuide.service]); |
|
|
|
|
|
|
|
|
|
var COORD_FACTOR = 1e7; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* For simplicity, a point is a record type that looks like |
|
|
|
|
* {latitude: number, longitude: number}, and a feature is a record type that |
|
|
|
|
* looks like {name: string, location: point}. feature objects with name==='' are |
|
|
|
|
* points with no feature. |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* List of feature objects at points that have been requested so far. |
|
|
|
|
*/ |
|
|
|
|
var feature_list = []; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Return a random "word" (alphabetic character sequence) of the given length. |
|
|
|
|
* @param {number} length The length of the word to create |
|
|
|
|
* @return {string} An alphabetic string with the given length. |
|
|
|
|
*/ |
|
|
|
|
function randomWord(length) { |
|
|
|
|
var alphabet = 'abcdefghijklmnopqrstuvwxyz'; |
|
|
|
|
var word = ''; |
|
|
|
|
for (var i = 0; i < length; i++) { |
|
|
|
|
// Add a random character from the alphabet to the word
|
|
|
|
|
word += alphabet[_.random(0, alphabet.length - 1)]; |
|
|
|
|
} |
|
|
|
|
return word; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Get a feature object at the given point, or creates one if it does not exist. |
|
|
|
|
* @param {point} point The point to check |
|
|
|
|
* @return {feature} The feature object at the point. Note that an empty name |
|
|
|
|
* indicates no feature |
|
|
|
|
*/ |
|
|
|
|
function checkFeature(point) { |
|
|
|
|
var feature; |
|
|
|
|
// Check if there is already a feature object for the given point
|
|
|
|
|
for (var i = 0; i < feature_list.length; i++) { |
|
|
|
|
feature = feature_list[i]; |
|
|
|
|
if (feature.point.latitude === point.latitude && |
|
|
|
@ -55,6 +78,7 @@ function checkFeature(point) { |
|
|
|
|
return feature; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// If not, create a new one with 50% chance of indicating "no feature present"
|
|
|
|
|
var name; |
|
|
|
|
if (_.random(0,1) === 0) { |
|
|
|
|
name = ''; |
|
|
|
@ -65,14 +89,27 @@ function checkFeature(point) { |
|
|
|
|
name: name, |
|
|
|
|
location: point |
|
|
|
|
}; |
|
|
|
|
// Add the feature object to the list and return it
|
|
|
|
|
feature_list.push(feature); |
|
|
|
|
return feature; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* getFeature request handler. Gets a request with a point, and responds with a |
|
|
|
|
* feature object indicating whether there is a feature at that point. |
|
|
|
|
* @param {EventEmitter} call Call object for the handler to process |
|
|
|
|
* @param {function(Error, feature)} callback Response callback |
|
|
|
|
*/ |
|
|
|
|
function getFeature(call, callback) { |
|
|
|
|
callback(null, checkFeature(call.request)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* listFeatures request handler. Gets a request with two points, and responds |
|
|
|
|
* with a stream of all features in the bounding box defined by those points. |
|
|
|
|
* @param {Writable} call Writable stream for responses with an additional |
|
|
|
|
* request property for the request value. |
|
|
|
|
*/ |
|
|
|
|
function listFeatures(call) { |
|
|
|
|
var lo = call.request.lo; |
|
|
|
|
var hi = call.request.hi; |
|
|
|
@ -80,7 +117,11 @@ function listFeatures(call) { |
|
|
|
|
var right = _.max(lo.longitude, hi.longitude); |
|
|
|
|
var top = _.max(lo.latitude, hi.latitude); |
|
|
|
|
var bottom = _.max(lo.latitude, hi.latitude); |
|
|
|
|
// For each feature, check if it is in the given bounding box
|
|
|
|
|
_.each(feature_list, function(feature) { |
|
|
|
|
if (feature.name === '') { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (feature.location.longitude >= left && |
|
|
|
|
feature.location.longitude <= right && |
|
|
|
|
feature.location.latitude >= bottom && |
|
|
|
@ -117,17 +158,28 @@ function getDistance(start, end) { |
|
|
|
|
return R * c; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* recordRoute handler. Gets a stream of points, and responds with statistics |
|
|
|
|
* about the "trip": number of points, number of known features visited, total |
|
|
|
|
* distance traveled, and total time spent. |
|
|
|
|
* @param {Readable} call The request point stream. |
|
|
|
|
* @param {function(Error, routeSummary)} callback The callback to pass the |
|
|
|
|
* response to |
|
|
|
|
*/ |
|
|
|
|
function recordRoute(call, callback) { |
|
|
|
|
var point_count = 0; |
|
|
|
|
var feature_count = 0; |
|
|
|
|
var distance = 0; |
|
|
|
|
var previous = null; |
|
|
|
|
// Start a timer
|
|
|
|
|
var start_time = process.hrtime(); |
|
|
|
|
call.on('data', function(point) { |
|
|
|
|
point_count += 1; |
|
|
|
|
if (checkFeature(point) !== '') { |
|
|
|
|
if (checkFeature(point).name !== '') { |
|
|
|
|
feature_count += 1; |
|
|
|
|
} |
|
|
|
|
/* For each point after the first, add the incremental distance from the |
|
|
|
|
* previous point to the total distance value */ |
|
|
|
|
if (previous != null) { |
|
|
|
|
distance += getDistance(previous, point); |
|
|
|
|
} |
|
|
|
@ -137,7 +189,9 @@ function recordRoute(call, callback) { |
|
|
|
|
callback(null, { |
|
|
|
|
point_count: point_count, |
|
|
|
|
feature_count: feature_count, |
|
|
|
|
// Cast the distance to an integer
|
|
|
|
|
distance: distance|0, |
|
|
|
|
// End the timer
|
|
|
|
|
elapsed_time: process.hrtime(start_time)[0] |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
@ -145,13 +199,25 @@ function recordRoute(call, callback) { |
|
|
|
|
|
|
|
|
|
var route_notes = {}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Turn the point into a dictionary key. |
|
|
|
|
* @param {point} point The point to use |
|
|
|
|
* @return {string} The key for an object |
|
|
|
|
*/ |
|
|
|
|
function pointKey(point) { |
|
|
|
|
return point.latitude + ' ' + point.longitude; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function routeChat(call, callback) { |
|
|
|
|
/** |
|
|
|
|
* routeChat handler. Receives a stream of message/location pairs, and responds |
|
|
|
|
* with a stream of all previous messages at each of those locations. |
|
|
|
|
* @param {Duplex} call The stream for incoming and outgoing messages |
|
|
|
|
*/ |
|
|
|
|
function routeChat(call) { |
|
|
|
|
call.on('data', function(note) { |
|
|
|
|
var key = pointKey(note.location); |
|
|
|
|
/* For each note sent, respond with all previous notes that correspond to |
|
|
|
|
* the same point */ |
|
|
|
|
if (route_notes.hasOwnProperty(key)) { |
|
|
|
|
_.each(route_notes[key], function(note) { |
|
|
|
|
call.write(note); |
|
|
|
@ -159,6 +225,7 @@ function routeChat(call, callback) { |
|
|
|
|
} else { |
|
|
|
|
route_notes[key] = []; |
|
|
|
|
} |
|
|
|
|
// Then add the new note to the list
|
|
|
|
|
route_notes[key].push(note); |
|
|
|
|
}); |
|
|
|
|
call.on('end', function() { |
|
|
|
@ -166,6 +233,11 @@ function routeChat(call, callback) { |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Get a new server with the handler functions in this file bound to the methods |
|
|
|
|
* it serves. |
|
|
|
|
* @return {Server} The new server object |
|
|
|
|
*/ |
|
|
|
|
function getServer() { |
|
|
|
|
return new Server({ |
|
|
|
|
'examples.RouteGuide' : { |
|
|
|
@ -178,6 +250,7 @@ function getServer() { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (require.main === module) { |
|
|
|
|
// If this is run as a script, start a server on an unused port
|
|
|
|
|
var routeServer = getServer(); |
|
|
|
|
routeServer.bind('0.0.0.0:0'); |
|
|
|
|
routeServer.listen(); |
|
|
|
|