示例#1
0
    def bisectors(self):
        """The angle bisectors of the triangle.

        An angle bisector of a triangle is a straight line through a vertex
        which cuts the corresponding angle in half.

        Returns
        -------
        bisectors : dict
            Each key is a vertex (Point) and each value is the corresponding
            bisector (Segment).

        See Also
        --------
        Point
        Segment

        Examples
        --------
        >>> from sympy.geometry import Point, Triangle, Segment
        >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
        >>> t = Triangle(p1, p2, p3)
        >>> from sympy import sqrt
        >>> t.bisectors()[p2] == Segment(Point(0, sqrt(2) - 1), Point(1, 0))
        True

        """
        s = self.sides
        v = self.vertices
        c = self.incenter
        l1 = Segment(v[0], Line(v[0], c).intersection(s[1])[0])
        l2 = Segment(v[1], Line(v[1], c).intersection(s[2])[0])
        l3 = Segment(v[2], Line(v[2], c).intersection(s[0])[0])
        return {v[0]: l1, v[1]: l2, v[2]: l3}
示例#2
0
    def medians(self):
        """The medians of the triangle.

        A median of a triangle is a straight line through a vertex and the
        midpoint of the opposite side, and divides the triangle into two
        equal areas.

        Returns
        -------
        medians : dict
            Each key is a vertex (Point) and each value is the median (Segment)
            at that point.

        See Also
        --------
        Point
        Segment

        Examples
        --------
        >>> from sympy.geometry import Point, Triangle
        >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1)
        >>> t = Triangle(p1, p2, p3)
        >>> t.medians[p1]
        Segment(Point(0, 0), Point(1/2, 1/2))

        """
        s = self.sides
        v = self.vertices
        return {
            v[0]: Segment(s[1].midpoint, v[0]),
            v[1]: Segment(s[2].midpoint, v[1]),
            v[2]: Segment(s[0].midpoint, v[2])
        }
示例#3
0
 def sides(self):
     """A list of the segments that form the sides of the polygon."""
     res = []
     for ind in xrange(0, len(self.vertices)-1):
         res.append( Segment(self.vertices[ind], self.vertices[ind+1]) )
     res.append( Segment(self.vertices[-1], self.vertices[0]) )
     return res
示例#4
0
文件: polygon.py 项目: pernici/sympy
    def sides(self):
        """The line segments that form the sides of the polygon.

        Returns
        -------
        sides : list of sides
            Each side is a Segment.

        See Also
        --------
        Point
        Segment

        Examples
        --------
        >>> from sympy import Point, Polygon
        >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
        >>> poly = Polygon(p1, p2, p3, p4)
        >>> poly.sides
        [Segment(Point(0, 0), Point(1, 0)),
        Segment(Point(1, 0), Point(5, 1)),
        Segment(Point(0, 1), Point(5, 1)), Segment(Point(0, 0), Point(0, 1))]

        """
        res = []
        for ind in xrange(0, len(self.vertices) - 1):
            res.append(Segment(self.vertices[ind], self.vertices[ind + 1]))
        res.append(Segment(self.vertices[-1], self.vertices[0]))
        return res
示例#5
0
    def bisectors(self):
        """
        The angle bisectors of the triangle in a dictionary where the
        key is the vertex and the value is the bisector at that point.

        Example:
        ========
            >>> from sympy.geometry import Point, Triangle, Segment

            >>> p1,p2,p3 = Point(0,0), Point(1,0), Point(0,1)
            >>> t = Triangle(p1, p2, p3)

            >>> from sympy import sqrt
            >>> t.bisectors[p2] == Segment(Point(0, sqrt(2)-1), Point(1, 0))
            True

        """
        s = self.sides
        v = self.vertices
        c = self.incenter
        l1 = Segment(v[0],
                     GeometryEntity.do_intersection(Line(v[0], c), s[1])[0])
        l2 = Segment(v[1],
                     GeometryEntity.do_intersection(Line(v[1], c), s[2])[0])
        l3 = Segment(v[2],
                     GeometryEntity.do_intersection(Line(v[2], c), s[0])[0])
        return {v[0]: l1, v[1]: l2, v[2]: l3}
示例#6
0
文件: polygon.py 项目: nkinar/sympy
 def is_right(self):
     """Returns True if the triangle is right-angled, False otherwise."""
     s = self.sides
     return (
         Segment.is_perpendicular(s[0], s[1])
         or Segment.is_perpendicular(s[1], s[2])
         or Segment.is_perpendicular(s[0], s[2])
     )
