在python中找到直线和圆的交点最有效的方法是什么?

时间:2015-06-15 11:51:21

标签: python geometry computational-geometry

我有一个由许多点组成的多边形。我想找到多边形和圆的交点。提供[x0,y0]的圆心和r0的半径,我写了一个粗略的函数来简单地求解圆和线的二次方程。但是逐个找到多边形的每个线段的交点效率怎么样? 有更有效的方法吗?

我知道同意已经提供了获取不同几何体交叉点的功能。但是,如果我想处理大量的多边形,那么如同通过我自己的函数来计算外部库的效率怎么样呢?

def LineIntersectCircle(p,lsp,lep):
# p is the circle parameter, lsp and lep is the two end of the line
  x0,y0,r0 = p
  x1,y1 = lsp
  x2,y2 = esp
  if x1 == x2:
    if abs(r0) >= abs(x1 - x0):
        p1 = x1, y0 - sqrt(r0**2 - (x1-x0)**2)
        p2 = x1, y0 + sqrt(r0**2 - (x1-x0)**2)
        inp = [p1,p2]
        # select the points lie on the line segment
        inp = [p for p in inp if p[1]>=min(y1,y2) and p[1]<=max(y1,y2)]
    else:
        inp = []
  else:
    k = (y1 - y2)/(x1 - x2)
    b0 = y1 - k*x1
    a = k**2 + 1
    b = 2*k*(b0 - y0) - 2*x0
    c = (b0 - y0)**2 + x0**2 - r0**2
    delta = b**2 - 4*a*c
    if delta >= 0:
        p1x = (-b - sqrt(delta))/(2*a)
        p2x = (-b + sqrt(delta))/(2*a)
        p1y = k*x1 + b0
        p2y = k*x2 + b0
        inp = [[p1x,p1y],[p2x,p2y]]
        # select the points lie on the line segment
        inp = [p for p in inp if p[0]>=min(x1,x2) and p[0]<=max(x1,x2)]
    else:
        inp = []
  return inp

4 个答案:

答案 0 :(得分:6)

我想也许你的问题是关于理论如何以最快的方式做到这一点。但是如果你想快速做到这一点,你应该使用用C / C ++编写的东西。

我很习惯Shapely,所以我将提供一个如何使用此库执行此操作的示例。 python有许多几何库。我会在这个答案的最后列出它们。

from shapely.geometry import LineString
from shapely.geometry import Point

p = Point(5,5)
c = p.buffer(3).boundary
l = LineString([(0,0), (10, 10)])
i = c.intersection(l)

print i.geoms[0].coords[0]
(2.8786796564403576, 2.8786796564403576)

print i.geoms[1].coords[0]
(7.121320343559642, 7.121320343559642)

Shapely中有点反直觉的是圆圈是具有缓冲区域的点的边界。这就是我做p.buffer(3).boundry

的原因

此外,交叉点i是几何形状的列表,在这种情况下它们都指向,这就是为什么我必须从i.geoms[]

获取它们的原因

对于那些感兴趣的人,有another Stackoverflow question详细介绍了这些库。

编辑,因为评论:

Shapely基于GEOS(trac.osgeo.org/geos),它是用C ++构建的,并且比你在python中本地编写的任何东西都要快得多。 SymPy似乎是基于mpmath(mpmath.org),它似乎也是python,但似乎有很多相当复杂的数学集成到它。实现这一点可能需要大量工作,并且可能不会像GEOS C ++实现那么快。

答案 1 :(得分:1)

低成本的空间分区可能是将平面划分为9个

这是一张糟糕的图表。想象一下,如果你愿意的话,线条只是触及圆圈。

  | |
__|_|__
__|O|__
  | |
  | |
我们感兴趣的8个区域围绕着圆圈。中心的广场对于廉价测试来说并不是很有用,但是你可以在圆圈内放置一个r/sqrt(2)的正方形,这样它的角就会碰到圆圈。

