и продолжать оттуда. Единственная проблема заключается в том, что мне, возможно, придется обновить некоторые ПК, на которых установлены более старые версии R. В любом случае, это в значительной степени именно то, что я искал, так что большое спасибо за это!
Как лучше всего использовать R для обработки пространственных полигонов, пересекающих / перекрывающих анти-меридиан на широте +/- 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")
Выглядит так:Затем я использую circle () из пакета dismo для создания круговых буферов вокруг этих мест. Я использую эту функцию, потому что она учитывает, что Земля не плоская:
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")
ПроблемаТеперь посмотрим, что происходит, когда мы вводим местоположения, которые настолько близки к анти-меридиану (+/- 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")
Команде circle () удается создать сегменты многоугольника на другой стороне антимеридиана (если disolve = FALSE):
но многоугольник пересекает весь земной шар вместо того, чтобы правильно обернуться (пересекается с 0 ° вместо 180 °). Это приводит к самопересечению и
buffr <- gUnaryUnion(buffr@polygons)
потерпит неудачу с
Ошибка в gUnaryUnion (buffr @ polygons): TopologyException: входной геом 0 недопустим: самопересечение в точке или рядом с ней 170.08604674698876 12.562175561621103 в 170.08604674698876 12.562175561621103
Быстрое и слегка грязное решениеВо-первых, нам нужно определить, пересекает ли полигон анти-меридиан. Тем не менее, ни один из них фактически не пересекает +/- 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)
После обнаружения и разделения «плохих» многоугольников я просто разбил их на две отдельные части, взглянув на продольные координаты. Каждая пара координат, имеющая отрицательное значение, переходит в новый западный многоугольник, положительные - в восточный. Затем я просто объединяю все это вместе, делаю мой 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)
Актуальный вопросИтак, это решение работает для меня в моем текущем случае использования, но у него есть некоторые проблемы:
Вероятно, он полностью сломается, как только один из буферов пересечет как анти-, так и первичный меридиан (что не так уж и маловероятно, если исходные точечные местоположения лежат близко к полюсам).это не совсем точно, так как две части многоугольника срезаются не при +/- 180 °, а при самых высоких отрицательных / положительных значениях широты, которые присутствовали в исходном многоугольнике.Мне трудно поверить, что нет «правильного» способа сделать это.Таким образом, вопрос все это сводится к следующему:Есть ли лучший способ сделать это?
Пока я пытался понять это, я наткнулся наnowrapRecenter()
а такжеnowrapSpatialPolygons()
функции отmaptools
пакет, который с первого взгляда выглядел так, как будто они делают именно то, что я хочу. При ближайшем рассмотрении они нацелены на почти противоположный вариант использования (центрирование карты на анти-меридиане и, таким образом, разрезание полигонов вдоль основного меридиана). Я играл с ними, но не смог заставить их работать на меня - на самом деле, они только усугубили ситуацию.
Спасибо за внимание!