在数据框中计算多个纬度、经度点的中心点

3

我有一个数据集,看起来像这样:

site   lat      long 
bras2  41.21   -115.11
tex4   45.3    -112.31
bras2  41.15   -115.15 
bras2  41.12   -115.19

对于具有相同“site”名称的样本,我想计算它们的中心点,然后将其添加为数据集的一列。一些“site”名称重复两次,其他三次,其他四次。

就像这样:

site   lat      long    centre_lat  centre_long 
bras2  41.21   -115.11  value here     value here
tex4   45.3    -112.31  45.3           -112.31 
bras2  41.15   -115.15  value here     value here
bras2  41.12   -115.19  value here     value here

我应该如何做到这一点?

1
中点是位于连接两个点的测地线上且与这两个点等距离的单一点。无论你认为三个点之间的中点是什么,midPoint函数并不计算这个。它只接受两个点作为参数。你是不是在想多个点的质心? - undefined
是的,一个质心。非常感谢,我之前对中点的理解是错误的。 - undefined
3个回答

6
如果您正在使用空间数据,您应该考虑使用sf软件包。它很好地处理几何体和操作函数。以下代码展示了同时使用sf::st_centroidgeosphere::centroid。我更喜欢sf的处理方式。
df <- read.table(header=TRUE, text= "site   lat      long 
bras2  41.21   -115.11
tex4   45.3    -112.31
bras2  41.15   -115.15 
bras2  41.12   -115.19")


library(dplyr)
library(geosphere)
library(sf)

# Using sf's st_centroid
df_sf <- st_as_sf(df, coords = c('long', 'lat'))

centroids_sf <- df_sf %>%
  group_by(site) %>% 
  summarize(geometry = st_union(geometry)) %>% 
  st_centroid

  
# Using geosphere::centroid
centroids_geoshpere <- df_sf %>%
  group_by(site) %>%
  filter(n() >2)  %>% ## geosphere needs polygons therefore 3+ points
  st_union() %>%
  st_cast('POLYGON') %>%
  as('Spatial') %>% # geoshpere expects SpatialPolygons objects
  centroid() 
  

centroids_geoshpere
#>         [,1]     [,2]
#> [1,] -115.15 41.16001
centroids_sf
#> Simple feature collection with 2 features and 1 field
#> geometry type:  POINT
#> dimension:      XY
#> bbox:           xmin: -115.15 ymin: 41.16 xmax: -112.31 ymax: 45.3
#> CRS:            NA
#> # A tibble: 2 x 2
#>   site         geometry
#> * <chr>         <POINT>
#> 1 bras2 (-115.15 41.16)
#> 2 tex4   (-112.31 45.3)

看起来它们非常接近同一个点。我认为geosphere :: centroid无法给出单个点的重心,但可能是错误的。sf :: st_centroid对1,2或多个点没有问题。 此内容于2020-12-20由reprex package(v0.3.0)创建


嗨!非常感谢。我可以问一下,这段代码如何处理只有两个点而无法构成多边形的情况吗?它会忽略这些情况吗? - undefined
1
上面的geosphere方法将删除具有少于3个点的位置。sf方法适用于1、2和3+个点。我建议使用sf方法。 - undefined

2

使用gsub去掉站点编号后,您可以使用ave计算按站点名称分组的平均值。

within(dat, {
  g <- gsub("\\d", "", site)
  mid.lat <- ave(lat, g)
  mid.long <- ave(long, g)
  rm(g)
})
#    site   lat    long mid.long mid.lat
# 1 bras2 41.21 -115.11 -115.150  41.160
# 2  tex4 45.30 -112.31 -112.310  45.300
# 3 bras2 41.15 -115.15 -115.150  41.160
# 4 bras2 41.12 -115.19 -115.150  41.160
# 5  foo1 42.10 -123.10 -123.225  42.225
# 6  foo2 42.20 -123.20 -123.225  42.225
# 7 foo11 42.30 -123.30 -123.225  42.225
# 8 foo12 42.30 -123.30 -123.225  42.225

或者,如果您依赖于NA

within(dat, {
  g <- gsub("\\d", "", site)
  n <- ave(site, g, FUN=length)
  mid.lat <- NA
  mid.long <- NA
  mid.lat[n > 1] <- ave(lat[n > 1], g[n > 1])
  mid.long[n > 1] <- ave(long[n > 1], g[n > 1])
  rm(g, n)
  })
#    site   lat    long mid.long mid.lat
# 1 bras2 41.21 -115.11 -115.150  41.160
# 2  tex4 45.30 -112.31       NA      NA
# 3 bras2 41.15 -115.15 -115.150  41.160
# 4 bras2 41.12 -115.19 -115.150  41.160
# 5  foo1 42.10 -123.10 -123.225  42.225
# 6  foo2 42.20 -123.20 -123.225  42.225
# 7 foo11 42.30 -123.30 -123.225  42.225
# 8 foo12 42.30 -123.30 -123.225  42.225

数据:

dat <- structure(list(site = c("bras2", "tex4", "bras2", "bras2", "foo1", 
"foo2", "foo11", "foo12"), lat = c(41.21, 45.3, 41.15, 41.12, 
42.1, 42.2, 42.3, 42.3), long = c(-115.11, -112.31, -115.15, 
-115.19, -123.1, -123.2, -123.3, -123.3)), class = "data.frame", row.names = c(NA, 
-8L))

嗨!谢谢。一个问题 - 在这种情况下,“ave”是指平均值吗?所以,它只是计算具有相同“site”名称的所有纬度和经度的平均值吗?我不确定是否可以像这样计算中心点,因为地球是弯曲的。如果我错了,请纠正我。 - undefined
1
@fifigoblin 是的,ave 函数将第一个参数按照第二个参数进行分组,并应用默认为 mean 的函数,详见 help("ave")。我承认自己不是专家,但是当将 GPS 坐标视为矩形投影时,曲率并不重要。在这个答案中,他们使用平均值类似地计算质心。 - undefined
1
@fifigoblin 这个计算将基于这个质心公式 - undefined

2

地球物理包中有一个函数centroid,可以解决这类问题。
只要形状中有一个以上的点,它就很容易。下面的大部分代码都涉及处理上述示例中的单个点情况。

df <- read.table(header=TRUE, text= "site   lat      long 
bras2  41.21   -115.11
tex4   45.3    -112.31
bras2  41.15   -115.15 
bras2  41.12   -115.19")


library(dplyr)
library(geosphere)

df %>% group_by(side) %>% centroid(.[ ,c(3,2)])

sites <- split(df, df$site)
results <-lapply(sites, function(x) {
   if(nrow(x)>1 ) {
     value <- as.data.frame(centroid(x[, c(3,2)]))
   }
   else {
      value <- x[1, c(3,2)]
      names(value) <- c("lon", "lat")
   }
   value$site <- x$site[1]
   value
})

answer<-bind_rows(results)

      lon      lat  site
1 -115.15 41.16001 bras2
2 -112.31 45.30000  tex4

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