Skip to content

Commit 2746bfa

Browse files
authored
Merge pull request #176 from jessecambon/dev
api_options error handling
2 parents 5fd1220 + b02d131 commit 2746bfa

26 files changed

+1846
-260
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- Changed the default `min_time` (minimum seconds elapsed per query) value to 1 (60 queries per minute) for the Location IQ service ([#166](https://github.com/jessecambon/tidygeocoder/issues/166)).
44
- Updated default Geocodio API URL from version 1.6 to 1.7.
55
- Fixed code and documentation that incorrectly referred to `mapquest_open` as `mapbox_open`.
6+
- An error is now thrown if an `api_options` parameter is not compatible with the specified `method`.
67
- A message is now displayed warning that `flatten=FALSE` is ignored for Geocodio and Mapquest (the output of these services requires flattening to avoid errors).
78

89
# tidygeocoder 1.0.5

R/geo.R

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,9 @@ geo <-
238238
api_options[["here_request_id"]] <- here_request_id
239239
}
240240
}
241-
241+
242242
# apply api options defaults for options not specified by the user
243-
api_options <- apply_api_options_defaults(api_options)
243+
api_options <- apply_api_options_defaults(method, api_options)
244244

245245
# capture all function arguments including default values as a named list.
246246
# IMPORTANT: make sure to put this statement before any other variables are defined in the function
@@ -267,7 +267,7 @@ geo <-
267267

268268
# Check parameter arguments --------------------------------------------------------
269269

270-
check_api_options(api_options, "geo")
270+
check_api_options(method, api_options, FALSE, return_addresses)
271271

