1/* 2 * 3 * Copyright 2015, Google Inc. 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are 8 * met: 9 * 10 * * Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * * Redistributions in binary form must reproduce the above 13 * copyright notice, this list of conditions and the following disclaimer 14 * in the documentation and/or other materials provided with the 15 * distribution. 16 * * Neither the name of Google Inc. nor the names of its 17 * contributors may be used to endorse or promote products derived from 18 * this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 * 32 */ 33 34// Package main implements a simple gRPC server that demonstrates how to use gRPC-Go libraries 35// to perform unary, client streaming, server streaming and full duplex RPCs. 36// 37// It implements the route guide service whose definition can be found in proto/route_guide.proto. 38package main 39 40import ( 41 "encoding/json" 42 "flag" 43 "fmt" 44 "io" 45 "io/ioutil" 46 "math" 47 "net" 48 "time" 49 50 "golang.org/x/net/context" 51 "google.golang.org/grpc" 52 53 "google.golang.org/grpc/credentials" 54 "google.golang.org/grpc/grpclog" 55 56 "github.com/golang/protobuf/proto" 57 58 pb "google.golang.org/grpc/examples/route_guide/routeguide" 59) 60 61var ( 62 tls = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP") 63 certFile = flag.String("cert_file", "testdata/server1.pem", "The TLS cert file") 64 keyFile = flag.String("key_file", "testdata/server1.key", "The TLS key file") 65 jsonDBFile = flag.String("json_db_file", "testdata/route_guide_db.json", "A json file containing a list of features") 66 port = flag.Int("port", 10000, "The server port") 67) 68 69type routeGuideServer struct { 70 savedFeatures []*pb.Feature 71 routeNotes map[string][]*pb.RouteNote 72} 73 74// GetFeature returns the feature at the given point. 75func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) { 76 for _, feature := range s.savedFeatures { 77 if proto.Equal(feature.Location, point) { 78 return feature, nil 79 } 80 } 81 // No feature was found, return an unnamed feature 82 return &pb.Feature{Location: point}, nil 83} 84 85// ListFeatures lists all features contained within the given bounding Rectangle. 86func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error { 87 for _, feature := range s.savedFeatures { 88 if inRange(feature.Location, rect) { 89 if err := stream.Send(feature); err != nil { 90 return err 91 } 92 } 93 } 94 return nil 95} 96 97// RecordRoute records a route composited of a sequence of points. 98// 99// It gets a stream of points, and responds with statistics about the "trip": 100// number of points, number of known features visited, total distance traveled, and 101// total time spent. 102func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error { 103 var pointCount, featureCount, distance int32 104 var lastPoint *pb.Point 105 startTime := time.Now() 106 for { 107 point, err := stream.Recv() 108 if err == io.EOF { 109 endTime := time.Now() 110 return stream.SendAndClose(&pb.RouteSummary{ 111 PointCount: pointCount, 112 FeatureCount: featureCount, 113 Distance: distance, 114 ElapsedTime: int32(endTime.Sub(startTime).Seconds()), 115 }) 116 } 117 if err != nil { 118 return err 119 } 120 pointCount++ 121 for _, feature := range s.savedFeatures { 122 if proto.Equal(feature.Location, point) { 123 featureCount++ 124 } 125 } 126 if lastPoint != nil { 127 distance += calcDistance(lastPoint, point) 128 } 129 lastPoint = point 130 } 131} 132 133// RouteChat receives a stream of message/location pairs, and responds with a stream of all 134// previous messages at each of those locations. 135func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error { 136 for { 137 in, err := stream.Recv() 138 if err == io.EOF { 139 return nil 140 } 141 if err != nil { 142 return err 143 } 144 key := serialize(in.Location) 145 if _, present := s.routeNotes[key]; !present { 146 s.routeNotes[key] = []*pb.RouteNote{in} 147 } else { 148 s.routeNotes[key] = append(s.routeNotes[key], in) 149 } 150 for _, note := range s.routeNotes[key] { 151 if err := stream.Send(note); err != nil { 152 return err 153 } 154 } 155 } 156} 157 158// loadFeatures loads features from a JSON file. 159func (s *routeGuideServer) loadFeatures(filePath string) { 160 file, err := ioutil.ReadFile(filePath) 161 if err != nil { 162 grpclog.Fatalf("Failed to load default features: %v", err) 163 } 164 if err := json.Unmarshal(file, &s.savedFeatures); err != nil { 165 grpclog.Fatalf("Failed to load default features: %v", err) 166 } 167} 168 169func toRadians(num float64) float64 { 170 return num * math.Pi / float64(180) 171} 172 173// calcDistance calculates the distance between two points using the "haversine" formula. 174// This code was taken from http://www.movable-type.co.uk/scripts/latlong.html. 175func calcDistance(p1 *pb.Point, p2 *pb.Point) int32 { 176 const CordFactor float64 = 1e7 177 const R float64 = float64(6371000) // metres 178 lat1 := float64(p1.Latitude) / CordFactor 179 lat2 := float64(p2.Latitude) / CordFactor 180 lng1 := float64(p1.Longitude) / CordFactor 181 lng2 := float64(p2.Longitude) / CordFactor 182 φ1 := toRadians(lat1) 183 φ2 := toRadians(lat2) 184 Δφ := toRadians(lat2 - lat1) 185 Δλ := toRadians(lng2 - lng1) 186 187 a := math.Sin(Δφ/2)*math.Sin(Δφ/2) + 188 math.Cos(φ1)*math.Cos(φ2)* 189 math.Sin(Δλ/2)*math.Sin(Δλ/2) 190 c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) 191 192 distance := R * c 193 return int32(distance) 194} 195 196func inRange(point *pb.Point, rect *pb.Rectangle) bool { 197 left := math.Min(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude)) 198 right := math.Max(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude)) 199 top := math.Max(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude)) 200 bottom := math.Min(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude)) 201 202 if float64(point.Longitude) >= left && 203 float64(point.Longitude) <= right && 204 float64(point.Latitude) >= bottom && 205 float64(point.Latitude) <= top { 206 return true 207 } 208 return false 209} 210 211func serialize(point *pb.Point) string { 212 return fmt.Sprintf("%d %d", point.Latitude, point.Longitude) 213} 214 215func newServer() *routeGuideServer { 216 s := new(routeGuideServer) 217 s.loadFeatures(*jsonDBFile) 218 s.routeNotes = make(map[string][]*pb.RouteNote) 219 return s 220} 221 222func main() { 223 flag.Parse() 224 lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) 225 if err != nil { 226 grpclog.Fatalf("failed to listen: %v", err) 227 } 228 var opts []grpc.ServerOption 229 if *tls { 230 creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile) 231 if err != nil { 232 grpclog.Fatalf("Failed to generate credentials %v", err) 233 } 234 opts = []grpc.ServerOption{grpc.Creds(creds)} 235 } 236 grpcServer := grpc.NewServer(opts...) 237 pb.RegisterRouteGuideServer(grpcServer, newServer()) 238 grpcServer.Serve(lis) 239} 240