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