示例#7
0
def convex_hull(*args):
    """
    Returns a Polygon representing the convex hull of a set of 2D points.

    Notes:
    ======
        This can only be performed on a set of non-symbolic points.

    Example:
    ========
        >>> from sympy.geometry import Point, convex_hull
        >>> points = [ Point(x) for x in [(1,1), (1,2), (3,1), (-5,2), (15,4)] ]
        >>> convex_hull(points)
        Polygon(Point(-5, 2), Point(1, 1), Point(3, 1), Point(15, 4))

    Description of method used:
    ===========================
        See http://en.wikipedia.org/wiki/Graham_scan.
    """
    from point import Point
    from line import Segment
    from polygon import Polygon

    def uniquify(a):
        # not order preserving
        return list(set(a))

    p = args[0]
    if isinstance(p, Point):
        p = uniquify(args)

    if len(p) == 1:
        return p[0]
    elif len(p) == 2:
        return Segment(p[0], p[1])

    def orientation(p, q, r):
        '''Return positive if p-q-r are clockwise, neg if ccw, zero if colinear.'''
        return (q[1]-p[1])*(r[0]-p[0]) - (q[0]-p[0])*(r[1]-p[1])

    '''scan to find upper and lower convex hulls of a set of 2d points.'''
    U = []
    L = []
    p.sort()
    for p_i in p:
        while len(U) > 1 and orientation(U[-2], U[-1], p_i) <= 0:
            U.pop()
        while len(L) > 1 and orientation(L[-2], L[-1], p_i) >= 0:
            L.pop()
        U.append(p_i)
        L.append(p_i)
    U.reverse()
    convexHull = tuple(L + U[1:-1])

    if len(convexHull) == 2:
        return Segment(convexHull[0], convexHull[1])
    return Polygon(convexHull)
示例#8
0
    def sides(self):
        """The line segments that form the sides of the polygon.

        Returns
        -------
        sides : list of sides
            Each side is a Segment.

        Note
        ----
        The Segments that represent the sides are an undirected
        line segment so cannot be used to tell the orientation of
        the polygon.

        See Also
        --------
        Point
        Segment

        Examples
        --------
        >>> from sympy import Point, Polygon
        >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)])
        >>> poly = Polygon(p1, p2, p3, p4)
        >>> poly.sides
        [Segment(Point(0, 0), Point(1, 0)),
        Segment(Point(1, 0), Point(5, 1)),
        Segment(Point(0, 1), Point(5, 1)), Segment(Point(0, 0), Point(0, 1))]

        """
        res = []
        for i in xrange(-len(self), 0):
            res.append(Segment(self[i], self[i + 1]))
        return res
示例#9
0
    def __new__(cls, *args, **kwargs):
        if len(args) != 3:
            raise GeometryError("Triangle.__new__ requires three points")

        vertices = [Point(a) for a in args]

        # remove consecutive duplicates
        nodup = []
        for p in vertices:
            if nodup and p == nodup[-1]:
                continue
            nodup.append(p)
        if len(nodup) > 1 and nodup[-1] == nodup[0]:
            nodup.pop()  # last point was same as first

        # remove collinear points
        i = -3
        while i < len(nodup) - 3 and len(nodup) > 2:
            a, b, c = sorted([nodup[i], nodup[i + 1], nodup[i + 2]])
            if Point.is_collinear(a, b, c):
                nodup[i] = a
                nodup[i + 1] = None
                nodup.pop(i + 1)
            i += 1

        vertices = filter(lambda x: x is not None, nodup)

        if len(vertices) == 3:
            return GeometryEntity.__new__(cls, *vertices, **kwargs)
        elif len(vertices) == 2:
            return Segment(*vertices, **kwargs)
        else:
            return Point(*vertices, **kwargs)
示例#10
0
    def medians(self):
        """
        The medians of the triangle in a dictionary where the key
        is the vertex and the value is the median at that point.

        Example:
        ========
            >>> p1,p2,p3 = Point(0,0), Point(1,0), Point(0,1)
            >>> t = Triangle(p1, p2, p3)
            >>> t.medians[p1]
            Segment(Point(0, 0), Point(1/2, 1/2))

        """
        s = self.sides
        v = self.vertices
        return {v[0]: Segment(s[1].midpoint, v[0]),
                v[1]: Segment(s[2].midpoint, v[1]),
                v[2]: Segment(s[0].midpoint, v[2])}
