改进的point in polygon problem算法介绍

背景知识

点和多边形的位置问题(point-in-polygon (PIP) problem), 一般指的是给定二维平面上的一个点Q以及一个多边形P,怎样判断点Q是位于多边形P内部还是外部。该算法在计算机图形学,地理空间信息学等方面有广泛的应用。目前有两种通用的算法实现: Ray casting algorithm(又称even-odd算法)和Winding number algorithm:

  • Ray casting algorithm
    从Q向无穷远处引一条射线,计算该射线和多边形的边相交的次数,如果是奇数次,则点在多边形内部,如果是偶数次,则点在多边形外部。

    改进的point in polygon problem算法介绍

    图1

    该算法的理论依据是约当曲线定理 Jordan curve theorem: 一个简单闭合多边形一定会将平面分为内部和外部,连接内部任意一点与外部任意一点的线段一定经过闭合图形的边。虽然直观上看上去理所当然,但是要给出严谨的数学证明却很困难,数学家Jordan第一个给出了证明,后来虽然被找出该证明有不少瑕疵,这个定理仍然以他的名字来命名。
    根据约当曲线定理,可以对算法有直观的解释:对于给定点Q和无穷远点间S的连线QS,由于S必然位于多边形P外部,而QS和多边形的每个交点K都是一个外部和内部切换的临界点:

    1. 如果是奇数个交点,从S出发,切换过程必然是:S(外部)–>k1(内部)–>k2(外部)….–>k2n+1(内部)–>Q,这样Q必然位于P内部。
    2. 如果是偶数个交点,从S出发,切换过程必然是:S(外部)–>k1(内部)–>k2(外部)….–>k2n(外部)–>Q,这样Q必然位于P外部。
  • Winding number algorithm
    winding number algorithm 又称为转角累加法,知乎上有一个比较直观的解释如下:

    改进的point in polygon problem算法介绍

    图2

    给多边形的顶点依次编号(如上图中为A,B,C,D,E),称待判断的点为O。依次考察角AOB、BOC、COD、DOE、EOA。这里的角均取小于180度的那一侧,并均为有向角,我们规定逆时针为正,顺时针为负。上面左图中,五个角都是正的;右图中,角DOE是负的。把所有的角累加起来,我们发现左图中角度之和为360度,右图为零。
    形象一点儿理解,就是你站在O处,转身用目光把整个多边形扫描一遍。若你转了奇数圈,则你在多边形内;若你转了偶数圈,则你在多边形外。

对于简单多边形(边不自交的多边形),数学上说法是同构于单位圆的图形,上面的两种算法通常能得到相同的结果。对于边相交的非简单多边形,内部与外部通常有不同的定义(详见page44):有的把重叠部分根据异或(XOR)原则,如下图3中左侧重叠部分定义为外部(Ray-casting算法给出在此种定义下点与多边形的关系),有的把重叠部分根据或(OR)原则定义为内部,如下图3右侧部分(Winding number算法给出此种定义下点与多边形的关系),

改进的point in polygon problem算法介绍

图3来源

由于一般的Ray-casting算法不能够处理如下图4射线和顶点相交的情形(如点g,d)以及射线和多变形边重合等特殊情形,本文的主要目的是介绍一种简单有效的改进了的Ray-casting算法。
改进的point in polygon problem算法介绍

图4

算法介绍

该算法由Michael Galetzka和Patrick Glauner于2012年提出,2017年发表于Proceedings of the 12th International Joint Conference on Computer Vision, Imaging and Computer Graphics Theory and Applications (VISIGRAPP 2017),在这里可以下载到算法全文。

由于点Q与多边形P(顶点为p1, p2, …, pn)位于二维平面上,可以考虑将Q和P通过坐标平移,使Q平移到坐标原点而其和P的相对位置不变,显然平移不会改变点Q与多边形P的位置关系(在多边形内或外),下面算法主要是针对平移后的情形进行说明,算法步骤如下:

1. 判断Q是否位于是P的顶点或者位于P的边上,如果是,点Q在多边形内部。
2.在顶点集合P中寻找不在x轴上的顶点ps,如果找不到,点Q在多边形外部。
   (注:根据步骤1,Q不在P的顶点或者边上,而找不到不在x轴上的点ps
  明多边形P的点都在X轴上,而Q位于原点,说明Q在P的外部。)
 3. 设i=1,从点ps开始通过重复下面步骤直到所有的顶点都被访问到:
  a.判断点ps+i是否位于x轴上,如果在,递增i,如果s+i>n,那么将i设置为s,从p0开始继续寻找,直到找到一个不位于x轴上的点ps+i为止。
  b. 根据步骤a中的查找过程,采取下面的操作:
   i. 如果步骤a中找ps+i时没有skip掉任何顶点,那么判断从psps+i的线段是否和x轴的正半轴相交,如相交,交点个数加1;
   ii. 如果步骤a中找ps+i时skip掉至少一个x轴坐标为正的顶点,那么判断从psps+i的线段是否和整个x轴相交,如相交,交点个数加1;
   iii. 如果步骤a中找ps+i时skip掉至少一个x轴坐标为负的顶点,不做任何操作;
   (注:不可能同时skip掉位于X轴正半轴和X轴负半轴的点,如果有,说明Q位于多边形的边上,步骤1就返回了)
  c. ps+i为下一轮迭代的起点
