Python:距离第三点最近的直线上的点

问题描述 投票:0回答:3

我在两个 XY 点(p1 和 p2)和线外的第三个 XY 点(p3)之间有一条线/矢量。根据这篇文章我知道如何获得该点到线的距离。但我实际上要寻找的是该线上的一个点(p4),该点与第三点(p3)的最小距离(d)。我找到了this post,但我觉得这不是正确的解决方案。也许 Numpy 或 Python 中包含一些东西?

根据@allo,我尝试了以下方法。您可以将我的代码下载为 Python 文件Jupyter Notebook(均为 Python3)。

points = [[1, 1], [3, 1], [2.5, 2], [2.5, 1]]
import matplotlib.pyplot as plt
%matplotlib inline

fig, ax = plt.subplots()
fig.set_size_inches(6,6)

x, y = zip(*points[:2])
l1, = ax.plot(x,y, color='blue')
scatter1 = ax.scatter(x=x,y=y, color='blue', marker='x', s=80, alpha=1.0)

x, y = zip(*points[2:])
l2, = ax.plot(x,y, color='red')
scatter2 = ax.scatter(x=x,y=y, color='red', marker='x', s=80, alpha=1.0)

p1 = Vector2D(*points[0])
p2 = Vector2D(*points[1])
p3 = Vector2D(*points[2])

p1p2 = p2.sub_vector(p1)
p1p3 = p3.sub_vector(p1)

angle_p1p2_p1p3 = p1p2.get_angle_radians(p1p3)
length_p1p3 = p1p3.get_length()
length_p1p2 = p1p2.get_length()

p4 = p1.add_vector(p1p2.multiply(p1p3.get_length()/p1p2.get_length()).multiply(math.cos(p1p2.get_angle_radians(p1p3))))

#p4 = p1 + p1p2 * length(p1p3)/length(p1p2)*cos(angle(p1p2, p1p3))

p4 = p1.add_vector(p1p2.multiply(length_p1p3/length_p1p2*math.cos(angle_p1p2_p1p3)))
p4

结果 p4 = (1.8062257748298551, 1.0) 但显然应该是 (2.5, 1.0).

python python-3.x numpy
3个回答
10
投票

解析几何

让我们从指定的线开始,我们用其上的两个点

(x1, y1)
(x2, y2)
来定义线。

使用

dx = x2-x1
dy = y2-y1
,我们可以将线上的每个点正式写为
(x12, y12) = (x1, y1) + a*(dx, dy)
,其中
a
是实数。

使用类似的符号,穿过

(x3, y3)
并垂直于指定点的直线上的点是
(x34, y34) = (x3, y3) + b*(-dy, +dx)

为了找到交点,我们必须施加

(x12, y12) = (x34, y34)
(x1, y1) + a*(dx, dy) = (x3, y3) + b*(-dy, +dx)

分别写出

x
y

的方程
y1 + a dy - y3 - b dx = 0   | +dy  -dx |   | a |   | y3-y1 |
                          → |          | × |   | = |       |
x1 + a dx + b dy - x3 = 0   | +dx  +dy |   | b |   | x3-x1 |

它是

a
b
中的线性系统,其解为

| a |       1       | +dy  +dx |   | y3-y1 |    1 | dy·δy+dx·δx |
|   | = --------- × |          | × |       | =  - |             |
| b |   dx² + dy²   | -dx  +dy |   | x3-x1 |    Δ | dy·δx-dx·δy |

dx=x2-x1, δx=x3-x1, dy=y2-t1, δy=y3-y1, Δ=dx²+dy².

直线上距离

(x3, y3)
最近的点的坐标 是
(x1+a*dx, y1+a*dy)
— 您只需计算系数
a = (dy·δy+dx·δx)/Δ

从数值上来说,线性系统的行列式是

Δ = dx**2+dy**2
,因此只有当两个初始点相对于第三点的距离 w/r 彼此非常接近时,才会出现问题。

Python 实现

我们使用 2-uple 浮点数来表示 2D 点,并定义一个函数,其参数为 3 个 2-uples,表示定义线 (

p1, p2
) 的点和确定位置的点 (
p3
)位于所述线上的
p4

