-
Notifications
You must be signed in to change notification settings - Fork 294
/
valid.R
101 lines (94 loc) · 5.23 KB
/
valid.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#' @name valid
#' @param NA_on_exception logical; if TRUE, for polygons that would otherwise raise a GEOS error (exception, e.g. for a POLYGON having more than zero but less than 4 points, or a LINESTRING having one point) return an \code{NA} rather than raising an error, and suppress warning messages (e.g. about self-intersection); if FALSE, regular GEOS errors and warnings will be emitted.
#' @param reason logical; if \code{TRUE}, return a character with, for each geometry, the reason for invalidity, \code{NA} on exception, or \code{"Valid Geometry"} otherwise.
#' @return \code{st_is_valid} returns a logical vector indicating for each geometries of \code{x} whether it is valid. \code{st_make_valid} returns an object with a topologically valid geometry.
#' @export
#' @examples
#' p1 = st_as_sfc("POLYGON((0 0, 0 10, 10 0, 10 10, 0 0))")
#' st_is_valid(p1)
#' st_is_valid(st_sfc(st_point(0:1), p1[[1]]), reason = TRUE)
st_is_valid = function(x, ...) UseMethod("st_is_valid")
#' @export
#' @name valid
st_is_valid.sfc = function(x, ..., NA_on_exception = TRUE, reason = FALSE) {
if (sf_use_s2() && isTRUE(st_is_longlat(x))) {
if (reason) {
r = s2::s2_is_valid_detail(x)
r$reason[r$is_valid] = "Valid Geometry"
r$reason
} else
s2::s2_is_valid(x)
} else if (reason) {
if (NA_on_exception) {
ret = rep(NA_character_, length(x))
not_na = !is.na(st_is_valid(x, reason = FALSE))
ret[not_na] = CPL_geos_is_valid_reason(x[not_na])
ret
} else
CPL_geos_is_valid_reason(x)
} else
CPL_geos_is_valid(x, as.logical(NA_on_exception))
}
#' @export
#' @name valid
st_is_valid.sf = function(x, ...) {
st_is_valid(st_geometry(x), ...)
}
#' @name valid
#' @export
st_is_valid.sfg = function(x, ...) {
st_is_valid(st_geometry(x), ...)
}
#' Check validity or make an invalid geometry valid
#'
#' Checks whether a geometry is valid, or makes an invalid geometry valid
#' @name valid
#' @param x object of class \code{sfg}, \code{sfc} or \code{sf}
#' @return Object of the same class as \code{x}
#' @details For projected geometries, \code{st_make_valid} uses the \code{lwgeom_makevalid} method also used by the PostGIS command \code{ST_makevalid} if the GEOS version linked to is smaller than 3.8.0, and otherwise the version shipped in GEOS; for geometries having ellipsoidal coordinates \code{s2::s2_rebuild} is being used.
#' @examples
#' library(sf)
#' x = st_sfc(st_polygon(list(rbind(c(0,0),c(0.5,0),c(0.5,0.5),c(0.5,0),c(1,0),c(1,1),c(0,1),c(0,0)))))
#' suppressWarnings(st_is_valid(x))
#' y = st_make_valid(x)
#' st_is_valid(y)
#' y %>% st_cast()
#' @export
st_make_valid = function(x, ...) UseMethod("st_make_valid")
#' @export
#' @name valid
st_make_valid.sfg = function(x, ...) {
st_make_valid(st_geometry(x), ...)[[1]]
}
#' @name valid
#' @export
#' @param ... passed on to \link[s2]{s2_options}
#' @param oriented logical; only relevant if \code{st_is_longlat(x)} is \code{TRUE}; see \link{s2}
#' @param s2_options only relevant if \code{st_is_longlat(x)} is \code{TRUE}; options for \link[s2]{s2_rebuild}, see \link[s2]{s2_options} and Details.
#' @param geos_method character; either "valid_linework" (Original method, combines all rings into a set of noded lines and then extracts valid polygons from that linework) or "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.) (requires GEOS >= 3.10.1)
#' @param geos_keep_collapsed logical; When this parameter is not set to \code{FALSE}, the "valid_structure" method will keep any component that has collapsed into a lower dimensionality. For example, a ring collapsing to a line, or a line collapsing to a point (requires GEOS >= 3.10.1)
#' @details if \code{s2_options} is not specified and \code{x} has a non-zero precision set, then this precision value will be used as the value in \code{s2_snap_precision}, passed on to \code{s2_options}, rather than the 1e7 default.
st_make_valid.sfc = function(x, ..., oriented = FALSE, s2_options = s2::s2_options(snap = s2::s2_snap_precision(1e7), ...),
geos_method = "valid_structure", geos_keep_collapsed = TRUE) {
crs = st_crs(x)
if (sf_use_s2() && isTRUE(st_is_longlat(x))) {
if (!missing(geos_method) || !missing(geos_keep_collapsed))
warning("arguments geos_method and geos_keep_collapsed are ignored for geodetic coordinates as sf_use_s2() is TRUE")
s2 = s2::as_s2_geography(st_as_binary(st_set_precision(x, 0.0)), oriented = oriented, check = FALSE)
if (st_precision(x) != 0 && missing(s2_options))
s2_options = s2::s2_options(snap = s2::s2_snap_precision(st_precision(x)), ...)
s2 = s2::s2_rebuild(s2, s2_options)
st_as_sfc(s2, crs = crs)
} else if (compareVersion(CPL_geos_version(), "3.8.0") == -1) {
if (!missing(geos_method) || !missing(geos_keep_collapsed))
warning("ignoring arguments geos_method and geos_keep_collapsed, as these require GEOS >= 3.8.0")
if (!requireNamespace("lwgeom", quietly = TRUE))
stop("package lwgeom required, please install it first") # nocov
st_sfc(lwgeom::lwgeom_make_valid(x), crs = crs)
} else
st_sfc(CPL_geos_make_valid(x, geos_method, geos_keep_collapsed), crs = crs)
}
#' @export
st_make_valid.sf = function(x, ...) {
st_set_geometry(x, st_make_valid(st_geometry(x), ...))
}