Module spatial.geometry

Expand source code
from abc import *
from collections import OrderedDict
from enum import Enum
from typing import Set

import matplotlib.patches as mp
import matplotlib.pyplot as plt
import numpy as np
import shapely.affinity as af
import shapely.geometry as sh

_DEBUG = False


class IColor(Enum):
    N = 0
    R = 1
    G = 2
    B = 3


class SpatialInterface(ABC):
    """
    Interface for spatial relation logic. All objects need to provide a quantitative semantic.
    """

    @abstractmethod
    def shapes(self) -> set:
        """
        Returns the shapes stored in the SpatialInterface object
        Returns: The shapes of the SpatialInterface object

        """
        pass

    @abstractmethod
    def distance(self, other: 'SpatialInterface') -> float:
        """
        Returns the signed distance to another spatial interface object
        Args:
            other: The other spatial interface object

        Returns: Distance (squared) to other object

        """
        pass

    @abstractmethod
    def overlap(self, other: 'SpatialInterface') -> float:
        """
        Computes if this object overlaps with another object
        Args:
            other: The other object

        Returns: >=0 if both objects overlap and <0 otherwise

        """
        pass

    @abstractmethod
    def enclosed_in(self, other: 'SpatialInterface') -> float:
        """
        Computes if this objects is enclosed in another object. If any this object is a collection, every object
        must be enclosed in an object of other
        Args:
            other: The other object

        Returns: >=0 if this object is enclosed in the other object and <0 otherwise

        """
        pass

    @abstractmethod
    def proximity(self, other: 'SpatialInterface', eps: float) -> bool:
        """
        Computes if this objects is in proximity to another object
        Args:
            other: The other object
            eps: Specification of proximity

        Returns: >=0 if objects are in proximity and <0 otherwise

        """
        pass

    @abstractmethod
    def distance_compare(self, other: 'SpatialInterface', eps: float, fun) -> bool:
        """
        Compares the distance between two objects and a target value (e.g., a dist b <= eps)
        Args:
            other: The other object
            eps: The target value
            fun: The function for comparing (<=,>=,==)

        Returns: >=0 if predicate is true and <0 otherwise

        """
        pass

    @abstractmethod
    def touching(self, other: 'SpatialInterface') -> bool:
        """
        Computes if two objects are touching
        Args:
            other: The other object

        Returns:

        """
        pass

    @abstractmethod
    def angle(self, other: 'SpatialInterface') -> bool:
        """
        Computes the angle between to objects
        Args:
            other: The other object

        Returns: NOT YET IMPLEMENTED / USED

        """
        pass

    @abstractmethod
    def above(self, other: 'SpatialInterface') -> bool:
        """
        Computes if this object is above another object
        Args:
            other: The other object

        Returns: >= 0 if this object is above the other object and <0 otherwise

        """
        pass

    @abstractmethod
    def below(self, other: 'SpatialInterface') -> bool:
        """
        Computes if this object is below another object
        Args:
            other: The other object

        Returns: >= 0 if this object is below the other object and <0 otherwise

        """
        pass

    @abstractmethod
    def left_of(self, other: 'SpatialInterface') -> bool:
        """
        Computes if this object is left of another object
        Args:
            other: The other object

        Returns: >= 0 if this object is left of the other object and <0 otherwise

        """
        pass

    @abstractmethod
    def right_of(self, other: 'SpatialInterface') -> bool:
        """
        Computes if this object is right of another object
        Args:
            other: The other object

        Returns: >= 0 if this object is right of the other object and <0 otherwise

        """
        pass

    @abstractmethod
    def close_to(self, other: 'SpatialInterface') -> bool:
        """
        Computes if this object is close to another object
        Args:
            other: The other object

        Returns: >= 0 if this object is close to the other object and <0 otherwise

        """
        pass

    @abstractmethod
    def far_from(self, other: 'SpatialInterface') -> bool:
        """
        Computes if this object is far from another object
        Args:
            other: The other object

        Returns: >= 0 if this object is far from the other object and <0 otherwise

        """
        pass

    @abstractmethod
    def closer_to_than(self, closer: 'SpatialInterface', than: 'SpatialInterface') -> bool:
        """
        Computes if this object is closer to one object than another
        Args:
            closer: The object that should be closer
            than: The object that should be further away

        Returns: >= 0 if this object is closer to one object than another and <0 otherwise

        """
        pass

    @abstractmethod
    def enlarge(self, radius: float) -> 'SpatialInterface':
        """
        Enlarges an object with a given radius
        Args:
            radius: The radius for enlarging the object

        Returns: The enlarged object

        """
        pass

    @abstractmethod
    def __or__(self, other: 'SpatialInterface'):
        pass

    @abstractmethod
    def __sub__(self, other: 'SpatialInterface'):
        pass


class ObjectInTime(ABC):
    """
    Interface for an object changing with time.
    """

    @abstractmethod
    def getObject(self, time) -> 'SpatialInterface':
        """
        Returns the object at the given time point
        """
        pass

    @abstractmethod
    def getObjectByIndex(self, idx: int) -> 'SpatialInterface':
        """
        Returns the object at the given time point (given as index)
        Args:
            idx: The index of the time step

        Returns:

        """
        pass


class DynamicObject(ObjectInTime):

    def __init__(self):
        self._shapes = OrderedDict()  # compatible with all Python versions, preserves insertion order
        self._latest_time = None

    def addObject(self, object: SpatialInterface, time: int):
        if self._latest_time is None:
            self._latest_time = time - 1
        assert time not in self._shapes, '<DynamicObject/add>: time step already added! t={}'.format(time)
        assert time == self._latest_time + 1, '<DynamicObject/add>: time step missing! t = {}'.format(time)
        self._shapes[time] = object
        self._latest_time = time

    def getObject(self, time) -> 'SpatialInterface':
        assert time in self._shapes, '<DynamicObject/add>: time step not yet added! t={}'.format(time)
        return self._shapes[time]

    def getObjectByIndex(self, idx: int) -> 'SpatialInterface':
        assert idx < len(self._shapes) if idx >= 0 else abs(idx) <= len(self._shapes)
        return list(self._shapes.values())[idx]

    def __or__(self, other):
        if isinstance(other, (StaticObject, DynamicObject)):
            return ObjectCollection(self, other)
        elif isinstance(other, ObjectCollection):
            return other + self
        else:
            raise Exception('<DynamicObject/add>: Provided object not supported! other = {}'.format(other))


class StaticObject(ObjectInTime):
    """
    An SpatialInterface object static in time. The simplest implementation of ObjectInTime
    """

    def __init__(self, spatial_object: SpatialInterface):
        super().__init__()
        self._spatial_obj = spatial_object

    def getObject(self, time) -> 'SpatialInterface':
        return self._spatial_obj

    def getObjectByIndex(self, idx: int) -> 'SpatialInterface':
        return self._spatial_obj

    def __or__(self, other):
        if isinstance(other, (StaticObject, DynamicObject)):
            return ObjectCollection(self, other)
        elif isinstance(other, ObjectCollection):
            return other + self
        else:
            raise Exception('<DynamicObject/add>: Provided object not supported! other = {}'.format(other))


