Search code examples
rggplot2polygonrasterspatial

How to plot geom_line features in ggplot2 map?


I want to plot rivers (lines) in a map containing polygons (counties, etc) from South Dakota. The river data is here, https://www.weather.gov/gis/Rivers. Use the subset of rivers data set. The county download can be obtained from here, https://www2.census.gov/geo/tiger/TIGER2020/COUNTY/.

I only want the rivers that lie within the county boundaries of South Dakota, so I am using rgeos::intersection to perform that, which produces a Large SpatialLines object, which ggplot2 doesn't like when I try to plot it with geom_line (I get an error that says "Error: data must be a data frame, or other object coercible by fortify(), not an S4 object with class SpatialLines.")

Here is my code:

library(rgdal)
library(raster)

counties <- readOGR('D:\\Shapefiles\\Counties\\tl_2020_us_county.shp')
counties <- counties[which(counties$STATEFP == '46'),]
counties <- spTransform(counties, CRS("+init=epsg:3395"))

rivers <- readOGR('D:\\Shapefiles\\Main_Rivers\\rs16my07.shp')
proj4string(rivers) <- CRS("+proj=longlat")
rivers <- spTransform(rivers, CRS("+init=epsg:3395"))
rivers <- as.SpatialLines.SLDF(rgeos::gIntersection(counties, rivers)) 

The raster packages "intersect" function does not work for doing the intersection. I think I need to change the SpatialLines object to a spatialLinesDataFrame object to get ggplot2 to plot the rivers. How do I do that? The as.SpatialLines.SLDF function is not doing it. Is there another way to get this to plot? My plotting code is here:

ggplot() +
geom_path(counties, mapping = aes(x = long, y = lat, group = group, col = 'darkgreen')) +
geom_path(rivers, mapping = aes(x = long, y = lat, color = 'blue'))

Solution

  • I would recommend handling your spatial data with the sf library. Firstly, it plays well with ggplot. Also, according to my very much infant understanding of GIS and spatial data in R, I believe that the idea is the sf will eventually take over from sp and the Spatial* data formats. sf is I think a standard format across multiple platforms. See this link for more details on sf.

    Onto your question - this is quite simple using sf. To find the rivers inside a specific county, we use st_intersection() (the sf version of gIntersection).

    library(sf)
    
    # read in the rivers data
    st_read(dsn = 'so_data/rs16my07', layer = 'rs16my07') %>%
      {. ->> my_rivers}
    
    # set the CRS for the rivers data
    st_crs(my_rivers) <- crs('+proj=longlat')
    
    # transform crs
    my_rivers %>% 
      st_transform('+init=epsg:3395') %>% 
      {. ->> my_rivers_trans}
      
    
    
    # read in counties data
    st_read(dsn = 'so_data/tl_2020_us_county') %>% 
      {. ->> my_counties}
    
    # keep state 46
    my_counties %>% 
      filter(
        STATEFP == 46
      ) %>% 
      {. ->> state_46}
    
    # transform crs
    state_46 %>% 
      st_transform('+init=epsg:3395') %>% 
      {. ->> state_46_trans}
    
    
    # keep only rivers inside state 46
    my_rivers_trans %>% 
      st_intersection(state_46_trans) %>% 
      {. ->> my_rivers_46}
    

    Then we can plot the sf objects using ggplot and geom_sf(), just like you would plot lines using geom_line() etc. geom_sf() seems to know if you are plotting point data, line data or polygon data, and plots accordingly. It is quite easy to use.

    # plot it
    state_46_trans %>% 
      ggplot()+
      geom_sf()+
      geom_sf(data = my_rivers_46, colour = 'red')
    

    Hopefully this looks right - I don't know my US states so have no idea if this is South Dakota or not.

    enter image description here