【问题标题】:Check whether a point is inside of a simple polygon检查一个点是否在一个简单的多边形内
【发布时间】:2015-06-03 09:14:02
【问题描述】:

我正在尝试确定一个点是否位于多边形内部。我使用来自this website 的以下(针对 Swift 修改的)算法:

func contains(polygon: [Point], test: Point) -> Bool {
    let count = polygon.count
    var i: Int, j: Int
    var contains = false
    for (i = 0, j = count - 1; i < count; j = i++) {
        if ( ((polygon[i].y >= test.y) != (polygon[j].y >= test.y)) &&
            (test.x <= (polygon[j].x - polygon[i].x) * (test.y - polygon[i].y) /
                (polygon[j].y - polygon[i].y) + polygon[i].x) ) {
                    contains = !contains;
        }
    }
    return contains;
}

但是,当有一个具有以下坐标的简单多边形时:(x: 0, y: 40), (x: 0, y: 0), (x: 20, y: 0), (x: 20, y: 20), (x: 40, y: 20), (x: 40, y: 40),并检查点(x: 30, y: 20),结果为真,因为当ijtrue 时,结果为真5 和 4((x: 40, y: 40)(x: 40, y: 20)),尽管该点仅位于多边形的边界。如果该点确实位于多边形中,该函数实际上应该只评估true。感谢您对算法的任何帮助或改进/调整!

【问题讨论】:

  • 虽然您描述的问题确实存在,但您对测试用例的描述看起来并不现实。 if 语句中的条件不会在点 (30, 20) 和边 (40, 20)-(40, 40) 上计算为 true((polygon[i].y &gt;= test.y) != (polygon[j].y &gt;= test.y)) 部分已经是 false(20 &gt;= 20) != (40 &gt;= 20)false
  • 没有答案。这就是所谓的退化。浮点运算只是如此准确,因此实际上存在模糊边界,点可能位于多边形内部或外部,这取决于您编写测试的方式,即使所有方法在数学上都是正确的。跨度>

标签: algorithm swift polygon


【解决方案1】:

这是javascript代码(容易理解,你可以在swift上重写)。它对我来说非常有效,几乎 100%,即非常精确。

您的解决方案精度不佳。

function pointIsInPoly(v, polygon) {
    var edge_error = 1.192092896e-07; // epsilon i.e ~0.000000192
    var x = 0;
    var y = 1;
    var i, j;
    var r = false;
    for (i = 0, j = polygon.length - 1; i < polygon.length; j = i++)
    {
        var pi = polygon[i];
        var pj = polygon[j];
        if (Math.abs(pi[y] - pj[y]) <= edge_error && Math.abs(pj[y] - v[y]) <= edge_error && (pi[x] >= v[x]) != (pj[x] >= v[x]))
        {   
            return true;
        }

        if ((pi[y] > v[y]) != (pj[y] > v[y]))
        {
            var c = (pj[x] - pi[x]) * (v[y] - pi[y]) / (pj[y] - pi[y]) + pi[x];
            if (Math.abs(v[x] - c) <= edge_error)
            {
                return true;
            }
            if (v[x] < c)
            {
                r = !r;
            }
        }
    }
    return r;
}

【讨论】:

  • 了解为什么为 edge_error 选择此特定值以及修改此值如何影响结果会很有趣。 – “对我来说完美,几乎 100%。” 是什么意思? :)
  • edge_error - 它是 epsilon,您可以将其更改为更小或更大的值,现在它是 0.00000011920...。 “几乎 100%” - 表示此任务的所有解决方案都有一定的精度,但这个非常准确。
  • 同样的问题......上面的坐标也导致true
【解决方案2】:

如果这是用于 iOS 应用程序,请将您的多边形转换为 UIBezierPath,然后使用函数 containtsPoint() 验证您的点是否在该 bezierpath 中

示例(iOS):

func contains(polygon: [CGPoint], test: CGPoint) -> Bool {
        if polygon.count <= 1 {
            return false //or if first point = test -> return true
        }

        var p = UIBezierPath()
        let firstPoint = polygon[0] as CGPoint

        p.moveToPoint(firstPoint)

        for index in 1...polygon.count-1 {
            p.addLineToPoint(polygon[index] as CGPoint)
        }

        p.closePath()

       return p.containsPoint(test)
    }

【讨论】:

  • 不,它不适用于 iOS 应用程序 - 但是这项工作并没有那么糟糕 - 知道 containsPoint() 函数使用什么算法会很有趣
【解决方案3】:

这是 PNPoly 算法的改进实现。我用过,效果很好。

func isPointInsidePolygon(polygon: [CGPoint], test:CGPoint) -> Bool {
 var  i:Int, j:Int = polygon.count - 1
 var  contains = false

 for (i = 0; i < polygon.count; i++) {
    if (((polygon[i].y < test.y && polygon[j].y >= test.y) || (polygon[j].y < test.y && polygon[i].y >= test.y))
        && (polygon[i].x <= test.x || polygon[j].x <= test.x)) {
            contains ^= (polygon[i].x + (test.y - polygon[i].y) / (polygon[j].y - polygon[i].y) * (polygon[j].x - polygon[i].x) < test.x)
    }

    j = i
 }

 return contains
}

进一步查询检查:http://alienryderflex.com/polygon/

【讨论】:

    【解决方案4】:

    也适合我,所以我不知道问题出在哪里

    我还做了一个稍微修改过的版本来使用快速迭代器:

    func contains(polygon: [Point], test: Point) -> Bool {
    
      var pJ=polygon.last!
      var contains = false
      for pI in polygon {
        if ( ((pI.y >= test.y) != (pJ.y >= test.y)) &&
        (test.x <= (pJ.x - pI.x) * (test.y - pI.y) / (pJ.y - pI.y) + pI.x) ){
              contains = !contains
        }
        pJ=pI
      }
      return contains
    }
    

    以下是您在 Playground 中的数组示例的结果:

    contains(poly,Point(x:40,y:40))   -> true
    contains(poly,Point(x:30,y:20))   -> false
    contains(poly,Point(x:40,y:20))   -> true
    contains(poly,Point(x:1,y:1))     -> true
    

    【讨论】:

    • 也许我的问题没有澄清这一点 - 位于多边形边缘的点不应被视为位于内部,因此 (40, 40) 实际上应该评估为假。但是不要浪费时间去寻找另一种算法,更普遍的问题已经用另一种方法解决了
    【解决方案5】:

    swiftMKPolygon的简单扩展:

    extension MKPolygon {
        func contain(coor: CLLocationCoordinate2D) -> Bool {
            let polygonRenderer = MKPolygonRenderer(polygon: self)
            let currentMapPoint: MKMapPoint = MKMapPoint(coor)
            let polygonViewPoint: CGPoint = polygonRenderer.point(for: currentMapPoint)
            if polygonRenderer.path == nil {
              return false
            }else{
              return polygonRenderer.path.contains(polygonViewPoint)
            }
        }
    }
    

    【讨论】:

    • 这个answer 避免使用MKPolygonRenderer 并直接使用MKPolygon
    • MKMapPointForCoordinate(coor) 现在是 MKMapPoint(coor)
    猜你喜欢
    • 1970-01-01
    • 2017-04-29
    • 2019-08-23
    • 1970-01-01
    • 2013-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多