Skip to content

Commit fc03f2a

Browse files
authored
Merge pull request #70 from r-spatial/dewey-simplify
Attempt s2_simplify() (closes #23, closes #51)
2 parents 27afb99 + e6fa9d6 commit fc03f2a

18 files changed

+687
-246
lines changed

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ export(s2_num_points)
101101
export(s2_options)
102102
export(s2_perimeter)
103103
export(s2_point)
104+
export(s2_rebuild)
105+
export(s2_simplify)
104106
export(s2_snap_distance)
105107
export(s2_snap_identity)
106108
export(s2_snap_level)

R/RcppExports.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,10 @@ cpp_s2_boundary <- function(geog) {
245245
.Call(`_s2_cpp_s2_boundary`, geog)
246246
}
247247

248+
cpp_s2_rebuild <- function(geog, s2options) {
249+
.Call(`_s2_cpp_s2_rebuild`, geog, s2options)
250+
}
251+
248252
cpp_s2_buffer_cells <- function(geog, distance, maxCells, minLevel) {
249253
.Call(`_s2_cpp_s2_buffer_cells`, geog, distance, maxCells, minLevel)
250254
}

R/s2-matrix.R

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,25 +73,25 @@ s2_max_distance_matrix <- function(x, y, radius = s2_earth_radius_meters()) {
7373

7474
#' @rdname s2_closest_feature
7575
#' @export
76-
s2_contains_matrix <- function(x, y, options = s2_options(model = 0)) {
76+
s2_contains_matrix <- function(x, y, options = s2_options(model = "open")) {
7777
cpp_s2_contains_matrix(as_s2_geography(x), as_s2_geography(y), options)
7878
}
7979

8080
#' @rdname s2_closest_feature
8181
#' @export
82-
s2_within_matrix <- function(x, y, options = s2_options(model = 0)) {
82+
s2_within_matrix <- function(x, y, options = s2_options(model = "open")) {
8383
cpp_s2_within_matrix(as_s2_geography(x), as_s2_geography(y), options)
8484
}
8585

8686
#' @rdname s2_closest_feature
8787
#' @export
88-
s2_covers_matrix <- function(x, y, options = s2_options(model = 2)) {
88+
s2_covers_matrix <- function(x, y, options = s2_options(model = "closed")) {
8989
cpp_s2_contains_matrix(as_s2_geography(x), as_s2_geography(y), options)
9090
}
9191

9292
#' @rdname s2_closest_feature
9393
#' @export
94-
s2_covered_by_matrix <- function(x, y, options = s2_options(model = 2)) {
94+
s2_covered_by_matrix <- function(x, y, options = s2_options(model = "closed")) {
9595
cpp_s2_within_matrix(as_s2_geography(x), as_s2_geography(y), options)
9696
}
9797

@@ -149,11 +149,11 @@ s2_within_matrix_brute_force <- function(x, y, options = s2_options()) {
149149
cpp_s2_within_matrix_brute_force(as_s2_geography(x), as_s2_geography(y), options)
150150
}
151151

152-
s2_covers_matrix_brute_force <- function(x, y, options = s2_options(model = 2)) {
152+
s2_covers_matrix_brute_force <- function(x, y, options = s2_options(model = "closed")) {
153153
cpp_s2_contains_matrix_brute_force(as_s2_geography(x), as_s2_geography(y), options)
154154
}
155155

156-
s2_covered_by_matrix_brute_force <- function(x, y, options = s2_options(model = 2)) {
156+
s2_covered_by_matrix_brute_force <- function(x, y, options = s2_options(model = "closed")) {
157157
cpp_s2_within_matrix_brute_force(as_s2_geography(x), as_s2_geography(y), options)
158158
}
159159

R/s2-options.R

Lines changed: 77 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,30 @@
66
#' and boolean operations (e.g., [s2_intersection()]) to specify the model for
77
#' containment and how new geometries should be constructed.
88
#'
9-
#' @param model,polygon_model,polyline_model See section 'Model'
9+
#' @param model One of 'open', 'semi-open' (default for polygons),
10+
#' or 'closed' (default for polylines). See section 'Model'
1011
#' @param snap Use `s2_snap_identity()`, `s2_snap_distance()`, `s2_snap_level()`,
1112
#' or `s2_snap_precision()` to specify how or if coordinate rounding should
1213
#' occur.
1314
#' @param snap_radius As opposed to the snap function, which specifies
1415
#' the maximum distance a vertex should move, the snap radius (in radians) sets
1516
#' the minimum distance between vertices of the output that don't cause vertices
1617
#' to move more than the distance specified by the snap function. This can be used
17-
#' to simplify the result of a boolean operation.
18+
#' to simplify the result of a boolean operation. Use -1 to specify that any
19+
#' minimum distance is acceptable.
20+
#' @param duplicate_edges Use `TRUE` to keep duplicate edges (e.g., duplicate
21+
#' points).
22+
#' @param edge_type One of 'directed' (default) or 'undirected'.
23+
#' @param polyline_type One of 'path' (default) or 'walk'. If 'walk',
24+
#' polylines that backtrack are preserved.
25+
#' @param polyline_sibling_pairs One of 'discard' (default) or 'keep'.
26+
#' @param simplify_edge_chains Use `TRUE` to remove vertices that are within
27+
#' `snap_radius` of the original vertex.
28+
#' @param split_crossing_edges Use `TRUE` to split crossing polyline edges
29+
#' when creating geometries.
30+
#' @param idempotent Use `FALSE` to apply snap even if snapping is not necessary
31+
#' to satisfy vertex constraints.
32+
#' @param validate Use `TRUE` to validate the result from the builder.
1833
#' @param level A value from 0 to 30 corresponding to the cell level
1934
#' at which snapping should occur.
2035
#' @param distance A distance (in radians) denoting the maximum
@@ -25,61 +40,61 @@
2540
#' @section Model:
2641
#' The geometry model indicates whether or not a geometry includes its boundaries.
2742
#' Boundaries of line geometries are its end points.
28-
#' OPEN geometries do not contain their boundary (`model = 0`); CLOSED
29-
#' geometries (`model = 2`) contain their boundary; HALF-CLOSED geometries
30-
#' (`model = 1`) contain half of their boundaries, such that when two polygons
43+
#' OPEN geometries do not contain their boundary (`model = "open"`); CLOSED
44+
#' geometries (`model = "closed"`) contain their boundary; SEMI-OPEN geometries
45+
#' (`model = "semi-open"`) contain half of their boundaries, such that when two polygons
3146
#' do not overlap or two lines do not cross, no point exist that belong to
3247
#' more than one of the geometries. (This latter form, half-closed, is
3348
#' not present in the OpenGIS "simple feature access" (SFA) standard nor DE9-IM on
34-
#' which that is based). A value of -1 does not set the model, leaving the
35-
#' S2 default (HALF-CLOSED). The default values for [s2_contains()] (0)
36-
#' and covers/covered_by (2) correspond to the SFA standard specification
49+
#' which that is based). The default values for [s2_contains()] (open)
50+
#' and covers/covered_by (closed) correspond to the SFA standard specification
3751
#' of these operators.
3852
#'
3953
#' @export
4054
#'
4155
#' @examples
42-
#' # use s2_options() to specify polygon/polyline models
43-
#' # and/or snap level
44-
#' s2_options(model = 1, snap = s2_snap_level(30))
56+
#' # use s2_options() to specify containment models, snap level
57+
#' # layer creation options, and builder options
58+
#' s2_options(model = "closed", snap = s2_snap_level(30))
4559
#'
46-
#' # model value affects boolean operations and binary predicates
47-
#' # in the open model, lines do not contain endpoints (but do contain other points)
48-
#' s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 0)", s2_options(model = 0))
49-
#' s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 1)", s2_options(model = 0))
50-
#' s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 0.5)", s2_options(model = 0))
51-
#'
52-
#' # in the semi-open and closed models, endpoints are contained
53-
#' s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 0)", s2_options(model = 1))
54-
#' s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 1)", s2_options(model = 1))
55-
#' s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 0)", s2_options(model = 2))
56-
#' s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 1)", s2_options(model = 2))
57-
#'
58-
#' # for polygons, boundary points are either contained or not contained depending on
59-
#' # the model of choice
60-
#' s2_contains("POLYGON ((0 0, 0 1, 1 1, 0 0))", "POINT (0 0)", s2_options(model = 0))
61-
#' s2_contains("POLYGON ((0 0, 0 1, 1 1, 0 0))", "POINT (0.5 0.75)", s2_options(model = 0))
62-
#'
63-
#' s2_contains("POLYGON ((0 0, 0 1, 1 1, 0 0))", "POINT (0 0)", s2_options(model = 1))
64-
#' s2_contains("POLYGON ((0 0, 0 1, 1 1, 0 0))", "POINT (0.5 0.75)", s2_options(model = 1))
65-
#' s2_contains("POLYGON ((0 0, 0 1, 1 1, 0 0))", "POINT (0 0)", s2_options(model = 2))
66-
#' s2_contains("POLYGON ((0 0, 0 1, 1 1, 0 0))", "POINT (0.5 0.75)", s2_options(model = 2))
67-
#'
68-
#' # s2_dwithin(x, y, epsilon) is a more explicit test if boundaries are important
69-
#' s2_dwithin(
70-
#' "LINESTRING (0 0, 0 1, 1 1)",
71-
#' c("POINT (0 0)", "POINT (0 1)", "POINT (0 0.5)"),
72-
#' 1e-7
73-
#' )
74-
#'
75-
s2_options <- function(model = -1, snap = s2_snap_identity(), snap_radius = -1,
76-
polygon_model = model, polyline_model = model) {
60+
s2_options <- function(model = NULL,
61+
snap = s2_snap_identity(),
62+
snap_radius = -1,
63+
duplicate_edges = FALSE,
64+
edge_type = "directed",
65+
validate = FALSE,
66+
polyline_type = "path",
67+
polyline_sibling_pairs = "keep",
68+
simplify_edge_chains = FALSE,
69+
split_crossing_edges = FALSE,
70+
idempotent = FALSE) {
71+
# check snap radius (passing in a huge snap radius can cause problems)
72+
if (snap_radius > 3) {
73+
stop(
74+
"Snap radius is too large. Did you pass in a snap radius in meters instead of radians?",
75+
call. = FALSE
76+
)
77+
}
78+
7779
structure(
7880
list(
79-
polygon_model = polygon_model,
80-
polyline_model = polyline_model,
81+
# model needs to be "unset" by default because there are differences in polygon
82+
# and polyline handling by default that are good defaults to preserve
83+
model = if (is.null(model)) -1 else match_option(model, c("open", "semi-open", "closed"), "model"),
8184
snap = snap,
82-
snap_radius = snap_radius
85+
snap_radius = snap_radius,
86+
duplicate_edges = duplicate_edges,
87+
edge_type = match_option(edge_type, c("directed", "undirected"), "edge_type"),
88+
validate = validate,
89+
polyline_type = match_option(polyline_type, c("path", "walk"), "polyline_type"),
90+
polyline_sibling_pairs = match_option(
91+
polyline_sibling_pairs,
92+
c("discard", "keep"),
93+
"polyline_sibling_pairs"
94+
),
95+
simplify_edge_chains = simplify_edge_chains,
96+
split_crossing_edges = split_crossing_edges,
97+
idempotent = idempotent
8398
),
8499
class = "s2_options"
85100
)
@@ -94,6 +109,10 @@ s2_snap_identity <- function() {
94109
#' @rdname s2_options
95110
#' @export
96111
s2_snap_level <- function(level) {
112+
if (level > 30) {
113+
stop("`level` must be an intger between 1 and 30", call. = FALSE)
114+
}
115+
97116
structure(list(level = level), class = "snap_level")
98117
}
99118

@@ -108,3 +127,16 @@ s2_snap_precision <- function(precision) {
108127
s2_snap_distance <- function(distance) {
109128
structure(list(distance = distance), class = "snap_distance")
110129
}
130+
131+
132+
match_option <- function(x, options, arg) {
133+
result <- match(x, options)
134+
if (identical(result, NA_integer_)) {
135+
stop(
136+
sprintf("`%s` must be one of %s", arg, paste0('"', options, '"', collapse = ", ")),
137+
call. = FALSE
138+
)
139+
}
140+
141+
result
142+
}

R/s2-predicates.R

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,26 +99,26 @@
9999
#' 1e6 # distance in meters
100100
#' )
101101
#'
102-
s2_contains <- function(x, y, options = s2_options(model = 0)) {
102+
s2_contains <- function(x, y, options = s2_options(model = "open")) {
103103
recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y))
104104
cpp_s2_contains(recycled[[1]], recycled[[2]], options)
105105
}
106106

107107
#' @rdname s2_contains
108108
#' @export
109-
s2_within <- function(x, y, options = s2_options(model = 0)) {
109+
s2_within <- function(x, y, options = s2_options(model = "open")) {
110110
s2_contains(y, x, options)
111111
}
112112

113113
#' @rdname s2_contains
114114
#' @export
115-
s2_covered_by <- function(x, y, options = s2_options(model = 2)) {
115+
s2_covered_by <- function(x, y, options = s2_options(model = "closed")) {
116116
s2_covers(y, x, options)
117117
}
118118

119119
#' @rdname s2_contains
120120
#' @export
121-
s2_covers <- function(x, y, options = s2_options(model = 2)) {
121+
s2_covers <- function(x, y, options = s2_options(model = "closed")) {
122122
recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y))
123123
cpp_s2_contains(recycled[[1]], recycled[[2]], options)
124124
}

R/s2-transformers.R

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#' (1 - 30). Setting this value too high will result in unnecessarily
1818
#' large geographies, but may help improve buffers along long, narrow
1919
#' regions.
20+
#' @param tolerance The minimum distance between vertexes to use when
21+
#' simplifying a geography.
2022
#'
2123
#' @inheritSection s2_options Model
2224
#'
@@ -164,7 +166,25 @@ s2_union <- function(x, y = NULL, options = s2_options()) {
164166
#' @rdname s2_boundary
165167
#' @export
166168
s2_snap_to_grid <- function(x, grid_size) {
167-
s2_union(x, options = s2_options(snap = s2_snap_precision(10^(-log10(grid_size)))))
169+
s2_rebuild(
170+
x,
171+
options = s2_options(
172+
snap = s2_snap_precision(10^(-log10(grid_size))),
173+
duplicate_edges = TRUE
174+
)
175+
)
176+
}
177+
178+
#' @rdname s2_boundary
179+
#' @export
180+
s2_simplify <- function(x, tolerance, radius = s2_earth_radius_meters()) {
181+
s2_rebuild(x, options = s2_options(snap_radius = tolerance / radius, simplify_edge_chains = TRUE))
182+
}
183+
184+
#' @rdname s2_boundary
185+
#' @export
186+
s2_rebuild <- function(x, options = s2_options()) {
187+
new_s2_xptr(cpp_s2_rebuild(as_s2_geography(x), options), "s2_geography")
168188
}
169189

170190
#' @rdname s2_boundary

man/as_s2_geography.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/s2_boundary.Rd

Lines changed: 17 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/s2_closest_feature.Rd

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)