Author: Adam <git@apiote.xyz>
sort queryables near point by distance
api/api.go | 71 +++++++++++++++++++++++++++++++++++++++++++--- api/structs.go | 13 ++++++++ server/parsers.go | 17 ----------- server/router.go | 24 +++++++------- traffic/position.go | 23 +++++++++++++++
diff --git a/api/api.go b/api/api.go index 78919e92d0d01fb84d85c841733172fe3a732445..0d8b4dabfb1fc8dacdcf0571161ed7304ff04765 100644 --- a/api/api.go +++ b/api/api.go @@ -17,6 +17,7 @@ "git.sr.ht/~sircmpwn/go-bare" "github.com/adrg/strutil" "github.com/adrg/strutil/metrics" + "github.com/dhconnelly/rtreego" ) func convertTrafficAlerts(trafficAlerts []traffic.Alert) []AlertV1 { @@ -257,7 +258,7 @@ Vehicles: v, }, nil*/ } -func CreateSuccessQueryables(items []traffic.Item, context traffic.Context, t *traffic.Traffic, other QueryablesResponse) (QueryablesResponse, error) { +func CreateSuccessQueryables(items []traffic.Item, context traffic.Context, t *traffic.Traffic, other QueryablesResponse, sortByDistance bool) (QueryablesResponse, error) { success := QueryablesResponseV1{ Queryables: []QueryableV1{}, } @@ -277,7 +278,7 @@ } return success, nil } -func CreateSuccessQueryablesV2(query string, items []traffic.Item, context traffic.Context, t *traffic.Traffic, other QueryablesResponse) (QueryablesResponse, error) { +func CreateSuccessQueryablesV2(query string, items []traffic.Item, context traffic.Context, t *traffic.Traffic, other QueryablesResponse, sortByDistance bool) (QueryablesResponse, error) { success := QueryablesResponseV2{ Queryables: []QueryableV2{}, } @@ -297,11 +298,15 @@ success.Queryables = append(success.Queryables, otherV2.Queryables...) } else { return success, errors.New("wrong version of other") } - success.Queryables = sortQueryables(query, success.Queryables) + if sortByDistance { + success.Queryables = sortQueryablesByDistanceV2(query, success.Queryables) + } else { + success.Queryables = sortQueryables(query, success.Queryables) + } return success, nil } -func CreateSuccessQueryablesV3(query string, items []traffic.Item, context traffic.Context, t *traffic.Traffic, other QueryablesResponse) (QueryablesResponse, error) { +func CreateSuccessQueryablesV3(query string, items []traffic.Item, context traffic.Context, t *traffic.Traffic, other QueryablesResponse, sortByDistance bool) (QueryablesResponse, error) { success := QueryablesResponseDev{ Queryables: []QueryableV3{}, } @@ -321,7 +326,11 @@ success.Queryables = append(success.Queryables, otherV3.Queryables...) } else { return success, errors.New("wrong version of other") } - success.Queryables = sortQueryablesV3(query, success.Queryables) + if sortByDistance { + success.Queryables = sortQueryablesByDistanceV3(query, success.Queryables) + } else { + success.Queryables = sortQueryablesV3(query, success.Queryables) + } return success, nil } @@ -395,6 +404,32 @@ }) return queryables } +func sortQueryablesByDistanceV2(query string, queryables []QueryableV2) []QueryableV2 { + queryPosition, err := traffic.ParsePosition(query) + if err != nil { + log.Printf("while parsing position %s: %v\n", query, err) + return queryables + } + + stops := []StopV2{} + for _, q := range queryables { + if s, ok := q.(StopV2); ok { + stops = append(stops, s) + } + } + tree := rtreego.NewTree(2, 1, 50) + for _, stop := range stops { + tree.Insert(stop) + } + spatials := tree.NearestNeighbors(12, rtreego.Point{queryPosition.Lat, queryPosition.Lon}) + queryables = make([]QueryableV2, len(spatials)) + for i, spatial := range spatials { + queryables[i] = spatial.(StopV2) + } + + return queryables +} + func sortQueryablesV3(query string, queryables []QueryableV3) []QueryableV3 { // fixme query and names should be cleaned sort.Slice(queryables, func(i, j int) bool { @@ -422,6 +457,32 @@ distance1 := strutil.Similarity(query, nameI, levenshtein) distance2 := strutil.Similarity(query, nameJ, levenshtein) return distance1 > distance2 }) + return queryables +} + +func sortQueryablesByDistanceV3(query string, queryables []QueryableV3) []QueryableV3 { + queryPosition, err := traffic.ParsePosition(query) + if err != nil { + log.Printf("while parsing position %s: %v\n", query, err) + return queryables + } + + stops := []StopV2{} + for _, q := range queryables { + if s, ok := q.(StopV2); ok { + stops = append(stops, s) + } + } + tree := rtreego.NewTree(2, 1, 50) + for _, stop := range stops { + tree.Insert(stop) + } + spatials := tree.NearestNeighbors(12, rtreego.Point{queryPosition.Lat, queryPosition.Lon}) + queryables = make([]QueryableV3, len(spatials)) + for i, spatial := range spatials { + queryables[i] = spatial.(StopV2) + } + return queryables } diff --git a/api/structs.go b/api/structs.go index d3901e5c276938a65893ea8b52bc70c60640f31a..840258175ddfbc64b2dc40bd524cc4311545bef7 100644 --- a/api/structs.go +++ b/api/structs.go @@ -1,6 +1,19 @@ package api +import "github.com/dhconnelly/rtreego" + type TimedStopStub struct { // StopStub Time uint } + +func (s StopV2) Bounds() *rtreego.Rect { + rect, err := rtreego.NewRectFromPoints( + rtreego.Point{s.Position.Lat, s.Position.Lon}, + rtreego.Point{s.Position.Lat, s.Position.Lon}, + ) + if err != nil { + panic(err.Error()) + } + return rect +} diff --git a/server/parsers.go b/server/parsers.go index 6e69d233af51b79db7dcbd0f10ce560ef641f0e7..5fe5c6b537a71f83a3d1da1a7d99937706278ced 100644 --- a/server/parsers.go +++ b/server/parsers.go @@ -4,7 +4,6 @@ import ( "apiote.xyz/p/szczanieckiej/traffic" traffic_errors "apiote.xyz/p/szczanieckiej/traffic/errors" - "fmt" "net/http" "strconv" "strings" @@ -62,19 +61,3 @@ } } return versionCode, date, nil } - -func parsePosition(location string) (traffic.Position, error) { - locationString := strings.Split(location, ",") - if len(locationString) != 2 { - return traffic.Position{}, fmt.Errorf("location is not two numbers") - } - lat, err := strconv.ParseFloat(locationString[0], 64) - if err != nil { - return traffic.Position{}, fmt.Errorf("latitude is not a float") - } - lon, err := strconv.ParseFloat(locationString[1], 64) - if err != nil { - return traffic.Position{}, fmt.Errorf("longitude is not a float") - } - return traffic.Position{Lat: lat, Lon: lon}, nil -} diff --git a/server/router.go b/server/router.go index f7675d44ba113e8279dcf8376211422dea8809eb..ba34211f7a60da274f71223e70d4304b5b39c133 100644 --- a/server/router.go +++ b/server/router.go @@ -88,7 +88,7 @@ if err != nil { return fmt.Errorf("while parsing form: %w", err) } dateString := r.Form.Get("date") - lb, err := parsePosition(r.Form.Get("lb")) + lb, err := traffic.ParsePosition(r.Form.Get("lb")) if err != nil { return ServerError{ code: http.StatusBadRequest, @@ -97,7 +97,7 @@ value: r.Form.Get("lb"), err: err, } } - rt, err := parsePosition(r.Form.Get("rt")) + rt, err := traffic.ParsePosition(r.Form.Get("rt")) if err != nil { return ServerError{ code: http.StatusBadRequest, @@ -215,7 +215,7 @@ Version: versionCode, } if near != "" { - location, err := parsePosition(near) + location, err := traffic.ParsePosition(near) if err != nil { return ServerError{ code: http.StatusBadRequest, @@ -234,11 +234,11 @@ items = append(items, stop) } switch accept { case 0: - queryablesSuccess, err = api.CreateSuccessQueryablesV3(query, items, context, t, queryablesSuccess) + queryablesSuccess, err = api.CreateSuccessQueryablesV3(near, items, context, t, queryablesSuccess, true) case 1: - queryablesSuccess, err = api.CreateSuccessQueryables(items, context, t, queryablesSuccess) + queryablesSuccess, err = api.CreateSuccessQueryables(items, context, t, queryablesSuccess, true) case 2: - queryablesSuccess, err = api.CreateSuccessQueryablesV2(query, items, context, t, queryablesSuccess) + queryablesSuccess, err = api.CreateSuccessQueryablesV2(near, items, context, t, queryablesSuccess, true) } if err != nil { return fmt.Errorf("while creating stopsSuccess from near stops: %w", err) @@ -254,11 +254,11 @@ return fmt.Errorf("while getting stop: %w", err) } switch accept { case 0: - queryablesSuccess, err = api.CreateSuccessQueryablesV3(query, []traffic.Item{stop}, context, t, queryablesSuccess) + queryablesSuccess, err = api.CreateSuccessQueryablesV3(query, []traffic.Item{stop}, context, t, queryablesSuccess, false) case 1: - queryablesSuccess, err = api.CreateSuccessQueryables([]traffic.Item{stop}, context, t, queryablesSuccess) + queryablesSuccess, err = api.CreateSuccessQueryables([]traffic.Item{stop}, context, t, queryablesSuccess, false) case 2: - queryablesSuccess, err = api.CreateSuccessQueryablesV2(query, []traffic.Item{stop}, context, t, queryablesSuccess) + queryablesSuccess, err = api.CreateSuccessQueryablesV2(query, []traffic.Item{stop}, context, t, queryablesSuccess, false) } if err != nil { return fmt.Errorf("while creating stopsSuccess from code: %w", err) @@ -283,11 +283,11 @@ } switch accept { case 0: - queryablesSuccess, err = api.CreateSuccessQueryablesV3(query, items, context, t, queryablesSuccess) + queryablesSuccess, err = api.CreateSuccessQueryablesV3(query, items, context, t, queryablesSuccess, false) case 1: - queryablesSuccess, err = api.CreateSuccessQueryables(items, context, t, queryablesSuccess) + queryablesSuccess, err = api.CreateSuccessQueryables(items, context, t, queryablesSuccess, false) case 2: - queryablesSuccess, err = api.CreateSuccessQueryablesV2(query, items, context, t, queryablesSuccess) + queryablesSuccess, err = api.CreateSuccessQueryablesV2(query, items, context, t, queryablesSuccess, false) } if err != nil { return fmt.Errorf("while creating stopsSuccess from lines and stops: %w", err) diff --git a/traffic/position.go b/traffic/position.go new file mode 100644 index 0000000000000000000000000000000000000000..017cb877a49f967d1e27a10f0cc570a21712c935 --- /dev/null +++ b/traffic/position.go @@ -0,0 +1,23 @@ +package traffic + +import ( + "fmt" + "strconv" + "strings" +) + +func ParsePosition(location string) (Position, error) { + locationString := strings.Split(location, ",") + if len(locationString) != 2 { + return Position{}, fmt.Errorf("location is not two numbers") + } + lat, err := strconv.ParseFloat(locationString[0], 64) + if err != nil { + return Position{}, fmt.Errorf("latitude is not a float") + } + lon, err := strconv.ParseFloat(locationString[1], 64) + if err != nil { + return Position{}, fmt.Errorf("longitude is not a float") + } + return Position{Lat: lat, Lon: lon}, nil +}