我有两个点 A (10,20) 和 B (15,30)。这些点生成一条线 AB。我需要在Python中在B点上画一条长度为6(每个方向3个单位)的垂线CD。
我已经使用以下代码获得了 AB 线的一些属性:
from scipy import stats
x = [10,15]
y = [20,30]
slope, intercept, r_value, p_value, std_err = stats.linregress(x,y)
C 和 D 的值将用于使用 Shapely 库实现另一个目标。
既然你对使用 Shapely 感兴趣,我能想到的获得垂直线的最简单方法是使用
parallel_offset
方法获得两条平行线到 AB,并连接它们的端点:
from shapely.geometry import LineString
a = (10, 20)
b = (15, 30)
cd_length = 6
ab = LineString([a, b])
left = ab.parallel_offset(cd_length / 2, 'left')
right = ab.parallel_offset(cd_length / 2, 'right')
c = left.boundary[1]
d = right.boundary[0] # note the different orientation for right offset
cd = LineString([c, d])
以及CD的坐标:
>>> c.x, c.y
(12.316718427000252, 31.341640786499873)
>>> d.x, d.y
(17.683281572999746, 28.658359213500127)
如果AB的斜率是
slope
,那么CD的斜率就是-1/slope
。这等于垂直变化对水平变化的影响:dy/dx = -1/slope
。这给出了dx = -slope*dx
。根据毕达哥拉斯定理,你有 3**2 = dy**2+dx**2
。替换 dx
,你得到
3**2 = (-slope*dy)**2+dy**2
3**2 = (slope**2 + 1)*dy**2
dy**2 = 3**2/(slope**2+1)
dy = math.sqrt(3**2/(slope**2+1))
然后你就可以得到
dx = -slope*dy
。最后,你可以使用 dx
和 dy
来得到 C 和 D。所以代码是:
import math
dy = math.sqrt(3**2/(slope**2+1))
dx = -slope*dy
C[0] = B[0] + dx
C[1] = B[1] + dy
D[0] = B[0] - dx
D[1] = B[1] - dy
(注意虽然
math.sqrt
只返回一个数字,但一般来说是有正负平方根的。C对应正平方根,D对应负平方根)。
您可能应该使用向量来计算点的位置。
vector AB
normalized perpendicular
B
借助简单、可重复使用的
Vector class
,计算变得微不足道,读起来像英语:
找到与点
AB
距离 3
垂直于 B
的点:P1 = B + (B-A).perp().normalized() * 3
P2 = B + (B-A).perp().normalized() * 3
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def dot(self, other):
return self.x * other.x + self.y * other.y
def norm(self):
return self.dot(self)**0.5
def normalized(self):
norm = self.norm()
return Vector(self.x / norm, self.y / norm)
def perp(self):
return Vector(1, -self.x / self.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
def __str__(self):
return f'({self.x}, {self.y})'
A = Vector(10, 20)
B = Vector(15, 30)
AB = B - A
AB_perp_normed = AB.perp().normalized()
P1 = B + AB_perp_normed * 3
P2 = B - AB_perp_normed * 3
print(f'Point{P1}, and Point{P2}')
输出:
Point(17.683281572999746, 28.658359213500127), and Point(12.316718427000252, 31.341640786499873)
如果我们有一条平行于x轴或y轴的线,那么我们可以将以下两个方法添加到Vector类中。
def perptoy(self):
return Vector(1, 0)
def perptox(self):
return Vector(1, -10000) // any big number will do instead of -inf
AB = B - A
if (A.y == B.y):
AB_perp_normed = AB.perptox().normalized();
elif (A.x == B.x):
AB_perp_normed = AB.perptoy().normalized();
else:
AB_perp_normed = AB.perp().normalized();
其余部分与之前的帖子相同。
我的版本如下
import math
def get_perp_coords(left_x, left_y, right_x, right_y, length, divider):
x_length = right_x - left_x
y_length = right_y - left_y
mag = math.sqrt(x_length**2 + y_length**2)
x_length_norm = x_length / mag
y_length_norm = y_length / mag
# middle point
middle_x = round((left_x + right_x) / 2)
middle_y = round((left_y + right_y) / 2)
# switch
temp = x_length_norm
x_length_norm = -1 * y_length_norm
y_length_norm = temp
# get coords
ratio = 1 / divider
new_x_1 = middle_x + x_length_norm * length * ratio
new_y_1 = middle_y + y_length_norm * length * ratio
new_x_2 = middle_x - x_length_norm * length * (1 - ratio)
new_y_2 = middle_y - y_length_norm * length * (1 - ratio)
# if coords goes beyond the original coordinate system
if new_x_1 < 0 or new_y_1 < 0 or new_x_2 < 0 or new_y_2 < 0:
ratio = 1 - ratio
new_x_1 = middle_x + x_length_norm * length * ratio
new_y_1 = middle_y + y_length_norm * length * ratio
new_x_2 = middle_x - x_length_norm * length * (1 - ratio)
new_y_2 = middle_y - y_length_norm * length * (1 - ratio)
return int(new_x_1), int(new_y_1), int(new_x_2), int(new_y_2)
length
是设置线的长度divider
是设置垂直线上下部分的比例,防止垂直线越过正值坐标系