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