Python Tkinter Canvas получает кардинальное направление линии

У меня есть две точки на холсте 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")

Ни один из них не работает. Результат кажется недостаточно последовательным для использования. Есть ли лучший способ сделать это?


person HeroHFM    schedule 16.09.2017    source источник


Ответы (3)


Вот мой предлагаемый подход для определения ближайшего направления компаса к тому, на которое указывает отрезок линии [A, B], определяемый его конечными точками point_a и point_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))

Тесты:

(с использованием стандартных квадрантов тригонометрического круга)

Квадрант I:

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
person Reblochon Masque    schedule 17.09.2017
comment
Ваше решение работает отлично! За исключением того, что вы перепутали W и E. Это не имеет значения. Благодарю вас! - person HeroHFM; 17.09.2017
comment
Замечательно, я рад, что смог помочь. Я исправил ошибку W<->E, спасибо, что указали на нее. - person Reblochon Masque; 18.09.2017

Я надеюсь, что это поможет - я реализовал это для (своего) удобства, используя черепаху Python, которая построена на tkinter. Я переключил черепаху в режим logo, в котором север равен 0 градусов, а положительные углы по часовой стрелке (т. е. восток равен 90 градусам), как компас. Метод черепахи 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()

Программа рисует случайную линию (с очевидной начальной точкой и направлением) и вычисляет кардинальное направление, которое можно найти в заголовке окна. Щелчок по окну генерирует новую строку и расчет.

Вы должны иметь возможность работать с 8 или 32 точками компаса, редактируя переменную DIRECTIONS.

введите здесь описание изображения

person cdlane    schedule 17.09.2017

Чтобы получить кардинальное направление, необходим словарь с углами (в данном случае градусами), относящимися к соответствующему направлению:

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

Обратите внимание, что север добавляется дважды, так как угол, полученный в 350 градусов между двумя точками, будет давать северо-запад, когда он должен давать север.

Пусть две точки на холсте Tkinter, a и b, имеют координаты (x1, y1) и (x2, y2) соответственно. Таким образом, разница между ними (dx и dy) составляет x1-x2 и y1-y2.

Теперь вы можете выполнить арктангенс dy/dx, чтобы получить угол. Стоит отметить, что если dx равно 0, то оно будет делиться на 0. Вы можете предотвратить это, добавив if not dx: return "N", возвращая север, если точки имеют одинаковое значение x.

Кроме того, если dx больше 0, то он вернет то же самое, как если бы он был меньше 0. Это связано с тем, что график касательной имеет период 180 градусов. Чтобы противодействовать этому, вы можете просто добавить if dx > 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, дает вам ответ.

person SneakyTurtle    schedule 17.09.2017
comment
Это прекрасно работает, за исключением случаев, когда у вас есть линия с изменением только по оси Y. Он вернет N независимо от того, является ли направление северным или южным, из-за - если не dx: вернуть N. - person HeroHFM; 17.09.2017