szczanieckiej.git

commit ec9df532b7c0c5885aa75730c24ae9ae6ccfb842

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
+}