4. 判断交点个数是奇数还是偶数,如果是奇数,说明点Q位于多边形P内部;如果是偶数,说明点Q位于多边形P外部

这种方法实际是通过添加辅助线来判断点是否位于多边形的内部,这种添加辅助线的方法是否正确呢?我们来分析一下:

  • 情形i中无顶点skip掉则判断线段是否和x正半轴相交的情形就是一般的Ray-casting算法处理的情形,x轴的正半轴就是一般Ray-casting算法中引出的射线;
  • 情形ii中如果skip掉的是x轴正向上的点,如下图5所示,通过判断线段p1p4和X轴的交点,就可以替换线段p1p2p2p3p3p4,并且有效的绕开了射线和多边形顶点相交以及与多边形边重合的情形。
    改进的point in polygon problem算法介绍

    图5

    那么为什么是和整个X轴相交而不是像情形i中和x轴的正半轴相交那样呢?这是为了处理下图6中的情形:
    改进的point in polygon problem算法介绍

    图6

    p1出发约过p2,我们实际上在p1p3间添加了一条辅助线,如果这种情况下仍然只考虑线段p1p3与x轴正半轴相交,将得不到任何交点,所以此种情况下,通过和x轴相交,得到一个交点。
  • 情形iii中为什么越过的是x轴负半轴上的顶点时,不做任何处理呢?分析如下图7可以得到答案:
    改进的point in polygon problem算法介绍

    图7

    如图7,从p1开始,越过p2,我们找到p3,按照规则iii,计数为0,但是当从p3开始到p1,我们没有越过任何点,根据情形i,和x轴正半轴相交,计数为1,点在多边形内部。当图7三角向左移动变为如下图8时,我们仍然可以结合情形iii和情形i,判断点在多边形外部。
    改进的point in polygon problem算法介绍

    图8

那么通过这种算法能否覆盖到所有情形呢?
根据Ray-casting算法,我们不考虑越过的点在x负半轴的情形(因为Q位于原点,如果起点pu和终点pw的分别位于2、3象限,交点pv在x负半轴上,那么根据一般的Ray-casting算法,从Q点引出的射线(x正半轴)不可能与线段pupvpupwpwpv有任何交点);根据算法步骤1,也不可能出现同时越过位于x轴正半轴的点和负半轴的点的情形、或者原点Q是多边形的一个顶点的情形。
那么,所有需要考虑的情形就是越过的点在x正半轴的情形 ,而从图5可以知道,不管越过的点是一个还是多个,其辅助线都是唯一的,所以该算法唯一要考虑的就是起点pu和终点pw在任意象限时结论是否成立,我们可以通过穷举法来证明,显然起点pu和终点pw在不同象限的组合为44=16种,去掉那些调换起点和终点后图形一致的情形,一共只剩下10种情形,如下表1所示:

改进的point in polygon problem算法介绍

表1

上表1中qi 其中i=1...4表示1到4象限,示例表示的是起点pu和终点pw在越过x轴上的点pv(当然pv可以表示多个点)后的交点情况,Desired count一列表示的是通过最初的Ray-casting 算法,线段pu和终点pw经交点pv产生的交点个数的奇偶性,这正好与上面改进的Ray-casting 算法的结论相符。要判断如图6中点Q是位于三角形内部还是外部,根据改进的Ray-casting 算法,我们以p1为起点,计算交点的过程如下表2:

pu(起点) pv(越过的点) pw(终点) 用来相交的x轴 是否相交 累计交点个数
p1 p2 p3 x轴全轴 1
p3 - p1 x轴正半轴 1

表2

所以交点总数为1,点在多边形内部。

复杂多边形例子

如下图9是一个点在复杂多边形内部的例子:

改进的point in polygon problem算法介绍

图9

起始点是 p1,依据算法,我们可以得到类似的表3:

pu(起点) pv(越过的点) pw(终点) 用来相交的x轴 是否相交 累计交点个数
p1 - p2 x轴正半轴 0
p2 p3p4 p5 x轴全轴 0
p5 p6 p1 x轴全轴 1

表3

所以交点总数为1,点在多边形内部。

不难发现,所有点均只访问一遍,时间复杂度为O(n),作者也在这里给出了在点的数值类型为整型情形下的一个实现, 我们可以很容易的扩展到浮点数下的情形。