class ObjectCollection(ObjectInTime):

    def __init__(self, *args):
        self._object_set = set(args)

    def getObject(self, time) -> 'SpatialInterface':
        objs = [o.getObject(time) for o in self._object_set]
        shapes = [o.shapes() for o in objs]
        shapes = shapes[0].union(*shapes[1:])
        assert len(objs) > 0
        return type(objs[0])(shapes)

    def getObjectByIndex(self, idx: int) -> 'SpatialInterface':
        objs = [o.getObjectByIndex(idx) for o in self._object_set]
        shapes = [o.shapes for o in objs]
        shapes = shapes[0].union(*shapes[1:])
        assert len(objs) > 0
        return type(objs[0])(shapes)

    def __len__(self):
        return len(self._object_set)

    @property
    def objects(self):
        return self._object_set

    def __or__(self, other):
        collection = ObjectCollection()
        if isinstance(other, ObjectCollection):
            collection._object_set = self._object_set | other._object_set
        elif isinstance(other, (StaticObject, DynamicObject)):
            collection._object_set = self._object_set | {other}
        else:
            raise Exception('<ObjectCollection/add>: Provided object not supported! other = {}'.format(other))
        return collection

    def __sub__(self, other):
        collection = ObjectCollection()
        if isinstance(other, ObjectCollection):
            collection._object_set = self._object_set - other._object_set
        elif isinstance(other, (StaticObject, DynamicObject)):
            collection._object_set = self._object_set - {other}
        else:
            raise Exception('<ObjectCollection/add>: Provided object not supported! other = {}'.format(other))
        return collection

    def __and__(self, other):
        collection = ObjectCollection()
        if isinstance(other, ObjectCollection):
            collection._object_set = self._object_set & other._object_set
        elif isinstance(other, (StaticObject, DynamicObject)):
            collection._object_set = self._object_set & {other}
        else:
            raise Exception('<ObjectCollection/add>: Provided object not supported! other = {}'.format(other))
        return collection


class Polygon(object):
    """
    Class representing a polygon
    """
    _id = 0
    _ORIGIN = sh.Point([0, 0])  # origin for penetration depth computation
    # _MinkowskiDiff = lambda a, b: sh.Polygon(np.vstack(np.repeat([a],len(b),axis=0)-b)).convex_hull
    _MinkowskiDiff = lambda a, b: sh.Polygon(np.vstack([a - v for v in b])).convex_hull

    @classmethod
    def _get_id(cls):
        cls._id += 1
        return cls._id

    def __init__(self, vertices: np.ndarray, color: IColor = IColor.N, convex_hull: bool = True):
        """
        Initializes a polygon object
        Args:
            vertices: The vertices of the polygon
            color: The color of the polygon. Default = IColor.N
            convex_hull: bool to set if convex hull should be computed. Default = True
        """

        assert isinstance(vertices, np.ndarray), '<Polygon/init>: vertices must be of type np.ndarray!'

        if convex_hull:
            self.shape = sh.Polygon(vertices).convex_hull
        else:
            self.shape = sh.Polygon(vertices)
        self.color = color
        self.id = self._get_id()

    @property
    def shape(self) -> sh.Polygon:
        """
        Returns the shapely polygon object of this polygon
        Returns: Shapely polygon

        """
        return self._shape

    @shape.setter
    def shape(self, shape: sh.Polygon):
        """
        Sets the shapely polygon of this polygon
        Args:
            shape: The new shapely polygon

        """
        assert isinstance(shape, sh.Polygon), '<Polygon/shape>: Only shapely polygons are supported'
        self._shape = shape

    @property
    def vertices(self) -> np.ndarray:
        """
        Returns the vertices of the polygon
        Returns: The vertices of the polygon as a numpy array

        """
        return np.array(self.shape.exterior.coords)

    @property
    def center(self) -> np.ndarray:
        """
        Returns the geometric center of the polygon
        Returns: Geometric center of the polygon as a numpy array

        """
        return np.array(self.shape.centroid)

    def enlarge(self, radius: float) -> 'Polygon':
        enlarged = self.shape.buffer(radius)
        return Polygon(np.array(enlarged.exterior.coords))

    def translate(self, t: np.ndarray):
        """
        Translates the polygon by the given translation vector
        Args:
            t: Translation vector as numpy array with shape (2x1)

        Returns: Translated version of this polygon (no copy)

        """

        assert len(t) == 2
        self.shape = af.translate(self.shape, t[0], t[1])
        return self

    def rotate(self, theta: float, from_origin: bool = True, use_radians=False):
        """
        Rotates the polygon around its center (of its bounding box)
        Args:
            theta: The angle of the rotation
            from_origin: currently not used
            use_radians: True if angle is given in radian

        Returns: Rotated version of this polygon (no copy)

        """

        self.shape = af.rotate(self.shape, theta, origin='center', use_radians=use_radians)
        return self

    def distance(self, other: 'Polygon'):
        """
        Computes the distance to another polygon object
        Args:
            other: The other polygon object

        Returns: The distance (>=0) between this and the other object

        """
        return self.shape.distance(other.shape)

    def penetration_depth(self, other: 'Polygon'):
        """
        Computes the penetration depth with another polygon object
        Args:
            other: The other polygon object

        Returns: The penetration depth (>=0) between this and the other object.
        Zero if no intersection between the objects.

        """
        # return Polygon._MinkowskiDiff(np.asarray(self.shape.exterior.coords),
        #                              np.asarray(other.shape.exterior.coords)).exterior.distance(self._ORIGIN)
        return self._penetration_depth(np.asarray(self.shape.exterior.coords), np.asarray(other.shape.exterior.coords))

    def _penetration_depth(self, vert1: np.ndarray, vert2: np.ndarray):
        return Polygon._MinkowskiDiff(vert1, vert2).exterior.distance(self._ORIGIN)

    def signed_distance(self, other: 'Polygon'):
        """
        Computes the signed distance of this polygon to another one
        Args:
            other: The other polygon

        Returns: The signed distance between the two polygons (<= 0 if touching/intersection, >0 if no penetration)

        """
        gjk = self.distance(other)
        return gjk - self.penetration_depth(other) if gjk <= 0.0000001 else gjk

    def enclosedIn(self, other: 'Polygon'):
        """
        Computes if this polygon is enclosed in another polygon (i.e., all vertices are have negative signed distance)
        Args:
            other: The superset polygon

        Returns: >=0 if this polygon is enclosed in the other polygon, <0 otherwise

        """
        sd = -np.inf
        o = np.array(other.shape.exterior.coords)
        for v in self.vertices:
            gjk = other.shape.distance(sh.Point(v))
            sd_c = gjk - self._penetration_depth(v, o) if gjk < 0.0000001 else gjk
            if sd_c > sd:
                sd = sd_c
        return -sd if not np.isclose(sd, 0) else sd

    def contains_point(self, point: np.ndarray):
        """
        Checks whether a given point is enclosed in the polygon
        Args:
            point: The point to check

        Returns: True if the point is enclosed in the polygon and False otherwise

        """
        return self.shape.contains(sh.Point(point))

    @property
    def color(self) -> IColor:
        """
        Color of polygon
        Returns: Color of polygon

        """
        return self._color

    @color.setter
    def color(self, color: IColor):
        """
        Color of polygon
        Args:
            color: New color of circle

        """
        self._color = color

    def plot(self, ax=None, alpha=1.0, label: bool = True, color='k'):
        """
        Plots the polygon
        Args:
            ax: The axis object to plot to (if provided)
            alpha: The alpha value of the circle
            label: bool to indicate whether to plot label

        """

        if ax is None:
            ax = plt.gca()

        ax.add_patch(
            mp.Polygon(self.vertices, color=color, alpha=alpha))
        if label:
            plt.text(self.center[0], self.center[1], s=str(self.id), c='white', bbox=dict(facecolor='white', alpha=0.5))

    def minkowski_sum(self, other: 'Polygon', sub: bool = False) -> 'Polygon':
        new_vertices = list()
        for v in other.vertices:
            if not sub:
                new_vertices.append(self.vertices + (v - other.center))
            else:
                new_vertices.append(self.vertices - (v - other.center))
        return Polygon(np.vstack(new_vertices))

    def __add__(self, other):
        return self.minkowski_sum(other)

    def __sub__(self, other):
        return self.minkowski_sum(other, sub=True)

    def __hash__(self):
        return self.id


