0
0
Fork 0

add support for customizing unit used in distance sorting

This commit is contained in:
Marty Schoch 2017-03-29 16:04:30 -04:00
parent fdbe669fd5
commit f44630a205
4 changed files with 75 additions and 14 deletions

View File

@ -15,6 +15,7 @@
package geo package geo
import ( import (
"fmt"
"math" "math"
"strconv" "strconv"
"strings" "strings"
@ -67,9 +68,23 @@ func ParseDistance(d string) (float64, error) {
return parsedNum, nil return parsedNum, nil
} }
// ParseDistanceUnit attempts to parse a distance unit and return the
// multiplier for converting this to meters. If the unit cannot be parsed
// then 0 and the error message is returned.
func ParseDistanceUnit(u string) (float64, error) {
for _, unit := range distanceUnits {
for _, unitSuffix := range unit.suffixes {
if u == unitSuffix {
return unit.conv, nil
}
}
}
return 0, fmt.Errorf("unknown distance unit: %s", u)
}
// Haversin computes the distance between two points. // Haversin computes the distance between two points.
// This implemenation uses the sloppy math implemenations which trade off // This implemenation uses the sloppy math implemenations which trade off
// accuracy for performance. // accuracy for performance. The distance returned is in kilometers.
func Haversin(lon1, lat1, lon2, lat2 float64) float64 { func Haversin(lon1, lat1, lon2, lat2 float64) float64 {
x1 := lat1 * degreesToRadian x1 := lat1 * degreesToRadian
x2 := lat2 * degreesToRadian x2 := lat2 * degreesToRadian

View File

@ -15,6 +15,7 @@
package geo package geo
import ( import (
"fmt"
"math" "math"
"reflect" "reflect"
"strconv" "strconv"
@ -46,6 +47,30 @@ func TestParseDistance(t *testing.T) {
} }
} }
func TestParseDistanceUnit(t *testing.T) {
tests := []struct {
dist string
want float64
wantErr error
}{
{"mi", 1609.344, nil},
{"m", 1, nil},
{"km", 1000, nil},
{"", 0, fmt.Errorf("unknown distance unit: ")},
{"kam", 0, fmt.Errorf("unknown distance unit: kam")},
}
for _, test := range tests {
got, err := ParseDistanceUnit(test.dist)
if !reflect.DeepEqual(err, test.wantErr) {
t.Errorf("expected err: %v, got %v for %s", test.wantErr, err, test.dist)
}
if got != test.want {
t.Errorf("expected distance %f got %f for %s", test.want, got, test.dist)
}
}
}
func TestHaversinDistance(t *testing.T) { func TestHaversinDistance(t *testing.T) {
earthRadiusKMs := 6378.137 earthRadiusKMs := 6378.137
halfCircle := earthRadiusKMs * math.Pi halfCircle := earthRadiusKMs * math.Pi

View File

@ -137,7 +137,7 @@ func init() {
} }
// earthDiameter returns an estimation of the earth's diameter at the specified // earthDiameter returns an estimation of the earth's diameter at the specified
// latitude // latitude in kilometers
func earthDiameter(lat float64) float64 { func earthDiameter(lat float64) float64 {
index := math.Mod(math.Abs(lat)*radiusIndexer+0.5, float64(len(earthDiameterPerLatitude))) index := math.Mod(math.Abs(lat)*radiusIndexer+0.5, float64(len(earthDiameterPerLatitude)))
if math.IsNaN(index) { if math.IsNaN(index) {

View File

@ -62,12 +62,22 @@ func ParseSearchSortObj(input map[string]interface{}) (SearchSort, error) {
if !foundLocation { if !foundLocation {
return nil, fmt.Errorf("unable to parse geo_distance location") return nil, fmt.Errorf("unable to parse geo_distance location")
} }
return &SortGeoDistance{ rvd := &SortGeoDistance{
Field: field, Field: field,
Desc: descending, Desc: descending,
lon: lon, lon: lon,
lat: lat, lat: lat,
}, nil unitMult: 1.0,
}
if distUnit, ok := input["unit"].(string); ok {
var err error
rvd.unitMult, err = geo.ParseDistanceUnit(distUnit)
if err != nil {
return nil, err
}
rvd.Unit = distUnit
}
return rvd, nil
case "field": case "field":
field, ok := input["field"].(string) field, ok := input["field"].(string)
if !ok { if !ok {
@ -546,11 +556,13 @@ var maxDistance = string(numeric.MustNewPrefixCodedInt64(math.MaxInt64, 0))
// Field is the name of the field // Field is the name of the field
// Descending reverse the sort order (default false) // Descending reverse the sort order (default false)
type SortGeoDistance struct { type SortGeoDistance struct {
Field string Field string
Desc bool Desc bool
values []string Unit string
lon float64 values []string
lat float64 lon float64
lat float64
unitMult float64
} }
// UpdateVisitor notifies this sort field that in this document // UpdateVisitor notifies this sort field that in this document
@ -581,7 +593,13 @@ func (s *SortGeoDistance) Value(i *DocumentMatch) string {
docLat := geo.MortonUnhashLat(uint64(i64)) docLat := geo.MortonUnhashLat(uint64(i64))
dist := geo.Haversin(s.lon, s.lat, docLon, docLat) dist := geo.Haversin(s.lon, s.lat, docLon, docLat)
return string(numeric.MustNewPrefixCodedInt64(int64(dist), 0)) // dist is returned in km, so convert to m
dist *= 1000
if s.unitMult != 0 {
dist /= s.unitMult
}
distInt64 := numeric.Float64ToInt64(dist)
return string(numeric.MustNewPrefixCodedInt64(distInt64, 0))
} }
// Descending determines the order of the sort // Descending determines the order of the sort
@ -628,6 +646,9 @@ func (s *SortGeoDistance) MarshalJSON() ([]byte, error) {
"lat": s.lat, "lat": s.lat,
}, },
} }
if s.Unit != "" {
sfm["unit"] = s.Unit
}
if s.Desc { if s.Desc {
sfm["desc"] = true sfm["desc"] = true
} }