Skip to content

Commit

Permalink
add geos_make_valid() with params
Browse files Browse the repository at this point in the history
  • Loading branch information
paleolimbot committed Oct 24, 2021
1 parent 428a297 commit 1c80196
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 6 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export(geos_make_linestring)
export(geos_make_point)
export(geos_make_polygon)
export(geos_make_valid)
export(geos_make_valid_params)
export(geos_maximum_inscribed_circle_spec)
export(geos_maximum_inscribed_crc)
export(geos_merge_lines)
Expand Down
29 changes: 27 additions & 2 deletions R/geos-unary-geometry.R
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
#' be preserved?
#' @param keep_collapsed Should items that become EMPTY due to rounding
#' be kept in the output?
#' @param make_valid_params A [geos_make_valid_params()] object.
#' @param method The method to use for [geos_make_valid()]. One of:
#' - "make_valid_linework" combines all rings into a set of noded lines
#' and then extracts valid polygons from that linework.
#' - "make_valid_structure" Structured method, first makes all rings valid
#' then merges shells and subtracts holes from shells to generate valid
#' result. Assumes that holes and shells are correctly categorized.
#' @param grid_size For `_prec()` variants, the grid size such that all vertices of
#' the resulting geometry will lie on the grid.
#'
Expand Down Expand Up @@ -174,9 +181,27 @@ geos_node <- function(geom) {

#' @rdname geos_centroid
#' @export
geos_make_valid <- function(geom) {
geos_make_valid <- function(geom, make_valid_params = geos_make_valid_params()) {
geom <- sanitize_geos_geometry(geom)
new_geos_geometry(.Call(geos_c_make_valid, geom), crs = attr(geom, "crs", exact = TRUE))
new_geos_geometry(
.Call(geos_c_make_valid_with_params, geom, make_valid_params),
crs = attr(geom, "crs", exact = TRUE)
)
}

#' @rdname geos_centroid
#' @export
geos_make_valid_params <- function(keep_collapsed = TRUE,
method = c("make_valid_linework", "make_valid_structure")) {
method <- match.arg(method)

structure(
list(
keep_collapsed = sanitize_logical_scalar(keep_collapsed),
method = match(method, c("make_valid_linework", "make_valid_structure")) - 1L
),
class = "geos_make_valid_params"
)
}

#' @rdname geos_centroid
Expand Down
25 changes: 21 additions & 4 deletions man/geos_centroid.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 76 additions & 0 deletions src/geos-unary-geometry.c
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,78 @@ SEXP geos_c_constrained_delaunay_triangulation(SEXP geom) {
#endif
}

SEXP geos_c_make_valid_with_params(SEXP geom, SEXP params_sexp) {
if (!Rf_inherits(params_sexp, "geos_make_valid_params")) {
Rf_error("`make_valid_params` must be created using `geos_make_valid_params()`");
}

int keepCollapsed = LOGICAL(VECTOR_ELT(params_sexp, 0))[0];
int method = INTEGER(VECTOR_ELT(params_sexp, 1))[0];

// non-default options need GEOS 3.10 and GEOSMakeValidWithParams_r()
// for now, just use the previous method
if ((keepCollapsed == 1) && (method == 0)) {
return geos_c_make_valid(geom);
}

#if LIBGEOS_VERSION_COMPILE_INT >= LIBGEOS_VERSION_INT(3, 10, 0)
if (libgeos_version_int() < LIBGEOS_VERSION_INT(3, 10, 0)) {
ERROR_OLD_LIBGEOS("GEOSMakeValidWithParams_r()", "3.10.0");
}

R_xlen_t size = Rf_xlength(geom);
SEXP result = PROTECT(Rf_allocVector(VECSXP, size));

GEOS_INIT();

GEOSMakeValidParams* params = GEOSMakeValidParams_create_r(handle);
if (params == NULL) {
Rf_error("Failed to alloc GEOSMakeValidWithParams_r()"); // # nocov
}

if (GEOSMakeValidParams_setKeepCollapsed_r(handle, params, keepCollapsed) == 0) {
GEOSMakeValidParams_destroy_r(handle, params); // # nocov
GEOS_ERROR("%s: ", "keep_collapsed"); // # nocov
}

if (GEOSMakeValidParams_setMethod_r(handle, params, method) == 0) {
GEOSMakeValidParams_destroy_r(handle, params);
GEOS_ERROR("%s: ", "method");
}

SEXP item;
GEOSGeometry* geometry;
GEOSGeometry* geometryResult;
for (R_xlen_t i = 0; i < size; i++) {
item = VECTOR_ELT(geom, i);

if (item == R_NilValue) {
SET_VECTOR_ELT(result, i, R_NilValue);
continue;
}

geometry = (GEOSGeometry*) R_ExternalPtrAddr(item);
GEOS_CHECK_GEOMETRY(geometry, i);

geometryResult = GEOSMakeValidWithParams_r(handle, geometry, params);

if (geometryResult == NULL) {
GEOSMakeValidParams_destroy_r(handle, params);
Rf_error("[%d] %s", i + 1, globalErrorMessage);
} else {
SET_VECTOR_ELT(result, i, geos_common_geometry_xptr(geometryResult));
}
}

GEOSMakeValidParams_destroy_r(handle, params);
UNPROTECT(1);
return result;

#else
ERROR_OLD_LIBGEOS_BUILD("GEOSMakeValidWithParams_r()", "3.10.0");
#endif
}


#define GEOS_UNARY_GEOMETRY_PARAM(_call, _param_scalar, _param_ptr, _na_check) \
R_xlen_t size = Rf_xlength(geom); \
Expand Down Expand Up @@ -507,6 +579,10 @@ SEXP geos_c_voronoi_diagram(SEXP geom, SEXP env, SEXP tolerace, SEXP edges) {
\
GEOS_INIT(); \
GEOSBufferParams* bufferParams = GEOSBufferParams_create_r(handle);\
if (bufferParams == NULL) { \
Rf_error("Failed to alloc GEOSBufferParams"); \
} \
\
if (GEOSBufferParams_setEndCapStyle_r(handle, bufferParams, endCapStyle) == 0) {\
GEOSBufferParams_destroy_r(handle, bufferParams); \
GEOS_ERROR("%s: ", "end_cap_style"); \
Expand Down
2 changes: 2 additions & 0 deletions src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ extern SEXP geos_c_point_start(SEXP geom);
extern SEXP geos_c_point_end(SEXP geom);
extern SEXP geos_c_clone(SEXP geom);
extern SEXP geos_c_constrained_delaunay_triangulation(SEXP geom);
extern SEXP geos_c_make_valid_with_params(SEXP geom, SEXP params_sexp);
extern SEXP geos_c_interpolate(SEXP geom, SEXP param);
extern SEXP geos_c_interpolate_normalized(SEXP geom, SEXP param);
extern SEXP geos_c_point_n(SEXP geom, SEXP param);
Expand Down Expand Up @@ -303,6 +304,7 @@ static const R_CallMethodDef CallEntries[] = {
{"geos_c_point_end", (DL_FUNC) &geos_c_point_end, 1},
{"geos_c_clone", (DL_FUNC) &geos_c_clone, 1},
{"geos_c_constrained_delaunay_triangulation", (DL_FUNC) &geos_c_constrained_delaunay_triangulation, 1},
{"geos_c_make_valid_with_params", (DL_FUNC) &geos_c_make_valid_with_params, 2},
{"geos_c_interpolate", (DL_FUNC) &geos_c_interpolate, 2},
{"geos_c_interpolate_normalized", (DL_FUNC) &geos_c_interpolate_normalized, 2},
{"geos_c_point_n", (DL_FUNC) &geos_c_point_n, 2},
Expand Down
29 changes: 29 additions & 0 deletions tests/testthat/test-geos-unary-geometry.R
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,35 @@ test_that("transformers work", {
)
})

test_that("geos_make_valid() works with params", {
skip_if_not(geos_version() >= "3.10.0")

expect_identical(
geos_area(
geos_make_valid(
c("POLYGON ((0 0, 1 1, 1 0, 0 1, 0 0))", NA),
geos_make_valid_params(keep_collapsed = FALSE)
)
),
geos_area(geos_make_valid(c("POLYGON ((0 0, 1 1, 1 0, 0 1, 0 0))", NA)))
)

expect_identical(
geos_area(
geos_make_valid(
c("POLYGON ((0 0, 1 1, 1 0, 0 1, 0 0))", NA),
geos_make_valid_params(method = "make_valid_structure")
)
),
geos_area(geos_make_valid(c("POLYGON ((0 0, 1 1, 1 0, 0 1, 0 0))", NA)))
)

expect_error(geos_make_valid("POINT (0 1)", NULL), "must be created using")
params_bad <- geos_make_valid_params()
params_bad$method <- 100L
expect_error(geos_make_valid("POINT (0 1)", params_bad), "Unknown method")
})

test_that("geos_envelope_rct() works", {
expect_identical(
geos_envelope_rct(c("LINESTRING (0 0, 1 2)", "LINESTRING EMPTY", NA)),
Expand Down

0 comments on commit 1c80196

Please sign in to comment.