In [16]: def p4(p1, p2, p3):
    ...:     x1, y1 = p1
    ...:     x2, y2 = p2
    ...:     x3, y3 = p3
    ...:     dx, dy = x2-x1, y2-y1
    ...:     det = dx*dx + dy*dy
    ...:     a = (dy*(y3-y1)+dx*(x3-x1))/det
    ...:     return x1+a*dx, y1+a*dy

为了测试实现,我使用了OP使用的三个点 来展示他们对这个问题的疑问:

In [17]: p4((1.0, 1.0), (3.0, 1.0), (2.5, 2))
Out[17]: (2.5, 1.0)

看来

p4(...)
的结果与OP的预期相符。此外,当
p1, p2, p3
共线时,函数会按照几何考虑的预期返回
p3


Matplotlib 示例

请注意,当且仅当您将轴的纵横比设置为 1(即轴的缩放比例相同)时,您的绘图才会显示预期为直角的直角。

import matplotlib.pyplot as plt

def p(p1, p2, p3):
    (x1, y1), (x2, y2), (x3, y3) = p1, p2, p3
    dx, dy = x2-x1, y2-y1
    det = dx*dx + dy*dy
    a = (dy*(y3-y1)+dx*(x3-x1))/det
    return x1+a*dx, y1+a*dy

p1, p2, p3 = (2, 4), (7, 3), (1, 1)
p4 = p(p1, p2, p3)

fig, ax = plt.subplots()

# if we want to see right angles where they should be,
# the aspect ratio y/x must be equal to one,
ax.set_aspect(1)

plt.plot(*zip(p1, p2, p4, p3), marker='*')

3
投票

Shapely 的 distance() 函数返回最小距离:

>>> from shapely.geometry import LineString as shLs
>>> from shapely.geometry import Point as shPt
>>> l = shLs([ (1,1), (3,1)])
>>> p = shPt(2,2)
>>> dist = p.distance(l)
1.0
>>> l.interpolate(dist).wkt
'POINT (2 1)'


0
投票

您想要做的是矢量投影

边缘

p1p3
旋转到边缘
p1p2
上,您需要找到线段
p1p4
的正确长度。然后你就可以使用
p1+FACTOR*p1p2 / length(p1p2)
。所需的因子由
p1p2
p1p3
之间的角度的余弦给出。然后你就得到了

p4 = p1 + p1p2 * length(p1p3)/length(p1p2)*cos(angle(p1p2, p1p3))

这里以两种边缘情况为例:

  • 如果
    0
    p1p3
    正交,则余弦为
    p1p2
    ,因此
    p4
    位于
    p1
    上。
  • p1p3
    位于
    p1p2
    上时,余弦为 1,因此
    p1p2
    只需按
    length(p1p3)/length(p1p2)
    缩放即可得到
    p1p4

您还可以将余弦替换为点积

dot(p1p2 / length(p1p2), p1p3 / length(p1p3)

您可以在关于线性代数的维基书中找到更多细节和精美插图

这里有一个源自 python 代码的完整示例。我在这里使用 numpy 而不是 Vector2D。

points = [[1, 1], [3, 1], [2.5, 2], [2.5, 1]] import matplotlib.pyplot as plt %matplotlib inline import numpy as np fig, ax = plt.subplots() fig.set_size_inches(6,6) x, y = zip(*points[:2]) l1, = ax.plot(x,y, color='blue') scatter1 = ax.scatter(x=x,y=y, color='blue', marker='x', s=80, alpha=1.0) x, y = zip(*points[2:]) l2, = ax.plot(x,y, color='red') scatter2 = ax.scatter(x=x,y=y, color='red', marker='x', s=80, alpha=1.0) p1 = np.array(points[0]) p2 = np.array(points[1]) p3 = np.array(points[2]) p1p2 = p2 - p1 p1p3 = p3 - p1 p4 = p1 + p1p2 / np.linalg.norm(p1p2) * np.linalg.norm(p1p3) * ((p1p2/np.linalg.norm(p1p2)).T * (p1p3/np.linalg.norm(p1p3))) p1, p2, p3, p4, p1p2, p1p3

我们可以使用标量积的线性度来缩短

p4

 线:

p4 = p1 + p1p2 / np.linalg.norm(p1p2) * ((p1p2/np.linalg.norm(p1p2)).T * (p1p3)) p4 = p1 + p1p2 / np.linalg.norm(p1p2)**2 * (p1p2.T * (p1p3))
    
© www.soinside.com 2019 - 2024. All rights reserved.