class Circle(Polygon):

    def __init__(self, center: np.ndarray, r: float):
        # approximate circle
        vertices = np.array(sh.Point(center).buffer(r).exterior)
        super().__init__(vertices, convex_hull=False)


class PolygonCollection(SpatialInterface):
    """
        Implements spatial interface for objects of type polytope. Represents set of polytopes
        """

    def __init__(self, polygons: Set[Polygon]):
        """
        Initializes a circle collection with a set of circles
        Args:
            circles: Set of circles
        """
        self.polygons = polygons if isinstance(polygons, set) else set(polygons)

    @property
    def polygons(self) -> Set[Polygon]:
        """
        Set of polygons
        Returns: set of polytopes

        """
        return self._polygons

    @polygons.setter
    def polygons(self, polygons: Set[Polygon]):
        """
        Set of polygons
        Args:
            polygons: new set of polytopes

        Returns:

        """
        self._polygons = polygons

    def add(self, p: Polygon):
        """
        Adds a polygons object to this collection
        Args:
            p: The polygons to add


        """
        self.polygons.add(p)

    def remove(self, p: Polygon):
        """
        Removes a polygons from this collection
        Args:
            p: The polygons to remove


        """
        self.polygons.discard(p)

    def shapes(self) -> set:
        return self.polygons

    def of_color(self, color: IColor) -> 'PolygoneCollection':
        """
        Returns a polygons collection containing polytopes of the specified color
        Args:
            color: The specified color

        Returns: polygons collection containing polytopes of specific color

        """
        return PolygonCollection(set([p for p in self.polygons if p.color == color]))

    def plot(self, ax=None, color='k', label=True):
        """
        Draws all polygons in this collection
        Args:
            ax: The axis object to plot to
            label: bool to indicate whether to plot labels

        Returns:

        """
        if ax is None:
            ax = plt.gca()
        for p in self.polygons:
            p.plot(ax=ax, label=label, color=color)
        plt.autoscale()
        plt.axis('equal')

    def distance(self, other: 'SpatialInterface') -> float:
        assert isinstance(other, PolygonCollection), \
            '<Polygon/distance>: Other object must be of type polygon, got {}'.format(other)

        # compute distances

        result = list()
        for p in self.polygons:
            result.append([p.signed_distance(o) for o in other.polygons])

        return result

    def overlap(self, other: 'SpatialInterface') -> bool:
        # intersection polygons
        inter = list()
        for p in self.polygons:
            inter.append([-p.signed_distance(o) for o in other.polygons])
        inter = np.array(inter)

        return np.max(inter)

    def enclosed_in(self, other: 'SpatialInterface') -> float:
        enclosed = list()
        for p in self.polygons:
            enclosed.append(np.array([p.enclosedIn(o) for o in other.polygons]).max())
        return np.array(enclosed).min()

    def proximity(self, other: 'SpatialInterface', eps: float) -> bool:
        return self.distance_compare(other, eps, np.less_equal)

    def distance_compare(self, other: 'SpatialInterface', eps: float, fun):
        assert np.positive(eps), '<Polygon>: Epsilon must be positive, got {}'.format(eps)

        # compute result
        if fun == np.less_equal:
            return np.max(np.repeat(eps, len(other.polygons)) - self.distance(other))
        if fun == np.greater_equal:
            return np.max(self.distance(other) - np.repeat(eps, len(other.polygons)))
        if fun == np.equal:
            return np.min([np.max(np.repeat(eps, len(other.polygons)) - self.distance(other)),
                           np.max(self.distance(other) - np.repeat(eps, len(other.polygons)))])

    def touching(self, other: 'SpatialInterface', eps: float = 5) -> bool:
        return self.proximity(other, eps=eps)
        return np.min([self.proximity(other, eps=eps), -self.proximity(other, eps=-eps)])

    def _min(self, axis: int) -> float:
        """
        Returns the minimum value of the projection of all polygons to the specified axis
        Args:
            axis: The specified axis

        Returns: The minimum value along the specified axis

        """
        return np.min([c.center[axis] for c in self.polygons])

    def _max(self, axis: int) -> float:
        """
        Returns the maximum value of the projection of all polygons to the specified axis
        Args:
            axis: The specified axis

        Returns: The maximum value along the specified axis

        """
        return np.max([c.center[axis] for c in self.polygons])

    def left_of(self, other: 'SpatialInterface') -> float:
        return other._min(0) - self._max(0)

    def right_of(self, other: 'SpatialInterface') -> float:
        return self._min(0) - other._max(0)

    def above(self, other: 'SpatialInterface') -> float:
        return self._min(1) - other._max(1)

    def below(self, other: 'SpatialInterface') -> float:
        return other._min(1) - self._max(1)

    def close_to(self, other: 'SpatialInterface') -> float:
        return self.proximity(other, 70.)

    def far_from(self, other: 'SpatialInterface') -> float:
        return -self.proximity(other, 150)

    def closer_to_than(self, closer: 'SpatialInterface', than: 'SpatialInterface') -> float:
        return np.min(self.distance(than)) - np.min(self.distance(closer))

    def enlarge(self, radius: float) -> 'SpatialInterface':
        return PolygonCollection(set([p.enlarge(radius) for p in self.polygons]))

    def angle(self, other: 'CircleOLD') -> float:
        pass

    def __or__(self, other: 'PolytopeCollection'):
        return PolygonCollection(self.polygons | other.polygons)

    def __sub__(self, other: 'PolytopeCollection'):
        return PolygonCollection(self.polygons - other.polygons)


if __name__ == '__main__':

    p1 = Polygon(np.array([[0, 0], [3, 3], [6, 0]]))
    p2 = Polygon(np.array([[3, 5], [7, 8], [10, 6]]))
    p2 = p2.rotate(30.45)
    p3 = Polygon(np.array([[3, 5], [7, 8], [10, 6]]) - 4, IColor.B)

    p_sum = p1.minkowski_sum(p2)
    (p1 + p2).plot(color='r')
    (p1 - p2).plot(color='g')
    plt.autoscale()
    plt.show()
    print(p1)

    p1.plot()
    p2.plot()
    p3.plot()
    plt.autoscale()
    plt.show()
    import time

    a = sh.Polygon(p1.vertices)
    b = sh.Polygon(p2.vertices)
    print('Area is {}'.format(a.area))
    print('Distance is {}'.format(a.distance(b)))

    t0 = time.time()
    for i in range(100):
        a.distance(b)
    print(f'Time took {time.time() - t0}')

    pc = PolygonCollection(set([p1, p2]))
    pd = PolygonCollection(set([p3]))

    t0 = time.time()
    print('Distance is {}'.format(pc.distance(pd)))
    print('Distance is {}'.format(pc.distance(pc)))
    print('Intersecting is {}'.format(pc.overlap(pd)))
    print('Intersecting is {}'.format(pc.overlap(pc)))
    print(f'Time took {time.time() - t0}')

    t0 = time.time()
    for i in range(1):
        pc.distance(pc)
        # p1.intersect(p2).volume
    print(f'Time took for 10 {time.time() - t0}')

Classes

class Circle (center: numpy.ndarray, r: float)

Class representing a polygon

Initializes a polygon object

Args

vertices
The vertices of the polygon
color
The color of the polygon. Default = IColor.N
convex_hull
bool to set if convex hull should be computed. Default = True
Expand source code
class Circle(Polygon):

    def __init__(self, center: np.ndarray, r: float):
        # approximate circle
        vertices = np.array(sh.Point(center).buffer(r).exterior)
        super().__init__(vertices, convex_hull=False)

Ancestors

Inherited members

class DynamicObject

Interface for an object changing with time.

