diff --git a/integration/cmd/oasis/flags.go b/integration/cmd/oasis/flags.go new file mode 100644 index 00000000000..3a16c804d92 --- /dev/null +++ b/integration/cmd/oasis/flags.go @@ -0,0 +1,15 @@ +package main + +import ( + "flag" +) + +var flags struct { + listenPort int + osrmBackendEndpoint string +} + +func init() { + flag.IntVar(&flags.listenPort, "p", 8090, "Listen port.") + flag.StringVar(&flags.osrmBackendEndpoint, "osrm", "", "Backend OSRM-backend endpoint") +} diff --git a/integration/cmd/oasis/main.go b/integration/cmd/oasis/main.go new file mode 100644 index 00000000000..dc726343a8c --- /dev/null +++ b/integration/cmd/oasis/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "net/http" + "strconv" + + "github.com/Telenav/osrm-backend/integration/oasis" + "github.com/golang/glog" +) + +func main() { + mux := http.NewServeMux() + + oasisService := oasis.New(flags.osrmBackendEndpoint) + mux.Handle("/oasis", oasisService) + + // listen + listening := ":" + strconv.Itoa(flags.listenPort) + glog.Infof("Listening on %s", listening) + glog.Fatal(http.ListenAndServe(listening, mux)) +} diff --git a/integration/oasis/handler.go b/integration/oasis/handler.go new file mode 100644 index 00000000000..f4b5495716b --- /dev/null +++ b/integration/oasis/handler.go @@ -0,0 +1,54 @@ +package oasis + +import ( + "encoding/json" + "net/http" + + "github.com/Telenav/osrm-backend/integration/pkg/api/oasis" +) + +type Handler struct { + osrmBackend string +} + +func New(osrmBackend string) *Handler { + return &Handler{ + osrmBackend, + } +} + +func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(200) + json.NewEncoder(w).Encode(generateFakeOasisResponse()) +} + +func generateFakeOasisResponse() *oasis.Response { + fakeSolution1 := new(oasis.Solution) + fakeSolution1.Distance = 90.0 + fakeSolution1.Duration = 300.0 + fakeSolution1.Weight = 300.0 + fakeSolution1.WeightName = "duration" + + fakeStation1 := new(oasis.ChargeStation) + fakeStation1.Location[0] = 13.39677 + fakeStation1.Location[1] = 52.54366 + fakeStation1.WaitTime = 30.0 + fakeStation1.ChargeTime = 100.0 + fakeStation1.ChargeRange = 100.0 + fakeSolution1.ChargeStations = append(fakeSolution1.ChargeStations, fakeStation1) + + fakeStation2 := new(oasis.ChargeStation) + fakeStation2.Location[0] = 13.40677 + fakeStation2.Location[1] = 52.53333 + fakeStation2.WaitTime = 100.0 + fakeStation2.ChargeTime = 100.0 + fakeStation2.ChargeRange = 100.0 + fakeSolution1.ChargeStations = append(fakeSolution1.ChargeStations, fakeStation2) + + r := new(oasis.Response) + r.Code = "200" + r.Message = "Optimized charge station selection result:" + r.Solutions = append(r.Solutions, fakeSolution1) + + return r +} diff --git a/integration/pkg/api/oasis/genericoptions/values.go b/integration/pkg/api/oasis/genericoptions/values.go new file mode 100644 index 00000000000..10bea0c6afe --- /dev/null +++ b/integration/pkg/api/oasis/genericoptions/values.go @@ -0,0 +1,8 @@ +package genericoptions + +const ( + InvalidMaxRangeValue = -1.0 // default + InvalidCurrentRangeValue = -1.0 // default + DefaultPreferLevel = 100000 // unit:meters + DefaultSafeLevel = 100000 // unit:meters +) diff --git a/integration/pkg/api/oasis/options/options.go b/integration/pkg/api/oasis/options/options.go new file mode 100644 index 00000000000..806d3cce7c2 --- /dev/null +++ b/integration/pkg/api/oasis/options/options.go @@ -0,0 +1,8 @@ +package options + +const ( + KeyMaxRange = "max_range" + KeyCurrRange = "curr_range" + KeyPreferLevel = "prefer_level" + KeySafeLevel = "safe_level" +) diff --git a/integration/pkg/api/oasis/request.go b/integration/pkg/api/oasis/request.go new file mode 100644 index 00000000000..4c173436125 --- /dev/null +++ b/integration/pkg/api/oasis/request.go @@ -0,0 +1,168 @@ +package oasis + +import ( + "errors" + "fmt" + "math" + "net/url" + "strconv" + "strings" + + "github.com/Telenav/osrm-backend/integration/pkg/api" + "github.com/Telenav/osrm-backend/integration/pkg/api/oasis/genericoptions" + "github.com/Telenav/osrm-backend/integration/pkg/api/oasis/options" + "github.com/Telenav/osrm-backend/integration/pkg/api/osrm/coordinate" + "github.com/golang/glog" +) + +// Request for oasis service +type Request struct { + Service string + Version string + Profile string + Coordinates coordinate.Coordinates + + MaxRange float64 + CurrRange float64 + PreferLevel float64 + SafeLevel float64 +} + +// NewRequest create an empty oasis Request. +func NewRequest() *Request { + return &Request{ + // Path + Service: "oasis", + Version: "v1", + Profile: "earliest", + Coordinates: coordinate.Coordinates{}, + + // generic options + MaxRange: genericoptions.InvalidMaxRangeValue, + CurrRange: genericoptions.InvalidCurrentRangeValue, + PreferLevel: genericoptions.DefaultPreferLevel, + SafeLevel: genericoptions.DefaultSafeLevel, + } +} + +// ParseRequestURL parse Request URL to Request. +func ParseRequestURL(u *url.URL) (*Request, error) { + if u == nil { + return nil, fmt.Errorf("empty URL input") + } + + req := NewRequest() + + if err := req.parsePath(u.Path); err != nil { + return nil, err + } + + params, err := url.ParseQuery(u.RawQuery) + if err != nil { + glog.Warning(err) + return nil, err + } + + if err := req.parseQuery(params); err != nil { + glog.Warning(err) + return nil, err + } + + if err := req.validate(); err != nil { + glog.Warning(err) + return nil, err + } + + return req, nil +} + +func (r *Request) parsePath(path string) error { + p := path + p = strings.TrimPrefix(p, api.Slash) + p = strings.TrimSuffix(p, api.Slash) + + s := strings.Split(p, api.Slash) + if len(s) < 4 { + return fmt.Errorf("invalid path values %v parsed from %s", s, path) + } + r.Service = s[0] + r.Version = s[1] + r.Profile = s[2] + + var err error + if r.Coordinates, err = coordinate.ParseCoordinates(s[3]); err != nil { + return err + } + + return nil +} + +func (r *Request) parseQuery(params url.Values) error { + for k, v := range params { + if len(v) <= 0 { + continue + } + + var valfloat float64 + if s, err := strconv.ParseFloat(v[0], 64); err != nil { + return err + } else { + valfloat = float64(s) + } + + switch k { + case options.KeyMaxRange: + r.MaxRange = valfloat + case options.KeyCurrRange: + r.CurrRange = valfloat + case options.KeyPreferLevel: + r.PreferLevel = valfloat + case options.KeySafeLevel: + r.SafeLevel = valfloat + } + } + + return nil +} + +func (r *Request) validate() error { + // MaxRange must be set + if floatEquals(r.MaxRange, genericoptions.InvalidMaxRangeValue) || isFloatNegative(r.MaxRange) { + return errors.New("Invalid value for " + options.KeyMaxRange + ".") + } + + // CurrRange must be set + if floatEquals(r.CurrRange, genericoptions.InvalidCurrentRangeValue) || isFloatNegative(r.CurrRange) { + return errors.New("Invalid value for " + options.KeyCurrRange + ".") + } + + // CurrRange must be smaller or equal to MaxRange + if r.CurrRange > r.MaxRange { + return errors.New(options.KeyCurrRange + " must be smaller or equal to " + options.KeyMaxRange + ".") + } + + // CurrRange must be smaller or equal to MaxRange + if r.PreferLevel > r.MaxRange { + return errors.New(options.KeyPreferLevel + " must be smaller or equal to " + options.KeyMaxRange + ".") + } + + // CurrRange must be smaller or equal to MaxRange + if r.SafeLevel > r.MaxRange { + return errors.New(options.KeySafeLevel + " must be smaller or equal to " + options.KeyMaxRange + ".") + } + + return nil +} + +var epsilon float64 = 0.00000001 + +func floatEquals(a, b float64) bool { + if (a-b) < epsilon && (b-a) < epsilon { + return true + } + return false +} + +func isFloatNegative(a float64) bool { + return !floatEquals(math.Abs(a), a) +} diff --git a/integration/pkg/api/oasis/response.go b/integration/pkg/api/oasis/response.go new file mode 100644 index 00000000000..f342b93b1fe --- /dev/null +++ b/integration/pkg/api/oasis/response.go @@ -0,0 +1,27 @@ +package oasis + +import "github.com/Telenav/osrm-backend/integration/pkg/api/osrm/route" + +// Response for oasis service +type Response struct { + Code string `json:"code"` + Message string `json:"message,omitempty"` + Solutions []*Solution `json:"solutions,omitempty"` +} + +// Solution contains recommended charge stations +type Solution struct { + Distance float64 `json:"distance"` + Duration float64 `json:"duration"` + Weight float64 `json:"weight"` + WeightName string `json:"weight_name"` + ChargeStations []*ChargeStation `json:"charge_station"` +} + +// ChargeStation contains location, time and energy level, could be used as waypoints for routing request +type ChargeStation struct { + route.Waypoint + WaitTime float64 `json:"wait_time"` + ChargeTime float64 `json:"charge_time"` + ChargeRange float64 `json:"charge_range"` +}