Python:查找点是否位于多边形的边界上

时间:2013-07-19 15:03:52

标签: python algorithm polygon computational-geometry point

我有一个点-i,我希望创建一个函数来知道这个点是否位于多边形的边界上。

使用:

def point_inside_polygon(x, y, poly):
    """Deciding if a point is inside (True, False otherwise) a polygon,
    where poly is a list of pairs (x,y) containing the coordinates
    of the polygon's vertices. The algorithm is called the 'Ray Casting Method'"""
    n = len(poly)
    inside = False
    p1x, p1y = poly[0]
    for i in range(n):
        p2x, p2y = poly[i % n]
        if y > min(p1y, p2y):
            if y <= max(p1y, p2y):
                if x <= max(p1x, p2x):
                    if p1y != p2y:
                        xinters = (y-p1y) * (p2x-p1x) / (p2y-p1y) + p1x
                    if p1x == p2x or x <= xinters:
                        inside = not inside
        p1x, p1y = p2x, p2y
    return inside

我只知道点是否在多边形内。

poly = [(0,0), (2,0), (2,2), (0,2)]
point_inside_polygon(1,1, poly)
True
point_inside_polygon(0,0, poly)
false
point_inside_polygon(2,0, poly)
False
point_inside_polygon(2,2, poly)
True
point_inside_polygon(0,2, poly)
True

如何编写函数来查找点是否位于多边形的边框上?

5 个答案:

答案 0 :(得分:2)

将问题分解为三个步骤可能会有所帮助:

  1. 编写一个可以确定if a point is on a line segment
  2. 的函数
  3. 计算构成多边形边框的所有线段。
  4. 如果点位于任何线段上,则该点位于边界上。
  5. 这是一些python代码,假设你已经为isPointOnLineSegmentBetweenPoints编写或找到合适的候选人:

    def pointOnPolygon(point, polygonVertices):
        n = len(polygonVertices)
        for i in range(n):
            p1 = polygonVertices[i]
            p2 = polygonVertices[-n+i+1]
            if isPointOnLineSegmentBetweenPoints(point, p1, p2):
                return true
        return false
    

答案 1 :(得分:1)

对于每对相邻顶点A,B:

  1. 构建一个从A到B的向量,称之为p

  2. 现在构建一个从A到测试点的向量X称之为q

  3. 一对向量的点积公式为p.q = | p || q | cosC 其中C是向量之间的角度。

  4. 所以如果p.q / | p || q | == 1然后点AX和AB是共线的。在计算机上工作,你需要1 - p.q / | p || q | &LT; some_small_value取决于你想要的准确度。

  5. 还需要检查| q | &LT; | P | (即X比B更接近A)

  6. 如果4&amp; 5为真,你的观点就在边界上。

    修改

    我认为我已经看到这样做的另一种方法是取你的测试点X,然后通过垂直于A和B之间的直线的X构造一条线。找到这条直线和A-> B线交叉的地方。计算从X到这个交叉点的距离,如果它足够小,你可以认为该点在线上。

    编辑 - 有趣的小练习!

    由于我误读了一些数学,因此发布了之前错误的代码。 在火车回家的Pythonista玩了一下,想出了基本上可行的。因为在iPad上编辑帖子很痛苦,所以已经把数学证明留下了!

    没有多少测试完成,没有测试除零等等,请警告用户。

        # we determine the point of intersection X between
        # the line between A and B and a line through T
        # that is perpendicular to the line AB (can't draw perpendicular
        # in ascii, you'll have to imagine that angle between AB and XT is 90
        # degrees.
        #
        #       B
        #      /
        #.    X  
        #    / \
        #   /   T
        #  A
        # once we know X we can work out the closest the line AB
        # comes to T, if that distance is 0 (or small enough)
        # we can consider T to be on the line
        import math
    
    
        # work out where the line through test point t
        # that is perpendicular to ab crosses ab
        #
        # inputs must be 2-tuples or 2-element lists of floats (x,y)
        # returns (x,y) of point of intersection
        def intersection_of_perpendicular(a,b,t):
    
        if a[0] == b[0]:
                return (a[0],t[1])
    
        if a[1] == b[1]:
                return (t[0],a[1])
    
        m = (a[1] - b[1])/(a[0] - b[0]) #slope of ab
    
        x_inter = (t[1] - a[1] + m*a[0] + (1/m)*t[0])*m/(m**2 + 1)
        y_inter = m*(x_inter - a[0]) + a[1]
        y_inter2 = -(1/m)*(x_inter - t[0]) + t[1]
    
        #print '...computed ',m,(x_inter, y_inter), y_inter2
        return (x_inter, y_inter)
    
        # basic Pythagorean formula for distance between two points
        def distance(a,b):
            return math.sqrt( (a[0]-b[0])**2 + (a[1]-b[1])**2 )
    
        # check if a point is within the box defined by a,b at
        # diagonally opposite corners
        def point_in_box(a,b,t):
            xmin = min(a[0],b[0])
            xmax = max(a[0],b[0])
            ymin = min(a[1],b[1])
            ymax = max(a[1],b[1])
    
            x_in_bounds = True
            if xmax != xmin:
                x_in_bounds = xmin <= t[0] <= xmax
            y_in_bounds = True
            if ymax != ymin:
                y_in_bounds = ymin <= t[1] <= ymax
            return x_in_bounds and y_in_bounds
    
        # determine if point t is within 'tolerance' distance
        # of the line between a and b
        # returns Boolean
        def is_on_line_between(a,b,t,tolerance=0.01):
            intersect = intersection_of_perpendicular(a,b,t)
            dist = distance(intersect, t)
            in_bounds = point_in_box(a,b,t)
            return in_bounds and (dist < tolerance)
    
    
    a = (0,0)
    b = (2,2)
    t = (0,2)
    
    p = intersection_of_perpendicular(a,b,t)
    bounded = point_in_box(a,b,t)
    print 'd ',distance(p,t), ' p ',p, bounded
    
    a = (0,2)
    b = (2,2)
    t = (1,3)
    
    p = intersection_of_perpendicular(a,b,t)
    bounded = point_in_box(a,b,t)
    print 'd ',distance(p,t),' p ',p, bounded
    
    a = (0.0,2.0)
    b = (2.0,7.0)
    t = (1.7,6.5)
    
    p = intersection_of_perpendicular(a,b,t)
    bounded = point_in_box(a,b,t)
    on = is_on_line_between(a,b,t,0.2)
    print 'd ',distance(p,t),' p ',p, bounded,on 
    