示例#11
0
    def is_right(self):
        """Is the triangle right-angled.

        Returns
        -------
        is_right : boolean

        Examples
        --------
        >>> from sympy.geometry import Triangle, Point
        >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
        >>> t1.is_right()
        True

        """
        s = self.sides
        return Segment.is_perpendicular(s[0], s[1]) or \
               Segment.is_perpendicular(s[1], s[2]) or \
               Segment.is_perpendicular(s[0], s[2])
示例#12
0
文件: polygon.py 项目: Kimay/sympy
    def is_right(self):
        """Is the triangle right-angled.

        Returns
        -------
        is_right : boolean

        Examples
        --------
        >>> from sympy.geometry import Triangle, Point
        >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3))
        >>> t1.is_right()
        True

        """
        s = self.sides
        return Segment.is_perpendicular(s[0], s[1]) or \
               Segment.is_perpendicular(s[1], s[2]) or \
               Segment.is_perpendicular(s[0], s[2])
示例#13
0
    def encloses_point(self, p):
        """
        Return True if p is enclosed by (is inside of) self.

        Notes
        -----
        Being on the border of self is considered False.

        The general Polygon.encloses_point method is called only if
        a point is not within or beyond the incircle or circumcircle,
        respectively.

        Parameters
        ----------
        p : Point

        Returns
        -------
        encloses_point : True, False or None

        Examples
        --------
        >>> from sympy import RegularPolygon, S, Point, Symbol
        >>> p = RegularPolygon((0, 0), 3, 4)
        >>> p.encloses_point(Point(0, 0))
        True
        >>> r, R = p.inradius, p.circumradius
        >>> p.encloses_point(Point((r + R)/2, 0))
        True
        >>> p.encloses_point(Point(R/2, R/2 + (R - r)/10))
        False
        >>> t = Symbol('t', real=True)
        >>> p.encloses_point(p.arbitrary_point().subs(t, S.Half))
        False
        >>> p.encloses_point(Point(5, 5))
        False

        """

        c = self.center
        d = Segment(c, p).length
        if d >= self.radius:
            return False
        elif d < self.inradius:
            return True
        else:
            # now enumerate the RegularPolygon like a general polygon.
            return Polygon.encloses_point(self, p)
示例#14
0
 def is_right(self):
     """Returns True if the triangle is right-angled, False otherwise."""
     s = self.sides
     return Segment.is_perpendicular(s[0], s[1]) or \
            Segment.is_perpendicular(s[1], s[2]) or \
            Segment.is_perpendicular(s[0], s[2])
示例#15
0
def convex_hull(*args):
    """The convex hull of a collection of 2-dimensional points.

    Parameters
    ----------
    args : a collection of Points

    Returns
    -------
    convex_hull : Polygon

    Notes
    -----
    This can only be performed on a set of non-symbolic points.

    See Also
    --------
    Point

    References
    ----------
    http://en.wikipedia.org/wiki/Graham_scan

    Examples
    --------
    >>> from sympy.geometry import Point, convex_hull
    >>> points = [Point(x) for x in [(1, 1), (1, 2), (3, 1), (-5, 2), (15, 4)]]
    >>> convex_hull(points)
    Polygon(Point(-5, 2), Point(1, 1), Point(3, 1), Point(15, 4))

    """
    from point import Point
    from line import Segment
    from polygon import Polygon

    def uniquify(a):
        # not order preserving
        return list(set(a))

    p = args[0]
    if isinstance(p, Point):
        p = uniquify(args)

    if len(p) == 1:
        return p[0]
    elif len(p) == 2:
        return Segment(p[0], p[1])

    def orientation(p, q, r):
        '''Return positive if p-q-r are clockwise, neg if ccw, zero if
        collinear.'''
        return (q[1] - p[1]) * (r[0] - p[0]) - (q[0] - p[0]) * (r[1] - p[1])

    # scan to find upper and lower convex hulls of a set of 2d points.
    U = []
    L = []
    p.sort()
    for p_i in p:
        while len(U) > 1 and orientation(U[-2], U[-1], p_i) <= 0:
            U.pop()
        while len(L) > 1 and orientation(L[-2], L[-1], p_i) >= 0:
            L.pop()
        U.append(p_i)
        L.append(p_i)
    U.reverse()
    convexHull = tuple(L + U[1:-1])

    if len(convexHull) == 2:
        return Segment(convexHull[0], convexHull[1])
    return Polygon(convexHull)
