判斷點是否在多邊型內

0.前言

最近不斷遇到類似的幾何位置問題,一直沒有花時間去總結,本文總結了我常用點跟多邊形的位置判斷方法以及代碼。希望能夠對大家有所幫助。

文中所指的多邊形均爲凸多邊形,一些描述可能有誤,歡迎指正。

1.測試的多邊形

在開始之前,我們需要先構建好測試環境。

我構建了一個比較特殊的多邊形,如下。

/ \

|  |

|_|

從最上面的頂點順時針座標(屏幕座標系)分別爲:(40,10) (60,30)(60,50) (20,50) (20,30)

2.射線判別法

 

根據對多邊形的瞭解,我們可以得出如下結論:

如果一個點在多邊形內部,任意角度做射線肯定會與多邊形要麼有一個交點,要麼有與多邊形邊界線重疊。

如果一個點在多邊形外部,任意角度做射線要麼與多邊形有一個交點,要麼有兩個交點,要麼沒有交點,要麼有與多邊形邊界線重疊。

利用上面的結論,我們只要判斷這個點與多邊形的交點個數,就可以判斷出點與多邊形的位置關係了。

首先羅列下注意事項:

l 射線跟多邊形的邊界線重疊的情況

l 區別內部點和外部點的射線在有一個交點時的情況

對於第一個注意事項,可以將射線角度設爲零度,這樣子只需要判斷兩個相鄰頂點的Y值是否相等即可。然後再判斷這個點的X值方位。

對於第二個注意事項,網上許多文章都說到做射線以後交點爲奇數則表示在多邊形內部,這是一個錯誤的觀點,不僅對於凹多邊形不成立,對於凸多邊形也不成立。

例如:從外部點做射線剛好經過一頂點,這樣子交點個數就爲奇數,但是該點卻不在多邊形內部。

至於要如何區分這兩種情況呢,我能想到了一個不完美的方法,外部點的射線跟多邊形有一個交點的時候,該交點肯定爲頂點,如果該射線上移一位或者下移一位,要麼變成有兩個交點要麼沒有交點。當然爲了安全起見,這裏把射線儘量往相鄰點中心移動。這樣子就能夠判斷出是外部點的射線跟多邊形有一個交點。

不過這個方法並不完美,因爲有了移位操作,可能會把內部點移動出外部。而且如果判斷點在(60,30)位置,判斷的時候先遇到(20,30),然後移位操作,就判斷就出錯了。

爲了解決這些問題,我在起初先掃描一次判斷點是否在頂點上雖然影響了一點效率,而且當判定點距離多邊形一個單位時,判斷可能會有誤。不過只要不是需要高精度的話,這個方法還是很有效的。

代碼如下:

  1. bool InsidePolygon1( POINTD *polygon,int N,POINTD pt )  
  2. {  
  3.     int i,j;  
  4.     bool inside,redo;  
  5.   
  6.     redo = true;  
  7.     for (i = 0;i < N;++i)  
  8.     {  
  9.         if (polygon[i].x == pt.x &&    // 是否在頂點上   
  10.             polygon[i].y == pt.y )  
  11.         {  
  12.             redo = false;  
  13.             inside = true;  
  14.             break;  
  15.         }  
  16.     }  
  17.   
  18.     while (redo)  
  19.     {  
  20.         redo = false;  
  21.         inside = false;  
  22.         for (i = 0,j = N - 1;i < N;j = i++)   
  23.         {  
  24.             if ( (polygon[i].y < pt.y && pt.y < polygon[j].y) ||   
  25.                 (polygon[j].y < pt.y && pt.y < polygon[i].y) )   
  26.             {  
  27.                 if (pt.x <= polygon[i].x || pt.x <= polygon[j].x)   
  28.                 {  
  29.                     double _x = (pt.y-polygon[i].y)*(polygon[j].x-polygon[i].x)/(polygon[j].y-polygon[i].y)+polygon[i].x;  
  30.                     if (pt.x < _x)          // 在線的左側   
  31.                         inside = !inside;  
  32.                     else if (pt.x == _x)    // 在線上   
  33.                     {  
  34.                         inside = true;  
  35.                         break;  
  36.                     }  
  37.                 }  
  38.             }  
  39.             else if ( pt.y == polygon[i].y)   
  40.             {  
  41.                 if (pt.x < polygon[i].x)    // 交點在頂點上   
  42.                 {  
  43.                     polygon[i].y > polygon[j].y ? --pt.y : ++pt.y;  
  44.                     redo = true;  
  45.                     break;  
  46.                 }  
  47.             }  
  48.             else if ( polygon[i].y ==  polygon[j].y && // 在水平的邊界線上   
  49.                 pt.y == polygon[i].y &&  
  50.                 ( (polygon[i].x < pt.x && pt.x < polygon[j].x) ||   
  51.                 (polygon[j].x < pt.x && pt.x < polygon[i].x) ) )  
  52.             {  
  53.                 inside = true;  
  54.                 break;  
  55.             }  
  56.         }  
  57.     }  
  58.   
  59.     return inside;  
  60. }  