272272
# Check argument inputs
273273
if (is.null(address) && is.null(street) && is.null(city) && is.null(county) && is.null(state) && is.null(postalcode) && is.null(country)) {
@@ -282,29 +282,24 @@ geo <-
282282
check_address_argument_datatype(postalcode, "postalcode")
283283
check_address_argument_datatype(country, "country")
284284

285-
if (!(api_options[["census_return_type"]] %in% c("geographies", "locations"))) {
286-
stop("Invalid return_type argument. See ?geo", call. = FALSE)
285+
if (!is.null(api_options[["census_return_type"]])) {
286+
if (!(api_options[["census_return_type"]] %in% c("geographies", "locations"))) {
287+
stop("Invalid census_return_type argument. See ?geo", call. = FALSE)
288+
}
287289
}
288290

289291
stopifnot(
290292
is.logical(verbose), is.logical(no_query), is.logical(flatten), is.null(param_error) || is.logical(param_error),
291293
is.logical(full_results), is.logical(unique_only), is.logical(return_addresses),
292294
is.null(batch_limit_error) || is.logical(batch_limit_error), is.logical(progress_bar), is.logical(quiet),
293295
is.numeric(timeout), timeout >= 0,
294-
is.list(custom_query),
295-
is.logical(api_options[["mapbox_permanent"]]),
296-
is.null(api_options[["here_request_id"]]) || is.character(api_options[["here_request_id"]]),
297-
is.logical(api_options[["mapquest_open"]]), is.logical(api_options[["geocodio_hipaa"]])
296+
is.list(custom_query)
298297
)
299298

300299
check_verbose_quiet(verbose, quiet, reverse = FALSE)
301300
check_common_args("geo", mode, limit, batch_limit, min_time)
302301
check_method(method, reverse = FALSE, mode, batch_func_map, cascade_order = cascade_order)
303302

304-
if (!(api_options[["census_return_type"]] %in% c("geographies", "locations"))) {
305-
stop("Invalid census_return_type value. See ?geo", call. = FALSE)
306-
}
307-
308303
# Deprecate parameters that only existed because of method = "cascade"
309304
if (api_options[["init"]] == TRUE) {
310305
if (!missing("cascade_order")) lifecycle::deprecate_warn("1.0.4", "geo(cascade_order)", "geocode_combine()")

R/global_variables.R

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ pkg.globals$no_reverse_methods <- c("census")
2626
# these methods do not support flatten=FALSE for batch geocoding
2727
pkg.globals$batch_flatten_required_methods <- c("geocodio", "mapquest")
2828

29+
# these are special api_options parameters that do not correspond to a method
30+
# cascade_flag: indicates (deprecated) cascade method was used
31+
# init: indicates if this is the first pass through the geo or reverse_geo function
32+
pkg.globals$special_api_options <- c("cascade_flag", "init")
33+
2934
# default settings for the `api_options` argument in geo() and reverse_geo()
3035
pkg.globals$default_api_options <- list(
3136
census_return_type = "locations",

R/input_handling.R

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,21 @@
66
# utility function for packaging either lat longs or address data
77
# takes a dataframe input
88
# cords = TRUE if processing coordinates
9+
# note that reverse_geo() converts lat and long into numeric before passing to this function
910
package_inputs <- function(input_orig, coords = FALSE) {
1011
input_colnames <- names(input_orig) # store column names
1112

1213
input_unique <- input_orig
1314

1415
# limit lat/longs in unique dataset to possible values
1516
if (coords == TRUE) {
16-
input_unique$lat[(input_unique$lat > 90 | input_unique$lat < -90)] <- NA
17-
input_unique$long[(input_unique$long > 180 | input_unique$long < -180)] <- NA
17+
input_unique$lat[(input_unique$lat > 90 | input_unique$lat < -90)] <- as.numeric(NA)
18+
input_unique$long[(input_unique$long > 180 | input_unique$long < -180)] <- as.numeric(NA)
1819

1920
# If lat is NA then make long NA and vice versa (ie. don't pass coordinate if
2021
# only one value exists)
21-
input_unique$lat <- ifelse(is.na(input_unique$long), NA, input_unique$lat)
22-
input_unique$long <- ifelse(is.na(input_unique$lat), NA, input_unique$long)
22+
input_unique$lat <- ifelse(is.na(input_unique$long), as.numeric(NA), input_unique$lat)
23+
input_unique$long <- ifelse(is.na(input_unique$lat), as.numeric(NA), input_unique$long)
2324
}
2425

2526
# remove rows that are entirely blank or NA

R/results_processing.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ extract_results <- function(method, response, full_results = TRUE, flatten = TRU
108108

109109
# add prefix to variable names that likely could be in our input dataset
110110
# to avoid variable name overlap
111-
for (var in c("address", "street", "city", "county", "state", "postalcode", "postcode", "country")) {
111+
for (var in c(pkg.globals$address_arg_names, "postcode")) {
112112
if (var %in% names(results)) {
113113
names(results)[names(results) == var] <- paste0(method, "_", var)
114114
}
@@ -177,7 +177,7 @@ extract_reverse_results <- function(method, response, full_results = TRUE, flatt
177177
"geoapify" = response$features$properties["formatted"]
178178
)
179179

180-
# Return NA if data is not empty or not valid (cannot be turned into a dataframe)
180+
# Return NA if data is empty or not valid (cannot be turned into a dataframe)
181181
if (is.null(names(address)) | all(sapply(address, is.null)) | length(address) == 0) {
182182
return(NA_result)
183183
}

R/reverse_geo.R

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ reverse_geo <-
171171
}
172172

173173
# apply api options defaults for options not specified by the user
174-
api_options <- apply_api_options_defaults(api_options)
174+
api_options <- apply_api_options_defaults(method, api_options)
175175

176176
# capture all function arguments including default values as a named list.
177177
# IMPORTANT: make sure to put this statement before any other variables are defined in the function
@@ -185,17 +185,13 @@ reverse_geo <-
185185
is.logical(verbose), is.logical(no_query), is.logical(flatten),
186186
is.logical(full_results), is.logical(unique_only), is.logical(progress_bar),
187187
is.logical(quiet),
188-
is.list(custom_query),
189-
is.logical(api_options[["mapbox_permanent"]]),
190-
is.null(api_options[["here_request_id"]]) || is.character(api_options[["here_request_id"]]),
191-
is.logical(api_options[["mapquest_open"]]), is.logical(api_options[["geocodio_hipaa"]])
188+
is.list(custom_query)
192189
)
193190

194191
check_verbose_quiet(verbose, quiet, reverse = FALSE)
195192

196193
# Check method argument
197-
198-
check_api_options(api_options, "reverse_geo")
194+
check_api_options(method, api_options, TRUE, return_coords)
199195
check_method(method, reverse = TRUE, mode, reverse_batch_func_map)
200196

201197
if (length(lat) != length(long)) stop("Lengths of lat and long must be equal.", call. = FALSE)
@@ -288,8 +284,6 @@ reverse_geo <-
288284
))
289285
}
290286

291-
if (method == "here") check_here_return_input(api_options[["here_request_id"]], return_coords, reverse = TRUE)
292-
293287
# Enforce batch limit if needed
294288
if (num_unique_coords > batch_limit) {
295289
stop(paste0(

R/utils.R

Lines changed: 106 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -104,20 +104,6 @@ format_address <- function(df, fields) {
104104
# QA Checks --------------------------------------------------------------------
105105
# functions called by reverse_geo() and/or geo()
106106

107-
check_api_options <- function(api_options, func_name) {
108-
for (param in names(api_options)) {
109-
if (!param %in% c("cascade_flag", "init", names(pkg.globals$default_api_options))) {
110-
stop(
111-
paste0(
112-
"Invalid parameter ", '"', param, '"',
113-
" used in the api_options argument. See ?", func_name
114-
),
115-
call. = FALSE
116-
)
117-
}
118-
}
119-
}
120-
121107
# check the data type of an address argument - called by geo() function
122108
# should not be a matrix, class, or dataframe for instance
123109
# allow factor since it could be coerced to a datatype by address handler function
@@ -229,22 +215,6 @@ check_limit_for_batch <- function(limit, return_input, reverse) {
229215
}
230216
}
231217

232-
233-
# check for HERE method batch queries --- for use in geo() and reverse_geo()
234-
check_here_return_input <- function(here_request_id, return_input, reverse) {
235-
input_terms <- get_coord_address_terms(reverse)
236-
237-
# If a previous job is requested return_addresses should be FALSE
238-
# This is because the job won't send the addresses, but would recover the
239-
# results of a previous request
240-
if (is.character(here_request_id) && return_input == TRUE) {
241-
stop("HERE: When requesting a previous job via here_request_id, set ", input_terms$return_arg,
242-
" to FALSE. See ?", input_terms$base_func_name, " for details.",
243-
call. = FALSE
244-
)
245-
}
246-
}
247-
248218
# Misc -----------------------------------------------------------------------------------------
249219

250220
## function for extracting everything except the single line
@@ -349,15 +319,26 @@ api_url_modification <- function(method, api_url, generic_query, custom_query, r
349319
return(api_url)
350320
}
351321

352-
# apply api options defaults for options not specified by the user.
353-
# called by geo() and reverse_geo()
354-
apply_api_options_defaults <- function(api_options) {
355-
for (name in names(pkg.globals$default_api_options)) {
356-
if (is.null(api_options[[name]])) api_options[[name]] <- pkg.globals$default_api_options[[name]]
322+
323+
# for specific geocoders in batch setting...
324+
# give a warning that the query is going to run with flatten=TRUE
325+
# even though the user specified flatten=FALSE
326+
flatten_override_warning <- function(flatten, method, reverse, batch) {
327+
if (flatten == FALSE && (method %in% pkg.globals$batch_flatten_required_methods)) {
328+
input_terms <- get_coord_address_terms(reverse)
329+
message(paste0(
330+
"Note: flatten=FALSE is ignored. Outputs must be flattened for the ",
331+
get_setting_value(tidygeocoder::api_info_reference, method, "method_display_name"), " ",
332+
if (batch == TRUE) "batch" else paste0("single ", input_terms$input_singular),
333+
" geocoder"
334+
))
357335
}
358-
return(api_options)
359336
}
360337

338+
339+
340+
# Api options functions ----------------------------------------------------------------------
341+
361342
# Set the api_options[["init"]] parameter
362343
# init is for internal package use only, used to designate if the geo() or reverse_geo() function
363344
# is being called for the first time (init = TRUE) or if it has called itself
@@ -369,17 +350,94 @@ initialize_init <- function(api_options) {
369350
return(api_options)
370351
}
371352

372-
# for specific geocoders in batch setting...
373-
# give a warning that the query is going to run with flatten=TRUE
374-
# even though the user specified flatten=FALSE
375-
flatten_override_warning <- function(flatten, method, reverse, batch) {
376-
if (flatten == FALSE && (method %in% pkg.globals$batch_flatten_required_methods)) {
377-
input_terms <- get_coord_address_terms(reverse)
378-
message(paste0(
379-
"Note: flatten=FALSE is ignored. Outputs must be flattened for the ",
380-
get_setting_value(tidygeocoder::api_info_reference, method, "method_display_name"), " ",
381-
if (batch == TRUE) "batch" else paste0("single ", input_terms$input_singular),
382-
" geocoder"
383-
))
353+
# check for HERE method batch queries --- for use in geo() and reverse_geo()
354+
check_here_return_input <- function(here_request_id, return_input, reverse) {
355+
input_terms <- get_coord_address_terms(reverse)
356+
357+
# If a previous job is requested return_addresses should be FALSE
358+
# This is because the job won't send the addresses, but would recover the
359+
# results of a previous request
360+
if (is.character(here_request_id) && return_input == TRUE) {
361+
stop("HERE: When requesting a previous job via here_request_id, set ", input_terms$return_arg,
362+
" to FALSE. See ?", input_terms$base_func_name, " for details.",
363+
call. = FALSE
364+
)
365+
}
366+
}
367+
368+
# apply api options defaults for options not specified by the user
369+
# that are relevant for the specified method
370+
# called by geo() and reverse_geo()
371+
apply_api_options_defaults <- function(method, api_options) {
372+
for (api_opt in names(pkg.globals$default_api_options)) {
373+
api_opt_method <- strsplit(api_opt, "_")[[1]][[1]] # extract method name from api option
374+
375+
if ((method == api_opt_method) && is.null(api_options[[api_opt]])) {
376+
api_options[[api_opt]] <- pkg.globals$default_api_options[[api_opt]]
377+
}
378+
}
379+
return(api_options)
380+
}
381+
382+
# throw error if method and a specified api_option is mismatched
383+
# ie. method='census' and api_options(list(geocodio_hipaa=TRUE))
384+
# return_inputs : return_addresses for geo() or return_inputs for reverse_geo()
385+
check_api_options <- function(method, api_options, reverse, return_inputs) {
386+
387+
stopifnot(
388+
is.null(api_options[["mapbox_permanent"]]) || is.logical(api_options[["mapbox_permanent"]]),
389+
is.null(api_options[["here_request_id"]]) || is.character(api_options[["here_request_id"]]),
390+
is.null(api_options[["mapquest_open"]]) || is.logical(api_options[["mapquest_open"]]),
391+
is.null(api_options[["geocodio_hipaa"]]) || is.logical(api_options[["geocodio_hipaa"]])
392+
)
393+
394+
if (method == "here") check_here_return_input(api_options[["here_request_id"]], return_inputs, reverse = reverse)
395+
396+
397+
# cycle through the api options specified (except for init)
398+
# if (api_options$init == TRUE) {
399+
api_method_mismatch_args <- c() # store mismatch api_options here
400+
api_bad_args <- c() # store invalid api_options here
401+
error_message <- c() # store error message here (if any)
402+
403+
for (api_opt in names(api_options)[!names(api_options) %in% pkg.globals$special_api_options]) {
404+
# extract method name from api_option
405+
api_opt_method <- strsplit(api_opt, "_")[[1]][[1]]
406+
407+
# check if api parameter is valid
408+
if (!api_opt %in% names(pkg.globals$default_api_options)) {
409+
api_bad_args <- c(api_bad_args, api_opt)
410+
}
411+
# if api parameter is valid but there is a mismatch with selected method
412+
# then add offending arg to vector
413+
else if (api_opt_method != method) {
414+
api_method_mismatch_args <- c(api_method_mismatch_args, api_opt)
415+
}
416+
} # end loop
417+
418+
# error message for bad api arguments
419+
if (length(api_bad_args) != 0) {
420+
error_message <- c(error_message,
421+
paste0(
422+
"Invalid api_options parameter(s) used:\n\n",
423+
paste0(api_bad_args, sep = " "), "\n\n"
424+
))
425+
}
426+
427+
# error message for api arguments that mismatch with the method argument
428+
if (length(api_method_mismatch_args) != 0) {
429+
error_message <- c(error_message,
430+
'method = "', method, '" is not compatible with the specified api_options parameter(s):\n\n',
431+
paste0(api_method_mismatch_args, sep = " "), "\n\n"
432+
)
433+
}
434+
435+
# show error (if applicable)
436+
if (length(error_message) != 0) {
437+
stop(error_message,
438+
'See ?', if (reverse == TRUE) "reverse_geo" else "geo",
439+
call. = FALSE
440+
)
384441
}
442+
# }
385443
}

docs/articles/developer_notes.html

Lines changed: 9 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)