有没有更好的方法来处理跨越反子午线(日期变更线)的 SpatialPolygons?

Is there a better way for handling SpatialPolygons that cross the antimeridian (date line)?

TL;DR

R 中处理 SpatialPolygons intersecting/overlapping 纬度 +/-180° 处的反子午线并沿该子午线将它们分成两部分的最佳方法是什么?

前言

这将是一篇很长的文章,但这只是因为我将包含大量代码和图表以供说明。我将向您展示我的目标是什么以及我通常如何实现该目标,然后演示如何在字面上的边缘案例中将其分解在一起。正如标题所暗示的那样,我已经找到了一种可能的解决方案来解决我的问题,因此我也将其包括在内。但它不是 100% 干净,我想看看是否有人能想出更优雅的东西。无论如何,我认为这是一个有趣的问题,因为就在几天前,我做梦也不会怀疑这甚至会在 2019 年成为一个问题。

R 中的常规工作流程

首先,创建一个有效的示例数据集

library(sp)
library(rgdal)
library(rgeos)
library(dismo)
library(maptools) # this is just for plotting a simple world map in the background
data("wrld_simpl")


# create a set of locations
locations <- SpatialPoints(coords=cbind(c(50,0,0,0), c(10, 30, 50, 70)), proj4string = CRS("+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"))
plot(wrld_simpl, border="grey50")
points(locations, pch=19, col="blue")

看起来像这样: 然后,我使用 dismo 包中的 circles() 在这些位置周围创建环形缓冲区。我用这个函数,因为它考虑到地球不是平的:

buffr <- circles(p = locations, d = 1500000, lonlat=TRUE, dissolve=FALSE)
plot(wrld_simpl, border="grey50")
plot(buffr, add=TRUE, border="red", lwd=2)
points(locations, pch=19, col="blue")

看起来像这样:

然后,将单个缓冲区合并为一个大(多)多边形:

buffr <- buffr@polygons # extract the SpatialPolygons object from the "CirclesRange" object
buffr <- gUnaryUnion(buffr) # merge

plot(wrld_simpl, border="grey50")
plot(buffr, add=TRUE, border="red", lwd=2)
points(locations, pch=19, col="blue")

这正是我需要的:

问题

现在观察当我们引入靠近缓冲区必须穿过那条线的 anti-meridian(+/-180° 经度)的位置时会发生什么:

locations <- SpatialPoints(coords=cbind(c(50,0,0,0, 175, -170), c(10, 30, 50, 70,0,-10)), proj4string = CRS("+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"))
buffr <- circles(p = locations, d = 1500000, lonlat=TRUE, dissolve=FALSE)

plot(wrld_simpl, border="grey50")
plot(buffr, add=TRUE, border="red", lwd=2)
points(locations, pch=19, col="blue")

circles() 命令确实设法在反子午线的另一侧创建多边形线段(如果 dissolve=FALSE):

但是多边形穿过整个地球而不是正确环绕(与 0° 相交而不是 180°)。这导致 self-intersections 和

buffr <- gUnaryUnion(buffr@polygons)

将失败

Error in gUnaryUnion(buffr@polygons) : TopologyException: Input geom 0 is invalid: Self-intersection at or near point 170.08604674698876 12.562175561621103 at 170.08604674698876 12.562175561621103

快速但有点脏的解决方案

首先,我们需要检测多边形是否穿过反子午线。然而,其中 none 实际上相交了 +/-180°。相反,我使用的是两条靠近真实子午线的伪反子午线,但距离东边和西边足够远,可能与所讨论的多边形相交。如果一个多边形与它们都相交,它也必须穿过反子午线。

antimeridian <- SpatialLines(list(Lines(slinelist=list(Line(coords=cbind(c(179,179), c(90,-90)))), ID="1"),
              Lines(slinelist=list(Line(coords=cbind(c(-179,-179), c(90,-90)))), ID="2")),
                                   proj4string = CRS("+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"))
intrscts <- gIntersects(antimeridian, buffr, byid = TRUE)
any(intrscts[,1] & intrscts[,2])
intrscts <- which(intrscts[,1] & intrscts[,2])
buffr.bad <- buffr[intrscts,]
buffr.good <- buffr[-intrscts,]

plot(wrld_simpl)
plot(buffr.good, border="blue", add=TRUE)
plot(buffr.bad, border="red", add=TRUE)

检测并分离 "bad" 多边形后,我通过查看纵坐标将它们分成两个独立的部分。每个具有负值的坐标对进入新的西部多边形,正值进入东部多边形。然后我将它们全部合并回去,做我的 gUnaryUnion 并得到我需要的东西:

buffr.fixed <- buffr.good
for(i in 1:length(buffr.bad)){
  thispoly <- buffr.bad[i,]                              # select first problematic polygon
  crds <- thispoly@polygons[[1]]@Polygons[[1]]@coords   # extract coordinates
  crds.west <- subset(crds, crds[,1] < 0)               # western half of the polygon
  crds.east<- subset(crds, crds[,1] > 0)
  # turn into Spatial*, merge back together, re-add original crs
  sppol.east <- SpatialPolygons(list(Polygons(list(Polygon(crds.east)), paste0("east_", i))))
  sppol.west <- SpatialPolygons(list(Polygons(list(Polygon(crds.west)), paste0("west_", i))))
  sppol <- spRbind(sppol.east, sppol.west)
  proj4string(sppol) <- proj4string(thispoly)

  buffr.fixed <- spRbind(buffr.fixed, sppol)
}
buffr.final <- gUnaryUnion(buffr.fixed)
plot(wrld_simpl, border="grey50")
points(locations, pch=19, col="blue")
plot(buffr.final, add=TRUE, border="red", lwd=2)

最终结果:

实际问题

所以,这个解决方案适用于我当前的用例,但它有一些问题:

所以所有这些问题归结为:有没有更好的方法

当我试图解决这个问题时,我发现了 maptools 包中的 nowrapRecenter() and nowrapSpatialPolygons() 函数,乍一看它们完全符合我的要求。仔细检查后,它们的目标几乎是相反的用例(将地图以反子午线为中心,从而沿本初子午线切割多边形)。我和他们一起玩,但没能让他们为我工作——事实上,他们只会让事情变得更糟。

感谢关注!

你是对的,现在是今年,你的问题有解决方案。 sf-package 具有 st_wrap_dateline() 的功能,这正是您所需要的。

library(dismo)
library(sf)


locations <- SpatialPoints(coords=cbind(c(50,0,0,0, 175, -170), c(10, 30, 50, 70,0,-10)), proj4string = CRS("+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0"))
buffr <- circles(p = locations, d = 1500000, lonlat=TRUE, dissolve=FALSE)

buffr2 <- as(buffr@polygons, Class = "sf") %>%
            st_wrap_dateline(options = c("WRAPDATELINE=YES")) %>%
             st_union()


plot(wrld_simpl, border="grey50")
plot(buffr2, add=TRUE, border="red", lwd=2)
points(locations, pch=19, col="blue")

st_wrap_dateline 将跨越国际日期变更线或 "antimeridian" 的多边形转换为 MULTIPOLYGON。仅此而已。

这能解决您的问题吗?至少它大大缩短了到达你现在所在位置的方式。 ^^