Expand source code
class DynamicObject(ObjectInTime):

    def __init__(self):
        self._shapes = OrderedDict()  # compatible with all Python versions, preserves insertion order
        self._latest_time = None

    def addObject(self, object: SpatialInterface, time: int):
        if self._latest_time is None:
            self._latest_time = time - 1
        assert time not in self._shapes, '<DynamicObject/add>: time step already added! t={}'.format(time)
        assert time == self._latest_time + 1, '<DynamicObject/add>: time step missing! t = {}'.format(time)
        self._shapes[time] = object
        self._latest_time = time

    def getObject(self, time) -> 'SpatialInterface':
        assert time in self._shapes, '<DynamicObject/add>: time step not yet added! t={}'.format(time)
        return self._shapes[time]

    def getObjectByIndex(self, idx: int) -> 'SpatialInterface':
        assert idx < len(self._shapes) if idx >= 0 else abs(idx) <= len(self._shapes)
        return list(self._shapes.values())[idx]

    def __or__(self, other):
        if isinstance(other, (StaticObject, DynamicObject)):
            return ObjectCollection(self, other)
        elif isinstance(other, ObjectCollection):
            return other + self
        else:
            raise Exception('<DynamicObject/add>: Provided object not supported! other = {}'.format(other))

Ancestors

Methods

def addObject(self, object: SpatialInterface, time: int)
Expand source code
def addObject(self, object: SpatialInterface, time: int):
    if self._latest_time is None:
        self._latest_time = time - 1
    assert time not in self._shapes, '<DynamicObject/add>: time step already added! t={}'.format(time)
    assert time == self._latest_time + 1, '<DynamicObject/add>: time step missing! t = {}'.format(time)
    self._shapes[time] = object
    self._latest_time = time

Inherited members

class IColor (value, names=None, *, module=None, qualname=None, type=None, start=1)

An enumeration.

Expand source code
class IColor(Enum):
    N = 0
    R = 1
    G = 2
    B = 3

Ancestors

  • enum.Enum

Class variables

var B
var G
var N
var R
class ObjectCollection (*args)

Interface for an object changing with time.

Expand source code
class ObjectCollection(ObjectInTime):

    def __init__(self, *args):
        self._object_set = set(args)

    def getObject(self, time) -> 'SpatialInterface':
        objs = [o.getObject(time) for o in self._object_set]
        shapes = [o.shapes() for o in objs]
        shapes = shapes[0].union(*shapes[1:])
        assert len(objs) > 0
        return type(objs[0])(shapes)

    def getObjectByIndex(self, idx: int) -> 'SpatialInterface':
        objs = [o.getObjectByIndex(idx) for o in self._object_set]
        shapes = [o.shapes for o in objs]
        shapes = shapes[0].union(*shapes[1:])
        assert len(objs) > 0
        return type(objs[0])(shapes)

    def __len__(self):
        return len(self._object_set)

    @property
    def objects(self):
        return self._object_set

    def __or__(self, other):
        collection = ObjectCollection()
        if isinstance(other, ObjectCollection):
            collection._object_set = self._object_set | other._object_set
        elif isinstance(other, (StaticObject, DynamicObject)):
            collection._object_set = self._object_set | {other}
        else:
            raise Exception('<ObjectCollection/add>: Provided object not supported! other = {}'.format(other))
        return collection

    def __sub__(self, other):
        collection = ObjectCollection()
        if isinstance(other, ObjectCollection):
            collection._object_set = self._object_set - other._object_set
        elif isinstance(other, (StaticObject, DynamicObject)):
            collection._object_set = self._object_set - {other}
        else:
            raise Exception('<ObjectCollection/add>: Provided object not supported! other = {}'.format(other))
        return collection

    def __and__(self, other):
        collection = ObjectCollection()
        if isinstance(other, ObjectCollection):
            collection._object_set = self._object_set & other._object_set
        elif isinstance(other, (StaticObject, DynamicObject)):
            collection._object_set = self._object_set & {other}
        else:
            raise Exception('<ObjectCollection/add>: Provided object not supported! other = {}'.format(other))
        return collection

Ancestors

Instance variables

var objects
Expand source code
@property
def objects(self):
    return self._object_set

Inherited members

class ObjectInTime

Interface for an object changing with time.

Expand source code
class ObjectInTime(ABC):
    """
    Interface for an object changing with time.
    """

    @abstractmethod
    def getObject(self, time) -> 'SpatialInterface':
        """
        Returns the object at the given time point
        """
        pass

    @abstractmethod
    def getObjectByIndex(self, idx: int) -> 'SpatialInterface':
        """
        Returns the object at the given time point (given as index)
        Args:
            idx: The index of the time step

        Returns:

        """
        pass

Ancestors

  • abc.ABC

Subclasses

Methods

def getObject(self, time) ‑> SpatialInterface

Returns the object at the given time point

Expand source code
@abstractmethod
def getObject(self, time) -> 'SpatialInterface':
    """
    Returns the object at the given time point
    """
    pass
def getObjectByIndex(self, idx: int) ‑> SpatialInterface

Returns the object at the given time point (given as index)

Args

idx
The index of the time step

Returns:

Expand source code
@abstractmethod
def getObjectByIndex(self, idx: int) -> 'SpatialInterface':
    """
    Returns the object at the given time point (given as index)
    Args:
        idx: The index of the time step

    Returns:

    """
    pass
class Polygon (vertices: numpy.ndarray, color: IColor = IColor.N, convex_hull: bool = True)

Class representing a polygon

Initializes a polygon object

Args

