You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为sf线要素交点内部转角做圆角处理实现道路平滑渲染

我正在使用OSM数据制作矢量街道地图,通过为OSM提供的道路线要素添加缓冲区生成道路面。本问题仅和几何处理相关,与OSM无关,因此用基础线要素做简化演示:

library(dplyr)
library(ggplot2)
library(sf)

# 两条代表交叉道路的线
l1 <- st_as_sfc(c("LINESTRING(0 0,0 5)","LINESTRING(-5 3,5 3)"))

# 合并两条线(这样缓冲后二者是相交关系而非重叠)
l2 <- l1 %>% st_union()

ggplot()+
  geom_sf(data = st_buffer(l2, dist = 0.75))

运行代码得到的交叉口效果如下:
交叉口初始效果

我最终希望实现谷歌、OSM、必应等地图API的道路渲染效果,为交叉口内部转角做圆角处理,预期效果如下:
圆角交叉口预期效果

我查阅了sf包的st_系列方法,仅找到st_buffer可控制端帽形状、外角角度数量的参数,未找到处理内角的相关方案。请问是否有简单实用的实现方法?

更新

Lovalery和mrhellmann提供了两种解决方案,我最终选择了mrhellmann的方案适配我的需求,同时我对比了两种方案的优劣势如下,供不同使用场景的用户参考:

先定义用到的不同处理方法:

l1 <- st_as_sfc(c("LINESTRING(0 0,0 5)","LINESTRING(-5 3,5 3)"))
l2 <- l1 %>% st_union()

l2.base <- l2 %>% st_buffer(dist = 0.75, endCapStyle = "ROUND") # 原始方案
l2.neg <- l2.base %>% st_buffer(dist = -0.25) # mrhellmann方案(负缓冲区法)
l2.smth <- l2.base %>% smooth(method = "ksmooth", smoothness = 3, n= 50L)# Lovalery方案(smoothr平滑法)

Lovalery指出负缓冲区法也会对线段端点做平滑处理。对比三种方法可见,负缓冲区法除了影响端帽外,还会按设置的缓冲区距离向所有方向收缩几何要素。
三种初始方法效果对比

l2.neg的问题可以通过将原缓冲区距离加上平滑所用的负缓冲区数值(0.75 + 0.25)解决:

l3 <- l1 %>% st_union %>% st_buffer(dist = 0.75+0.25, endCapStyle = "ROUND")
l3.neg <- st_buffer(l3, dist = -0.25)

调整后负缓冲区法效果

经过调整后两种方法效果接近,l3.neg的结果比l2.smth更贴近原l2几何形态,更推荐l3.neg法的原因如下:

  • 仅使用sf包无需额外依赖
  • 几何还原度更高
  • st_buffer的距离参数使用与地图CRS一致的单位,比smoothr的平滑度参数(基于顶点平均距离的系数)更易理解

但Lovalery提出的线段端点精度问题也值得注意:st_buffer默认的ROUND端帽样式会按缓冲区距离延长线段长度。如果绘图范围会裁剪所有道路端点则无影响,若需要渲染道路端点则需保证长度准确。

更合适的缓冲区设置是endCapStyle = FLAT,此时l2.smth的smoothness参数需要从3调整为0.2。
FLAT端帽下的方法对比

l2和l2.smth方法的线段长度渲染符合预期,l2.neg和l3.neg保留了端点的90度角(非必需的优化点),但线段长度会因负缓冲区距离缩短,这一点smoothr更有优势。

最后要注意smoothr生成的曲线不对称,平滑度数值越高、顶点长度差异越大,不对称越明显,下图是smoothness=1时的效果:
smoothr平滑不对称效果展示
可见较长顶点处的曲线有拉长现象,多数渲染尺度下该问题不明显。

正如Lovalery所说,方案选择取决于具体使用场景,普通用户很难注意到道路比OSM标注短1米或是曲线轻微不对称,图形渲染的精度需求可按需平衡。

完整代码如下:

library(dplyr)
library(ggplot2)
library(sf)
library(smoothr)


l1 <- st_as_sfc(c("LINESTRING(0 0,0 5)","LINESTRING(-5 3,5 3)"))
l2 <- l1 %>% st_union() 

#### endCapStyle = ROUND 场景 ####

l2.base <- l2 %>% st_buffer(dist = 0.75, endCapStyle = "ROUND")
l2.neg <- l2 %>% st_buffer(dist = -0.25)
l2.smth <- l2 %>% smooth(method = "ksmooth", smoothness = 3, n= 50L)

l3 <- l1 %>% st_union %>% st_buffer(dist = 0.75+0.25, endCapStyle = "ROUND")
l3.neg <- st_buffer(l3, dist = -0.25)

ggplot()+
  geom_sf(data = l2.base, colour = "darkgreen", fill = "palegreen")+
  geom_sf(data = l2.neg, colour = "blue", fill = NA)+
  geom_sf(data = l2.smth, colour = "red", fill = NA)+
  geom_sf(data = l3.neg, colour = "cyan", fill = NA)+
  ggtitle("方法对比", 
        subtitle = "ROUND端帽:原始方案(绿色) vs 平滑法(红色) vs 初始负缓冲区(蓝色) vs 调整后负缓冲区(青色)") +
  scale_y_continuous(breaks = c(-1:6), limits = c(-1,6))+
  scale_x_continuous(breaks = c(-6:6), limits = c(-6,6))

#### endCapStyle = FLAT 场景 ####   

l2.base <- l1 %>% st_union() %>% st_buffer(dist = 0.75, endCapStyle = "FLAT")
l2.neg <- l2.base %>% st_buffer(dist = -0.25)
l2.smth <- l2.base %>% smooth(method = "ksmooth", smoothness = 0.2, n= 50L)

l3 <- l1 %>% st_union %>% st_buffer(dist = 0.75+0.25, endCapStyle = "FLAT")
l3.neg <- st_buffer(l3, dist = -0.25)

ggplot()+
  geom_sf(data = l2.base, colour = "darkgreen", fill = "palegreen")+
  geom_sf(data = l2.neg, colour = "blue", fill = NA)+
  geom_sf(data = l2.smth, colour = "red", fill = NA)+
  geom_sf(data = l3.neg, colour = "cyan", fill = NA)+
  ggtitle("方法对比", 
          subtitle = "FLAT端帽:原始方案(绿色) vs 平滑法(红色) vs 初始负缓冲区(蓝色) vs 调整后负缓冲区(青色)") +
  scale_y_continuous(breaks = c(-1:6), limits = c(-1,6))+
  scale_x_continuous(breaks = c(-6:6), limits = c(-6,6))

#### 展示smoothr方法的不对称性 ####

l4.smth <- l2.base %>% smooth(method = "ksmooth", smoothness = 1, n= 50L)

ggplot()+
  geom_sf(data = l2.base, colour = "darkgreen", fill = "palegreen")+
  geom_sf(data = l4.smth, colour = "red", fill = NA)+
  geom_sf(data = l3.neg, colour = "blue", fill = NA)+
  geom_abline(slope = 1, intercept = 3, colour = "gray", linetype = "dashed")+
  ggtitle("方法对比", 
          subtitle = "FLAT端帽:调整后负缓冲区(蓝色) vs smoothr平滑度=1(红色)") +
  coord_sf(xlim = c(0,5), ylim = c(3,5))

内容的提问来源于stack exchange,提问作者AWaddington

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 11:18:04