示例#16
0
    def _do_poly_distance(self, e2):
        """
        Calculates the least distance between the exteriors of two
        convex polygons e1 and e2. Does not check for the convexity
        of the polygons as it is assumed only called by Polygon.distance
        which does such checks.

        Notes
        -----
            - Prints a warning if the two polygons possibly intersect as the return
              value will not be valid in such a case. For a more through test of
              intersection use intersection().

        Example
        -------
            >>> from sympy.geometry import Point, Polygon
            >>> square = Polygon(Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0))
            >>> triangle = Polygon(Point(1, 2), Point(2, 2), Point(2, 1))
            >>> square._do_poly_distance(triangle)
            sqrt(2)/2

        Description of method used
        --------------------------
        Method:
        [1] http://cgm.cs.mcgill.ca/~orm/mind2p.html
        Uses rotating calipers:
        [2] http://en.wikipedia.org/wiki/Rotating_calipers
        and antipodal points:
        [3] http://en.wikipedia.org/wiki/Antipodal_point
        """
        e1 = self
        '''Tests for a possible intersection between the polygons and outputs a warning'''
        e1_center = e1.centroid
        e2_center = e2.centroid
        e1_max_radius = S(0)
        e2_max_radius = S(0)
        for vertex in e1.vertices:
            r = Point.distance(e1_center, vertex)
            if e1_max_radius < r:
                e1_max_radius = r
        for vertex in e2.vertices:
            r = Point.distance(e2_center, vertex)
            if e2_max_radius < r:
                e2_max_radius = r
        center_dist = Point.distance(e1_center, e2_center)
        if center_dist <= e1_max_radius + e2_max_radius:
            warnings.warn("Polygons may intersect producing erroneous output")
        '''
        Find the upper rightmost vertex of e1 and the lowest leftmost vertex of e2
        '''
        e1_ymax = (S(0), -oo)
        e2_ymin = (S(0), oo)

        for vertex in e1.vertices:
            if vertex[1] > e1_ymax[1] or (vertex[1] == e1_ymax[1]
                                          and vertex[0] > e1_ymax[0]):
                e1_ymax = vertex
        for vertex in e2.vertices:
            if vertex[1] < e2_ymin[1] or (vertex[1] == e2_ymin[1]
                                          and vertex[0] < e2_ymin[0]):
                e2_ymin = vertex
        min_dist = Point.distance(e1_ymax, e2_ymin)
        '''
        Produce a dictionary with vertices of e1 as the keys and, for each vertex, the points
        to which the vertex is connected as its value. The same is then done for e2.
        '''
        e1_connections = {}
        e2_connections = {}

        for side in e1.sides:
            if side.p1 in e1_connections:
                e1_connections[side.p1].append(side.p2)
            else:
                e1_connections[side.p1] = [side.p2]

            if side.p2 in e1_connections:
                e1_connections[side.p2].append(side.p1)
            else:
                e1_connections[side.p2] = [side.p1]

        for side in e2.sides:
            if side.p1 in e2_connections:
                e2_connections[side.p1].append(side.p2)
            else:
                e2_connections[side.p1] = [side.p2]

            if side.p2 in e2_connections:
                e2_connections[side.p2].append(side.p1)
            else:
                e2_connections[side.p2] = [side.p1]

        e1_current = e1_ymax
        e2_current = e2_ymin
        support_line = Line(Point(S(0), S(0)), Point(S(1), S(0)))
        '''
        Determine which point in e1 and e2 will be selected after e2_ymin and e1_ymax,
        this information combined with the above produced dictionaries determines the
        path that will be taken around the polygons
        '''
        point1 = e1_connections[e1_ymax][0]
        point2 = e1_connections[e1_ymax][1]
        angle1 = support_line.angle_between(Line(e1_ymax, point1))
        angle2 = support_line.angle_between(Line(e1_ymax, point2))
        if angle1 < angle2: e1_next = point1
        elif angle2 < angle1: e1_next = point2
        elif Point.distance(e1_ymax, point1) > Point.distance(e1_ymax, point2):
            e1_next = point2
        else:
            e1_next = point1

        point1 = e2_connections[e2_ymin][0]
        point2 = e2_connections[e2_ymin][1]
        angle1 = support_line.angle_between(Line(e2_ymin, point1))
        angle2 = support_line.angle_between(Line(e2_ymin, point2))
        if angle1 > angle2: e2_next = point1
        elif angle2 > angle1: e2_next = point2
        elif Point.distance(e2_ymin, point1) > Point.distance(e2_ymin, point2):
            e2_next = point2
        else:
            e2_next = point1
        '''
        Loop which determins the distance between anti-podal pairs and updates the
        minimum distance accordingly. It repeats until it reaches the starting position.
        '''
        while True:
            e1_angle = support_line.angle_between(Line(e1_current, e1_next))
            e2_angle = pi - support_line.angle_between(
                Line(e2_current, e2_next))

            if e1_angle < e2_angle:
                support_line = Line(e1_current, e1_next)
                e1_segment = Segment(e1_current, e1_next)
                min_dist_current = e1_segment.distance(e2_current)

                if min_dist_current.evalf() < min_dist.evalf():
                    min_dist = min_dist_current

                if e1_connections[e1_next][0] != e1_current:
                    e1_current = e1_next
                    e1_next = e1_connections[e1_next][0]
                else:
                    e1_current = e1_next
                    e1_next = e1_connections[e1_next][1]
            elif e1_angle > e2_angle:
                support_line = Line(e2_next, e2_current)
                e2_segment = Segment(e2_current, e2_next)
                min_dist_current = e2_segment.distance(e1_current)

                if min_dist_current.evalf() < min_dist.evalf():
                    min_dist = min_dist_current

                if e2_connections[e2_next][0] != e2_current:
                    e2_current = e2_next
                    e2_next = e2_connections[e2_next][0]
                else:
                    e2_current = e2_next
                    e2_next = e2_connections[e2_next][1]
            else:
                support_line = Line(e1_current, e1_next)
                e1_segment = Segment(e1_current, e1_next)
                e2_segment = Segment(e2_current, e2_next)
                min1 = e1_segment.distance(e2_next)
                min2 = e2_segment.distance(e1_next)

                min_dist_current = min(min1, min2)
                if min_dist_current.evalf() < min_dist.evalf():
                    min_dist = min_dist_current

                if e1_connections[e1_next][0] != e1_current:
                    e1_current = e1_next
                    e1_next = e1_connections[e1_next][0]
                else:
                    e1_current = e1_next
                    e1_next = e1_connections[e1_next][1]

                if e2_connections[e2_next][0] != e2_current:
                    e2_current = e2_next
                    e2_next = e2_connections[e2_next][0]
                else:
                    e2_current = e2_next
                    e2_next = e2_connections[e2_next][1]
            if e1_current == e1_ymax and e2_current == e2_ymin: break
        return min_dist
