1. 程式人生 > >用射線法實現判斷點是否在多邊形內部

用射線法實現判斷點是否在多邊形內部

有意 遇到 原理 lines 意思 容易 int points spa

最近工作中遇到了這個問題,檢索之後發現這種實現方式挺有意思的,無論是凸多邊形還是凹多邊形都可以判斷。

射線法是用被測點向任意方向(通常為了好算,使其射向右側)做一條射線,判斷射線與多邊形的交點。如果交點的數量為奇數,則被測點在多邊形內;如果交點的數量為偶數,則被測點在多邊形以外。

期間,有些特殊情況需要判斷,比如:

1. 射線剛好經過凸多邊形兩條相鄰邊的交點上的情況會導致重復判斷;

2.射線和多邊形的邊重合的情況。

先上js代碼。

function isDotInPolygon(point, polygonPoints) {
  var flag = false,
      p1,
      p2;
  
for(var i = 0, j = polygonPoints.length - 1; i < polygonPoints.length; j = i++) { p1 = polygonPoints[i]; p2 = polygonPoints[j]; // 這裏判斷是否剛好被測點在多邊形的邊上 if(isDotInLineSegment(point, p1, p2)) return true; if((p1.y > point.y != p2.y > point.y) && (point.x < (point.y - p1.y) * (p1.x - p2.x) / (p1.y - p2.y) + p1.x)) { flag
= !flag; } } return flag; }

判斷點是否在線段上的 isDotInLineSegment 的函數我懶得寫了,這個很好實現。

關鍵點在於判斷射線與多邊形邊相交的部分,這段代碼原理不是我想出來的,它實現的方式很是精妙。

首先咱們看 表達式1: p1.y > point.y != p2.y > point.y

表面上看,表達式1是用了一個類似於異或的判斷,要求被測點的y坐標在循環中多邊形當前邊的y軸投影範圍內;

它其實還隱藏了另外一層條件,可以過濾掉前面提到的特殊情況1 和 2。 舉個例子,多邊形相鄰的倆個邊所在線段(p1, p2)和 (p2, p3),為了解釋方便,咱們假設其中p3.y > p2.y > p1.y。 如果從被測點發射出來的射線經過了p2 ,那麽上面這段表達式1其實在判斷(p1, p2)時為false,判斷(p2, p3)時為true,這樣就巧妙地避免了重復計數的問題。 而如果是個凹多邊形,存在p3.y > p2.y < p1.y , 那麽表達式1 在判斷(p1, p2)與(p2, p3)時都為true, 可以正確地計數兩次。

然後看 表達式2 : point.x < (point.y - p1.y) * (p1.x - p2.x) / (p1.y - p2.y) + p1.x

同樣為了解釋方便,咱們假設其中p2.y > point.y > p1.y, 表達式2 等同於 (point.x - p1.x) / (point.y - p1.y) < (p1.x - p2.x) / (p1.y - p2.y) ,其中 (p1.x - p2.x) / (p1.y - p2.y) 可以理解為是線段(p1, p2)斜率的倒數, (point.x - p1.x) / (point.y - p1.y) 則是線段 (p1, point) 斜率的倒數。如果線段(p1, p2)的斜率的倒數要大於線段 (p1, point) 的斜率的倒數,則點point就只能在線段(p1, p2)的左側。這樣就確保了射線與線段(p1, p2)的相交。

這段看起來有點復雜,其實拿動筆畫一畫就很容易明白(沒錯,我又懶得上圖了)。

用射線法實現判斷點是否在多邊形內部