OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * |
| 3 * Copyright 2015-2016, Google Inc. |
| 4 * All rights reserved. |
| 5 * |
| 6 * Redistribution and use in source and binary forms, with or without |
| 7 * modification, are permitted provided that the following conditions are |
| 8 * met: |
| 9 * |
| 10 * * Redistributions of source code must retain the above copyright |
| 11 * notice, this list of conditions and the following disclaimer. |
| 12 * * Redistributions in binary form must reproduce the above |
| 13 * copyright notice, this list of conditions and the following disclaimer |
| 14 * in the documentation and/or other materials provided with the |
| 15 * distribution. |
| 16 * * Neither the name of Google Inc. nor the names of its |
| 17 * contributors may be used to endorse or promote products derived from |
| 18 * this software without specific prior written permission. |
| 19 * |
| 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 31 * |
| 32 */ |
| 33 |
| 34 var PROTO_PATH = __dirname + '/../../protos/route_guide.proto'; |
| 35 |
| 36 var fs = require('fs'); |
| 37 var parseArgs = require('minimist'); |
| 38 var path = require('path'); |
| 39 var _ = require('lodash'); |
| 40 var grpc = require('grpc'); |
| 41 var routeguide = grpc.load(PROTO_PATH).routeguide; |
| 42 |
| 43 var COORD_FACTOR = 1e7; |
| 44 |
| 45 /** |
| 46 * For simplicity, a point is a record type that looks like |
| 47 * {latitude: number, longitude: number}, and a feature is a record type that |
| 48 * looks like {name: string, location: point}. feature objects with name==='' |
| 49 * are points with no feature. |
| 50 */ |
| 51 |
| 52 /** |
| 53 * List of feature objects at points that have been requested so far. |
| 54 */ |
| 55 var feature_list = []; |
| 56 |
| 57 /** |
| 58 * Get a feature object at the given point, or creates one if it does not exist. |
| 59 * @param {point} point The point to check |
| 60 * @return {feature} The feature object at the point. Note that an empty name |
| 61 * indicates no feature |
| 62 */ |
| 63 function checkFeature(point) { |
| 64 var feature; |
| 65 // Check if there is already a feature object for the given point |
| 66 for (var i = 0; i < feature_list.length; i++) { |
| 67 feature = feature_list[i]; |
| 68 if (feature.location.latitude === point.latitude && |
| 69 feature.location.longitude === point.longitude) { |
| 70 return feature; |
| 71 } |
| 72 } |
| 73 var name = ''; |
| 74 feature = { |
| 75 name: name, |
| 76 location: point |
| 77 }; |
| 78 return feature; |
| 79 } |
| 80 |
| 81 /** |
| 82 * getFeature request handler. Gets a request with a point, and responds with a |
| 83 * feature object indicating whether there is a feature at that point. |
| 84 * @param {EventEmitter} call Call object for the handler to process |
| 85 * @param {function(Error, feature)} callback Response callback |
| 86 */ |
| 87 function getFeature(call, callback) { |
| 88 callback(null, checkFeature(call.request)); |
| 89 } |
| 90 |
| 91 /** |
| 92 * listFeatures request handler. Gets a request with two points, and responds |
| 93 * with a stream of all features in the bounding box defined by those points. |
| 94 * @param {Writable} call Writable stream for responses with an additional |
| 95 * request property for the request value. |
| 96 */ |
| 97 function listFeatures(call) { |
| 98 var lo = call.request.lo; |
| 99 var hi = call.request.hi; |
| 100 var left = _.min([lo.longitude, hi.longitude]); |
| 101 var right = _.max([lo.longitude, hi.longitude]); |
| 102 var top = _.max([lo.latitude, hi.latitude]); |
| 103 var bottom = _.min([lo.latitude, hi.latitude]); |
| 104 // For each feature, check if it is in the given bounding box |
| 105 _.each(feature_list, function(feature) { |
| 106 if (feature.name === '') { |
| 107 return; |
| 108 } |
| 109 if (feature.location.longitude >= left && |
| 110 feature.location.longitude <= right && |
| 111 feature.location.latitude >= bottom && |
| 112 feature.location.latitude <= top) { |
| 113 call.write(feature); |
| 114 } |
| 115 }); |
| 116 call.end(); |
| 117 } |
| 118 |
| 119 /** |
| 120 * Calculate the distance between two points using the "haversine" formula. |
| 121 * This code was taken from http://www.movable-type.co.uk/scripts/latlong.html. |
| 122 * @param start The starting point |
| 123 * @param end The end point |
| 124 * @return The distance between the points in meters |
| 125 */ |
| 126 function getDistance(start, end) { |
| 127 function toRadians(num) { |
| 128 return num * Math.PI / 180; |
| 129 } |
| 130 var lat1 = start.latitude / COORD_FACTOR; |
| 131 var lat2 = end.latitude / COORD_FACTOR; |
| 132 var lon1 = start.longitude / COORD_FACTOR; |
| 133 var lon2 = end.longitude / COORD_FACTOR; |
| 134 var R = 6371000; // metres |
| 135 var φ1 = toRadians(lat1); |
| 136 var φ2 = toRadians(lat2); |
| 137 var Δφ = toRadians(lat2-lat1); |
| 138 var Δλ = toRadians(lon2-lon1); |
| 139 |
| 140 var a = Math.sin(Δφ/2) * Math.sin(Δφ/2) + |
| 141 Math.cos(φ1) * Math.cos(φ2) * |
| 142 Math.sin(Δλ/2) * Math.sin(Δλ/2); |
| 143 var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); |
| 144 |
| 145 return R * c; |
| 146 } |
| 147 |
| 148 /** |
| 149 * recordRoute handler. Gets a stream of points, and responds with statistics |
| 150 * about the "trip": number of points, number of known features visited, total |
| 151 * distance traveled, and total time spent. |
| 152 * @param {Readable} call The request point stream. |
| 153 * @param {function(Error, routeSummary)} callback The callback to pass the |
| 154 * response to |
| 155 */ |
| 156 function recordRoute(call, callback) { |
| 157 var point_count = 0; |
| 158 var feature_count = 0; |
| 159 var distance = 0; |
| 160 var previous = null; |
| 161 // Start a timer |
| 162 var start_time = process.hrtime(); |
| 163 call.on('data', function(point) { |
| 164 point_count += 1; |
| 165 if (checkFeature(point).name !== '') { |
| 166 feature_count += 1; |
| 167 } |
| 168 /* For each point after the first, add the incremental distance from the |
| 169 * previous point to the total distance value */ |
| 170 if (previous != null) { |
| 171 distance += getDistance(previous, point); |
| 172 } |
| 173 previous = point; |
| 174 }); |
| 175 call.on('end', function() { |
| 176 callback(null, { |
| 177 point_count: point_count, |
| 178 feature_count: feature_count, |
| 179 // Cast the distance to an integer |
| 180 distance: distance|0, |
| 181 // End the timer |
| 182 elapsed_time: process.hrtime(start_time)[0] |
| 183 }); |
| 184 }); |
| 185 } |
| 186 |
| 187 var route_notes = {}; |
| 188 |
| 189 /** |
| 190 * Turn the point into a dictionary key. |
| 191 * @param {point} point The point to use |
| 192 * @return {string} The key for an object |
| 193 */ |
| 194 function pointKey(point) { |
| 195 return point.latitude + ' ' + point.longitude; |
| 196 } |
| 197 |
| 198 /** |
| 199 * routeChat handler. Receives a stream of message/location pairs, and responds |
| 200 * with a stream of all previous messages at each of those locations. |
| 201 * @param {Duplex} call The stream for incoming and outgoing messages |
| 202 */ |
| 203 function routeChat(call) { |
| 204 call.on('data', function(note) { |
| 205 var key = pointKey(note.location); |
| 206 /* For each note sent, respond with all previous notes that correspond to |
| 207 * the same point */ |
| 208 if (route_notes.hasOwnProperty(key)) { |
| 209 _.each(route_notes[key], function(note) { |
| 210 call.write(note); |
| 211 }); |
| 212 } else { |
| 213 route_notes[key] = []; |
| 214 } |
| 215 // Then add the new note to the list |
| 216 route_notes[key].push(JSON.parse(JSON.stringify(note))); |
| 217 }); |
| 218 call.on('end', function() { |
| 219 call.end(); |
| 220 }); |
| 221 } |
| 222 |
| 223 /** |
| 224 * Get a new server with the handler functions in this file bound to the methods |
| 225 * it serves. |
| 226 * @return {Server} The new server object |
| 227 */ |
| 228 function getServer() { |
| 229 var server = new grpc.Server(); |
| 230 server.addProtoService(routeguide.RouteGuide.service, { |
| 231 getFeature: getFeature, |
| 232 listFeatures: listFeatures, |
| 233 recordRoute: recordRoute, |
| 234 routeChat: routeChat |
| 235 }); |
| 236 return server; |
| 237 } |
| 238 |
| 239 if (require.main === module) { |
| 240 // If this is run as a script, start a server on an unused port |
| 241 var routeServer = getServer(); |
| 242 routeServer.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure()); |
| 243 var argv = parseArgs(process.argv, { |
| 244 string: 'db_path' |
| 245 }); |
| 246 fs.readFile(path.resolve(argv.db_path), function(err, data) { |
| 247 if (err) throw err; |
| 248 feature_list = JSON.parse(data); |
| 249 routeServer.start(); |
| 250 }); |
| 251 } |
| 252 |
| 253 exports.getServer = getServer; |
OLD | NEW |