knitr::opts_chunk$set(echo = TRUE, warning = FALSE,message = FALSE) library(sf) library(dplyr)
knitr::include_graphics("images/topology_rules/0_0.jpg")
In context of DE-9IM, this is a simple case. The polygon interiors should not overlap at all, everything else does not matter. Interior-Interior is the first of the 9 intersections, so the the intersection matrix as a code string would be: 2********
. In the case of the example below:
set.seed(10) nrows <- 10 circs <- data.frame( id = 1:nrows, x = rnorm(nrows), y = rnorm(nrows) ) %>% st_as_sf(coords = c(2,3)) %>% st_buffer(0.25)
circsplot <- ggplot(circs) + geom_sf(fill = "blue",alpha = 0.3) + geom_sf_text(aes(label = id)) + theme_void() circsplot
This gives us a sparse matrix as an output, which is esentially a list with the same length as the x
, where each position is a vector of integers with the indicies of the features in y
(which may equal to x
) where the pattern matches.
st_relate(circs,pattern = "2********")
Setting sparse = FALSE
returns a crossmatrix of all combinations.W
crossmatrix <- st_relate(circs,pattern = "2********",sparse = FALSE) crossmatrix[1:6,1:6] # only showing 6 since this prints nicely # Remove the diagonals since it's simply each feature tested against itself diag(crossmatrix) <- FALSE error <- which(crossmatrix,arr.ind = TRUE) %>% as.vector() %>% unique() circsplot + geom_sf(data = circs[error,], fill = "red", alpha = 0.3)
knitr::include_graphics("images/topology_rules/0_0.jpg")
Lets cosider the North Carolina Dataset for this question.
nc = st_read(system.file("shape/nc.shp", package="sf"), quiet = TRUE) ggplot(nc) + geom_sf() + theme_void()
The first task is to dissolve all adjecent polygons together
nc_union <- st_union(nc) nc_union
If the output is a multipolygon as it is the case here, it's bad news, there are gaps. To check which parts are disconnected from each other, we can cast the multipolygon to a polygon (in ArcGIS Terms "Multipart to singlepart"), add a rowname for each part and colour it by rowname.
nc_singlepart <- nc_union %>% st_cast("POLYGON")%>% st_sf() %>% mutate(id = 1:n()) ggplot(nc_singlepart) + geom_sf(aes(fill = factor(id))) + labs(fill = "id") + theme_void()
But maybe we can live with these Islands in the state of North Carolina, since this is in fact an accurate representation of reality (the gaps are a result of the Atlantic Ocean). We must now check whether the individual geometries have holes. Here we can make use of the way polygons are defined in sf
:
geometry with a positive area (two-dimensional); sequence of points form a closed, non-self intersecting ring; the first ring denotes the exterior ring, zero or more subsequent rings denote holes in this exterior ring
This means that the length of each Polygon geometry must be 1. A length of 2 or more would mean that there are one (or more) holes in the geometry. We can do this with any of the functions from the apply
family, I prefer purrr
:
map_lgl(nc_singlepart$geometry,~length(.x)== 1)
Let's see what happens if we cut a hole into the polygons
holes <- nc_singlepart %>% st_union() %>% st_centroid() %>% st_buffer(0.5) nc_holes <- st_difference(nc_singlepart,holes) ggplot(nc_holes) + geom_sf() + theme_void() map_lgl(nc_holes$geometry,~length(.x)== 1)
knitr::include_graphics("images/topology_rules/1_0.jpg")
knitr::include_graphics("images/topology_rules/1_1.jpg")
knitr::include_graphics("images/topology_rules/2_0.jpg")
knitr::include_graphics("images/topology_rules/2_1.jpg")
knitr::include_graphics("images/topology_rules/3_0.jpg")
knitr::include_graphics("images/topology_rules/3_1.jpg")
knitr::include_graphics("images/topology_rules/4_0.jpg")
knitr::include_graphics("images/topology_rules/4_1.jpg")
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.