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.Cos1)*math.Cos2)*
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