• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.examples.routeguide;
18 
19 import static java.lang.Math.atan2;
20 import static java.lang.Math.cos;
21 import static java.lang.Math.max;
22 import static java.lang.Math.min;
23 import static java.lang.Math.sin;
24 import static java.lang.Math.sqrt;
25 import static java.lang.Math.toRadians;
26 import static java.util.concurrent.TimeUnit.NANOSECONDS;
27 
28 import io.grpc.Grpc;
29 import io.grpc.InsecureServerCredentials;
30 import io.grpc.Server;
31 import io.grpc.ServerBuilder;
32 import io.grpc.stub.StreamObserver;
33 import java.io.IOException;
34 import java.net.URL;
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.List;
39 import java.util.concurrent.ConcurrentHashMap;
40 import java.util.concurrent.ConcurrentMap;
41 import java.util.concurrent.TimeUnit;
42 import java.util.logging.Level;
43 import java.util.logging.Logger;
44 
45 /**
46  * A sample gRPC server that serve the RouteGuide (see route_guide.proto) service.
47  */
48 public class RouteGuideServer {
49   private static final Logger logger = Logger.getLogger(RouteGuideServer.class.getName());
50 
51   private final int port;
52   private final Server server;
53 
RouteGuideServer(int port)54   public RouteGuideServer(int port) throws IOException {
55     this(port, RouteGuideUtil.getDefaultFeaturesFile());
56   }
57 
58   /** Create a RouteGuide server listening on {@code port} using {@code featureFile} database. */
RouteGuideServer(int port, URL featureFile)59   public RouteGuideServer(int port, URL featureFile) throws IOException {
60     this(Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()),
61         port, RouteGuideUtil.parseFeatures(featureFile));
62   }
63 
64   /** Create a RouteGuide server using serverBuilder as a base and features as data. */
RouteGuideServer(ServerBuilder<?> serverBuilder, int port, Collection<Feature> features)65   public RouteGuideServer(ServerBuilder<?> serverBuilder, int port, Collection<Feature> features) {
66     this.port = port;
67     server = serverBuilder.addService(new RouteGuideService(features))
68         .build();
69   }
70 
71   /** Start serving requests. */
start()72   public void start() throws IOException {
73     server.start();
74     logger.info("Server started, listening on " + port);
75     Runtime.getRuntime().addShutdownHook(new Thread() {
76       @Override
77       public void run() {
78         // Use stderr here since the logger may have been reset by its JVM shutdown hook.
79         System.err.println("*** shutting down gRPC server since JVM is shutting down");
80         try {
81           RouteGuideServer.this.stop();
82         } catch (InterruptedException e) {
83           e.printStackTrace(System.err);
84         }
85         System.err.println("*** server shut down");
86       }
87     });
88   }
89 
90   /** Stop serving requests and shutdown resources. */
stop()91   public void stop() throws InterruptedException {
92     if (server != null) {
93       server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
94     }
95   }
96 
97   /**
98    * Await termination on the main thread since the grpc library uses daemon threads.
99    */
blockUntilShutdown()100   private void blockUntilShutdown() throws InterruptedException {
101     if (server != null) {
102       server.awaitTermination();
103     }
104   }
105 
106   /**
107    * Main method.  This comment makes the linter happy.
108    */
main(String[] args)109   public static void main(String[] args) throws Exception {
110     RouteGuideServer server = new RouteGuideServer(8980);
111     server.start();
112     server.blockUntilShutdown();
113   }
114 
115   /**
116    * Our implementation of RouteGuide service.
117    *
118    * <p>See route_guide.proto for details of the methods.
119    */
120   private static class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
121     private final Collection<Feature> features;
122     private final ConcurrentMap<Point, List<RouteNote>> routeNotes =
123         new ConcurrentHashMap<Point, List<RouteNote>>();
124 
RouteGuideService(Collection<Feature> features)125     RouteGuideService(Collection<Feature> features) {
126       this.features = features;
127     }
128 
129     /**
130      * Gets the {@link Feature} at the requested {@link Point}. If no feature at that location
131      * exists, an unnamed feature is returned at the provided location.
132      *
133      * @param request the requested location for the feature.
134      * @param responseObserver the observer that will receive the feature at the requested point.
135      */
136     @Override
getFeature(Point request, StreamObserver<Feature> responseObserver)137     public void getFeature(Point request, StreamObserver<Feature> responseObserver) {
138       responseObserver.onNext(checkFeature(request));
139       responseObserver.onCompleted();
140     }
141 
142     /**
143      * Gets all features contained within the given bounding {@link Rectangle}.
144      *
145      * @param request the bounding rectangle for the requested features.
146      * @param responseObserver the observer that will receive the features.
147      */
148     @Override
listFeatures(Rectangle request, StreamObserver<Feature> responseObserver)149     public void listFeatures(Rectangle request, StreamObserver<Feature> responseObserver) {
150       int left = min(request.getLo().getLongitude(), request.getHi().getLongitude());
151       int right = max(request.getLo().getLongitude(), request.getHi().getLongitude());
152       int top = max(request.getLo().getLatitude(), request.getHi().getLatitude());
153       int bottom = min(request.getLo().getLatitude(), request.getHi().getLatitude());
154 
155       for (Feature feature : features) {
156         if (!RouteGuideUtil.exists(feature)) {
157           continue;
158         }
159 
160         int lat = feature.getLocation().getLatitude();
161         int lon = feature.getLocation().getLongitude();
162         if (lon >= left && lon <= right && lat >= bottom && lat <= top) {
163           responseObserver.onNext(feature);
164         }
165       }
166       responseObserver.onCompleted();
167     }
168 
169     /**
170      * Gets a stream of points, and responds with statistics about the "trip": number of points,
171      * number of known features visited, total distance traveled, and total time spent.
172      *
173      * @param responseObserver an observer to receive the response summary.
174      * @return an observer to receive the requested route points.
175      */
176     @Override
recordRoute(final StreamObserver<RouteSummary> responseObserver)177     public StreamObserver<Point> recordRoute(final StreamObserver<RouteSummary> responseObserver) {
178       return new StreamObserver<Point>() {
179         int pointCount;
180         int featureCount;
181         int distance;
182         Point previous;
183         final long startTime = System.nanoTime();
184 
185         @Override
186         public void onNext(Point point) {
187           pointCount++;
188           if (RouteGuideUtil.exists(checkFeature(point))) {
189             featureCount++;
190           }
191           // For each point after the first, add the incremental distance from the previous point to
192           // the total distance value.
193           if (previous != null) {
194             distance += calcDistance(previous, point);
195           }
196           previous = point;
197         }
198 
199         @Override
200         public void onError(Throwable t) {
201           logger.log(Level.WARNING, "recordRoute cancelled");
202         }
203 
204         @Override
205         public void onCompleted() {
206           long seconds = NANOSECONDS.toSeconds(System.nanoTime() - startTime);
207           responseObserver.onNext(RouteSummary.newBuilder().setPointCount(pointCount)
208               .setFeatureCount(featureCount).setDistance(distance)
209               .setElapsedTime((int) seconds).build());
210           responseObserver.onCompleted();
211         }
212       };
213     }
214 
215     /**
216      * Receives a stream of message/location pairs, and responds with a stream of all previous
217      * messages at each of those locations.
218      *
219      * @param responseObserver an observer to receive the stream of previous messages.
220      * @return an observer to handle requested message/location pairs.
221      */
222     @Override
routeChat(final StreamObserver<RouteNote> responseObserver)223     public StreamObserver<RouteNote> routeChat(final StreamObserver<RouteNote> responseObserver) {
224       return new StreamObserver<RouteNote>() {
225         @Override
226         public void onNext(RouteNote note) {
227           List<RouteNote> notes = getOrCreateNotes(note.getLocation());
228 
229           // Respond with all previous notes at this location.
230           for (RouteNote prevNote : notes.toArray(new RouteNote[0])) {
231             responseObserver.onNext(prevNote);
232           }
233 
234           // Now add the new note to the list
235           notes.add(note);
236         }
237 
238         @Override
239         public void onError(Throwable t) {
240           logger.log(Level.WARNING, "routeChat cancelled");
241         }
242 
243         @Override
244         public void onCompleted() {
245           responseObserver.onCompleted();
246         }
247       };
248     }
249 
250     /**
251      * Get the notes list for the given location. If missing, create it.
252      */
253     private List<RouteNote> getOrCreateNotes(Point location) {
254       List<RouteNote> notes = Collections.synchronizedList(new ArrayList<RouteNote>());
255       List<RouteNote> prevNotes = routeNotes.putIfAbsent(location, notes);
256       return prevNotes != null ? prevNotes : notes;
257     }
258 
259     /**
260      * Gets the feature at the given point.
261      *
262      * @param location the location to check.
263      * @return The feature object at the point. Note that an empty name indicates no feature.
264      */
265     private Feature checkFeature(Point location) {
266       for (Feature feature : features) {
267         if (feature.getLocation().getLatitude() == location.getLatitude()
268             && feature.getLocation().getLongitude() == location.getLongitude()) {
269           return feature;
270         }
271       }
272 
273       // No feature was found, return an unnamed feature.
274       return Feature.newBuilder().setName("").setLocation(location).build();
275     }
276 
277     /**
278      * Calculate the distance between two points using the "haversine" formula.
279      * The formula is based on http://mathforum.org/library/drmath/view/51879.html.
280      *
281      * @param start The starting point
282      * @param end The end point
283      * @return The distance between the points in meters
284      */
285     private static int calcDistance(Point start, Point end) {
286       int r = 6371000; // earth radius in meters
287       double lat1 = toRadians(RouteGuideUtil.getLatitude(start));
288       double lat2 = toRadians(RouteGuideUtil.getLatitude(end));
289       double lon1 = toRadians(RouteGuideUtil.getLongitude(start));
290       double lon2 = toRadians(RouteGuideUtil.getLongitude(end));
291       double deltaLat = lat2 - lat1;
292       double deltaLon = lon2 - lon1;
293 
294       double a = sin(deltaLat / 2) * sin(deltaLat / 2)
295           + cos(lat1) * cos(lat2) * sin(deltaLon / 2) * sin(deltaLon / 2);
296       double c = 2 * atan2(sqrt(a), sqrt(1 - a));
297 
298       return (int) (r * c);
299     }
300   }
301 }
302