如何修圆SF线交点的内角?

6
我正在使用OSM数据创建矢量街道地图。对于道路,我使用OSM提供的线几何体,并添加缓冲区将线转换为类似道路的几何体。
我的问题与几何体有关,而不是OSM,因此我将使用基本线来简化。
library(dplyr)
library(ggplot2)
library(sf)

# two lines representing an intersection of roads
l1 <- st_as_sfc(c("LINESTRING(0 0,0 5)","LINESTRING(-5 3,5 3)"))

# union the two lines (so they "intersect" instead of overlap once buffered
l2 <- l1 %>% st_union()

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

这将产生类似于以下内容:

Intersection

最终,我试图像Google、OSM、Bing等地图API一样渲染道路。它们将内角倒圆,如下所示:

Filleted corners

我已经在sf包中搜索了st_系列方法,但没有找到解决方案。我找到的最接近的是some arguments 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") # OP
l2.neg <- l2.base %>% st_buffer(dist = -0.25) # mrhellmann (st_buffer)
l2.smth <- l2.base %>% smooth(method = "ksmooth", smoothness = 3, n= 50L)# Lovalery (smoothr)

Lovalery 提出了一个好问题,负缓冲方法也可以使端点/极端值更平滑。
比较这三种方法表明,负缓冲方法影响的不仅是端盖,它会将几何体从所有侧面收缩 st_buffer 中使用的距离。

enter image description here

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)

enter image description here

通过此调整,这些方法具有类似的结果。事实上,l3 可能比 l2.smth 更类似于 l2.base。在这一点上,我可能会偏向于选择 l3.neg 方法,理由如下:
  1. 它使用相同的软件包(不是很重要),
  2. 它似乎稍微更接近于 l2 几何形状,
  3. 对于 st_buffer 的距离值(使用与地图的 crs 相同的单位),比起 smoothr 的平滑设置(顶点之间的平均距离因子 -- 更多内容请参见下文)更容易为用户所理解。

然而,Lovalery 关于线段端点准确性的观点提出了另一个好的问题。st_buffer 默认(ROUND)的 endCapStyle 会将线段长度扩展至缓冲区距离设置。

如果绘图限制截断所有街道末端,这是无关紧要的。如果街道的末端需要呈现在绘图中,则它们应该具有正确的长度。

更适当的缓冲设置是 endCapStyle = FLAT。请注意,当不匹配圆形线帽时,l2.smth 的平滑度设置从smoothness = 3 更改为smoothness = 0.2

enter image description here

对于l2和l2.smth方法,线条长度呈现正常。l2.neg和l3.neg保留了末端的90度角(这是一个“好有”但不是关键特性),但缩短了线条长度,距离为平滑缓冲区中使用的负距离。点给smoothr
最后,要挑剔一下,smoothr提供的曲线不是对称的。平滑值越大,顶点长度之间的差异越大,则不对称性越明显。这是使用smoothness = 1smoothr方法。

enter image description here

请注意较长顶点上曲线的延伸。在大多数比例尺下可能不会引起注意。
正如Lovalery所提到的,这取决于个体使用情况最好的选择。此外,普通观众是否会注意到街道比OSM说的短一米,或者曲线略微不对称?精度就像图形一样有趣...完美的精度可能并不重要,但它涉及到数学和数字,所以感觉应该很重要。
以上所有代码:
library(dplyr)
library(ggplot2)
library(sf)
library(smoothr)


l1 <- st_as_sfc(c("LINESTRING(0 0,0 5)","LINESTRING(-5 3,5 3)"))#,"LINESTRING(-5 7,5 7)"))
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("Method Comparison", 
        subtitle = "EndCapStyle ROUND: l2 (green) v l2.smth (red), l2.neg (blue), l3.neg (cyan)") +
  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("Method Comparison", 
          subtitle = "EndCapStyle FLAT: l2 (green) v l2.smth (red), l2.neg (blue), l3.neg (cyan)") +
  scale_y_continuous(breaks = c(-1:6), limits = c(-1,6))+
  scale_x_continuous(breaks = c(-6:6), limits = c(-6,6))

