Skip to content

Instantly share code, notes, and snippets.

@chapmanjacobd
Last active November 21, 2022 19:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chapmanjacobd/abc1839bf69299c1a3b00090be60f238 to your computer and use it in GitHub Desktop.
Save chapmanjacobd/abc1839bf69299c1a3b00090be60f238 to your computer and use it in GitHub Desktop.
Dimensionally Extended 9-Intersections Matrix utilities
# Author: Sean Gillies
# License: BSD
# https://pypi.org/project/de9im/
"""
>>> from de9im import pattern
>>> side_hug = pattern('FF*F0****')
>>> im = p.relate(q)
>>> print im
FF2F01212
>>> side_hug.matches(im)
True
One may also use familiarly named patterns::
>>> from de9im.patterns import touches
>>> repr(touches)
"DE-9IM or-pattern: 'FT*******||F**T*****||F***T****'"
>>> touches.matches(im)
True
"""
DIMS = {
'F': frozenset('F'),
'T': frozenset('012'),
'*': frozenset('F012'),
'0': frozenset('0'),
'1': frozenset('1'),
'2': frozenset('2'),
}
def pattern(pattern_string):
return Pattern(pattern_string)
class Pattern(object):
def __init__(self, pattern_string):
self.pattern = tuple(pattern_string.upper())
def __str__(self):
return ''.join(self.pattern)
def __repr__(self):
return "DE-9IM pattern: '%s'" % str(self)
def matches(self, matrix_string):
matrix = tuple(matrix_string.upper())
def onematch(p, m):
return m in DIMS[p]
return bool(
reduce(lambda x, y: x * onematch(*y), zip(self.pattern, matrix), 1)
)
class AntiPattern(object):
def __init__(self, anti_pattern_string):
self.anti_pattern = tuple(anti_pattern_string.upper())
def __str__(self):
return '!' + ''.join(self.anti_pattern)
def __repr__(self):
return "DE-9IM anti-pattern: '%s'" % str(self)
def matches(self, matrix_string):
matrix = tuple(matrix_string.upper())
def onematch(p, m):
return m in DIMS[p]
return not (
reduce(lambda x, y: x * onematch(*y),
zip(self.anti_pattern, matrix),
1)
)
class NOrPattern(object):
def __init__(self, pattern_strings):
self.patterns = [tuple(s.upper()) for s in pattern_strings]
def __str__(self):
return '||'.join([''.join(list(s)) for s in self.patterns])
def __repr__(self):
return "DE-9IM or-pattern: '%s'" % str(self)
def matches(self, matrix_string):
matrix = tuple(matrix_string.upper())
def onematch(p, m):
return m in DIMS[p]
for pattern in self.patterns:
val = bool(
reduce(lambda x, y: x * onematch(*y), zip(pattern, matrix), 1))
if val is True:
break
return val
# Familiar names for patterns or patterns grouped in logical expression
# ---------------------------------------------------------------------
contains = Pattern('T*****FF*')
# cross_lines is only valid for pairs of lines and/or multi-lines
crosses_lines = Pattern('0********')
# following are valid for mixed types
crosses_point_line = crosses_point_region = crosses_line_region = Pattern(
'T*T******')
disjoint = Pattern('FF*FF****')
equal = Pattern('T*F**FFF*')
intersects = AntiPattern('FF*FF****')
# points and regions share an overlap pattern
overlaps_points = overlaps_regions = Pattern('T*T***T**')
# following is valid for lines only
overlaps_lines = Pattern('1*T***T**')
touches = NOrPattern(['FT*******', 'F**T*****', 'F***T****'])
within = Pattern('T*F**F***')
@chapmanjacobd
Copy link
Author

tests

class TestPatternRepr:
    def test(self):
        pat = pattern('T*F**F***')
        assert str(pat) == 'T*F**F***'
        assert repr(pat) == "DE-9IM pattern: 'T*F**F***'"

class TestPatternMatch:
    def test(self):
        pat = pattern('T*F**F***')
        assert pat.matches('10F00F000') == True
        assert pat.matches('F0F00F000') == False

class TestContainsMatch:
    def test(self):
        assert contains.matches('10F00FFF0') == True
        assert contains.matches('F0F00F000') == False

class TestLineCrossMatch:
    def test(self):
        assert crosses_lines.matches('00F00FFF0') == True
        assert crosses_lines.matches('10F00F000') == False

class TestOtherCrossMatch:
    def test(self):
        assert crosses_point_line.matches('10100FFF0') == True
        assert crosses_lines.matches('10F00F000') == False

class TestDisjointMatch:
    def test(self):
        assert disjoint.matches('FF0FF0000') == True
        assert disjoint.matches('0F0FF0000') == False

class TestEqualMatch:
    def test(self):
        assert equal.matches('10F00FFF0') == True
        assert equal.matches('10100FFF0') == False

class TestIntersectsMatch:
    def test(self):
        assert repr(intersects) == "DE-9IM anti-pattern: '!FF*FF****'"
        assert intersects.matches('FF0FF0000') == False
        assert intersects.matches('0F0FF0000') == True

class TestOverlapPointsMatch:
    def test(self):
        assert overlaps_points.matches('101000100') == True
        assert overlaps_points.matches('F01000100') == False

class TestOverlapLinesMatch:
    def test(self):
        assert overlaps_lines.matches('101000100') == True
        assert overlaps_lines.matches('201000100') == False

class TestTouchMatch1(unittest.TestCase):
    def test(self):
        self.failUnlessEqual(repr(touches), "DE-9IM or-pattern: 'FT*******||F**T*****||F***T****'")
        assert touches.matches('FF0100000') == True
        assert touches.matches('101000100') == False

class TestWithinMatch:
    def test(self):
        assert within.matches('10F00F000') == True
        assert within.matches('F0F00F000') == False

@chapmanjacobd
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment