route_guide_server.js 7.7 KB

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