判斷點與多邊形關係
阿新 • • 發佈:2019-01-04
以前上學就學過,現在工作又遇到了,拿出來複習一下(看的很老的部落格講的都比較細了,不知道最近又有沒有新方法)
- 引射線法:從被判斷的點發射一條射線,與多邊形有奇數個交點則在多邊形內
- 面積和法:從多邊形一頂點出發,計算被判斷的點和相鄰兩點組成的三角形的面積和(可用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();