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 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
|
#' @name geos_query
#' @export
#' @return st_is_simple returns a logical vector, indicating for each geometry whether it is simple (e.g., not self-intersecting)
#' @examples
#' ls = st_linestring(rbind(c(0,0), c(1,1), c(1,0), c(0,1)))
#' st_is_simple(st_sfc(ls, st_point(c(0,0))))
st_is_simple = function(x) CPL_geos_is_simple(st_geometry(x))
#' @name geos_query
#' @export
#' @return st_is_empty returns for each geometry whether it is empty
#' @examples
#' ls = st_linestring(rbind(c(0,0), c(1,1), c(1,0), c(0,1)))
#' st_is_empty(st_sfc(ls, st_point(), st_linestring()))
st_is_empty = function(x) CPL_geos_is_empty(st_geometry(x))
is_symmetric = function(operation, pattern) {
if (!is.na(pattern)) {
m = matrix(sapply(1:9, function(i) substr(pattern, i, i)), 3, 3)
isTRUE(all(m == t(m)))
} else
isTRUE(operation %in% c("intersects", "touches", "overlaps", "disjoint", "equals"))
}
# binary, interfaced through GEOS or S2:
# [1] X "s2_contains_matrix" X "s2_covered_by_matrix"
# [3] X "s2_covers_matrix" X "s2_disjoint_matrix"
# [5] X "s2_distance_matrix" X "s2_dwithin_matrix"
# [7] X "s2_equals_matrix" X "s2_intersects_matrix"
# [9] "s2_max_distance_matrix" "s2_may_intersect_matrix"
#[11] X "s2_touches_matrix" X "s2_within_matrix"
# returning matrix, distance or relation string -- the work horse is:
st_geos_binop = function(op, x, y, par = 0.0, pattern = NA_character_,
sparse = TRUE, prepared = FALSE, s2_model = "closed", ...) {
if (missing(y))
y = x
else if (inherits(x, c("sf", "sfc")) && inherits(y, c("sf", "sfc")))
stopifnot(st_crs(x) == st_crs(y))
longlat = inherits(x, "s2geography") || isTRUE(st_is_longlat(x))
if (longlat && sf_use_s2() && op %in% c("intersects", "contains", "within",
"covers", "covered_by", "disjoint", "equals", "touches")) {
if (!requireNamespace("s2", quietly = TRUE))
stop("package s2 required, please install it first")
fn = get(paste0("s2_", op, "_matrix"), envir = getNamespace("s2")) # get op function
lst = fn(x, y, s2::s2_options(model = s2_model, ...)) # call function
id = if (is.null(row.names(x)))
as.character(seq_along(lst))
else
row.names(x)
sgbp(lst, predicate = op, region.id = id, ncol = length(st_geometry(y)), sparse)
} else {
if (longlat && !(op %in% c("equals", "equals_exact")))
message_longlat(paste0("st_", op))
if (prepared && is_symmetric(op, pattern) &&
length(dx <- st_dimension(x)) && length(dy <- st_dimension(y)) &&
isTRUE(all(dx == 0)) && isTRUE(all(dy == 2))) {
t(st_geos_binop(op, y, x, par = par, pattern = pattern, sparse = sparse,
prepared = prepared))
} else {
ret = CPL_geos_binop(st_geometry(x), st_geometry(y), op, par, pattern, prepared)
if (length(ret) == 0 || is.null(dim(ret[[1]]))) {
id = if (is.null(row.names(x)))
as.character(seq_along(ret))
else
row.names(x)
sgbp(ret, predicate = op, region.id = id, ncol = length(st_geometry(y)), sparse)
} else # CPL_geos_binop returned a matrix, e.g. from op = "relate"
ret[[1]]
}
}
}
#' Compute DE9-IM relation between pairs of geometries, or match it to a given pattern
#'
#' Compute DE9-IM relation between pairs of geometries, or match it to a given pattern
#' @param x object of class \code{sf}, \code{sfc} or \code{sfg}
#' @param y object of class \code{sf}, \code{sfc} or \code{sfg}
#' @param pattern character; define the pattern to match to, see details.
#' @param sparse logical; should a sparse matrix be returned (TRUE) or a dense matrix?
#' @return In case \code{pattern} is not given, \code{st_relate} returns a dense \code{character} matrix; element [i,j] has nine characters, referring to the DE9-IM relationship between x[i] and y[j], encoded as IxIy,IxBy,IxEy,BxIy,BxBy,BxEy,ExIy,ExBy,ExEy where I refers to interior, B to boundary, and E to exterior, and e.g. BxIy the dimensionality of the intersection of the the boundary of x[i] and the interior of y[j], which is one of {0,1,2,F}, digits denoting dimensionality, F denoting not intersecting. When \code{pattern} is given, a dense logical matrix or sparse index list returned with matches to the given pattern; see \link{st_intersection} for a description of the returned matrix or list. See also \url{https://en.wikipedia.org/wiki/DE-9IM} for further explanation.
#' @export
#' @examples
#' p1 = st_point(c(0,0))
#' p2 = st_point(c(2,2))
#' pol1 = st_polygon(list(rbind(c(0,0),c(1,0),c(1,1),c(0,1),c(0,0)))) - 0.5
#' pol2 = pol1 + 1
#' pol3 = pol1 + 2
#' st_relate(st_sfc(p1, p2), st_sfc(pol1, pol2, pol3))
#' sfc = st_sfc(st_point(c(0,0)), st_point(c(3,3)))
#' grd = st_make_grid(sfc, n = c(3,3))
#' st_intersects(grd)
#' st_relate(grd, pattern = "****1****") # sides, not corners, internals
#' st_relate(grd, pattern = "****0****") # only corners touch
#' st_rook = function(a, b = a) st_relate(a, b, pattern = "F***1****")
#' st_rook(grd)
#' # queen neighbours, see \url{https://github.com/r-spatial/sf/issues/234#issuecomment-300511129}
#' st_queen <- function(a, b = a) st_relate(a, b, pattern = "F***T****")
st_relate = function(x, y, pattern = NA_character_, sparse = !is.na(pattern)) {
if (!is.na(pattern)) {
stopifnot(is.character(pattern) && length(pattern) == 1 && nchar(pattern) == 9)
st_geos_binop("relate_pattern", x, y, pattern = pattern, sparse = sparse)
} else
st_geos_binop("relate", x, y, sparse = FALSE)
}
#' Geometric binary predicates on pairs of simple feature geometry sets
#'
#' Geometric binary predicates on pairs of simple feature geometry sets
#' @name geos_binary_pred
#' @param x object of class \code{sf}, \code{sfc} or \code{sfg}
#' @param y object of class \code{sf}, \code{sfc} or \code{sfg}; if missing, \code{x} is used
#' @param sparse logical; should a sparse index list be returned (TRUE) or a dense logical matrix? See below.
#' @param ... passed on to \link[s2]{s2_options}
#' @param prepared logical; prepare geometry for x, before looping over y? See Details.
#' @details If \code{prepared} is \code{TRUE}, and \code{x} contains POINT geometries and \code{y} contains polygons, then the polygon geometries are prepared, rather than the points.
#' @return If \code{sparse=FALSE}, \code{st_predicate} (with \code{predicate} e.g. "intersects") returns a dense logical matrix with element \code{i,j} \code{TRUE} when \code{predicate(x[i], y[j])} (e.g., when geometry of feature i and j intersect); if \code{sparse=TRUE}, an object of class \code{\link{sgbp}} with a sparse list representation of the same matrix, with list element \code{i} an integer vector with all indices j for which \code{predicate(x[i],y[j])} is \code{TRUE} (and hence a zero-length integer vector if none of them is \code{TRUE}). From the dense matrix, one can find out if one or more elements intersect by \code{apply(mat, 1, any)}, and from the sparse list by \code{lengths(lst) > 0}, see examples below.
#' @details For most predicates, a spatial index is built on argument \code{x}; see \url{https://www.r-spatial.org/r/2017/06/22/spatial-index.html}.
#' Specifically, \code{st_intersects}, \code{st_disjoint}, \code{st_touches} \code{st_crosses}, \code{st_within}, \code{st_contains}, \code{st_contains_properly}, \code{st_overlaps}, \code{st_equals}, \code{st_covers} and \code{st_covered_by} all build spatial indexes for more efficient geometry calculations. \code{st_relate}, \code{st_equals_exact}, and do not; \code{st_is_within_distance} uses a spatial index for geographic coordinates when \code{sf_use_s2()} is true.
#'
#' If \code{y} is missing, `st_predicate(x, x)` is effectively called, and a square matrix is returned with diagonal elements `st_predicate(x[i], x[i])`.
#'
#' Sparse geometry binary predicate (\code{\link{sgbp}}) lists have the following attributes: \code{region.id} with the \code{row.names} of \code{x} (if any, else \code{1:n}), \code{ncol} with the number of features in \code{y}, and \code{predicate} with the name of the predicate used.
#'
#' @note For intersection on pairs of simple feature geometries, use
#' the function \code{\link{st_intersection}} instead of \code{st_intersects}.
#'
#' @examples
#' pts = st_sfc(st_point(c(.5,.5)), st_point(c(1.5, 1.5)), st_point(c(2.5, 2.5)))
#' pol = st_polygon(list(rbind(c(0,0), c(2,0), c(2,2), c(0,2), c(0,0))))
#' (lst = st_intersects(pts, pol))
#' (mat = st_intersects(pts, pol, sparse = FALSE))
#' # which points fall inside a polygon?
#' apply(mat, 1, any)
#' lengths(lst) > 0
#' # which points fall inside the first polygon?
#' st_intersects(pol, pts)[[1]]
#' @export
st_intersects = function(x, y, sparse = TRUE, ...) UseMethod("st_intersects")
#' @export
st_intersects.sfc = function(x, y, sparse = TRUE, prepared = TRUE, ...)
st_geos_binop("intersects", x, y, sparse = sparse, prepared = prepared, ...)
#' @export
st_intersects.sf = function(x, y, sparse = TRUE, prepared = TRUE, ...)
st_geos_binop("intersects", x, y, sparse = sparse, prepared = prepared, ...)
#' @export
st_intersects.sfg = function(x, y, sparse = TRUE, prepared = TRUE, ...)
st_geos_binop("intersects", x, y, sparse = sparse, prepared = prepared, ...)
#' @name geos_binary_pred
#' @export
st_disjoint = function(x, y = x, sparse = TRUE, prepared = TRUE) {
# st_geos_binop("disjoint", x, y, sparse = sparse, prepared = prepared) -> didn't use STRtree
int = st_geos_binop("intersects", x, y, sparse = sparse, prepared = prepared)
# disjoint = !intersects :
if (sparse)
sgbp(lapply(int, function(g) setdiff(seq_along(st_geometry(y)), g)),
predicate = "disjoint",
ncol = attr(int, "ncol"),
region.id = attr(int, "region.id"))
else
!int
}
#' @name geos_binary_pred
#' @export
st_touches = function(x, y, sparse = TRUE, prepared = TRUE, ...)
st_geos_binop("touches", x, y, sparse = sparse, prepared = prepared, ...)
#' @name geos_binary_pred
#' @export
st_crosses = function(x, y, sparse = TRUE, prepared = TRUE, ...)
st_geos_binop("crosses", x, y, sparse = sparse, prepared = prepared, ...)
#' @name geos_binary_pred
#' @export
st_within = function(x, y, sparse = TRUE, prepared = TRUE, ...)
st_geos_binop("within", x, y, sparse = sparse, prepared = prepared, ...)
#' @name geos_binary_pred
#' @param s2_model character; polygon/polyline model; one of
#' "open", "semi-open" or "closed"; see Details.
#' @details for \code{s2_model}, see https://github.com/r-spatial/s2/issues/32
#' @export
st_contains = function(x, y, sparse = TRUE, prepared = TRUE, ..., s2_model = "open")
st_geos_binop("contains", x, y, sparse = sparse, prepared = prepared, ..., s2_model = s2_model)
#' @name geos_binary_pred
#' @export
#' @details `st_contains_properly(A,B)` is true if A intersects B's interior, but not its edges or exterior; A contains A, but A does not properly contain A.
#'
#' See also \link{st_relate} and \url{https://en.wikipedia.org/wiki/DE-9IM} for a more detailed description of the underlying algorithms.
st_contains_properly = function(x, y, sparse = TRUE, prepared = TRUE, ...) {
if (! prepared)
stop("non-prepared geometries not supported for st_contains_properly")
st_geos_binop("contains_properly", x, y, sparse = sparse, prepared = TRUE, ...)
}
#' @name geos_binary_pred
#' @export
st_overlaps = function(x, y, sparse = TRUE, prepared = TRUE, ...)
st_geos_binop("overlaps", x, y, sparse = sparse, prepared = prepared, ...)
#' @name geos_binary_pred
#' @export
st_equals = function(x, y, sparse = TRUE, prepared = FALSE, ...) {
if (prepared)
stop("prepared geometries not supported for st_equals")
st_geos_binop("equals", x, y, sparse = sparse, ...)
}
#' @name geos_binary_pred
#' @export
st_covers = function(x, y, sparse = TRUE, prepared = TRUE, ..., s2_model = "closed")
st_geos_binop("covers", x, y, sparse = sparse, prepared = prepared, ..., s2_model = s2_model)
#' @name geos_binary_pred
#' @export
st_covered_by = function(x, y = x, sparse = TRUE, prepared = TRUE, ..., s2_model = "closed")
st_geos_binop("covered_by", x, y, sparse = sparse, prepared = prepared, ...)
#' @name geos_binary_pred
#' @export
#' @param par numeric; parameter used for "equals_exact" (margin);
#' @details \code{st_equals_exact} returns true for two geometries of the same type and their vertices corresponding by index are equal up to a specified tolerance.
st_equals_exact = function(x, y, par, sparse = TRUE, prepared = FALSE, ...) {
if (prepared)
stop("prepared geometries not supported for st_equals_exact")
st_geos_binop("equals_exact", x, y, par = par, sparse = sparse, ...)
}
#' @name geos_binary_pred
#' @export
#' @param dist distance threshold; geometry indexes with distances smaller or equal to this value are returned; numeric value or units value having distance units.
st_is_within_distance = function(x, y = x, dist, sparse = TRUE, ...) {
ret = if (isTRUE(st_is_longlat(x))) {
units(dist) = as_units("m") # might convert
r = if (sf_use_s2()) {
if (!requireNamespace("s2", quietly = TRUE))
stop("package s2 required, please install it first")
if (inherits(dist, "units"))
dist = drop_units(dist)
s2::s2_dwithin_matrix(x, y, dist, ...)
} else {
if (!requireNamespace("lwgeom", quietly = TRUE) ||
utils::packageVersion("lwgeom") <= "0.1-2")
stop("lwgeom > 0.1-2 required: install first?")
lwgeom::st_geod_distance(x, y, tolerance = dist, sparse = TRUE)
}
sgbp(r, predicate = "is_within_distance", region.id = seq_along(x),
ncol = length(st_geometry(y)))
} else {
if (! is.na(st_crs(x)))
units(dist) = crs_parameters(st_crs(x))$ud_unit # might convert
st_geos_binop("is_within_distance", x, y, par = dist, sparse = sparse, ...)
}
if (!sparse)
as.matrix(ret)
else
ret
}
|