天天看點

PIP多邊形與點位置關系判斷:射線法純手撸代碼解決PIP問題

算法:PIP 多邊形與點位置關系

  • 純手撸代碼解決PIP問題
    • 面向對象
    • 射線法解決PIP問題
    • 結果

純手撸代碼解決PIP問題

程式設計語言為Python

面向對象

建立一個geometry類,其中包括點、線、多邊形對象。

class Geometry:

    def __init__(self, name):
        self.__name = name

    def get_name(self):
        return self.__name


class Point(Geometry):
    # contains X/Y

    def __init__(self, name, x, y):
        super().__init__(name)
        self.__x = x
        self.__y = y

    def get_x(self):
        return float(self.__x)

    def get_y(self):
        return float(self.__y)


class Line(Geometry):
    # contains 2 points

    def __init__(self, name, p1: Point, p2: Point):
        super().__init__(name)
        if not isinstance(p1, Point) or not isinstance(p2, Point):
            raise ValueError("point must be an object of " + str(type(Point)))
        self.__p1 = p1
        self.__p2 = p2
        self.__len = ((p1.get_x() - p2.get_x()) ** 2 + (p1.get_y() - p2.get_y()) ** 2) ** 0.5

    def get_length(self):
        return self.__len

    def get_points(self):
        return self.__p1, self.__p2


class Polygon(Geometry):
    # contains many points

    def __init__(self, name, points):
        super().__init__(name)
        self.__points = points

    def get_points(self):
        return self.__points

    def get_lines(self):
        line_set = []
        points = self.__points
        line_length = len(points)
        for i in range(line_length - 1):
            line_set.append(Line(points[i].get_name() + "-" + points[i + 1].get_name(), points[i], points[i + 1]))
        line_set.append(Line(points[-1].get_name() + "-" + points[0].get_name(), points[-1], points[0]))
        return line_set
           

射線法解決PIP問題

射線法理論不再贅述,感興趣可以自行百度,在這裡需要讨論幾個特殊情況:

PIP多邊形與點位置關系判斷:射線法純手撸代碼解決PIP問題

a、b 兩種情況下都不算射線穿越多邊形矩陣邊界,而 c、d 兩種情況下需要記錄一次穿越。

def is_ray_cross(point: Point, line: Line, last_line_position):
    start_point, end_point = line.get_points()  # define direct for each line
    if point.get_y() > max(start_point.get_y(), end_point.get_y()):
        return False, 0
    elif point.get_y() < min(start_point.get_y(), end_point.get_y()):
        return False, 0
    elif point.get_x() > max(start_point.get_x(), end_point.get_x()):  # the ray is on the right of the line
        return False, 0
    elif point.get_y() == start_point.get_y() and (point.get_y() - end_point.get_y()) * last_line_position >= 0:
        if start_point.get_y() == end_point.get_y():
            return False, last_line_position
        else:
            return False, 0
    elif point.get_y() == end_point.get_y():  # exclude the situation of overlaps and intersects at the end point
        if point.get_y() - start_point.get_y() < 0:  # use mark to handle the situation that ray passes through a vertex
            mark = -1
        else:
            mark = 1
        return False, mark

    intersect_x = end_point.get_x() - (end_point.get_x() - start_point.get_x()) * (
                end_point.get_y() - point.get_y()) / (end_point.get_y() - start_point.get_y())
    if intersect_x < point.get_x():  # the intersect is at the left of the point
        return False, 0
    return True, 0
           

射線法的核心思想為:從待判斷點引出一條射線,該射線與多邊形相交的次數(奇/偶)決定了點與多邊形的位置關系。

# implement RCA (method, need to be transfer into oop)
def rca(polygon: Polygon, *test_points):
    res = []
    lines = polygon.get_lines()
    is_boundary = False
    for point in test_points:
        count = 0
        mark = 0
        start_point, end_point = lines[-1].get_points()
        if end_point.get_y() == point.get_y():  # use mark to handle the situation that ray passes through a vertex
            if point.get_y() - start_point.get_y() < 0:
                mark = -1
            else:
                mark = 1
        for line in lines:
            if is_on_boundary(point, line):
                res.append('boundary')
                is_boundary = True
                break
            is_cross, mark = is_ray_cross(point,line,mark)
            if is_cross:
                count += 1
        if is_boundary:
            is_boundary = False
        else:
            if count % 2 == 1:
                res.append('inside')
            else:
                res.append('outside')
    return res
           

結果

PIP多邊形與點位置關系判斷:射線法純手撸代碼解決PIP問題