Search code examples
rggplot2polygon

Identify non self-intersecting polygons


I have a shiny app where the user can define a polygon, but I want to throw a warning if the polygon self-intersects.

I made the MWE below.

polygon=data.frame(x_coord=c(1,2.5,7.5,6.75,4), y_coord=c(5,7,6.5,0.75,0.25))
polygon=data.frame(x_coord=c(1.5,2.5,7.5,5.5,4.5), y_coord=c(3,7,6.5,3,7.5))
not_intersect <- TRUE #how to define if the polygon self-intersects?
if (not_intersect==TRUE){
  P <- ggplot2::ggplot(polygon, ggplot2::aes(x=x_coord, y=y_coord)) +
       ggplot2::geom_polygon(fill="#99CCFF", color="red", alpha=0.25, linewidth=1, linetype=2) +
       ggplot2::geom_point(color="red", size=1.5) +
       ggplot2::scale_x_continuous(limits = c(0, 10), breaks = seq(0,10)) +
       ggplot2::scale_y_continuous(limits = c(0, 10), breaks = seq(0,10)) +
       ggplot2::theme_light()
  grDevices::pdf(file="test.pdf", height=6, width=6)
  print(P)
  grDevices::dev.off()
}

Essentially, if the polygon is like the first one, I want to plot it:

test1

But if it is like the second one, I don't want to plot it:

test2

Any idea on how to identify these 2 types of polygons? Thanks!


Solution

  • A valid polygon is defined by the Open Geospatial Consortium as:

    1. The polygon is closed; its start point is the same as its end point.
    2. Its boundary is a set of linestrings.
    3. The boundary does not touch or cross itself.
    4. Any polygons in the interior do not touch the boundary of the exterior polygon except at a vertex.

    We can use sf::st_is_valid() to check for validity. We can set the reason = TRUE parameter to check in particular for self-intersection (the third condition):

    # The same polygons as in your question
    polygon1 <- data.frame(x_coord = c(1, 2.5, 7.5, 6.75, 4), y_coord = c(5, 7, 6.5, 0.75, 0.25))
    polygon2 <- data.frame(x_coord = c(1.5, 2.5, 7.5, 5.5, 4.5), y_coord = c(3, 7, 6.5, 3, 7.5))
    
    library(sf)
    
    has_self_intersection  <- function(p) {
         p_is_valid <- list(as.matrix(p)) |>
            st_multilinestring() |>
            st_cast("POLYGON") |>
            st_is_valid(reason = TRUE)
        any(startsWith(p_is_valid, "Self-intersection"))
    }
    
    has_self_intersection(polygon1) # FALSE
    has_self_intersection(polygon2) # TRUE
    

    Note: I have called it has_self_intersection() rather than not_intersects because of the advice in How to name functions that return booleans:

    Steve McDonnel's popular Code Complete suggests the list that you have "is", "has", "should", or "can", but for variable names.

    Your code will then be:

    if (!has_self_intersection(polygon)){
        # draw the plot
    }