找到一组交叉点中的所有四边形

时间:2017-07-21 23:43:25

标签: math language-agnostic geometry theory

我想要获取一组线的所有交点,并找到它们创建的所有凸四边形。我不确定是否有适合这种情况的算法,或者我需要循环创建自己的算法。

我有一系列线条及其所有交叉点。

线和交叉点:

enter image description here

示例四边形1:

enter image description here

示例四边形2 enter image description here

在这种情况下,我会推出8个四边形。

我怎样才能做到这一点?如果没有我可以为此实现的算法,我如何检查每个交叉点与其他交叉点以确定它们是否形成凸四边形?

2 个答案:

答案 0 :(得分:5)

有一种简单,非快速,强力的全面算法可以找到那些四边形。但是,首先你需要澄清一些定义,尤其是四边形的定义。"如果它的面积为零,你是否将其视为四边形,例如当所有顶点都是共线时?如果它自相交或交叉,你会把它算作四边形吗?如果它不凸出你算吗?如果两个相邻边是直的(包括连续的顶点相同),你算一算吗?如果多边形"再加倍"结果看起来像一个三角形,一边延伸?

Bad quadrilaterals

这是一个顶级算法:考虑一次四个线段的所有组合。如果有n个线段,则会有n*(n-1)*(n-2)*(n-3)/24个组合。对于每个组合,请查看这些段对的交叉点:最多将有6个交叉点。现在看看你是否可以从那些交叉点和段中制作四边形。

这是暴力,但至少在执行时间是多项式,O(n ^ 4)。对于8个线段的例子,这意味着考虑70个段的组合 - 不是太糟糕。这可以通过预先计算交叉点来加速:在它们中最多只有n*(n-1)/2个,在你的例子中为28个。

这种整体算法是否满足您的需求?是你的最后一个问题"我如何检查每个交叉路口与其他交叉路口以确定它们是否构成四边形?"询问如何实施我的陈述"看看你是否可以从这些交叉点和段中制作四边形"?你还需要答案吗?在我回答这个问题之前,你需要澄清你对四边形的定义。

我将解释"四边形的定义"更多。该图显示了四个线段"在一般位置,"其中每个段与所有其他段相交,并且没有三个段在同一点相交。

enter image description here

以下是(一些)"四边形"由这四个线段和六个交叉点产生。

  • 1简单和凸(ABDE)
  • 1简单而非凸(ACDF)
  • 1穿越(BCEF)
  • 在三角形的一侧有一个额外顶点的4个三角形(ABCE,ACDE,ABDF,ABFE)。请注意,前两个定义了具有不同顶点的相同区域,最后两个也是如此。
  • 4"双背"看起来像三角形,一边延伸(ACEF,BCDF,BDEF,CDEF)

取决于您如何定义" quadrilateral"和#34;平等"您可以在该图表中获得1到11个任意位置。 Wikipedia's definition只会在我的列表中包含第一个,第二个和第四个 - 我不确定这是多少重复"重复"在我的第四组。而且我甚至不确定我是否在图表中找到了所有可能性,因此可能会有更多可能性。

我看到我们现在正在定义一个四边形,如四个不同的线段所示,它们是给定线段的子段,形成严格凸起的多边形 - 顶角都小于直角。这仍然会在一些边缘情况下留下一个模糊性 - 如果两个线段重叠超过一个点会怎么样 - 但是除了定义两个这样的线段没有交叉点之外,让我们留下它。然后这个算法,基于Python的伪代码应该可以工作。

