roaming fence

This commit is contained in:
Josh Baker 2016-05-23 13:01:42 -07:00
parent b5b4f92d77
commit 8fdc35af61
3 changed files with 138 additions and 40 deletions

View File

@ -1,6 +1,7 @@
package controller package controller
import ( import (
"strconv"
"strings" "strings"
"github.com/tidwall/tile38/controller/server" "github.com/tidwall/tile38/controller/server"
@ -33,46 +34,63 @@ func FenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, detai
return nil return nil
} }
match = false match = false
var roamkeys, roamids []string
var roammeters []float64
detect := "outside" detect := "outside"
if fence != nil { if fence != nil {
match1 := fenceMatchObject(fence, details.oldObj) if fence.roam.on {
match2 := fenceMatchObject(fence, details.obj) if details.command == "set" {
if match1 && match2 { // println("roam", fence.roam.key, fence.roam.id, strconv.FormatFloat(fence.roam.meters, 'f', -1, 64))
match = true roamkeys, roamids, roammeters = fenceMatchRoam(sw.c, fence, details.key, details.id, details.obj)
detect = "inside"
} else if match1 && !match2 {
match = true
detect = "exit"
} else if !match1 && match2 {
match = true
detect = "enter"
if details.command == "fset" {
detect = "inside"
} }
if len(roamids) == 0 || len(roamids) != len(roamkeys) {
return nil
}
match = true
detect = "roam"
} else { } else {
if details.command != "fset" {
// Maybe the old object and new object create a line that crosses the fence. // not using roaming
// Must detect for that possibility. match1 := fenceMatchObject(fence, details.oldObj)
if details.oldObj != nil { match2 := fenceMatchObject(fence, details.obj)
ls := geojson.LineString{ if match1 && match2 {
Coordinates: []geojson.Position{ match = true
details.oldObj.CalculatedPoint(), detect = "inside"
details.obj.CalculatedPoint(), } else if match1 && !match2 {
}, match = true
} detect = "exit"
temp := false } else if !match1 && match2 {
if fence.cmd == "within" { match = true
// because we are testing if the line croses the area we need to use detect = "enter"
// "intersects" instead of "within". if details.command == "fset" {
fence.cmd = "intersects" detect = "inside"
temp = true }
} } else {
if fenceMatchObject(fence, ls) { if details.command != "fset" {
//match = true // Maybe the old object and new object create a line that crosses the fence.
detect = "cross" // Must detect for that possibility.
} if details.oldObj != nil {
if temp { ls := geojson.LineString{
fence.cmd = "within" Coordinates: []geojson.Position{
details.oldObj.CalculatedPoint(),
details.obj.CalculatedPoint(),
},
}
temp := false
if fence.cmd == "within" {
// because we are testing if the line croses the area we need to use
// "intersects" instead of "within".
fence.cmd = "intersects"
temp = true
}
if fenceMatchObject(fence, ls) {
//match = true
detect = "cross"
}
if temp {
fence.cmd = "within"
}
} }
} }
} }
@ -93,21 +111,22 @@ func FenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, detai
sw.mu.Unlock() sw.mu.Unlock()
return nil return nil
} }
res := sw.wr.String() res := sw.wr.String()
resb := make([]byte, len(res)) resb := make([]byte, len(res))
copy(resb, res) copy(resb, res)
res = string(resb)
sw.wr.Reset() sw.wr.Reset()
res = string(resb)
if sw.output == outputIDs { if sw.output == outputIDs {
res = `{"id":` + res + `}` res = `{"id":` + res + `}`
} }
sw.mu.Unlock() sw.mu.Unlock()
if strings.HasPrefix(res, ",") { if strings.HasPrefix(res, ",") {
res = res[1:] res = res[1:]
} }
jskey := jsonString(details.key) jskey := jsonString(details.key)
ores := res ores := res
msgs := make([]string, 0, 2) msgs := make([]string, 0, 2)
if fence.detect == nil || fence.detect[detect] { if fence.detect == nil || fence.detect[detect] {
@ -125,6 +144,15 @@ func FenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, detai
if fence.detect == nil || fence.detect["outside"] { if fence.detect == nil || fence.detect["outside"] {
msgs = append(msgs, `{"command":"`+details.command+`","detect":"outside","hook":`+jshookName+`,"key":`+jskey+`,"time":`+jstime+`,`+ores[1:]) msgs = append(msgs, `{"command":"`+details.command+`","detect":"outside","hook":`+jshookName+`,"key":`+jskey+`,"time":`+jstime+`,`+ores[1:])
} }
case "roam":
if len(msgs) > 0 {
var nmsgs []string
msg := msgs[0][:len(msgs[0])-1]
for i, id := range roamids {
nmsgs = append(nmsgs, msg+`,"nearby":{"key":`+jsonString(roamkeys[i])+`,"id":`+jsonString(id)+`,"meters":`+strconv.FormatFloat(roammeters[i], 'f', -1, 64)+`}}`)
}
msgs = nmsgs
}
} }
return msgs return msgs
} }
@ -133,6 +161,10 @@ func fenceMatchObject(fence *liveFenceSwitches, obj geojson.Object) bool {
if obj == nil { if obj == nil {
return false return false
} }
if fence.roam.on {
// we need to check this object against
return false
}
if fence.cmd == "nearby" { if fence.cmd == "nearby" {
return obj.Nearby(geojson.Position{X: fence.lon, Y: fence.lat, Z: 0}, fence.meters) return obj.Nearby(geojson.Position{X: fence.lon, Y: fence.lat, Z: 0}, fence.meters)
} else if fence.cmd == "within" { } else if fence.cmd == "within" {
@ -154,3 +186,33 @@ func fenceMatchObject(fence *liveFenceSwitches, obj geojson.Object) bool {
} }
return false return false
} }
func fenceMatchRoam(c *Controller, fence *liveFenceSwitches, tkey, tid string, obj geojson.Object) (keys, ids []string, meterss []float64) {
c.mu.RLock()
defer c.mu.RUnlock()
col := c.getCol(fence.roam.key)
if col == nil {
return
}
p := obj.CalculatedPoint()
col.Nearby(0, 0, p.Y, p.X, fence.roam.meters,
func(id string, obj geojson.Object, fields []float64) bool {
var match bool
if id == tid {
return true // skip self
}
if fence.roam.pattern {
match, _ = globMatch(fence.roam.id, id)
} else {
match = fence.roam.id == id
}
if match {
keys = append(keys, fence.roam.key)
ids = append(ids, id)
meterss = append(meterss, obj.CalculatedPoint().DistanceTo(p))
}
return true
},
)
return
}

