| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 | // Copyright 2015, Google Inc.// All rights reserved.//// Redistribution and use in source and binary forms, with or without// modification, are permitted provided that the following conditions are// met:////     * Redistributions of source code must retain the above copyright// notice, this list of conditions and the following disclaimer.//     * Redistributions in binary form must reproduce the above// copyright notice, this list of conditions and the following disclaimer// in the documentation and/or other materials provided with the// distribution.//     * Neither the name of Google Inc. nor the names of its// contributors may be used to endorse or promote products derived from// this software without specific prior written permission.//// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.var _ = require('underscore');var grpc = require('..');var examples = grpc.load(__dirname + '/route_guide.proto').examples;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 &&        feature.point.longitude === point.longitude) {      return feature;    }  }  // If not, create a new one with 50% chance of indicating "no feature present"  var name;  if (_.random(0,1) === 0) {    name = '';  } else {    name = randomWord(5);  }  feature = {    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;  var left = _.min(lo.longitude, hi.longitude);  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 &&        feature.location.latitude <= top) {      call.write(feature);    }  });  call.end();}/** * Calculate the distance between two points using the "haversine" formula. * This code was taken from http://www.movable-type.co.uk/scripts/latlong.html. * @param start The starting point * @param end The end point * @return The distance between the points in meters */function getDistance(start, end) {  var lat1 = start.latitude / COORD_FACTOR;  var lat2 = end.latitude / COORD_FACTOR;  var lon1 = start.longitude / COORD_FACTOR;  var lon2 = end.longitude / COORD_FACTOR;  var R = 6371000; // metres  var φ1 = lat1.toRadians();  var φ2 = lat2.toRadians();  var Δφ = (lat2-lat1).toRadians();  var Δλ = (lon2-lon1).toRadians();  var a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +      Math.cos(φ1) * Math.cos(φ2) *      Math.sin(Δλ/2) * Math.sin(Δλ/2);  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));  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).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);    }    previous = point;  });  call.on('end', function() {    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]    });  });}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;}/** * 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);      });    } else {      route_notes[key] = [];    }    // Then add the new note to the list    route_notes[key].push(note);  });  call.on('end', function() {    call.end();  });}/** * 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' : {      getFeature: getFeature,      listFeatures: listFeatures,      recordRoute: recordRoute,      routeChat: routeChat    }  });}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();}exports.getServer = getServer;
 |