From c7540369d7443e96b6a5ac34ccdbae5dba081fbf Mon Sep 17 00:00:00 2001 From: "Xun(Perry) Liu" Date: Wed, 5 Feb 2020 09:56:40 -0800 Subject: [PATCH] Feature/osrm table connector (#139) * feat: initial implementation for osrm table connector issue: https://github.com/Telenav/osrm-backend/issues/137 * feat: implement request for table service issue: https://github.com/Telenav/osrm-backend/issues/137 * feat: implement osrm table response issue: https://github.com/Telenav/osrm-backend/issues/137 * fix: fix decoded problem related with table response issue: https://github.com/Telenav/osrm-backend/issues/137 * fix: remove comments issue: https://github.com/Telenav/osrm-backend/issues/137 * fix: fix typo. issue: https://github.com/Telenav/osrm-backend/issues/137 * fix: add default value for Request's element issue: https://github.com/Telenav/osrm-backend/issues/137 * fix: add comments issue: https://github.com/Telenav/osrm-backend/issues/137 * fix: modify comments issue: https://github.com/Telenav/osrm-backend/issues/137 * fix: refactor code. issue: https://github.com/Telenav/osrm-backend/issues/137 * fix: in table.response using osrmtype.waypoint instead of route.waypoint issue: https://github.com/Telenav/osrm-backend/issues/137 --- integration/pkg/api/osrm/osrmtype/waypoint.go | 9 + integration/pkg/api/osrm/table/options/key.go | 8 + .../pkg/api/osrm/table/options/parser.go | 30 ++++ .../pkg/api/osrm/table/options/values.go | 12 ++ integration/pkg/api/osrm/table/request.go | 158 ++++++++++++++++++ .../pkg/api/osrm/table/request_test.go | 98 +++++++++++ integration/pkg/api/osrm/table/response.go | 15 ++ 7 files changed, 330 insertions(+) create mode 100644 integration/pkg/api/osrm/osrmtype/waypoint.go create mode 100644 integration/pkg/api/osrm/table/options/key.go create mode 100644 integration/pkg/api/osrm/table/options/parser.go create mode 100644 integration/pkg/api/osrm/table/options/values.go create mode 100644 integration/pkg/api/osrm/table/request.go create mode 100644 integration/pkg/api/osrm/table/request_test.go create mode 100644 integration/pkg/api/osrm/table/response.go diff --git a/integration/pkg/api/osrm/osrmtype/waypoint.go b/integration/pkg/api/osrm/osrmtype/waypoint.go new file mode 100644 index 00000000000..2d28590b2eb --- /dev/null +++ b/integration/pkg/api/osrm/osrmtype/waypoint.go @@ -0,0 +1,9 @@ +package osrmtype + +// Waypoint object used to describe waypoint used in route or table. +type Waypoint struct { + Name string `json:"name"` + Location [2]float64 `json:"location,omitempty"` // [longitude, latitude] + Distance float64 `json:"distance"` + Hint string `json:"hint"` +} diff --git a/integration/pkg/api/osrm/table/options/key.go b/integration/pkg/api/osrm/table/options/key.go new file mode 100644 index 00000000000..0648b790ee6 --- /dev/null +++ b/integration/pkg/api/osrm/table/options/key.go @@ -0,0 +1,8 @@ +package options + +// Table service Query Parameter/Option Keys +const ( + KeySources = "sources" + KeyDestinations = "destinations" + KeyAnnotations = "annotations" +) diff --git a/integration/pkg/api/osrm/table/options/parser.go b/integration/pkg/api/osrm/table/options/parser.go new file mode 100644 index 00000000000..15f25bf5f32 --- /dev/null +++ b/integration/pkg/api/osrm/table/options/parser.go @@ -0,0 +1,30 @@ +package options + +import ( + "fmt" + "strings" + + "github.com/Telenav/osrm-backend/integration/pkg/api" + "github.com/golang/glog" +) + +// ParseAnnotations parses table service Annotations option. +func ParseAnnotations(s string) (string, error) { + + validAnnotationsValues := map[string]struct{}{ + AnnotationsValueDistance: struct{}{}, + AnnotationsValueDuration: struct{}{}, + } + + splits := strings.Split(s, api.Comma) + for _, split := range splits { + if _, found := validAnnotationsValues[split]; !found { + + err := fmt.Errorf("invalid annotations value: %s", s) + glog.Warning(err) + return "", err + } + } + + return s, nil +} diff --git a/integration/pkg/api/osrm/table/options/values.go b/integration/pkg/api/osrm/table/options/values.go new file mode 100644 index 00000000000..fd8099f8bca --- /dev/null +++ b/integration/pkg/api/osrm/table/options/values.go @@ -0,0 +1,12 @@ +package options + +// Annotations default value +const ( + AnnotationsDefaultValue = AnnotationsValueDuration +) + +// Annotations values +const ( + AnnotationsValueDistance = "distance" + AnnotationsValueDuration = "duration" +) diff --git a/integration/pkg/api/osrm/table/request.go b/integration/pkg/api/osrm/table/request.go new file mode 100644 index 00000000000..6765e6d5e75 --- /dev/null +++ b/integration/pkg/api/osrm/table/request.go @@ -0,0 +1,158 @@ +package table + +import ( + "fmt" + "net/url" + "strings" + + "github.com/Telenav/osrm-backend/integration/pkg/api" + "github.com/Telenav/osrm-backend/integration/pkg/api/osrm/coordinate" + "github.com/Telenav/osrm-backend/integration/pkg/api/osrm/genericoptions" + "github.com/Telenav/osrm-backend/integration/pkg/api/osrm/table/options" + "github.com/golang/glog" +) + +// Request for OSRM table service +// http://project-osrm.org/docs/v5.5.1/api/#table-service +type Request struct { + // Path + Service string + Version string + Profile string + Coordinates coordinate.Coordinates + + // Options + Sources genericoptions.Elements + Destinations genericoptions.Elements + Annotations string +} + +// NewRequest create an empty table Request. +func NewRequest() *Request { + + return &Request{ + // Path + Service: "table", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{}, + + // Options + Sources: genericoptions.Elements{}, + Destinations: genericoptions.Elements{}, + Annotations: options.AnnotationsDefaultValue, + } + +} + +// 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 + } + + req.parseQuery(api.ParseQueryDiscardError(u.RawQuery)) + + 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(values url.Values) { + + if v := values.Get(options.KeySources); len(v) > 0 { + if sources, err := genericoptions.ParseElemenets(v); err == nil { + r.Sources = sources + } + } + + if v := values.Get(options.KeyDestinations); len(v) > 0 { + if destinations, err := genericoptions.ParseElemenets(v); err == nil { + r.Destinations = destinations + } + } + + if v := values.Get(options.KeyAnnotations); len(v) > 0 { + if annotations, err := options.ParseAnnotations(v); err == nil { + r.Annotations = annotations + } + } +} + +// RequestURI convert TableRequest to RequestURI (e.g. "/path?foo=bar"). +// see more in https://golang.org/pkg/net/url/#URL.RequestURI +func (r *Request) RequestURI() string { + s := r.pathPrefix() + + coordinatesStr := r.Coordinates.String() + if len(coordinatesStr) > 0 { + s += coordinatesStr + } + + queryStr := r.QueryString() + if len(queryStr) > 0 { + s += api.QuestionMark + queryStr + } + + return s +} + +func (r *Request) pathPrefix() string { + //i.e. "/table/v1/driving/" + return api.Slash + r.Service + api.Slash + r.Version + api.Slash + r.Profile + api.Slash +} + +// QueryString convert TableRequest to "URL encoded" form ("bar=baz&foo=quux"), but NOT escape. +func (r *Request) QueryString() string { + rawQuery := r.QueryValues().Encode() + query, err := url.QueryUnescape(rawQuery) + if err != nil { + glog.Warning(err) + return rawQuery // use rawQuery if unescape fail + } + return query +} + +// QueryValues convert table Request to url.Values. +func (r *Request) QueryValues() (v url.Values) { + v = make(url.Values) + + if len(r.Sources) > 0 { + v.Add(options.KeySources, r.Sources.String()) + } + + if len(r.Destinations) > 0 { + v.Add(options.KeyDestinations, r.Destinations.String()) + } + + if len(r.Annotations) > 0 { + v.Add(options.KeyAnnotations, r.Annotations) + } + + return +} diff --git a/integration/pkg/api/osrm/table/request_test.go b/integration/pkg/api/osrm/table/request_test.go new file mode 100644 index 00000000000..0b31c989a49 --- /dev/null +++ b/integration/pkg/api/osrm/table/request_test.go @@ -0,0 +1,98 @@ +package table + +import ( + "testing" + + "github.com/Telenav/osrm-backend/integration/pkg/api/osrm/coordinate" + "github.com/Telenav/osrm-backend/integration/pkg/api/osrm/genericoptions" +) + +func TestTableRequestURI(t *testing.T) { + cases := []struct { + r Request + expect string + }{ + { + Request{ + Service: "table", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + }, + "/table/v1/driving/-122.006349,37.364336;-121.875654,37.313767", + }, + { + Request{ + Service: "table", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}, coordinate.Coordinate{Lat: 37.313769, Lon: -121.875655}}, + }, + "/table/v1/driving/-122.006349,37.364336;-121.875654,37.313767;-121.875655,37.313769", + }, + { + Request{ + Service: "table", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}, coordinate.Coordinate{Lat: 37.313769, Lon: -121.875655}}, + Sources: genericoptions.Elements{"0"}, + }, + "/table/v1/driving/-122.006349,37.364336;-121.875654,37.313767;-121.875655,37.313769?sources=0", + }, + { + Request{ + Service: "table", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}, coordinate.Coordinate{Lat: 37.313769, Lon: -121.875655}}, + Sources: genericoptions.Elements{"0"}, + Destinations: genericoptions.Elements{"1", "2"}, + }, + "/table/v1/driving/-122.006349,37.364336;-121.875654,37.313767;-121.875655,37.313769?destinations=1;2&sources=0", + }, + { + Request{ + Service: "table", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}, coordinate.Coordinate{Lat: 37.313769, Lon: -121.875655}}, + Sources: genericoptions.Elements{"0"}, + Destinations: genericoptions.Elements{"1", "2"}, + Annotations: "duration", + }, + "/table/v1/driving/-122.006349,37.364336;-121.875654,37.313767;-121.875655,37.313769?annotations=duration&destinations=1;2&sources=0", + }, + { + Request{ + Service: "table", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}, coordinate.Coordinate{Lat: 37.313769, Lon: -121.875655}}, + Sources: genericoptions.Elements{"0"}, + Destinations: genericoptions.Elements{"1", "2"}, + Annotations: "distance", + }, + "/table/v1/driving/-122.006349,37.364336;-121.875654,37.313767;-121.875655,37.313769?annotations=distance&destinations=1;2&sources=0", + }, + { + Request{ + Service: "table", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}, coordinate.Coordinate{Lat: 37.313769, Lon: -121.875655}}, + Sources: genericoptions.Elements{"0"}, + Destinations: genericoptions.Elements{"1", "2"}, + Annotations: "duration,distance", + }, + "/table/v1/driving/-122.006349,37.364336;-121.875654,37.313767;-121.875655,37.313769?annotations=duration,distance&destinations=1;2&sources=0", + }, + } + + for _, c := range cases { + s := c.r.RequestURI() + if s != c.expect { + t.Errorf("%v QueryString(), expect %s, but got %s", c.r, c.expect, s) + } + } +} diff --git a/integration/pkg/api/osrm/table/response.go b/integration/pkg/api/osrm/table/response.go new file mode 100644 index 00000000000..361186705e9 --- /dev/null +++ b/integration/pkg/api/osrm/table/response.go @@ -0,0 +1,15 @@ +package table + +import ( + "github.com/Telenav/osrm-backend/integration/pkg/api/osrm/osrmtype" +) + +// Response represents OSRM api v1 table response. +type Response struct { + Code string `json:"code"` + Message string `json:"message,omitempty"` + Sources []*osrmtype.Waypoint `json:"sources"` + Destinations []*osrmtype.Waypoint `json:"destinations"` + Durations [][]*float64 `json:"durations"` + Distances [][]*float64 `json:"distances"` +}