Skip to content

Commit 1bcce69

Browse files
committed
Merge branch 'main' into pointx
2 parents 203187d + 732525f commit 1bcce69

32 files changed

+248
-112
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ S3method("$",z_range)
77
S3method("$<-",sf)
88
S3method("[",sf)
99
S3method("[",sfc)
10+
S3method("[<-",sf)
1011
S3method("[<-",sfc)
1112
S3method("[[",sfc)
1213
S3method("[[<-",sf)

NEWS.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
1+
# version 1.0-15
2+
3+
* `plot.sf()`: `key.width` is sensitive to pointsize graphics parameter, `key.pos` can hold a second value in [0, 1] determining the relative position of the key in the available space; keys with factor levels suggest a proper size if they won't fit.
4+
5+
* `[<-.sf` fixes the `agr` attribute when it is broken; #2211
6+
7+
* `sf` objects get a new attribute, `.sf_namespace`, which forces loading the `sf` namespace when it has not been loaded so far, e.g. for proper printing or plotting of an `sf` object; #2212 by Mike Mahoney
8+
9+
* `distinct.sf` is type-safe for `sf` objects with zero rows; #2204
10+
11+
* `summarise.sf` raises an error if `.by` is given but no `across()` on the geometry; #2207
12+
13+
* `st_write()` matches fields on name first, than on position; this matters for formats that have pre-defined names, such as GPX; #2202
14+
115
# version 1.0-14
216

17+
* fix `plot.sf()` when using a key for multiple factor variables; #2196, #2195
18+
319
* fix use of `as.numeric_version` in a test, for upcoming change in r-devel
420

521
* code tidy-ing: fix many lintr suggestions, thanks to Michael Chirico (#2181 - #2191)

R/agr.R

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,15 @@ st_agr.default = function(x = NA_character_, ...) {
8080
#' @name st_agr
8181
#' @export
8282
st_set_agr = function(x, value) {
83-
st_agr(x) = value
83+
if (!missing(value))
84+
st_agr(x) = value
85+
else { # needs repair?
86+
value = st_agr(x)
87+
if (any(is.na(names(value))) && length(value) == length(x) - 1) {
88+
names(value) = setdiff(names(x), attr(x, "sf_column"))
89+
st_agr(x) = value
90+
}
91+
}
8492
x
8593
}
8694

R/cast_sfc.R

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ empty_sfg <- function(to) {
147147
)
148148
}
149149

150+
has_curves = function(x) {
151+
if (inherits(x, c("sfc_MULTICURVE", "sfc_COMPOUNDCURVE", "sfc_CURVEPOLYGON"))) # for which GEOS has no st_is_empty()
152+
TRUE
153+
else if(inherits(x, "sfc_GEOMETRY")) {
154+
cls = sapply(x, class)
155+
any(cls[2,] %in% c("MULTICURVE", "COMPOUNDCURVE", "CURVEPOLYGON"))
156+
} else
157+
FALSE
158+
}
150159

151160
#' @name st_cast
152161
#' @param ids integer vector, denoting how geometries should be grouped (default: no grouping)
@@ -169,7 +178,7 @@ st_cast.sfc = function(x, to, ..., ids = seq_along(x), group_or_split = TRUE) {
169178
return(st_cast_sfc_default(x))
170179

171180
e = rep(FALSE, length(x))
172-
if (!inherits(x, c("sfc_MULTICURVE", "sfc_COMPOUNDCURVE", "sfc_CURVEPOLYGON"))) { # for which GEOS has no st_is_empty()
181+
if (!has_curves(x)) { # for which GEOS has no st_is_empty()
173182
e = st_is_empty(x)
174183
if (all(e)) {
175184
x[e] = empty_sfg(to)
@@ -248,7 +257,6 @@ st_cast.sfc = function(x, to, ..., ids = seq_along(x), group_or_split = TRUE) {
248257
#' @details the \code{st_cast} method for \code{sf} objects can only split geometries, e.g. cast \code{MULTIPOINT} into multiple \code{POINT} features. In case of splitting, attributes are repeated and a warning is issued when non-constant attributes are assigned to sub-geometries. To merge feature geometries and attribute values, use \link[sf:aggregate.sf]{aggregate} or \link[sf:tidyverse]{summarise}.
249258
st_cast.sf = function(x, to, ..., warn = TRUE, do_split = TRUE) {
250259
geom = st_cast(st_geometry(x), to, group_or_split = do_split)
251-
crs = st_crs(x)
252260
agr = st_agr(x)
253261
all_const = all_constant(x)
254262
sf_column = attr(x, "sf_column") # keep name

R/defunct.R

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@
1313
#' it will try to cast all the character columns, which can be long for very wide
1414
#' tables.
1515
#' @inheritParams st_read
16-
#' @docType package
1716
#' @export st_read_db st_write_db
18-
#' @aliases st_read_db, st_write_db
17+
#' @aliases sf-package st_read_db, st_write_db
1918
#' @section Details:
2019
#' \tabular{rl}{
2120
#' \code{st_read_db} \tab now a synonym for \code{\link{st_read}}\cr

R/plot.R

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#' @param nbreaks number of colors breaks (ignored for \code{factor} or \code{character} variables)
1212
#' @param breaks either a numeric vector with the actual breaks, or a name of a method accepted by the \code{style} argument of \link[classInt]{classIntervals}
1313
#' @param max.plot integer; lower boundary to maximum number of attributes to plot; the default value (9) can be overriden by setting the global option \code{sf_max.plot}, e.g. \code{options(sf_max.plot=2)}
14-
#' @param key.pos integer; side to plot a color key: 1 bottom, 2 left, 3 top, 4 right; set to \code{NULL} to omit key completely, 0 to only not plot the key, or -1 to select automatically. If multiple columns are plotted in a single function call by default no key is plotted and every submap is stretched individually; if a key is requested (and \code{col} is missing) all maps are colored according to a single key. Auto select depends on plot size, map aspect, and, if set, parameter \code{asp}.
14+
#' @param key.pos numeric; side to plot a color key: 1 bottom, 2 left, 3 top, 4 right; set to \code{NULL} to omit key completely, 0 to only not plot the key, or -1 to select automatically. If multiple columns are plotted in a single function call by default no key is plotted and every submap is stretched individually; if a key is requested (and \code{col} is missing) all maps are colored according to a single key. Auto select depends on plot size, map aspect, and, if set, parameter \code{asp}. If it has lenght 2, the second value, ranging from 0 to 1, determines where the key is placed in the available space (default: 0.5, center).
1515
#' @param key.width amount of space reserved for the key (incl. labels), thickness/width of the scale bar
1616
#' @param key.length amount of space reserved for the key along its axis, length of the scale bar
1717
#' @param pch plotting symbol
@@ -72,7 +72,7 @@
7272
#' @export
7373
plot.sf <- function(x, y, ..., main, pal = NULL, nbreaks = 10, breaks = "pretty",
7474
max.plot = if(is.null(n <- getOption("sf_max.plot"))) 9 else n,
75-
key.pos = get_key_pos(x, ...), key.length = .618, key.width = lcm(1.8),
75+
key.pos = get_key_pos(x, ...), key.length = .618, key.width = lcm(1.8 * par("ps")/12),
7676
reset = TRUE, logz = FALSE, extent = x, xlim = st_bbox(extent)[c(1,3)],
7777
ylim = st_bbox(extent)[c(2,4)]) {
7878

@@ -89,8 +89,9 @@ plot.sf <- function(x, y, ..., main, pal = NULL, nbreaks = 10, breaks = "pretty"
8989
opar = par()
9090
if (ncol(x) > 2 && !isTRUE(dots$add)) { # multiple maps to plot...
9191
cols = setdiff(names(x), attr(x, "sf_column"))
92-
lt = .get_layout(st_bbox(x), min(max.plot, length(cols)), par("din"), key.pos, key.width)
93-
key.pos = lt$key.pos
92+
lt = .get_layout(st_bbox(x), min(max.plot, length(cols)), par("din"), key.pos[1], key.width)
93+
if (key.pos.missing || key.pos == -1)
94+
key.pos = lt$key.pos
9495
layout(lt$m, widths = lt$widths, heights = lt$heights, respect = FALSE)
9596

9697
if (isTRUE(dots$axes))
@@ -113,9 +114,11 @@ plot.sf <- function(x, y, ..., main, pal = NULL, nbreaks = 10, breaks = "pretty"
113114

114115
if (!is.null(key.pos)) {
115116
values = do.call(c, as.data.frame(x)[cols])
117+
if (is.character(values))
118+
values = as.factor(values)
116119
if (logz)
117120
values = log10(values)
118-
if (is.character(breaks)) { # compute breaks from values:
121+
if (is.character(breaks) && is.numeric(values)) { # compute breaks from values:
119122
v0 = values[!is.na(values)]
120123
n.unq = length(unique(v0))
121124
breaks = if (! all(is.na(values)) && n.unq > 1)
@@ -138,7 +141,7 @@ plot.sf <- function(x, y, ..., main, pal = NULL, nbreaks = 10, breaks = "pretty"
138141
plot.new()
139142

140143
# plot key?
141-
if (!is.null(key.pos) && key.pos != 0 && col_missing) {
144+
if (!is.null(key.pos) && key.pos[1] != 0 && col_missing) {
142145
if (is.null(pal))
143146
pal = function(n) sf.colors(n, categorical = is.factor(values))
144147
colors = if (is.function(pal))
@@ -227,7 +230,7 @@ plot.sf <- function(x, y, ..., main, pal = NULL, nbreaks = 10, breaks = "pretty"
227230
(is.factor(values) || length(unique(na.omit(values))) > 1 || breaks_numeric) && # 2065
228231
length(col) > 1) { # plot key?
229232

230-
switch(key.pos,
233+
switch(key.pos[1],
231234
layout(matrix(c(2,1), nrow = 2, ncol = 1),
232235
widths = 1, heights = c(1, key.width)), # 1 bottom
233236
layout(matrix(c(1,2), nrow = 1, ncol = 2),
@@ -778,6 +781,24 @@ bb2merc = function(x, cls = "ggmap") { # return bbox in the appropriate "web mer
778781
axis(side, at = at, labels = labels, ...)
779782
}
780783

784+
# find out where to place the legend key:
785+
# given range r = (a, b), key.length l, key offset o, return a value range that:
786+
# * scales such that (b - a) / (y - x) = l
787+
# * shifts linearly within [x, y] from a = x when o = 0 to b = y when o = 1
788+
xy_from_r = function(r, l, o) {
789+
stopifnot(length(r) == 2, l <= 1, l > 0, o >= 0, o <= 1)
790+
a = r[1]; b = r[2]
791+
if (o == 1) {
792+
y = b
793+
x = b - (b - a)/l
794+
} else {
795+
i = o / (o - 1)
796+
y = (a + (b - a)/l - i * b)/(1 - i)
797+
x = i * (y - b) + a
798+
}
799+
c(x, y)
800+
}
801+
781802
#' @name stars
782803
#' @export
783804
#' @param z ignore
@@ -789,15 +810,22 @@ bb2merc = function(x, cls = "ggmap") { # return bbox in the appropriate "web mer
789810
#' @param logz ignore
790811
#' @param ... ignore
791812
#' @param lab ignore
813+
#' @param cex.axis see \link{par}
792814
.image_scale = function(z, col, breaks = NULL, key.pos, add.axis = TRUE,
793-
at = NULL, ..., axes = FALSE, key.length, logz = FALSE, lab = "") {
815+
at = NULL, ..., axes = FALSE, key.length, logz = FALSE, lab = "",
816+
cex.axis = par("cex.axis")) {
794817
if (!is.null(breaks) && length(breaks) != (length(col) + 1))
795818
stop("must have one more break than colour")
796819
stopifnot(is.character(lab) || is.expression(lab))
797820
lab_set = (is.character(lab) && lab != "") || is.expression(lab)
798821
zlim = range(z, na.rm = TRUE)
799822
if (is.null(breaks))
800823
breaks = seq(zlim[1], zlim[2], length.out = length(col) + 1)
824+
offset = 0.5
825+
if (length(key.pos) == 2) {
826+
offset = key.pos[2]
827+
key.pos = key.pos[1]
828+
}
801829
if (is.character(key.length)) {
802830
kl = as.numeric(gsub(" cm", "", key.length))
803831
sz = if (key.pos %in% c(1,3))
@@ -811,14 +839,13 @@ bb2merc = function(x, cls = "ggmap") { # return bbox in the appropriate "web mer
811839
at = pretty(br)
812840
at = at[at > br[1] & at < br[2]]
813841
}
814-
kl_lim = function(r, kl) { m = mean(r); (r - m)/kl + m }
815842
if (key.pos %in% c(1,3)) {
816843
ylim = c(0, 1)
817-
xlim = kl_lim(range(breaks), key.length)
844+
xlim = xy_from_r(range(breaks), key.length, offset)
818845
mar = c(0, ifelse(axes, 2.1, 1), 0, 1)
819846
}
820847
if (key.pos %in% c(2,4)) {
821-
ylim = kl_lim(range(breaks), key.length)
848+
ylim = xy_from_r(range(breaks), key.length, offset)
822849
xlim = c(0, 1)
823850
mar = c(ifelse(axes, 2.1, 1), 0, 1.2, 0)
824851
}
@@ -860,19 +887,23 @@ bb2merc = function(x, cls = "ggmap") { # return bbox in the appropriate "web mer
860887
TRUE
861888

862889
if (add.axis)
863-
axis(key.pos, at = at, labels = labels)
890+
axis(key.pos, at = at, labels = labels, cex.axis = cex.axis)
864891
}
865892

866893
#' @name stars
867894
#' @export
868895
#' @param key.width ignore
869896
.image_scale_factor = function(z, col, key.pos, add.axis = TRUE,
870-
..., axes = FALSE, key.width, key.length) {
897+
..., axes = FALSE, key.width, key.length, cex.axis = par("cex.axis")) {
871898

872899
n = length(z)
873-
# TODO:
874-
ksz = as.numeric(gsub(" cm", "", key.width)) * 2
900+
ksz = max(strwidth(z, "inches")) / par("cin")[1] # in "mar" lines
875901
breaks = (0:n) + 0.5
902+
offset = 0.5
903+
if (length(key.pos) == 2) {
904+
offset = key.pos[2]
905+
key.pos = key.pos[1]
906+
}
876907
if (is.character(key.length)) {
877908
kl = as.numeric(gsub(" cm", "", key.length))
878909
sz = if (key.pos %in% c(1,3))
@@ -881,26 +912,33 @@ bb2merc = function(x, cls = "ggmap") { # return bbox in the appropriate "web mer
881912
dev.size("cm")[2]
882913
key.length = kl/sz
883914
}
884-
kl_lim = function(r, kl) { m = mean(r); (r - m)/kl + m }
885915
if (key.pos %in% c(1,3)) {
886916
ylim = c(0, 1)
887-
xlim = kl_lim(range(breaks), key.length)
917+
xlim = xy_from_r(range(breaks), key.length, offset)
888918
mar = c(0, ifelse(axes, 2.1, 1), 0, 1)
889919
mar[key.pos] = 2.1
890920
} else {
891-
ylim = kl_lim(range(breaks), key.length)
921+
ylim = xy_from_r(range(breaks), key.length, offset)
892922
xlim = c(0, 1)
893923
mar = c(ifelse(axes, 2.1, 1), 0, 1.2, 0)
894-
#mar[key.pos] = 2.1
895-
mar[key.pos] = max(ksz - 1.3, 0.0)
924+
mar[key.pos] = ksz
896925
}
897926
par(mar = mar)
898927

899928
poly = vector(mode="list", length(col))
900929
for (i in seq(poly))
901930
poly[[i]] = c(breaks[i], breaks[i+1], breaks[i+1], breaks[i])
902-
plot(1, 1, t = "n", ylim = ylim, xlim = xlim, axes = FALSE,
903-
xlab = "", ylab = "", xaxs = "i", yaxs = "i")
931+
932+
tryCatch({
933+
plot(1, 1, t = "n", ylim = ylim, xlim = xlim, axes = FALSE,
934+
xlab = "", ylab = "", xaxs = "i", yaxs = "i")
935+
},
936+
error = function(x) {
937+
sz = max(strwidth(z, "inches")) * 2.54 * 1.1 + par("ps")/12 # cm
938+
stop(paste0("key.width too small, try key.width = lcm(", signif(sz, 3), ")"), call. = FALSE)
939+
}
940+
)
941+
904942
for(i in seq_along(poly)) {
905943
if (key.pos %in% c(1,3))
906944
polygon(poly[[i]], c(0, 0, 1, 1), col = col[i], border = NA)
@@ -917,7 +955,7 @@ bb2merc = function(x, cls = "ggmap") { # return bbox in the appropriate "web mer
917955

918956
if (add.axis) {
919957
opar = par(las = 1)
920-
axis(key.pos, at = 1:n, labels = z)
958+
axis(key.pos, at = 1:n, labels = z, cex.axis = cex.axis)
921959
par(opar)
922960
}
923961
}

R/sf.R

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,12 +299,17 @@ st_sf = function(..., agr = NA_agr_, row.names,
299299
st_agr(df) = agr
300300
if (! missing(crs))
301301
st_crs(df) = crs
302+
303+
attr(df, ".sf_namespace") <- .sf_namespace
304+
302305
df
303306
}
304307

308+
.sf_namespace <- function() NULL
309+
305310
#' @name sf
306311
#' @param x object of class \code{sf}
307-
#' @param i record selection, see \link{[.data.frame}
312+
#' @param i record selection, see \link{[.data.frame}, or a \code{sf} object to work with the \code{op} argument
308313
#' @param j variable selection, see \link{[.data.frame}
309314
#' @param drop logical, default \code{FALSE}; if \code{TRUE} drop the geometry column and return a \code{data.frame}, else make the geometry sticky and return a \code{sf} object.
310315
#' @param op function; geometrical binary predicate function to apply when \code{i} is a simple feature object
@@ -374,6 +379,11 @@ st_sf = function(..., agr = NA_agr_, row.names,
374379
}
375380
}
376381

382+
#' @export
383+
"[<-.sf" = function(x, i, j, value) {
384+
st_set_agr(NextMethod())
385+
}
386+
377387
#' @export
378388
"[[<-.sf" = function(x, i, value) {
379389
agr = st_agr(x)

R/spatstat.R

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ check_spatstat <- function(pkg, X = NULL) {
1919
if (!requireNamespace(pkg, quietly = TRUE))
2020
stop("package ", pkg, " required, please install it (or the full spatstat package) first", call. = FALSE)
2121
spst_ver <- try(packageVersion("spatstat"), silent = TRUE)
22-
if (!inherits(spst_ver, "try-error") && spst_ver < 2.0-0)
23-
stop(wrp(paste("You have an old version of spatstat installed which is incompatible with ", pkg,
22+
if (!inherits(spst_ver, "try-error") && spst_ver < "2.0-0")
23+
stop(wrp(paste0("You have an old version of spatstat installed that is incompatible with ", pkg,
2424
". Please update spatstat (or uninstall it).")), call. = FALSE)
2525
if (!is.null(X))
2626
check_spatstat_ll(X)
@@ -138,9 +138,9 @@ as.ppp.sfc = function(X, W = NULL, ..., check = TRUE) {
138138
spatstat.geom::ppp(cc[,1], cc[,2], window = W, marks = NULL, check = check)
139139
}
140140

141-
as.ppp.sf = function(X) {
141+
as.ppp.sf = function(X, ...) {
142142
check_spatstat("spatstat.geom", X)
143-
pp = spatstat.geom::as.ppp(st_geometry(X))
143+
pp = spatstat.geom::as.ppp(st_geometry(X), ...)
144144
if (st_dimension(X[1,]) == 2)
145145
X = X[-1,]
146146
st_geometry(X) = NULL # remove geometry column

R/tidyverse.R

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,19 @@ rename_with.sf = function(.data, .fn, .cols, ...) {
244244
if (!requireNamespace("rlang", quietly = TRUE))
245245
stop("rlang required: install that first") # nocov
246246
.fn = rlang::as_function(.fn)
247+
248+
sf_column = attr(.data, "sf_column")
249+
sf_column_loc = match(sf_column, names(.data))
250+
251+
if (length(sf_column_loc) != 1 || is.na(sf_column_loc))
252+
stop("internal error: can't find sf column") # nocov
253+
247254
agr = st_agr(.data)
255+
248256
ret = NextMethod()
249257
names(agr) = .fn(names(agr))
250258
st_agr(ret) = agr
259+
st_geometry(ret) = names(ret)[sf_column_loc]
251260
ret
252261
}
253262

@@ -313,6 +322,8 @@ summarise.sf <- function(.data, ..., .dots, do_union = TRUE, is_coverage = FALSE
313322
geom = list() #676 #nocov
314323
do.call(st_sfc, c(geom, crs = list(crs), precision = precision))
315324
} else { # single group:
325+
if (nrow(ret) > 1)
326+
stop(paste0("when using .by, also add across(", sf_column, ", st_union) as argument")) # https://github.com/r-spatial/sf/issues/2207
316327
if (do_union)
317328
st_union(geom, is_coverage = is_coverage)
318329
else
@@ -336,6 +347,8 @@ distinct.sf <- function(.data, ..., .keep_all = FALSE) {
336347
sf_column = attr(.data, "sf_column")
337348
geom = st_geometry(.data)
338349
eq = sapply(st_equals(.data), head, n = 1)
350+
if (is.list(eq) && length(eq) == 0) # empty list: geometry was empty set
351+
eq = integer(0)
339352
empties = which(lengths(eq) == 0)
340353
eq[ empties ] = empties[1] # first empty record
341354
.data[[ sf_column ]] = unlist(eq)

0 commit comments

Comments
 (0)