答案 2 :(得分:1)

我没有对此进行测试,但一般的想法是:

def pointOnBorder(x, y, poly):
    n = len(poly)
    for(i in range(n)):
        p1x, p1y = poly[i]
        p2x, p2y = poly[(i + 1) % n]
        v1x = p2x - p1x
        v1y = p2y - p1y #vector for the edge between p1 and p2
        v2x = x - p1x
        v2y = y - p1y #vector from p1 to the point in question
        if(v1x * v2y - v1y * v2x == 0): #if vectors are parallel 
            if(v2x / v1x > 0): #if vectors are pointing in the same direction
                if(v1x * v1x + v1y * v1y >= v2x * v2x + v2y * v2y): #if v2 is shorter than v1
                    return true
    return false

答案 3 :(得分:0)

对于凸多边形,您可以通过按时钟顺序排序顶点并为每个顶点存储顶点与多边形内部中的点c之间的角度来解决O(log n)时间中的问题。然后对于查询点x,您获得从c到x的角度和二分搜索,以找到唯一的相邻顶点对(v1,v2),使得x的角度在v1和v2的角度之间。然后x位于边缘(v1,v2),或x不在边界上。

如果你有一个更复杂的多边形,那么你可以尝试通过添加一些内部边缘(例如,先是三角形然后去除边缘以获得更大的凸多边形)将多边形分解为凸多边形的并集。如果你得到的凸多边形的数量很小(比如k),那么你可以测试每个凸多边形以查看一个点是否在一个边上,整个运行时间是O(k lg n)其中n是总数多边形中的顶点。

或者,如果您不担心使用额外的空间,并且您真的想要快速确定是否在边缘,那么您可以通过沿每条边添加额外的“顶点”将每条边分成等间距的片段;很难说有多少是足够的(听起来像一个有趣的数学问题),但很明显,如果沿每条边添加足够的额外顶点,那么只需找到你的最近邻居就可以分辨出一个点必须位于哪一边。顶点(原始顶点和您添加的顶点),然后只测试最近邻居顶点所在的一条或两条边。如果使用二维kd树(您将树构建为预处理步骤,然后树支持快速k-最近邻居查询),则可以在2-d中快速找到k-最近邻居,并且kd-tree树只使用线性空间。

答案 4 :(得分:0)

http://geospatialpython.com/2011/08/point-in-polygon-2-on-line.html

找到解决方法

这里是代码:

# Improved point in polygon test which includes edge
# and vertex points

def point_in_poly(x,y,poly):

   # check if point is a vertex
   if (x,y) in poly: return "IN"

   # check if point is on a boundary
   for i in range(len(poly)):
      p1 = None
      p2 = None
      if i==0:
         p1 = poly[0]
         p2 = poly[1]
      else:
         p1 = poly[i-1]
         p2 = poly[i]
      if p1[1] == p2[1] and p1[1] == y and x > min(p1[0], p2[0]) and x < max(p1[0], p2[0]):
         return "IN"

   n = len(poly)
   inside = False

   p1x,p1y = poly[0]
   for i in range(n+1):
      p2x,p2y = poly[i % n]
      if y > min(p1y,p2y):
         if y <= max(p1y,p2y):
            if x <= max(p1x,p2x):
               if p1y != p2y:
                  xints = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
               if p1x == p2x or x <= xints:
                  inside = not inside
      p1x,p1y = p2x,p2y

   if inside: return "IN"
   else: return "OUT"

# Test a vertex for inclusion
polygon = [(-33.416032,-70.593016), (-33.415370,-70.589604),
(-33.417340,-70.589046), (-33.417949,-70.592351),
(-33.416032,-70.593016)]
lat= -33.416032
lon= -70.593016

print point_in_poly(lat, lon, polygon)

# test a boundary point for inclusion
poly2 = [(1,1), (5,1), (5,5), (1,5), (1,1)]
x = 3
y = 1
print point_in_poly(x, y, poly2)
相关问题