1# Copyright 2020 The gRPC Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""The Python AsyncIO implementation of the gRPC route guide server.""" 15 16import asyncio 17import logging 18import math 19import time 20from typing import AsyncIterable, Iterable 21 22import grpc 23import route_guide_pb2 24import route_guide_pb2_grpc 25import route_guide_resources 26 27 28def get_feature( 29 feature_db: Iterable[route_guide_pb2.Feature], point: route_guide_pb2.Point 30) -> route_guide_pb2.Feature: 31 """Returns Feature at given location or None.""" 32 for feature in feature_db: 33 if feature.location == point: 34 return feature 35 return None 36 37 38def get_distance( 39 start: route_guide_pb2.Point, end: route_guide_pb2.Point 40) -> float: 41 """Distance between two points.""" 42 coord_factor = 10000000.0 43 lat_1 = start.latitude / coord_factor 44 lat_2 = end.latitude / coord_factor 45 lon_1 = start.longitude / coord_factor 46 lon_2 = end.longitude / coord_factor 47 lat_rad_1 = math.radians(lat_1) 48 lat_rad_2 = math.radians(lat_2) 49 delta_lat_rad = math.radians(lat_2 - lat_1) 50 delta_lon_rad = math.radians(lon_2 - lon_1) 51 52 # Formula is based on http://mathforum.org/library/drmath/view/51879.html 53 a = pow(math.sin(delta_lat_rad / 2), 2) + ( 54 math.cos(lat_rad_1) 55 * math.cos(lat_rad_2) 56 * pow(math.sin(delta_lon_rad / 2), 2) 57 ) 58 c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) 59 R = 6371000 60 # metres 61 return R * c 62 63 64class RouteGuideServicer(route_guide_pb2_grpc.RouteGuideServicer): 65 """Provides methods that implement functionality of route guide server.""" 66 67 def __init__(self) -> None: 68 self.db = route_guide_resources.read_route_guide_database() 69 70 def GetFeature( 71 self, request: route_guide_pb2.Point, unused_context 72 ) -> route_guide_pb2.Feature: 73 feature = get_feature(self.db, request) 74 if feature is None: 75 return route_guide_pb2.Feature(name="", location=request) 76 else: 77 return feature 78 79 async def ListFeatures( 80 self, request: route_guide_pb2.Rectangle, unused_context 81 ) -> AsyncIterable[route_guide_pb2.Feature]: 82 left = min(request.lo.longitude, request.hi.longitude) 83 right = max(request.lo.longitude, request.hi.longitude) 84 top = max(request.lo.latitude, request.hi.latitude) 85 bottom = min(request.lo.latitude, request.hi.latitude) 86 for feature in self.db: 87 if ( 88 feature.location.longitude >= left 89 and feature.location.longitude <= right 90 and feature.location.latitude >= bottom 91 and feature.location.latitude <= top 92 ): 93 yield feature 94 95 async def RecordRoute( 96 self, 97 request_iterator: AsyncIterable[route_guide_pb2.Point], 98 unused_context, 99 ) -> route_guide_pb2.RouteSummary: 100 point_count = 0 101 feature_count = 0 102 distance = 0.0 103 prev_point = None 104 105 start_time = time.time() 106 async for point in request_iterator: 107 point_count += 1 108 if get_feature(self.db, point): 109 feature_count += 1 110 if prev_point: 111 distance += get_distance(prev_point, point) 112 prev_point = point 113 114 elapsed_time = time.time() - start_time 115 return route_guide_pb2.RouteSummary( 116 point_count=point_count, 117 feature_count=feature_count, 118 distance=int(distance), 119 elapsed_time=int(elapsed_time), 120 ) 121 122 async def RouteChat( 123 self, 124 request_iterator: AsyncIterable[route_guide_pb2.RouteNote], 125 unused_context, 126 ) -> AsyncIterable[route_guide_pb2.RouteNote]: 127 prev_notes = [] 128 async for new_note in request_iterator: 129 for prev_note in prev_notes: 130 if prev_note.location == new_note.location: 131 yield prev_note 132 prev_notes.append(new_note) 133 134 135async def serve() -> None: 136 server = grpc.aio.server() 137 route_guide_pb2_grpc.add_RouteGuideServicer_to_server( 138 RouteGuideServicer(), server 139 ) 140 server.add_insecure_port("[::]:50051") 141 await server.start() 142 await server.wait_for_termination() 143 144 145if __name__ == "__main__": 146 logging.basicConfig(level=logging.INFO) 147 asyncio.get_event_loop().run_until_complete(serve()) 148