Skip to content

Commit d1526f7

Browse files
authored
Merge branch 'main' into tests
2 parents e711f59 + c880270 commit d1526f7

29 files changed

+343
-216
lines changed

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,9 @@ export(st_jitter)
461461
export(st_join)
462462
export(st_layers)
463463
export(st_length)
464+
export(st_line_interpolate)
464465
export(st_line_merge)
466+
export(st_line_project)
465467
export(st_line_sample)
466468
export(st_linestring)
467469
export(st_m_range)

NEWS.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1+
# version 1.0-16
2+
3+
* if environment variable `R_SF_USE_PROJ_DATA` is `true`, `GDAL_DATA`, `PROJ_DATA` (and deprecated `PROJ_LIB`) will not be ignored.
4+
5+
* environment variables `PROJ_LIB` and `PROJ_DATA` are (again) ignored on `sf` binary CRAN installations (win + macos); #2298
6+
7+
* add `st_line_project()` to find how far a point is when projected on a line; #2291
8+
9+
* add `st_line_interpolate()` to obtain a point at a certain distance along a line; #2291
10+
111
# version 1.0-15
212

13+
* add `st_perimeter()` to cover both geographic and projected coordinates; #268, #2279, by @JosiahParry
14+
315
* add `st_sample()` method for `bbox`, with special provisions for ellipsoidal coordinates; #2283
416

517
* documentation clean-up by @olivroy; #2266, #2285

R/RcppExports.R

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,6 @@ CPL_axis_order_authority_compliant <- function(authority_compliant) {
7777
.Call(`_sf_CPL_axis_order_authority_compliant`, authority_compliant)
7878
}
7979

80-
CPL_get_proj_search_paths <- function(paths) {
81-
.Call(`_sf_CPL_get_proj_search_paths`, paths)
82-
}
83-
84-
CPL_set_proj_search_paths <- function(paths) {
85-
.Call(`_sf_CPL_set_proj_search_paths`, paths)
86-
}
87-
8880
CPL_area <- function(sfc) {
8981
.Call(`_sf_CPL_area`, sfc)
9082
}
@@ -257,6 +249,14 @@ CPL_nary_intersection <- function(sfc) {
257249
.Call(`_sf_CPL_nary_intersection`, sfc)
258250
}
259251

252+
CPL_line_project <- function(lines, points, normalized) {
253+
.Call(`_sf_CPL_line_project`, lines, points, normalized)
254+
}
255+
256+
CPL_line_interpolate <- function(lines, dists, normalized) {
257+
.Call(`_sf_CPL_line_interpolate`, lines, dists, normalized)
258+
}
259+
260260
CPL_hex_to_raw <- function(cx) {
261261
.Call(`_sf_CPL_hex_to_raw`, cx)
262262
}