示例#17
0
def convex_hull(*args):
    """The convex hull of a collection of 2-dimensional points.

    Parameters
    ----------
    args : a collection of Points

    Returns
    -------
    convex_hull : Polygon

    Notes
    -----
    This can only be performed on a set of non-symbolic points.

    See Also
    --------
    Point

    References
    ----------
    [1] http://en.wikipedia.org/wiki/Graham_scan

    [2] Andrew's Monotone Chain Algorithm
    ( A.M. Andrew, "Another Efficient Algorithm for Convex Hulls in Two Dimensions", 1979)
    http://softsurfer.com/Archive/algorithm_0109/algorithm_0109.htm

    Examples
    --------
    >>> from sympy.geometry import Point, convex_hull
    >>> points = [(1,1), (1,2), (3,1), (-5,2), (15,4)]
    >>> convex_hull(*points)
    Polygon(Point(-5, 2), Point(1, 1), Point(3, 1), Point(15, 4))

    """
    from entity import GeometryEntity
    from point import Point
    from line import Segment
    from polygon import Polygon

    p = set()
    for e in args:
        if not isinstance(e, GeometryEntity):
            try:
                e = Point(e)
            except NotImplementedError:
                raise ValueError('%s is not a GeometryEntity and cannot be made into Point' % str(e))
        if isinstance(e, Point):
            p.add(e)
        elif isinstance(e, Segment):
            p.update(e.points)
        elif isinstance(e, Polygon):
            p.update(e.vertices)
        else:
            raise NotImplementedError('Convex hull for %s not implemented.' % type(e))

    p = list(p)
    if len(p) == 1:
        return p[0]
    elif len(p) == 2:
        return Segment(p[0], p[1])

    def orientation(p, q, r):
        '''Return positive if p-q-r are clockwise, neg if ccw, zero if
        collinear.'''
        return (q[1] - p[1])*(r[0] - p[0]) - (q[0] - p[0])*(r[1] - p[1])

    # scan to find upper and lower convex hulls of a set of 2d points.
    U = []
    L = []
    p.sort()
    for p_i in p:
        while len(U) > 1 and orientation(U[-2], U[-1], p_i) <= 0:
            U.pop()
        while len(L) > 1 and orientation(L[-2], L[-1], p_i) >= 0:
            L.pop()
        U.append(p_i)
        L.append(p_i)
    U.reverse()
    convexHull = tuple(L + U[1:-1])

    if len(convexHull) == 2:
        return Segment(convexHull[0], convexHull[1])
    return Polygon(*convexHull)
