python 判断多边形,点是否重合(方法简单易懂,没有使用 cv2.pointPolygonTest函数)

python 判断多边形,点是否重合

首先代码并未使用 cv2.pointPolygonTest 这一opencv函数,因为自己在使用时,一直报错,很难自己构造出适用于 pointPolygonTest ()的 tuple参数,然后在网上找到了如下这边博客写的特别好,亲测实用。
https://www.jianshu.com/p/ba03c600a557
下面是以防博主删帖,自己做的笔记。还加了一些自己完善的内容

#######################################

判断一个点是否在多边形内是处理空间数据时经常面对的需求,例如GIS中的点选功能、根据多边形边界筛选出位于多边形内的点、求交集、筛选不在多边形内的点等等。
判断一个点是否在多边形内有几种不同的思路,相应的方法(感觉还谈不上算法)有:

射线法:从判断点向某个统一方向作射线,依交点个数的奇偶判断;
转角法:按照多边形顶点逆时针顺序,根据顶点和判断点连线的方向正负(设定角度逆时针为正)求和判断;
夹角和法:求判断点与所有边的夹角和,等于360度则在多边形内部。
面积和法:求判断点与多边形边组成的三角形面积和,等于多边形面积则点在多边形内部。

面积和法涉及多个面积的计算,比较复杂,夹角和法以及转角法用到角度计算,会涉及反三角函数,计算开销比较大,而射线法主要涉及循环多边形的每条边进行求交运算,但大部分边可以通过简单坐标比对直接排除,因此这是比较好的方法。
射线法的实现
射线法就是以判断点开始,向右(或向左)的水平方向作一射线,计算该射线与多边形每条边的交点个数,如果交点个数为奇数,则点位于多边形内,偶数则在多边形外。该算法对于复合多边形也能正确判断。
在这里插入图片描述
射线法的关键是正确计算射线与每条边是否相交。并且规定线段与射线重叠或者射线经过线段下端点属于不相交。首先排除掉不相交的情况,下图的情况都是需要排除掉的:
在这里插入图片描述
排除掉这些情况的函数如下:

def isRayIntersectsSegment(poi,s_poi,e_poi): #[x,y] [lng,lat]
    #输入:判断点,边起点,边终点,都是[lng,lat]格式数组
    if s_poi[1]==e_poi[1]: #排除与射线平行、重合,线段首尾端点重合的情况
        return False
    if s_poi[1]>poi[1] and e_poi[1]>poi[1]: #线段在射线上边
        return False
    if s_poi[1]<poi[1] and e_poi[1]<poi[1]: #线段在射线下边
        return False
    if s_poi[1]==poi[1] and e_poi[1]>poi[1]: #交点为下端点,对应spoint
        return False
    if e_poi[1]==poi[1] and s_poi[1]>poi[1]: #交点为下端点,对应epoint
        return False
    if s_poi[0]<poi[0] and e_poi[1]<poi[1]: #线段在射线左边
        return False

    xseg=e_poi[0]-(e_poi[0]-s_poi[0])*(e_poi[1]-poi[1])/(e_poi[1]-s_poi[1]) #求交
    if xseg<poi[0]: #交点在射线起点的左侧
        return False
    return True  #排除上述情况之后

排除掉上述情况真正需要求交点来判断的情况只有两种:
在这里插入图片描述
函数isRayIntersectsSegment()里求交的部分就是利用两个三角形的比例关系求出交点在起点的左边还是右边;用图去理解如下:
在这里插入图片描述
最后判断的代码如下:
def isPoiWithinPoly(poi,poly):
#输入:点,多边形三维数组
#poly=[[[x1,y1],[x2,y2],……,[xn,yn],[x1,y1]],[[w1,t1],……[wk,tk]]] 三维数组

#可以先判断点是否在外包矩形内 
#if not isPoiWithinBox(poi,mbr=[[0,0],[180,90]]): return False
#但算最小外包矩形本身需要循环边,会造成开销,本处略去
sinsc=0 #交点个数
for epoly in poly: #循环每条边的曲线->each polygon 是二维数组[[x1,y1],…[xn,yn]]
    for i in range(len(epoly)-1): #[0,len-1]
        s_poi=epoly[i]
        e_poi=epoly[i+1]
        if isRayIntersectsSegment(poi,s_poi,e_poi):
            sinsc+=1 #有交点就加1

return True if sinsc%2==1 else  False

测试结果:

contours = [[[10,10],[80,10],[10,80],[80,80]]]
point = [30,10]
sign = isPoiWithinPoly(point,contours)
print (sign)

result:
False
contours = [[[10,10],[80,10],[10,80],[80,80]]]
point = [30,30]
sign = isPoiWithinPoly(point,contours)
print (sign)

result:
True

###ps:经过几天的使用测试,发现上述代码有几个大BUG

在这里插入图片描述
上述代码,导致不能遍历到,一个轮廓中的所有线段,最后一根线段无法遍历,从而引起错误。
再有就是下图中几种情况,均会误判 “点在轮廓内部”
在这里插入图片描述
所以最后修改代码如下(代码注释并不完善,此处只是当笔记用,以后再重新整理):