View File

@ -29,6 +29,7 @@ const (
type scanWriter struct { type scanWriter struct {
mu sync.Mutex mu sync.Mutex
c *Controller
wr *bytes.Buffer wr *bytes.Buffer
msg *server.Message msg *server.Message
col *collection.Collection col *collection.Collection
@ -67,6 +68,7 @@ func (c *Controller) newScanWriter(
case outputIDs, outputObjects, outputCount, outputBounds, outputPoints, outputHashes: case outputIDs, outputObjects, outputCount, outputBounds, outputPoints, outputHashes:
} }
sw := &scanWriter{ sw := &scanWriter{
c: c,
wr: wr, wr: wr,
msg: msg, msg: msg,
output: output, output: output,

View File

@ -20,6 +20,15 @@ type liveFenceSwitches struct {
minLat, minLon float64 minLat, minLon float64
maxLat, maxLon float64 maxLat, maxLon float64
cmd string cmd string
roam roamSwitches
}
type roamSwitches struct {
on bool
key string
id string
pattern bool
meters float64
} }
func (s liveFenceSwitches) Error() string { func (s liveFenceSwitches) Error() string {
@ -46,18 +55,23 @@ func (c *Controller) cmdSearchArgs(cmd string, vs []resp.Value, types []string)
} }
} }
} }
ltyp := strings.ToLower(typ)
var found bool var found bool
for _, t := range types { for _, t := range types {
if strings.ToLower(typ) == t { if ltyp == t {
found = true found = true
break break
} }
} }
if !found && s.searchScanBaseTokens.fence && ltyp == "roam" && cmd == "nearby" {
// allow roaming for nearby fence searches.
found = true
}
if !found { if !found {
err = errInvalidArgument(typ) err = errInvalidArgument(typ)
return return
} }
switch strings.ToLower(typ) { switch ltyp {
case "point": case "point":
var slat, slon, smeters string var slat, slon, smeters string
if vs, slat, ok = tokenval(vs); !ok || slat == "" { if vs, slat, ok = tokenval(vs); !ok || slat == "" {
@ -206,6 +220,26 @@ func (c *Controller) cmdSearchArgs(cmd string, vs []resp.Value, types []string)
} else { } else {
s.o = o s.o = o
} }
case "roam":
s.roam.on = true
if vs, s.roam.key, ok = tokenval(vs); !ok || s.roam.key == "" {
err = errInvalidNumberOfArguments
return
}
if vs, s.roam.id, ok = tokenval(vs); !ok || s.roam.id == "" {
err = errInvalidNumberOfArguments
return
}
s.roam.pattern = globIsGlob(s.roam.id)
var smeters string
if vs, smeters, ok = tokenval(vs); !ok || smeters == "" {
err = errInvalidNumberOfArguments
return
}
if s.roam.meters, err = strconv.ParseFloat(smeters, 64); err != nil {
err = errInvalidArgument(smeters)
return
}
} }
if len(vs) != 0 { if len(vs) != 0 {
err = errInvalidNumberOfArguments err = errInvalidNumberOfArguments