hypertidy / silicate

A general form for complex data
https://hypertidy.github.io/silicate/
53 stars 4 forks source link

generic constructor #32

Closed mdsumner closed 5 years ago

mdsumner commented 7 years ago

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.

#' 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))