vertices
The vertices of the polygon
color
The color of the polygon. Default = IColor.N
convex_hull
bool to set if convex hull should be computed. Default = True
Expand source code
class Polygon(object):
    """
    Class representing a polygon
    """
    _id = 0
    _ORIGIN = sh.Point([0, 0])  # origin for penetration depth computation
    # _MinkowskiDiff = lambda a, b: sh.Polygon(np.vstack(np.repeat([a],len(b),axis=0)-b)).convex_hull
    _MinkowskiDiff = lambda a, b: sh.Polygon(np.vstack([a - v for v in b])).convex_hull

    @classmethod
    def _get_id(cls):
        cls._id += 1
        return cls._id

    def __init__(self, vertices: np.ndarray, color: IColor = IColor.N, convex_hull: bool = True):
        """
        Initializes a polygon object
        Args:
            vertices: The vertices of the polygon
            color: The color of the polygon. Default = IColor.N
            convex_hull: bool to set if convex hull should be computed. Default = True
        """

        assert isinstance(vertices, np.ndarray), '<Polygon/init>: vertices must be of type np.ndarray!'

        if convex_hull:
            self.shape = sh.Polygon(vertices).convex_hull
        else:
            self.shape = sh.Polygon(vertices)
        self.color = color
        self.id = self._get_id()

    @property
    def shape(self) -> sh.Polygon:
        """
        Returns the shapely polygon object of this polygon
        Returns: Shapely polygon

        """
        return self._shape

    @shape.setter
    def shape(self, shape: sh.Polygon):
        """
        Sets the shapely polygon of this polygon
        Args:
            shape: The new shapely polygon

        """
        assert isinstance(shape, sh.Polygon), '<Polygon/shape>: Only shapely polygons are supported'
        self._shape = shape

    @property
    def vertices(self) -> np.ndarray:
        """
        Returns the vertices of the polygon
        Returns: The vertices of the polygon as a numpy array

        """
        return np.array(self.shape.exterior.coords)

    @property
    def center(self) -> np.ndarray:
        """
        Returns the geometric center of the polygon
        Returns: Geometric center of the polygon as a numpy array

        """
        return np.array(self.shape.centroid)

    def enlarge(self, radius: float) -> 'Polygon':
        enlarged = self.shape.buffer(radius)
        return Polygon(np.array(enlarged.exterior.coords))

    def translate(self, t: np.ndarray):
        """
        Translates the polygon by the given translation vector
        Args:
            t: Translation vector as numpy array with shape (2x1)

        Returns: Translated version of this polygon (no copy)

        """

        assert len(t) == 2
        self.shape = af.translate(self.shape, t[0], t[1])
        return self

    def rotate(self, theta: float, from_origin: bool = True, use_radians=False):
        """
        Rotates the polygon around its center (of its bounding box)
        Args:
            theta: The angle of the rotation
            from_origin: currently not used
            use_radians: True if angle is given in radian

        Returns: Rotated version of this polygon (no copy)

        """

        self.shape = af.rotate(self.shape, theta, origin='center', use_radians=use_radians)
        return self

    def distance(self, other: 'Polygon'):
        """
        Computes the distance to another polygon object
        Args:
            other: The other polygon object

        Returns: The distance (>=0) between this and the other object

        """
        return self.shape.distance(other.shape)

    def penetration_depth(self, other: 'Polygon'):
        """
        Computes the penetration depth with another polygon object
        Args:
            other: The other polygon object

        Returns: The penetration depth (>=0) between this and the other object.
        Zero if no intersection between the objects.

        """
        # return Polygon._MinkowskiDiff(np.asarray(self.shape.exterior.coords),
        #                              np.asarray(other.shape.exterior.coords)).exterior.distance(self._ORIGIN)
        return self._penetration_depth(np.asarray(self.shape.exterior.coords), np.asarray(other.shape.exterior.coords))

    def _penetration_depth(self, vert1: np.ndarray, vert2: np.ndarray):
        return Polygon._MinkowskiDiff(vert1, vert2).exterior.distance(self._ORIGIN)

    def signed_distance(self, other: 'Polygon'):
        """
        Computes the signed distance of this polygon to another one
        Args:
            other: The other polygon

        Returns: The signed distance between the two polygons (<= 0 if touching/intersection, >0 if no penetration)

        """
        gjk = self.distance(other)
        return gjk - self.penetration_depth(other) if gjk <= 0.0000001 else gjk

    def enclosedIn(self, other: 'Polygon'):
        """
        Computes if this polygon is enclosed in another polygon (i.e., all vertices are have negative signed distance)
        Args:
            other: The superset polygon

        Returns: >=0 if this polygon is enclosed in the other polygon, <0 otherwise

        """
        sd = -np.inf
        o = np.array(other.shape.exterior.coords)
        for v in self.vertices:
            gjk = other.shape.distance(sh.Point(v))
            sd_c = gjk - self._penetration_depth(v, o) if gjk < 0.0000001 else gjk
            if sd_c > sd:
                sd = sd_c
        return -sd if not np.isclose(sd, 0) else sd

    def contains_point(self, point: np.ndarray):
        """
        Checks whether a given point is enclosed in the polygon
        Args:
            point: The point to check

        Returns: True if the point is enclosed in the polygon and False otherwise

        """
        return self.shape.contains(sh.Point(point))

    @property
    def color(self) -> IColor:
        """
        Color of polygon
        Returns: Color of polygon

        """
        return self._color

    @color.setter
    def color(self, color: IColor):
        """
        Color of polygon
        Args:
            color: New color of circle

        """
        self._color = color

    def plot(self, ax=None, alpha=1.0, label: bool = True, color='k'):
        """
        Plots the polygon
        Args:
            ax: The axis object to plot to (if provided)
            alpha: The alpha value of the circle
            label: bool to indicate whether to plot label

        """

        if ax is None:
            ax = plt.gca()

        ax.add_patch(
            mp.Polygon(self.vertices, color=color, alpha=alpha))
        if label:
            plt.text(self.center[0], self.center[1], s=str(self.id), c='white', bbox=dict(facecolor='white', alpha=0.5))

    def minkowski_sum(self, other: 'Polygon', sub: bool = False) -> 'Polygon':
        new_vertices = list()
        for v in other.vertices:
            if not sub:
                new_vertices.append(self.vertices + (v - other.center))
            else:
                new_vertices.append(self.vertices - (v - other.center))
        return Polygon(np.vstack(new_vertices))

    def __add__(self, other):
        return self.minkowski_sum(other)

    def __sub__(self, other):
        return self.minkowski_sum(other, sub=True)

    def __hash__(self):
        return self.id

Subclasses

Instance variables

var center : numpy.ndarray

Returns the geometric center of the polygon Returns: Geometric center of the polygon as a numpy array

Expand source code
@property
def center(self) -> np.ndarray:
    """
    Returns the geometric center of the polygon
    Returns: Geometric center of the polygon as a numpy array

    """
    return np.array(self.shape.centroid)
var colorIColor

Color of polygon Returns: Color of polygon

Expand source code
@property
def color(self) -> IColor:
    """
    Color of polygon
    Returns: Color of polygon

    """
    return self._color
var shape : shapely.geometry.polygon.Polygon

Returns the shapely polygon object of this polygon Returns: Shapely polygon

Expand source code
@property
def shape(self) -> sh.Polygon:
    """
    Returns the shapely polygon object of this polygon
    Returns: Shapely polygon

    """
    return self._shape
var vertices : numpy.ndarray

Returns the vertices of the polygon Returns: The vertices of the polygon as a numpy array

Expand source code
@property
def vertices(self) -> np.ndarray:
    """
    Returns the vertices of the polygon
    Returns: The vertices of the polygon as a numpy array

    """
    return np.array(self.shape.exterior.coords)

Methods

def contains_point(self, point: numpy.ndarray)

Checks whether a given point is enclosed in the polygon

Args

point
The point to check

Returns: True if the point is enclosed in the polygon and False otherwise

Expand source code
def contains_point(self, point: np.ndarray):
    """
    Checks whether a given point is enclosed in the polygon
    Args:
        point: The point to check

    Returns: True if the point is enclosed in the polygon and False otherwise

    """
    return self.shape.contains(sh.Point(point))
def distance(self, other: Polygon)

Computes the distance to another polygon object

Args

other
The other polygon object

Returns: The distance (>=0) between this and the other object

Expand source code
def distance(self, other: 'Polygon'):
    """
    Computes the distance to another polygon object
    Args:
        other: The other polygon object

    Returns: The distance (>=0) between this and the other object

    """
    return self.shape.distance(other.shape)
def enclosedIn(self, other: Polygon)

Computes if this polygon is enclosed in another polygon (i.e., all vertices are have negative signed distance)

Args

other
The superset polygon

Returns: >=0 if this polygon is enclosed in the other polygon, <0 otherwise

Expand source code
def enclosedIn(self, other: 'Polygon'):
    """
    Computes if this polygon is enclosed in another polygon (i.e., all vertices are have negative signed distance)
    Args:
        other: The superset polygon

    Returns: >=0 if this polygon is enclosed in the other polygon, <0 otherwise

    """
    sd = -np.inf
    o = np.array(other.shape.exterior.coords)
    for v in self.vertices:
        gjk = other.shape.distance(sh.Point(v))
        sd_c = gjk - self._penetration_depth(v, o) if gjk < 0.0000001 else gjk
        if sd_c > sd:
            sd = sd_c
    return -sd if not np.isclose(sd, 0) else sd
def enlarge(self, radius: float) ‑> Polygon
Expand source code
def enlarge(self, radius: float) -> 'Polygon':
    enlarged = self.shape.buffer(radius)
    return Polygon(np.array(enlarged.exterior.coords))
def minkowski_sum(self, other: Polygon, sub: bool = False) ‑> Polygon
Expand source code
def minkowski_sum(self, other: 'Polygon', sub: bool = False) -> 'Polygon':
    new_vertices = list()
    for v in other.vertices:
        if not sub:
            new_vertices.append(self.vertices + (v - other.center))
        else:
            new_vertices.append(self.vertices - (v - other.center))
    return Polygon(np.vstack(new_vertices))
