Sébastien Rochette
Sébastien Rochette

Reputation: 6661

Draw SpatialPolygons with multiple subpolygons and holes using ggplot2

I would like to draw SpatialPolygons from library sp with holes in ggplot2. Thanks to others questions on stackoverflow, I know that this is allowed while dealing with clockwise written polygons:
http://stackoverflow.com/questions/12047643/geom-polygon-with-multiple-hole/12051278#12051278
Indeed, when transforming a SpatialPolygons using broom::tidy (replacing ggplot2::fortify), holes polygons are saved in clockwise direction to be drawn as holes.
In ggplot2, the way polygons with holes are drawn force to draw them once using fill, and another time using colour, otherwise you may see lines crossing polygons. When dealing with multiple subpolygons, some with holes, this is more tricky, the order of points features as defined by broom::tidy may not allow for filling polygons (see image below).
Do any of you have a solution to get rid of this filling problem behaviour ?

Here is a reproducible example:

library(sp)
library(ggplot2)

# Create two polygons: second would be a hole inside the first
xy = cbind(
  x = c(13.4, 13.4, 13.6, 13.6, 13.4),
  y = c(48.9, 49, 49, 48.9, 48.9)
    )
hole.xy <- cbind(
  x = c(13.5, 13.5, 13.45, 13.45, 13.5),
  y = c(48.98, 48.92, 48.92, 48.98, 48.98)
  )

# Transform as SpatialPolygons with holes
xy.sp <- SpatialPolygons(list(
  Polygons(list(Polygon(xy),
                Polygon(hole.xy, hole = TRUE)), "1"),
  Polygons(list(Polygon(xy + 0.2),
                Polygon(xy + 0.35),
                Polygon(hole.xy + 0.2, hole = TRUE)), "2")
  ))

# Transform SpatialObject to be used by ggplot2
xy.sp.l <- broom::tidy(xy.sp)

ggplot(xy.sp.l) +
  geom_polygon(aes(x = long, y = lat, group = id, fill = id))

ggplot fill problem with SpatialPolygons with holes
(source: statnmap.com)

Upvotes: 10

Views: 1843

Answers (3)

www
www

Reputation: 39154

This post is a good question and already received great answers. I also believe people should learn how to work with sf objects as it is the next generation of the spatial data type in R. But I want to share that in this case geom_spatial from the ggspatial package could be an option to plot the SpatialPolygons.

library(sp)
library(ggplot2)
library(ggspatial)

ggplot() +
  geom_spatial(xy.sp, aes(fill = id))
# Ignoring argument 'mapping' in geom_spatial.SpatialPolygons
# Autodetect projection: assuming lat/lon (epsg 4326)

enter image description here

Upvotes: 1

Jeremy Voisey
Jeremy Voisey

Reputation: 1297

Adding lines shows the source of the problem. The blue "polygon" is being drawn, lower -> upper -> hole.

enter image description here

This code (which is not very elegant, sorry) makes the path go back to the starting point of the first piece before proceeding to 3rd.

 library(dplyr)
    extra <- xy.sp.l %>%
        filter(piece != 1) %>%
        group_by(id, group) %>%
        summarise(last_pt = max(order))


for (n in 1:nrow(extra)) {
    id_ex <- as.character(extra[n,"id"])
    x <- subset(xy.sp.l, id == id_ex & piece == 1 & order == 1)
    x$order <- as.numeric(extra[n,"last_pt"]) + 0.5
    xy.sp.l <- rbind(xy.sp.l,x)
}

xy.sp.l <- xy.sp.l[order(xy.sp.l$id, xy.sp.l$order),] 

enter image description here

Upvotes: 1

lbusett
lbusett

Reputation: 5932

Could be a good time to "go over" to the sf package. Working with sf object is in fact much easier in ggplot, thanks to the geom_sf geometry:

library("sf")
library("rgeos")
sf_poly <- as(xy.sp, "sf")
sf::st_crs(sf_poly) <- 4326
sf_poly$id <- c(1,2)
ggplot(sf_poly) +
  geom_sf(aes(fill = as.factor(id)))

enter image description here

Upvotes: 11

Related Questions