#### illustrate asymmetry of smoothr method as l4.smth ####

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("Method Comparison", 
          subtitle = "EndCapStyle FLAT: l3.neg (blue), smoothr smoothness = 1 (red)") +
  coord_sf(xlim = c(0,5), ylim = c(3,5))
2个回答

2
你可以缓冲线路,然后对该结果进行负缓冲。
ggplot()+
  geom_sf(data = st_buffer(st_buffer(l2, dist = 1), dist = -0.5))

enter image description here

更详细,更少嵌套:

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

# two lines representing an intersection of roads
l1 <- st_as_sfc(c("LINESTRING(0 0,0 5)","LINESTRING(-5 3,5 3)"))

# union the two lines (so they "intersect" instead of overlap once buffered
l2 <- l1 %>% st_union()

# buffer the lines with a positive dist argument
l2_buffered <- st_buffer(l2, dist = 1)

# un-buffer (negative buffer?) the lines for rounded inner corners
#  with a negative dist argument
l2_unbuffered <- st_buffer(l2_buffered, dist = -0.5)

两者合在一起,除了曲线内角,黑色多边形会覆盖红色去缓冲的多边形:

ggplot()+
  geom_sf(data = st_buffer(st_buffer(l2, dist = 1.5), dist = -0.75), fill = NA, col = 'red') + 
  geom_sf(data = st_buffer(l2, dist = .75), fill = NA, col = 'black')

enter image description here

放大了一个角落:
ggplot()+
  geom_sf(data = st_buffer(st_buffer(l2, dist = 1.5), dist = -0.75), fill = NA, col = 'red') + 
  geom_sf(data = st_buffer(l2, dist = .75), fill = NA, col = 'black') + 
  coord_sf(xlim = c(-2,0), ylim = c(1,3))

enter image description here


谢谢,这非常有用。我想知道您对我的更新与下面lovalery的回复进行比较的想法。这似乎是我需要的最简单的解决方案,但有一些缺点。 - AWaddington
1
@AWaddington 使用负缓冲区方法,首先考虑缓冲区过大,然后使用负缓冲区来达到正确的大小。对于正方形端盖,请使用第一个(正)st_buffer调用参数endCapStyle ='FLAT'或'SQUARE'。我很快会发布一篇编辑文章。 - mrhellmann
是的,这就是我在更新中所做的。我认为这是最接近我所需的。实际上只有行长度可能会稍微有些问题(但这可能并不是问题)。 - AWaddington
1
@AWaddington 听起来你已经基本弄清楚了。我不知道有没有办法将一个线串只在正确的方向上转换为多边形以获得类似道路的多边形。考虑到你只需要将线串从0延伸到约16英尺(5米)以获得双车道道路,这应该只会使道路两端多出约8英尺(2.5米)。这很可能不足以产生影响,特别是如果你不是在绘制非常小的区域(<<1平方英里)。 - mrhellmann

1
我建议采用与@mrhellmann提出的方法不同的替代方案。这样,您就可以根据自己的目标选择最合适的方案。
我建议您使用“smoothr”包来平滑缓冲多边形。唯一的区别是在多边形的端点/极值处也进行了平滑处理,因此它们在该点不会完全重叠原始多边形边界(如下面的示例所示),尽管我不确定这种差异对您的目的是否重要。 示例代码
library(sf)
library(smoothr) 

# two lines representing an intersection of roads
l1 <- st_as_sfc(c("LINESTRING(0 0,0 5)","LINESTRING(-5 3,5 3)"))

# union the two lines (so they "intersect" instead of overlap once buffered
l2 <- l1 %>% st_union()
  • 缓冲区平滑

当然,您可以修改算法和参数以控制平滑的类型和程度,以适应您的需求,参见?smooth

l2_smooth <- smooth(st_buffer(l2, dist = 0.75), 
                    method = "ksmooth", smoothness = 3, n = 50L)
  • 平滑多边形概述
