1/*
2 *
3 * Copyright 2015 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19//go:generate protoc -I ../routeguide --go_out=plugins=grpc:../routeguide ../routeguide/route_guide.proto
20
21// Package main implements a simple gRPC server that demonstrates how to use gRPC-Go libraries
22// to perform unary, client streaming, server streaming and full duplex RPCs.
23//
24// It implements the route guide service whose definition can be found in routeguide/route_guide.proto.
25package main
26
27import (
28	"context"
29	"encoding/json"
30	"flag"
31	"fmt"
32	"io"
33	"io/ioutil"
34	"log"
35	"math"
36	"net"
37	"sync"
38	"time"
39
40	"google.golang.org/grpc"
41
42	"google.golang.org/grpc/credentials"
43	"google.golang.org/grpc/testdata"
44
45	"github.com/golang/protobuf/proto"
46
47	pb "google.golang.org/grpc/examples/route_guide/routeguide"
48)
49
50var (
51	tls        = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP")
52	certFile   = flag.String("cert_file", "", "The TLS cert file")
53	keyFile    = flag.String("key_file", "", "The TLS key file")
54	jsonDBFile = flag.String("json_db_file", "testdata/route_guide_db.json", "A json file containing a list of features")
55	port       = flag.Int("port", 10000, "The server port")
56)
57
58type routeGuideServer struct {
59	savedFeatures []*pb.Feature // read-only after initialized
60
61	mu         sync.Mutex // protects routeNotes
62	routeNotes map[string][]*pb.RouteNote
63}
64
65// GetFeature returns the feature at the given point.
66func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
67	for _, feature := range s.savedFeatures {
68		if proto.Equal(feature.Location, point) {
69			return feature, nil
70		}
71	}
72	// No feature was found, return an unnamed feature
73	return &pb.Feature{Location: point}, nil
74}
75
76// ListFeatures lists all features contained within the given bounding Rectangle.
77func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
78	for _, feature := range s.savedFeatures {
79		if inRange(feature.Location, rect) {
80			if err := stream.Send(feature); err != nil {
81				return err
82			}
83		}
84	}
85	return nil
86}
87
88// RecordRoute records a route composited of a sequence of points.
89//
90// It gets a stream of points, and responds with statistics about the "trip":
91// number of points,  number of known features visited, total distance traveled, and
92// total time spent.
93func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {
94	var pointCount, featureCount, distance int32
95	var lastPoint *pb.Point
96	startTime := time.Now()
97	for {
98		point, err := stream.Recv()
99		if err == io.EOF {
100			endTime := time.Now()
101			return stream.SendAndClose(&pb.RouteSummary{
102				PointCount:   pointCount,
103				FeatureCount: featureCount,
104				Distance:     distance,
105				ElapsedTime:  int32(endTime.Sub(startTime).Seconds()),
106			})
107		}
108		if err != nil {
109			return err
110		}
111		pointCount++
112		for _, feature := range s.savedFeatures {
113			if proto.Equal(feature.Location, point) {
114				featureCount++
115			}
116		}
117		if lastPoint != nil {
118			distance += calcDistance(lastPoint, point)
119		}
120		lastPoint = point
121	}
122}
123
124// RouteChat receives a stream of message/location pairs, and responds with a stream of all
125// previous messages at each of those locations.
126func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
127	for {
128		in, err := stream.Recv()
129		if err == io.EOF {
130			return nil
131		}
132		if err != nil {
133			return err
134		}
135		key := serialize(in.Location)
136
137		s.mu.Lock()
138		s.routeNotes[key] = append(s.routeNotes[key], in)
139		// Note: this copy prevents blocking other clients while serving this one.
140		// We don't need to do a deep copy, because elements in the slice are
141		// insert-only and never modified.
142		rn := make([]*pb.RouteNote, len(s.routeNotes[key]))
143		copy(rn, s.routeNotes[key])
144		s.mu.Unlock()
145
146		for _, note := range rn {
147			if err := stream.Send(note); err != nil {
148				return err
149			}
150		}
151	}
152}
153
154// loadFeatures loads features from a JSON file.
155func (s *routeGuideServer) loadFeatures(filePath string) {
156	file, err := ioutil.ReadFile(filePath)
157	if err != nil {
158		log.Fatalf("Failed to load default features: %v", err)
159	}
160	if err := json.Unmarshal(file, &s.savedFeatures); err != nil {
161		log.Fatalf("Failed to load default features: %v", err)
162	}
163}
164
165func toRadians(num float64) float64 {
166	return num * math.Pi / float64(180)
167}
168
169// calcDistance calculates the distance between two points using the "haversine" formula.
170// The formula is based on http://mathforum.org/library/drmath/view/51879.html.
171func calcDistance(p1 *pb.Point, p2 *pb.Point) int32 {
172	const CordFactor float64 = 1e7
173	const R float64 = float64(6371000) // earth radius in metres
174	lat1 := toRadians(float64(p1.Latitude) / CordFactor)
175	lat2 := toRadians(float64(p2.Latitude) / CordFactor)
176	lng1 := toRadians(float64(p1.Longitude) / CordFactor)
177	lng2 := toRadians(float64(p2.Longitude) / CordFactor)
178	dlat := lat2 - lat1
179	dlng := lng2 - lng1
180
181	a := math.Sin(dlat/2)*math.Sin(dlat/2) +
182		math.Cos(lat1)*math.Cos(lat2)*
183			math.Sin(dlng/2)*math.Sin(dlng/2)
184	c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
185
186	distance := R * c
187	return int32(distance)
188}
189
190func inRange(point *pb.Point, rect *pb.Rectangle) bool {
191	left := math.Min(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude))
192	right := math.Max(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude))
193	top := math.Max(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude))
194	bottom := math.Min(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude))
195
196	if float64(point.Longitude) >= left &&
197		float64(point.Longitude) <= right &&
198		float64(point.Latitude) >= bottom &&
199		float64(point.Latitude) <= top {
200		return true
201	}
202	return false
203}
204
205func serialize(point *pb.Point) string {
206	return fmt.Sprintf("%d %d", point.Latitude, point.Longitude)
207}
208
209func newServer() *routeGuideServer {
210	s := &routeGuideServer{routeNotes: make(map[string][]*pb.RouteNote)}
211	s.loadFeatures(*jsonDBFile)
212	return s
213}
214
215func main() {
216	flag.Parse()
217	lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
218	if err != nil {
219		log.Fatalf("failed to listen: %v", err)
220	}
221	var opts []grpc.ServerOption
222	if *tls {
223		if *certFile == "" {
224			*certFile = testdata.Path("server1.pem")
225		}
226		if *keyFile == "" {
227			*keyFile = testdata.Path("server1.key")
228		}
229		creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile)
230		if err != nil {
231			log.Fatalf("Failed to generate credentials %v", err)
232		}
233		opts = []grpc.ServerOption{grpc.Creds(creds)}
234	}
235	grpcServer := grpc.NewServer(opts...)
236	pb.RegisterRouteGuideServer(grpcServer, newServer())
237	grpcServer.Serve(lis)
238}
239