tile38/internal/server/expression.go
2019-06-13 09:53:17 -07:00

243 lines
4.6 KiB
Go

package server
import (
"strings"
"github.com/tidwall/geojson"
)
type BinaryOp byte
const (
NOOP BinaryOp = iota
AND
OR
tokenAND = "and"
tokenOR = "or"
tokenNOT = "not"
tokenLParen = "("
tokenRParen = ")"
)
// areaExpression is either an object or operator+children
type areaExpression struct {
negate bool
obj geojson.Object
op BinaryOp
children children
}
type children []*areaExpression
func (e *areaExpression) String() (res string) {
if e.obj != nil {
res = e.obj.String()
} else {
var chStrings []string
for _, c := range e.children {
chStrings = append(chStrings, c.String())
}
switch e.op {
case NOOP:
res = "empty operator"
case AND:
res = "(" + strings.Join(chStrings, " "+tokenAND+" ") + ")"
case OR:
res = "(" + strings.Join(chStrings, " "+tokenOR+" ") + ")"
default:
res = "unknown operator"
}
}
if e.negate {
res = tokenNOT + " " + res
}
return
}
// Return boolean value modulo negate field of the expression.
func (e *areaExpression) maybeNegate(val bool) bool {
if e.negate {
return !val
}
return val
}
// Methods for testing an areaExpression against the spatial object
func (e *areaExpression) rawIntersects(o geojson.Object) bool {
if e.obj != nil {
return e.obj.Intersects(o)
}
switch e.op {
case AND:
for _, c := range e.children {
if !c.Intersects(o) {
return false
}
}
return true
case OR:
for _, c := range e.children {
if c.Intersects(o) {
return true
}
}
return false
}
return false
}
func (e *areaExpression) rawContains(o geojson.Object) bool {
if e.obj != nil {
return e.obj.Contains(o)
}
switch e.op {
case AND:
for _, c:= range e.children {
if !c.Contains(o) {
return false
}
}
return true
case OR:
for _, c:= range e.children {
if c.Contains(o) {
return true
}
}
return false
}
return false
}
func (e *areaExpression) rawWithin(o geojson.Object) bool {
if e.obj != nil {
return e.obj.Within(o)
}
switch e.op {
case AND:
for _, c:= range e.children {
if !c.Within(o) {
return false
}
}
return true
case OR:
for _, c:= range e.children {
if c.Within(o) {
return true
}
}
return false
}
return false
}
func (e *areaExpression) Intersects(o geojson.Object) bool {
return e.maybeNegate(e.rawIntersects(o))
}
func (e *areaExpression) Contains(o geojson.Object) bool {
return e.maybeNegate(e.rawContains(o))
}
func (e *areaExpression) Within(o geojson.Object) bool {
return e.maybeNegate(e.rawWithin(o))
}
// Methods for testing an areaExpression against another areaExpression
func (e *areaExpression) rawIntersectsExpr(oe *areaExpression) bool {
if oe.negate {
e2 := &areaExpression{negate:!e.negate, obj:e.obj, op: e.op, children:e.children}
oe2 := &areaExpression{negate:false, obj:oe.obj, op:oe.op, children:oe.children}
return e2.IntersectsExpr(oe2)
}
if oe.obj != nil {
return e.rawIntersects(oe.obj)
}
switch oe.op {
case AND:
for _, c := range oe.children {
if !e.rawIntersectsExpr(c) {
return false
}
}
return true
case OR:
for _, c := range oe.children {
if e.rawIntersectsExpr(c) {
return true
}
}
return false
}
return false
}
func (e *areaExpression) rawWithinExpr(oe *areaExpression) bool {
if oe.negate {
e2 := &areaExpression{negate:!e.negate, obj:e.obj, op: e.op, children:e.children}
oe2 := &areaExpression{negate:false, obj:oe.obj, op:oe.op, children:oe.children}
return e2.WithinExpr(oe2)
}
if oe.obj != nil {
return e.rawWithin(oe.obj)
}
switch oe.op {
case AND:
for _, c:= range oe.children {
if !e.rawWithinExpr(c) {
return false
}
}
return true
case OR:
for _, c:= range oe.children {
if e.rawWithinExpr(c) {
return true
}
}
return false
}
return false
}
func (e *areaExpression) rawContainsExpr(oe *areaExpression) bool {
if oe.negate {
e2 := &areaExpression{negate:!e.negate, obj:e.obj, op: e.op, children:e.children}
oe2 := &areaExpression{negate:false, obj:oe.obj, op:oe.op, children:oe.children}
return e2.ContainsExpr(oe2)
}
if oe.obj != nil {
return e.rawContains(oe.obj)
}
switch oe.op {
case AND:
for _, c:= range oe.children {
if !e.rawContainsExpr(c) {
return false
}
}
return true
case OR:
for _, c:= range oe.children {
if e.rawContainsExpr(c) {
return true
}
}
return false
}
return false
}
func (e *areaExpression) IntersectsExpr(oe *areaExpression) bool {
return e.maybeNegate(e.rawIntersectsExpr(oe))
}
func (e *areaExpression) WithinExpr(oe *areaExpression) bool {
return e.maybeNegate(e.rawWithinExpr(oe))
}
func (e *areaExpression) ContainsExpr(oe *areaExpression) bool {
return e.maybeNegate(e.rawContainsExpr(oe))
}