3.角度和判別法

角度和判別法就是判定點與多邊形所有相鄰頂點組成的角的角度和來判斷點與多邊形的位置關係。

如果點在多邊形內部,只要該點不在邊界線或者頂點上,則角度和爲三百六十度。

如果在邊界線上,則角度和爲一百八十度。

如果點在多邊形外部,則角度和無法達到三百六十度。但是角度和可能會達到一百八十度,比如判斷點在(60,10)

代碼如下:

  1. // 根據需要不判斷頂點   
  2. bool IsPointInLine( const POINTD &pt,const POINTD &pt1,const POINTD &pt2 )  
  3. {  
  4.     bool inside = false;  
  5.     if (pt.y == pt1.y &&  
  6.         pt1.y == pt2.y &&  
  7.         ((pt1.x < pt.x && pt.x < pt2.x) ||   
  8.         (pt2.x < pt.x && pt.x < pt1.x)) )  
  9.     {  
  10.         inside = true;  
  11.     }  
  12.     else if (pt.x == pt1.x &&  
  13.         pt1.x == pt2.x &&  
  14.         ((pt1.y < pt.y && pt.y < pt2.y) ||   
  15.         (pt2.y < pt.y && pt.y < pt1.y)) )  
  16.     {  
  17.         inside = true;  
  18.     }  
  19.     else if ( ((pt1.y < pt.y && pt.y < pt2.y) ||   
  20.         (pt2.y < pt.y && pt.y < pt1.y)) &&  
  21.         ((pt1.x < pt.x && pt.x < pt2.x) ||   
  22.         (pt2.x < pt.x && pt.x < pt1.x)) )  
  23.     {  
  24.         if (0 == (pt.y-pt1.y)/(pt2.y-pt1.y)-(pt.x - pt1.x) / (pt2.x-pt1.x))  
  25.         {  
  26.             inside = true;  
  27.         }  
  28.     }  
  29.     return inside;  
  30. }  
  31.   
  32. bool InsidePolygon2( POINTD *polygon,int N,POINTD p )  
  33. {  
  34.     int i,j;  
  35.     double angle = 0;  
  36.     bool inside = false;  
  37.   
  38.     for (i = 0,j = N - 1;i < N;j = i++)   
  39.     {  
  40.         if (polygon[i].x == p.x &&    // 是否在頂點上   
  41.             polygon[i].y == p.y)  
  42.         {  
  43.             inside = true;  
  44.             break;  
  45.         }  
  46.         else if (IsPointInLine(p,polygon[i],polygon[j]))    // 是否在邊界線上   
  47.         {  
  48.             inside = true;  
  49.             break;  
  50.         }  
  51.   
  52.         double x1,y1,x2,y2;  
  53.         x1 = polygon[i].x - p.x;  
  54.         y1 = polygon[i].y - p.y;  
  55.         x2 = polygon[j].x - p.x;  
  56.         y2 = polygon[j].y - p.y;  
  57.   
  58.         double radian = atan2(y1,x1) - atan2(y2,x2);  
  59.         radian = abs(radian);  
  60.         if (radian > M_PI) radian = 2* M_PI - radian;  
  61.         angle += radian;            // 計算角度和   
  62.     }  
  63.   
  64.     if ( fabs(6.28318530717958647692 - angle) < 1e-7 )  
  65.         inside = true;  
  66.   
  67.     return inside;  
  68. }  

