This is a set of tests related to a Github issue for the sf package.

library(tidyverse)
library(sf)

Make test objects

Make a polygon, a multipolygon, and two dataframes that can be combined with the geometries to create simple feature objects.

# Polygon geometry: pol
p1 <- rbind(c(0,0), c(1,0), c(3,2), c(2,4), c(1,4), c(0,0))
p2 <- rbind(c(1,1), c(1,2), c(2,2), c(1,1))
pol <-st_polygon(list(p1,p2))
# 
# Multi-polygon geometry: mpol
p3 <- rbind(c(3,0), c(4,0), c(4,1), c(3,1), c(3,0))
p4 <- rbind(c(3.3,0.3), c(3.8,0.3), c(3.8,0.8), c(3.3,0.8), c(3.3,0.3))[5:1,]
p5 <- rbind(c(3,3), c(4,2), c(4,3), c(3,3))
mpol <- st_multipolygon(list(list(p1,p2), list(p3,p4), list(p5)))
# 
# sf object w/ polygon: sf_pol
df1 <- 
        data.frame(VAR1 = 'foo',VAR2 = 'bar') %>% 
        mutate(geom = st_sfc(pol))
sf_pol <- st_as_sf(df1) 
# 
# sf object w/ multi-polygon: sf_mpol
df2 <- 
        data.frame(VAR1 = 'foo',VAR2 = 'bar') %>% 
        mutate(geom = st_sfc(mpol))
sf_mpol <- st_as_sf(df2)

Test 1: rbind two sf objects with different geometry classes

# Check the sf object metadata: sf_pol
sf_pol
Simple feature collection with 1 feature and 2 fields
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: 0 ymin: 0 xmax: 3 ymax: 4
epsg (SRID):    NA
proj4string:    NA
  VAR1 VAR2                           geom
