Imported Upstream version 1.34.0
[platform/upstream/grpc.git] / examples / node / static_codegen / route_guide / route_guide_server.js
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
19 var messages = require('./route_guide_pb');
20 var services = require('./route_guide_grpc_pb');
21
22 var fs = require('fs');
23 var parseArgs = require('minimist');
24 var path = require('path');
25 var _ = require('lodash');
26 var grpc = require('@grpc/grpc-js');
27
28 var 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  */
40 var 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  */
48 function 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  */
71 function 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  */
81 function 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  */
110 function 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  */
137 function 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
168 var 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  */
175 function 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  */
184 function 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  */
209 function 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
220 if (require.main === module) {
221   // If this is run as a script, start a server on an unused port
222   var routeServer = getServer();
223   routeServer.bindAsync('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 }
243
244 exports.getServer = getServer;