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