When one thinks of geometries in R
, one of the most
common data structures is the matrix. For example, in the
sf
world, an POINT is a single-row matrix (i.e, a
vector)
sf::st_point( 1:2 )
POINT (1 2)
A LINESTRING is a matrix
sf::st_linestring( matrix( c(1,1,1,2,2,2,2,1,1,1), ncol = 2, byrow = T) )
LINESTRING (1 1, 1 2, 2 2, 2 1, 1 1)
and a POLYGON is a list of matrices
sf::st_polygon( list( matrix( c(1,1,1,2,2,2,2,1,1,1), ncol = 2, byrow = T) ) )
POLYGON ((1 1, 1 2, 2 2, 2 1, 1 1))
And to group these into a collection you would put each geometry inside a list
sf::st_sfc(
list(
sf::st_linestring( matrix( c(1,1,1,2,2,2,2,1,1,1), ncol = 2, byrow = T) )
, sf::st_polygon( list( matrix( c(1,1,1,2,2,2,2,1,1,1), ncol = 2, byrow = T) ) )
)
)
Geometry set for 2 features
geometry type: GEOMETRY
dimension: XY
bbox: xmin: 1 ymin: 1 xmax: 2 ymax: 2
CRS: NA
LINESTRING (1 1, 1 2, 2 2, 2 1, 1 1)
POLYGON ((1 1, 1 2, 2 2, 2 1, 1 1))
From my limited research (i.e., practically none), I estimate most
users will have a data.frame
and will want to convert it
into a collection of geometries.
For example, take a data.frame
of x
and
y
coordinates, and two id
columns.
df <- data.frame(
id1 = c( rep(1,12), rep(2, 12) )
, id2 = c( rep(1:4, each = 3), rep(1:4, each = 3) )
, x = 1:24
, y = 24:1
)
df
# id1 id2 x y
# 1 1 1 1 24
# 2 1 1 2 23
# 3 1 1 3 22
# 4 1 2 4 21
# 5 1 2 5 20
# 6 1 2 6 19
# 7 1 3 7 18
# 8 1 3 8 17
# 9 1 3 9 16
# 10 1 4 10 15
# 11 1 4 11 14
# 12 1 4 12 13
# 13 2 1 13 12
# 14 2 1 14 11
# 15 2 1 15 10
# 16 2 2 16 9
# 17 2 2 17 8
# 18 2 2 18 7
# 19 2 3 19 6
# 20 2 3 20 5
# 21 2 3 21 4
# 22 2 4 22 3
# 23 2 4 23 2
# 24 2 4 24 1
You can think of the ID columns in this example as
id1
defines a polygonid2
is each ring inside the polygonCalling geometries::make_geometries()
will split this
data.frame by these ID columns and put the resulting matrices inside
list elements.
cppFunction(
depends = "geometries"
, includes = '#include "geometries/geometries.hpp"'
, code = '
SEXP my_shape( SEXP df, SEXP id_cols, SEXP geometry_cols ) {
return geometries::make_geometries( df, id_cols, geometry_cols );
}
'
, plugins = "cpp11"
)
my_shape( df, c(0L,1L), c(2L,3L) )
# [[1]]
# [[1]][[1]]
# [,1] [,2]
# [1,] 1 24
# [2,] 2 23
# [3,] 3 22
#
# [[1]][[2]]
# [,1] [,2]
# [1,] 4 21
# [2,] 5 20
# [3,] 6 19
#
# [[1]][[3]]
# [,1] [,2]
# [1,] 7 18
# [2,] 8 17
# [3,] 9 16
#
# [[1]][[4]]
# [,1] [,2]
# [1,] 10 15
# [2,] 11 14
# [3,] 12 13
#
#
# [[2]]
# [[2]][[1]]
# [,1] [,2]
# [1,] 13 12
# [2,] 14 11
# [3,] 15 10
#
# [[2]][[2]]
# [,1] [,2]
# [1,] 16 9
# [2,] 17 8
# [3,] 18 7
#
# [[2]][[3]]
# [,1] [,2]
# [1,] 19 6
# [2,] 20 5
# [3,] 21 4
#
# [[2]][[4]]
# [,1] [,2]
# [1,] 22 3
# [2,] 23 2
# [3,] 24 1
Notice here there are no class attributes on the shapes. In geometries I only want to provide the tools to build these structures, then each user can define what they mean.
For example, if you want to define a class for each geometry you can supply a list containing a “class” vector as the 4th argument
cppFunction(
depends = "geometries"
, includes = '#include "geometries/geometries.hpp"'
, code = '
SEXP my_shape( Rcpp::DataFrame df, Rcpp::IntegerVector id_cols, Rcpp::IntegerVector geometry_cols, Rcpp::List class_attributes ) {
return geometries::make_geometries( df, id_cols, geometry_cols, class_attributes );
}
'
, plugins = "cpp11"
)
my_shape( df, c(0,1), c(2,3), list(class = "my_polygon") )
# [[1]]
# [[1]]
# [,1] [,2]
# [1,] 1 24
# [2,] 2 23
# [3,] 3 22
#
# [[2]]
# [,1] [,2]
# [1,] 4 21
# [2,] 5 20
# [3,] 6 19
#
# [[3]]
# [,1] [,2]
# [1,] 7 18
# [2,] 8 17
# [3,] 9 16
#
# [[4]]
# [,1] [,2]
# [1,] 10 15
# [2,] 11 14
# [3,] 12 13
#
# attr(,"class")
# [1] "my_polygon"
#
# [[2]]
# [[1]]
# [,1] [,2]
# [1,] 13 12
# [2,] 14 11
# [3,] 15 10
#
# [[2]]
# [,1] [,2]
# [1,] 16 9
# [2,] 17 8
# [3,] 18 7
#
# [[3]]
# [,1] [,2]
# [1,] 19 6
# [2,] 20 5
# [3,] 21 4
#
# [[4]]
# [,1] [,2]
# [1,] 22 3
# [2,] 23 2
# [3,] 24 1
#
# attr(,"class")
# [1] "my_polygon"
Notice here that each list element now has a
"my_polygon"
class.
And if you have library(sf)
loaded, setting the class as
sfg
POLYGON
, you should see each element
printed in the usual sf
way
library(sf)
my_shape( df, c(0,1), c(2,3), list( class = c("XY", "POLYGON","sfg") ) )
# [[1]]
# POLYGON ((1 24, 2 23, 3 22), (4 21, 5 20, 6 19), (7 18, 8 17, 9 16), (10 15, 11 14, 12 13))
#
# [[2]]
# POLYGON ((13 12, 14 11, 15 10), (16 9, 17 8, 18 7), (19 6, 20 5, 21 4), (22 3, 23 2, 24 1))
You can use this function to define any shape you want. The number of
id
columns you supply will determine how deeply nested the
matrices are. If I add two more id
columns, this will nest
each matrix 2-levels deeper
df$id0 <- 1
df$id00 <- 1
head( df )
my_shape( df, c(0,1,4,5), c(2,3), list(class = "my_new_shape") )
# [[1]]
# [[1]]
# [[1]][[1]]
# [[1]][[1]][[1]]
# [,1] [,2]
# [1,] 1 24
# [2,] 2 23
# [3,] 3 22
#
#
#
# [[2]]
# [[2]][[1]]
# [[2]][[1]][[1]]
# [,1] [,2]
# [1,] 4 21
# [2,] 5 20
# [3,] 6 19
#
#
#
# [[3]]
# [[3]][[1]]
# [[3]][[1]][[1]]
# [,1] [,2]
# [1,] 7 18
# [2,] 8 17
# [3,] 9 16
#
#
#
# [[4]]
# [[4]][[1]]
# [[4]][[1]][[1]]
# [,1] [,2]
# [1,] 10 15
# [2,] 11 14
# [3,] 12 13
#
#
#
# attr(,"class")
# [1] "my_new_shape"
#
# [[2]]
# [[1]]
# [[1]][[1]]
# [[1]][[1]][[1]]
# [,1] [,2]
# [1,] 13 12
# [2,] 14 11
# [3,] 15 10
#
#
#
# [[2]]
# [[2]][[1]]
# [[2]][[1]][[1]]
# [,1] [,2]
# [1,] 16 9
# [2,] 17 8
# [3,] 18 7
#
#
#
# [[3]]
# [[3]][[1]]
# [[3]][[1]][[1]]
# [,1] [,2]
# [1,] 19 6
# [2,] 20 5
# [3,] 21 4
#
#
#
# [[4]]
# [[4]][[1]]
# [[4]][[1]][[1]]
# [,1] [,2]
# [1,] 22 3
# [2,] 23 2
# [3,] 24 1
#
#
#
# attr(,"class")
# [1] "my_new_shape"