1#!/usr/bin/env ruby 2# -*- coding: utf-8 -*- 3 4# Copyright 2015 gRPC authors. 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17 18# Sample app that connects to a Route Guide service. 19# 20# Usage: $ path/to/route_guide_server.rb path/to/route_guide_db.json & 21 22this_dir = File.expand_path(File.dirname(__FILE__)) 23lib_dir = File.join(File.dirname(this_dir), 'lib') 24$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir) 25 26require 'grpc' 27require 'multi_json' 28require 'route_guide_services_pb' 29 30include Routeguide 31COORD_FACTOR = 1e7 32RADIUS = 637_100 33 34# Determines the distance between two points. 35# The formula is based on http://mathforum.org/library/drmath/view/51879.html. 36def calculate_distance(point_a, point_b) 37 to_radians = proc { |x| x * Math::PI / 180 } 38 lat_a = to_radians.call(point_a.latitude / COORD_FACTOR) 39 lat_b = to_radians.call(point_b.latitude / COORD_FACTOR) 40 lon_a = to_radians.call(point_a.longitude / COORD_FACTOR) 41 lon_b = to_radians.call(point_b.longitude / COORD_FACTOR) 42 delta_lat = lat_a - lat_b 43 delta_lon = lon_a - lon_b 44 a = Math.sin(delta_lat / 2)**2 + 45 Math.cos(lat_a) * Math.cos(lat_b) + 46 Math.sin(delta_lon / 2)**2 47 (2 * RADIUS * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))).to_i 48end 49 50# RectangleEnum provides an Enumerator of the points in a feature_db within a 51# given Rectangle. 52class RectangleEnum 53 # @param [Hash] feature_db 54 # @param [Rectangle] bounds 55 def initialize(feature_db, bounds) 56 @feature_db = feature_db 57 @bounds = bounds 58 lats = [@bounds.lo.latitude, @bounds.hi.latitude] 59 longs = [@bounds.lo.longitude, @bounds.hi.longitude] 60 @lo_lat, @hi_lat = lats.min, lats.max 61 @lo_long, @hi_long = longs.min, longs.max 62 end 63 64 # in? determines if location lies within the bounds of this instances 65 # Rectangle. 66 def in?(location) 67 location['longitude'] >= @lo_long && 68 location['longitude'] <= @hi_long && 69 location['latitude'] >= @lo_lat && 70 location['latitude'] <= @hi_lat 71 end 72 73 # each yields the features in the instances feature_db that lie within the 74 # instance rectangle. 75 def each 76 return enum_for(:each) unless block_given? 77 @feature_db.each_pair do |location, name| 78 next unless in?(location) 79 next if name.nil? || name == '' 80 pt = Point.new( 81 Hash[location.each_pair.map { |k, v| [k.to_sym, v] }]) 82 yield Feature.new(location: pt, name: name) 83 end 84 end 85end 86 87# ServerImpl provides an implementation of the RouteGuide service. 88class ServerImpl < RouteGuide::Service 89 # @param [Hash] feature_db {location => name} 90 def initialize(feature_db) 91 @feature_db = feature_db 92 @received_notes = Hash.new { |h, k| h[k] = [] } 93 end 94 95 def get_feature(point, _call) 96 name = @feature_db[{ 97 'longitude' => point.longitude, 98 'latitude' => point.latitude }] || '' 99 Feature.new(location: point, name: name) 100 end 101 102 def list_features(rectangle, _call) 103 RectangleEnum.new(@feature_db, rectangle).each 104 end 105 106 def record_route(call) 107 started, elapsed_time = 0, 0 108 distance, count, features, last = 0, 0, 0, nil 109 call.each_remote_read do |point| 110 count += 1 111 name = @feature_db[{ 112 'longitude' => point.longitude, 113 'latitude' => point.latitude }] || '' 114 features += 1 unless name == '' 115 if last.nil? 116 last = point 117 started = Time.now.to_i 118 next 119 end 120 elapsed_time = Time.now.to_i - started 121 distance += calculate_distance(point, last) 122 last = point 123 end 124 RouteSummary.new(point_count: count, 125 feature_count: features, 126 distance: distance, 127 elapsed_time: elapsed_time) 128 end 129 130 def route_chat(notes) 131 RouteChatEnumerator.new(notes, @received_notes).each_item 132 end 133end 134 135class RouteChatEnumerator 136 def initialize(notes, received_notes) 137 @notes = notes 138 @received_notes = received_notes 139 end 140 def each_item 141 return enum_for(:each_item) unless block_given? 142 begin 143 @notes.each do |n| 144 key = { 145 'latitude' => n.location.latitude, 146 'longitude' => n.location.longitude 147 } 148 earlier_msgs = @received_notes[key] 149 @received_notes[key] << n.message 150 # send back the earlier messages at this point 151 earlier_msgs.each do |r| 152 yield RouteNote.new(location: n.location, message: r) 153 end 154 end 155 rescue StandardError => e 156 fail e # signal completion via an error 157 end 158 end 159end 160 161def main 162 if ARGV.length == 0 163 fail 'Please specify the path to the route_guide json database' 164 end 165 raw_data = [] 166 File.open(ARGV[0]) do |f| 167 raw_data = MultiJson.load(f.read) 168 end 169 feature_db = Hash[raw_data.map { |x| [x['location'], x['name']] }] 170 port = '0.0.0.0:50051' 171 s = GRPC::RpcServer.new 172 s.add_http2_port(port, :this_port_is_insecure) 173 GRPC.logger.info("... running insecurely on #{port}") 174 s.handle(ServerImpl.new(feature_db)) 175 # Runs the server with SIGHUP, SIGINT and SIGQUIT signal handlers to 176 # gracefully shutdown. 177 # User could also choose to run server via call to run_till_terminated 178 s.run_till_terminated_or_interrupted([1, 'int', 'SIGQUIT']) 179end 180 181main 182