update to geo query parsing and top-level bleve accessibility
- make geo queries accessible from top-level bleve - update query parsing to support same geo point formats as document parsing - add constructor for easier sorting by geo distance in Go - additional integration tests using alternate (GeoJSON) style points
This commit is contained in:
parent
5636536583
commit
9790574610
15
query.go
15
query.go
|
@ -184,3 +184,18 @@ func NewTermQuery(term string) *query.TermQuery {
|
|||
func NewWildcardQuery(wildcard string) *query.WildcardQuery {
|
||||
return query.NewWildcardQuery(wildcard)
|
||||
}
|
||||
|
||||
// NewGeoBoundingBoxQuery creates a new Query for performing geo bounding
|
||||
// box searches. The arguments describe the position of the box and documents
|
||||
// which have an indexed geo point inside the box will be returned.
|
||||
func NewGeoBoundingBoxQuery(topLeftLon, topLeftLat, bottomRightLon, bottomRightLat float64) *query.GeoBoundingBoxQuery {
|
||||
return query.NewGeoBoundingBoxQuery(topLeftLon, topLeftLat, bottomRightLon, bottomRightLat)
|
||||
}
|
||||
|
||||
// NewGeoDistanceQuery creates a new Query for performing geo bounding
|
||||
// box searches. The arguments describe a position and a distance. Docuements
|
||||
// which have an indexed geo point which is less than or equal to the provided
|
||||
// distance will be returned.
|
||||
func NewGeoDistanceQuery(lon, lat float64, distance string) *query.GeoDistanceQuery {
|
||||
return query.NewGeoDistanceQuery(lon, lat, distance)
|
||||
}
|
||||
|
|
|
@ -15,34 +15,27 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/geo"
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"github.com/blevesearch/bleve/search"
|
||||
"github.com/blevesearch/bleve/search/searcher"
|
||||
)
|
||||
|
||||
type GeoPoint struct {
|
||||
Lon float64 `json:"lon,omitempty"`
|
||||
Lat float64 `json:"lat,omitempty"`
|
||||
}
|
||||
|
||||
type GeoBoundingBoxQuery struct {
|
||||
TopLeft *GeoPoint `json:"top_left,omitempty"`
|
||||
BottomRight *GeoPoint `json:"bottom_right,omitempty"`
|
||||
TopLeft []float64 `json:"top_left,omitempty"`
|
||||
BottomRight []float64 `json:"bottom_right,omitempty"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}
|
||||
|
||||
func NewGeoBoundingBoxQuery(topLeftLon, topLeftLat, bottomRightLon, bottomRightLat float64) *GeoBoundingBoxQuery {
|
||||
return &GeoBoundingBoxQuery{
|
||||
TopLeft: &GeoPoint{
|
||||
Lon: topLeftLon,
|
||||
Lat: topLeftLat,
|
||||
},
|
||||
BottomRight: &GeoPoint{
|
||||
Lon: bottomRightLon,
|
||||
Lat: bottomRightLat,
|
||||
},
|
||||
TopLeft: []float64{topLeftLon, topLeftLat},
|
||||
BottomRight: []float64{bottomRightLon, bottomRightLat},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,14 +62,14 @@ func (q *GeoBoundingBoxQuery) Searcher(i index.IndexReader, m mapping.IndexMappi
|
|||
field = m.DefaultSearchField()
|
||||
}
|
||||
|
||||
if q.BottomRight.Lon < q.TopLeft.Lon {
|
||||
if q.BottomRight[0] < q.TopLeft[0] {
|
||||
// cross date line, rewrite as two parts
|
||||
|
||||
leftSearcher, err := searcher.NewGeoBoundingBoxSearcher(i, -180, q.BottomRight.Lat, q.BottomRight.Lon, q.TopLeft.Lat, field, q.BoostVal.Value(), options, true)
|
||||
leftSearcher, err := searcher.NewGeoBoundingBoxSearcher(i, -180, q.BottomRight[1], q.BottomRight[0], q.TopLeft[1], field, q.BoostVal.Value(), options, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rightSearcher, err := searcher.NewGeoBoundingBoxSearcher(i, q.TopLeft.Lon, q.BottomRight.Lat, 180, q.TopLeft.Lat, field, q.BoostVal.Value(), options, true)
|
||||
rightSearcher, err := searcher.NewGeoBoundingBoxSearcher(i, q.TopLeft[0], q.BottomRight[1], 180, q.TopLeft[1], field, q.BoostVal.Value(), options, true)
|
||||
if err != nil {
|
||||
_ = leftSearcher.Close()
|
||||
return nil, err
|
||||
|
@ -85,9 +78,36 @@ func (q *GeoBoundingBoxQuery) Searcher(i index.IndexReader, m mapping.IndexMappi
|
|||
return searcher.NewDisjunctionSearcher(i, []search.Searcher{leftSearcher, rightSearcher}, 0, options)
|
||||
}
|
||||
|
||||
return searcher.NewGeoBoundingBoxSearcher(i, q.TopLeft.Lon, q.BottomRight.Lat, q.BottomRight.Lon, q.TopLeft.Lat, field, q.BoostVal.Value(), options, true)
|
||||
return searcher.NewGeoBoundingBoxSearcher(i, q.TopLeft[0], q.BottomRight[1], q.BottomRight[0], q.TopLeft[1], field, q.BoostVal.Value(), options, true)
|
||||
}
|
||||
|
||||
func (q *GeoBoundingBoxQuery) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *GeoBoundingBoxQuery) UnmarshalJSON(data []byte) error {
|
||||
tmp := struct {
|
||||
TopLeft interface{} `json:"top_left,omitempty"`
|
||||
BottomRight interface{} `json:"bottom_right,omitempty"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}{}
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// now use our generic point parsing code from the geo package
|
||||
lon, lat, found := geo.ExtractGeoPoint(tmp.TopLeft)
|
||||
if !found {
|
||||
return fmt.Errorf("geo location top_left not in a valid format")
|
||||
}
|
||||
q.TopLeft = []float64{lon, lat}
|
||||
lon, lat, found = geo.ExtractGeoPoint(tmp.BottomRight)
|
||||
if !found {
|
||||
return fmt.Errorf("geo location top_left not in a valid format")
|
||||
}
|
||||
q.BottomRight = []float64{lon, lat}
|
||||
q.FieldVal = tmp.FieldVal
|
||||
q.BoostVal = tmp.BoostVal
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/blevesearch/bleve/geo"
|
||||
"github.com/blevesearch/bleve/index"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
|
@ -23,7 +26,7 @@ import (
|
|||
)
|
||||
|
||||
type GeoDistanceQuery struct {
|
||||
Location *GeoPoint `json:"location,omitempty"`
|
||||
Location []float64 `json:"location,omitempty"`
|
||||
Distance string `json:"distance,omitempty"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
|
@ -31,10 +34,7 @@ type GeoDistanceQuery struct {
|
|||
|
||||
func NewGeoDistanceQuery(lon, lat float64, distance string) *GeoDistanceQuery {
|
||||
return &GeoDistanceQuery{
|
||||
Location: &GeoPoint{
|
||||
Lon: lon,
|
||||
Lat: lat,
|
||||
},
|
||||
Location: []float64{lon, lat},
|
||||
Distance: distance,
|
||||
}
|
||||
}
|
||||
|
@ -67,9 +67,32 @@ func (q *GeoDistanceQuery) Searcher(i index.IndexReader, m mapping.IndexMapping,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return searcher.NewGeoPointDistanceSearcher(i, q.Location.Lon, q.Location.Lat, 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 {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *GeoDistanceQuery) UnmarshalJSON(data []byte) error {
|
||||
tmp := struct {
|
||||
Location interface{} `json:"location,omitempty"`
|
||||
Distance string `json:"distance,omitempty"`
|
||||
FieldVal string `json:"field,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
}{}
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// now use our generic point parsing code from the geo package
|
||||
lon, lat, found := geo.ExtractGeoPoint(tmp.Location)
|
||||
if !found {
|
||||
return fmt.Errorf("geo location not in a valid format")
|
||||
}
|
||||
q.Location = []float64{lon, lat}
|
||||
q.Distance = tmp.Distance
|
||||
q.FieldVal = tmp.FieldVal
|
||||
q.BoostVal = tmp.BoostVal
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -551,6 +551,26 @@ func (s *SortScore) MarshalJSON() ([]byte, error) {
|
|||
|
||||
var maxDistance = string(numeric.MustNewPrefixCodedInt64(math.MaxInt64, 0))
|
||||
|
||||
// NewSortGeoDistance creates SearchSort instance for sorting documents by
|
||||
// their distance from the specified point.
|
||||
func NewSortGeoDistance(field, unit string, lon, lat float64, desc bool) (
|
||||
*SortGeoDistance, error) {
|
||||
|
||||
rv := &SortGeoDistance{
|
||||
Field: field,
|
||||
Desc: desc,
|
||||
Unit: unit,
|
||||
lon: lon,
|
||||
lat: lat,
|
||||
}
|
||||
var err error
|
||||
rv.unitMult, err = geo.ParseDistanceUnit(unit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
// SortGeoDistance will sort results by the distance of an
|
||||
// indexed geo point, from the provided location.
|
||||
// Field is the name of the field
|
||||
|
|
|
@ -157,5 +157,68 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "breweries near the couchbase office, using GeoJSON style points",
|
||||
"search": {
|
||||
"from": 0,
|
||||
"size": 10,
|
||||
"query": {
|
||||
"location": [-122.107799,37.399285],
|
||||
"distance": "100mi",
|
||||
"field": "geo"
|
||||
},
|
||||
"sort": [
|
||||
{
|
||||
"by": "geo_distance",
|
||||
"field": "geo",
|
||||
"unit": "mi",
|
||||
"location": [-122.107799,37.399285]
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"total_hits": 3,
|
||||
"hits": [
|
||||
{
|
||||
"id": "firehouse_grill_brewery"
|
||||
},
|
||||
{
|
||||
"id": "jack_s_brewing"
|
||||
},
|
||||
{
|
||||
"id": "brewpub_on_the_green"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "bounding box around DC area, using GeoJSON style",
|
||||
"search": {
|
||||
"from": 0,
|
||||
"size": 10,
|
||||
"query": {
|
||||
"top_left": [-78,39.5],
|
||||
"bottom_right": [-76,38.5],
|
||||
"field": "geo"
|
||||
},
|
||||
"sort": [
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"total_hits": 3,
|
||||
"hits": [
|
||||
{
|
||||
"id": "capital_city_brewing_company"
|
||||
},
|
||||
{
|
||||
"id": "hook_ladder_brewing_company"
|
||||
},
|
||||
{
|
||||
"id": "sweet_water_tavern_and_brewery"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue