在R语言中计算多边形和点之间的距离

4

我有一个不一定是凸多边形,没有交叉点,以及一个在多边形外的点。我想知道如何在二维空间中最有效地计算欧几里得距离。在R中是否有标准方法?

我的第一个想法是计算多边形所有线段(无限延伸为直线)的最小距离,然后使用线段起点和勾股定理计算点到每条线段的距离。

您是否知道有实现高效算法的软件包?


1
我不知道有没有预先打包好的东西可以做到你要求的功能。你能给我们更多信息吗?在计算距离之前,你对多边形有了解吗?还是它可以是任何形状?我假设由于你提到了勾股定理,所以这是在笛卡尔空间中进行的。 - Dinre
1
根据提出的方法,多边形似乎不允许内部交叉,这表明顶点具有旋转顺序(顺时针或逆时针)。这是真的吗? - Dinre
1
你看过 spatstat 包吗?我知道他们至少有一些线段和点集的距离算法。 - Carl Witthoft
4个回答

8
您可以使用rgeos包gDistance方法。这将要求您准备好您的几何图形,从您拥有的数据(我假设它是一个数据框或类似物)创建spgeom对象。rgeos文档非常详细(请参见CRAN页面上该软件包的PDF手册),以下是gDistance文档中的一个相关示例:
pt1 = readWKT("POINT(0.5 0.5)")
pt2 = readWKT("POINT(2 2)")
p1 = readWKT("POLYGON((0 0,1 0,1 1,0 1,0 0))")
p2 = readWKT("POLYGON((2 0,3 1,4 0,2 0))")
gDistance(pt1,pt2)
gDistance(p1,pt1)
gDistance(p1,pt2)
gDistance(p1,p2)

readWKT也包含在rgeos中。

rgeos基于GEOS库,是几何计算中事实上的标准之一。如果您不想重复造轮子,这是一个不错的选择。


6

我决定回来写一个理论解决方案,供后人参考。这不是最简洁的例子,但对于那些想知道如何手动解决此类问题的人来说,它是完全透明的。

理论算法

首先,我们的假设。

  1. 我们假设多边形的顶点按顺时针或逆时针旋转顺序指定,并且多边形的线不能相交。这意味着我们有一个正常的几何多边形,而不是某种奇怪定义的矢量图形形状。
  2. 我们假设这是一组笛卡尔坐标,使用代表二维平面上位置的“x”和“y”值。
  3. 我们假设该点必须位于多边形内部区域之外。
  4. 最后,我们假设所需距离是该点与多边形周长上无限点之间的最小距离。

现在,在编码之前,我们应该用基本术语写出我们想要做的事情。我们可以假设多边形和多边形外的点之间的最短距离将始终是以下两种情况之一:多边形的顶点或两个顶点之间的线上的一个点。考虑到这一点,我们执行以下步骤:

  1. 计算所有顶点与目标点之间的距离。
  2. 找到最靠近目标点的两个顶点。
  3. 如果任何一个: (a) 两个最接近的顶点不相邻或 (b) 任一顶点的内角大于或等于90度, 则最接近的顶点是最接近的点。计算最接近点和目标点之间的距离。
  4. 否则,计算两点之间形成的三角形的高度。

我们基本上只是想看看顶点是否最接近该点,或者线上的点是否最接近该点。我们必须使用一些三角函数使其正常工作。

代码

为了使其正常工作,我们要避免任何“for”循环,并且在查看整个多边形顶点列表时仅使用矢量化函数。幸运的是,在R中,这非常容易。我们接受一个带有“x”和“y”列的数据框,用于多边形的顶点,并接受一个具有一个“x”和“y”值的向量,表示点的位置。

get_Point_Dist_from_Polygon <- function(.polygon, .point){

    # Calculate all vertex distances from the target point.
    vertex_Distance <- sqrt((.point[1] - .polygon$x)^2 + (.point[2] - .polygon$y)^2)

    # Select two closest vertices.
    min_1_Index <- which.min(vertex_Distance)
    min_2_Index <- which.min(vertex_Distance[-min_1_Index])

    # Calculate lengths of triangle sides made of
    # the target point and two closest points.
    a <- vertex_Distance[min_1_Index]
    b <- vertex_Distance[min_2_Index]
    c <- sqrt(diff(.polygon$x[c(min_1_Index, min_2_Index)])^2 + diff(.polygon$y[c(min_1_Index, min_2_Index)])^2)

    if(abs(min_1_Index - min_2_Index) != 1 |
        acos((b^2 + c^2 - a^2)/(2*b*c)) >= pi/2 | 
        acos((a^2 + c^2 - b^2)/(2*a*c)) >= pi/2
        ){
        # Step 3 of algorithm.
        return(vertex_Distance[min_1_Index])
    } else {
        # Step 4 of algorithm.
        # Here we are using the law of cosines.
        return(sqrt((a+b-c) * (a-b+c) * (-a+b+c) * (a+b+c)) / (2 * c))
    }
}

演示

polygon <- read.table(text="
x,  y
0,  1
1,  0.8
2,  1.3
3,  1.4
2.5,0.3
1.5,0.5
0.5,0.1", header=TRUE, sep=",")

point <- c(3.2, 4.1)

get_Point_Dist_from_Polygon(polygon, point)
# 2.707397

也许我误解了这个解决方案,但是由点到最近的两个顶点形成的多边形的某个面不一定是距离该点最近的面。 - richard
我完全同意Richard的观点。Dinre,你的解决方案在一个简单的例子中失败了,其中查询点位于四边形下方,底部有一条非常长的边,最靠近查询点的两个顶点位于边的正上方(不在与查询点相同的边侧)。 - Vadim

1
否则:
p2poly <- function(pt, poly){
    # Closing the polygon
    if(!identical(poly[1,],poly[nrow(poly),])){poly<-rbind(poly,poly[1,])}
    # A simple distance function
    dis <- function(x0,x1,y0,y1){sqrt((x0-x1)^2 +(y0-y1)^2)}
    d <- c()   # Your distance vector
    for(i in 1:(nrow(poly)-1)){
        ba <- c((pt[1]-poly[i,1]),(pt[2]-poly[i,2])) #Vector BA
        bc <- c((poly[i+1,1]-poly[i,1]),(poly[i+1,2]-poly[i,2])) #Vector BC
        dbc <- dis(poly[i+1,1],poly[i,1],poly[i+1,2],poly[i,2]) #Distance BC
        dp <- (ba[1]*bc[1]+ba[2]*bc[2])/dbc          #Projection of A on BC
        if(dp<=0){ #If projection is outside of BC on B side
            d[i] <- dis(pt[1],poly[i,1],pt[2],poly[i,2])
            }else if(dp>=dbc){ #If projection is outside of BC on C side
                d[i] <- dis(poly[i+1,1],pt[1],poly[i+1,2],pt[2])
                }else{ #If projection is inside of BC
                    d[i] <- sqrt(abs((ba[1]^2 +ba[2]^2)-dp^2))
                    }
        }
    min(d)
    }

例子:

pt <- c(3,2)
triangle <- matrix(c(1,3,2,3,4,2),byrow=T, nrow=3)
p2poly(pt,triangle)
[1] 0.3162278

底部有一个示例。Poly可以是矩阵、数组或数据框,只要每行都是顶点坐标。这是我能想到的最简单的算法,就在我脑海中。 - plannapus
我喜欢你的努力,但是被接受的答案更容易实现。 - Bob Jansen

0

我使用了geosphere包中的distm()函数来计算坐标系中点和顶点之间的距离。此外,您还可以通过将dis <- function(x0,x1,y0,y1){sqrt((x0-x1)^2 +(y0-y1)^2)}替换distm()来轻松进行一些更改。

algo.p2poly <- function(pt, poly){
  if(!identical(poly[1,],poly[nrow(poly),])){poly<-rbind(poly,poly[1,])}
  library(geosphere)
  n <- nrow(poly) - 1
  pa <- distm(pt, poly[1:n, ])
  pb <- distm(pt, poly[2:(n+1), ])
  ab <- diag(distm(poly[1:n, ], poly[2:(n+1), ]))
  p <- (pa + pb + ab) / 2
  d <- 2 * sqrt(p * (p - pa) * (p - pb) * (p - ab)) / ab
  cosa <- (pa^2 + ab^2 - pb^2) / (2 * pa * ab)
  cosb <- (pb^2 + ab^2 - pa^2) / (2 * pb * ab)
  d[which(cosa <= 0)] <- pa[which(cosa <= 0)]
  d[which(cosb <= 0)] <- pb[which(cosb <= 0)]
  return(min(d))
}

例子:

poly <- matrix(c(114.33508, 114.33616,
                 114.33551, 114.33824,
                 114.34629, 114.35053,
                 114.35592, 114.35951, 
                 114.36275, 114.35340, 
                 114.35391, 114.34715,
                 114.34385, 114.34349,
                 114.33896, 114.33917,
                 30.48271, 30.47791,
                 30.47567, 30.47356, 
                 30.46876, 30.46851,
                 30.46882, 30.46770, 
                 30.47219, 30.47356,
                 30.47499, 30.47673,
                 30.47405, 30.47723, 
                 30.47872, 30.48320),
               byrow = F, nrow = 16)
pt1 <- c(114.33508, 30.48271)
pt2 <- c(114.6351, 30.98271)
algo.p2poly(pt1, poly)
algo.p2poly(pt2, poly)

结果:

> algo.p2poly(pt1, poly)
[1] 0
> algo.p2poly(pt2, poly)
[1] 62399.81

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接