1. 程式人生 > >判斷點與多邊形關係

判斷點與多邊形關係

以前上學就學過,現在工作又遇到了,拿出來複習一下(看的很老的部落格講的都比較細了,不知道最近又有沒有新方法)

  1. 引射線法:從被判斷的點發射一條射線,與多邊形有奇數個交點則在多邊形內
  2. 面積和法:從多邊形一頂點出發,計算被判斷的點和相鄰兩點組成的三角形的面積和(可用1/2*向量叉乘求),面積和與多邊形面積相等則在多邊形內
  3. 夾角和法:從多邊形一頂點出發,計算被判斷的點和多邊形相鄰兩頂點的夾角和(可用向量點積推出的夾角公式求),夾角和為360則在多邊形內部
  4. 遮罩法:生成多邊形的點陣圖(多邊形內部區域置為指定的顏色),找出將被判斷點處點陣圖的顏色,為指定的顏色則在內部

這幾種方法除了遮罩法別的在判斷凹多邊形都得注意下細節,比如:

  • 夾角和法的射線正好卡在拐角處。
  • 面積和法、夾角和法順時針為加,逆時針為減(要是都按加算的話肯定會算多了。。)

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軸正方向發出的射線是否穿過線段

  1. 計算改點平行於X軸的直線與該線段所在直線的交點的橫座標:(xj - xi) * (y - yi) / (yj - yi) + xi
  2. 判斷該點橫座標是否小於交點的橫座標與孰大孰小: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();