ggplot2::ggplot()+
  ggplot2::geom_sf(data = l2_smooth)

  • 平滑多边形(红色)与原始多边形(黑色)的总体比较
ggplot2::ggplot()+
  ggplot2::geom_sf(data = l2_smooth, col = "red", fill = NA) + 
  ggplot2::geom_sf(data = st_buffer(l2, dist = .75),  col = 'black', fill = NA)

  • 放大一个角落
ggplot2::ggplot()+
  ggplot2::geom_sf(data = l2_smooth, col = "red", fill = NA) + 
  ggplot2::geom_sf(data = st_buffer(l2, dist = .75),  col = 'black', fill = NA) +
  ggplot2::coord_sf(xlim = c(-1.2,-0.6), ylim = c(1.9,2.4))

  • 放大一个极端
ggplot2::ggplot()+
  ggplot2::geom_sf(data = l2_smooth, col = "red", fill = NA) + 
  ggplot2::geom_sf(data = st_buffer(l2, dist = .75),  col = 'black', fill = NA) +
  ggplot2::coord_sf(xlim = c(-6,-4), ylim = c(2,4))

reprex包(v2.0.1)于2021年10月16日创建


编辑/更新

首先,非常感谢@AWaddington对不同提议解决方案的深入报告,包括您的、@mrhellmann的答案和我的。这样的建设性交流总是很好的。

所以,正如您邀请我做的那样,本次编辑/更新的目的是澄清和改进我提出的方法,并给出我对不同方法的比较分析。

我同意您和mrhellmann在以下几点上的观点:

  1. mrhellmann提出的解决方案确实不需要任何额外的软件包(在某些情况下可能是一个优势)。在这一点上,我无法“争论”,我很高兴地屈服;-)
然而,可以通过修正平滑方法使您提出的关于参数endCapStyle = "ROUND"endCapStyle = "FLAT"之间段落结尾差异的观点无效。从地图制图的角度来看,“我们处于线的厚度中”(当然,在非常大的比例尺下除外)。话虽如此,让我们保守一些并保持实际长度:如果由于某些原因(地理计算)需要精确到最近一米的长度,则始终更好。因此,在下面的reprex中,我仅使用了参数endCapStyle = FLAT,认为这个选项适用于所有情况。
关于第二点,如下面的reprex所示,甚至可以将内角的四舍五入更接近直角,而不是mrhellman提出的解决方案。通过改变smooth函数的smoothness参数,可以轻松选择圆角的形状:它越接近0,圆角就越接近直角。 另一方面,关于您正确提出的对称性问题,只需在平滑之前增加节点/点数即可。事实上,平滑方法依赖于这些节点/点,如果它们在角度的每一侧分布不均匀/对称,则平滑本身就无法对称。要解决这个问题,只需增加点数(在下面的reprex中,我已将函数参数化为每米插入一个节点/点)。
最后,由于可以按地图单位(在此reprex中,每米)插入附加节点/点,因此第3点中突出的差异似乎不再有效。
总之,我认为mrhellmann的方法和我的方法之间的剩余差异非常微弱,我认为任何一种解决方案都可以。

优点:无需额外的包,保持线段末端的直角。

缺点:内部角度的圆角调整不太容易(仅为个人意见!)

lovalery的方法:

优点:更容易调整内部角度的圆角。

缺点:需要额外的包,在线段末端略有圆角(但在正常地图比例下不可见)

Reprex

注意:此reprex仅保留了endCapStyle = "FLAT"选项(详见上文解释)

  • 使用l2.base和l3.neg解决方案进行比较
library(sf)
library(smoothr)

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 = "FLAT") # OP FLAT version

l3 <- l1 %>% st_union %>% st_buffer(dist = 0.75+0.25, endCapStyle = "FLAT") # mrhellmann (st_buffer) modified, FLAT version
l3.neg <- st_buffer(l3, dist = -0.25)
  • 在保持内角对称的同时平滑缓冲区(即l2.base)= lovalery(smoothr)修改版,FLAT版本
