由於業務需要, 我總結了一種快速判斷點在多邊形內的算法。
先說思路:
如圖:
- 如果點在多邊形內部,射線第一次穿越邊界一定是穿出多邊形。
- 如果點在多邊形外部,射線第一次穿越邊界一定是進入多邊形。
我們可以歸納出:
-
- 當射線穿越多邊形邊界的次數爲偶數時,所有第偶數次(包括最後一次)穿越都是穿出,因此所有第奇數次(包括第一次)穿越爲穿入,由此可推斷點在多邊形外部。
- 當射線穿越多邊形邊界的次數爲奇數時,所有第奇數次(包括第一次和最後一次)穿越都是穿出,由此可推斷點在多邊形內部。
實現關鍵點
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