/
cartographer-global.R
209 lines (192 loc) · 6.82 KB
/
cartographer-global.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
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
cartographer_global <- new.env(parent = emptyenv())
#' Register a new feature type
#'
#' This adds a new feature type that can then be used by all the geoms in this
#' package. If registering from another package, this should occur in the
#' \code{.onLoad()} hook in the package.
#'
#' Registration supports delayed evaluation (lazy loading). This is particularly
#' useful for larger datasets, so that they are not loaded into memory until
#' they are accessed.
#'
#' @param feature_type Name of the type. If registering from within a package,
#' the suggested format is \code{"<package name>.<map name>"} to avoid clashes
#' between packages.
#' @param data A simple feature data frame with the map data, or a function
#' that returns a data frame. When \code{lazy} is \code{TRUE}, the value
#' will not be evaluated until the data is first accessed.
#' @param feature_column Name of the column of \code{data} that contains the
#' feature names.
#' @param aliases Optional named character vector or list that maps aliases to
#' values that appear in the feature column. This allows abbreviations or
#' alternative names to be supported.
#' @param outline Optional sf geometry containing just the outline of the map,
#' or a function returning such a geometry. When \code{lazy} is \code{TRUE},
#' the value will not be evaluated until the data is first accessed.
#' @param lazy When \code{TRUE}, defer evaluation of \code{data} and
#' \code{outline} until it is used.
#'
#' @returns No return value; this updates the global feature registry.
#' @seealso `vignette("registering_maps")`
#' @export
#'
#' @examples
#' # register a map of the states of Italy from rnaturalearth using the
#' # Italian names, and providing an outline of the country
#' register_map(
#' "italy",
#' data = rnaturalearth::ne_states(country = "italy", returnclass = "sf"),
#' feature_column = "name_it",
#' outline = rnaturalearth::ne_countries(country = "italy", returnclass = "sf", scale = "large")
#' )
register_map <- function(feature_type, data, feature_column,
aliases = NULL, outline = NULL, lazy = TRUE) {
if (is.null(aliases)) aliases <- character(0)
if (lazy) {
delayedAssign(feature_type,
list(
data = validate_map_data(data, feature_column),
feature_column = feature_column,
aliases = aliases,
outline = validate_outline_data(outline)
),
assign.env = cartographer_global
)
} else {
cartographer_global[[feature_type]] <- list(
data = validate_map_data(data, feature_column),
feature_column = feature_column,
aliases = aliases,
outline = validate_outline_data(outline)
)
}
}
validate_map_data <- function(data, feature_column) {
if (is.function(data)) data <- data()
if (!inherits(data, "sf")) {
cli::cli_abort("{.arg data} must be an sf object, not {class(data)}")
}
if (!feature_column %in% names(data)) {
cli::cli_abort("{.field feature_column} {feature_column} not found in registered data")
}
invisible(data)
}
validate_outline_data <- function(data) {
if (is.null(data)) {
return(NULL)
}
if (is.function(data)) data <- data()
if (!inherits(data, "sf")) {
cli::cli_abort("{.arg data} must be an sf object, not {class(data)}")
}
invisible(data)
}
#' List known feature types
#'
#' Each feature type corresponds to map data that has been registered.
#'
#' @seealso [register_map()]
#'
#' @returns Character vector of registered feature types.
#' @export
#' @examples
#' feature_types()
feature_types <- function() {
names(cartographer_global)
}
is_promise <- function(ftype) {
.Call(C_is_promise, as.name(ftype), cartographer_global)
}
promise_was_forced <- function(ftype) {
.Call(C_promise_was_forced, as.name(ftype), cartographer_global)
}
#' List known feature names
#'
#' This gives the list of feature names that are part of the specified map data.
#' The list includes any aliases defined when the map was registered. Note that
#' the \code{location} column matching is case insensitive (see Details below).
#'
#' @seealso [register_map()] and [resolve_feature_names()]
#'
#' @param feature_type Type of map feature. See [feature_types()] for a list of
#' registered types.
#'
#' @returns Character vector of feature names.
#' @export
#'
#' @examples
#' head(feature_names("sf.nc"))
feature_names <- function(feature_type) {
names <- get_feature_names(feature_type)
aliases <- map_aliases(feature_type)
c(names, names(aliases))
}
get_feature_names <- function(feature_type) {
if (missing(feature_type) || is.na(feature_type)) {
cli::cli_abort("Must specify a {.arg feature_type}")
}
cfg <- cartographer_global[[feature_type]]
if (is.null(cfg)) cli::cli_abort("Unknown feature type {feature_type}")
cfg$data[[cfg$feature_column]]
}
#' Retrieve map data registered with cartographer.
#'
#' @param feature_type Type of map feature. See [feature_types()] for a list of
#' registered types.
#'
#' @returns The spatial data frame that was registered under `feature_type`.
#' @export
#'
#' @examples
#' map_sf("sf.nc")
map_sf <- function(feature_type) {
cfg <- cartographer_global[[feature_type]]
if (is.null(cfg)) cli::cli_abort("Unknown feature type {feature_type}")
cfg$data
}
#' Retrieve geometry of a single location.
#'
#' @param feature_names Name of the feature(s) to retrieve. This must be an exact
#' case-sensitive match, and aliases are not consulted.
#' @param feature_type Type of map feature. See [feature_types()] for a list of
#' registered types.
#'
#' @returns The geometry as a `sfc` object.
#' @export
#'
#' @examples
#' map_sfc("Ashe", "sf.nc")
#' map_sfc(c("Craven", "Buncombe"), "sf.nc")
map_sfc <- function(feature_names, feature_type) {
geoms <- map_sf(feature_type)
registered_names <- get_feature_names(feature_type)
matches <- match(feature_names, registered_names)
if (any(is.na(matches))) {
unmatched <- feature_names[is.na(matches)] # nolint: object_usage_linter
cli::cli_abort(c("Feature names are not all present in the data registered for {feature_type}",
"i" = "These are missing: {head(unmatched, n = 3)}"
))
}
sf::st_geometry(geoms[matches, ])
}
map_aliases <- function(feature_type) {
cfg <- cartographer_global[[feature_type]]
if (is.null(cfg)) cli::cli_abort("Unknown feature type {feature_type}")
cfg$aliases
}
#' Retrieve a map outline registered with cartographer.
#'
#' @param feature_type Type of map feature. See [feature_types()] for a list of
#' registered types.
#'
#' @returns The map outline that was registered under `feature_type`. Note that
#' the outline is optional, so this will return `NULL` if none was registered.
#' @export
#'
#' @examples
#' map_outline("sf.nc")
map_outline <- function(feature_type) {
cfg <- cartographer_global[[feature_type]]
if (is.null(cfg)) cli::cli_abort("Unknown feature type {feature_type}")
cfg$outline
}