示例#18
0
    def __new__(cls, *args, **kwargs):
        if kwargs.get('n', 0):
            n = kwargs.pop('n')
            args = list(args)
            # return a virtual polygon with n sides
            if len(args) == 2:  # center, radius
                args.append(n)
            elif len(args) == 3:  # center, radius, rotation
                args.insert(2, n)
            return RegularPolygon(*args, **kwargs)

        vertices = [Point(a) for a in args]

        # remove consecutive duplicates
        nodup = []
        for p in vertices:
            if nodup and p == nodup[-1]:
                continue
            nodup.append(p)
        if len(nodup) > 1 and nodup[-1] == nodup[0]:
            nodup.pop()  # last point was same as first

        # remove collinear points unless they are shared points
        got = set()
        shared = set()
        for p in nodup:
            if p in got:
                shared.add(p)
            else:
                got.add(p)
        i = -3
        while i < len(nodup) - 3 and len(nodup) > 2:
            a, b, c = sorted([nodup[i], nodup[i + 1], nodup[i + 2]])
            if b not in shared and Point.is_collinear(a, b, c):
                nodup[i] = a
                nodup[i + 1] = None
                nodup.pop(i + 1)
            i += 1

        vertices = filter(lambda x: x is not None, nodup)

        if len(vertices) > 3:
            rv = GeometryEntity.__new__(cls, *vertices, **kwargs)
        elif len(vertices) == 3:
            return Triangle(*vertices, **kwargs)
        elif len(vertices) == 2:
            return Segment(*vertices, **kwargs)
        else:
            return Point(*vertices, **kwargs)

        # reject polygons that have intersecting sides unless the
        # intersection is a shared point or a generalized intersection.
        # A self-intersecting polygon is easier to detect than a
        # random set of segments since only those sides that are not
        # part of the convex hull can possibly intersect with other
        # sides of the polygon...but for now we use the n**2 algorithm
        # and check all sides with intersection with any preceding sides
        hit = _symbol('hit')
        if not rv.is_convex:
            sides = rv.sides
            for i, si in enumerate(sides):
                pts = si[0], si[1]
                ai = si.arbitrary_point(hit)
                for j in xrange(i):
                    sj = sides[j]
                    if sj[0] not in pts and sj[1] not in pts:
                        aj = si.arbitrary_point(hit)
                        tx = (solve(ai[0] - aj[0]) or [S.Zero])[0]
                        if tx.is_number and 0 <= tx <= 1:
                            ty = (solve(ai[1] - aj[1]) or [S.Zero])[0]
                            if (tx or ty) and ty.is_number and 0 <= ty <= 1:
                                print ai, aj
                                raise GeometryError(
                                    "Polygon has intersecting sides.")

        return rv
