I think this is finally a workable general solution for path-based structures. A build_type function needs to exist for all types, but each can take the exact same inputs:
geometry map - a table summarizing the paths, one row each
coordinates - the vertex instances in order as implied by the geometry map
Each specific form will need to be able to pass in other information, such as the crs, and each will need to be able to have an override type argument - so we can force path interpretation to be a particular kind, rather than per path or per object.
A prototype for sf is below, this started life in gibble (when the first attempt was used for mapedit), and was used within scsf a little - but all that now belongs here.
A round trip function below shows the utility of this, by decomposing an input to g-map/coord form and recomposing.
#' a pattern for building sf objects from
#' - a gibble, the map of the paths
#' - the coordinates
#' the map is an encoding of the structural
build_sf <- function(gm, coords_in, crs = NULL) {
glist <- vector("list", length(unique(gm$object)))
coords_in <- gm %>% dplyr::select(-type, -ncol, -nrow) %>%
dplyr::slice(rep(seq_len(nrow(gm)), gm$nrow)) %>% dplyr::bind_cols(coords_in)
ufeature <- unique(gm$object)
for (ifeature in seq_along(ufeature)) {
gm0 <- gm %>% dplyr::filter(object == ufeature[ifeature])
type <- gm0$type[1]
coord0 <- coords_in %>% dplyr::filter(object == ifeature)
## object becomes sub-feature element (not a hole, that is "part")
coord0$path <- rep(seq_len(nrow(gm0)), gm0$nrow)
glist[[ifeature]] <- switch(type,
POINT = sf::st_point(unlist(coord0 %>% dplyr::select(x_, y_))),
MULTIPOINT = sf::st_multipoint(as.matrix(coord0 %>% dplyr::select(x_, y_))),
LINESTRING = sf::st_linestring(as.matrix(coord0 %>% dplyr::select(x_, y_))),
MULTILINESTRING = sf::st_multilinestring(lapply(split(coord0 %>% dplyr::select(x_, y_), coord0$path), as.matrix)),
POLYGON = sf::st_polygon(lapply(split(coord0 %>% dplyr::select(x_, y_), coord0$path), as.matrix)),
MULTIPOLYGON = sf::st_multipolygon(lapply(split(coord0 %>% dplyr::select(x_, y_, path), coord0$subobject),
function(path) lapply(split(path %>% select(x_, y_), path$path), as.matrix)))
)
}
if (is.null(crs)) crs <- NA_crs_
out <- st_sfc(glist, crs = crs)
out
}
## this version simply decomposes and then recomposes
round_trip <- function(x) {
gm <- gibble::gibble(x)
coord <- sc_coord(x)
## might be no object identifier, because this is only one
if (is.null(gm[["object"]])) gm[["object"]] <- 1
build_sf(gm, coord)
}
# this version also extract unique vertices, allows an operation on them (e.g. jitter)
# and then restores the vertex identity, then recomposes
round_trip_topology <- function(x, ...) {
coord <- sc_coord(x)
gm <- gibble::gibble(x)
path <- silicate::PATH(x)
coord_unique <- path$vertex %>% dplyr::select(-vertex_)
#coord_unique[] <- lapply(coord_unique, jitter, ...)
coord_unique[["vertex_"]] <- path$vertex[["vertex_"]]
coord_out <- dplyr::inner_join(path$path_link_vertex, coord_unique) %>% dplyr::select(-vertex_)
st_buffer(build_sf(gm, coord_out, crs = st_crs(x)), dist = 0)
}
round_trip(sfzoo$multipolygon)
round_trip(sfzoo$polygon)
round_trip(sfzoo$multilinestring)
round_trip(sfzoo$linestring)
round_trip(sfzoo$multipoint)
round_trip(sfzoo$point)
data("holey", package = "spbabel")
plot(round_trip(st_as_sf(spbabel::sp(holey))), col = viridis::viridis(3))
I think this is finally a workable general solution for path-based structures. A build_type function needs to exist for all types, but each can take the exact same inputs:
Each specific form will need to be able to pass in other information, such as the crs, and each will need to be able to have an override type argument - so we can force path interpretation to be a particular kind, rather than per path or per object.
A prototype for sf is below, this started life in gibble (when the first attempt was used for mapedit), and was used within scsf a little - but all that now belongs here.
A round trip function below shows the utility of this, by decomposing an input to g-map/coord form and recomposing.