Как узнать, находится ли точка по периметру QPolygon (PyQt4)

Есть ли в PyQt4 какая-либо функция, помогающая мне определить, находится ли точка по периметру QPolygon? Например:

from PyQt4 import QtGui
from PyQt4.QtCore import Qt, QPoint as QP

polygon = QtGui.QPolygon([QP(0, 1),  QP(3,7), QP(4, 6), QP(4,3), QP(2,1), QP(0,1])

Функция должна вернуть true, если я передам ей QP(1,3), QP(4,5), QP(3,2) или QP(1,1).

1 ответ

Это действительно сложный вопрос. Я много играл с несколькими методами QPolygon лайк intersected, united, subtracted но никто не принес успеха.

Например, я подумал, что это может сработать: Скопируйте многоугольник, добавьте точку к копии, а затем проверьте, является ли результат пустым. Если точка лежит по периметру многоугольника, то и оригинал, и копия должны иметь одинаковую форму и, следовательно, результат должен быть пустым.

def on_perimeter(poly, point):
    poly2 = poly + QtGui.QPolygon()  # copy the polygon
    poly2.add(point)
    return poly.subtracted(poly2).isEmpty()

Однако, кажется, что многоугольник "нарисован" в том порядке, в котором указаны точки, поэтому, если вы просто добавите точку, это приведет к некоторой другой форме. Например рассмотрим точки (0,0) (0,2) (2.2) (2,0) которые образуют квадрат, и вы хотите проверить (0,1), Тогда, если вы просто добавите точку в конце, это "соединится" (2,0) с (0,1) а также (0,1) с (0,0) так как многоугольник должен быть замкнутой формы. Это дает другую форму. Таким образом, вам придется вставить точку в правильном положении, чтобы получить ту же форму. Для этого примера это будет сразу после (0,0), Итак, я подумал, хорошо, давайте попробуем описанное выше со всеми возможными перестановками, и будет только одна конфигурация (и ее преобразования, являющиеся результатом вращения и инверсии), такая, что результат вычитания будет пустым.

import itertools

def on_perimeter(poly, point):
    points = [point]  # the points of the new polygon
    for ii in range(0, poly.size()):
        points += [poly.point(ii)]

    permuts = list(itertools.permutations(points))  # all possible permutations

    checks = 0
    for permut in permuts:
        checks += int(poly.subtracted(QtGui.QPolygon(list(permut))).isEmpty())

    return checks

Но почему-то это тоже не работает. Испытывая свой пример, я получаю за QP(4,5) а также QP(3,2) значение checks = 10 за QP(1,1)checks = 20 и для QP(1,3)checks = 0, То, что я ожидал, это получить checks = 12 для всех точек (так как все они лежат по периметру). 12 так как poly2 сделан из 6 точки, чтобы вы могли вращать точки 6 раз и сделать то же самое после того, как вы изменили порядок, так что 12 различные конфигурации, содержащиеся в permuts приводя к той же форме. Кроме того, если вычитание выполняется наоборот (то есть QtGui.QPolygon(list(permut)).subtracted(poly).isEmpty()) Я получил True для каждой точки также для точек, даже не лежащих внутри многоугольника, но снаружи.

Я пробовал похожие вещи, используя united а также intersected вместо isEmpty в вышеуказанной функции:

tmp = QtGui.QPolygon(list(permut))
checks += int(poly.intersected(tmp) == poly.united(tmp))

То же самое здесь, это следует оценивать только как True если точка фактически лежит по периметру. Но это возвращает False почти для каждого пункта я проверил ваш пример выше.

Я не смотрел на исходный код методов QPolygon (если таковые имеются), но кажется, что происходит что-то странное.

Поэтому я бы предложил вам написать собственный метод, который оценивает все линии в многоугольнике, если точка лежит на одной из них.

def on_perimeter(poly, point):
    lines = []
    for ii in range(1, poly.size()):
        p1 = poly.point(ii-1)
        p2 = poly.point(ii)
        lines += [ ( (p1.x(), p1.y()), (p2.x(), p2.y()) ) ]
    lines += [ ( (poly.last.x(), poly.last.y()), (poly.first.x(), poly.first.y()) ) ]

    for line in lines:
        dx = line[1][0] - line[0][0]
        dy = line[1][1] - line[0][1]

        if abs(dx) > abs(dy) and dx*dy != 0 or dx == 0 and dy == 0:  # abs(slope) < 1 and != 0 thus no point with integer coordinates can lie on this line
            continue

        if dx == 0:
            if point.x() == line[0][0] and (point.y()-line[[0][1])*abs(dy)/dy > 0 and (line[1][1]-point.y())*abs(dy)/dy > 0:
                return True

        if dy == 0:
            if point.y() == line[0][1] and (point.x()-line[[0][0])*abs(dx)/dx > 0 and (line[1][0]-point.x())*abs(dx)/dx > 0:
                return True

        dx2 = point.x() - line[0][0]
        dy2 = point.y() - line[0][1]

        if dx*dx2 < 0 or dy*dy2 < 0:
            continue

        if abs(dx) % abs(dx2) == 0 and abs(dy) % abs(dy2) == 0:
            return True

    return False

Это кажется немного тяжелым, но важно выполнять все вычисления только с целыми числами, поскольку вы можете получить неправильные результаты из-за точности с плавающей запятой (QPolygon в любом случае принимает целочисленные баллы). Хотя еще не проверено, все должно работать.

Другие вопросы по тегам