geo review comments from sreekanth
also one fix came from steve, i must have forgotten to push that commit up before merging
This commit is contained in:
parent
024877f311
commit
6554e9624f
|
@ -65,14 +65,12 @@ func ExtractGeoPoint(thing interface{}) (lon, lat float64, success bool) {
|
||||||
if lval, ok := l["lat"]; ok {
|
if lval, ok := l["lat"]; ok {
|
||||||
lat, foundLat = extractNumericVal(lval)
|
lat, foundLat = extractNumericVal(lval)
|
||||||
}
|
}
|
||||||
return lon, lat, foundLon && foundLat
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// now try reflection on struct fields
|
// now try reflection on struct fields
|
||||||
if thingVal.IsValid() && thingVal.Kind() == reflect.Struct {
|
if thingVal.IsValid() && thingVal.Kind() == reflect.Struct {
|
||||||
for i := 0; i < thingVal.NumField(); i++ {
|
for i := 0; i < thingVal.NumField(); i++ {
|
||||||
field := thingTyp.Field(i)
|
fieldName := thingTyp.Field(i).Name
|
||||||
fieldName := field.Name
|
|
||||||
if strings.HasPrefix(strings.ToLower(fieldName), "lon") {
|
if strings.HasPrefix(strings.ToLower(fieldName), "lon") {
|
||||||
if thingVal.Field(i).CanInterface() {
|
if thingVal.Field(i).CanInterface() {
|
||||||
fieldVal := thingVal.Field(i).Interface()
|
fieldVal := thingVal.Field(i).Interface()
|
||||||
|
@ -112,7 +110,7 @@ func ExtractGeoPoint(thing interface{}) (lon, lat float64, success bool) {
|
||||||
return lon, lat, foundLon && foundLat
|
return lon, lat, foundLon && foundLat
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract numeric value (if possible) and returna s float64
|
// extract numeric value (if possible) and returns a float64
|
||||||
func extractNumericVal(v interface{}) (float64, bool) {
|
func extractNumericVal(v interface{}) (float64, bool) {
|
||||||
val := reflect.ValueOf(v)
|
val := reflect.ValueOf(v)
|
||||||
typ := val.Type()
|
typ := val.Type()
|
||||||
|
|
4
query.go
4
query.go
|
@ -193,9 +193,9 @@ func NewGeoBoundingBoxQuery(topLeftLon, topLeftLat, bottomRightLon, bottomRightL
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGeoDistanceQuery creates a new Query for performing geo bounding
|
// NewGeoDistanceQuery creates a new Query for performing geo bounding
|
||||||
// box searches. The arguments describe a position and a distance. Docuements
|
// box searches. The arguments describe a position and a distance. Documents
|
||||||
// which have an indexed geo point which is less than or equal to the provided
|
// which have an indexed geo point which is less than or equal to the provided
|
||||||
// distance will be returned.
|
// distance from the given position will be returned.
|
||||||
func NewGeoDistanceQuery(lon, lat float64, distance string) *query.GeoDistanceQuery {
|
func NewGeoDistanceQuery(lon, lat float64, distance string) *query.GeoDistanceQuery {
|
||||||
return query.NewGeoDistanceQuery(lon, lat, distance)
|
return query.NewGeoDistanceQuery(lon, lat, distance)
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ func (q *GeoBoundingBoxQuery) UnmarshalJSON(data []byte) error {
|
||||||
q.TopLeft = []float64{lon, lat}
|
q.TopLeft = []float64{lon, lat}
|
||||||
lon, lat, found = geo.ExtractGeoPoint(tmp.BottomRight)
|
lon, lat, found = geo.ExtractGeoPoint(tmp.BottomRight)
|
||||||
if !found {
|
if !found {
|
||||||
return fmt.Errorf("geo location top_left not in a valid format")
|
return fmt.Errorf("geo location bottom_right not in a valid format")
|
||||||
}
|
}
|
||||||
q.BottomRight = []float64{lon, lat}
|
q.BottomRight = []float64{lon, lat}
|
||||||
q.FieldVal = tmp.FieldVal
|
q.FieldVal = tmp.FieldVal
|
||||||
|
|
|
@ -56,7 +56,8 @@ func (q *GeoDistanceQuery) Field() string {
|
||||||
return q.FieldVal
|
return q.FieldVal
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *GeoDistanceQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, options search.SearcherOptions) (search.Searcher, error) {
|
func (q *GeoDistanceQuery) Searcher(i index.IndexReader, m mapping.IndexMapping,
|
||||||
|
options search.SearcherOptions) (search.Searcher, error) {
|
||||||
field := q.FieldVal
|
field := q.FieldVal
|
||||||
if q.FieldVal == "" {
|
if q.FieldVal == "" {
|
||||||
field = m.DefaultSearchField()
|
field = m.DefaultSearchField()
|
||||||
|
@ -67,7 +68,8 @@ func (q *GeoDistanceQuery) Searcher(i index.IndexReader, m mapping.IndexMapping,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return searcher.NewGeoPointDistanceSearcher(i, q.Location[0], q.Location[1], dist, field, q.BoostVal.Value(), options)
|
return searcher.NewGeoPointDistanceSearcher(i, q.Location[0], q.Location[1],
|
||||||
|
dist, field, q.BoostVal.Value(), options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *GeoDistanceQuery) Validate() error {
|
func (q *GeoDistanceQuery) Validate() error {
|
||||||
|
|
|
@ -36,7 +36,10 @@ type GeoBoundingBoxSearcher struct {
|
||||||
searcher *DisjunctionSearcher
|
searcher *DisjunctionSearcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGeoBoundingBoxSearcher(indexReader index.IndexReader, minLon, minLat, maxLon, maxLat float64, field string, boost float64, options search.SearcherOptions, checkBoundaries bool) (*GeoBoundingBoxSearcher, error) {
|
func NewGeoBoundingBoxSearcher(indexReader index.IndexReader, minLon, minLat,
|
||||||
|
maxLon, maxLat float64, field string, boost float64,
|
||||||
|
options search.SearcherOptions, checkBoundaries bool) (
|
||||||
|
*GeoBoundingBoxSearcher, error) {
|
||||||
var openedSearchers []search.Searcher
|
var openedSearchers []search.Searcher
|
||||||
cleanupOpenedSearchers := func() {
|
cleanupOpenedSearchers := func() {
|
||||||
for _, s := range openedSearchers {
|
for _, s := range openedSearchers {
|
||||||
|
@ -72,49 +75,57 @@ func NewGeoBoundingBoxSearcher(indexReader index.IndexReader, minLon, minLat, ma
|
||||||
|
|
||||||
var filterOnBoundarySearcher search.Searcher
|
var filterOnBoundarySearcher search.Searcher
|
||||||
if len(termsOnBoundary) > 0 {
|
if len(termsOnBoundary) > 0 {
|
||||||
onBoundarySearcher, err := NewDisjunctionSearcher(indexReader, termsOnBoundary, 0, options)
|
onBoundarySearcher, err := NewDisjunctionSearcher(indexReader,
|
||||||
|
termsOnBoundary, 0, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanupOpenedSearchers()
|
cleanupOpenedSearchers()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
filterOnBoundarySearcher = NewFilteringSearcher(onBoundarySearcher, func(d *search.DocumentMatch) bool {
|
filterOnBoundarySearcher = NewFilteringSearcher(onBoundarySearcher,
|
||||||
var lon, lat float64
|
func(d *search.DocumentMatch) bool {
|
||||||
var found bool
|
var lon, lat float64
|
||||||
err = indexReader.DocumentVisitFieldTerms(d.IndexInternalID, []string{field}, func(field string, term []byte) {
|
var found bool
|
||||||
// only consider the values which are shifted 0
|
err = indexReader.DocumentVisitFieldTerms(d.IndexInternalID,
|
||||||
prefixCoded := numeric.PrefixCoded(term)
|
[]string{field}, func(field string, term []byte) {
|
||||||
var shift uint
|
// only consider the values which are shifted 0
|
||||||
shift, err = prefixCoded.Shift()
|
prefixCoded := numeric.PrefixCoded(term)
|
||||||
if err == nil && shift == 0 {
|
var shift uint
|
||||||
var i64 int64
|
shift, err = prefixCoded.Shift()
|
||||||
i64, err = prefixCoded.Int64()
|
if err == nil && shift == 0 {
|
||||||
if err == nil {
|
var i64 int64
|
||||||
lon = geo.MortonUnhashLon(uint64(i64))
|
i64, err = prefixCoded.Int64()
|
||||||
lat = geo.MortonUnhashLat(uint64(i64))
|
if err == nil {
|
||||||
found = true
|
lon = geo.MortonUnhashLon(uint64(i64))
|
||||||
}
|
lat = geo.MortonUnhashLat(uint64(i64))
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err == nil && found {
|
||||||
|
return geo.BoundingBoxContains(lon, lat,
|
||||||
|
minLon, minLat, maxLon, maxLat)
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
if err == nil && found {
|
|
||||||
return geo.BoundingBoxContains(lon, lat, minLon, minLat, maxLon, maxLat)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
openedSearchers = append(openedSearchers, filterOnBoundarySearcher)
|
openedSearchers = append(openedSearchers, filterOnBoundarySearcher)
|
||||||
}
|
}
|
||||||
notOnBoundarySearcher, err := NewDisjunctionSearcher(indexReader, termsNotOnBoundary, 0, options)
|
notOnBoundarySearcher, err := NewDisjunctionSearcher(indexReader,
|
||||||
|
termsNotOnBoundary, 0, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanupOpenedSearchers()
|
cleanupOpenedSearchers()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
openedSearchers = append(openedSearchers, notOnBoundarySearcher)
|
openedSearchers = append(openedSearchers, notOnBoundarySearcher)
|
||||||
|
|
||||||
// if there is no filterOnBoundary searcher, just return the notOnBoundarySearcher
|
// if there is no filterOnBoundary searcher,
|
||||||
|
// just return the notOnBoundarySearcher
|
||||||
if filterOnBoundarySearcher == nil {
|
if filterOnBoundarySearcher == nil {
|
||||||
rv.searcher = notOnBoundarySearcher
|
rv.searcher = notOnBoundarySearcher
|
||||||
return rv, nil
|
return rv, nil
|
||||||
}
|
}
|
||||||
rv.searcher, err = NewDisjunctionSearcher(indexReader, []search.Searcher{filterOnBoundarySearcher, notOnBoundarySearcher}, 0, options)
|
rv.searcher, err = NewDisjunctionSearcher(indexReader,
|
||||||
|
[]search.Searcher{filterOnBoundarySearcher, notOnBoundarySearcher},
|
||||||
|
0, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanupOpenedSearchers()
|
cleanupOpenedSearchers()
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -134,11 +145,13 @@ func (s *GeoBoundingBoxSearcher) SetQueryNorm(qnorm float64) {
|
||||||
s.searcher.SetQueryNorm(qnorm)
|
s.searcher.SetQueryNorm(qnorm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GeoBoundingBoxSearcher) Next(ctx *search.SearchContext) (*search.DocumentMatch, error) {
|
func (s *GeoBoundingBoxSearcher) Next(ctx *search.SearchContext) (
|
||||||
|
*search.DocumentMatch, error) {
|
||||||
return s.searcher.Next(ctx)
|
return s.searcher.Next(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GeoBoundingBoxSearcher) Advance(ctx *search.SearchContext, ID index.IndexInternalID) (*search.DocumentMatch, error) {
|
func (s *GeoBoundingBoxSearcher) Advance(ctx *search.SearchContext,
|
||||||
|
ID index.IndexInternalID) (*search.DocumentMatch, error) {
|
||||||
return s.searcher.Advance(ctx, ID)
|
return s.searcher.Advance(ctx, ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,10 +191,17 @@ func (s *GeoBoundingBoxSearcher) relateAndRecurse(start, end uint64, res uint) {
|
||||||
|
|
||||||
level := ((geo.GeoBits << 1) - res) >> 1
|
level := ((geo.GeoBits << 1) - res) >> 1
|
||||||
|
|
||||||
within := res%document.GeoPrecisionStep == 0 && geo.RectWithin(minLon, minLat, maxLon, maxLat, s.minLon, s.minLat, s.maxLon, s.maxLat)
|
within := res%document.GeoPrecisionStep == 0 &&
|
||||||
if within || (level == geoDetailLevel && geo.RectIntersects(minLon, minLat, maxLon, maxLat, s.minLon, s.minLat, s.maxLon, s.maxLat)) {
|
geo.RectWithin(minLon, minLat, maxLon, maxLat,
|
||||||
s.rangeBounds = append(s.rangeBounds, newGeoRange(start, res, level, !within))
|
s.minLon, s.minLat, s.maxLon, s.maxLat)
|
||||||
} else if level < geoDetailLevel && geo.RectIntersects(minLon, minLat, maxLon, maxLat, s.minLon, s.minLat, s.maxLon, s.maxLat) {
|
if within || (level == geoDetailLevel &&
|
||||||
|
geo.RectIntersects(minLon, minLat, maxLon, maxLat,
|
||||||
|
s.minLon, s.minLat, s.maxLon, s.maxLat)) {
|
||||||
|
s.rangeBounds = append(s.rangeBounds,
|
||||||
|
newGeoRange(start, res, level, !within))
|
||||||
|
} else if level < geoDetailLevel &&
|
||||||
|
geo.RectIntersects(minLon, minLat, maxLon, maxLat,
|
||||||
|
s.minLon, s.minLat, s.maxLon, s.maxLat) {
|
||||||
s.computeRange(start, res-1)
|
s.computeRange(start, res-1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,10 @@ func TestGeoBoundingBox(t *testing.T) {
|
||||||
{0.001, 0.001, 0.002, 0.002, "loc", []string{"a"}},
|
{0.001, 0.001, 0.002, 0.002, "loc", []string{"a"}},
|
||||||
{0.001, 0.001, 1.002, 1.002, "loc", []string{"a", "b"}},
|
{0.001, 0.001, 1.002, 1.002, "loc", []string{"a", "b"}},
|
||||||
{0.001, 0.001, 9.002, 9.002, "loc", []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}},
|
{0.001, 0.001, 9.002, 9.002, "loc", []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}},
|
||||||
|
// same upper-left, bottom-right point
|
||||||
|
{25, 25, 25, 25, "loc", nil},
|
||||||
|
// box that would return points, but points reversed
|
||||||
|
{0.002, 0.002, 0.001, 0.001, "loc", nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
i := setupGeo(t)
|
i := setupGeo(t)
|
||||||
|
|
|
@ -34,7 +34,9 @@ type GeoPointDistanceSearcher struct {
|
||||||
searcher *FilteringSearcher
|
searcher *FilteringSearcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGeoPointDistanceSearcher(indexReader index.IndexReader, centerLon, centerLat, dist float64, field string, boost float64, options search.SearcherOptions) (*GeoPointDistanceSearcher, error) {
|
func NewGeoPointDistanceSearcher(indexReader index.IndexReader, centerLon,
|
||||||
|
centerLat, dist float64, field string, boost float64,
|
||||||
|
options search.SearcherOptions) (*GeoPointDistanceSearcher, error) {
|
||||||
rv := &GeoPointDistanceSearcher{
|
rv := &GeoPointDistanceSearcher{
|
||||||
indexReader: indexReader,
|
indexReader: indexReader,
|
||||||
centerLon: centerLon,
|
centerLon: centerLon,
|
||||||
|
@ -45,23 +47,28 @@ func NewGeoPointDistanceSearcher(indexReader index.IndexReader, centerLon, cente
|
||||||
}
|
}
|
||||||
|
|
||||||
// compute bounding box containing the circle
|
// compute bounding box containing the circle
|
||||||
topLeftLon, topLeftLat, bottomRightLon, bottomRightLat := geo.ComputeBoundingBox(centerLon, centerLat, dist)
|
topLeftLon, topLeftLat, bottomRightLon, bottomRightLat :=
|
||||||
|
geo.ComputeBoundingBox(centerLon, centerLat, dist)
|
||||||
|
|
||||||
var boxSearcher search.Searcher
|
var boxSearcher search.Searcher
|
||||||
if bottomRightLon < topLeftLon {
|
if bottomRightLon < topLeftLon {
|
||||||
// cross date line, rewrite as two parts
|
// cross date line, rewrite as two parts
|
||||||
|
|
||||||
leftSearcher, err := NewGeoBoundingBoxSearcher(indexReader, -180, bottomRightLat, bottomRightLon, topLeftLat, field, boost, options, false)
|
leftSearcher, err := NewGeoBoundingBoxSearcher(indexReader,
|
||||||
|
-180, bottomRightLat, bottomRightLon, topLeftLat,
|
||||||
|
field, boost, options, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rightSearcher, err := NewGeoBoundingBoxSearcher(indexReader, topLeftLon, bottomRightLat, 180, topLeftLat, field, boost, options, false)
|
rightSearcher, err := NewGeoBoundingBoxSearcher(indexReader,
|
||||||
|
topLeftLon, bottomRightLat, 180, topLeftLat, field, boost, options, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = leftSearcher.Close()
|
_ = leftSearcher.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
boxSearcher, err = NewDisjunctionSearcher(indexReader, []search.Searcher{leftSearcher, rightSearcher}, 0, options)
|
boxSearcher, err = NewDisjunctionSearcher(indexReader,
|
||||||
|
[]search.Searcher{leftSearcher, rightSearcher}, 0, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = leftSearcher.Close()
|
_ = leftSearcher.Close()
|
||||||
_ = rightSearcher.Close()
|
_ = rightSearcher.Close()
|
||||||
|
@ -71,37 +78,41 @@ func NewGeoPointDistanceSearcher(indexReader index.IndexReader, centerLon, cente
|
||||||
|
|
||||||
// build geoboundinggox searcher for that bounding box
|
// build geoboundinggox searcher for that bounding box
|
||||||
var err error
|
var err error
|
||||||
boxSearcher, err = NewGeoBoundingBoxSearcher(indexReader, topLeftLon, bottomRightLat, bottomRightLon, topLeftLat, field, boost, options, false)
|
boxSearcher, err = NewGeoBoundingBoxSearcher(indexReader,
|
||||||
|
topLeftLon, bottomRightLat, bottomRightLon, topLeftLat, field, boost,
|
||||||
|
options, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrap it in a filtering searcher which checks the actual distance
|
// wrap it in a filtering searcher which checks the actual distance
|
||||||
rv.searcher = NewFilteringSearcher(boxSearcher, func(d *search.DocumentMatch) bool {
|
rv.searcher = NewFilteringSearcher(boxSearcher,
|
||||||
var lon, lat float64
|
func(d *search.DocumentMatch) bool {
|
||||||
var found bool
|
var lon, lat float64
|
||||||
err := indexReader.DocumentVisitFieldTerms(d.IndexInternalID, []string{field}, func(field string, term []byte) {
|
var found bool
|
||||||
// only consider the values which are shifted 0
|
err := indexReader.DocumentVisitFieldTerms(d.IndexInternalID,
|
||||||
prefixCoded := numeric.PrefixCoded(term)
|
[]string{field}, func(field string, term []byte) {
|
||||||
shift, err := prefixCoded.Shift()
|
// only consider the values which are shifted 0
|
||||||
if err == nil && shift == 0 {
|
prefixCoded := numeric.PrefixCoded(term)
|
||||||
i64, err := prefixCoded.Int64()
|
shift, err := prefixCoded.Shift()
|
||||||
if err == nil {
|
if err == nil && shift == 0 {
|
||||||
lon = geo.MortonUnhashLon(uint64(i64))
|
i64, err := prefixCoded.Int64()
|
||||||
lat = geo.MortonUnhashLat(uint64(i64))
|
if err == nil {
|
||||||
found = true
|
lon = geo.MortonUnhashLon(uint64(i64))
|
||||||
|
lat = geo.MortonUnhashLat(uint64(i64))
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err == nil && found {
|
||||||
|
dist := geo.Haversin(lon, lat, rv.centerLon, rv.centerLat)
|
||||||
|
if dist <= rv.dist/1000 {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
if err == nil && found {
|
|
||||||
dist := geo.Haversin(lon, lat, rv.centerLon, rv.centerLat)
|
|
||||||
if dist <= rv.dist/1000 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
return rv, nil
|
return rv, nil
|
||||||
}
|
}
|
||||||
|
@ -118,11 +129,13 @@ func (s *GeoPointDistanceSearcher) SetQueryNorm(qnorm float64) {
|
||||||
s.searcher.SetQueryNorm(qnorm)
|
s.searcher.SetQueryNorm(qnorm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GeoPointDistanceSearcher) Next(ctx *search.SearchContext) (*search.DocumentMatch, error) {
|
func (s *GeoPointDistanceSearcher) Next(ctx *search.SearchContext) (
|
||||||
|
*search.DocumentMatch, error) {
|
||||||
return s.searcher.Next(ctx)
|
return s.searcher.Next(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GeoPointDistanceSearcher) Advance(ctx *search.SearchContext, ID index.IndexInternalID) (*search.DocumentMatch, error) {
|
func (s *GeoPointDistanceSearcher) Advance(ctx *search.SearchContext,
|
||||||
|
ID index.IndexInternalID) (*search.DocumentMatch, error) {
|
||||||
return s.searcher.Advance(ctx, ID)
|
return s.searcher.Advance(ctx, ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue