Python Tkinter Canvas 获取线的基本方向
Python Tkinter Canvas get line cardinal direction
我在 tkinter 上有两点 canvas。我需要一个函数来确定在它们之间绘制的线最接近哪个基本方向(N、NW、W SW、S 等)(方向很重要)?我该怎么做呢?请注意,在 canvas 中,左上角是 (0,0).
我试过:
def dot_product(self, v, w):
return v[0]*w[0]+v[1]*w[1]
def inner_angle(self, v, w):
cosx=self.dot_product(v,w)/(sqrt(v[0]**2+v[1]**2)*sqrt(w[0]**2+w[1]**2))
rad=acos(cosx)
return rad*180/pi
def getAngle(self, A, B):
inner=self.inner_angle(A,B)
det = A[0]*B[1]-A[1]*B[0]
if det<0:
return inner
else:
return 360-inner
和:
def getBearing(self, pointA, pointB):
if (type(pointA) != tuple) or (type(pointB) != tuple):
raise TypeError("Only tuples are supported as arguments")
lat1 = math.radians(pointA[0])
lat2 = math.radians(pointB[0])
diffLong = math.radians(pointB[1] - pointA[1])
x = math.sin(diffLong) * math.cos(lat2)
y = math.cos(lat1) * math.sin(lat2) - (math.sin(lat1) * math.cos(lat2) * math.cos(diffLong))
initial_bearing = math.atan2(x, y)
initial_bearing = math.degrees(initial_bearing)
compass_bearing = (initial_bearing + 360) % 360
return compass_bearing
(我是用这个函数来获取方向的(代码不完整,更多的是例子))
def findDirection(self, p1, p2):
bearing = self.getBearing(p1, p2) # OR getAngle()
print(bearing)
index = [180, 0]
closest = min(index, key=lambda x:abs(x-bearing))
if closest == 10:
print(str(bearing) + " : UP")
elif closest == 360:
print(str(bearing) + " : DOWN")
elif closest == 0:
print(str(bearing) + " : RIGHT")
elif closest == 180:
print(str(bearing) + " : LEFT")
None 这些工作。结果似乎不够一致,无法使用。
有更好的方法吗?
我希望这对您有所帮助——为了(我的)方便,我使用基于 tkinter 的 Python turtle 实现了它。我将 turtle 切换为 logo 模式,使北 0 度和顺时针正角(即东为 90 度)像指南针一样。 turtle 方法 towards()
完成了大部分你想要的,所以我在计算基本方向时尝试模仿它:
from random import randrange
from turtle import Turtle, Screen
from math import pi, atan2, degrees
DIRECTIONS = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
BUCKET = 360.0 / len(DIRECTIONS)
X, Y = 0, 1
SIZE = 500
def onclick_handler(x, y):
# Draw random vector
yertle.reset()
yertle.hideturtle()
yertle.penup()
start = (randrange(-SIZE//2, SIZE//2), randrange(-SIZE//2, SIZE//2))
end = (randrange(-SIZE//2, SIZE//2), randrange(-SIZE//2, SIZE//2))
yertle.goto(start)
yertle.dot()
yertle.showturtle()
yertle.pendown()
yertle.setheading(yertle.towards(end))
yertle.goto(end)
# Compute vector direction
x, y = end[X] - start[X], end[Y] - start[Y]
angle = round(degrees(atan2(y, -x) - pi / 2), 10) % 360.0
direction = DIRECTIONS[round(angle / BUCKET) % len(DIRECTIONS)]
screen.title("{} degress is {}".format(round(angle, 2), direction))
yertle = Turtle()
screen = Screen()
screen.mode('logo')
screen.setup(SIZE, SIZE)
screen.onclick(onclick_handler)
onclick_handler(0, 0)
screen.mainloop()
程序随机绘制一条线(起点和方向明显)并计算出基本方向,可在window标题中找到。单击 window 生成一个新行和计算。
您应该可以通过编辑 DIRECTIONS
变量来处理 8 或 32 个罗盘点。
这是我提出的确定最接近由其端点 point_a
和 point_b
定义的线段 [A, B]
指向的罗盘方向的方法:
- 所有计算均在标准笛卡尔坐标系中完成,
最后更改为屏幕坐标。这简化了
方法,并使代码可在其他地方重复使用。
- 先把原点改成
point_a
- 第二个计算线段与x_axis
的夹角
- 确定最近的方位角(在标准笛卡尔坐标系中)
- 将标准方位角转换为屏幕坐标方位角(水平翻转)
with points defined in screen coordinates (Y axis down), call get_bearings(point_a, point_b)
If the points defined in standard
cartesian coordinates (Y axis up), call
assign_bearing_to_compass(point_a, point_b)
(The tests underneath the code show the results of using points in standard coordinates, and screen coordinates.)
import math
def _change_origin_of_point_b_to_point_a(point_a, point_b):
# uses standard Y axis orientation, not screen orientation
return (point_b[0] - point_a[0], point_b[1] - point_a[1])
def _calc_angle_segment_a_b_with_x_axis(point_a, point_b):
# uses standard Y axis orientation, not screen orientation
xa, ya = point_a
xb, yb = _change_origin_of_point_b_to_point_a(point_a, point_b)
return math.atan2(yb, xb)
def determine_bearing_in_degrees(point_a, point_b):
"""returns the angle in degrees that line segment [point_a, point_b)]
makes with the horizontal X axis
"""
# uses standard Y axis orientation, not screen orientation
return _calc_angle_segment_a_b_with_x_axis(point_a, point_b) * 180 / math.pi
def assign_bearing_to_compass(point_a, point_b):
"""returns the standard bearing of line segment [point_a, point_b)
"""
# uses standard Y axis orientation, not screen orientation
compass = {'W' : [157.5, -157.5],
'SW': [-157.5, -112.5],
'S' : [-112.5, -67.5],
'SE': [-67.5, -22.5],
'E' : [-22.5, 22.5],
"NE": [22.5, 67.5],
'N' : [67.5, 112.5],
'NW': [112.5, 157.5]}
bear = determine_bearing_in_degrees(point_a, point_b)
for direction, interval in compass.items():
low, high = interval
if bear >= low and bear < high:
return direction
return 'W'
def _convert_to_negative_Y_axis(compass_direction):
"""flips the compass_direction horizontally
"""
compass_conversion = {'E' : 'E',
'SE': 'NE',
'S' : 'N',
'SW': 'NW',
'W' : 'W',
"NW": 'SW',
'N' : 'S',
'NE': 'SE'}
return compass_conversion[compass_direction]
def get_bearings(point_a, point_b):
return _convert_to_negative_Y_axis(assign_bearing_to_compass(point_a, point_b))
测试:
(使用标准三角圆象限)
第一象限:
point_a = (0, 0)
points_b = [(1, 0), (1, 3), (1, 2), (1, 1), (2, 1), (3, 1), (0, 1)]
print("point_a, point_b Y_up Y_down (in screen coordinates)")
for point_b in points_b:
print(point_a, ' ', point_b, ' ', assign_bearing_to_compass(point_a, point_b), ' ', get_bearings(point_a, point_b))
结果:
point_a, point_b Y_up Y_down (in screen coordinates)
(0, 0) (1, 0) E E
(0, 0) (1, 3) N S
(0, 0) (1, 2) NE SE
(0, 0) (1, 1) NE SE
(0, 0) (2, 1) NE SE
(0, 0) (3, 1) E E
(0, 0) (0, 1) N S
象限 II:
point_a = (0, 0)
points_b = [(-1, 0), (-1, 3), (-1, 2), (-1, 1), (-2, 1), (-3, 1), (0, 1)]
print("point_a, point_b Y_up Y_down (in screen coordinates)")
for point_b in points_b:
print(point_a, ' ', point_b, ' ', assign_bearing_to_compass(point_a, point_b), ' ', get_bearings(point_a, point_b))
结果:
point_a, point_b Y_up Y_down (in screen coordinates)
(0, 0) (-1, 0) W W
(0, 0) (-1, 3) N S
(0, 0) (-1, 2) NW SW
(0, 0) (-1, 1) NW SW
(0, 0) (-2, 1) NW SW
(0, 0) (-3, 1) W W
(0, 0) (0, 1) N S
象限 III:
point_a = (0, 0)
points_b = [(-1, 0), (-1, -3), (-1, -2), (-1, -1), (-2, -1), (-3, -1), (0, -1)]
print("point_a, point_b Y_up Y_down (in screen coordinates)")
for point_b in points_b:
print(point_a, ' ', point_b, ' ', assign_bearing_to_compass(point_a, point_b), ' ', get_bearings(point_a, point_b))
结果:
point_a, point_b Y_up Y_down (in screen coordinates)
(0, 0) (-1, 0) W W
(0, 0) (-1, -3) S N
(0, 0) (-1, -2) SW NW
(0, 0) (-1, -1) SW NW
(0, 0) (-2, -1) SW NW
(0, 0) (-3, -1) W W
(0, 0) (0, -1) S N
象限 IV:
point_a = (0, 0)
points_b = [(1, 0), (1, -3), (1, -2), (1, -1), (2, -1), (3, -1), (0, -1)]
print("point_a, point_b Y_up Y_down (in screen coordinates)")
for point_b in points_b:
print(point_a, ' ', point_b, ' ', assign_bearing_to_compass(point_a, point_b), ' ', get_bearings(point_a, point_b))
结果:
point_a, point_b Y_up Y_down (in screen coordinates)
(0, 0) (1, 0) E E
(0, 0) (1, -3) S N
(0, 0) (1, -2) SE NE
(0, 0) (1, -1) SE NE
(0, 0) (2, -1) SE NE
(0, 0) (3, -1) E E
(0, 0) (0, -1) S N
要获得基本方向,需要一个包含指向相关方向的角度(在本例中为度数)的字典:
directions = {0:"N", 45:"NE", 90:"E", 135:"SE", 180:"S",
225:"SW", 270:"W", 315:"NW", 360:"N"}
请注意,北方被添加了两次,因为在两点之间获得的 350 度角将是西北,而当它应该是北方时。
让Tkinter上的两个点canvas、a
和b
的坐标分别为(x1, y1)
和(x2, y2)
。因此,它们之间的差异(dx
和 dy
)是 x1-x2
和 y1-y2
。
您现在可以执行 dy/dx
的反正切来获得角度。值得指出的是,如果 dx
为 0,则除以 0。如果点具有相同的 x 值,则可以通过添加 if not dx: return "N"
、returning North 来防止这种情况。
此外,如果 dx 大于 0,那么它将 return 与小于 0 时相同。这是因为切线图的周期为 180 度。为了解决这个问题,您可以添加 if dx > 0: angle += 180
.
现在你有了一个角度,你可以在之前定义的 directions
字典中引用它,使用 Python 内置的 min
函数:min(self.directions, key=lambda x: abs(x-angle))
。这 return 是字典中指定的最接近的度数。要获得基数值,我们可以在字典中访问它。
把这些放在一起得到以下函数 (TLDR):
from math import atan, degrees
...
def get_cardinal(a, b):
dx, dy = a[0]-b[0], a[1]-b[1]
if not dx:
return "N"
angle = degrees(atan(dy/dx))+90 #+90 to take into account TKinters coordinate system.
if dx > 0:
angle += 180
return directions[min(directions, key=lambda x: abs(x-angle))]
这与 directions
词典相结合可以为您提供答案。
我在 tkinter 上有两点 canvas。我需要一个函数来确定在它们之间绘制的线最接近哪个基本方向(N、NW、W SW、S 等)(方向很重要)?我该怎么做呢?请注意,在 canvas 中,左上角是 (0,0).
我试过:
def dot_product(self, v, w):
return v[0]*w[0]+v[1]*w[1]
def inner_angle(self, v, w):
cosx=self.dot_product(v,w)/(sqrt(v[0]**2+v[1]**2)*sqrt(w[0]**2+w[1]**2))
rad=acos(cosx)
return rad*180/pi
def getAngle(self, A, B):
inner=self.inner_angle(A,B)
det = A[0]*B[1]-A[1]*B[0]
if det<0:
return inner
else:
return 360-inner
和:
def getBearing(self, pointA, pointB):
if (type(pointA) != tuple) or (type(pointB) != tuple):
raise TypeError("Only tuples are supported as arguments")
lat1 = math.radians(pointA[0])
lat2 = math.radians(pointB[0])
diffLong = math.radians(pointB[1] - pointA[1])
x = math.sin(diffLong) * math.cos(lat2)
y = math.cos(lat1) * math.sin(lat2) - (math.sin(lat1) * math.cos(lat2) * math.cos(diffLong))
initial_bearing = math.atan2(x, y)
initial_bearing = math.degrees(initial_bearing)
compass_bearing = (initial_bearing + 360) % 360
return compass_bearing
(我是用这个函数来获取方向的(代码不完整,更多的是例子))
def findDirection(self, p1, p2):
bearing = self.getBearing(p1, p2) # OR getAngle()
print(bearing)
index = [180, 0]
closest = min(index, key=lambda x:abs(x-bearing))
if closest == 10:
print(str(bearing) + " : UP")
elif closest == 360:
print(str(bearing) + " : DOWN")
elif closest == 0:
print(str(bearing) + " : RIGHT")
elif closest == 180:
print(str(bearing) + " : LEFT")
None 这些工作。结果似乎不够一致,无法使用。 有更好的方法吗?
我希望这对您有所帮助——为了(我的)方便,我使用基于 tkinter 的 Python turtle 实现了它。我将 turtle 切换为 logo 模式,使北 0 度和顺时针正角(即东为 90 度)像指南针一样。 turtle 方法 towards()
完成了大部分你想要的,所以我在计算基本方向时尝试模仿它:
from random import randrange
from turtle import Turtle, Screen
from math import pi, atan2, degrees
DIRECTIONS = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
BUCKET = 360.0 / len(DIRECTIONS)
X, Y = 0, 1
SIZE = 500
def onclick_handler(x, y):
# Draw random vector
yertle.reset()
yertle.hideturtle()
yertle.penup()
start = (randrange(-SIZE//2, SIZE//2), randrange(-SIZE//2, SIZE//2))
end = (randrange(-SIZE//2, SIZE//2), randrange(-SIZE//2, SIZE//2))
yertle.goto(start)
yertle.dot()
yertle.showturtle()
yertle.pendown()
yertle.setheading(yertle.towards(end))
yertle.goto(end)
# Compute vector direction
x, y = end[X] - start[X], end[Y] - start[Y]
angle = round(degrees(atan2(y, -x) - pi / 2), 10) % 360.0
direction = DIRECTIONS[round(angle / BUCKET) % len(DIRECTIONS)]
screen.title("{} degress is {}".format(round(angle, 2), direction))
yertle = Turtle()
screen = Screen()
screen.mode('logo')
screen.setup(SIZE, SIZE)
screen.onclick(onclick_handler)
onclick_handler(0, 0)
screen.mainloop()
程序随机绘制一条线(起点和方向明显)并计算出基本方向,可在window标题中找到。单击 window 生成一个新行和计算。
您应该可以通过编辑 DIRECTIONS
变量来处理 8 或 32 个罗盘点。
这是我提出的确定最接近由其端点 point_a
和 point_b
定义的线段 [A, B]
指向的罗盘方向的方法:
- 所有计算均在标准笛卡尔坐标系中完成, 最后更改为屏幕坐标。这简化了 方法,并使代码可在其他地方重复使用。
- 先把原点改成
point_a
- 第二个计算线段与x_axis 的夹角
- 确定最近的方位角(在标准笛卡尔坐标系中)
- 将标准方位角转换为屏幕坐标方位角(水平翻转)
with points defined in screen coordinates (Y axis down), call
get_bearings(point_a, point_b)
If the points defined in standard cartesian coordinates (Y axis up), callassign_bearing_to_compass(point_a, point_b)
(The tests underneath the code show the results of using points in standard coordinates, and screen coordinates.)
import math
def _change_origin_of_point_b_to_point_a(point_a, point_b):
# uses standard Y axis orientation, not screen orientation
return (point_b[0] - point_a[0], point_b[1] - point_a[1])
def _calc_angle_segment_a_b_with_x_axis(point_a, point_b):
# uses standard Y axis orientation, not screen orientation
xa, ya = point_a
xb, yb = _change_origin_of_point_b_to_point_a(point_a, point_b)
return math.atan2(yb, xb)
def determine_bearing_in_degrees(point_a, point_b):
"""returns the angle in degrees that line segment [point_a, point_b)]
makes with the horizontal X axis
"""
# uses standard Y axis orientation, not screen orientation
return _calc_angle_segment_a_b_with_x_axis(point_a, point_b) * 180 / math.pi
def assign_bearing_to_compass(point_a, point_b):
"""returns the standard bearing of line segment [point_a, point_b)
"""
# uses standard Y axis orientation, not screen orientation
compass = {'W' : [157.5, -157.5],
'SW': [-157.5, -112.5],
'S' : [-112.5, -67.5],
'SE': [-67.5, -22.5],
'E' : [-22.5, 22.5],
"NE": [22.5, 67.5],
'N' : [67.5, 112.5],
'NW': [112.5, 157.5]}
bear = determine_bearing_in_degrees(point_a, point_b)
for direction, interval in compass.items():
low, high = interval
if bear >= low and bear < high:
return direction
return 'W'
def _convert_to_negative_Y_axis(compass_direction):
"""flips the compass_direction horizontally
"""
compass_conversion = {'E' : 'E',
'SE': 'NE',
'S' : 'N',
'SW': 'NW',
'W' : 'W',
"NW": 'SW',
'N' : 'S',
'NE': 'SE'}
return compass_conversion[compass_direction]
def get_bearings(point_a, point_b):
return _convert_to_negative_Y_axis(assign_bearing_to_compass(point_a, point_b))
测试:
(使用标准三角圆象限)
第一象限:
point_a = (0, 0)
points_b = [(1, 0), (1, 3), (1, 2), (1, 1), (2, 1), (3, 1), (0, 1)]
print("point_a, point_b Y_up Y_down (in screen coordinates)")
for point_b in points_b:
print(point_a, ' ', point_b, ' ', assign_bearing_to_compass(point_a, point_b), ' ', get_bearings(point_a, point_b))
结果:
point_a, point_b Y_up Y_down (in screen coordinates)
(0, 0) (1, 0) E E
(0, 0) (1, 3) N S
(0, 0) (1, 2) NE SE
(0, 0) (1, 1) NE SE
(0, 0) (2, 1) NE SE
(0, 0) (3, 1) E E
(0, 0) (0, 1) N S
象限 II:
point_a = (0, 0)
points_b = [(-1, 0), (-1, 3), (-1, 2), (-1, 1), (-2, 1), (-3, 1), (0, 1)]
print("point_a, point_b Y_up Y_down (in screen coordinates)")
for point_b in points_b:
print(point_a, ' ', point_b, ' ', assign_bearing_to_compass(point_a, point_b), ' ', get_bearings(point_a, point_b))
结果:
point_a, point_b Y_up Y_down (in screen coordinates)
(0, 0) (-1, 0) W W
(0, 0) (-1, 3) N S
(0, 0) (-1, 2) NW SW
(0, 0) (-1, 1) NW SW
(0, 0) (-2, 1) NW SW
(0, 0) (-3, 1) W W
(0, 0) (0, 1) N S
象限 III:
point_a = (0, 0)
points_b = [(-1, 0), (-1, -3), (-1, -2), (-1, -1), (-2, -1), (-3, -1), (0, -1)]
print("point_a, point_b Y_up Y_down (in screen coordinates)")
for point_b in points_b:
print(point_a, ' ', point_b, ' ', assign_bearing_to_compass(point_a, point_b), ' ', get_bearings(point_a, point_b))
结果:
point_a, point_b Y_up Y_down (in screen coordinates)
(0, 0) (-1, 0) W W
(0, 0) (-1, -3) S N
(0, 0) (-1, -2) SW NW
(0, 0) (-1, -1) SW NW
(0, 0) (-2, -1) SW NW
(0, 0) (-3, -1) W W
(0, 0) (0, -1) S N
象限 IV:
point_a = (0, 0)
points_b = [(1, 0), (1, -3), (1, -2), (1, -1), (2, -1), (3, -1), (0, -1)]
print("point_a, point_b Y_up Y_down (in screen coordinates)")
for point_b in points_b:
print(point_a, ' ', point_b, ' ', assign_bearing_to_compass(point_a, point_b), ' ', get_bearings(point_a, point_b))
结果:
point_a, point_b Y_up Y_down (in screen coordinates)
(0, 0) (1, 0) E E
(0, 0) (1, -3) S N
(0, 0) (1, -2) SE NE
(0, 0) (1, -1) SE NE
(0, 0) (2, -1) SE NE
(0, 0) (3, -1) E E
(0, 0) (0, -1) S N
要获得基本方向,需要一个包含指向相关方向的角度(在本例中为度数)的字典:
directions = {0:"N", 45:"NE", 90:"E", 135:"SE", 180:"S",
225:"SW", 270:"W", 315:"NW", 360:"N"}
请注意,北方被添加了两次,因为在两点之间获得的 350 度角将是西北,而当它应该是北方时。
让Tkinter上的两个点canvas、a
和b
的坐标分别为(x1, y1)
和(x2, y2)
。因此,它们之间的差异(dx
和 dy
)是 x1-x2
和 y1-y2
。
您现在可以执行 dy/dx
的反正切来获得角度。值得指出的是,如果 dx
为 0,则除以 0。如果点具有相同的 x 值,则可以通过添加 if not dx: return "N"
、returning North 来防止这种情况。
此外,如果 dx 大于 0,那么它将 return 与小于 0 时相同。这是因为切线图的周期为 180 度。为了解决这个问题,您可以添加 if dx > 0: angle += 180
.
现在你有了一个角度,你可以在之前定义的 directions
字典中引用它,使用 Python 内置的 min
函数:min(self.directions, key=lambda x: abs(x-angle))
。这 return 是字典中指定的最接近的度数。要获得基数值,我们可以在字典中访问它。
把这些放在一起得到以下函数 (TLDR):
from math import atan, degrees
...
def get_cardinal(a, b):
dx, dy = a[0]-b[0], a[1]-b[1]
if not dx:
return "N"
angle = degrees(atan(dy/dx))+90 #+90 to take into account TKinters coordinate system.
if dx > 0:
angle += 180
return directions[min(directions, key=lambda x: abs(x-angle))]
这与 directions
词典相结合可以为您提供答案。