示例#19
0
文件: polygon.py 项目: Kimay/sympy
    def _do_poly_distance(self, e2):
        """
        Calculates the least distance between the exteriors of two
        convex polygons e1 and e2. Does not check for the convexity
        of the polygons as it is assumed only called by Polygon.distance
        which does such checks.

        Notes
        -----
            - Prints a warning if the two polygons possibly intersect as the return
              value will not be valid in such a case. For a more through test of
              intersection use intersection().

        Examples
        -------
            >>> from sympy.geometry import Point, Polygon
            >>> square = Polygon(Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0))
            >>> triangle = Polygon(Point(1, 2), Point(2, 2), Point(2, 1))
            >>> square._do_poly_distance(triangle)
            sqrt(2)/2

        Description of method used
        --------------------------
        Method:
        [1] http://cgm.cs.mcgill.ca/~orm/mind2p.html
        Uses rotating calipers:
        [2] http://en.wikipedia.org/wiki/Rotating_calipers
        and antipodal points:
        [3] http://en.wikipedia.org/wiki/Antipodal_point
        """
        e1 = self

        '''Tests for a possible intersection between the polygons and outputs a warning'''
        e1_center = e1.centroid
        e2_center = e2.centroid
        e1_max_radius = S(0)
        e2_max_radius = S(0)
        for vertex in e1.vertices:
            r = Point.distance(e1_center, vertex)
            if e1_max_radius < r:
                e1_max_radius = r
        for vertex in e2.vertices:
            r = Point.distance(e2_center, vertex)
            if e2_max_radius < r:
                e2_max_radius = r
        center_dist = Point.distance(e1_center, e2_center)
        if center_dist <= e1_max_radius + e2_max_radius:
            warnings.warn("Polygons may intersect producing erroneous output")

        '''
        Find the upper rightmost vertex of e1 and the lowest leftmost vertex of e2
        '''
        e1_ymax = (S(0), -oo)
        e2_ymin = (S(0), oo)

        for vertex in e1.vertices:
            if vertex[1] > e1_ymax[1] or (vertex[1] == e1_ymax[1] and vertex[0] > e1_ymax[0]):
                e1_ymax = vertex
        for vertex in e2.vertices:
            if vertex[1] < e2_ymin[1] or (vertex[1] == e2_ymin[1] and vertex[0] < e2_ymin[0]):
                e2_ymin = vertex
        min_dist = Point.distance(e1_ymax, e2_ymin)

        '''
        Produce a dictionary with vertices of e1 as the keys and, for each vertex, the points
        to which the vertex is connected as its value. The same is then done for e2.
        '''
        e1_connections = {}
        e2_connections = {}

        for side in e1.sides:
            if side.p1 in e1_connections:
                e1_connections[side.p1].append(side.p2)
            else:
                e1_connections[side.p1] = [side.p2]

            if side.p2 in e1_connections:
                e1_connections[side.p2].append(side.p1)
            else:
                e1_connections[side.p2] = [side.p1]

        for side in e2.sides:
            if side.p1 in e2_connections:
                e2_connections[side.p1].append(side.p2)
            else:
                e2_connections[side.p1] = [side.p2]

            if side.p2 in e2_connections:
                e2_connections[side.p2].append(side.p1)
            else:
                e2_connections[side.p2] = [side.p1]

        e1_current = e1_ymax
        e2_current = e2_ymin
        support_line = Line(Point(S(0), S(0)), Point(S(1), S(0)))

        '''
        Determine which point in e1 and e2 will be selected after e2_ymin and e1_ymax,
        this information combined with the above produced dictionaries determines the
        path that will be taken around the polygons
        '''
        point1 = e1_connections[e1_ymax][0]
        point2 = e1_connections[e1_ymax][1]
        angle1 = support_line.angle_between(Line(e1_ymax, point1))
        angle2 = support_line.angle_between(Line(e1_ymax, point2))
        if angle1 < angle2: e1_next = point1
        elif angle2 < angle1: e1_next = point2
        elif Point.distance(e1_ymax, point1) > Point.distance(e1_ymax, point2):
            e1_next = point2
        else: e1_next = point1

        point1 = e2_connections[e2_ymin][0]
        point2 = e2_connections[e2_ymin][1]
        angle1 = support_line.angle_between(Line(e2_ymin, point1))
        angle2 = support_line.angle_between(Line(e2_ymin, point2))
        if angle1 > angle2: e2_next = point1
        elif angle2 > angle1: e2_next = point2
        elif Point.distance(e2_ymin, point1) > Point.distance(e2_ymin, point2):
            e2_next = point2
        else: e2_next = point1

        '''
        Loop which determins the distance between anti-podal pairs and updates the
        minimum distance accordingly. It repeats until it reaches the starting position.
        '''
        while True:
            e1_angle = support_line.angle_between(Line(e1_current, e1_next))
            e2_angle = pi - support_line.angle_between(Line(e2_current, e2_next))

            if e1_angle < e2_angle:
                support_line = Line(e1_current, e1_next)
                e1_segment = Segment(e1_current, e1_next)
                min_dist_current = e1_segment.distance(e2_current)

                if min_dist_current.evalf() < min_dist.evalf(): min_dist = min_dist_current

                if e1_connections[e1_next][0] != e1_current:
                    e1_current = e1_next
                    e1_next = e1_connections[e1_next][0]
                else:
                    e1_current = e1_next
                    e1_next = e1_connections[e1_next][1]
            elif e1_angle > e2_angle:
                support_line = Line(e2_next, e2_current)
                e2_segment = Segment(e2_current, e2_next)
                min_dist_current = e2_segment.distance(e1_current)

                if min_dist_current.evalf() < min_dist.evalf(): min_dist = min_dist_current

                if e2_connections[e2_next][0] != e2_current:
                    e2_current = e2_next
                    e2_next = e2_connections[e2_next][0]
                else:
                    e2_current = e2_next
                    e2_next = e2_connections[e2_next][1]
            else:
                support_line = Line(e1_current, e1_next)
                e1_segment = Segment(e1_current, e1_next)
                e2_segment = Segment(e2_current, e2_next)
                min1 = e1_segment.distance(e2_next)
                min2 = e2_segment.distance(e1_next)

                min_dist_current = min(min1, min2)
                if min_dist_current.evalf() < min_dist.evalf(): min_dist = min_dist_current

                if e1_connections[e1_next][0] != e1_current:
                    e1_current = e1_next
                    e1_next = e1_connections[e1_next][0]
                else:
                    e1_current = e1_next
                    e1_next = e1_connections[e1_next][1]

                if e2_connections[e2_next][0] != e2_current:
                    e2_current = e2_next
                    e2_next = e2_connections[e2_next][0]
                else:
                    e2_current = e2_next
                    e2_next = e2_connections[e2_next][1]
            if e1_current == e1_ymax and e2_current == e2_ymin: break
        return min_dist
