-
Notifications
You must be signed in to change notification settings - Fork 1
/
lonlat_to_sfc.R
115 lines (97 loc) 路 3.46 KB
/
lonlat_to_sfc.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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#' Convert a lon/lat or lat/lon coordinate pair to a sfc object
#'
#' `r lifecycle::badge("experimental")`
#'
#' @name lonlat_to_sfc
#' @param x A length 2 numeric vector with geodetic coordinates in a
#' [EPSG:4326](https://epsg.org/crs_4326/WGS-84.html) coordinate reference
#' system.
#' @param range For [lonlat_to_sfc()], an object that is coercible to a `bbox`
#' object or a length 4 vector with names xmin, xmax, ymin, and ymax. If a
#' coordinate pair falls outside the latitude/longitude range defined by the
#' vector but inside the range if reversed, the coordinates are assumed to be
#' in lat/lon order and are switched to lon/lat order before being converted
#' to a point. Defaults to `c("xmin" = -180, "ymin" = -50, "xmax" = 180,
#' "ymax" = 60)`. Note that this default setting will reverse valid
#' coordinates north of Anchorage, Alaska or south of New Zealand.
#' @param quiet If `TRUE`, suppress alert messages when converting a lat/lon
#' coordinate pair to a lon/lat pair. Defaults to `FALSE`.
#' @inheritParams rlang::args_error_context
#' @inheritDotParams sf::st_sfc -crs
#' @seealso [is_geo_coords()]
#' @export
#' @importFrom cli cli_alert_warning cli_alert_success
#' @importFrom sf st_point st_sfc
lonlat_to_sfc <- function(x,
range = getOption("sfext.coord_range", c("xmin" = -180, "ymin" = -50, "xmax" = 180, "ymax" = 60)),
quiet = FALSE,
call = parent.frame(),
...) {
if (!is_geo_coords(x)) {
cli_abort("{.arg x} must be geodetic coordinates.", call = call)
}
cli_quiet(quiet)
rev_latlon <- FALSE
if (!is_null(range)) {
if (is_sf(range, ext = TRUE)) {
range <- as_bbox(range, crs = 4326)
range <- set_names(as.numeric(range), names(range))
}
likely_latlon <-
!is_lonlat_in_range(x, range) && is_lonlat_in_range(x, range, rev = TRUE)
rev_latlon <- any(
c(
likely_latlon,
max(abs(x[[2]])) > 90
)
)
}
if (isTRUE(rev_latlon)) {
if (isTRUE(likely_latlon)) {
cli::cli_alert_warning(
"Supplied coordinates appear to be in lat/lon order based on {.arg range}."
)
}
cli::cli_alert_success(
"Reversing lat/lon coordinates to lon/lat order:
{.val {x}} {cli::symbol$arrow_right} {.val {rev(x)}}"
)
x <- rev(x)
}
sf::st_sfc(sf::st_point(x), crs = 4326, ...)
}
#' @keywords internal
#' @noRd
check_range <- function(range = NULL, nm = c("xmin", "ymin", "xmax", "ymax"), call = caller_env()) {
if (!is_vector(range, length(nm)) || !all(has_name(range, nm))) {
cli_abort(
"{.arg range} must be a length {length(nm)} vector with names {.val {nm}}.",
call = call
)
}
}
#' @keywords internal
#' @noRd
is_lonlat_in_range <- function(x,
range = NULL,
rev = FALSE,
allow_null = TRUE,
call = caller_env()) {
if (allow_null && is_null(range)) {
return(TRUE)
}
check_range(range, call = call)
if (isTRUE(rev)) {
x <- rev(x)
}
x <- as.numeric(x)
check_bare_numeric(x, call = call)
cli_abort_ifnot(
has_length(x, 2),
message = "{.arg x} must be a length 2 vector.",
call = call
)
lon_in_range <- (x[[1]] >= range[["xmin"]]) && (x[[1]] <= range[["xmax"]])
lat_in_range <- (x[[2]] >= range[["ymin"]]) && (x[[2]] <= range[["ymax"]])
lon_in_range && lon_in_range
}