package handler import ( "context" "fmt" "googlemaps.github.io/maps" "github.com/micro/micro/v3/service/errors" "github.com/micro/micro/v3/service/logger" pb "github.com/micro/services/routing/proto" ) var ( ErrDownstream = errors.InternalServerError("ROUTING_ERROR", "Unable to connect to routing provider") ErrMissingOrigin = errors.BadRequest("MISSING_ORIGIN", "Missing origin") ErrMissingDestination = errors.BadRequest("MISSING_DESTINATION", "Missing destination") ErrMissingLatitude = errors.BadRequest("MISSING_LATITUDE", "Missing latitude") ErrMissingLongitude = errors.BadRequest("MISSING_LONGITUDE", "Missing longitude") ErrNoRoutes = errors.BadRequest("NO_ROUTES", "No routes found") ErrUnimplemented = errors.InternalServerError("UNIMPLEMENTED", "endpoint is unimplemented") ) type Google struct { Maps *maps.Client } func (r *Google) Directions(ctx context.Context, req *pb.DirectionsRequest, rsp *pb.DirectionsResponse) error { // TODO: implement eta return ErrUnimplemented } // Calculate the ETAs for a route func (r *Google) Eta(ctx context.Context, req *pb.EtaRequest, rsp *pb.EtaResponse) error { // validate the request if req.Origin == nil { return ErrMissingOrigin } if req.Destination == nil { return ErrMissingDestination } if err := validatePoint(req.Origin); err != nil { return err } if err := validatePoint(req.Destination); err != nil { return err } // construct the request resp, err := r.Maps.DistanceMatrix(ctx, &maps.DistanceMatrixRequest{ Origins: []string{pointToString(req.Origin)}, Destinations: []string{pointToString(req.Destination)}, DepartureTime: "now", Units: "UnitsMetric", Mode: maps.TravelModeDriving, }) if err != nil { return err } // check the correct number of elements (route segments) were returned // from the Google API if len(resp.Rows[0].Elements) != 1 { return errors.InternalServerError("routing.eta", "Invalid downstream response. Expected %v segments but got %v", 1, len(resp.Rows[0].Elements)) } // set the response duration in seconds rsp.Duration = float64(resp.Rows[0].Elements[0].Duration.Seconds()) return nil } func (r *Google) Route(ctx context.Context, req *pb.RouteRequest, rsp *pb.RouteResponse) error { // validate the request if req.Origin == nil { return ErrMissingOrigin } if req.Destination == nil { return ErrMissingDestination } if err := validatePoint(req.Origin); err != nil { return err } if err := validatePoint(req.Destination); err != nil { return err } // query google maps routes, _, err := r.Maps.Directions(ctx, &maps.DirectionsRequest{ Origin: pointToString(req.Origin), Destination: pointToString(req.Destination), }) if err != nil { logger.Errorf("Error geocoding: %v. Origin: '%v', Destination: '%v'", err, pointToString(req.Origin), pointToString(req.Destination)) return ErrDownstream } if len(routes) == 0 { return ErrNoRoutes } // decode the points points, err := routes[0].OverviewPolyline.Decode() if err != nil { logger.Errorf("Error decoding polyline: %v", err) return ErrDownstream } // return the result rsp.Waypoints = make([]*pb.Waypoint, len(points)) for i, p := range points { rsp.Waypoints[i] = &pb.Waypoint{ Location: &pb.Point{ Latitude: p.Lat, Longitude: p.Lng, }, } } return nil } func validatePoint(p *pb.Point) error { if p.Latitude == 0.0 { return ErrMissingLatitude } if p.Longitude == 0.0 { return ErrMissingLongitude } return nil } func pointToString(p *pb.Point) string { return fmt.Sprintf("%v,%v", p.Latitude, p.Longitude) }