def penetration_depth(self, other: Polygon)

Computes the penetration depth with another polygon object

Args

other
The other polygon object

Returns: The penetration depth (>=0) between this and the other object. Zero if no intersection between the objects.

Expand source code
def penetration_depth(self, other: 'Polygon'):
    """
    Computes the penetration depth with another polygon object
    Args:
        other: The other polygon object

    Returns: The penetration depth (>=0) between this and the other object.
    Zero if no intersection between the objects.

    """
    # return Polygon._MinkowskiDiff(np.asarray(self.shape.exterior.coords),
    #                              np.asarray(other.shape.exterior.coords)).exterior.distance(self._ORIGIN)
    return self._penetration_depth(np.asarray(self.shape.exterior.coords), np.asarray(other.shape.exterior.coords))
def plot(self, ax=None, alpha=1.0, label: bool = True, color='k')

Plots the polygon

Args

ax
The axis object to plot to (if provided)
alpha
The alpha value of the circle
label
bool to indicate whether to plot label
Expand source code
def plot(self, ax=None, alpha=1.0, label: bool = True, color='k'):
    """
    Plots the polygon
    Args:
        ax: The axis object to plot to (if provided)
        alpha: The alpha value of the circle
        label: bool to indicate whether to plot label

    """

    if ax is None:
        ax = plt.gca()

    ax.add_patch(
        mp.Polygon(self.vertices, color=color, alpha=alpha))
    if label:
        plt.text(self.center[0], self.center[1], s=str(self.id), c='white', bbox=dict(facecolor='white', alpha=0.5))
def rotate(self, theta: float, from_origin: bool = True, use_radians=False)

Rotates the polygon around its center (of its bounding box)

Args

theta
The angle of the rotation
from_origin
currently not used
use_radians
True if angle is given in radian

Returns: Rotated version of this polygon (no copy)

Expand source code
def rotate(self, theta: float, from_origin: bool = True, use_radians=False):
    """
    Rotates the polygon around its center (of its bounding box)
    Args:
        theta: The angle of the rotation
        from_origin: currently not used
        use_radians: True if angle is given in radian

    Returns: Rotated version of this polygon (no copy)

    """

    self.shape = af.rotate(self.shape, theta, origin='center', use_radians=use_radians)
    return self
def signed_distance(self, other: Polygon)

Computes the signed distance of this polygon to another one

Args

other
The other polygon

Returns: The signed distance between the two polygons (<= 0 if touching/intersection, >0 if no penetration)

Expand source code
def signed_distance(self, other: 'Polygon'):
    """
    Computes the signed distance of this polygon to another one
    Args:
        other: The other polygon

    Returns: The signed distance between the two polygons (<= 0 if touching/intersection, >0 if no penetration)

    """
    gjk = self.distance(other)
    return gjk - self.penetration_depth(other) if gjk <= 0.0000001 else gjk
def translate(self, t: numpy.ndarray)

Translates the polygon by the given translation vector

Args

t
Translation vector as numpy array with shape (2x1)

Returns: Translated version of this polygon (no copy)

Expand source code
def translate(self, t: np.ndarray):
    """
    Translates the polygon by the given translation vector
    Args:
        t: Translation vector as numpy array with shape (2x1)

    Returns: Translated version of this polygon (no copy)

    """

    assert len(t) == 2
    self.shape = af.translate(self.shape, t[0], t[1])
    return self
class PolygonCollection (polygons: Set[Polygon])

Implements spatial interface for objects of type polytope. Represents set of polytopes

Initializes a circle collection with a set of circles

Args

circles
Set of circles
Expand source code
class PolygonCollection(SpatialInterface):
    """
        Implements spatial interface for objects of type polytope. Represents set of polytopes
        """

    def __init__(self, polygons: Set[Polygon]):
        """
        Initializes a circle collection with a set of circles
        Args:
            circles: Set of circles
        """
        self.polygons = polygons if isinstance(polygons, set) else set(polygons)

    @property
    def polygons(self) -> Set[Polygon]:
        """
        Set of polygons
        Returns: set of polytopes

        """
        return self._polygons

    @polygons.setter
    def polygons(self, polygons: Set[Polygon]):
        """
        Set of polygons
        Args:
            polygons: new set of polytopes

        Returns:

        """
        self._polygons = polygons

    def add(self, p: Polygon):
        """
        Adds a polygons object to this collection
        Args:
            p: The polygons to add


        """
        self.polygons.add(p)

    def remove(self, p: Polygon):
        """
        Removes a polygons from this collection
        Args:
            p: The polygons to remove


        """
        self.polygons.discard(p)

    def shapes(self) -> set:
        return self.polygons

    def of_color(self, color: IColor) -> 'PolygoneCollection':
        """
        Returns a polygons collection containing polytopes of the specified color
        Args:
            color: The specified color

        Returns: polygons collection containing polytopes of specific color

        """
        return PolygonCollection(set([p for p in self.polygons if p.color == color]))

    def plot(self, ax=None, color='k', label=True):
        """
        Draws all polygons in this collection
        Args:
            ax: The axis object to plot to
            label: bool to indicate whether to plot labels

        Returns:

        """
        if ax is None:
            ax = plt.gca()
        for p in self.polygons:
            p.plot(ax=ax, label=label, color=color)
        plt.autoscale()
        plt.axis('equal')

    def distance(self, other: 'SpatialInterface') -> float:
        assert isinstance(other, PolygonCollection), \
            '<Polygon/distance>: Other object must be of type polygon, got {}'.format(other)

        # compute distances

        result = list()
        for p in self.polygons:
            result.append([p.signed_distance(o) for o in other.polygons])

        return result

    def overlap(self, other: 'SpatialInterface') -> bool:
        # intersection polygons
        inter = list()
        for p in self.polygons:
            inter.append([-p.signed_distance(o) for o in other.polygons])
        inter = np.array(inter)

        return np.max(inter)

    def enclosed_in(self, other: 'SpatialInterface') -> float:
        enclosed = list()
        for p in self.polygons:
            enclosed.append(np.array([p.enclosedIn(o) for o in other.polygons]).max())
        return np.array(enclosed).min()

    def proximity(self, other: 'SpatialInterface', eps: float) -> bool:
        return self.distance_compare(other, eps, np.less_equal)

    def distance_compare(self, other: 'SpatialInterface', eps: float, fun):
        assert np.positive(eps), '<Polygon>: Epsilon must be positive, got {}'.format(eps)

        # compute result
        if fun == np.less_equal:
            return np.max(np.repeat(eps, len(other.polygons)) - self.distance(other))
        if fun == np.greater_equal:
            return np.max(self.distance(other) - np.repeat(eps, len(other.polygons)))
        if fun == np.equal:
            return np.min([np.max(np.repeat(eps, len(other.polygons)) - self.distance(other)),
                           np.max(self.distance(other) - np.repeat(eps, len(other.polygons)))])

    def touching(self, other: 'SpatialInterface', eps: float = 5) -> bool:
        return self.proximity(other, eps=eps)
        return np.min([self.proximity(other, eps=eps), -self.proximity(other, eps=-eps)])

    def _min(self, axis: int) -> float:
        """
        Returns the minimum value of the projection of all polygons to the specified axis
        Args:
            axis: The specified axis

        Returns: The minimum value along the specified axis

        """
        return np.min([c.center[axis] for c in self.polygons])

    def _max(self, axis: int) -> float:
        """
        Returns the maximum value of the projection of all polygons to the specified axis
        Args:
            axis: The specified axis

        Returns: The maximum value along the specified axis

        """
        return np.max([c.center[axis] for c in self.polygons])

    def left_of(self, other: 'SpatialInterface') -> float:
        return other._min(0) - self._max(0)

    def right_of(self, other: 'SpatialInterface') -> float:
        return self._min(0) - other._max(0)

    def above(self, other: 'SpatialInterface') -> float:
        return self._min(1) - other._max(1)

    def below(self, other: 'SpatialInterface') -> float:
        return other._min(1) - self._max(1)

    def close_to(self, other: 'SpatialInterface') -> float:
        return self.proximity(other, 70.)

    def far_from(self, other: 'SpatialInterface') -> float:
        return -self.proximity(other, 150)

    def closer_to_than(self, closer: 'SpatialInterface', than: 'SpatialInterface') -> float:
        return np.min(self.distance(than)) - np.min(self.distance(closer))

    def enlarge(self, radius: float) -> 'SpatialInterface':
        return PolygonCollection(set([p.enlarge(radius) for p in self.polygons]))

    def angle(self, other: 'CircleOLD') -> float:
        pass

    def __or__(self, other: 'PolytopeCollection'):
        return PolygonCollection(self.polygons | other.polygons)

    def __sub__(self, other: 'PolytopeCollection'):
        return PolygonCollection(self.polygons - other.polygons)

