222 lines
6.7 KiB
Go
222 lines
6.7 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
|
|
|
|
import (
|
|
"encoding/json"
|
|
"flag"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"math"
|
|
"net"
|
|
"strconv"
|
|
"time"
|
|
|
|
"golang.org/x/net/context"
|
|
"google.golang.org/grpc"
|
|
|
|
"google.golang.org/grpc/credentials"
|
|
|
|
pb "google.golang.org/grpc/examples/route_guide"
|
|
)
|
|
|
|
var (
|
|
useTLS = flag.Bool("use_tls", false, "Connection uses TLS if true, else plain TCP")
|
|
certFile = flag.String("tls_cert_file", "testdata/server1.pem", "The TLS cert file")
|
|
keyFile = flag.String("tls_key_file", "testdata/server1.key", "The TLS key file")
|
|
jsonDBFile = flag.String("route_guide_db", "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[pb.Point][]*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 *(feature.Location) == *point {
|
|
return &feature, nil
|
|
}
|
|
}
|
|
// No feature was found, return an unnamed feature
|
|
return &pb.Feature{"", point}, nil
|
|
}
|
|
|
|
// ListFeatures lists all features comtained within the given bounding Rectangle.
|
|
func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
|
|
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))
|
|
for _, feature := range s.savedFeatures {
|
|
if float64(feature.Location.Longitude) >= left &&
|
|
float64(feature.Location.Longitude) <= right &&
|
|
float64(feature.Location.Latitude) >= bottom &&
|
|
float64(feature.Location.Latitude) <= top {
|
|
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 *(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
|
|
}
|
|
point := *(in.Location)
|
|
if _, present := s.routeNotes[point]; !present {
|
|
s.routeNotes[point] = []*pb.RouteNote{in}
|
|
|
|
} else {
|
|
s.routeNotes[point] = append(s.routeNotes[point], in)
|
|
}
|
|
for _, note := range s.routeNotes[point] {
|
|
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 {
|
|
log.Fatal("Failed to load default features: %v\n", err)
|
|
}
|
|
if err := json.Unmarshal(file, &(s.savedFeatures)); err != nil {
|
|
log.Fatal("Failed to load default features: %v\n", err)
|
|
}
|
|
}
|
|
|
|
func toRadians(num float64) float64 {
|
|
return num * math.Pi / float64(180)
|
|
}
|
|
|
|
func calcDistance(p1 *pb.Point, p2 *pb.Point) int32 {
|
|
const COORD_FACTOR float64 = 1e7
|
|
const R float64 = float64(6371000) // metres
|
|
lat1 := float64(p1.Latitude) / COORD_FACTOR
|
|
lat2 := float64(p2.Latitude) / COORD_FACTOR
|
|
lon1 := float64(p1.Longitude) / COORD_FACTOR
|
|
lon2 := float64(p2.Longitude) / COORD_FACTOR
|
|
φ1 := toRadians(lat1)
|
|
φ2 := toRadians(lat2)
|
|
Δφ := toRadians(lat2 - lat1)
|
|
Δλ := toRadians(lon2 - lon1)
|
|
|
|
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 newServer() *routeGuideServer {
|
|
s := new(routeGuideServer)
|
|
s.loadFeatures(*jsonDBFile)
|
|
s.routeNotes = make(map[pb.Point][]*pb.RouteNote, 0)
|
|
return s
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
p := strconv.Itoa(*port)
|
|
lis, err := net.Listen("tcp", ":"+p)
|
|
if err != nil {
|
|
log.Fatalf("failed to listen: %v", err)
|
|
}
|
|
grpcServer := grpc.NewServer()
|
|
pb.RegisterRouteGuideServer(grpcServer, newServer())
|
|
if *useTLS {
|
|
creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile)
|
|
if err != nil {
|
|
log.Fatalf("Failed to generate credentials %v", err)
|
|
}
|
|
grpcServer.Serve(creds.NewListener(lis))
|
|
} else {
|
|
grpcServer.Serve(lis)
|
|
}
|
|
}
|