# 1. Densify the number of nodes/points (here, one node every meters, cf. max_distance argument)
l3.smth.dens <- smooth(st_buffer(l2, dist = 0.75,endCapStyle = "FLAT"), method = "densify", max_distance = 1)

# 2. Smooth with kernel method (as in my first post). Test of two smoothness indices (i.e. 0.2 and 1)
l3.smth.dens1 <- smooth(l3.smth.dens, method = "ksmooth", smoothness = 0.2, n = 50L)

l3.smth.dens2 <- smooth(l3.smth.dens, method = "ksmooth", smoothness = 1, n = 50L)
  • 将两个平滑多边形(红色,其中一个具有平滑指数=1)与原始多边形(黑色)和mrhellman的多边形(蓝色)进行总体比较
ggplot2::ggplot()+
  ggplot2::geom_sf(data = l3.smth.dens2, col = "red", fill = NA) + 
  ggplot2::geom_sf(data = l3.neg, col = "blue", fill = NA) +
  ggplot2::geom_sf(data = l2.base,  col = 'black', fill = NA) +
  ggplot2::geom_abline(intercept = 3, slope = 1, color="gray",linetype="dashed", size=1) 

  • 在一个角落里放大:比较两个平滑的多边形(红色-平滑度指数=1-和橙色-平滑度指数=0.2)与原始多边形(黑色)和mrhellman的多边形(蓝色)

请注意,当平滑度指数=0.2时,内角的圆角接近于原始几何图形,而不是mrhellman解决方案的圆角(如果您想要大约相同的圆角,请将平滑度指数设置为约0.6)

ggplot2::ggplot()+
  ggplot2::geom_sf(data = l3.smth.dens2, col = "red", fill = NA) + 
  ggplot2::geom_sf(data = l3.smth.dens1, col = "orange", fill = NA) +
  ggplot2::geom_sf(data = l3.neg, col = "blue", fill = NA) +
  ggplot2::geom_sf(data = l2.base,  col = 'black', fill = NA) +
  ggplot2::geom_abline(intercept = 3, slope = 1, color="gray",linetype="dashed", size=1) +
  ggplot2::coord_sf(xlim = c(-1.55,-0.6), ylim = c(1.4,2.4))

  • 放大一个极端:将两个平滑的多边形(红色-平滑度指数=1和橙色-平滑度指数=0.2)与原始多边形(黑色)和mrhellman的多边形(蓝色)进行比较。

请注意,使用平滑度指数=0.2时,尽管渲染比例不成比例,但末端的舍入仍然微不足道。

ggplot2::ggplot()+
  ggplot2::geom_sf(data = l3.smth.dens2, col = "red", fill = NA) +
  ggplot2::geom_sf(data = l3.smth.dens1, col = "orange", fill = NA) +
  ggplot2::geom_sf(data = l3.neg, col = "blue", fill = NA) +
  ggplot2::geom_sf(data = l2.base,  col = 'black', fill = NA) +
  ggplot2::geom_abline(intercept = 3, slope = 1, color="gray",linetype="dashed", size=1) +
  ggplot2::coord_sf(xlim = c(-6,-4), ylim = c(2,4))

reprex package (v2.0.1) 于2021年10月17日创建


谢谢,这个替代方案解决了负缓冲方法所识别的问题。我已经在原帖中添加了更新,更详细地比较了这两种方法。这两种方法都有优点和可能的弱点。我很好奇你对这个讨论有什么想法。 - AWaddington
@AWaddington。抱歉,我的R和RStudio突然崩溃了。我得重新安装所有东西。现在已经很晚了(在法国!),我明天会详细回答你的。 - lovalery
1
就是这样!R和RStudio已经重新安装了!!!再次为此延误道歉。请在我最初的帖子底部找到我的答案作为编辑/更新。我希望它能帮助您选择最适合您需求的最佳方案。我注意到您已经验证了@mrhellman的回复,不用担心。正如您通过阅读我的编辑/更新所看到的那样,我认为这两个解决方案在质量上非常接近。干杯 - lovalery

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