Ancestors

Instance variables

var polygons : Set[Polygon]

Set of polygons Returns: set of polytopes

Expand source code
@property
def polygons(self) -> Set[Polygon]:
    """
    Set of polygons
    Returns: set of polytopes

    """
    return self._polygons

Methods

def add(self, p: Polygon)

Adds a polygons object to this collection

Args

p
The polygons to add
Expand source code
def add(self, p: Polygon):
    """
    Adds a polygons object to this collection
    Args:
        p: The polygons to add


    """
    self.polygons.add(p)
def of_color(self, color: IColor) ‑> PolygoneCollection

Returns a polygons collection containing polytopes of the specified color

Args

color
The specified color

Returns: polygons collection containing polytopes of specific color

Expand source code
def of_color(self, color: IColor) -> 'PolygoneCollection':
    """
    Returns a polygons collection containing polytopes of the specified color
    Args:
        color: The specified color

    Returns: polygons collection containing polytopes of specific color

    """
    return PolygonCollection(set([p for p in self.polygons if p.color == color]))
def plot(self, ax=None, color='k', label=True)

Draws all polygons in this collection

Args

ax
The axis object to plot to
label
bool to indicate whether to plot labels

Returns:

Expand source code
def plot(self, ax=None, color='k', label=True):
    """
    Draws all polygons in this collection
    Args:
        ax: The axis object to plot to
        label: bool to indicate whether to plot labels

    Returns:

    """
    if ax is None:
        ax = plt.gca()
    for p in self.polygons:
        p.plot(ax=ax, label=label, color=color)
    plt.autoscale()
    plt.axis('equal')
def remove(self, p: Polygon)

Removes a polygons from this collection

Args

p
The polygons to remove
Expand source code
def remove(self, p: Polygon):
    """
    Removes a polygons from this collection
    Args:
        p: The polygons to remove


    """
    self.polygons.discard(p)

Inherited members

class SpatialInterface

Interface for spatial relation logic. All objects need to provide a quantitative semantic.

Expand source code
class SpatialInterface(ABC):
    """
    Interface for spatial relation logic. All objects need to provide a quantitative semantic.
    """

    @abstractmethod
    def shapes(self) -> set:
        """
        Returns the shapes stored in the SpatialInterface object
        Returns: The shapes of the SpatialInterface object

        """
        pass

    @abstractmethod
    def distance(self, other: 'SpatialInterface') -> float:
        """
        Returns the signed distance to another spatial interface object
        Args:
            other: The other spatial interface object

        Returns: Distance (squared) to other object

        """
        pass

    @abstractmethod
    def overlap(self, other: 'SpatialInterface') -> float:
        """
        Computes if this object overlaps with another object
        Args:
            other: The other object

        Returns: >=0 if both objects overlap and <0 otherwise

        """
        pass

    @abstractmethod
    def enclosed_in(self, other: 'SpatialInterface') -> float:
        """
        Computes if this objects is enclosed in another object. If any this object is a collection, every object
        must be enclosed in an object of other
        Args:
            other: The other object

        Returns: >=0 if this object is enclosed in the other object and <0 otherwise

        """
        pass

    @abstractmethod
    def proximity(self, other: 'SpatialInterface', eps: float) -> bool:
        """
        Computes if this objects is in proximity to another object
        Args:
            other: The other object
            eps: Specification of proximity

        Returns: >=0 if objects are in proximity and <0 otherwise

        """
        pass

    @abstractmethod
    def distance_compare(self, other: 'SpatialInterface', eps: float, fun) -> bool:
        """
        Compares the distance between two objects and a target value (e.g., a dist b <= eps)
        Args:
            other: The other object
            eps: The target value
            fun: The function for comparing (<=,>=,==)

        Returns: >=0 if predicate is true and <0 otherwise

        """
        pass

    @abstractmethod
    def touching(self, other: 'SpatialInterface') -> bool:
        """
        Computes if two objects are touching
        Args:
            other: The other object

        Returns:

        """
        pass

    @abstractmethod
    def angle(self, other: 'SpatialInterface') -> bool:
        """
        Computes the angle between to objects
        Args:
            other: The other object

        Returns: NOT YET IMPLEMENTED / USED

        """
        pass

    @abstractmethod
    def above(self, other: 'SpatialInterface') -> bool:
        """
        Computes if this object is above another object
        Args:
            other: The other object

        Returns: >= 0 if this object is above the other object and <0 otherwise

        """
        pass

    @abstractmethod
    def below(self, other: 'SpatialInterface') -> bool:
        """
        Computes if this object is below another object
        Args:
            other: The other object

        Returns: >= 0 if this object is below the other object and <0 otherwise

        """
        pass

    @abstractmethod
    def left_of(self, other: 'SpatialInterface') -> bool:
        """
        Computes if this object is left of another object
        Args:
            other: The other object

        Returns: >= 0 if this object is left of the other object and <0 otherwise

        """
        pass

    @abstractmethod
    def right_of(self, other: 'SpatialInterface') -> bool:
        """
        Computes if this object is right of another object
        Args:
            other: The other object

        Returns: >= 0 if this object is right of the other object and <0 otherwise

        """
        pass

    @abstractmethod
    def close_to(self, other: 'SpatialInterface') -> bool:
        """
        Computes if this object is close to another object
        Args:
            other: The other object

        Returns: >= 0 if this object is close to the other object and <0 otherwise

        """
        pass

    @abstractmethod
    def far_from(self, other: 'SpatialInterface') -> bool:
        """
        Computes if this object is far from another object
        Args:
            other: The other object

        Returns: >= 0 if this object is far from the other object and <0 otherwise

        """
        pass

    @abstractmethod
    def closer_to_than(self, closer: 'SpatialInterface', than: 'SpatialInterface') -> bool:
        """
        Computes if this object is closer to one object than another
        Args:
            closer: The object that should be closer
            than: The object that should be further away

        Returns: >= 0 if this object is closer to one object than another and <0 otherwise

        """
        pass

    @abstractmethod
    def enlarge(self, radius: float) -> 'SpatialInterface':
        """
        Enlarges an object with a given radius
        Args:
            radius: The radius for enlarging the object

        Returns: The enlarged object

        """
        pass

    @abstractmethod
    def __or__(self, other: 'SpatialInterface'):
        pass

    @abstractmethod
    def __sub__(self, other: 'SpatialInterface'):
        pass

Ancestors

  • abc.ABC

Subclasses

Methods

def above(self, other: SpatialInterface) ‑> bool

Computes if this object is above another object

Args

other
The other object

Returns: >= 0 if this object is above the other object and <0 otherwise

Expand source code
@abstractmethod
def above(self, other: 'SpatialInterface') -> bool:
    """
    Computes if this object is above another object
    Args:
        other: The other object

    Returns: >= 0 if this object is above the other object and <0 otherwise

    """
    pass