我们需要一个函数intersection_point(seg1, seg2),它返回两个给定线段的交点,如果没有或者段重叠,则返回None。我们还需要一个函数polygon_is_strictly_convex(tuple of points),它返回TrueFalse,具体取决于点的元组是否定义了一个严格凸多边形,并补充说如果任何一个点是{{1然后返回None。这两个函数都是计算几何中的标准函数。请注意"组合"在下面的意思是,对于每个返回的组合,项目按排序顺序排列,因此False(seg1, seg2)我们将获得其中一个。 Python的(seg2, seg1)做得很好。

itertools.combinations()

这是我的实际测试的Python 3.6代码,它为您的段提供了八个多边形。首先,这里是实用程序,几何例程,收集到模块intersections = {} # empty dictionary/hash table for each combination (seg1, seg2) of given line segments: intersections[(seg1, seg2)] = intersection_point(seg1, seg2) quadrilaterals = emptyset for each combination (seg1, seg2, seg3, seg4) of given line segments: for each tuple (sega, segb, segc, segc) in [ (seg1, seg2, seg3, seg4), (seg1, seg2, seg4, seg3), (seg1, seg3, seg2, seg4)]: a_quadrilateral = (intersections[(sega, segb)], intersections[(segb, segc)], intersections[(segc, segd)], intersections[(segd, sega)]) if polygon_is_strictly_convex(a_quadrilateral): quadrilaterals.add(a_quadrilateral) break # only one possible strictly convex quad per 4 segments

rdgeometry

这是我算法的代码。我添加了一点复杂性,只使用"规范形式"一对线段和多边形,以减少交叉点和多边形容器的内存使用。

def segments_intersection_point(segment1, segment2):
    """Return the intersection of two line segments. If none, return
    None.
    NOTES:  1.  This version returns None if the segments are parallel,
                even if they overlap or intersect only at endpoints.
            2.  This is optimized for assuming most segments intersect.
    """
    try:
        pt1seg1, pt2seg1 = segment1  # points defining the segment
        pt1seg2, pt2seg2 = segment2
        seg1_delta_x = pt2seg1[0] - pt1seg1[0]
        seg1_delta_y = pt2seg1[1] - pt1seg1[1]
        seg2_delta_x = pt2seg2[0] - pt1seg2[0]
        seg2_delta_y = pt2seg2[1] - pt1seg2[1]
        denom = seg2_delta_x * seg1_delta_y - seg1_delta_x * seg2_delta_y
        if denom == 0.0:  # lines containing segments are parallel or equal
            return None

        # solve for scalars t_seg1 and t_seg2 in the vector equation
        #   pt1seg1 + t_seg1 * (pt2seg1 - pt1seg1)
        # = pt1seg2 + t_seg2(pt2seg2 - pt1seg2)  and note the segments
        # intersect iff 0 <= t_seg1 <= 1, 0 <= t_seg2 <= 1 .
        pt1seg1pt1seg2_delta_x = pt1seg2[0] - pt1seg1[0]
        pt1seg1pt1seg2_delta_y = pt1seg2[1] - pt1seg1[1]
        t_seg1 = (seg2_delta_x * pt1seg1pt1seg2_delta_y
                  - pt1seg1pt1seg2_delta_x * seg2_delta_y) / denom
        t_seg2 = (seg1_delta_x * pt1seg1pt1seg2_delta_y
                  - pt1seg1pt1seg2_delta_x * seg1_delta_y) / denom
        if 0 <= t_seg1 <= 1 and 0 <= t_seg2 <= 1:
            return (pt1seg1[0] + t_seg1 * seg1_delta_x,
                    pt1seg1[1] + t_seg1 * seg1_delta_y)
        else:
            return None
    except ArithmeticError:
        return None

def orientation3points(pt1, pt2, pt3):
    """Return the orientation of three 2D points in order.
    Moving from Pt1 to Pt2 to Pt3 in cartesian coordinates:
        1 means counterclockwise (as in standard trigonometry),
        0 means straight, back, or stationary (collinear points),
       -1 means counterclockwise,
    """
    signed = ((pt2[0] - pt1[0]) * (pt3[1] - pt1[1])
              - (pt2[1] - pt1[1]) * (pt3[0] - pt1[0]))
    return 1 if signed > 0.0 else (-1 if signed < 0.0 else 0)

def is_convex_quadrilateral(pt1, pt2, pt3, pt4):
    """Return True if the quadrilateral defined by the four 2D points is
    'strictly convex', not a triangle nor concave nor self-intersecting.
    This version allows a 'point' to be None: if so, False is returned.
    NOTES:  1.  Algorithm: check that no points are None and that all
                angles are clockwise or all counter-clockwise.
            2.  This does not generalize to polygons with > 4 sides
                since it misses star polygons.
    """
    if pt1 and pt2 and pt3 and pt4:
        orientation = orientation3points(pt4, pt1, pt2)
        if (orientation != 0 and orientation
                == orientation3points(pt1, pt2, pt3)
                == orientation3points(pt2, pt3, pt4)
                == orientation3points(pt3, pt4, pt1)):
            return True
    return False

def polygon_in_canonical_order(point_seq):
    """Return a polygon, reordered so that two different
    representations of the same geometric polygon get the same result.
    The result is a tuple of the polygon's points. `point_seq` must be
    a sequence of 'points' (which can be anything).
    NOTES:  1.  This is intended for the points to be distinct. If two
                points are equal and minimal or adjacent to the minimal
                point, which result is returned is undefined.
    """
    pts = tuple(point_seq)
    length = len(pts)
    ndx = min(range(length), key=pts.__getitem__)  # index of minimum
    if pts[(ndx + 1) % length] < pts[(ndx - 1) % length]:
        return (pts[ndx],) + pts[ndx+1:] + pts[:ndx]  # forward
    else:
        return (pts[ndx],) + pts[:ndx][::-1] + pts[ndx+1:][::-1] # back

def sorted_pair(val1, val2):
    """Return a 2-tuple in sorted order from two given values."""
    if val1 <= val2:
        return (val1, val2)
    else:
        return (val2, val1)

打印出来的是:

from itertools import combinations

from rdgeometry import segments_intersection_point, \
                       is_strictly_convex_quadrilateral, \
                       polygon_in_canonical_order, \
                       sorted_pair

segments = [(( 2, 16), (22, 10)),
            (( 4,  4), (14, 14)),
            (( 4,  6), (12.54, 0.44)),
            (( 4, 14), (20,  6)),
            (( 4, 18), (14,  2)),
            (( 8,  2), (22, 16))]

intersections = dict()
for seg1, seg2 in combinations(segments, 2):
    intersections[sorted_pair(seg1, seg2)] = (
            segments_intersection_point(seg1, seg2))
quadrilaterals = set()
for seg1, seg2, seg3, seg4 in combinations(segments, 4):
    for sega, segb, segc, segd in [(seg1, seg2, seg3, seg4),
                                   (seg1, seg2, seg4, seg3),
                                   (seg1, seg3, seg2, seg4)]:
        a_quadrilateral = (intersections[sorted_pair(sega, segb)],
                           intersections[sorted_pair(segb, segc)],
                           intersections[sorted_pair(segc, segd)],
                           intersections[sorted_pair(segd, sega)])
        if is_strictly_convex_quadrilateral(*a_quadrilateral):
            quadrilaterals.add(polygon_in_canonical_order(a_quadrilateral))
            break  # only one possible strictly convex quadr per 4 segments

print('\nThere are {} strictly convex quadrilaterals, namely:'
      .format(len(quadrilaterals)))
for p in sorted(quadrilaterals):
    print(p)

答案 1 :(得分:0)

O(intersection_count 2 )算法如下:

For each intersection:
     Add the the intersection point to
         a hash table with the lines as the key.
     Let int be a lookup function that returns
         true iff the inputted lines intersect.
RectCount = 0
For each distinct pair of intersections a,b:
    Let A be the list of lines that pass
        through point a but not through b.
    Let B '' '' '' through b but not a.
    For each pair of lines c,d in A:
        For each pair of lines e,f in B:
            If (int(c,e) and int(d,f) and
                !int(c,f) and !int(d,e)) or
                (int(c,f) and int(d,e) and
                !int(c,e) and !int(d,f)):
                    RectCount += 1