示例#20
0
def convex_hull(*args):
    """
    Returns a Polygon representing the convex hull of a set of 2D points.

    Notes:
    ======
        This can only be performed on a set of non-symbolic points.

    Example:
    ========
        >>> from sympy.geometry import Point
        >>> points = [ Point(x) for x in [(1,1), (1,2), (3,1), (-5,2), (15,4)] ]
        >>> convex_hull(points)
        Polygon(Point(3, 1), Point(15, 4), Point(-5, 2), Point(1, 1))

    Description of method used:
    ===========================
        See http://en.wikipedia.org/wiki/Graham_scan.
    """
    from point import Point
    from line import Segment
    from polygon import Polygon

    p = args[0]
    if isinstance(p, Point):
        p = args

    # Basic checks
    if len(p) == 1:
        return p[0]
    elif len(p) == 2:
        return Segment(p[0], p[1])

    # Find lowest+rightmost point
    m = 0
    for i in xrange(1, len(p)):
        if (p[i][1] < p[m][1]) or ((p[i][1] == p[m][1]) and
                                   (p[i][0] > p[m][0])):
            m = i
    p[0], p[m] = p[m], p[0]

    def tarea(a, b, c):
        return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1])

    # Radial sort of points with respect to p[0] (our pivot)
    destroy = {}
    p0 = p[0]

    def pcompare(p1, p2):
        a = tarea(p0, p1, p2)
        if a > 0:
            return -1
        elif a < 0:
            return 1
        else:
            x = abs(p1[0] - p0[0]) - abs(p2[0] - p0[0])
            y = abs(p1[1] - p0[1]) - abs(p2[1] - p0[1])
            if (x < 0) or (y < 0):
                destroy[p1] = True
                return -1
            elif (x > 0) or (y > 0):
                destroy[p2] = True
                return 1
            else:
                destroy[p1] = True
                return 0

    p = p[1:]
    p.sort(pcompare)
    p.insert(0, p0)

    # Destroy points as found by sorting
    for i in xrange(len(p) - 1, -1, -1):
        if p[i] in destroy:
            del p[i]

    # Graham scan
    def isleft(a, b, c):
        return (tarea(a, b, c) > 0)

    top = [p[0], p[1]]
    i = 2
    while i < len(p):
        p1 = top[-2]
        p2 = top[-1]
        if isleft(p1, p2, p[i]):
            top.append(p[i])
            i += 1
        else:
            top.pop()
    return Polygon(top)