web-dev-qa-db-ja.com

Pythonで線と円の交点を見つける最も効率的な方法は何ですか?

ポリゴンがたくさんのポイントで構成されています。多角形と円の交点を見つけたい。 [x0、y0]の円の中心とr0の半径を提供して、円と直線の2次方程式を簡単に解く大まかな関数を作成しました。しかし、ポリゴンのすべての線分の交点を1つずつ見つける効率はどうでしょうか。 もっと効率的な方法はありますか?

Sympyは、異なるジオメトリの交差を取得する機能をすでに提供していることを私は知っています。しかしまた私自身の関数で計算するのと比較したsympyのような外部ライブラリの効率はどうですか、私がたくさんのポリゴンを扱いたいのなら?

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
16
xibinke

おそらくあなたの質問は、理論的にこれを最速の方法で行う方法についてだと思います。ただし、これをすばやく実行したい場合は、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[]_から両方を取得する必要がある理由です。

別のStackoverflowの質問 があります。これは、興味のある人のためにこれらのライブラリの詳細を説明しています。

コメントのために編集:

Shapelyは、C++で構築されたGEOS(trac.osgeo.org/geos)に基づいており、Pythonでネイティブに作成するものよりもかなり高速です。 SymPyはmpmath(mpmath.org)に基づいているようで、これもpythonのようですが、非常に複雑な数学がたくさん統合されているようです。それを自分で実装するには多くの作業が必要になる可能性があり、おそらくGEOS C++の実装ほど速くはありません。

12
firelynx

低コストの空間パーティションは、平面を9つの部分に分割することかもしれません

これはくだらない図です。もしそうなら、線がちょうど円に触れていると想像してみてください。

 | | 
 __ | _ | __ 
 __ | O | __ 
 | | 
 | | 

私たちが興味を持っている8つのエリアが円を囲んでいます。中央の正方形は安価なテストにはあまり使用されませんが、円の内側にr/sqrt(2)の正方形を配置できるので、角が円に触れるだけです。

エリアにラベルを付けましょう

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

そして、中央のr/sqrt(2)の二乗をJと呼びます。

JZにない、図に示されている中央の正方形の点のセットを呼び出します。

ポリゴンの各頂点に文字コードのラベルを付けます。

今、私たちはすぐに見ることができます

 AA =>外側
 AB =>外側
 AC =>外側
 ... 
 AJ =>交差点
 BJ =>交差点
 ... 
 JJ =>内部

これはルックアップテーブルに変えることができます

したがって、データセットによっては、作業量を節約できた可能性があります。ただし、エンドポイントがZにあるものはすべてテストする必要があります。

1
John La Rooy

2つの交差点の座標を見つけるために使用する式は、これ以上最適化できないと思います。唯一の改善点(数値的に重要)は、2つのケースを区別することです。|x_2-x_1| >= |y_2-y_1|および|x_2-x1| < |y_2-y1|kが常に-1から1の間になるようにします(計算では、| x_2-x_1 |が非常に小さい場合、非常に高い数値エラーが発生する可能性があります)。 x-sとy-sを入れ替えて、一方のケースをもう一方のケースに減らすことができます。

予備チェックを実装することもできます。両方の端点が円の内部にある場合、交差はありません。点から円の中心までの二乗距離を計算することにより、これは平方根関数を使用しない単純な式になります。もう1つのチェック:「線が円の外側にあるかどうか」はすでにコードに実装されており、デルタ<0に対応します。小さなセグメントが多数ある場合、ほとんどの場合、これら2つのチェックでショートカットの回答(交差なし)が得られます。

0

円と、2つの(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
0
Peter