以前上學就學過,現在工作又遇到了,拿出來復習一下(看的很老的博客講的都比較細了,不知道最近又有沒有新方法)
- 引射線法:從被判斷的點發射一條射線,與多邊形有奇數個交點則在多邊形內
- 面積和法:從多邊形一頂點出發,計算被判斷的點和相鄰兩點組成的三角形的面積和(可用 1/2 * 向量叉乘求),面積和與多邊形面積相等則在多邊形內
- 夾角和法:從多邊形一頂點出發,計算被判斷的點和多邊形相鄰兩頂點的夾角和(可用向量點積推出的夾角公式求),夾角和為 360 則在多邊形內部
- 遮罩法:生成多邊形的位圖(多邊形內部區域置為指定的顏色),找出將被判斷點處位圖的顏色,為指定的顏色則在內部
這幾種方法除了遮罩法別的在判斷凹多邊形都得注意下細節,比如:
- 夾角和法的射線正好卡在拐角處。
- 面積和法、夾角和法順時針為加,逆時針為減(要是都按加算的話肯定會算多了。。)
JS 使用引射線法實現
已經有大神寫出來了:substack/point-in-polygon: determine if a point is inside a polygon
/**
* 判斷點與多邊形位置關系
* @param {Array<number>} point 待判斷的點
* @param {Array<Array<number>>} vs 多邊形點數組
* @return {bool} 是否在內部
*/
function inside(point, vs){
// ray-casting algorithm based on
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
var x = point[0], y = point[1];
var inside = false;
// 依次遍歷多邊形的每個邊
for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
var xi = vs[i][0], yi = vs[i][1];
var xj = vs[j][0], yj = vs[j][1];
var intersect =
((yi > y) != (yj > y)) // 判斷該點縱坐標是否在線段最高點和最低點之間[注1]
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi); // 判斷該點向x軸正方向發出的射線是否穿過線段[注2]
if (intersect) inside = !inside;
}
return inside;
}
inside([0.5,2.5], [[1,1], [2,2], [1,3]])// false
inside([1.5,2], [[1,1], [2,2], [1,3]])// true
// 在邊上的點又是什么情況?
inside([1.5,1.5], [[1,1], [2,2], [1,3]])// false
inside([1,2], [[1,1], [2,2], [1,3]])// true
注 1:判斷該點縱坐標是否在線段最高點和最低點之間
這里有 5 種情況:
false (F!=F)
------false (F!=F)-------yi
true (T!=F)
------true (T!=F)-------yj
false (T!=T)
注 2:判斷該點向 x 軸正方向發出的射線是否穿過線段
- 計算改點平行於 X 軸的直線與該線段所在直線的交點的橫坐標:
(xj - xi) * (y - yi) / (yj - yi) + xi - 判斷該點橫坐標是否小於交點的橫坐標與孰大孰小:
x < 交點的橫坐標
C# 實現
// 上面說的都沒用到 =_=,C# 自帶一個檢測的方法
/* 說明:
* 1. 雖然地球是圓的但中國坐標都是正的這么處理也沒毛病
*
* 2. 經緯度一般小數點前3位后6位一共9位
*
* PointF(float):32位,1位符號,8位指數,23位尾數。
* 2^23 = 8,388,608 精度6-7位
* float不夠用
*
* Point(int):32位,1位符號,31位數
* 2^31 = 2,147,483,648 精度9-10位
* int夠用,但經測試只能乘100000,精確到小數點后5位(米級)
* 乘1000000,精確到小數點后6位(分米級)時會全返回False,可能內部計算時越界了
*
* 計算時全是按雙精度算的夠用
*/
// 創建多邊形區域
GraphicsPath gp = new GraphicsPath();
Region region = new Region();
gp.Reset();
gp.AddPolygon(new Point[]{
new Point((int)(28.87243083439048 * 100000.0), (int)(106.83294296264648 * 100000.0)),
new Point((int)(28.87243083439048 * 100000.0), (int)(106.84285640716554 * 100000.0)),
new Point((int)(28.880735867389312 * 100000.0), (int)(106.84285640716554 * 100000.0)),
new Point((int)(28.880735867389312 * 100000.0), (int)(106.83294296264648 * 100000.0)),
new Point((int)(28.87243083439048 * 100000.0), (int)(106.83294296264648 * 100000.0))
});
region.MakeEmpty();
region.Union(gp);
//判斷點是否在多邊形區域里
bool result1 = region.IsVisible(new Point((int)(28.87243083439048 * 100000.0), (int)(106.83294296264648 * 100000.0)));
bool result2 = region.IsVisible(new Point((int)(39.904030 * 100000.0), (int)(116.407526 * 100000.0)));
Console.WriteLine(result1.ToString());
Console.WriteLine(result2.ToString());
Console.ReadLine();
