"""@package grass.temporal
Temporal operator evaluation with PLY
(C) 2013 by the GRASS Development Team
This program is free software under the GNU General Public
License (>=v2). Read the file COPYING that comes with GRASS
for details.
:authors: Thomas Leppelt and Soeren Gebbert
.. code-block:: python
>>> p = TemporalOperatorParser()
>>> expression = "{equal|equivalent|cover|in|meet|contain|overlap}"
>>> p.parse(expression, optype="relation")
>>> print((p.relations, p.temporal, p.function))
(['equal', 'equivalent', 'cover', 'in', 'meet', 'contain', 'overlap'], None, None)
>>> p = TemporalOperatorParser()
>>> expression = "{equal| during}"
>>> p.parse(expression, optype="relation")
>>> print((p.relations, p.temporal, p.function))
(['equal', 'during'], None, None)
>>> p = TemporalOperatorParser()
>>> expression = "{contains | starts}"
>>> p.parse(expression)
>>> print((p.relations, p.temporal, p.function))
(['contains', 'starts'], None, None)
>>> p = TemporalOperatorParser()
>>> expression = "{&&, during}"
>>> p.parse(expression, optype="boolean")
>>> print((p.relations, p.temporal, p.function, p.aggregate))
(['during'], 'l', '&&', '&')
>>> p = TemporalOperatorParser()
>>> expression = "{||, equal | during}"
>>> p.parse(expression, optype="boolean")
>>> print((p.relations, p.temporal, p.function, p.aggregate))
(['equal', 'during'], 'l', '||', '|')
>>> p = TemporalOperatorParser()
>>> expression = "{||, equal | during, &}"
>>> p.parse(expression, optype="boolean")
>>> print((p.relations, p.temporal, p.function, p.aggregate))
(['equal', 'during'], 'l', '||', '&')
>>> p = TemporalOperatorParser()
>>> expression = "{&&, during, |}"
>>> p.parse(expression, optype="boolean")
>>> print((p.relations, p.temporal, p.function, p.aggregate))
(['during'], 'l', '&&', '|')
>>> p = TemporalOperatorParser()
>>> expression = "{&&, during, |, r}"
>>> p.parse(expression, optype="boolean")
>>> print((p.relations, p.temporal, p.function, p.aggregate))
(['during'], 'r', '&&', '|')
>>> p = TemporalOperatorParser()
>>> expression = "{&&, during, u}"
>>> p.parse(expression, optype="boolean")
>>> print((p.relations, p.temporal, p.function, p.aggregate))
(['during'], 'u', '&&', '&')
>>> p = TemporalOperatorParser()
>>> expression = "{:, during, r}"
>>> p.parse(expression, optype="select")
>>> print((p.relations, p.temporal, p.function))
(['during'], 'r', ':')
>>> p = TemporalOperatorParser()
>>> expression = "{!:, equal | contains, d}"
>>> p.parse(expression, optype="select")
>>> print((p.relations, p.temporal, p.function))
(['equal', 'contains'], 'd', '!:')
>>> p = TemporalOperatorParser()
>>> expression = "{#, during, r}"
>>> p.parse(expression, optype="hash")
>>> print((p.relations, p.temporal, p.function))
(['during'], 'r', '#')
>>> p = TemporalOperatorParser()
>>> expression = "{#, equal | contains}"
>>> p.parse(expression, optype="hash")
>>> print((p.relations, p.temporal, p.function))
(['equal', 'contains'], 'l', '#')
>>> p = TemporalOperatorParser()
>>> expression = "{+, during, r}"
>>> p.parse(expression, optype="raster")
>>> print((p.relations, p.temporal, p.function))
(['during'], 'r', '+')
>>> p = TemporalOperatorParser()
>>> expression = "{/, equal | contains}"
>>> p.parse(expression, optype="raster")
>>> print((p.relations, p.temporal, p.function))
(['equal', 'contains'], 'l', '/')
>>> p = TemporalOperatorParser()
>>> expression = "{+, equal | contains,intersect}"
>>> p.parse(expression, optype="raster")
>>> print((p.relations, p.temporal, p.function))
(['equal', 'contains'], 'i', '+')
>>> p = TemporalOperatorParser()
>>> expression = "{*, contains,disjoint}"
>>> p.parse(expression, optype="raster")
>>> print((p.relations, p.temporal, p.function))
(['contains'], 'd', '*')
>>> p = TemporalOperatorParser()
>>> expression = "{~, equal,left}"
>>> p.parse(expression, optype="overlay")
>>> print((p.relations, p.temporal, p.function))
(['equal'], 'l', '~')
>>> p = TemporalOperatorParser()
>>> expression = "{^, over,right}"
>>> p.parse(expression, optype="overlay")
>>> print((p.relations, p.temporal, p.function))
(['overlaps', 'overlapped'], 'r', '^')
>>> p = TemporalOperatorParser()
>>> expression = "{&&, equal | during | contains | starts, &}"
>>> p.parse(expression, optype="boolean")
>>> print((p.relations, p.temporal, p.function, p.aggregate))
(['equal', 'during', 'contains', 'starts'], 'l', '&&', '&')
>>> p = TemporalOperatorParser()
>>> expression = "{&&, equal | during | contains | starts, &&&&&}"
>>> p.parse(expression, optype="boolean")
Traceback (most recent call last):
SyntaxError: Unexpected syntax error in expression "{&&, equal | during | contains | starts, &&&&&}" at position 42 near &
>>> p = TemporalOperatorParser()
>>> expression = "{+, starting}"
>>> p.parse(expression)
Traceback (most recent call last):
SyntaxError: syntax error on line 1 position 4 near 'starting'
>>> p = TemporalOperatorParser()
>>> expression = "{nope, start, |, l}"
>>> p.parse(expression)
Traceback (most recent call last):
SyntaxError: syntax error on line 1 position 1 near 'nope'
>>> p = TemporalOperatorParser()
>>> expression = "{++, start, |, l}"
>>> p.parse(expression)
Traceback (most recent call last):
SyntaxError: Unexpected syntax error in expression "{++, start, |, l}" at position 2 near +
>>> p = TemporalOperatorParser()
>>> expression = "{^, over, right}"
>>> p.parse(expression, optype="rter")
Traceback (most recent call last):
SyntaxError: Unknown optype rter, must be one of ['select', 'boolean', 'raster', 'hash', 'relation', 'overlay']
""" # noqa: E501
try:
from ply import lex, yacc
except ImportError:
pass
[docs]class TemporalOperatorLexer:
"""Lexical analyzer for the GRASS GIS temporal operator"""
# Functions that defines topological relations.
relations = {
# temporal relations
"equal": "EQUAL",
"follows": "FOLLOWS",
"precedes": "PRECEDES",
"overlaps": "OVERLAPS",
"overlapped": "OVERLAPPED",
"during": "DURING",
"starts": "STARTS",
"finishes": "FINISHES",
"contains": "CONTAINS",
"started": "STARTED",
"finished": "FINISHED",
"over": "OVER",
# spatial relations
"equivalent": "EQUIVALENT",
"cover": "COVER",
"overlap": "OVERLAP",
"in": "IN",
"contain": "CONTAIN",
"meet": "MEET",
}
# This is the list of token names.
tokens = (
"COMMA",
"LEFTREF",
"RIGHTREF",
"UNION",
"DISJOINT",
"INTERSECT",
"HASH",
"OR",
"AND",
"DISOR",
"XOR",
"NOT",
"MOD",
"DIV",
"MULT",
"ADD",
"SUB",
"T_SELECT",
"T_NOT_SELECT",
"CLPAREN",
"CRPAREN",
)
# Build the token list
tokens += tuple(relations.values())
# Regular expression rules for simple tokens
t_T_SELECT = r":"
t_T_NOT_SELECT = r"!:"
t_COMMA = r","
t_LEFTREF = "^[l|left]"
t_RIGHTREF = "^[r|right]"
t_UNION = "^[u|union]"
t_DISJOINT = "^[d|disjoint]"
t_INTERSECT = "^[i|intersect]"
t_HASH = r"\#"
t_OR = r"[\|]"
t_AND = r"[&]"
t_DISOR = r"\+"
t_XOR = r"\^"
t_NOT = r"\~"
t_MOD = r"[\%]"
t_DIV = r"[\/]"
t_MULT = r"[\*]"
t_ADD = r"[\+]"
t_SUB = r"[-]"
t_CLPAREN = r"\{"
t_CRPAREN = r"\}"
# These are the things that should be ignored.
t_ignore = " \t\n"
# Track line numbers.
[docs] def t_newline(self, t) -> None:
r"\n+"
t.lineno += len(t.value)
[docs] def t_NAME(self, t):
r"[a-zA-Z_][a-zA-Z_0-9]*"
return self.temporal_symbol(t)
# Parse symbols
[docs] def temporal_symbol(self, t):
# Check for reserved words
if t.value in TemporalOperatorLexer.relations.keys():
t.type = TemporalOperatorLexer.relations.get(t.value)
elif t.value in {"l", "left"}:
t.value = "l"
t.type = "LEFTREF"
elif t.value in {"r", "right"}:
t.value = "r"
t.type = "RIGHTREF"
elif t.value in {"u", "union"}:
t.value = "u"
t.type = "UNION"
elif t.value in {"d", "disjoint"}:
t.value = "d"
t.type = "DISJOINT"
elif t.value in {"i", "intersect"}:
t.value = "i"
t.type = "INTERSECT"
else:
self.t_error(t)
return t
# Handle errors.
[docs] def t_error(self, t):
raise SyntaxError(
"syntax error on line %d position %i near '%s'"
% (t.lineno, t.lexpos, t.value)
)
# Build the lexer
[docs] def build(self, **kwargs) -> None:
self.lexer = lex.lex(
module=self, optimize=False, nowarn=True, debug=0, **kwargs
)
# Just for testing
[docs] def test(self, data) -> None:
self.name_list = {}
print(data)
self.lexer.input(data)
while True:
tok = self.lexer.token()
if not tok:
break
print(tok)
###############################################################################
[docs]class TemporalOperatorParser:
"""The temporal operator class"""
def __init__(self) -> None:
self.lexer = TemporalOperatorLexer()
self.lexer.build()
self.parser = yacc.yacc(module=self, debug=0)
self.relations = None # Temporal relations (equals, contain, during, ...)
self.temporal = None # Temporal operation (intersect, left, right, ...)
self.function = None # Actual operation (+, -, /, *, ... )
self.aggregate = None # Aggregation function (|, &)
self.optype_list = [
"select",
"boolean",
"raster",
"hash",
"relation",
"overlay",
]
[docs] def parse(self, expression, optype="relation"):
"""Parse the expression and fill the object variables
:param expression:
:param optype: The parameter optype can be of type:
- select { :, during, r}
- boolean {&&, contains, |}
- raster { *, equal, |}
- overlay { |, starts, &}
- hash { #, during, l}
- relation {during}
:return:
"""
self.optype = optype
if optype not in self.optype_list:
raise SyntaxError(
"Unknown optype %s, must be one of %s"
% (self.optype, str(self.optype_list))
)
self.expression = expression
self.parser.parse(expression)
# Error rule for syntax errors.
[docs] def p_error(self, t):
raise SyntaxError(
"Unexpected syntax error in expression"
' "%s" at position %i near %s' % (self.expression, t.lexpos, t.value)
)
# Get the tokens from the lexer class
tokens = TemporalOperatorLexer.tokens
[docs] def p_relation_operator(self, t):
# {during}
# {during | equal | starts}
"""
operator : CLPAREN relation CRPAREN
| CLPAREN relationlist CRPAREN
"""
# Check for correct type.
if not self.optype == "relation":
raise SyntaxError('Wrong optype "%s" must be "relation"' % self.optype)
# Set three operator components.
if isinstance(t[2], list):
self.relations = t[2]
else:
self.relations = [t[2]]
self.temporal = None
self.function = None
t[0] = t[2]
[docs] def p_relation_bool_operator(self, t):
# {||, during}
# {&&, during | equal | starts}
"""
operator : CLPAREN OR OR COMMA relation CRPAREN
| CLPAREN AND AND COMMA relation CRPAREN
| CLPAREN OR OR COMMA relationlist CRPAREN
| CLPAREN AND AND COMMA relationlist CRPAREN
"""
if not self.optype == "boolean":
raise SyntaxError('Wrong optype "%s" must be "boolean"' % self.optype)
# Set three operator components.
if isinstance(t[5], list):
self.relations = t[5]
else:
self.relations = [t[5]]
self.temporal = "l"
self.function = t[2] + t[3]
self.aggregate = t[2]
t[0] = t[2]
[docs] def p_relation_bool_combi_operator(self, t):
# {||, during, &}
# {&&, during | equal | starts, |}
"""
operator : CLPAREN OR OR COMMA relation COMMA OR CRPAREN
| CLPAREN OR OR COMMA relation COMMA AND CRPAREN
| CLPAREN AND AND COMMA relation COMMA OR CRPAREN
| CLPAREN AND AND COMMA relation COMMA AND CRPAREN
| CLPAREN OR OR COMMA relationlist COMMA OR CRPAREN
| CLPAREN OR OR COMMA relationlist COMMA AND CRPAREN
| CLPAREN AND AND COMMA relationlist COMMA OR CRPAREN
| CLPAREN AND AND COMMA relationlist COMMA AND CRPAREN
"""
if not self.optype == "boolean":
raise SyntaxError('Wrong optype "%s" must be "boolean"' % self.optype)
# Set three operator components.
if isinstance(t[5], list):
self.relations = t[5]
else:
self.relations = [t[5]]
self.temporal = "l"
self.function = t[2] + t[3]
self.aggregate = t[7]
t[0] = t[2]
[docs] def p_relation_bool_combi_operator2(self, t):
# {||, during, left}
# {&&, during | equal | starts, union}
"""
operator : CLPAREN OR OR COMMA relation COMMA temporal CRPAREN
| CLPAREN AND AND COMMA relation COMMA temporal CRPAREN
| CLPAREN OR OR COMMA relationlist COMMA temporal CRPAREN
| CLPAREN AND AND COMMA relationlist COMMA temporal CRPAREN
"""
if not self.optype == "boolean":
raise SyntaxError('Wrong optype "%s" must be "boolean"' % self.optype)
# Set three operator components.
if isinstance(t[5], list):
self.relations = t[5]
else:
self.relations = [t[5]]
self.temporal = t[7]
self.function = t[2] + t[3]
self.aggregate = t[2]
t[0] = t[2]
[docs] def p_relation_bool_combi_operator3(self, t):
# {||, during, |, left}
# {&&, during | equal | starts, &, union}
"""
operator : CLPAREN OR OR COMMA relation COMMA OR COMMA temporal CRPAREN
| CLPAREN OR OR COMMA relation COMMA AND COMMA temporal CRPAREN
| CLPAREN AND AND COMMA relation COMMA OR COMMA temporal CRPAREN
| CLPAREN AND AND COMMA relation COMMA AND COMMA temporal CRPAREN
| CLPAREN OR OR COMMA relationlist COMMA OR COMMA temporal CRPAREN
| CLPAREN OR OR COMMA relationlist COMMA AND COMMA temporal CRPAREN
| CLPAREN AND AND COMMA relationlist COMMA OR COMMA temporal CRPAREN
| CLPAREN AND AND COMMA relationlist COMMA AND COMMA temporal CRPAREN
"""
if not self.optype == "boolean":
raise SyntaxError('Wrong optype "%s" must be "relation"' % self.optype)
# Set three operator components.
if isinstance(t[5], list):
self.relations = t[5]
else:
self.relations = [t[5]]
self.temporal = t[9]
self.function = t[2] + t[3]
self.aggregate = t[7]
t[0] = t[2]
[docs] def p_select_relation_operator(self, t):
# {!:}
# { :, during}
# {!:, during | equal | starts}
# { :, during | equal | starts, l}
"""
operator : CLPAREN select CRPAREN
| CLPAREN select COMMA relation CRPAREN
| CLPAREN select COMMA relationlist CRPAREN
| CLPAREN select COMMA relation COMMA temporal CRPAREN
| CLPAREN select COMMA relationlist COMMA temporal CRPAREN
"""
if not self.optype == "select":
raise SyntaxError('Wrong optype "%s" must be "select"' % self.optype)
if len(t) == 4:
# Set three operator components.
self.relations = ["equal", "equivalent"]
self.temporal = "l"
self.function = t[2]
elif len(t) == 6:
if isinstance(t[4], list):
self.relations = t[4]
else:
self.relations = [t[4]]
self.temporal = "l"
self.function = t[2]
elif len(t) == 8:
if isinstance(t[4], list):
self.relations = t[4]
else:
self.relations = [t[4]]
self.temporal = t[6]
self.function = t[2]
t[0] = t[2]
[docs] def p_hash_relation_operator(self, t):
# {#}
# {#, during}
# {#, during | equal | starts}
# {#, during | equal | starts, l}
"""
operator : CLPAREN HASH CRPAREN
| CLPAREN HASH COMMA relation CRPAREN
| CLPAREN HASH COMMA relationlist CRPAREN
| CLPAREN HASH COMMA relation COMMA temporal CRPAREN
| CLPAREN HASH COMMA relationlist COMMA temporal CRPAREN
"""
if not self.optype == "hash":
raise SyntaxError('Wrong optype "%s" must be "hash"' % self.optype)
if len(t) == 4:
# Set three operator components.
self.relations = ["equal"]
self.temporal = "l"
self.function = t[2]
elif len(t) == 6:
if isinstance(t[4], list):
self.relations = t[4]
else:
self.relations = [t[4]]
self.temporal = "l"
self.function = t[2]
elif len(t) == 8:
if isinstance(t[4], list):
self.relations = t[4]
else:
self.relations = [t[4]]
self.temporal = t[6]
self.function = t[2]
t[0] = t[2]
[docs] def p_raster_relation_operator(self, t):
# {+}
# {-, during}
# {*, during | equal | starts}
# {/, during | equal | starts, l}
"""
operator : CLPAREN arithmetic CRPAREN
| CLPAREN arithmetic COMMA relation CRPAREN
| CLPAREN arithmetic COMMA relationlist CRPAREN
| CLPAREN arithmetic COMMA relation COMMA temporal CRPAREN
| CLPAREN arithmetic COMMA relationlist COMMA temporal CRPAREN
"""
if not self.optype == "raster":
raise SyntaxError('Wrong optype "%s" must be "raster"' % self.optype)
if len(t) == 4:
# Set three operator components.
self.relations = ["equal"]
self.temporal = "l"
self.function = t[2]
elif len(t) == 6:
if isinstance(t[4], list):
self.relations = t[4]
else:
self.relations = [t[4]]
self.temporal = "l"
self.function = t[2]
elif len(t) == 8:
if isinstance(t[4], list):
self.relations = t[4]
else:
self.relations = [t[4]]
self.temporal = t[6]
self.function = t[2]
t[0] = t[2]
[docs] def p_overlay_relation_operator(self, t):
# {+}
# {-, during}
# {~, during | equal | starts}
# {^, during | equal | starts, l}
"""
operator : CLPAREN overlay CRPAREN
| CLPAREN overlay COMMA relation CRPAREN
| CLPAREN overlay COMMA relationlist CRPAREN
| CLPAREN overlay COMMA relation COMMA temporal CRPAREN
| CLPAREN overlay COMMA relationlist COMMA temporal CRPAREN
"""
if not self.optype == "overlay":
raise SyntaxError('Wrong optype "%s" must be "overlay"' % self.optype)
if len(t) == 4:
# Set three operator components.
self.relations = ["equal"]
self.temporal = "l"
self.function = t[2]
elif len(t) == 6:
if isinstance(t[4], list):
self.relations = t[4]
else:
self.relations = [t[4]]
self.temporal = "l"
self.function = t[2]
elif len(t) == 8:
if isinstance(t[4], list):
self.relations = t[4]
else:
self.relations = [t[4]]
self.temporal = t[6]
self.function = t[2]
t[0] = t[2]
[docs] def p_relation(self, t) -> None:
# The list of relations. Temporal and spatial relations are supported
"""
relation : EQUAL
| FOLLOWS
| PRECEDES
| OVERLAPS
| OVERLAPPED
| DURING
| STARTS
| FINISHES
| CONTAINS
| STARTED
| FINISHED
| EQUIVALENT
| COVER
| OVERLAP
| IN
| CONTAIN
| MEET
"""
t[0] = t[1]
[docs] def p_over(self, t) -> None:
# The the over keyword
"""
relation : OVER
"""
over_list = ["overlaps", "overlapped"]
t[0] = over_list
[docs] def p_relationlist(self, t) -> None:
# The list of relations.
"""
relationlist : relation OR relation
| relation OR relationlist
"""
rel_list = []
rel_list.append(t[1])
if isinstance(t[3], list):
rel_list += t[3]
else:
rel_list.append(t[3])
t[0] = rel_list
[docs] def p_temporal_operator(self, t) -> None:
# The list of relations.
"""
temporal : LEFTREF
| RIGHTREF
| UNION
| DISJOINT
| INTERSECT
"""
t[0] = t[1]
[docs] def p_select_operator(self, t) -> None:
# The list of relations.
"""
select : T_SELECT
| T_NOT_SELECT
"""
t[0] = t[1]
[docs] def p_arithmetic_operator(self, t) -> None:
# The list of relations.
"""
arithmetic : MOD
| DIV
| MULT
| ADD
| SUB
"""
t[0] = t[1]
[docs] def p_overlay_operator(self, t) -> None:
# The list of relations.
"""
overlay : AND
| OR
| XOR
| DISOR
| NOT
"""
t[0] = t[1]
###############################################################################
if __name__ == "__main__":
import doctest
doctest.testmod()