From 30339c9bdd8372be84957ca8374d70d2afa4904a Mon Sep 17 00:00:00 2001 From: Arnaud Delcasse Date: Thu, 30 Mar 2023 00:45:55 +0200 Subject: [PATCH] fix decoding encoreding --- encoding/polylines/chunks.go | 95 ++++++++++++++++++++++++++++++++++ encoding/polylines/decoding.go | 56 ++++++++++++++++++++ encoding/polylines/encoding.go | 71 ++++++++++++++++++++++++- valhalla.go | 10 ++-- 4 files changed, 227 insertions(+), 5 deletions(-) create mode 100644 encoding/polylines/chunks.go diff --git a/encoding/polylines/chunks.go b/encoding/polylines/chunks.go new file mode 100644 index 0000000..08df0a1 --- /dev/null +++ b/encoding/polylines/chunks.go @@ -0,0 +1,95 @@ +package polylines + +import ( + "math" +) + +type chunks []int32 + +// Parse splices an integer into chunks +func (c *chunks) Parse(element int32) { + + c.slice(element, 5) + +} + +// ParseLine converts and splices string into integer chunks +func (c *chunks) ParseLine(line string) { + + chunkSlice := []int32{} + + for index, letter := range line { + elementInt := int32(letter) - 63 + if index != len(line)-1 { + elementInt = elementInt & 31 + } + + chunkSlice = append(chunkSlice, elementInt) + } + + *c = chunkSlice + +} + +// String returns the chunks as polyline in base64 +func (c *chunks) String() string { + + c.or() + s := "" + for i := range *c { + s += string((*c)[i]) + } + return s + +} + +// Coordinate converts integer chunks into a single coordinate +func (c *chunks) Coordinate(precision uint32) float64 { + resultInt := int32(0) + + for i, element := range *c { + resultInt += element << uint32(i*5) + } + + if resultInt&1 == 1 { + resultInt = ^resultInt + } + resultInt = resultInt >> 1 + + return float64(resultInt) / math.Pow10(int(precision)) +} + +// slice splits elements into group of "length" bits +func (c *chunks) slice(element int32, length int) { + + if element == 0 { + *c = []int32{0} + return + } + chunkSlice := []int32{} + + bitMask := int32(31) + + for i := 0; int32(math.Pow(2, float64(i))) <= element; i += 5 { + group := (element >> uint(i)) & bitMask + chunkSlice = append(chunkSlice, group) + } + + *c = chunkSlice + +} + +// or sets the 6th bit to 1 for every chunk except the last one +// as indicator bit for coordinate boundaries. +// It also adds 63 (decimal) to every group to ensure it is in +// ASCII range +func (c *chunks) or() { + + for i := range *c { + if i < len(*c)-1 { + (*c)[i] = (*c)[i] | 0x20 + } + (*c)[i] += 63 + } + +} diff --git a/encoding/polylines/decoding.go b/encoding/polylines/decoding.go index e31c0b5..7c15d65 100644 --- a/encoding/polylines/decoding.go +++ b/encoding/polylines/decoding.go @@ -6,6 +6,62 @@ import ( "github.com/paulmach/orb" ) +// // Decode decodes coordinates to the "Encoded Polyline Algorithm Format" +// // More info: https://developers.google.com/maps/documentation/utilities/polylinealgorithm +// // polyline: polyline string in "Encoded Polyline Algorithm Format" +// // precision: usually 5 or 6; Google's original algorithm uses 5 digits of decimal precision, +// // which is accurate to about a meter. A precision of 6 gives you an accuracy of about 10cm +// // more info: https://mapzen.com/blog/polyline-precision/ +// func Decode(polyline string, precision uint32) orb.LineString { + +// group := "" +// coordinates := []float64{} + +// for _, letter := range polyline { +// group += string(letter) + +// if (int32(letter)-63)&0x20 == 0 { +// coordinates = append(coordinates, decodeElement(group, precision)) +// group = "" +// } +// } + +// points := orb.LineString{} +// for i := 1; i < len(coordinates); i += 2 { +// points = append(points, orb.Point{round(coordinates[i], precision), round(coordinates[i-1], precision)}) +// } + +// for i := range points { +// if i > 0 { +// points[i][1] = round(points[i-1].Lat()+points[i].Lat(), precision) +// points[i][0] = round(points[i-1].Lon()+points[i].Lon(), precision) +// } +// } + +// return points +// } + +// // Decode5 is a short call for Decode with precision set to 5 +// // Accuracy is about one meter +// func Decode5(polyline string) orb.LineString { +// return Decode(polyline, 5) +// } + +// // Decode6 is a short call for Decode with precision set to 6 +// // Accuracy is about ten centimeters +// func Decode6(polyline string) orb.LineString { +// return Decode(polyline, 6) +// } + +// // decodeElement decodes an coordinate element (i.e. latitude or longitude) +// // from the "Encoded Polyline Algorithm Format" +// func decodeElement(group string, precision uint32) float64 { + +// var c chunks +// c.ParseLine(group) +// return c.Coordinate(precision) +// } + func Decode(encoded *string, precisionOptional ...int) orb.LineString { // default to 6 digits of precision precision := 6 diff --git a/encoding/polylines/encoding.go b/encoding/polylines/encoding.go index b093ca7..ea30882 100644 --- a/encoding/polylines/encoding.go +++ b/encoding/polylines/encoding.go @@ -5,12 +5,79 @@ import ( "github.com/twpayne/go-polyline" ) -func Encode(line orb.LineString) string { +// // Encode encodes coordinates to the "Encoded Polyline Algorithm Format" +// // More info: https://developers.google.com/maps/documentation/utilities/polylinealgorithm +// // points: points of the polyline +// // precision: usually 5 or 6; Google's original algorithm uses 5 digits of decimal precision, +// // which is accurate to about a meter. A precision of 6 gives you an accuracy of about 10cm +// // more info: https://mapzen.com/blog/polyline-precision/ +// func Encode(linestring orb.LineString, precision uint32) string { + +// encoded := "" + +// latitude := 0.0 +// longitude := 0.0 + +// for _, point := range linestring { +// polyLatitude := encodeElement(point.Lat()-latitude, precision) +// encoded += polyLatitude + +// polyLongitude := encodeElement(point.Lon()-longitude, precision) +// encoded += polyLongitude + +// // to conserve space, points only include the offset from the previous point +// latitude = point.Lat() +// longitude = point.Lon() + +// } + +// return encoded + +// } + +// // Encode5 is a short call for Encode with precision set to 5 +// // Accuracy is about one meter +// func Encode5(linestring orb.LineString) string { +// return Encode(linestring, 5) +// } + +// // Encode6 is a short call for Encode with precision set to 6 +// // Accuracy is about ten centimeters +// func Encode6(linestring orb.LineString) string { +// return Encode(linestring, 6) +// } + +// // encodeElement encodes an coordinate element (i.e. latitude or longitude) +// // to the "Encoded Polyline Algorithm Format" +// func encodeElement(element float64, precision uint32) string { + +// elementInt := int32(math.Round(element * math.Pow10(int(precision)))) +// elementInt = elementInt << 1 +// if element < 0 { +// elementInt = ^elementInt +// } + +// var c chunks +// c.Parse(elementInt) + +// return c.String() + +// } + +// // round n to precision digits +// func round(n float64, precision uint32) float64 { + +// factor := math.Pow10(int(precision)) +// return math.Round(n*factor) / factor + +// } + +func Encode(line orb.LineString, precision uint32) string { preparedLine := [][]float64{} for _, point := range line { - preparedLine = append(preparedLine, []float64{point.Lon(), point.Lat()}) + preparedLine = append(preparedLine, []float64{point.Lat(), point.Lon()}) } return string(polyline.EncodeCoords(preparedLine)) diff --git a/valhalla.go b/valhalla.go index d0b3da4..a41f30d 100644 --- a/valhalla.go +++ b/valhalla.go @@ -10,6 +10,7 @@ import ( "git.coopgo.io/coopgo-platform/routing-service/encoding/polylines" "git.coopgo.io/coopgo-platform/routing-service/proto/valhalla" "github.com/paulmach/orb" + "github.com/rs/zerolog/log" "google.golang.org/protobuf/proto" ) @@ -47,11 +48,14 @@ func (v *ValhallaRouting) Route(locations []orb.Point) (route *Route, err error) resp, err := v.protocolBufferRequest(request, "route") if err != nil { + log.Error().Err(err).Msg("pb request to valhalla error") return nil, err } + log.Debug().Any("resp", resp).Msg("valhalla response") + if resp.Directions == nil || resp.Directions.Routes == nil || len(resp.Directions.Routes) < 1 { - return nil, errors.New("no routes returnes by valhalla") + return nil, errors.New("no routes returned by valhalla") } decodedLinestring := orb.LineString{} @@ -66,7 +70,7 @@ func (v *ValhallaRouting) Route(locations []orb.Point) (route *Route, err error) routeLeg := RouteLeg{ Distance: float64(leg.Summary.Length), Duration: time.Duration(leg.Summary.Time) * time.Second, - Polyline: polylines.Encode(decodedShape), + Polyline: polylines.Encode(decodedShape, 5), } legs = append(legs, routeLeg) @@ -74,7 +78,7 @@ func (v *ValhallaRouting) Route(locations []orb.Point) (route *Route, err error) return &Route{ Summary: RouteSummary{ - Polyline: polylines.Encode(decodedLinestring), + Polyline: polylines.Encode(decodedLinestring, 5), }, Legs: legs, }, nil