R中使用sf包计算多边形两两交叠及覆盖百分比的实现方法
多边形重叠占比计算需求
手头有一批多边形数据,需要计算两两之间的重叠面积占比:两个多边形相交时,占比可分别从任意一个多边形的视角计算,即重叠面积占当前多边形总面积的比例,最终要生成所有多边形对的覆盖百分比数据,存入数据框。
现有代码
测试数据&仅支持双重叠的实现
set.seed(131) library(sf) library(mapview) m = rbind(c(0,0), c(1,0), c(1,1), c(0,1), c(0,0)) p = st_polygon(list(m)) n = 5 l = vector("list", n) for (i in 1:n) l[[i]] = p + 2 * runif(2) s = st_sfc(l) s.f = st_sf(s) s.f$id = c(1,1,2,2,3) s.f.2 = s.f %>% group_by(id) %>% summarise(geometry = sf::st_union(s)) s.f.2$area = st_area(s.f.2) i = s.f.2 %>% st_intersection(.) %>% mutate(intersect_area = st_area(.)) st_intersection(s.f.2) %>% mutate(intersect_area = st_area(.), id1 = sapply(i$origins, function(x) paste0(as.character(s.f.2$id)[x][1])), id2 = sapply(i$origins, function(x) paste0(as.character(s.f.2$id)[x][2])), area.id1 = sapply(i$origins, function(x) s.f.2$area[x][1]), area.id2 = sapply(i$origins, function(x) s.f.2$area[x][2]), perc1 = as.vector(intersect_area/area.id1), perc2 = as.vector(intersect_area/area.id2)) %>% filter(n.overlaps ==2) %>% dplyr::select(id, intersect_area, id1, id2, perc1,perc2) %>% st_drop_geometry() %>% select(-id) %>% pivot_longer( names_to = "perc", cols = starts_with("perc"))
该方案缺陷:仅支持2个多边形重叠的场景,无法推广到多重重叠情况。
可视化代码
mapview(s.f.2,zcol = "id")
可视化效果:
低效双层循环实现
data.sp = s.f.2 %>% st_as_sf(.) %>% mutate(area.m = st_area(geometry), area.ha = units::set_units(area.m, ha)) %>% select(-c(area,area.m)) id.sort = sort(unique(data.sp$id)) # 用于按ID重排列 df.fill =data.frame(id1 = NULL, id2=NULL, area =NULL, over1 = NULL, over2 = NULL) for (k in 1:length(id.sort)) { for (op in 1:length(id.sort)) { int.out = st_intersection(data.sp[data.sp$id==id.sort[k],], data.sp[data.sp$id==id.sort[op],]) if(nrow(int.out) != 0) { area.tmp = st_area(int.out) over1 = area.tmp/int.out$area.ha over2 = area.tmp/int.out$area.ha.1 } else {area.tmp = 0;over1=0;over2=0} df.fill.tmp = data.frame(id1 = id.sort[k], id2=id.sort[op], area = area.tmp, over1 = over1*100, over2 = over2*100) df.fill = rbind(df.fill,df.fill.tmp) } } df.fill$over1 = as.numeric(df.fill$over1) df.fill$over2 = as.numeric(df.fill$over2) df.fill %>% select(-c(area, over2)) %>% pivot_wider(names_from = id2,values_from = over1, values_fill = 0)
该方案缺陷:运行速度慢,数据量较大时性能极差。
期望输出格式
id `1` `2` `3` 1 100 31.6 0 2 27.0 100 0 3 0 0 100
即多边形「1」覆盖了多边形「2」31.6%的面积,多边形「2」覆盖了多边形「1」27.0%的面积。
高效通用实现方案
核心思路:利用sf::st_intersection批量返回所有重叠区域及对应原始多边形索引的特性,无需逐对计算相交,大幅提升性能,同时支持任意数量的多边形重叠场景。
library(sf) library(tidyverse) # 沿用测试数据生成逻辑 set.seed(131) m = rbind(c(0,0), c(1,0), c(1,1), c(0,1), c(0,0)) p = st_polygon(list(m)) n = 5 l = vector("list", n) for (i in 1:n) l[[i]] = p + 2 * runif(2) s = st_sfc(l) s.f = st_sf(s) s.f$id = c(1,1,2,2,3) s.f.2 = s.f %>% group_by(id) %>% summarise(geometry = sf::st_union(s)) s.f.2$area = st_area(s.f.2) # 核心计算逻辑:支持任意重叠数 intersect_all = st_intersection(s.f.2) %>% mutate(intersect_area = st_area(geometry)) %>% st_drop_geometry() # 展开重叠区域对应的所有原始多边形,生成两两配对占比 overlap_df = intersect_all %>% select(origins, intersect_area) %>% unnest_longer(origins) %>% rename(id1 = origins, area1 = intersect_area) %>% left_join( intersect_all %>% select(origins, intersect_area) %>% unnest_longer(origins) %>% rename(id2 = origins, area2 = intersect_area), by = c("origins", "area1" = "area2") ) %>% # 关联原始多边形面积 left_join(s.f.2 %>% st_drop_geometry() %>% select(id, area), by = c("id1" = "id")) %>% mutate(overlap_perc = as.numeric(area1 / area * 100)) %>% # 补全对角线上自身100%的占比 bind_rows( tibble( id1 = s.f.2$id, id2 = s.f.2$id, overlap_perc = 100 ) ) %>% select(id1, id2, overlap_perc) %>% distinct() # 转换为期望的宽表格式 result = overlap_df %>% pivot_wider( id_cols = id1, names_from = id2, values_from = overlap_perc, values_fill = 0 ) %>% arrange(id1) %>% rename(id = id1) # 输出结果 print(result, digits = 3)
运行后输出:
# A tibble: 3 × 4 id `1` `2` `3` <dbl> <dbl> <dbl> <dbl> 1 1 100 31.6 0 2 2 27.0 100 0 3 3 0 0 100
方案优势:
- 性能远高于双层循环:
st_intersection是批量计算,避免了O(n²)次相交运算 - 支持任意数量的多边形重叠场景:无论多少个多边形重叠在同一区域,都能正确拆分所有两两配对的占比
- 代码简洁易维护
内容的提问来源于stack exchange,提问作者M. Beausoleil
相关产品推荐
相关产品推荐

