Skip to content

Commit 65f74a5

Browse files
committed
support all sf geometry types
1 parent 9aba9c8 commit 65f74a5

File tree

5 files changed

+56
-19
lines changed

5 files changed

+56
-19
lines changed

R/ggplotly.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ gg2list <- function(p, width = NULL, height = NULL,
621621

622622
if (sum(isGrill) == 0) {
623623
# TODO: reduce the number of points (via coord_munch?)
624-
d <- sf_fortify(rng$graticule)
624+
d <- fortify_sf(rng$graticule)
625625
d$x <- scales::rescale(d$x, rng$x_range, from = c(0, 1))
626626
d$y <- scales::rescale(d$y, rng$y_range, from = c(0, 1))
627627
params <- list(

R/group2NA.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ group2NA <- function(data, groupNames = "group", nested = NULL, ordered = NULL,
4949
datClass <- oldClass(data)
5050

5151
# data.table doesn't play nice with list-columns
52-
if (inherits(data, "sf") || inherits(data, "GeomSf")) data <- sf_fortify(data)
52+
if (inherits(data, "sf")) data <- fortify_sf(data)
5353

5454
# evaluate this lazy argument now (in case we change class of data)
5555
retrace <- force(retrace.first)

R/layers2traces.R

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,27 @@ to_basic.GeomRect <- function(data, prestats_data, layout, params, p, ...) {
273273
#' @export
274274
to_basic.GeomSf <- function(data, prestats_data, layout, params, p, ...) {
275275

276-
# map sf geometry types to a suitable "basic geom"
277-
# TODO: support more of the esoteric geometry types, see sf::st_geometry_type
278-
# most important is probable geometry collection
279-
geom_type <- sf::st_geometry_type(sf::st_as_sf(data))
276+
data <- sf::st_as_sf(data)
277+
geom_type <- sf::st_geometry_type(data)
278+
# st_cast should "expand" a collection into multiple rows (one per feature)
279+
if ("GEOMETRYCOLLECTION" %in% geom_type) {
280+
data <- sf::st_cast(data)
281+
geom_type <- sf::st_geometry_type(data)
282+
}
283+
data <- remove_class(data, "sf")
284+
280285
basic_type <- dplyr::recode(
281286
as.character(geom_type),
287+
TRIANGLE = "GeomPolygon",
288+
TIN = "GeomPolygon",
289+
POLYHEDRALSURFACE = "GeomPolygon",
290+
SURFACE = "GeomPolygon",
291+
CURVE = "GeomPath",
292+
MULTISURFACE = "GeomPolygon",
293+
MULTICURVE = "GeomPath",
294+
CURVEPOLYGON = "GeomPolygon",
295+
COMPOUNDCURVE = "GeomPath",
296+
CIRCULARSTRING = "GeomPath",
282297
MULTIPOLYGON = "GeomPolygon",
283298
MULTILINESTRING = "GeomPath",
284299
MULTIPOINT = "GeomPoint",
@@ -290,7 +305,9 @@ to_basic.GeomSf <- function(data, prestats_data, layout, params, p, ...) {
290305
# return a list of data frames...one for every geometry (a la, GeomSmooth)
291306
d <- split(data, basic_type)
292307
for (i in seq_along(d)) {
293-
d[[i]] <- prefix_class(d[[i]], names(d)[[i]])
308+
d[[i]] <- prefix_class(
309+
fortify_sf(d[[i]]), c(names(d)[[i]], "GeomSf")
310+
)
294311
}
295312
if (length(d) == 1) d[[1]] else d
296313
}

R/sf.R

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
1-
sf_fortify <- function(model, ...) {
1+
fortify_sf <- function(model, ...) {
22
# TODO:
33
# (1) avoid converting redundant features
44
# (2) warn/error if data already contains x/y
55
geoms <- sf::st_geometry(sf::st_as_sf(model))
66
xy <- lapply(geoms, st_as_plotly)
77
ids <- rep(seq_len(nrow(model)), sapply(xy, nrow))
8+
# TODO: faster way to row bind matrices?
89
xy_all <- cbind(do.call(rbind, xy), ids)
9-
sf_key_name <- ".sf-group-id"
10-
xy_dat <- setNames(as.data.frame(xy_all), c("x", "y", sf_key_name))
10+
xy_dat <- setNames(as.data.frame(xy_all), c("x", "y", sf_key()))
1111

1212
d <- as.data.frame(model)
1313
d$geometry <- NULL
14-
d[[sf_key_name]] <- seq_len(nrow(d))
15-
xy_dat <- dplyr::left_join(xy_dat, d, by = sf_key_name)
16-
xy_dat[[sf_key_name]] <- NULL
14+
d[[sf_key()]] <- seq_len(nrow(d))
15+
xy_dat <- dplyr::left_join(xy_dat, d, by = sf_key())
16+
xy_dat[[sf_key()]] <- NULL
1717
xy_dat
1818
}
1919

20+
sf_key <- function() ".sf-group-id"
21+
2022
# kind of like sf_as_grob(), but maps to a plotly data structure, rather than a grob
2123
st_as_plotly <- function(x, ...) {
2224
UseMethod("st_as_plotly")
@@ -84,7 +86,6 @@ st_as_plotly.CURVEPOLYGON = st_as_plotly.GEOMETRYCOLLECTION
8486
st_as_plotly.COMPOUNDCURVE = st_as_plotly.GEOMETRYCOLLECTION
8587

8688

87-
8889
empty_xy <- function() {
8990
matrix(rep(NA, 2), ncol = 2)
9091
}

tests/testthat/test-ggplot-sf.R

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ context("geom_sf")
22

33
nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE)
44

5-
65
test_that("geom_sf() basic polygons.", {
76
skip_if_not_installed("sf")
87

@@ -20,7 +19,30 @@ test_that("geom_sf() basic polygons.", {
2019
)
2120
})
2221

23-
22+
test_that("geom_sf() geometry collection.", {
23+
skip_if_not_installed("sf")
24+
25+
# example from the sf vignette
26+
a <- sf::st_polygon(list(cbind(c(0,0,7.5,7.5,0),c(0,-1,-1,0,0))))
27+
b <- sf::st_polygon(list(cbind(c(0,1,2,3,4,5,6,7,7,0),c(1,0,.5,0,0,0.5,-0.5,-0.5,1,1))))
28+
i <- sf::st_intersection(a, b)
29+
cd <- sf::st_as_sf(data.frame(x = 1, geometry = sf::st_sfc(i)))
30+
31+
p <- ggplot(cd) + geom_sf()
32+
l <- save_outputs(p, "sf-geom-collection")
33+
34+
# graticule, point, line, polygon
35+
expect_length(l$data, 4)
36+
37+
# test data/default for line
38+
# TODO: test that defaults are correct one geom_sf() becomes stable
39+
expect_equivalent(l$data[[2]]$x, c(4, 3))
40+
expect_equivalent(l$data[[2]]$y, c(0, 0))
41+
expect_equivalent(l$data[[3]]$x, I(1))
42+
expect_equivalent(l$data[[3]]$y, I(0))
43+
expect_equivalent(l$data[[4]]$x, c(5.5, 7, 7, 6, 5.5, NA, 5.5))
44+
expect_equivalent(l$data[[4]]$y, c(0, 0, -.5, -.5, 0, NA, 0))
45+
})
2446

2547
test_that("geom_sf() polygons with fill/text.", {
2648
skip_if_not_installed("sf")
@@ -40,8 +62,6 @@ test_that("geom_sf() polygons with fill/text.", {
4062
)
4163
})
4264

43-
44-
4565
test_that("geom_sf() with basic polygons and points.", {
4666
skip_if_not_installed("sf")
4767

@@ -65,7 +85,6 @@ test_that("sf aspect ratio is correct", {
6585
p <- ggplot(nc) + geom_sf()
6686

6787
l <- save_outputs(p, "sf-aspect")
68-
6988
expect_equivalent(l$layout$xaxis$scaleanchor, "y")
7089
expect_equal(l$layout$xaxis$scaleratio, 0.81678435872298)
7190
})

0 commit comments

Comments
 (0)