240 lines
7.5 KiB
Go
240 lines
7.5 KiB
Go
|
/*
|
||
|
*
|
||
|
* Copyright 2015, Google Inc.
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions are
|
||
|
* met:
|
||
|
*
|
||
|
* * Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* * Redistributions in binary form must reproduce the above
|
||
|
* copyright notice, this list of conditions and the following disclaimer
|
||
|
* in the documentation and/or other materials provided with the
|
||
|
* distribution.
|
||
|
* * Neither the name of Google Inc. nor the names of its
|
||
|
* contributors may be used to endorse or promote products derived from
|
||
|
* this software without specific prior written permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
// Package main implements a simple gRPC server that demonstrates how to use gRPC-Go libraries
|
||
|
// to perform unary, client streaming, server streaming and full duplex RPCs.
|
||
|
//
|
||
|
// It implements the route guide service whose definition can be found in proto/route_guide.proto.
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"flag"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"math"
|
||
|
"net"
|
||
|
"time"
|
||
|
|
||
|
"golang.org/x/net/context"
|
||
|
"google.golang.org/grpc"
|
||
|
|
||
|
"google.golang.org/grpc/credentials"
|
||
|
"google.golang.org/grpc/grpclog"
|
||
|
|
||
|
"github.com/golang/protobuf/proto"
|
||
|
|
||
|
pb "google.golang.org/grpc/examples/route_guide/routeguide"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
tls = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP")
|
||
|
certFile = flag.String("cert_file", "testdata/server1.pem", "The TLS cert file")
|
||
|
keyFile = flag.String("key_file", "testdata/server1.key", "The TLS key file")
|
||
|
jsonDBFile = flag.String("json_db_file", "testdata/route_guide_db.json", "A json file containing a list of features")
|
||
|
port = flag.Int("port", 10000, "The server port")
|
||
|
)
|
||
|
|
||
|
type routeGuideServer struct {
|
||
|
savedFeatures []*pb.Feature
|
||
|
routeNotes map[string][]*pb.RouteNote
|
||
|
}
|
||
|
|
||
|
// GetFeature returns the feature at the given point.
|
||
|
func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
|
||
|
for _, feature := range s.savedFeatures {
|
||
|
if proto.Equal(feature.Location, point) {
|
||
|
return feature, nil
|
||
|
}
|
||
|
}
|
||
|
// No feature was found, return an unnamed feature
|
||
|
return &pb.Feature{Location: point}, nil
|
||
|
}
|
||
|
|
||
|
// ListFeatures lists all features contained within the given bounding Rectangle.
|
||
|
func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
|
||
|
for _, feature := range s.savedFeatures {
|
||
|
if inRange(feature.Location, rect) {
|
||
|
if err := stream.Send(feature); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// RecordRoute records a route composited of a sequence of points.
|
||
|
//
|
||
|
// It gets a stream of points, and responds with statistics about the "trip":
|
||
|
// number of points, number of known features visited, total distance traveled, and
|
||
|
// total time spent.
|
||
|
func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {
|
||
|
var pointCount, featureCount, distance int32
|
||
|
var lastPoint *pb.Point
|
||
|
startTime := time.Now()
|
||
|
for {
|
||
|
point, err := stream.Recv()
|
||
|
if err == io.EOF {
|
||
|
endTime := time.Now()
|
||
|
return stream.SendAndClose(&pb.RouteSummary{
|
||
|
PointCount: pointCount,
|
||
|
FeatureCount: featureCount,
|
||
|
Distance: distance,
|
||
|
ElapsedTime: int32(endTime.Sub(startTime).Seconds()),
|
||
|
})
|
||
|
}
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
pointCount++
|
||
|
for _, feature := range s.savedFeatures {
|
||
|
if proto.Equal(feature.Location, point) {
|
||
|
featureCount++
|
||
|
}
|
||
|
}
|
||
|
if lastPoint != nil {
|
||
|
distance += calcDistance(lastPoint, point)
|
||
|
}
|
||
|
lastPoint = point
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// RouteChat receives a stream of message/location pairs, and responds with a stream of all
|
||
|
// previous messages at each of those locations.
|
||
|
func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
|
||
|
for {
|
||
|
in, err := stream.Recv()
|
||
|
if err == io.EOF {
|
||
|
return nil
|
||
|
}
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
key := serialize(in.Location)
|
||
|
if _, present := s.routeNotes[key]; !present {
|
||
|
s.routeNotes[key] = []*pb.RouteNote{in}
|
||
|
} else {
|
||
|
s.routeNotes[key] = append(s.routeNotes[key], in)
|
||
|
}
|
||
|
for _, note := range s.routeNotes[key] {
|
||
|
if err := stream.Send(note); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// loadFeatures loads features from a JSON file.
|
||
|
func (s *routeGuideServer) loadFeatures(filePath string) {
|
||
|
file, err := ioutil.ReadFile(filePath)
|
||
|
if err != nil {
|
||
|
grpclog.Fatalf("Failed to load default features: %v", err)
|
||
|
}
|
||
|
if err := json.Unmarshal(file, &s.savedFeatures); err != nil {
|
||
|
grpclog.Fatalf("Failed to load default features: %v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func toRadians(num float64) float64 {
|
||
|
return num * math.Pi / float64(180)
|
||
|
}
|
||
|
|
||
|
// calcDistance calculates the distance between two points using the "haversine" formula.
|
||
|
// This code was taken from http://www.movable-type.co.uk/scripts/latlong.html.
|
||
|
func calcDistance(p1 *pb.Point, p2 *pb.Point) int32 {
|
||
|
const CordFactor float64 = 1e7
|
||
|
const R float64 = float64(6371000) // metres
|
||
|
lat1 := float64(p1.Latitude) / CordFactor
|
||
|
lat2 := float64(p2.Latitude) / CordFactor
|
||
|
lng1 := float64(p1.Longitude) / CordFactor
|
||
|
lng2 := float64(p2.Longitude) / CordFactor
|
||
|
φ1 := toRadians(lat1)
|
||
|
φ2 := toRadians(lat2)
|
||
|
Δφ := toRadians(lat2 - lat1)
|
||
|
Δλ := toRadians(lng2 - lng1)
|
||
|
|
||
|
a := math.Sin(Δφ/2)*math.Sin(Δφ/2) +
|
||
|
math.Cos(φ1)*math.Cos(φ2)*
|
||
|
math.Sin(Δλ/2)*math.Sin(Δλ/2)
|
||
|
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
|
||
|
|
||
|
distance := R * c
|
||
|
return int32(distance)
|
||
|
}
|
||
|
|
||
|
func inRange(point *pb.Point, rect *pb.Rectangle) bool {
|
||
|
left := math.Min(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude))
|
||
|
right := math.Max(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude))
|
||
|
top := math.Max(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude))
|
||
|
bottom := math.Min(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude))
|
||
|
|
||
|
if float64(point.Longitude) >= left &&
|
||
|
float64(point.Longitude) <= right &&
|
||
|
float64(point.Latitude) >= bottom &&
|
||
|
float64(point.Latitude) <= top {
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func serialize(point *pb.Point) string {
|
||
|
return fmt.Sprintf("%d %d", point.Latitude, point.Longitude)
|
||
|
}
|
||
|
|
||
|
func newServer() *routeGuideServer {
|
||
|
s := new(routeGuideServer)
|
||
|
s.loadFeatures(*jsonDBFile)
|
||
|
s.routeNotes = make(map[string][]*pb.RouteNote)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
flag.Parse()
|
||
|
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||
|
if err != nil {
|
||
|
grpclog.Fatalf("failed to listen: %v", err)
|
||
|
}
|
||
|
var opts []grpc.ServerOption
|
||
|
if *tls {
|
||
|
creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile)
|
||
|
if err != nil {
|
||
|
grpclog.Fatalf("Failed to generate credentials %v", err)
|
||
|
}
|
||
|
opts = []grpc.ServerOption{grpc.Creds(creds)}
|
||
|
}
|
||
|
grpcServer := grpc.NewServer(opts...)
|
||
|
pb.RegisterRouteGuideServer(grpcServer, newServer())
|
||
|
grpcServer.Serve(lis)
|
||
|
}
|