一種快速判斷點在多邊形內的算法

由於業務需要, 我總結了一種快速判斷點在多邊形內的算法。

先說思路:

如圖:

  • 如果點在多邊形內部,射線第一次穿越邊界一定是穿出多邊形。
  • 如果點在多邊形外部,射線第一次穿越邊界一定是進入多邊形。

我們可以歸納出:

    • 當射線穿越多邊形邊界的次數爲偶數時,所有第偶數次(包括最後一次)穿越都是穿出,因此所有第奇數次(包括第一次)穿越爲穿入,由此可推斷點在多邊形外部。
    • 當射線穿越多邊形邊界的次數爲奇數時,所有第奇數次(包括第一次和最後一次)穿越都是穿出,由此可推斷點在多邊形內部。

 

實現關鍵點

1. 點在多邊形的邊上

前面我們講到,射線法的主要思路就是計算射線穿越多邊形邊界的次數。那麼對於點在多邊形的邊上這種特殊情況,射線出發的這一次,是否應該算作穿越呢?

思路: 先求邊和點的交點, 即邊的起點y乘以邊斜率,得到交點的x, 若x == X, X是參考點的橫座標,則點在線上。

 

2. 點和多邊形的頂點重合

思路:參考點與邊頂點重合,則直接是 x == X && y == Y ,其中x,y是邊頂點, X,Y是參考點, 則直接返回。

 

3. 射線經過多邊形頂點

思路:這時相交點次數無論內外都是偶數次,無法判斷。不過,這裏看射線兩側的邊如果都在兩側時算作一次“穿過”,即 y == Y時, x1 > X 並且 x2 <= X (或  x1 < X 且 x2 > X),穿過數次加1 , 其中X,Y是參考點, x1,y1 ,  x2, y2是線段頂點。

 

4. 射線剛好經過一條邊

思路: 這個最簡單, 直接判斷 y == Y,可以理解成穿過了這條邊的2個頂點, Y是參考點的縱座標, y是邊的縱座標。

 

問題都解決了,其實並不複雜。

 

實現代碼

type Point struct {
	X float64
	Y float64
}

func IfPointsInPolygon(point Point, area []Point) bool {
	// 目標點的x, y座標
	x := point.X
	y := point.Y

	// 多邊形的點數
	count := len(area)

	// 點是否在多邊形中
	var inInside bool

	// 浮點類型計算與0的容差
	precision := 2e-10

	// 依次計算每條邊,根據每邊兩端點和目標點的狀態欄判斷
	for i, j := 0, count-1; i < count; j, i = i, i+1 {
		// 記錄每條邊上的兩個點座標
		x1 := area[i].X
		y1 := area[i].Y
		x2 := area[j].X
		y2 := area[j].Y

		// 判斷點與多邊形頂點是否重合
		if (x1 == x && y1 == y) || (x2 == x && y2 == y) {
			return true
		}

		// 判斷點是否在水平直線上
		if (y == y1) && (y == y2) {
			return true
		}

		// 判斷線段兩端點是否在射線兩側
		if (y >= y1 && y < y2) || (y < y1 && y >= y2) {
			// 斜率
			k := (x2 - x1) / (y2 - y1)

			// 相交點的 x 座標
			_x := x1 + k*(y-y1)

			// 點在多邊形的邊上
			if _x == x {
				return true
			}

			// 浮點類型計算容差
			if math.Abs(_x-x) < precision {
				return true
			}

			// 射線平行於x軸,穿過多邊形的邊
			if _x > x {
				inInside = !inInside
			}
		}
	}

	return inInside
}
#壓測
/private/var/folders/9l/9h6c50j93qs8c43wpwnvhnbc0000gn/T/GoLand/___gobench_common_algorithm_polygon.test -test.v -test.paniconexit0 -test.bench . -test.run ^$
goos: darwin
goarch: amd64
pkg: common/algorithm/polygon
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkPIP
BenchmarkPIP-12    	29140065	        40.57 ns/op
PASS

 

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