def angle(self, other: SpatialInterface) ‑> bool

Computes the angle between to objects

Args

other
The other object

Returns: NOT YET IMPLEMENTED / USED

Expand source code
@abstractmethod
def angle(self, other: 'SpatialInterface') -> bool:
    """
    Computes the angle between to objects
    Args:
        other: The other object

    Returns: NOT YET IMPLEMENTED / USED

    """
    pass
def below(self, other: SpatialInterface) ‑> bool

Computes if this object is below another object

Args

other
The other object

Returns: >= 0 if this object is below the other object and <0 otherwise

Expand source code
@abstractmethod
def below(self, other: 'SpatialInterface') -> bool:
    """
    Computes if this object is below another object
    Args:
        other: The other object

    Returns: >= 0 if this object is below the other object and <0 otherwise

    """
    pass
def close_to(self, other: SpatialInterface) ‑> bool

Computes if this object is close to another object

Args

other
The other object

Returns: >= 0 if this object is close to the other object and <0 otherwise

Expand source code
@abstractmethod
def close_to(self, other: 'SpatialInterface') -> bool:
    """
    Computes if this object is close to another object
    Args:
        other: The other object

    Returns: >= 0 if this object is close to the other object and <0 otherwise

    """
    pass
def closer_to_than(self, closer: SpatialInterface, than: SpatialInterface) ‑> bool

Computes if this object is closer to one object than another

Args

closer
The object that should be closer
than
The object that should be further away

Returns: >= 0 if this object is closer to one object than another and <0 otherwise

Expand source code
@abstractmethod
def closer_to_than(self, closer: 'SpatialInterface', than: 'SpatialInterface') -> bool:
    """
    Computes if this object is closer to one object than another
    Args:
        closer: The object that should be closer
        than: The object that should be further away

    Returns: >= 0 if this object is closer to one object than another and <0 otherwise

    """
    pass
def distance(self, other: SpatialInterface) ‑> float

Returns the signed distance to another spatial interface object

Args

other
The other spatial interface object

Returns: Distance (squared) to other object

Expand source code
@abstractmethod
def distance(self, other: 'SpatialInterface') -> float:
    """
    Returns the signed distance to another spatial interface object
    Args:
        other: The other spatial interface object

    Returns: Distance (squared) to other object

    """
    pass
def distance_compare(self, other: SpatialInterface, eps: float, fun) ‑> bool

Compares the distance between two objects and a target value (e.g., a dist b <= eps)

Args

other
The other object
eps
The target value
fun
The function for comparing (<=,>=,==)

Returns: >=0 if predicate is true and <0 otherwise

Expand source code
@abstractmethod
def distance_compare(self, other: 'SpatialInterface', eps: float, fun) -> bool:
    """
    Compares the distance between two objects and a target value (e.g., a dist b <= eps)
    Args:
        other: The other object
        eps: The target value
        fun: The function for comparing (<=,>=,==)

    Returns: >=0 if predicate is true and <0 otherwise

    """
    pass
def enclosed_in(self, other: SpatialInterface) ‑> float

Computes if this objects is enclosed in another object. If any this object is a collection, every object must be enclosed in an object of other

Args

other
The other object

Returns: >=0 if this object is enclosed in the other object and <0 otherwise

Expand source code
@abstractmethod
def enclosed_in(self, other: 'SpatialInterface') -> float:
    """
    Computes if this objects is enclosed in another object. If any this object is a collection, every object
    must be enclosed in an object of other
    Args:
        other: The other object

    Returns: >=0 if this object is enclosed in the other object and <0 otherwise

    """
    pass
def enlarge(self, radius: float) ‑> SpatialInterface

Enlarges an object with a given radius

Args

radius
The radius for enlarging the object

Returns: The enlarged object

Expand source code
@abstractmethod
def enlarge(self, radius: float) -> 'SpatialInterface':
    """
    Enlarges an object with a given radius
    Args:
        radius: The radius for enlarging the object

    Returns: The enlarged object

    """
    pass
def far_from(self, other: SpatialInterface) ‑> bool

Computes if this object is far from another object

Args

other
The other object

Returns: >= 0 if this object is far from the other object and <0 otherwise

Expand source code
@abstractmethod
def far_from(self, other: 'SpatialInterface') -> bool:
    """
    Computes if this object is far from another object
    Args:
        other: The other object

    Returns: >= 0 if this object is far from the other object and <0 otherwise

    """
    pass
def left_of(self, other: SpatialInterface) ‑> bool

Computes if this object is left of another object

Args

other
The other object

Returns: >= 0 if this object is left of the other object and <0 otherwise

Expand source code
@abstractmethod
def left_of(self, other: 'SpatialInterface') -> bool:
    """
    Computes if this object is left of another object
    Args:
        other: The other object

    Returns: >= 0 if this object is left of the other object and <0 otherwise

    """
    pass
def overlap(self, other: SpatialInterface) ‑> float

Computes if this object overlaps with another object

Args

other
The other object

Returns: >=0 if both objects overlap and <0 otherwise

Expand source code
@abstractmethod
def overlap(self, other: 'SpatialInterface') -> float:
    """
    Computes if this object overlaps with another object
    Args:
        other: The other object

    Returns: >=0 if both objects overlap and <0 otherwise

    """
    pass
def proximity(self, other: SpatialInterface, eps: float) ‑> bool

Computes if this objects is in proximity to another object

Args

other
The other object
eps
Specification of proximity

Returns: >=0 if objects are in proximity and <0 otherwise

Expand source code
@abstractmethod
def proximity(self, other: 'SpatialInterface', eps: float) -> bool:
    """
    Computes if this objects is in proximity to another object
    Args:
        other: The other object
        eps: Specification of proximity

    Returns: >=0 if objects are in proximity and <0 otherwise

    """
    pass
def right_of(self, other: SpatialInterface) ‑> bool

Computes if this object is right of another object

Args

other
The other object

Returns: >= 0 if this object is right of the other object and <0 otherwise

Expand source code
@abstractmethod
def right_of(self, other: 'SpatialInterface') -> bool:
    """
    Computes if this object is right of another object
    Args:
        other: The other object

    Returns: >= 0 if this object is right of the other object and <0 otherwise

    """
    pass
def shapes(self) ‑> set

Returns the shapes stored in the SpatialInterface object Returns: The shapes of the SpatialInterface object

Expand source code
@abstractmethod
def shapes(self) -> set:
    """
    Returns the shapes stored in the SpatialInterface object
    Returns: The shapes of the SpatialInterface object

    """
    pass
def touching(self, other: SpatialInterface) ‑> bool

Computes if two objects are touching

Args

other
The other object

Returns:

Expand source code
@abstractmethod
def touching(self, other: 'SpatialInterface') -> bool:
    """
    Computes if two objects are touching
    Args:
        other: The other object

    Returns:

    """
    pass
class StaticObject (spatial_object: SpatialInterface)

An SpatialInterface object static in time. The simplest implementation of ObjectInTime

Expand source code
class StaticObject(ObjectInTime):
    """
    An SpatialInterface object static in time. The simplest implementation of ObjectInTime
    """

    def __init__(self, spatial_object: SpatialInterface):
        super().__init__()
        self._spatial_obj = spatial_object

    def getObject(self, time) -> 'SpatialInterface':
        return self._spatial_obj

    def getObjectByIndex(self, idx: int) -> 'SpatialInterface':
        return self._spatial_obj

    def __or__(self, other):
        if isinstance(other, (StaticObject, DynamicObject)):
            return ObjectCollection(self, other)
        elif isinstance(other, ObjectCollection):
            return other + self
        else:
            raise Exception('<DynamicObject/add>: Provided object not supported! other = {}'.format(other))

Ancestors

Inherited members