import os
import numpy as np

def isRayIntersectsSegment(poi,s_poi,e_poi): #[x,y] [lng,lat]
    #输入:判断点,边起点,边终点,都是[lng,lat]格式数组
    # if s_poi[1]==e_poi[1]: #排除与射线平行、重合,线段首尾端点重合的情况
    #     return False
    if s_poi[1]>poi[1] and e_poi[1]>poi[1]: #线段在射线上边
        return False
    if s_poi[1]<poi[1] and e_poi[1]<poi[1]: #线段在射线下边
        return False
    # if s_poi[1]==poi[1] and e_poi[1]>poi[1]: #交点为下端点,对应spoint
    #     return False
    # if e_poi[1]==poi[1] and s_poi[1]>poi[1]: #交点为下端点,对应epoint
    #     return False
    if s_poi[0]<poi[0] and e_poi[1]<poi[1]: #线段在射线左边
        return False

    xseg=e_poi[0]-(e_poi[0]-s_poi[0])*(e_poi[1]-poi[1])/(e_poi[1]-s_poi[1]) #求交
    if xseg<poi[0]: #交点在射线起点的左侧
        return False
    else:
        return True  #排除上述情况之后

# 判断一个点是否在一个多边形区域内(射线法),如果在则返回值为True,如果不在则返回值为 False
def isPoiWithinPoly(poi,poly):
    #输入:点,多边形三维数组
    #poly=[[[x1,y1],[x2,y2],……,[xn,yn],[x1,y1]],[[w1,t1],……[wk,tk]]] 三维数组

    #可以先判断点是否在外包矩形内
    #if not isPoiWithinBox(poi,mbr=[[0,0],[180,90]]): return False
    #但算最小外包矩形本身需要循环边,会造成开销,本处略去
    sinsc=0 #交点个数
    for epoly in poly: #循环每条边的曲线->each polygon 是二维数组[[x1,y1],…[xn,yn]]
        for i in range(len(epoly)): #[0,len-1]
            s_poi=epoly[i]

            s_poi_bf = epoly[i-1]

            if i < (len(epoly)-2):  #首先限制下标范围,防止超出
                e_poi = epoly[i + 1]
                e_poi_af = epoly[i + 2]
            elif i == len(epoly)-2: # 若超出循环,则设置为起始值
                e_poi = epoly[-1]
                e_poi_af = epoly[0]
            elif i == len(epoly)-1: # 若超出循环,则设置为起始值
                e_poi = epoly[0]
                e_poi_af = epoly[1]

            if poi[1] == s_poi[1] == e_poi[1]: # 判断平行线段,是否位于区域中间位置,若位于,则应该 +1
                if ((s_poi[1]-s_poi_bf[1])*(e_poi_af[1]-s_poi[1]) > 0):
                    sinsc += 1
                    continue

            elif poi[1] == s_poi[1] != e_poi[1]: # 点
                if ((s_poi_bf[1]-s_poi[1])*(s_poi[1]-e_poi[1])>0):
                    sinsc += 1
                    continue

            elif s_poi[1] > poi[1] and e_poi[1] > poi[1]:  # 线段在射线上边
                continue
            elif s_poi[1] < poi[1] and e_poi[1] < poi[1]:  # 线段在射线下边
                continue
            elif s_poi[0] < poi[0] and e_poi[1] < poi[1]:  # 线段在射线左边
                continue
            else:
                xseg = e_poi[0] - (e_poi[0] - s_poi[0]) * (e_poi[1] - poi[1]) / (e_poi[1] - s_poi[1])  # 求交
                if xseg < poi[0]:  # 交点在射线起点的左侧
                    continue
                else:
                    sinsc += 1  # 排除上述情况之后

            # if isRayIntersectsSegment(poi,s_poi,e_poi):
            #     sinsc+=1 #有交点就加1
    # print ('sinsc: ',sinsc)

    return True if sinsc%2==1 else  False

y = '0;1960;1156;1655;1556;1201;2158;1314;2160;1746;1501;1900;1272;2040;1062;2070;1017;2075;1008;2078;999;2070;1013;2060;1028;2044;1047;2005;1097;800Y;'
# yy = '0;1746;1500;1900;1272;2040;1062;800Y;'

x = [[[2443,1245],[2469,1244],[2912,1536],[2852,1537]],]

point_y = y.split(';')
point_y = np.asarray(point_y[1:-2],dtype=int)
nvertices = len(point_y) / 2
assert np.mod(nvertices, 1) == 0
point_y = point_y.reshape(int(nvertices), 2)

for point in point_y:
    sign = isPoiWithinPoly(point,x)

    print(point, '  ',sign)

PS:经过几天测试,又发现了一个bug,就是如下图情况,
在这里插入图片描述
上图中,A,B区域明显重叠,但是,若使用上述算法判断,则A区域的点,均会被判断为在B区域外。所以上述算法,只适用于 A,B区域相交,但不互相包含的情况,大家使用的时候,根据自己情况进行判断。

  • 4
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值