Added BUFFER option for Within and Intersects
This commit allows for buffering any GeoJSON object. For example: INTERSECTS fleet BUFFER 1000 OBJECT {...LineString...} This will buffer add a 1 kilometer buffer to a linesting and search the 'fleet' collection for all objects that intersect the buffered linestring. This commit also allows for performing INTERSECTS with a POINT type. Thus allowing for a polygon-over-point operation, which is an inverted point-in-polygon.
This commit is contained in:
parent
29a6d05f3f
commit
241117c7ba
169
internal/buffer/buffer.go
Normal file
169
internal/buffer/buffer.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package buffer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/tidwall/geojson"
|
||||||
|
"github.com/tidwall/geojson/geo"
|
||||||
|
"github.com/tidwall/geojson/geometry"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: detect of pole and antimeridian crossing and generate
|
||||||
|
// valid multigeometries
|
||||||
|
|
||||||
|
const bufferSteps = 15
|
||||||
|
|
||||||
|
// Simple performs a very simple buffer operation on a geojson object.
|
||||||
|
func Simple(g geojson.Object, meters float64) (geojson.Object, error) {
|
||||||
|
if meters <= 0 {
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
if math.IsInf(meters, 0) || math.IsNaN(meters) {
|
||||||
|
return g, errors.New("invalid meters")
|
||||||
|
}
|
||||||
|
switch g := g.(type) {
|
||||||
|
case *geojson.Point:
|
||||||
|
return bufferSimplePoint(g.Base(), meters), nil
|
||||||
|
case *geojson.SimplePoint:
|
||||||
|
return bufferSimplePoint(g.Base(), meters), nil
|
||||||
|
case *geojson.MultiPoint:
|
||||||
|
return bufferSimpleGeometries(g.Base(), meters)
|
||||||
|
case *geojson.LineString:
|
||||||
|
return bufferSimpleLineString(g, meters)
|
||||||
|
case *geojson.MultiLineString:
|
||||||
|
return bufferSimpleGeometries(g.Base(), meters)
|
||||||
|
case *geojson.Polygon:
|
||||||
|
return bufferSimplePolygon(g, meters)
|
||||||
|
case *geojson.MultiPolygon:
|
||||||
|
return bufferSimpleGeometries(g.Base(), meters)
|
||||||
|
case *geojson.FeatureCollection:
|
||||||
|
return bufferSimpleFeatures(g.Base(), meters)
|
||||||
|
case *geojson.Feature:
|
||||||
|
bg, err := Simple(g.Base(), meters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return geojson.NewFeature(bg, g.Members()), nil
|
||||||
|
case *geojson.Circle:
|
||||||
|
return Simple(g.Primative(), meters)
|
||||||
|
case nil:
|
||||||
|
return nil, errors.New("cannot buffer nil object")
|
||||||
|
default:
|
||||||
|
typ := gjson.Get(g.JSON(), "type").String()
|
||||||
|
return nil, errors.New("cannot buffer " + typ + " type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bufferSimplePoint(p geometry.Point, meters float64) *geojson.Polygon {
|
||||||
|
meters = geo.NormalizeDistance(meters)
|
||||||
|
points := make([]geometry.Point, 0, bufferSteps+1)
|
||||||
|
|
||||||
|
// calc the four corners
|
||||||
|
maxY, _ := geo.DestinationPoint(p.Y, p.X, meters, 0)
|
||||||
|
_, maxX := geo.DestinationPoint(p.Y, p.X, meters, 90)
|
||||||
|
minY, _ := geo.DestinationPoint(p.Y, p.X, meters, 180)
|
||||||
|
_, minX := geo.DestinationPoint(p.Y, p.X, meters, 270)
|
||||||
|
|
||||||
|
// use the half width of the lat and lon
|
||||||
|
lons := (maxX - minX) / 2
|
||||||
|
lats := (maxY - minY) / 2
|
||||||
|
|
||||||
|
// generate the circle polygon
|
||||||
|
for th := 0.0; th <= 360.0; th += 360.0 / float64(bufferSteps) {
|
||||||
|
radians := (math.Pi / 180) * th
|
||||||
|
x := p.X + lons*math.Cos(radians)
|
||||||
|
y := p.Y + lats*math.Sin(radians)
|
||||||
|
points = append(points, geometry.Point{X: x, Y: y})
|
||||||
|
}
|
||||||
|
// add last connecting point, make a total of steps+1
|
||||||
|
points = append(points, points[0])
|
||||||
|
poly := geojson.NewPolygon(
|
||||||
|
geometry.NewPoly(points, nil, &geometry.IndexOptions{
|
||||||
|
Kind: geometry.None,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
return poly
|
||||||
|
}
|
||||||
|
|
||||||
|
func bufferSimpleGeometries(objs []geojson.Object, meters float64,
|
||||||
|
) (*geojson.GeometryCollection, error) {
|
||||||
|
geoms := make([]geojson.Object, len(objs))
|
||||||
|
for i := 0; i < len(objs); i++ {
|
||||||
|
g, err := Simple(objs[i], meters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
geoms[i] = g
|
||||||
|
}
|
||||||
|
return geojson.NewGeometryCollection(geoms), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bufferSimpleFeatures(objs []geojson.Object, meters float64,
|
||||||
|
) (*geojson.FeatureCollection, error) {
|
||||||
|
geoms := make([]geojson.Object, len(objs))
|
||||||
|
for i := 0; i < len(objs); i++ {
|
||||||
|
g, err := Simple(objs[i], meters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
geoms[i] = g
|
||||||
|
}
|
||||||
|
return geojson.NewFeatureCollection(geoms), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendBufferSimpleSeries buffers a series and appends its parts to dst
|
||||||
|
func appendBufferSimpleSeries(dst []geojson.Object, s geometry.Series, meters float64) []geojson.Object {
|
||||||
|
nsegs := s.NumSegments()
|
||||||
|
for i := 0; i < nsegs; i++ {
|
||||||
|
dst = appendSimpleBufferSegment(dst, s.SegmentAt(i), meters, i == 0)
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendSimpleBufferSegment buffers a segment and appends its parts to dst
|
||||||
|
func appendSimpleBufferSegment(dst []geojson.Object, seg geometry.Segment,
|
||||||
|
meters float64, first bool,
|
||||||
|
) []geojson.Object {
|
||||||
|
if first {
|
||||||
|
// endcap A
|
||||||
|
dst = append(dst, bufferSimplePoint(seg.A, meters))
|
||||||
|
}
|
||||||
|
// line polygon
|
||||||
|
bear1 := geo.BearingTo(seg.A.Y, seg.A.X, seg.B.Y, seg.B.X)
|
||||||
|
lat1, lon1 := geo.DestinationPoint(seg.A.Y, seg.A.X, meters, bear1-90)
|
||||||
|
lat2, lon2 := geo.DestinationPoint(seg.A.Y, seg.A.X, meters, bear1+90)
|
||||||
|
bear2 := geo.BearingTo(seg.B.Y, seg.B.X, seg.A.Y, seg.A.X)
|
||||||
|
lat3, lon3 := geo.DestinationPoint(seg.B.Y, seg.B.X, meters, bear2-90)
|
||||||
|
lat4, lon4 := geo.DestinationPoint(seg.B.Y, seg.B.X, meters, bear2+90)
|
||||||
|
dst = append(dst, geojson.NewPolygon(
|
||||||
|
geometry.NewPoly([]geometry.Point{
|
||||||
|
{X: lon1, Y: lat1},
|
||||||
|
{X: lon2, Y: lat2},
|
||||||
|
{X: lon3, Y: lat3},
|
||||||
|
{X: lon4, Y: lat4},
|
||||||
|
{X: lon1, Y: lat1},
|
||||||
|
}, nil, nil)))
|
||||||
|
// endcap B
|
||||||
|
dst = append(dst, bufferSimplePoint(seg.B, meters))
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func bufferSimplePolygon(p *geojson.Polygon, meters float64,
|
||||||
|
) (*geojson.GeometryCollection, error) {
|
||||||
|
var geoms []geojson.Object
|
||||||
|
b := p.Base()
|
||||||
|
geoms = appendBufferSimpleSeries(geoms, b.Exterior, meters)
|
||||||
|
for _, hole := range b.Holes {
|
||||||
|
geoms = appendBufferSimpleSeries(geoms, hole, meters)
|
||||||
|
}
|
||||||
|
geoms = append(geoms, p)
|
||||||
|
return geojson.NewGeometryCollection(geoms), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bufferSimpleLineString(l *geojson.LineString, meters float64,
|
||||||
|
) (*geojson.GeometryCollection, error) {
|
||||||
|
geoms := appendBufferSimpleSeries(nil, l.Base(), meters)
|
||||||
|
return geojson.NewGeometryCollection(geoms), nil
|
||||||
|
}
|
113
internal/buffer/buffer_test.go
Normal file
113
internal/buffer/buffer_test.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package buffer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tidwall/geojson"
|
||||||
|
"github.com/tidwall/geojson/geometry"
|
||||||
|
)
|
||||||
|
|
||||||
|
const lineString = `{"type":"LineString","coordinates":[
|
||||||
|
[-116.40289306640624,34.125447565116126],
|
||||||
|
[-116.36444091796875,34.14818102254435],
|
||||||
|
[-116.0980224609375,34.15045403191448],
|
||||||
|
[-115.74920654296874,34.127721186043985],
|
||||||
|
[-115.54870605468749,34.075412438417395],
|
||||||
|
[-115.5267333984375,34.11407854333859],
|
||||||
|
[-115.21911621093749,34.048108084909835],
|
||||||
|
[-115.25207519531249,33.8339199536547],
|
||||||
|
[-115.40588378906249,33.71748624018193]
|
||||||
|
]}`
|
||||||
|
|
||||||
|
var lineInPoints = []geometry.Point{
|
||||||
|
{X: -115.64363479614258, Y: 34.108251327293296},
|
||||||
|
{X: -115.54355621337892, Y: 34.07199987534163},
|
||||||
|
{X: -115.21482467651367, Y: 34.051237154976164},
|
||||||
|
{X: -115.4110336303711, Y: 33.715201644740844},
|
||||||
|
{X: -116.40701293945311, Y: 34.12345809664606},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufferLineString(t *testing.T) {
|
||||||
|
g, err := geojson.Parse(lineString, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
g2, err := Simple(g, 1000)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, pt := range lineInPoints {
|
||||||
|
ok := g2.Contains(geojson.NewPoint(pt))
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("!ok")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const polygon = `{"type": "Polygon","coordinates":[
|
||||||
|
[
|
||||||
|
[116.46881103515624,34.277644878733824],
|
||||||
|
[115.87280273437499,34.20953080048952],
|
||||||
|
[115.70251464843749,34.397844946449865],
|
||||||
|
[115.9881591796875,34.61286625296406],
|
||||||
|
[116.46881103515624,34.277644878733824]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[115.90438842773436,34.38651267795365],
|
||||||
|
[116.05270385742188,34.35023911062779],
|
||||||
|
[115.99914550781249,34.44655621402982],
|
||||||
|
[115.90438842773436,34.38651267795365]
|
||||||
|
]
|
||||||
|
]}`
|
||||||
|
|
||||||
|
var polyInPoints = []geometry.Point{
|
||||||
|
{X: 115.95837593078612, Y: 34.59887847065301},
|
||||||
|
{X: 115.98755836486816, Y: 34.61879975173954},
|
||||||
|
{X: 115.98833084106445, Y: 34.59795999847678},
|
||||||
|
{X: 116.04536533355714, Y: 34.58082509817638},
|
||||||
|
{X: 116.47567749023438, Y: 34.27651009584797},
|
||||||
|
{X: 116.42005920410155, Y: 34.32018817684490},
|
||||||
|
{X: 116.33216857910156, Y: 34.25948651450623},
|
||||||
|
{X: 115.89340209960939, Y: 34.24132422972854},
|
||||||
|
{X: 115.95588684082033, Y: 34.42786803680155},
|
||||||
|
{X: 115.97236633300783, Y: 34.42107129982385},
|
||||||
|
{X: 115.99639892578125, Y: 34.43579686485573},
|
||||||
|
{X: 116.04652404785155, Y: 34.35364042469895},
|
||||||
|
{X: 115.92155456542967, Y: 34.38877925439021},
|
||||||
|
{X: 115.96755981445311, Y: 34.37687904351907},
|
||||||
|
{X: 115.88859558105467, Y: 34.42956713470528},
|
||||||
|
{X: 115.97511291503906, Y: 34.36327673174518},
|
||||||
|
{X: 115.69564819335938, Y: 34.39784494644986},
|
||||||
|
{X: 115.87005615234375, Y: 34.20385213966983},
|
||||||
|
{X: 115.76980590820312, Y: 34.31678550602221},
|
||||||
|
}
|
||||||
|
var polyOutPoints = []geometry.Point{
|
||||||
|
{X: 115.68534851074217, Y: 34.40917568058836},
|
||||||
|
{X: 115.98953247070312, Y: 34.63038297923298},
|
||||||
|
{X: 115.98541259765624, Y: 34.39671178864245},
|
||||||
|
{X: 116.31500244140626, Y: 34.22145474280257},
|
||||||
|
{X: 115.85426330566406, Y: 34.18510984477340},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufferPolygon(t *testing.T) {
|
||||||
|
g, err := geojson.Parse(polygon, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
g2, err := Simple(g, 1000)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, pt := range polyInPoints {
|
||||||
|
ok := g2.Contains(geojson.NewPoint(pt))
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("!ok")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, pt := range polyOutPoints {
|
||||||
|
ok := g2.Contains(geojson.NewPoint(pt))
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("ok")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -56,7 +56,7 @@ func (s *Server) cmdSetHook(msg *Message) (
|
|||||||
}
|
}
|
||||||
var commandvs []string
|
var commandvs []string
|
||||||
var cmdlc string
|
var cmdlc string
|
||||||
var types []string
|
var types map[string]bool
|
||||||
var expires float64
|
var expires float64
|
||||||
var expiresSet bool
|
var expiresSet bool
|
||||||
metaMap := make(map[string]string)
|
metaMap := make(map[string]string)
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/tidwall/geojson/geometry"
|
"github.com/tidwall/geojson/geometry"
|
||||||
"github.com/tidwall/resp"
|
"github.com/tidwall/resp"
|
||||||
"github.com/tidwall/tile38/internal/bing"
|
"github.com/tidwall/tile38/internal/bing"
|
||||||
|
"github.com/tidwall/tile38/internal/buffer"
|
||||||
"github.com/tidwall/tile38/internal/clip"
|
"github.com/tidwall/tile38/internal/clip"
|
||||||
"github.com/tidwall/tile38/internal/glob"
|
"github.com/tidwall/tile38/internal/glob"
|
||||||
)
|
)
|
||||||
@ -170,7 +171,7 @@ func parseRectArea(ltyp string, vs []string) (nvs []string, rect *geojson.Rect,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) cmdSearchArgs(
|
func (s *Server) cmdSearchArgs(
|
||||||
fromFenceCmd bool, cmd string, vs []string, types []string,
|
fromFenceCmd bool, cmd string, vs []string, types map[string]bool,
|
||||||
) (lfs liveFenceSwitches, err error) {
|
) (lfs liveFenceSwitches, err error) {
|
||||||
var t searchScanBaseTokens
|
var t searchScanBaseTokens
|
||||||
if fromFenceCmd {
|
if fromFenceCmd {
|
||||||
@ -198,13 +199,7 @@ func (s *Server) cmdSearchArgs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ltyp := strings.ToLower(typ)
|
ltyp := strings.ToLower(typ)
|
||||||
var found bool
|
found := types[ltyp]
|
||||||
for _, t := range types {
|
|
||||||
if ltyp == t {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found && lfs.searchScanBaseTokens.fence && ltyp == "roam" && cmd == "nearby" {
|
if !found && lfs.searchScanBaseTokens.fence && ltyp == "roam" && cmd == "nearby" {
|
||||||
// allow roaming for nearby fence searches.
|
// allow roaming for nearby fence searches.
|
||||||
found = true
|
found = true
|
||||||
@ -215,7 +210,41 @@ func (s *Server) cmdSearchArgs(
|
|||||||
}
|
}
|
||||||
switch ltyp {
|
switch ltyp {
|
||||||
case "point":
|
case "point":
|
||||||
fallthrough
|
var slat, slon, smeters string
|
||||||
|
if vs, slat, ok = tokenval(vs); !ok || slat == "" {
|
||||||
|
err = errInvalidNumberOfArguments
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if vs, slon, ok = tokenval(vs); !ok || slon == "" {
|
||||||
|
err = errInvalidNumberOfArguments
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var lat, lon, meters float64
|
||||||
|
if lat, err = strconv.ParseFloat(slat, 64); err != nil {
|
||||||
|
err = errInvalidArgument(slat)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if lon, err = strconv.ParseFloat(slon, 64); err != nil {
|
||||||
|
err = errInvalidArgument(slon)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// radius is optional for nearby, but mandatory for others
|
||||||
|
if cmd == "nearby" {
|
||||||
|
if vs, smeters, ok = tokenval(vs); ok && smeters != "" {
|
||||||
|
meters, err = strconv.ParseFloat(smeters, 64)
|
||||||
|
if err != nil || meters < 0 {
|
||||||
|
err = errInvalidArgument(smeters)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
meters = -1
|
||||||
|
}
|
||||||
|
// Nearby used the Circle type
|
||||||
|
lfs.obj = geojson.NewCircle(geometry.Point{X: lon, Y: lat}, meters, defaultCircleSteps)
|
||||||
|
} else {
|
||||||
|
// Intersects and Within use the Point type
|
||||||
|
lfs.obj = geojson.NewPoint(geometry.Point{X: lon, Y: lat})
|
||||||
|
}
|
||||||
case "circle":
|
case "circle":
|
||||||
if lfs.clip {
|
if lfs.clip {
|
||||||
err = errInvalidArgument("cannot clip with " + ltyp)
|
err = errInvalidArgument("cannot clip with " + ltyp)
|
||||||
@ -239,33 +268,14 @@ func (s *Server) cmdSearchArgs(
|
|||||||
err = errInvalidArgument(slon)
|
err = errInvalidArgument(slon)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// radius is optional for nearby, but mandatory for others
|
if vs, smeters, ok = tokenval(vs); !ok || smeters == "" {
|
||||||
if cmd == "nearby" {
|
err = errInvalidNumberOfArguments
|
||||||
if vs, smeters, ok = tokenval(vs); ok && smeters != "" {
|
return
|
||||||
if meters, err = strconv.ParseFloat(smeters, 64); err != nil {
|
}
|
||||||
err = errInvalidArgument(smeters)
|
meters, err = strconv.ParseFloat(smeters, 64)
|
||||||
return
|
if err != nil || meters < 0 {
|
||||||
}
|
err = errInvalidArgument(smeters)
|
||||||
if meters < 0 {
|
return
|
||||||
err = errInvalidArgument(smeters)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
meters = -1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if vs, smeters, ok = tokenval(vs); !ok || smeters == "" {
|
|
||||||
err = errInvalidNumberOfArguments
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if meters, err = strconv.ParseFloat(smeters, 64); err != nil {
|
|
||||||
err = errInvalidArgument(smeters)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if meters < 0 {
|
|
||||||
err = errInvalidArgument(smeters)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
lfs.obj = geojson.NewCircle(geometry.Point{X: lon, Y: lat}, meters, defaultCircleSteps)
|
lfs.obj = geojson.NewCircle(geometry.Point{X: lon, Y: lat}, meters, defaultCircleSteps)
|
||||||
case "object":
|
case "object":
|
||||||
@ -436,12 +446,24 @@ func (s *Server) cmdSearchArgs(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if lfs.hasbuffer {
|
||||||
|
lfs.obj, err = buffer.Simple(lfs.obj, lfs.buffer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var nearbyTypes = []string{"point"}
|
var nearbyTypes = map[string]bool{
|
||||||
var withinOrIntersectsTypes = []string{
|
"point": true,
|
||||||
"geo", "bounds", "hash", "tile", "quadkey", "get", "object", "circle", "sector"}
|
}
|
||||||
|
var withinOrIntersectsTypes = map[string]bool{
|
||||||
|
"geo": true, "bounds": true, "hash": true, "tile": true, "quadkey": true,
|
||||||
|
"get": true, "object": true, "circle": true, "point": true, "sector": true,
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) cmdNearby(msg *Message) (res resp.Value, err error) {
|
func (s *Server) cmdNearby(msg *Message) (res resp.Value, err error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
@ -211,6 +211,8 @@ type searchScanBaseTokens struct {
|
|||||||
sparse uint8
|
sparse uint8
|
||||||
desc bool
|
desc bool
|
||||||
clip bool
|
clip bool
|
||||||
|
buffer float64
|
||||||
|
hasbuffer bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) parseSearchScanBaseTokens(
|
func (s *Server) parseSearchScanBaseTokens(
|
||||||
@ -234,6 +236,22 @@ func (s *Server) parseSearchScanBaseTokens(
|
|||||||
nvs, wtok, ok := tokenval(vs)
|
nvs, wtok, ok := tokenval(vs)
|
||||||
if ok && len(wtok) > 0 {
|
if ok && len(wtok) > 0 {
|
||||||
switch strings.ToLower(wtok) {
|
switch strings.ToLower(wtok) {
|
||||||
|
case "buffer":
|
||||||
|
vs = nvs
|
||||||
|
var sbuf string
|
||||||
|
if vs, sbuf, ok = tokenval(vs); !ok || sbuf == "" {
|
||||||
|
err = errInvalidNumberOfArguments
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var buf float64
|
||||||
|
buf, err = strconv.ParseFloat(sbuf, 64)
|
||||||
|
if err != nil || buf < 0 || math.IsInf(buf, 0) || math.IsNaN(buf) {
|
||||||
|
err = errInvalidArgument(sbuf)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.buffer = buf
|
||||||
|
t.hasbuffer = true
|
||||||
|
continue
|
||||||
case "cursor":
|
case "cursor":
|
||||||
vs = nvs
|
vs = nvs
|
||||||
if scursor != "" {
|
if scursor != "" {
|
||||||
|
@ -32,6 +32,7 @@ func subTestSearch(t *testing.T, mc *mockServer) {
|
|||||||
runStep(t, mc, "SEARCH_CURSOR", keys_SEARCH_CURSOR_test)
|
runStep(t, mc, "SEARCH_CURSOR", keys_SEARCH_CURSOR_test)
|
||||||
runStep(t, mc, "MATCH", keys_MATCH_test)
|
runStep(t, mc, "MATCH", keys_MATCH_test)
|
||||||
runStep(t, mc, "FIELDS", keys_FIELDS_search_test)
|
runStep(t, mc, "FIELDS", keys_FIELDS_search_test)
|
||||||
|
runStep(t, mc, "BUFFER", keys_BUFFER_search_test)
|
||||||
}
|
}
|
||||||
|
|
||||||
func keys_KNN_basic_test(mc *mockServer) error {
|
func keys_KNN_basic_test(mc *mockServer) error {
|
||||||
@ -696,6 +697,37 @@ func keys_FIELDS_search_test(mc *mockServer) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func keys_BUFFER_search_test(mc *mockServer) error {
|
||||||
|
const lineString = `{"type":"LineString","coordinates":[
|
||||||
|
[-116.40289306640624,34.125447565116126],
|
||||||
|
[-116.36444091796875,34.14818102254435],
|
||||||
|
[-116.0980224609375,34.15045403191448],
|
||||||
|
[-115.74920654296874,34.127721186043985],
|
||||||
|
[-115.54870605468749,34.075412438417395],
|
||||||
|
[-115.5267333984375,34.11407854333859],
|
||||||
|
[-115.21911621093749,34.048108084909835],
|
||||||
|
[-115.25207519531249,33.8339199536547],
|
||||||
|
[-115.40588378906249,33.71748624018193]
|
||||||
|
]}`
|
||||||
|
|
||||||
|
return mc.DoBatch([][]interface{}{
|
||||||
|
// points in
|
||||||
|
{"SET", "fleet", "truck01", "POINT", "34.10825132729329", "-115.6436347961428"}, {"OK"},
|
||||||
|
{"SET", "fleet", "truck02", "POINT", "34.07199987534163", "-115.5435562133782"}, {"OK"},
|
||||||
|
{"SET", "fleet", "truck03", "POINT", "34.05123715497616", "-115.2148246765137"}, {"OK"},
|
||||||
|
{"SET", "fleet", "truck04", "POINT", "33.71520164474084", "-115.4110336303711"}, {"OK"},
|
||||||
|
{"SET", "fleet", "truck05", "POINT", "34.12345809664606", "-116.4070129394531"}, {"OK"},
|
||||||
|
// points out
|
||||||
|
{"SET", "fleet", "truck06", "POINT", "35.10825132729329", "-115.6436347961428"}, {"OK"},
|
||||||
|
{"SET", "fleet", "truck07", "POINT", "35.07199987534163", "-115.5435562133782"}, {"OK"},
|
||||||
|
{"SET", "fleet", "truck08", "POINT", "35.05123715497616", "-115.2148246765137"}, {"OK"},
|
||||||
|
{"SET", "fleet", "truck09", "POINT", "35.71520164474084", "-115.4110336303711"}, {"OK"},
|
||||||
|
{"SET", "fleet", "truck10", "POINT", "35.12345809664606", "-116.4070129394531"}, {"OK"},
|
||||||
|
// buffered intersects
|
||||||
|
{"INTERSECTS", "fleet", "BUFFER", "1000", "COUNT", "OBJECT", lineString}, {"5"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// match sorts the response and compares to the expected input
|
// match sorts the response and compares to the expected input
|
||||||
func match(expectIn string) func(org, v interface{}) (resp, expect interface{}) {
|
func match(expectIn string) func(org, v interface{}) (resp, expect interface{}) {
|
||||||
return func(v, org interface{}) (resp, expect interface{}) {
|
return func(v, org interface{}) (resp, expect interface{}) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user