R/aggregate.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ aggregate.sf = function(x, by, FUN, ..., do_union = TRUE, simplify = TRUE,
107107
#' sum(a2$BIR74) / sum(nc$BIR74)
108108
#' a1$intensive = a1$BIR74
109109
#' a1$extensive = a2$BIR74
110-
#' plot(a1[c("intensive", "extensive")], key.pos = 4)
110+
#' \donttest{plot(a1[c("intensive", "extensive")], key.pos = 4)}
111111
#' @export
112112
st_interpolate_aw = function(x, to, extensive, ...) UseMethod("st_interpolate_aw")
113113

R/crs.R

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -392,15 +392,16 @@ is.na.crs = function(x) {
392392
#' @examples
393393
#' st_crs("EPSG:3857")$input
394394
#' st_crs(3857)$proj4string
395-
#' st_crs(3857)$b # numeric
396-
#' st_crs(3857)$units # character
397-
#' @details the \code{$} method for \code{crs} objects retrieves named elements
395+
#' @details the `$` method for `crs` objects retrieves named elements
398396
#' using the GDAL interface; named elements include
399-
#' \code{"SemiMajor"}, \code{"SemiMinor"}, \code{"InvFlattening"}, \code{"IsGeographic"},
400-
#' \code{"units_gdal"}, \code{"IsVertical"}, \code{"WktPretty"}, \code{"Wkt"},
401-
#' \code{"Name"}, \code{"proj4string"}, \code{"epsg"}, \code{"yx"},
402-
#' \code{"ud_unit"}, and \code{axes} (this may be subject to changes in future GDAL versions).
403-
#' \code{"ud_unit"} returns a valid \link[units]{units} object or \code{NULL} if units are missing.
397+
#' `SemiMajor`, `SemiMinor`, `InvFlattening`, `IsGeographic`,
398+
#' `units_gdal`, `IsVertical`, `WktPretty`, `Wkt`,
399+
#' `Name`, `proj4string`, `epsg`, `yx`,
400+
#' `ud_unit`, and `axes` (this may be subject to changes in future GDAL versions).
401+
#'
402+
#' Note that not all valid CRS have a corresponding `proj4string`.
403+
#'
404+
#' `ud_unit` returns a valid \link[units]{units} object or `NULL` if units are missing.
404405
#' @export
405406
`$.crs` = function(x, name) {
406407

R/geom-measures.R

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -115,27 +115,18 @@ message_longlat = function(caller) {
115115
#' st_perimeter(mpoly)
116116
st_perimeter = function(x, ...) {
117117
x = st_geometry(x)
118-
119-
# for spherical geometries we use s2
120-
if (isTRUE(st_is_longlat(x))) {
121-
122-
if (!requireNamespace("s2", quietly = TRUE)) {
118+
if (isTRUE(st_is_longlat(x))) { # for spherical geometries we use s2
119+
if (!requireNamespace("s2", quietly = TRUE))
123120
stop("package s2 required to calculate the perimeter of spherical geometries")
124-
}
125-
126121
# ensure units are set to meters
127122
units::set_units(
128123
s2::s2_perimeter(x, ...),
129124
"m",
130125
mode = "standard"
131126
)
132-
133-
# non-spherical geometries use lwgeom
134-
} else {
135-
if (!requireNamespace("lwgeom", quietly = TRUE)) {
127+
} else { # non-spherical geometries use lwgeom:
128+
if (!requireNamespace("lwgeom", quietly = TRUE))
136129
stop("package lwgeom required, please install it first")
137-
}
138-
139130
# note that units are handled appropriately by lwgeom
140131
lwgeom::st_perimeter(x)
141132
}
@@ -220,3 +211,52 @@ st_distance = function(x, y, ..., dist_fun, by_element = FALSE,
220211
d
221212
}
222213
}
214+
215+
check_lengths = function (dots) {
216+
lengths <- vapply(dots, length, integer(1))
217+
non_constant_lengths <- unique(lengths[lengths != 1])
218+
if (length(non_constant_lengths) == 0) {
219+
1
220+
}
221+
else if (length(non_constant_lengths) == 1) {
222+
non_constant_lengths
223+
}
224+
else {
225+
lengths_label <- paste0(non_constant_lengths, collapse = ", ")
226+
stop(sprintf("Incompatible lengths: %s", lengths_label),
227+
call. = FALSE)
228+
}
229+
}
230+
231+
recycle_common = function (dots) {
232+
final_length <- check_lengths(dots)
233+
lapply(dots, rep_len, final_length)
234+
}
235+
236+
237+
#' Project point on linestring, interpolate along a linestring
238+
#'
239+
#' Project point on linestring, interpolate along a linestring
240+
#' @param line object of class `sfc` with `LINESTRING` geometry
241+
#' @param point object of class `sfc` with `POINT` geometry
242+
#' @param normalized logical; if `TRUE`, use or return distance normalised to 0-1
243+
#' @name st_line_project_point
244+
#' @returns `st_line_project` returns the distance(s) of point(s) along line(s), when projected on the line(s)
245+
#' @export
246+
#' @details
247+
#' arguments `line`, `point` and `dist` are recycled to common length when needed
248+
#' @examples
249+
#' st_line_project(st_as_sfc("LINESTRING (0 0, 10 10)"), st_as_sfc(c("POINT (0 0)", "POINT (5 5)")))
250+
#' st_line_project(st_as_sfc("LINESTRING (0 0, 10 10)"), st_as_sfc("POINT (5 5)"), TRUE)
251+
st_line_project = function(line, point, normalized = FALSE) {
252+
stopifnot(inherits(line, "sfc"), inherits(point, "sfc"),
253+
all(st_dimension(line) == 1), all(st_dimension(point) == 0),
254+
is.logical(normalized), length(normalized) == 1,
255+
st_crs(line) == st_crs(point))
256+
line = st_cast(line, "LINESTRING")
257+
point = st_cast(point, "POINT")
258+
if (isTRUE(st_is_longlat(line)))
259+
message_longlat("st_project_point")
260+
recycled = recycle_common(list(line, point))
261+
CPL_line_project(recycled[[1]], recycled[[2]], normalized)
262+
}

R/geom-transformers.R

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
#' @details \code{st_buffer} computes a buffer around this geometry/each geometry. If any of \code{endCapStyle},
2222
#' \code{joinStyle}, or \code{mitreLimit} are set to non-default values ('ROUND', 'ROUND', 1.0 respectively) then
2323
#' the underlying 'buffer with style' GEOS function is used.
24-
#' If a negative buffer returns empty polygons instead of shrinking, set st_use_s2() to FALSE
24+
#' If a negative buffer returns empty polygons instead of shrinking, set sf_use_s2() to FALSE
2525
#' See \href{https://postgis.net/docs/ST_Buffer.html}{postgis.net/docs/ST_Buffer.html} for details.
2626
#'
2727
#' \code{nQuadSegs}, \code{endCapsStyle}, \code{joinStyle}, \code{mitreLimit} and \code{singleSide} only
@@ -1121,3 +1121,22 @@ st_line_sample = function(x, n, density, type = "regular", sample = NULL) {
11211121
if (length(pts) == 2 && is.numeric(pts))
11221122
assign(".geos_error", st_point(pts), envir=.sf_cache)
11231123
} #nocov end
1124+
1125+
#' @param dist numeric, vector with distance value(s)
1126+
#' @name st_line_project_point
1127+
#' @returns `st_line_interpolate` returns the point(s) at dist(s), when measured along (interpolated on) the line(s)
1128+
#' @export
1129+
#' @examples
1130+
#' st_line_interpolate(st_as_sfc("LINESTRING (0 0, 1 1)"), 1)
1131+
#' st_line_interpolate(st_as_sfc("LINESTRING (0 0, 1 1)"), 1, TRUE)
1132+
st_line_interpolate = function(line, dist, normalized = FALSE) {
1133+
stopifnot(inherits(line, "sfc"), all(st_dimension(line) == 1),
1134+
is.logical(normalized), length(normalized) == 1,
1135+
is.numeric(dist))
1136+
if (isTRUE(st_is_longlat(line)))
1137+
message_longlat("st_project_point")
1138+
line = st_cast(line, "LINESTRING")
1139+
recycled = recycle_common(list(line, dist))
1140+
st_sfc(CPL_line_interpolate(recycled[[1]], recycled[[2]], normalized),
1141+
crs = st_crs(line))
1142+
}

R/init.R

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ pathGrob <- NULL
5757
packageStartupMessage(paste(
5858
"Linked against:", CPL_geos_version(TRUE, TRUE),
5959
"compiled against:", CPL_geos_version(FALSE, TRUE)))
60-
packageStartupMessage("It is probably a good idea to reinstall sf, and maybe rgeos and rgdal too")
60+
packageStartupMessage("It is probably a good idea to reinstall sf (and maybe lwgeom too)")
6161
} # nocov end
6262
}
6363

@@ -72,39 +72,43 @@ sf_extSoftVersion = function() {
7272
names = c("GEOS", "GDAL", "proj.4", "GDAL_with_GEOS", "USE_PROJ_H", "PROJ"))
7373
}
7474

75+
save_and_replace = function(var, value, where) {
76+
if (Sys.getenv(var) != "")
77+
assign(paste0(".sf.", var), Sys.getenv(var), envir = where)
78+
# Sys.setenv(var = value) uses NSE and will set var, not the variable var points to:
79+
do.call(Sys.setenv, setNames(list(value), var))
80+
}
81+
82+
if_exists_restore = function(vars, where) {
83+
fn = function(var, where) {
84+
lname = paste0(".sf.", var)
85+
if (!is.null(get0(lname, envir = where)))
86+
do.call(Sys.setenv, setNames(list(get(lname, envir = where)), var)) # see above
87+
}
88+
lapply(vars, fn, where = where)
89+
}
90+
7591
load_gdal <- function() {
76-
if (file.exists(system.file("proj/nad.lst", package = "sf")[1])) {
77-
# nocov start
78-
prj = system.file("proj", package = "sf")[1]
79-
if (! CPL_set_data_dir(prj)) { # if TRUE, uses C API to set path, leaving PROJ_LIB alone
80-
assign(".sf.PROJ_LIB", Sys.getenv("PROJ_LIB"), envir=.sf_cache)
81-
Sys.setenv("PROJ_LIB" = prj)
92+
if (!identical(Sys.getenv("R_SF_USE_PROJ_DATA"), "true")) {
93+
if (file.exists(prj <- system.file("proj", package = "sf")[1])) {
94+
# nocov start
95+
if (! CPL_set_data_dir(prj)) { # if TRUE, uses C API to set path, leaving PROJ_LIB / PROJ_DATA alone
96+
save_and_replace("PROJ_LIB", prj, .sf_cache)
97+
save_and_replace("PROJ_DATA", prj, .sf_cache)
98+
}
99+
# CPL_use_proj4_init_rules(1L)
100+
# nocov end
82101
}
83-
CPL_use_proj4_init_rules(1L)
84-
assign(".sf.GDAL_DATA", Sys.getenv("GDAL_DATA"), envir=.sf_cache)
85-
gdl = system.file("gdal", package = "sf")[1]
86-
Sys.setenv("GDAL_DATA" = gdl)
87-
# nocov end
102+
if (file.exists(gdl <- system.file("gdal", package = "sf")[1]))
103+
save_and_replace("GDAL_DATA", gdl, .sf_cache)
88104
}
89105
CPL_gdal_init()
90106
register_all_s3_methods() # dynamically registers non-imported pkgs (tidyverse)
91107
}
92108

93109
unload_gdal <- function() {
94110
CPL_gdal_cleanup_all()
95-
if (file.exists(system.file("proj/nad.lst", package = "sf")[1])) {
96-
# nocov start
97-
if (! CPL_set_data_dir(system.file("proj", package = "sf")[1])) # set back:
98-
Sys.setenv("PROJ_LIB"=get(".sf.PROJ_LIB", envir=.sf_cache))
99-
100-
Sys.setenv("GDAL_DATA"=get(".sf.GDAL_DATA", envir=.sf_cache))
101-
# nocov end
102-
}
103-
#units::remove_symbolic_unit("link")
104-
#units::remove_symbolic_unit("us_in")
105-
#units::remove_symbolic_unit("ind_yd")
106-
#units::remove_symbolic_unit("ind_ft")
107-
#units::remove_symbolic_unit("ind_ch")
111+
if_exists_restore(c("PROJ_LIB", "PROJ_DATA", "GDAL_DATA"), .sf_cache)
108112
}
109113

110114

R/proj.R

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
#' @name st_transform
3-
#' @param type character; one of \code{have_datum_files}, \code{proj}, \code{ellps}, \code{datum}, \code{units} or \code{prime_meridians}; see Details.
3+
#' @param type character; one of `have_datum_files`, `proj`, `ellps`, `datum`, `units`, `path`, or `prime_meridians`; see Details.
44
#' @param path character; PROJ search path to be set
55
#' @export
66
#' @details \code{sf_proj_info} lists the available projections, ellipses, datums, units, or data search path of the PROJ library when \code{type} is equal to proj, ellps, datum, units or path; when \code{type} equals \code{have_datum_files} a boolean is returned indicating whether datum files are installed and accessible (checking for \code{conus}).
@@ -21,7 +21,7 @@ sf_proj_info = function(type = "proj", path) {
2121
return(CPL_get_data_dir(FALSE))
2222

2323
if (!missing(path) && is.character(path))
24-
return(invisible(CPL_set_data_dir(path)))
24+
return(invisible(unique(CPL_set_data_dir(path))))
2525

2626
if (type == "network")
2727
return(CPL_is_network_enabled(TRUE))
@@ -88,9 +88,9 @@ sf_project = function(from = character(0), to = character(0), pts, keep = FALSE,
8888
#' @export
8989
sf_proj_search_paths = function(paths = character(0)) {
9090
if (length(paths) == 0)
91-
CPL_get_proj_search_paths(paths) # get
91+
CPL_get_data_dir(FALSE)
9292
else
93-
CPL_set_proj_search_paths(as.character(paths)) # set
93+
CPL_set_data_dir(as.character(paths)) # set
9494
}
9595

9696
#' @param enable logical; set this to enable (TRUE) or disable (FALSE) the proj network search facility

R/sample.R

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,12 @@ st_sample.sfg = function(x, size, ...) {
153153
#' st_bbox(s1) # within bbox
154154
#' s2 = st_sample(bbox, 400, great_circles = TRUE)
155155
#' st_bbox(s2) # outside bbox
156-
st_sample.bbox = function(x, size, ..., great_circles = FALSE, segments = units::set_units(2, degrees)) {
156+
st_sample.bbox = function(x, size, ..., great_circles = FALSE, segments = units::set_units(2, "degree", mode = "standard")) {
157157
polygon = st_as_sfc(x)
158158
crs = st_crs(x)
159159
if (isTRUE(st_is_longlat(x)) && !great_circles) {
160160
st_crs(polygon) = NA_crs_ # to fool segmentize that we're on R2:
161-
segments = units::drop_units(units::set_units(segments, degrees))
161+
segments = units::drop_units(units::set_units(segments, "degree", mode = "standard"))
162162
polygon = st_set_crs(st_segmentize(polygon, segments), crs)
163163
}
164164
st_sample(polygon, size, ...)
@@ -193,8 +193,8 @@ st_poly_sample = function(x, size, ..., type = "random",
193193
if (!requireNamespace("lwgeom", quietly = TRUE))
194194
warning("coordinate ranges not computed along great circles; install package lwgeom to get rid of this warning")
195195
else
196-
bb = st_bbox(st_segmentize(st_as_sfc(bb),
197-
units::set_units(1, "degree", mode = "standard"))) # get coordinate range on S2
196+
bb = st_bbox(st_segmentize(st_as_sfc(bb),
197+
units::set_units(1, "degree", mode = "standard"))) # get coordinate range on S2
198198
}
199199
R = s2::s2_earth_radius_meters()
200200
toRad = pi / 180

0 commit comments

Comments
 (0)