【问题标题】:Python Tkinter Canvas get line cardinal directionPython Tkinter Canvas 获取线基方向
【发布时间】:2017-09-16 22:09:35
【问题描述】:

我在 tkinter 画布上有两个点。我需要一个函数来确定它们之间绘制的线最接近(N、NW、W SW、S 等)的基本方向(方向很重要)?我该怎么做呢?请注意,在画布中,左上角为 (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")

这些都不起作用。结果似乎不够一致,无法使用。 有没有更好的方法?

【问题讨论】:

    标签: python tkinter canvas line tkinter-canvas


    【解决方案1】:

    这是我提出的确定最接近指南针方向的方法,该指南针方向由线段 [A, B] 所指向的方向由其端点 point_apoint_b 定义:

    1. 所有计算均在标准笛卡尔坐标中完成, 最后完成对屏幕坐标的更改。这简化了 方法,并使代码可在其他地方重用。
    2. 先把原点改成point_a
    3. 第二次计算线段与x_axis的夹角
    4. 确定最近的方位(在标准笛卡尔坐标中)
    5. 将标准方位转换为屏幕坐标方位(水平翻转)

    在屏幕坐标中定义点(Y 轴向下),调用get_bearings(point_a, point_b)
    如果标准中定义的点 笛卡尔坐标(Y轴向上),调用 assign_bearing_to_compass(point_a, point_b)
    (代码下方的测试显示了使用标准坐标和屏幕坐标中的点的结果。)


    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
    

    象限二:

    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
    

    第四象限:

    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
    

    【讨论】:

    • 您的解决方案完美运行!除了你把“W”和“E”混在一起的事实。不过那没关系。谢谢!
    • 太好了,我很高兴能帮上忙。我更正了W&lt;-&gt;E 事故,感谢您指出。
    【解决方案2】:

    我希望这对您有所帮助——为了(我的)方便,我使用基于 tkinter 构建的 Python 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()
    

    程序绘制一条随机线(起点和方向明显)并计算可以在窗口标题中找到的基本方向。单击窗口会生成一个新行和计算。

    您应该能够通过编辑 DIRECTIONS 变量来使用 8 或 32 个罗盘点。

    【讨论】:

      【解决方案3】:

      要获得基本方向,需要一个带有角度(在本例中为度数)引用相关方向的字典:

      directions = {0:"N", 45:"NE", 90:"E", 135:"SE", 180:"S",
                    225:"SW", 270:"W", 315:"NW", 360:"N"}
      

      请注意,北被添加了两次,因为在两点之间获得的 350 度角会给出西北,而它应该给出北方。

      让 Tkinter 画布上的两个点 ab分别具有坐标 (x1, y1)(x2, y2)。因此,它们之间的区别(dxdy)是 x1-x2y1-y2

      您现在可以执行dy/dx 的反正切以获得角度。值得指出的是,如果dx 为 0,那么它将被 0 除。您可以通过添加 if not dx: return "N" 来防止这种情况,如果点具有相同的 x 值,则返回 North。

      此外,如果 dx 大于 0,那么它将返回与小于 0 相同的结果。这是因为切线图的周期为 180 度。为了解决这个问题,您只需添加if dx &gt; 0: angle += 180

      现在你有了一个角度,你可以在前面定义的 directions 字典中引用它,使用 Python 内置的 min 函数:min(self.directions, key=lambda x: abs(x-angle))。这将返回字典中指定的最接近的度数。为了获得基值,我们可以在字典中访问它。

      将所有这些放在一起给出以下函数(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 字典给你答案。

      【讨论】:

      • 这很好用,除非你有一条只在 y 轴上发生变化的线。无论方向是北还是南,它都会返回“N”,因为 - 如果不是 dx:返回“N”。
      猜你喜欢
      • 1970-01-01
      • 2021-08-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-02
      • 1970-01-01
      • 2013-07-13
      • 1970-01-01
      相关资源
      最近更新 更多