有的人管角度和判別法叫做轉角法,還有一個類似的方法叫弧長法。

http://haibarali.blog.163.com/blog/static/23474013200722813356858/ 

4.面積和判別法

 

根據角度和判別法,我們可以繼續演化,可以得出如下結論:

如果一個點在多邊形內部,該點與多邊形所有相鄰頂點組成的三角形面積和爲多邊形面積。反之不成立。

求三角形面積:

已知三角形Ax1,y1Bx2,y2Cx3,y3),則面積公式爲:

                 |x1  x2  x3|   

S(A,B,C) = |y1  y2  y3| * 0.5 (當三點爲逆時針時爲正,順時針則爲負的)   

                 |1   1   1 |  

= ( x1*y2 - x1*y3 - x2*y1 + x2*y3 + x3*y1 - x3*y2 ) * 0.5

求多邊形面積:

根據上面求三角形的方法,

已知多邊形A1A2A3...An(順或逆時針都可以),設平面上有任意的一點P,則面積公式爲:   

S(A1,A2,A3...An)   

     =  S(PA1A2)+ S(PA2A3)+...+S(P,An,A1)

    

既然P是可以任取,爲了方便用(0,0)好了。

代碼如下:

  1. bool InsidePolygon3( POINTD *polygon,int N,POINTD pt )  
  2. {  
  3.     int i,j;  
  4.     bool inside = false;  
  5.     double polygon_area = 0;  
  6.     double trigon_area = 0;  
  7.   
  8.     for (i = 0,j = N - 1;i < N;j = i++)   
  9.     {  
  10.         polygon_area += polygon[i].x * polygon[j].y - polygon[j].x * polygon[i].y;  
  11.         trigon_area += abs(  
  12.             pt.x * polygon[i].y -  
  13.             pt.x * polygon[j].y -  
  14.             polygon[i].x * pt.y +  
  15.             polygon[i].x * polygon[j].y +  
  16.             polygon[j].x * pt.y -  
  17.             polygon[j].x * polygon[i].y  
  18.             );  
  19.     }  
  20.   
  21.     trigon_area *= 0.5;  
  22.     polygon_area = abs(polygon_area * 0.5);  
  23.     if ( fabs(trigon_area - polygon_area) < 1e-7 )  
  24.         inside = true;  
  25.   
  26.     return inside;  
  27. }  

多邊形面積計算

http://www.cppblog.com/zyzx/archive/2009/04/27/81241.html 

行列式如何展開

http://www.tongji.edu.cn/~math/xxds/kcja/kcja_b/1-6.htm 

5.點線判別法

經網友glshader提示,添加了這個方法。

如果判斷點在所有邊界線的同側,就能判定該點在多邊形內部。

判斷方法就是判斷兩條同起點射線斜率差。

代碼如下:

  1. bool InsidePolygon4( POINTD *polygon,int N,POINTD p )  
  2. {  
  3.     int i,j;  
  4.     bool inside = false;  
  5.     int count1 = 0;  
  6.     int count2 = 0;  
  7.   
  8.     for (i = 0,j = N - 1;i < N;j = i++)   
  9.     {  
  10.         double value = (p.x - polygon[j].x) * (polygon[i].y - polygon[j].y) - (p.y - polygon[j].y) * (polygon[i].x - polygon[j].x);  
  11.         if (value > 0)  
  12.             ++count1;  
  13.         else if (value < 0)  
  14.             ++count2;  
  15.     }  
  16.   
  17.     if (0 == count1 ||  
  18.  == count2)  
  19.     {  
  20.         inside = true;  
  21.     }  
  22.     return inside;  
  23. }  

6.總結

    總結了四種判別法,雖然很多原理都很簡單,但是實際操作起來,發現很多細節還是值得推敲的。

 

此博文轉自http://blog.csdn.net/fwj380891124/article/details/7737143

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章