让我们标记区域

A |B| C
__|_|__
D_|O|_E
  | |
F |G| H

中心的r/sqrt(2)正方形我们会调用J

我们将调用图中显示的中心方块中的一组点,这些点不在JZ

用它的字母代码标记多边形的每个顶点。

现在我们可以快速看到

AA => Outside
AB => Outside
AC => Outside
...
AJ => Intersects
BJ => Intersects
...
JJ => Inside

这可以变成查找表

因此,根据您的数据集,您可能为自己节省了大量工作。然而,需要对Z中具有端点的任何内容进行测试。

答案 2 :(得分:1)

这是一种计算圆与线或由两个(x,y)点定义的线段的交点的解决方案:

def circle_line_segment_intersection(circle_center, circle_radius, pt1, pt2, full_line=True, tangent_tol=1e-9):
    """ Find the points at which a circle intersects a line-segment.  This can happen at 0, 1, or 2 points.

    :param circle_center: The (x, y) location of the circle center
    :param circle_radius: The radius of the circle
    :param pt1: The (x, y) location of the first point of the segment
    :param pt2: The (x, y) location of the second point of the segment
    :param full_line: True to find intersections along full line - not just in the segment.  False will just return intersections within the segment.
    :param tangent_tol: Numerical tolerance at which we decide the intersections are close enough to consider it a tangent
    :return Sequence[Tuple[float, float]]: A list of length 0, 1, or 2, where each element is a point at which the circle intercepts a line segment.

    Note: We follow: http://mathworld.wolfram.com/Circle-LineIntersection.html
    """

    (p1x, p1y), (p2x, p2y), (cx, cy) = pt1, pt2, circle_center
    (x1, y1), (x2, y2) = (p1x - cx, p1y - cy), (p2x - cx, p2y - cy)
    dx, dy = (x2 - x1), (y2 - y1)
    dr = (dx ** 2 + dy ** 2)**.5
    big_d = x1 * y2 - x2 * y1
    discriminant = circle_radius ** 2 * dr ** 2 - big_d ** 2

    if discriminant < 0:  # No intersection between circle and line
        return []
    else:  # There may be 0, 1, or 2 intersections with the segment
        intersections = [
            (cx + (big_d * dy + sign * (-1 if dy < 0 else 1) * dx * discriminant**.5) / dr ** 2,
             cy + (-big_d * dx + sign * abs(dy) * discriminant**.5) / dr ** 2)
            for sign in ((1, -1) if dy < 0 else (-1, 1))]  # This makes sure the order along the segment is correct
        if not full_line:  # If only considering the segment, filter out intersections that do not fall within the segment
            fraction_along_segment = [(xi - p1x) / dx if abs(dx) > abs(dy) else (yi - p1y) / dy for xi, yi in intersections]
            intersections = [pt for pt, frac in zip(intersections, fraction_along_segment) if 0 <= frac <= 1]
        if len(intersections) == 2 and abs(discriminant) <= tangent_tol:  # If line is tangent to circle, return just one point (as both intersections have same location)
            return [intersections[0]]
        else:
            return intersections

答案 3 :(得分:0)

I think that the formula you use to find the coordinates of the two intersections cannot be optimized further. The only improvement (which is numerically important) is to distinguish the two cases: |x_2-x_1| >= |y_2-y_1| and |x_2-x1| < |y_2-y1| so that the quantity k is always between -1 and 1 (in your computation you can get very high numerical errors if |x_2-x_1| is very small). You can swap x-s and y-s to reduce one case to the other.

You could also implement a preliminary check: if both endpoints are internal to the circle there are no intersection. By computing the squared distance from the points to the center of the circle this becomes a simple formula which does not use the square root function. The other check: "whether the line is outside the circle" is already implemented in your code and corresponds to delta < 0. If you have a lot of small segments these two check should give a shortcut answer (no intersection) in most cases.