1  foo  bar POLYGON((0 0, 1 0, 3 2, 2 4...
# Check the sf object metadata: sf_mpol
sf_mpol
Simple feature collection with 1 feature and 2 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 0 ymin: 0 xmax: 4 ymax: 4
epsg (SRID):    NA
proj4string:    NA
  VAR1 VAR2                           geom
1  foo  bar MULTIPOLYGON(((0 0, 1 0, 3 ...
# Attempt to combine the object with `rbind()`
sf_both <- rbind(sf_pol,sf_mpol)
sf_both
Simple feature collection with 2 features and 2 fields
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: 0 ymin: 0 xmax: 3 ymax: 4
epsg (SRID):    NA
proj4string:    NA
  VAR1 VAR2                           geom
1  foo  bar POLYGON((0 0, 1 0, 3 2, 2 4...
2  foo  bar MULTIPOLYGON(((0 0, 1 0, 3 ...

Looking at the metadata above, I’m surprised to see geometry type: POLYGON. Based on the sf package vignette, I would have guessed that the geometry of this new simple feature would be of the GEOMETRY class (but perhaps that was wishful thinking).

Let’s see what happens when we plot this object:

try(plot(sf_both))
Error in eval(substitute(expr), envir, enclos) : 
  not compatible with requested type

It seems that plot() cannot handle this mixed-type sfc. It would be ideal if rbind() could detect the presence of different types of sfg’s and coerce them into a GEOMETRY sfc.

Otherwise, users need to find work-arounds. The following is one way to manually build the GEOMETRY sfc by looping over the two sfg’s:

sfc_new <- st_sfc()
        for(i in seq_along(st_geometry(sf_pol))){
                 
                 geom <- st_geometry(sf_pol)[[i]]
                 sfc_new[[i]] <- geom
        }
        for(i in seq_along(st_geometry(sf_mpol))){
                 
                 geom <- st_geometry(sf_mpol)[[i]]
                 sfc_new[[i + nrow(sf_pol)]] <- geom
                 
        }
sf_both_new <- 
        rbind(sf_pol,
              sf_mpol) %>% 
        mutate(geom = sfc_new)
sf_both_new
Simple feature collection with 2 features and 2 fields
geometry type:  GEOMETRY
dimension:      XY
bbox:           xmin: NA ymin: NA xmax: NA ymax: NA
epsg (SRID):    NA
proj4string:    NA
  VAR1 VAR2                           geom
1  foo  bar POLYGON((0 0, 1 0, 3 2, 2 4...
2  foo  bar MULTIPOLYGON(((0 0, 1 0, 3 ...
try(plot(sf_both_new))

So that work-around returns the desired results, but it wouldn’t scale very well to accomodate the merger of many sf objects and it certainly breaks the continuity and readability of a %>% workflow (see princples 2 and 4 of the Tidy Tools Manifesto).

Test 2: bind sf objects with non-matching columns and different geometry types

# sf object w/ polygon: sf_pol2
df3 <- data.frame(VAR1 = 'foo',VAR2 = 'bar') 
df3$geom <- st_sfc(pol)
sf_pol2 <- st_as_sf(df1) 
# 
# sf object w/ multi-polygon: sf_mpol2 (new column: VAR3)
df4 <- data.frame(VAR1 = 'foo',VAR3 = 'bar') 
df4$geom <- st_sfc(mpol)
sf_mpol2 <- st_as_sf(df4)
# 
# Try rbind()
sf_pol2;sf_mpol2
Simple feature collection with 1 feature and 2 fields
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: 0 ymin: 0 xmax: 3 ymax: 4
epsg (SRID):    NA
proj4string:    NA
  VAR1 VAR2                           geom
1  foo  bar POLYGON((0 0, 1 0, 3 2, 2 4...
Simple feature collection with 1 feature and 2 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 0 ymin: 0 xmax: 4 ymax: 4
epsg (SRID):    NA
proj4string:    NA
  VAR1 VAR3                           geom
1  foo  bar MULTIPOLYGON(((0 0, 1 0, 3 ...
try(rbind(sf_pol2,sf_mpol2))
Error in match.names(clabs, names(xi)) : 
  names do not match previous names
# 
# Try dplyr::bind_rows()
# NOTE: this throws an error, even with the help of try() 
# 
# try(dplyr::bind_rows(sf_pol2,sf_mpol2))
# 
# Work-around
df_both_new2 <- dplyr::bind_rows(sf_pol2[,1:2],
                                 sf_mpol2[,1:2])
df_both_new2$geom <- sfc_new
sf_both_new2 <- st_sf(df_both_new2)
sf_both_new2; try(plot(sf_both_new2))
Simple feature collection with 2 features and 3 fields
geometry type:  GEOMETRY
dimension:      XY
bbox:           xmin: NA ymin: NA xmax: NA ymax: NA
epsg (SRID):    NA
proj4string:    NA
  VAR1 VAR2 VAR3                           geom
1  foo  bar <NA> POLYGON((0 0, 1 0, 3 2, 2 4...
2  foo <NA>  bar MULTIPOLYGON(((0 0, 1 0, 3 ...

dplyr::bind_rows() fails because it doesn’t recognize the sf class. However, stripping the sf objects of their sfc’s, passing them to bind_rows(), and then adding the manually-built GEOMETRY sfc returns the desired result.

Thoughts

Perhaps these tests make the case for the following enhancements to sf:

  1. an sf-compatible version of dplyr::bind_rows() (provided it’s even possible - see Hadley’s comment)
  2. an sf function for combining sfc’s with different geometry types into a sfc with GEOMETRY type sfg’s
LS0tCnRpdGxlOiAic3Agw5cgdGlkeXZlcnNlIHRlc3Q6IHJiaW5kIgphdXRob3I6IFRpZXJuYW4gTWFydGluCm91dHB1dDogaHRtbF9ub3RlYm9vawpkYXRlOiBOb3ZlbWJlciA1LCAyMDE2Ci0tLQoKVGhpcyBpcyBhIHNldCBvZiB0ZXN0cyByZWxhdGVkIHRvIGEgW0dpdGh1YiBpc3N1ZV0oaHR0cHM6Ly9naXRodWIuY29tL2VkemVyL3Nmci9pc3N1ZXMvNDkpIGZvciB0aGUgYHNmYCBwYWNrYWdlLiAKCgpgYGB7ciBzZXR1cH0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2YpCmBgYAoKIyMjIE1ha2UgdGVzdCBvYmplY3RzCk1ha2UgYSBwb2x5Z29uLCBhIG11bHRpcG9seWdvbiwgYW5kIHR3byBkYXRhZnJhbWVzIHRoYXQgY2FuIGJlIGNvbWJpbmVkIHdpdGggdGhlIGdlb21ldHJpZXMgdG8gY3JlYXRlIHNpbXBsZSBmZWF0dXJlIG9iamVjdHMuCmBgYHtyIG1ha2UtdGVzdC1vYmpzLCBlY2hvPVRSVUV9CgojIFBvbHlnb24gZ2VvbWV0cnk6IHBvbAoKcDEgPC0gcmJpbmQoYygwLDApLCBjKDEsMCksIGMoMywyKSwgYygyLDQpLCBjKDEsNCksIGMoMCwwKSkKcDIgPC0gcmJpbmQoYygxLDEpLCBjKDEsMiksIGMoMiwyKSwgYygxLDEpKQpwb2wgPC1zdF9wb2x5Z29uKGxpc3QocDEscDIpKQojIAojIE11bHRpLXBvbHlnb24gZ2VvbWV0cnk6IG1wb2wKCnAzIDwtIHJiaW5kKGMoMywwKSwgYyg0LDApLCBjKDQsMSksIGMoMywxKSwgYygzLDApKQpwNCA8LSByYmluZChjKDMuMywwLjMpLCBjKDMuOCwwLjMpLCBjKDMuOCwwLjgpLCBjKDMuMywwLjgpLCBjKDMuMywwLjMpKVs1OjEsXQpwNSA8LSByYmluZChjKDMsMyksIGMoNCwyKSwgYyg0LDMpLCBjKDMsMykpCm1wb2wgPC0gc3RfbXVsdGlwb2x5Z29uKGxpc3QobGlzdChwMSxwMiksIGxpc3QocDMscDQpLCBsaXN0KHA1KSkpCiMgCiMgc2Ygb2JqZWN0IHcvIHBvbHlnb246IHNmX3BvbAoKZGYxIDwtIAogICAgICAgIGRhdGEuZnJhbWUoVkFSMSA9ICdmb28nLFZBUjIgPSAnYmFyJykgJT4lIAogICAgICAgIG11dGF0ZShnZW9tID0gc3Rfc2ZjKHBvbCkpCnNmX3BvbCA8LSBzdF9hc19zZihkZjEpIAojIAojIHNmIG9iamVjdCB3LyBtdWx0aS1wb2x5Z29uOiBzZl9tcG9sCgpkZjIgPC0gCiAgICAgICAgZGF0YS5mcmFtZShWQVIxID0gJ2ZvbycsVkFSMiA9ICdiYXInKSAlPiUgCiAgICAgICAgbXV0YXRlKGdlb20gPSBzdF9zZmMobXBvbCkpCnNmX21wb2wgPC0gc3RfYXNfc2YoZGYyKQpgYGAKCgoKIyMjIFRlc3QgMTogcmJpbmQgdHdvIHNmIG9iamVjdHMgd2l0aCBkaWZmZXJlbnQgZ2VvbWV0cnkgY2xhc3NlcwpgYGB7ciByYmluZC10ZXN0LCBlY2hvPVRSVUV9CgojIENoZWNrIHRoZSBzZiBvYmplY3QgbWV0YWRhdGE6IHNmX3BvbApzZl9wb2wKCiMgQ2hlY2sgdGhlIHNmIG9iamVjdCBtZXRhZGF0YTogc2ZfbXBvbApzZl9tcG9sCgojIEF0dGVtcHQgdG8gY29tYmluZSB0aGUgb2JqZWN0IHdpdGggYHJiaW5kKClgCgpzZl9ib3RoIDwtIHJiaW5kKHNmX3BvbCxzZl9tcG9sKQpzZl9ib3RoCgpgYGAKCkxvb2tpbmcgYXQgdGhlIG1ldGFkYXRhIGFib3ZlLCBJJ20gc3VycHJpc2VkIHRvIHNlZSBgZ2VvbWV0cnkgdHlwZTogUE9MWUdPTmAuIEJhc2VkIG9uIHRoZSBgc2ZgIFtwYWNrYWdlIHZpZ25ldHRlXShodHRwczovL2VkemVyLmdpdGh1Yi5pby9zZnIvYXJ0aWNsZXMvc2ZyLmh0bWwjbWl4ZWQtZ2VvbWV0cnktdHlwZXMpLCBJIHdvdWxkIGhhdmUgZ3Vlc3NlZCB0aGF0IHRoZSBnZW9tZXRyeSBvZiB0aGlzIG5ldyBzaW1wbGUgZmVhdHVyZSB3b3VsZCBiZSBvZiB0aGUgYEdFT01FVFJZYCBjbGFzcyAoYnV0IHBlcmhhcHMgdGhhdCB3YXMgd2lzaGZ1bCB0aGlua2luZykuCgpMZXQncyBzZWUgd2hhdCBoYXBwZW5zIHdoZW4gd2UgcGxvdCB0aGlzIG9iamVjdDoKCmBgYHtyIHJiaW5kLXBsb3QtdGVzdCwgZWNobz1UUlVFfQp0cnkocGxvdChzZl9ib3RoKSkKCmBgYAoKSXQgc2VlbXMgdGhhdCBgcGxvdCgpYCBjYW5ub3QgaGFuZGxlIHRoaXMgbWl4ZWQtdHlwZSBgc2ZjYC4gSXQgd291bGQgYmUgaWRlYWwgaWYgYHJiaW5kKClgIGNvdWxkIGRldGVjdCB0aGUgcHJlc2VuY2Ugb2YgZGlmZmVyZW50IHR5cGVzIG9mIGBzZmdgJ3MgYW5kIGNvZXJjZSB0aGVtIGludG8gYSBgR0VPTUVUUllgIGBzZmNgLgoKT3RoZXJ3aXNlLCB1c2VycyBuZWVkIHRvIGZpbmQgd29yay1hcm91bmRzLiBUaGUgZm9sbG93aW5nIGlzIG9uZSB3YXkgdG8gbWFudWFsbHkgYnVpbGQgdGhlIGBHRU9NRVRSWWAgYHNmY2AgYnkgbG9vcGluZyBvdmVyIHRoZSB0d28gYHNmZ2AnczoKCmBgYHtyIHJiaW5kLW1ha2Utc2ZjLXRlc3QsZWNobz1UUlVFfQpzZmNfbmV3IDwtIHN0X3NmYygpCiAgICAgICAgZm9yKGkgaW4gc2VxX2Fsb25nKHN0X2dlb21ldHJ5KHNmX3BvbCkpKXsKICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICBnZW9tIDwtIHN0X2dlb21ldHJ5KHNmX3BvbClbW2ldXQogICAgICAgICAgICAgICAgIHNmY19uZXdbW2ldXSA8LSBnZW9tCiAgICAgICAgfQogICAgICAgIGZvcihpIGluIHNlcV9hbG9uZyhzdF9nZW9tZXRyeShzZl9tcG9sKSkpewogICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgIGdlb20gPC0gc3RfZ2VvbWV0cnkoc2ZfbXBvbClbW2ldXQogICAgICAgICAgICAgICAgIHNmY19uZXdbW2kgKyBucm93KHNmX3BvbCldXSA8LSBnZW9tCiAgICAgICAgICAgICAgICAgCiAgICAgICAgfQoKc2ZfYm90aF9uZXcgPC0gCiAgICAgICAgcmJpbmQoc2ZfcG9sLAogICAgICAgICAgICAgIHNmX21wb2wpICU+JSAKICAgICAgICBtdXRhdGUoZ2VvbSA9IHNmY19uZXcpCgpzZl9ib3RoX25ldwp0cnkocGxvdChzZl9ib3RoX25ldykpCgpgYGAKU28gdGhhdCB3b3JrLWFyb3VuZCByZXR1cm5zIHRoZSBkZXNpcmVkIHJlc3VsdHMsIGJ1dCBpdCB3b3VsZG4ndCBzY2FsZSB2ZXJ5IHdlbGwgdG8gYWNjb21vZGF0ZSB0aGUgbWVyZ2VyIG9mIG1hbnkgYHNmYCBvYmplY3RzIGFuZCBpdCBjZXJ0YWlubHkgYnJlYWtzIHRoZSBjb250aW51aXR5IGFuZCByZWFkYWJpbGl0eSBvZiBhIGAlPiVgIHdvcmtmbG93IChzZWUgcHJpbmNwbGVzIDIgYW5kIDQgb2YgdGhlIFtUaWR5IFRvb2xzIE1hbmlmZXN0b10oaHR0cHM6Ly9tcmFuLm1pY3Jvc29mdC5jb20vd2ViL3BhY2thZ2VzL3RpZHl2ZXJzZS92aWduZXR0ZXMvbWFuaWZlc3RvLmh0bWwpKS4KCiMjIyBUZXN0IDI6IGJpbmQgc2Ygb2JqZWN0cyB3aXRoIG5vbi1tYXRjaGluZyBjb2x1bW5zIF9hbmRfIGRpZmZlcmVudCBnZW9tZXRyeSB0eXBlcwoKYGBge3IgYmluZF9yb3dzLXRlc3QsIGVjaG8gPSBUUlVFfQoKIyBzZiBvYmplY3Qgdy8gcG9seWdvbjogc2ZfcG9sMgpkZjMgPC0gZGF0YS5mcmFtZShWQVIxID0gJ2ZvbycsVkFSMiA9ICdiYXInKSAKZGYzJGdlb20gPC0gc3Rfc2ZjKHBvbCkKc2ZfcG9sMiA8LSBzdF9hc19zZihkZjEpIAojIAojIHNmIG9iamVjdCB3LyBtdWx0aS1wb2x5Z29uOiBzZl9tcG9sMiAobmV3IGNvbHVtbjogVkFSMykKZGY0IDwtIGRhdGEuZnJhbWUoVkFSMSA9ICdmb28nLFZBUjMgPSAnYmFyJykgCmRmNCRnZW9tIDwtIHN0X3NmYyhtcG9sKQpzZl9tcG9sMiA8LSBzdF9hc19zZihkZjQpCiMgCiMgVHJ5IHJiaW5kKCkKc2ZfcG9sMjtzZl9tcG9sMgoKdHJ5KHJiaW5kKHNmX3BvbDIsc2ZfbXBvbDIpKQojIAojIFRyeSBkcGx5cjo6YmluZF9yb3dzKCkKIyBOT1RFOiB0aGlzIHRocm93cyBhbiBlcnJvciwgZXZlbiB3aXRoIHRoZSBoZWxwIG9mIHRyeSgpIAojIAojIHRyeShkcGx5cjo6YmluZF9yb3dzKHNmX3BvbDIsc2ZfbXBvbDIpKQojIAojIFdvcmstYXJvdW5kCgpkZl9ib3RoX25ldzIgPC0gZHBseXI6OmJpbmRfcm93cyhzZl9wb2wyWywxOjJdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZl9tcG9sMlssMToyXSkKCmRmX2JvdGhfbmV3MiRnZW9tIDwtIHNmY19uZXcKCnNmX2JvdGhfbmV3MiA8LSBzdF9zZihkZl9ib3RoX25ldzIpCgpzZl9ib3RoX25ldzI7IHRyeShwbG90KHNmX2JvdGhfbmV3MikpCgoKYGBgCgpgZHBseXI6OmJpbmRfcm93cygpYCBmYWlscyBiZWNhdXNlIGl0IGRvZXNuJ3QgcmVjb2duaXplIHRoZSBgc2ZgIGNsYXNzLiBIb3dldmVyLCBzdHJpcHBpbmcgdGhlIGBzZmAgb2JqZWN0cyBvZiB0aGVpciBgc2ZjYCdzLCBwYXNzaW5nIHRoZW0gdG8gYGJpbmRfcm93cygpYCwgYW5kIHRoZW4gYWRkaW5nIHRoZSBtYW51YWxseS1idWlsdCBgR0VPTUVUUllgIGBzZmNgIHJldHVybnMgdGhlIGRlc2lyZWQgcmVzdWx0LgoKCiMjIyBUaG91Z2h0cwpQZXJoYXBzIHRoZXNlIHRlc3RzIG1ha2UgdGhlIGNhc2UgZm9yIHRoZSBmb2xsb3dpbmcgZW5oYW5jZW1lbnRzIHRvIGBzZmA6CgogIDEuIGFuIGBzZmAtY29tcGF0aWJsZSB2ZXJzaW9uIG9mIGBkcGx5cjo6YmluZF9yb3dzKClgIChwcm92aWRlZCBpdCdzIGV2ZW4gcG9zc2libGUgLSBzZWUgSGFkbGV5J3MgW2NvbW1lbnRdKGh0dHBzOi8vZ2l0aHViLmNvbS9lZHplci9zZnIvaXNzdWVzLzQ5I2lzc3VlY29tbWVudC0yNTg1NTUyNzUpKQogIDIuIGFuIGBzZmAgZnVuY3Rpb24gZm9yIGNvbWJpbmluZyBgc2ZjYCdzIHdpdGggZGlmZmVyZW50IGdlb21ldHJ5IHR5cGVzIGludG8gYSBgc2ZjYCB3aXRoIGBHRU9NRVRSWWAgdHlwZSBgc2ZnYCdz