From 1fce7e55823c59f239bef5a92d6bd69803edcd36 Mon Sep 17 00:00:00 2001 From: "Xun(Perry) Liu" Date: Mon, 20 Jan 2020 17:50:58 -0800 Subject: [PATCH] Implement initial connector with Telenav search (#135) * feat: Implement initial connector with telenav search Only support nearest charge station search for now. issues: https://github.com/Telenav/osrm-backend/issues/132 * fix: update code. issue: #132 --- integration/cmd/search-adhoc-request/flags.go | 17 +++ integration/cmd/search-adhoc-request/main.go | 49 ++++++++ .../pkg/api/search/coordinate/coordinate.go | 41 +++++++ .../api/search/nearbychargestation/request.go | 109 ++++++++++++++++++ .../search/nearbychargestation/response.go | 57 +++++++++ integration/pkg/api/search/options/key.go | 13 +++ integration/pkg/api/search/options/value.go | 9 ++ 7 files changed, 295 insertions(+) create mode 100644 integration/cmd/search-adhoc-request/flags.go create mode 100644 integration/cmd/search-adhoc-request/main.go create mode 100644 integration/pkg/api/search/coordinate/coordinate.go create mode 100644 integration/pkg/api/search/nearbychargestation/request.go create mode 100644 integration/pkg/api/search/nearbychargestation/response.go create mode 100644 integration/pkg/api/search/options/key.go create mode 100644 integration/pkg/api/search/options/value.go diff --git a/integration/cmd/search-adhoc-request/flags.go b/integration/cmd/search-adhoc-request/flags.go new file mode 100644 index 00000000000..f53447f7e35 --- /dev/null +++ b/integration/cmd/search-adhoc-request/flags.go @@ -0,0 +1,17 @@ +package main + +import ( + "flag" +) + +var flags struct { + entityEndpoint string + apiKey string + apiSignature string +} + +func init() { + flag.StringVar(&flags.entityEndpoint, "entity", "", "Backend entity service endpoint") + flag.StringVar(&flags.apiKey, "apikey", "", "API key for entity service") + flag.StringVar(&flags.apiSignature, "apisignature", "", "API signature for entity service") +} diff --git a/integration/cmd/search-adhoc-request/main.go b/integration/cmd/search-adhoc-request/main.go new file mode 100644 index 00000000000..e8a802eec41 --- /dev/null +++ b/integration/cmd/search-adhoc-request/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "net/http" + + "github.com/Telenav/osrm-backend/integration/pkg/api/search/coordinate" + "github.com/Telenav/osrm-backend/integration/pkg/api/search/nearbychargestation" + "github.com/Telenav/osrm-backend/integration/pkg/backend" +) + +func main() { + flag.Parse() + + if len(flags.entityEndpoint) == 0 { + fmt.Println("[ERROR] -entity should not be empty!") + return + } + + if len(flags.apiKey) == 0 { + fmt.Println("[ERROR] -apikey should not be empty.") + return + } + if len(flags.apiSignature) == 0 { + fmt.Println("[ERROR] -apisignature should not be empty.") + return + } + req := nearbychargestation.NewRequest() + req.APIKey = flags.apiKey + req.APISignature = flags.apiSignature + req.Location = coordinate.Coordinate{Lat: 37.78509, Lon: -122.41988} + + clt := http.Client{Timeout: backend.Timeout()} + requestURL := flags.entityEndpoint + req.RequestURI() + fmt.Println(requestURL) + resp, err := clt.Get(requestURL) + if err != nil { + fmt.Printf("route request %s against search failed, err %v", requestURL, err) + return + } + defer resp.Body.Close() + + var response nearbychargestation.Response + err = json.NewDecoder(resp.Body).Decode(&response) + jsonResult, _ := json.Marshal(response) + fmt.Println(string(jsonResult)) +} diff --git a/integration/pkg/api/search/coordinate/coordinate.go b/integration/pkg/api/search/coordinate/coordinate.go new file mode 100644 index 00000000000..87067e07d1d --- /dev/null +++ b/integration/pkg/api/search/coordinate/coordinate.go @@ -0,0 +1,41 @@ +package coordinate + +import ( + "fmt" + "strconv" + "strings" + + "github.com/Telenav/osrm-backend/integration/pkg/api" +) + +// Coordinate represents lat/lon of a GPS point. +type Coordinate struct { + Lat float64 + Lon float64 +} + +// String convert Coordinate to string. Lat/lon precision is 6. +func (c *Coordinate) String() string { + + s := fmt.Sprintf("%.6f%s%.6f", c.Lat, api.Comma, c.Lon) + return s +} + +// ParseCoordinate parses string to coordinate. +func ParseCoordinate(str string) (Coordinate, error) { + c := Coordinate{} + + splits := strings.Split(str, api.Comma) + if len(splits) < 2 { + return c, fmt.Errorf("parse %s to Coordinate failed", str) + } + + var err error + if c.Lat, err = strconv.ParseFloat(splits[0], 64); err != nil { + return c, fmt.Errorf("parse Lon from %s failed", str) + } + if c.Lon, err = strconv.ParseFloat(splits[1], 64); err != nil { + return c, fmt.Errorf("parse Lan from %s failed", str) + } + return c, nil +} diff --git a/integration/pkg/api/search/nearbychargestation/request.go b/integration/pkg/api/search/nearbychargestation/request.go new file mode 100644 index 00000000000..90e2fdfcf92 --- /dev/null +++ b/integration/pkg/api/search/nearbychargestation/request.go @@ -0,0 +1,109 @@ +package nearbychargestation + +import ( + "net/url" + "strconv" + + "github.com/Telenav/osrm-backend/integration/pkg/api" + "github.com/Telenav/osrm-backend/integration/pkg/api/search/coordinate" + "github.com/Telenav/osrm-backend/integration/pkg/api/search/options" + "github.com/golang/glog" +) + +// Request for search service +type Request struct { + Service string + Version string + Action string + Format string + + APIKey string + APISignature string + Category string + Location coordinate.Coordinate + Intent string + Locale string + Limit int +} + +// NewRequest create an empty entity Request. +func NewRequest() *Request { + return &Request{ + // Path + Service: "entity", + Version: "v4", + Action: "search", + Format: "json", + + // Options + APIKey: "", + APISignature: "", + Category: options.ChargeStationCategory, + Location: coordinate.Coordinate{}, + Intent: options.AroundIntent, + Locale: options.ENUSLocale, + Limit: options.DefaultLimitValue, + } +} + +// RequestURI convert RouteRequest 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() + + queryStr := r.QueryString() + if len(queryStr) > 0 { + s += api.QuestionMark + queryStr + } + + return s +} + +func (r *Request) pathPrefix() string { + //i.e. "/entity/v4/search/json?" + return api.Slash + r.Service + api.Slash + r.Version + api.Slash + r.Action + api.Slash + r.Format +} + +// QueryString convert SearchRequest 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 route Request to url.Values. +// i.e. "api_key=-&api_signature=-&query=-&location=-&intent=-&locale=-&limit=-" +func (r *Request) QueryValues() (v url.Values) { + v = make(url.Values) + + if len(r.APIKey) > 0 { + v.Add(options.KeyAPIKey, r.APIKey) + } + + if len(r.APISignature) > 0 { + v.Add(options.KeyAPISignature, r.APISignature) + } + + if len(r.Category) > 0 { + v.Add(options.KeyQuery, r.Category) + } + + coordinateStr := r.Location.String() + if len(coordinateStr) > 0 { + v.Add(options.KeyLocation, coordinateStr) + } + + if len(r.Locale) > 0 { + v.Add(options.KeyLocale, r.Locale) + } + + if r.Limit > 0 { + v.Add(options.KeyLimit, strconv.Itoa(r.Limit)) + } + + return +} diff --git a/integration/pkg/api/search/nearbychargestation/response.go b/integration/pkg/api/search/nearbychargestation/response.go new file mode 100644 index 00000000000..6e231748aae --- /dev/null +++ b/integration/pkg/api/search/nearbychargestation/response.go @@ -0,0 +1,57 @@ +package nearbychargestation + +// Response for search service +type Response struct { + Status Status `json:"status"` + ResponseTime int `json:"response_time"` + Results []*Result `json:"results"` +} + +// Status for search response +type Status struct { + Code string `json:"code"` + Message string `json:"message"` +} + +// Result for search places +type Result struct { + ID string `json:"id"` + Place Place `json:"place"` + Distance int `json:"distance"` + Facets Facet `json:"facets"` + DetailURL string `json:"detail_url"` +} + +// Place contains name, phone, address information +type Place struct { + Address []*Address `json:"address"` +} + +// Address contains coordinate information +type Address struct { + GeoCoordinate Coordinate `json:"geo_coordinates"` + NavCoordinates []*Coordinate `json:"nav_coordinates"` +} + +// Coordinate for specific location +type Coordinate struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` +} + +// Facet contains open hours, charge station status, nearby information +type Facet struct { + EVConnectors EVConnector `json:"ev_connectors"` +} + +// EVConnector contains charge station status +type EVConnector struct { + TotalNumber int `json:"total_number"` + ConnectorCounts []*ConnectorCount `json:"connector_counts"` +} + +// ConnectorCount contains charge level and related count +type ConnectorCount struct { + Level int `json:"level"` + Total int `json:"total"` +} diff --git a/integration/pkg/api/search/options/key.go b/integration/pkg/api/search/options/key.go new file mode 100644 index 00000000000..df7832209eb --- /dev/null +++ b/integration/pkg/api/search/options/key.go @@ -0,0 +1,13 @@ +package options + +// Query parameter for Telenav search service +// https://github.com/Telenav/osrm-backend/issues/132 +const ( + KeyAPIKey = "api_key" + KeyAPISignature = "api_signature" + KeyLocation = "location" + KeyQuery = "query" + KeyLimit = "limit" + KeyIntent = "intent" + KeyLocale = "locale" +) diff --git a/integration/pkg/api/search/options/value.go b/integration/pkg/api/search/options/value.go new file mode 100644 index 00000000000..37b006fc33a --- /dev/null +++ b/integration/pkg/api/search/options/value.go @@ -0,0 +1,9 @@ +package options + +// Default configuration for search service +const ( + ChargeStationCategory = "EV+Charging+Station" // default + AroundIntent = "around" // default + ENUSLocale = "en-US" // default + DefaultLimitValue = 50 // default +)