Skip to content

Commit

Permalink
Merge pull request #3 from dieghernan/geo
Browse files Browse the repository at this point in the history
Add geocoding capabilities
  • Loading branch information
dieghernan authored Jan 10, 2024
2 parents f6a57f8 + 19841f8 commit 634c6ea
Show file tree
Hide file tree
Showing 8 changed files with 470 additions and 31 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Generated by roxygen2: do not edit by hand

export(arc_geo)
export(arc_reverse_geo)
export(arcgeocoder_check_access)
importFrom(utils,URLencode)
Expand Down
160 changes: 138 additions & 22 deletions R/arc_geo.R
Original file line number Diff line number Diff line change
@@ -1,26 +1,144 @@
arc_geo_single <- function(address,
lat = "lat",
long = "lon",
limit = 1,
full_results = TRUE,
return_addresses = TRUE,
verbose = TRUE,
custom_query = list()) {
#' Geocoding using the ArcGIS REST API
#'
#' @description
#' Geocodes addresses given as character values. This
#' function returns the \CRANpkg{tibble} associated with the query.
#'
#' @param address character with single line address
#' (`"1600 Pennsylvania Ave NW, Washington"`) or a vector of addresses
#' (`c("Madrid", "Barcelona")`).
#' @param lat latitude column name in the output data (default `"lat"`).
#' @param long longitude column name in the output data (default `"lon"`).
#' @param limit maximum number of results to return per input address. Note
#' that each query returns a maximum of 50 results.
#' @param full_results returns all available data from the API service. This
#' is a shorthand of `outFields=*`. See **References**.
#' If `FALSE` (default) only the default values of the API would be returned.
#' See also `return_addresses`.
#' @param return_addresses return input addresses with results if `TRUE`.
#' @param sourcecountry Limits the candidates returned to the specified country
#' or countries. Acceptable values include the three-character country code.
#' You can specify multiple country codes to limit results to more than one
#' country.
#'
#' @inheritParams arc_reverse_geo
#'
#'
#' @references
#' [ArcGIS REST
#' `findAddressCandidates`](https://developers.arcgis.com/rest/geocode/api-reference/geocoding-find-address-candidates.htm)

Check warning on line 29 in R/arc_geo.R

View workflow job for this annotation

GitHub Actions / Run lintr scanning

file=R/arc_geo.R,line=29,col=81,[line_length_linter] Lines should not be more than 80 characters. This line is 123 characters.
#'
#' @return A \CRANpkg{tibble} with the results.
#'
#' @details
#' More info and valid values in the [ArcGIS REST
#' docs](https://developers.arcgis.com/rest/geocode/api-reference/geocoding-find-address-candidates.htm)

Check warning on line 35 in R/arc_geo.R

View workflow job for this annotation

GitHub Actions / Run lintr scanning

file=R/arc_geo.R,line=35,col=81,[line_length_linter] Lines should not be more than 80 characters. This line is 104 characters.
#'
#' ## `outsr`
#'
#' The spatial reference can be specified as either a well-known ID (WKID). If
#' not specified, the spatial reference of the output locations is the same as
#' that of the service ( WGS84, i.e. WKID = 4326)).
#'
#'
#' @examplesIf arcgeocoder_check_access()
#' \donttest{
#' arc_geo("Madrid, Spain")
#'
#' # Several addresses with additional output fields
#' with_params <- arc_geo(c("Madrid", "Barcelona"),
#' full_results = TRUE,
#' custom_query = list(outFields = "LongLabel")
#' )
#'
#' with_params[, c("lat", "lon", "LongLabel")]
#'
#' # With options: restrict search to USA
#' with_params_usa <- arc_geo(c("Madrid", "Barcelona"),
#' full_results = TRUE,
#' sourcecountry = "USA",
#' custom_query = list(outFields = "LongLabel")
#' )
#'
#' with_params_usa[, c("lat", "lon", "LongLabel")]
#' }
#' @export
#'
#' @seealso [tidygeocoder::geo()]
#' @family geocoding
arc_geo <- function(address, lat = "lat", long = "lon", limit = 1,
full_results = FALSE, return_addresses = TRUE,
verbose = FALSE, progressbar = TRUE,
outsr = NULL, langcode = NULL, sourcecountry = NULL,
custom_query = list()) {
if (limit > 50) {
message(paste(
"ArcGIS REST API provides 50 results as a maximum. ",
"Your query may be incomplete"
))
limit <- min(50, limit)
}

# Dedupe for query
init_key <- dplyr::tibble(query = address)
key <- unique(address)

# Set progress bar
ntot <- length(key)
# Set progress bar if n > 1
progressbar <- all(progressbar, ntot > 1)
if (progressbar) {
pb <- txtProgressBar(min = 0, max = ntot, width = 50, style = 3)
}
seql <- seq(1, ntot, 1)

# Add additional parameters to the custom query
if (isTRUE(full_results)) {
# This will override the outFields param provided in the custom_query
custom_query$outFields <- "*"
}

custom_query$sourceCountry <- sourcecountry
custom_query$outSR <- outsr
custom_query$langCode <- langcode


all_res <- lapply(seql, function(x) {
ad <- key[x]
if (progressbar) {
setTxtProgressBar(pb, x)
}
arc_geo_single(
address = ad, lat, long, limit, full_results, return_addresses,
verbose, custom_query, singleline = TRUE
)
})
if (progressbar) close(pb)

all_res <- dplyr::bind_rows(all_res)
all_res <- dplyr::left_join(init_key, all_res, by = "query")

all_res[all_res == ""] <- NA
return(all_res)
}



arc_geo_single <- function(address, lat = "lat", long = "lon", limit = 1,
full_results = TRUE, return_addresses = TRUE,
verbose = TRUE, custom_query = list(),
singleline = TRUE) {
# Step 1: Download ----
api <- paste0(
"https://geocode.arcgis.com/arcgis/rest/",
"services/World/GeocodeServer/findAddressCandidates?"
)

# Compose url
url <- paste0(api, "SingleLine=", address, "&f=json&maxLocations=", limit)
if (singleline) ad_q <- paste0("SingleLine=", address)

url <- paste0(api, ad_q, "&f=json&maxLocations=", limit)

# Add options

if (isTRUE(full_results)) {
custom_query$outFields <- "*"
}

url <- add_custom_query(custom_query, url)

Expand Down Expand Up @@ -57,14 +175,12 @@ arc_geo_single <- function(address,
result_end <- dplyr::bind_cols(tbl_query, result_unn)
result_end$lat <- as.double(result_unn$y)
result_end$lon <- as.double(result_unn$x)
return(result_end)

# Keep names
result_out <- keep_names_rev(result,
address = address,
# Return coords here always FALSE, check that in the top-level query
return_coords = FALSE,
full_results = full_results

# Keep names in the right order

result_out <- keep_names(
result_end, lat, long, full_results,
return_addresses
)

return(result_out)
Expand Down
10 changes: 4 additions & 6 deletions R/arc_reverse_geo.R
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,10 @@ arc_reverse_geo <- function(x, y, address = "address", full_results = FALSE,

# Add additional parameters to the custom query

custom_query$outsr <- outsr
custom_query$langcode <- langcode
custom_query$featuretypes <- featuretypes
custom_query$locationtype <- locationtype
custom_query$outSR <- outsr
custom_query$langCode <- langcode
custom_query$featureTypes <- featuretypes
custom_query$locationType <- locationtype

all_res <- lapply(seql, function(x) {
if (progressbar) {
Expand Down Expand Up @@ -257,8 +257,6 @@ arc_reverse_geo_single <- function(lat_cap,
# Keep names
result_out <- keep_names_rev(result,
address = address,
# Return coords here always FALSE, check that in the top-level query
return_coords = FALSE,
full_results = full_results
)

Expand Down
23 changes: 21 additions & 2 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ is_named <- function(x) {
return(TRUE)
}

# Specific ----
unnest_reverse <- function(x) {
x_add <- x$address
lngths <- vapply(x_add, length, FUN.VALUE = numeric(1))
Expand Down Expand Up @@ -81,13 +82,12 @@ unnest_geo <- function(x) {
return(endobj)
}

keep_names_rev <- function(x, address = "address", return_coords = FALSE,
keep_names_rev <- function(x, address = "address",
full_results = FALSE,
colstokeep = address) {
names(x) <- gsub("address", address, names(x))

out_cols <- colstokeep
if (return_coords) out_cols <- c(out_cols, "lat", "lon")
if (full_results) out_cols <- c(out_cols, "lat", "lon", names(x))

out_cols <- unique(out_cols)
Expand All @@ -96,6 +96,25 @@ keep_names_rev <- function(x, address = "address", return_coords = FALSE,
return(out)
}

keep_names <- function(x, lat = "lat", lon = "lon",
full_results = TRUE,
return_addresses = TRUE,
colstokeep = c("query", lat, lon)) {
names(x) <- gsub("^lon$", lon, names(x))
names(x) <- gsub("^lat$", lat, names(x))

out_cols <- colstokeep
out_cols <- c(out_cols, names(x))

if (!return_addresses) out_cols <- colstokeep
if (full_results) out_cols <- c(out_cols, names(x))

out_cols <- unique(out_cols)
out <- x[, out_cols]

return(out)
}

empty_tbl_rev <- function(x, address) {
init_nm <- names(x)
x <- dplyr::as_tibble(x)
Expand Down
2 changes: 1 addition & 1 deletion codemeta.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
},
"applicationCategory": "cartography",
"keywords": ["r", "geocoding", "arcgis", "address", "reverse-geocoding", "rstats", "r-package", "api-wrapper", "api-rest", "arcgis-api", "gis"],
"fileSize": "129.577KB",
"fileSize": "141.444KB",
"citation": [
{
"@type": "SoftwareSourceCode",
Expand Down
107 changes: 107 additions & 0 deletions man/arc_geo.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions tests/testthat/_snaps/arc_geo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Messages

Code
out <- arc_geo("Madrid", limit = 200)
Message
ArcGIS REST API provides 50 results as a maximum. Your query may be incomplete

---

Code
out <- arc_geo("Madrid", verbose = TRUE)
Message
URL: https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates?
Parameters:
- SingleLine=Madrid
- f=json
- maxLocations=1

Loading

0 comments on commit 634c6ea

Please sign in to comment.