tile38/internal/server/expression.go

190 lines
4.4 KiB
Go

package server
import (
"strings"
"github.com/tidwall/geojson"
)
// BinaryOp represents various operators for expressions
type BinaryOp byte
// expression operator enum
const (
NOOP BinaryOp = iota
AND
OR
tokenAND = "and"
tokenOR = "or"
tokenNOT = "not"
tokenLParen = "("
tokenRParen = ")"
)
// areaExpression is (maybe negated) either an spatial object or operator +
// children (other expressions).
type areaExpression struct {
negate bool
obj geojson.Object
op BinaryOp
children children
}
type children []*areaExpression
// String representation, helpful in logging.
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) testObject(
o geojson.Object,
objObjTest func(o1, o2 geojson.Object) bool,
exprObjTest func(ae *areaExpression, ob geojson.Object) bool,
) bool {
if e.obj != nil {
return objObjTest(e.obj, o)
}
switch e.op {
case AND:
for _, c := range e.children {
if !exprObjTest(c, o) {
return false
}
}
return true
case OR:
for _, c := range e.children {
if exprObjTest(c, o) {
return true
}
}
return false
}
return false
}
func (e *areaExpression) rawIntersects(o geojson.Object) bool {
return e.testObject(o, geojson.Object.Intersects, (*areaExpression).Intersects)
}
func (e *areaExpression) rawContains(o geojson.Object) bool {
return e.testObject(o, geojson.Object.Contains, (*areaExpression).Contains)
}
func (e *areaExpression) rawWithin(o geojson.Object) bool {
return e.testObject(o, geojson.Object.Within, (*areaExpression).Within)
}
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) testExpression(
other *areaExpression,
exprObjTest func(ae *areaExpression, ob geojson.Object) bool,
rawExprExprTest func(ae1, ae2 *areaExpression) bool,
exprExprTest func(ae1, ae2 *areaExpression) bool,
) bool {
if other.negate {
oppositeExp := &areaExpression{negate: !e.negate, obj: e.obj, op: e.op, children: e.children}
nonNegateOther := &areaExpression{obj: other.obj, op: other.op, children: other.children}
return exprExprTest(oppositeExp, nonNegateOther)
}
if other.obj != nil {
return exprObjTest(e, other.obj)
}
switch other.op {
case AND:
for _, c := range other.children {
if !rawExprExprTest(e, c) {
return false
}
}
return true
case OR:
for _, c := range other.children {
if rawExprExprTest(e, c) {
return true
}
}
return false
}
return false
}
func (e *areaExpression) rawIntersectsExpr(other *areaExpression) bool {
return e.testExpression(
other,
(*areaExpression).rawIntersects,
(*areaExpression).rawIntersectsExpr,
(*areaExpression).IntersectsExpr)
}
func (e *areaExpression) rawWithinExpr(other *areaExpression) bool {
return e.testExpression(
other,
(*areaExpression).rawWithin,
(*areaExpression).rawWithinExpr,
(*areaExpression).WithinExpr)
}
func (e *areaExpression) rawContainsExpr(other *areaExpression) bool {
return e.testExpression(
other,
(*areaExpression).rawContains,
(*areaExpression).rawContainsExpr,
(*areaExpression).ContainsExpr)
}
func (e *areaExpression) IntersectsExpr(other *areaExpression) bool {
return e.maybeNegate(e.rawIntersectsExpr(other))
}
func (e *areaExpression) WithinExpr(other *areaExpression) bool {
return e.maybeNegate(e.rawWithinExpr(other))
}
func (e *areaExpression) ContainsExpr(other *areaExpression) bool {
return e.maybeNegate(e.rawContainsExpr(other))
}