tile38/pkg/controller/fence.go

427 lines
11 KiB
Go
Raw Normal View History

2016-03-19 17:16:19 +03:00
package controller
import (
2016-10-03 21:37:16 +03:00
"math"
2016-05-23 23:01:42 +03:00
"strconv"
2016-12-28 21:16:28 +03:00
"time"
2016-03-19 17:16:19 +03:00
"github.com/tidwall/gjson"
"github.com/tidwall/tile38/pkg/geojson"
"github.com/tidwall/tile38/pkg/glob"
"github.com/tidwall/tile38/pkg/server"
2016-03-19 17:16:19 +03:00
)
2016-04-03 05:16:36 +03:00
// FenceMatch executes a fence match returns back json messages for fence detection.
2018-08-14 03:05:30 +03:00
func FenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas []FenceMeta, details *commandDetailsT) []string {
msgs := fenceMatch(hookName, sw, fence, metas, details)
if len(fence.accept) == 0 {
return msgs
}
2018-08-14 03:05:30 +03:00
nmsgs := make([]string, 0, len(msgs))
for _, msg := range msgs {
2018-08-14 03:05:30 +03:00
if fence.accept[gjson.Get(msg, "command").String()] {
nmsgs = append(nmsgs, msg)
}
}
return nmsgs
}
func appendHookDetails(b []byte, hookName string, metas []FenceMeta) []byte {
if len(hookName) > 0 {
b = append(b, `,"hook":`...)
b = appendJSONString(b, hookName)
}
if len(metas) > 0 {
b = append(b, `,"meta":{`...)
for i, meta := range metas {
if i > 0 {
b = append(b, ',')
}
b = appendJSONString(b, meta.Name)
b = append(b, ':')
b = appendJSONString(b, meta.Value)
}
b = append(b, '}')
}
return b
}
func hookJSONString(hookName string, metas []FenceMeta) string {
return string(appendHookDetails(nil, hookName, metas))
}
2018-08-14 20:48:28 +03:00
func fenceMatch(
hookName string, sw *scanWriter, fence *liveFenceSwitches,
metas []FenceMeta, details *commandDetailsT,
) []string {
2016-03-19 17:16:19 +03:00
if details.command == "drop" {
2018-08-14 03:05:30 +03:00
return []string{
`{"command":"drop"` + hookJSONString(hookName, metas) +
`,"time":` + jsonTimeFormat(details.timestamp) + `}`,
}
2016-03-19 17:16:19 +03:00
}
2016-12-28 21:16:28 +03:00
if len(fence.glob) > 0 && !(len(fence.glob) == 1 && fence.glob[0] == '*') {
match, _ := glob.Match(fence.glob, details.id)
if !match {
return nil
}
2016-03-19 17:16:19 +03:00
}
2016-12-28 21:16:28 +03:00
if details.obj == nil || !details.obj.IsGeometry() {
2016-03-19 17:16:19 +03:00
return nil
}
2016-12-28 21:16:28 +03:00
if details.command == "fset" {
sw.mu.Lock()
nofields := sw.nofields
sw.mu.Unlock()
if nofields {
return nil
}
}
if details.command == "del" {
2018-08-14 20:48:28 +03:00
if fence.roam.on {
if fence.roam.nearbys != nil {
delete(fence.roam.nearbys, details.id)
if len(fence.roam.nearbys) == 0 {
fence.roam.nearbys = nil
}
}
}
2018-08-14 03:05:30 +03:00
return []string{
2018-08-14 20:48:28 +03:00
`{"command":"del"` + hookJSONString(hookName, metas) +
`,"id":` + jsonString(details.id) +
2018-08-14 03:05:30 +03:00
`,"time":` + jsonTimeFormat(details.timestamp) + `}`,
}
2016-03-19 17:16:19 +03:00
}
2018-08-14 20:48:28 +03:00
var roamNearbys, roamFaraways []roamMatch
2017-10-05 18:20:40 +03:00
var detect = "outside"
2016-03-19 17:16:19 +03:00
if fence != nil {
2016-05-23 23:01:42 +03:00
if fence.roam.on {
if details.command == "set" {
2018-08-14 20:48:28 +03:00
roamNearbys, roamFaraways =
2018-08-14 06:27:22 +03:00
fenceMatchRoam(sw.c, fence, details.key,
details.id, details.obj)
2016-05-23 23:01:42 +03:00
}
2018-08-14 20:48:28 +03:00
if len(roamNearbys) == 0 && len(roamFaraways) == 0 {
2016-05-23 23:01:42 +03:00
return nil
2016-04-02 17:20:30 +03:00
}
2016-05-23 23:01:42 +03:00
detect = "roam"
2016-03-19 17:16:19 +03:00
} else {
2016-05-23 23:01:42 +03:00
// not using roaming
match1 := fenceMatchObject(fence, details.oldObj)
match2 := fenceMatchObject(fence, details.obj)
if match1 && match2 {
detect = "inside"
} else if match1 && !match2 {
detect = "exit"
} else if !match1 && match2 {
detect = "enter"
if details.command == "fset" {
detect = "inside"
}
} else {
if details.command != "fset" {
// Maybe the old object and new object create a line that crosses the fence.
// Must detect for that possibility.
if details.oldObj != nil {
ls := geojson.LineString{
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) {
detect = "cross"
}
if temp {
fence.cmd = "within"
}
2016-04-02 17:20:30 +03:00
}
2016-03-19 17:16:19 +03:00
}
}
}
}
2016-12-28 21:16:28 +03:00
2016-04-02 17:20:30 +03:00
if details.fmap == nil {
2016-03-19 17:16:19 +03:00
return nil
}
2017-02-24 16:03:11 +03:00
for {
if fence.detect != nil && !fence.detect[detect] {
if detect == "enter" {
detect = "inside"
continue
}
if detect == "exit" {
detect = "outside"
continue
}
return nil
}
break
2016-12-28 21:16:28 +03:00
}
2016-04-03 00:13:20 +03:00
sw.mu.Lock()
2017-02-24 16:25:50 +03:00
var distance float64
if fence.distance {
distance = details.obj.CalculatedPoint().DistanceTo(geojson.Position{X: fence.lon, Y: fence.lat, Z: 0})
}
2016-04-02 17:20:30 +03:00
sw.fmap = details.fmap
2016-03-19 17:16:19 +03:00
sw.fullFields = true
2016-03-30 19:32:38 +03:00
sw.msg.OutputType = server.JSON
2017-01-10 19:49:48 +03:00
sw.writeObject(ScanWriterParams{
2017-02-24 16:25:50 +03:00
id: details.id,
o: details.obj,
fields: details.fields,
noLock: true,
distance: distance,
2017-01-10 19:49:48 +03:00
})
2017-02-08 14:16:54 +03:00
2016-03-19 17:16:19 +03:00
if sw.wr.Len() == 0 {
2016-04-03 00:13:20 +03:00
sw.mu.Unlock()
2016-03-19 17:16:19 +03:00
return nil
}
2016-05-23 23:01:42 +03:00
2018-08-14 03:05:30 +03:00
res := sw.wr.String()
2016-03-19 17:16:19 +03:00
sw.wr.Reset()
if len(res) > 0 && res[0] == ',' {
2016-05-23 23:40:08 +03:00
res = res[1:]
}
2016-03-19 17:16:19 +03:00
if sw.output == outputIDs {
2018-08-14 03:05:30 +03:00
res = `{"id":` + string(res) + `}`
2016-03-19 17:16:19 +03:00
}
2016-04-03 00:13:20 +03:00
sw.mu.Unlock()
if fence.groups == nil {
fence.groups = make(map[string]string)
}
groupkey := details.key + ":" + details.id
var group string
var ok bool
if detect == "enter" {
group = bsonID()
fence.groups[groupkey] = group
} else if detect == "cross" {
group = bsonID()
delete(fence.groups, groupkey)
} else {
group, ok = fence.groups[groupkey]
if !ok {
group = bsonID()
fence.groups[groupkey] = group
}
}
2018-08-14 03:05:30 +03:00
var msgs []string
2016-04-01 22:46:39 +03:00
if fence.detect == nil || fence.detect[detect] {
2017-02-12 17:06:56 +03:00
if len(res) > 0 && res[0] == '{' {
2018-08-14 03:05:30 +03:00
msgs = append(msgs, makemsg(details.command, group, detect,
hookName, metas, details.key, details.timestamp, res[1:]))
2017-02-12 17:06:56 +03:00
} else {
2018-08-14 03:05:30 +03:00
msgs = append(msgs, string(res))
2016-04-01 22:46:39 +03:00
}
2016-03-19 17:16:19 +03:00
}
switch detect {
case "enter":
2016-04-01 22:46:39 +03:00
if fence.detect == nil || fence.detect["inside"] {
msgs = append(msgs, makemsg(details.command, group, "inside", hookName, metas, details.key, details.timestamp, res[1:]))
2016-04-01 22:46:39 +03:00
}
2016-03-19 17:16:19 +03:00
case "exit", "cross":
2016-04-01 22:46:39 +03:00
if fence.detect == nil || fence.detect["outside"] {
msgs = append(msgs, makemsg(details.command, group, "outside", hookName, metas, details.key, details.timestamp, res[1:]))
2016-04-01 22:46:39 +03:00
}
2016-05-23 23:01:42 +03:00
case "roam":
if len(msgs) > 0 {
2018-08-14 03:05:30 +03:00
var nmsgs []string
2018-08-14 20:48:28 +03:00
for i := range roamNearbys {
nmsg := extendRoamMessage(sw, fence,
"nearby", msgs[0], roamNearbys[i])
nmsgs = append(nmsgs, string(nmsg))
}
for i := range roamFaraways {
nmsg := extendRoamMessage(sw, fence,
"faraway", msgs[0], roamFaraways[i])
2018-08-14 03:05:30 +03:00
nmsgs = append(nmsgs, string(nmsg))
2016-05-23 23:01:42 +03:00
}
msgs = nmsgs
}
2016-03-19 17:16:19 +03:00
}
return msgs
}
2018-08-14 20:48:28 +03:00
func extendRoamMessage(
sw *scanWriter, fence *liveFenceSwitches,
kind string, baseMsg string, match roamMatch,
) string {
// hack off the last '}'
nmsg := []byte(baseMsg[:len(baseMsg)-1])
nmsg = append(nmsg, `,"`+kind+`":{"key":`...)
nmsg = appendJSONString(nmsg, fence.roam.key)
nmsg = append(nmsg, `,"id":`...)
nmsg = appendJSONString(nmsg, match.id)
nmsg = append(nmsg, `,"object":`...)
nmsg = append(nmsg, match.obj.JSON()...)
nmsg = append(nmsg, `,"meters":`...)
nmsg = strconv.AppendFloat(nmsg,
math.Floor(match.meters*1000)/1000, 'f', -1, 64)
if fence.roam.scan != "" {
nmsg = append(nmsg, `,"scan":[`...)
col := sw.c.getCol(fence.roam.key)
if col != nil {
obj, _, ok := col.Get(match.id)
if ok {
nmsg = append(nmsg,
`{"id":`+jsonString(match.id)+
`,"self":true,"object":`+obj.JSON()+`}`...)
}
pattern := match.id + fence.roam.scan
iterator := func(
oid string, o geojson.Object, fields []float64,
) bool {
if oid == match.id {
return true
}
if matched, _ := glob.Match(pattern, oid); matched {
nmsg = append(nmsg,
`,{"id":`+jsonString(oid)+
`,"object":`+o.JSON()+`}`...)
}
return true
}
g := glob.Parse(pattern, false)
if g.Limits[0] == "" && g.Limits[1] == "" {
col.Scan(false, iterator)
} else {
col.ScanRange(g.Limits[0], g.Limits[1],
false, iterator)
}
}
nmsg = append(nmsg, ']')
}
nmsg = append(nmsg, '}')
// re-add the last '}'
nmsg = append(nmsg, '}')
return string(nmsg)
}
2018-08-14 03:05:30 +03:00
func makemsg(
command, group, detect, hookName string,
metas []FenceMeta, key string, t time.Time, tail string,
) string {
var buf []byte
buf = append(append(buf, `{"command":"`...), command...)
buf = append(append(buf, `","group":"`...), group...)
buf = append(append(buf, `","detect":"`...), detect...)
buf = append(buf, '"')
buf = appendHookDetails(buf, hookName, metas)
2016-12-28 21:16:28 +03:00
buf = appendJSONString(append(buf, `,"key":`...), key)
buf = appendJSONTimeFormat(append(buf, `,"time":`...), t)
buf = append(append(buf, ','), tail...)
2018-08-14 03:05:30 +03:00
return string(buf)
}
2016-03-19 17:16:19 +03:00
func fenceMatchObject(fence *liveFenceSwitches, obj geojson.Object) bool {
if obj == nil {
return false
}
2016-05-23 23:01:42 +03:00
if fence.roam.on {
// we need to check this object against
return false
}
2016-12-28 21:16:28 +03:00
2016-03-19 17:16:19 +03:00
if fence.cmd == "nearby" {
return obj.Nearby(geojson.Position{X: fence.lon, Y: fence.lat, Z: 0}, fence.meters)
2016-12-28 21:16:28 +03:00
}
if fence.cmd == "within" {
2016-03-19 17:16:19 +03:00
if fence.o != nil {
return obj.Within(fence.o)
}
2016-04-03 05:16:36 +03:00
return obj.WithinBBox(geojson.BBox{
Min: geojson.Position{X: fence.minLon, Y: fence.minLat, Z: 0},
Max: geojson.Position{X: fence.maxLon, Y: fence.maxLat, Z: 0},
})
2016-12-28 21:16:28 +03:00
}
if fence.cmd == "intersects" {
2016-03-19 17:16:19 +03:00
if fence.o != nil {
return obj.Intersects(fence.o)
}
2016-04-03 05:16:36 +03:00
return obj.IntersectsBBox(geojson.BBox{
Min: geojson.Position{X: fence.minLon, Y: fence.minLat, Z: 0},
Max: geojson.Position{X: fence.maxLon, Y: fence.maxLat, Z: 0},
})
2016-03-19 17:16:19 +03:00
}
return false
}
2016-05-23 23:01:42 +03:00
2018-08-14 06:27:22 +03:00
func fenceMatchRoam(
c *Controller, fence *liveFenceSwitches,
tkey, tid string, obj geojson.Object,
2018-08-14 20:48:28 +03:00
) (nearbys, faraways []roamMatch) {
2016-05-23 23:01:42 +03:00
col := c.getCol(fence.roam.key)
if col == nil {
return
}
p := obj.CalculatedPoint()
2018-08-14 20:48:28 +03:00
var prevNearbys []roamMatch
if fence.roam.nearbys != nil {
prevNearbys = fence.roam.nearbys[tid]
}
col.Nearby(0, p.Y, p.X, fence.roam.meters, math.Inf(-1), math.Inf(+1),
2016-05-23 23:01:42 +03:00
func(id string, obj geojson.Object, fields []float64) bool {
2018-08-14 20:48:28 +03:00
var idMatch bool
2016-05-23 23:01:42 +03:00
if id == tid {
return true // skip self
}
if fence.roam.pattern {
2018-08-14 20:48:28 +03:00
idMatch, _ = glob.Match(fence.roam.id, id)
2016-05-23 23:01:42 +03:00
} else {
2018-08-14 20:48:28 +03:00
idMatch = fence.roam.id == id
2016-05-23 23:01:42 +03:00
}
2018-08-14 20:48:28 +03:00
if !idMatch {
return true
2016-05-23 23:01:42 +03:00
}
2018-08-14 20:48:28 +03:00
match := roamMatch{
id: id,
obj: obj,
meters: obj.CalculatedPoint().DistanceTo(p),
}
for i := 0; i < len(prevNearbys); i++ {
if prevNearbys[i].id == match.id {
prevNearbys[i] = prevNearbys[len(prevNearbys)-1]
prevNearbys = prevNearbys[:len(prevNearbys)-1]
i--
break
}
}
nearbys = append(nearbys, match)
2016-05-23 23:01:42 +03:00
return true
},
)
2018-08-19 01:53:02 +03:00
for i := 0; i < len(prevNearbys); i++ {
obj, _, ok := col.Get(prevNearbys[i].id)
if ok {
faraways = append(faraways, roamMatch{
id: prevNearbys[i].id,
obj: obj,
meters: obj.CalculatedPoint().DistanceTo(p),
})
}
2018-08-14 20:48:28 +03:00
}
if len(nearbys) == 0 {
if fence.roam.nearbys != nil {
delete(fence.roam.nearbys, tid)
if len(fence.roam.nearbys) == 0 {
fence.roam.nearbys = nil
}
}
} else {
if fence.roam.nearbys == nil {
fence.roam.nearbys = make(map[string][]roamMatch)
}
fence.roam.nearbys[tid] = nearbys
}
2016-05-23 23:01:42 +03:00
return
}