route_guide_server.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. /*
  2. *
  3. * Copyright 2015 gRPC authors.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. var PROTO_PATH = __dirname + '/../../../protos/route_guide.proto';
  19. var fs = require('fs');
  20. var parseArgs = require('minimist');
  21. var path = require('path');
  22. var _ = require('lodash');
  23. var grpc = require('@grpc/grpc-js');
  24. var protoLoader = require('@grpc/proto-loader');
  25. var packageDefinition = protoLoader.loadSync(
  26. PROTO_PATH,
  27. {keepCase: true,
  28. longs: String,
  29. enums: String,
  30. defaults: true,
  31. oneofs: true
  32. });
  33. var routeguide = grpc.loadPackageDefinition(packageDefinition).routeguide;
  34. var COORD_FACTOR = 1e7;
  35. /**
  36. * For simplicity, a point is a record type that looks like
  37. * {latitude: number, longitude: number}, and a feature is a record type that
  38. * looks like {name: string, location: point}. feature objects with name===''
  39. * are points with no feature.
  40. */
  41. /**
  42. * List of feature objects at points that have been requested so far.
  43. */
  44. var feature_list = [];
  45. /**
  46. * Get a feature object at the given point, or creates one if it does not exist.
  47. * @param {point} point The point to check
  48. * @return {feature} The feature object at the point. Note that an empty name
  49. * indicates no feature
  50. */
  51. function checkFeature(point) {
  52. var feature;
  53. // Check if there is already a feature object for the given point
  54. for (var i = 0; i < feature_list.length; i++) {
  55. feature = feature_list[i];
  56. if (feature.location.latitude === point.latitude &&
  57. feature.location.longitude === point.longitude) {
  58. return feature;
  59. }
  60. }
  61. var name = '';
  62. feature = {
  63. name: name,
  64. location: point
  65. };
  66. return feature;
  67. }
  68. /**
  69. * getFeature request handler. Gets a request with a point, and responds with a
  70. * feature object indicating whether there is a feature at that point.
  71. * @param {EventEmitter} call Call object for the handler to process
  72. * @param {function(Error, feature)} callback Response callback
  73. */
  74. function getFeature(call, callback) {
  75. callback(null, checkFeature(call.request));
  76. }
  77. /**
  78. * listFeatures request handler. Gets a request with two points, and responds
  79. * with a stream of all features in the bounding box defined by those points.
  80. * @param {Writable} call Writable stream for responses with an additional
  81. * request property for the request value.
  82. */
  83. function listFeatures(call) {
  84. var lo = call.request.lo;
  85. var hi = call.request.hi;
  86. var left = _.min([lo.longitude, hi.longitude]);
  87. var right = _.max([lo.longitude, hi.longitude]);
  88. var top = _.max([lo.latitude, hi.latitude]);
  89. var bottom = _.min([lo.latitude, hi.latitude]);
  90. // For each feature, check if it is in the given bounding box
  91. _.each(feature_list, function(feature) {
  92. if (feature.name === '') {
  93. return;
  94. }
  95. if (feature.location.longitude >= left &&
  96. feature.location.longitude <= right &&
  97. feature.location.latitude >= bottom &&
  98. feature.location.latitude <= top) {
  99. call.write(feature);
  100. }
  101. });
  102. call.end();
  103. }
  104. /**
  105. * Calculate the distance between two points using the "haversine" formula.
  106. * The formula is based on http://mathforum.org/library/drmath/view/51879.html.
  107. * @param start The starting point
  108. * @param end The end point
  109. * @return The distance between the points in meters
  110. */
  111. function getDistance(start, end) {
  112. function toRadians(num) {
  113. return num * Math.PI / 180;
  114. }
  115. var R = 6371000; // earth radius in metres
  116. var lat1 = toRadians(start.latitude / COORD_FACTOR);
  117. var lat2 = toRadians(end.latitude / COORD_FACTOR);
  118. var lon1 = toRadians(start.longitude / COORD_FACTOR);
  119. var lon2 = toRadians(end.longitude / COORD_FACTOR);
  120. var deltalat = lat2-lat1;
  121. var deltalon = lon2-lon1;
  122. var a = Math.sin(deltalat/2) * Math.sin(deltalat/2) +
  123. Math.cos(lat1) * Math.cos(lat2) *
  124. Math.sin(deltalon/2) * Math.sin(deltalon/2);
  125. var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  126. return R * c;
  127. }
  128. /**
  129. * recordRoute handler. Gets a stream of points, and responds with statistics
  130. * about the "trip": number of points, number of known features visited, total
  131. * distance traveled, and total time spent.
  132. * @param {Readable} call The request point stream.
  133. * @param {function(Error, routeSummary)} callback The callback to pass the
  134. * response to
  135. */
  136. function recordRoute(call, callback) {
  137. var point_count = 0;
  138. var feature_count = 0;
  139. var distance = 0;
  140. var previous = null;
  141. // Start a timer
  142. var start_time = process.hrtime();
  143. call.on('data', function(point) {
  144. point_count += 1;
  145. if (checkFeature(point).name !== '') {
  146. feature_count += 1;
  147. }
  148. /* For each point after the first, add the incremental distance from the
  149. * previous point to the total distance value */
  150. if (previous != null) {
  151. distance += getDistance(previous, point);
  152. }
  153. previous = point;
  154. });
  155. call.on('end', function() {
  156. callback(null, {
  157. point_count: point_count,
  158. feature_count: feature_count,
  159. // Cast the distance to an integer
  160. distance: distance|0,
  161. // End the timer
  162. elapsed_time: process.hrtime(start_time)[0]
  163. });
  164. });
  165. }
  166. var route_notes = {};
  167. /**
  168. * Turn the point into a dictionary key.
  169. * @param {point} point The point to use
  170. * @return {string} The key for an object
  171. */
  172. function pointKey(point) {
  173. return point.latitude + ' ' + point.longitude;
  174. }
  175. /**
  176. * routeChat handler. Receives a stream of message/location pairs, and responds
  177. * with a stream of all previous messages at each of those locations.
  178. * @param {Duplex} call The stream for incoming and outgoing messages
  179. */
  180. function routeChat(call) {
  181. call.on('data', function(note) {
  182. var key = pointKey(note.location);
  183. /* For each note sent, respond with all previous notes that correspond to
  184. * the same point */
  185. if (route_notes.hasOwnProperty(key)) {
  186. _.each(route_notes[key], function(note) {
  187. call.write(note);
  188. });
  189. } else {
  190. route_notes[key] = [];
  191. }
  192. // Then add the new note to the list
  193. route_notes[key].push(JSON.parse(JSON.stringify(note)));
  194. });
  195. call.on('end', function() {
  196. call.end();
  197. });
  198. }
  199. /**
  200. * Get a new server with the handler functions in this file bound to the methods
  201. * it serves.
  202. * @return {Server} The new server object
  203. */
  204. function getServer() {
  205. var server = new grpc.Server();
  206. server.addService(routeguide.RouteGuide.service, {
  207. getFeature: getFeature,
  208. listFeatures: listFeatures,
  209. recordRoute: recordRoute,
  210. routeChat: routeChat
  211. });
  212. return server;
  213. }
  214. if (require.main === module) {
  215. // If this is run as a script, start a server on an unused port
  216. var routeServer = getServer();
  217. routeServer.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
  218. var argv = parseArgs(process.argv, {
  219. string: 'db_path'
  220. });
  221. fs.readFile(path.resolve(argv.db_path), function(err, data) {
  222. if (err) throw err;
  223. feature_list = JSON.parse(data);
  224. routeServer.start();
  225. });
  226. });
  227. }
  228. exports.getServer = getServer;