"""@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']
"""
from __future__ import print_function
try:
import ply.lex as lex
import ply.yacc as yacc
except:
pass
[docs]class TemporalOperatorLexer(object):
"""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 = 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):
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 == 'l' or t.value == 'left':
t.value = 'l'
t.type = 'LEFTREF'
elif t.value == 'r' or t.value == 'right':
t.value = 'r'
t.type = 'RIGHTREF'
elif t.value == 'u' or t.value == 'union':
t.value = 'u'
t.type = 'UNION'
elif t.value == 'd' or t.value == 'disjoint':
t.value = 'd'
t.type = 'DISJOINT'
elif t.value == 'i' or t.value == '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):
self.lexer = lex.lex(module=self, optimize=False,
nowarn=True, debug=0, **kwargs)
# Just for testing
[docs] def test(self,data):
self.name_list = {}
print(data)
self.lexer.input(data)
while True:
tok = self.lexer.token()
if not tok: break
print(tok)
###############################################################################
[docs]class TemporalOperatorParser(object):
"""The temporal operator class"""
def __init__(self):
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)
else:
# 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)
else:
# 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)
else:
# 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)
else:
# 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)
else:
# 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)
else:
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)
else:
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)
else:
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)
else:
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):
# 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):
# The the over keyword
"""
relation : OVER
"""
over_list = ["overlaps", "overlapped"]
t[0] = over_list
[docs] def p_relationlist(self, t):
# 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 = rel_list + t[3]
else:
rel_list.append(t[3])
t[0] = rel_list
[docs] def p_temporal_operator(self, t):
# The list of relations.
"""
temporal : LEFTREF
| RIGHTREF
| UNION
| DISJOINT
| INTERSECT
"""
t[0] = t[1]
[docs] def p_select_operator(self, t):
# The list of relations.
"""
select : T_SELECT
| T_NOT_SELECT
"""
t[0] = t[1]
[docs] def p_arithmetic_operator(self, t):
# The list of relations.
"""
arithmetic : MOD
| DIV
| MULT
| ADD
| SUB
"""
t[0] = t[1]
[docs] def p_overlay_operator(self, t):
# The list of relations.
"""
overlay : AND
| OR
| XOR
| DISOR
| NOT
"""
t[0] = t[1]
###############################################################################
if __name__ == "__main__":
import doctest
doctest.testmod()