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