From dfadb24ec4e4882e679be415d01662233125e2d5 Mon Sep 17 00:00:00 2001 From: dieghernan Date: Wed, 3 Apr 2024 15:25:46 +0000 Subject: [PATCH 1/8] Move to structured query --- DESCRIPTION | 1 + NEWS.md | 10 ++ R/bbox_to_poly.R | 4 +- R/data.R | 35 ++++++ R/geo_address_lookup.R | 6 +- R/geo_address_lookup_sf.R | 2 - R/geo_lite.R | 16 ++- R/geo_lite_sf.R | 5 +- R/geo_lite_struct.R | 113 ++++++++++++++++++ R/reverse_geo_lite.R | 12 +- R/reverse_geo_lite_sf.R | 2 - R/utils.R | 8 ++ data-raw/amenities.csv | 101 ---------------- data-raw/osm_amenities.R | 24 +++- data/osm_amenities.rda | Bin 0 -> 6116 bytes man/bbox_to_poly.Rd | 4 +- man/geo_address_lookup.Rd | 14 +-- man/geo_address_lookup_sf.Rd | 10 +- man/geo_lite.Rd | 14 +-- man/geo_lite_sf.Rd | 10 +- man/osm_amenities.Rd | 34 ++++++ man/reverse_geo_lite.Rd | 14 +-- man/reverse_geo_lite_sf.Rd | 12 +- pkgdown/_pkgdown.yml | 4 + tests/testthat/_snaps/geo_address_lookup.md | 8 ++ .../testthat/_snaps/geo_address_lookup_sf.md | 8 ++ tests/testthat/_snaps/geo_lite.md | 7 ++ tests/testthat/_snaps/geo_lite_sf.md | 7 ++ tests/testthat/test-geo_address_lookup.R | 19 ++- tests/testthat/test-geo_address_lookup_sf.R | 19 ++- tests/testthat/test-geo_lite.R | 16 ++- tests/testthat/test-geo_lite_sf.R | 16 ++- tests/testthat/test-reverse_geo_lite.R | 2 +- tests/testthat/test-reverse_geo_lite_sf.R | 2 +- 34 files changed, 381 insertions(+), 178 deletions(-) create mode 100644 R/data.R create mode 100644 R/geo_lite_struct.R delete mode 100644 data-raw/amenities.csv create mode 100644 data/osm_amenities.rda create mode 100644 man/osm_amenities.Rd create mode 100644 tests/testthat/_snaps/geo_address_lookup.md create mode 100644 tests/testthat/_snaps/geo_address_lookup_sf.md create mode 100644 tests/testthat/_snaps/geo_lite.md create mode 100644 tests/testthat/_snaps/geo_lite_sf.md diff --git a/DESCRIPTION b/DESCRIPTION index 0d83e8ed..8aab2196 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -48,3 +48,4 @@ X-schema.org-applicationCategory: cartography X-schema.org-keywords: r, geocoding, openstreetmap, address, nominatim, reverse-geocoding, rstats, shapefile, r-package, spatial, cran, api-wrapper +LazyData: true diff --git a/NEWS.md b/NEWS.md index ae47982d..b36626e6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,8 +2,18 @@ - It is possible to use **nominatimlite** with local server thanks to the new argument `nominatim_server` (#42 \@alexwhitedatamine). + - Adapt endpoints to **Nominatim v4.4.0** `[Python-only]`. +- `custom_query` argument can use vectors and `logical`: + + ``` r + geo_lite(address = "New York", + custom_query = list(addressdetails = TRUE, + viewbox = c(-60, -20, 60, 20)) + ) + ``` + # nominatimlite 0.3.0 - Add a `progressbar` parameter to `geo_lite()`, `geo_lite_sf()`, diff --git a/R/bbox_to_poly.R b/R/bbox_to_poly.R index fde22808..c5a0a3f2 100644 --- a/R/bbox_to_poly.R +++ b/R/bbox_to_poly.R @@ -5,9 +5,9 @@ #' #' @family spatial #' -#' @param bbox numeric vector of 4 elements representing the coordinates of the +#' @param bbox Numeric vector of 4 elements representing the coordinates of the #' bounding box. Values should be `c(xmin, ymin, xmax, ymax)`. -#' @param xmin,ymin,xmax,ymax alternatively, you can use these named parameters +#' @param xmin,ymin,xmax,ymax Alternatively, you can use these named parameters #' instead of `bbox`. #' #' @inheritParams sf::st_sf diff --git a/R/data.R b/R/data.R new file mode 100644 index 00000000..4e368e1f --- /dev/null +++ b/R/data.R @@ -0,0 +1,35 @@ +#' OpenStreetMap amenity database +#' +#' @description +#' Database with the list of amenities available on OpenStreetMap. +#' +#' @family datasets +#' @family amenity +#' +#' @encoding UTF-8 +#' +#' @name osm_amenities +#' +#' @docType data +#' +#' @format +#' A [`tibble`][tibble::tibble] with with +#' `r prettyNum(nrow(nominatimlite::osm_amenities), big.mark=",")` rows and +#' fields: +#' \describe{ +#' \item{category}{The category of the amenity.} +#' \item{amenity}{The value of the amenity.} +#' \item{comment}{A brief description of the type of amenity.} +#' } +#' +#' +#' @source +#' +#' @note Data extracted on **03 April 2024**. +#' +#' @examples +#' +#' data("osm_amenities") +#' +#' amenities +NULL diff --git a/R/geo_address_lookup.R b/R/geo_address_lookup.R index 8b7b83c2..75e95aa7 100644 --- a/R/geo_address_lookup.R +++ b/R/geo_address_lookup.R @@ -10,9 +10,9 @@ #' @family lookup #' @family geocoding #' -#' @param osm_ids vector of OSM identifiers as **numeric** +#' @param osm_ids Vector of OSM identifiers as **numeric** #' (`c(00000, 11111, 22222)`). -#' @param type vector character of the type of the OSM type associated to each +#' @param type Vector character of the type of the OSM type associated to each #' `osm_ids`. Possible values are node (`"N"`), way (`"W"`) or relation #' (`"R"`). If a single value is provided it would be recycled. #' @@ -81,14 +81,12 @@ geo_address_lookup <- function(osm_ids, # Keep a tbl with the query tbl_query <- dplyr::tibble(query = paste0(type, osm_ids)) - # nocov start # If no response... if (isFALSE(res)) { message(url, " not reachable.") out <- empty_tbl(tbl_query, lat, long) return(invisible(out)) } - # nocov end result <- dplyr::as_tibble(jsonlite::fromJSON(json, flatten = TRUE)) diff --git a/R/geo_address_lookup_sf.R b/R/geo_address_lookup_sf.R index 91c13429..6dc4ef68 100644 --- a/R/geo_address_lookup_sf.R +++ b/R/geo_address_lookup_sf.R @@ -97,14 +97,12 @@ geo_address_lookup_sf <- function(osm_ids, # Keep a tbl with the query tbl_query <- dplyr::tibble(query = paste0(type, osm_ids)) - # nocov start # If no response... if (isFALSE(res)) { message(url, " not reachable.") out <- empty_sf(tbl_query) return(invisible(out)) } - # nocov end # Read sfobj <- sf::read_sf(json, stringsAsFactors = FALSE) diff --git a/R/geo_lite.R b/R/geo_lite.R index 082ee702..a28a8fc9 100644 --- a/R/geo_lite.R +++ b/R/geo_lite.R @@ -10,18 +10,18 @@ #' #' @family geocoding #' -#' @param address character with single line address, e.g. +#' @param address `character` with single line address, e.g. #' (`"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 `"long"`). -#' @param limit maximum number of results to return per input address. Note +#' @param lat Latitude column name in the output data (default `"lat"`). +#' @param long Longitude column name in the output data (default `"long"`). +#' @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. +#' @param full_results Returns all available data from the API service. #' If `FALSE` (default) only latitude, longitude and address columns are #' returned. See also `return_addresses`. -#' @param return_addresses return input addresses with results if `TRUE`. -#' @param verbose if `TRUE` then detailed logs are output to the console. +#' @param return_addresses Return input addresses with results if `TRUE`. +#' @param verbose If `TRUE` then detailed logs are output to the console. #' @param nominatim_server The URL of the Nominatim server to use. #' Defaults to `"https://nominatim.openstreetmap.org/"`. #' @param progressbar Logical. If `TRUE` displays a progress bar to indicate @@ -153,13 +153,11 @@ geo_lite_single <- function(address, tbl_query <- dplyr::tibble(query = address) - # nocov start if (isFALSE(res)) { message(url, " not reachable.") out <- empty_tbl(tbl_query, lat, long) return(invisible(out)) } - # nocov end result <- dplyr::as_tibble(jsonlite::fromJSON(json, flatten = TRUE)) diff --git a/R/geo_lite_sf.R b/R/geo_lite_sf.R index 07fe0ac8..2e91b01e 100644 --- a/R/geo_lite_sf.R +++ b/R/geo_lite_sf.R @@ -13,10 +13,9 @@ #' @family geocoding #' @family spatial #' -#' @param full_results returns all available data from the API service. +#' @param full_results Returns all available data from the API service. #' If `FALSE` (default) only address columns are returned. See also #' `return_addresses`. -#' #' @param points_only Logical `TRUE/FALSE`. Whether to return only spatial #' points (`TRUE`, which is the default) or potentially other shapes as #' provided by the Nominatim API (`FALSE`). See **About Geometry Types**. @@ -195,13 +194,11 @@ geo_lite_sf_single <- function(address, # Keep a tbl with the query tbl_query <- dplyr::tibble(query = address) - # nocov start if (isFALSE(res)) { message(url, " not reachable.") out <- empty_sf(tbl_query) return(invisible(out)) } - # nocov end # Read sfobj <- sf::read_sf(json, stringsAsFactors = FALSE) diff --git a/R/geo_lite_struct.R b/R/geo_lite_struct.R new file mode 100644 index 00000000..83d47e1d --- /dev/null +++ b/R/geo_lite_struct.R @@ -0,0 +1,113 @@ +geo_lite_struct <- function( + amenity = NULL, street = NULL, city = NULL, county = NULL, state = NULL, + country = NULL, postalcode = NULL, lat = "lat", long = "lon", limit = 50, + full_results = FALSE, return_addresses = TRUE, verbose = FALSE, + nominatim_server = "https://nominatim.openstreetmap.org/", + custom_query = list()) { + if (limit > 50) { + message(paste( + "Nominatim provides 50 results as a maximum. ", + "Your query may be incomplete" + )) + limit <- min(50, limit) + } + + # Check params, not vectorized + pars <- list( + amenity = amenity[1], + street = street[1], + city = city[1], + county = county[1], + state = state[1], + country = country[1], + postalcode = postalcode[1] + ) + + pars <- lapply(pars, function(x) { + if (is.null(x)) { + return(NA_character_) + } + + a_char <- as.character(x) + # Replace spaces with + + a_char <- gsub(" ", "+", a_char) + a_char + }) + + tbl_query <- tibble::as_tibble(pars) + names(tbl_query) <- paste0("q_", names(tbl_query)) + + if (all(is.na(pars))) { + message("Nothing to search for.") + out <- empty_tbl(tbl_query, lat, long) + return(invisible(out)) + } + + # First build the api address. If the passed nominatim_server does not end + # with a trailing forward-slash, add one + api <- prepare_api_url(nominatim_server, "search?") + # Compose url + url <- paste0(api, "format=json&limit=", limit) + + if (full_results) url <- paste0(url, "&addressdetails=1") + + # Clean and add options + newopts <- c(pars, custom_query) + + logis <- vapply(newopts, function(x) { + any(is.na(x), is.null(x)) + }, FUN.VALUE = logical(1)) + + + newopts <- newopts[!logis] + url <- add_custom_query(newopts, url) + + # Download to temp file + json <- tempfile(fileext = ".json") + res <- api_call(url, json, isFALSE(verbose)) + + # Step 2: Read and parse results ---- + if (isFALSE(res)) { + message(url, " not reachable.") + out <- empty_tbl(tbl_query, lat, long) + return(invisible(out)) + } + + result <- dplyr::as_tibble(jsonlite::fromJSON(json, flatten = TRUE)) + + # Rename lat and lon + nmes <- names(result) + nmes[nmes == "lat"] <- lat + nmes[nmes == "lon"] <- long + + names(result) <- nmes + + # Empty query + if (nrow(result) == 0) { + message("No results for query ") + out <- empty_tbl(tbl_query, lat, long) + return(invisible(out)) + } + + + # Coords as double + result[lat] <- as.double(result[[lat]]) + result[long] <- as.double(result[[long]]) + + + # Add query + result_clean <- dplyr::bind_cols( + tbl_query[rep(1, nrow(result)), ], + result + ) + + # Keep names + result_out <- keep_names(result_clean, return_addresses, full_results, + colstokeep = c(names(tbl_query), lat, long) + ) + + # As tibble + result_out <- dplyr::as_tibble(result_out) + + result_out +} diff --git a/R/reverse_geo_lite.R b/R/reverse_geo_lite.R index 95ad95a4..1eb0274a 100644 --- a/R/reverse_geo_lite.R +++ b/R/reverse_geo_lite.R @@ -11,12 +11,12 @@ #' #' @family reverse #' -#' @param lat latitude values in numeric format. Must be in the range +#' @param lat Latitude values in numeric format. Must be in the range #' \eqn{\left[-90, 90 \right]}. -#' @param long longitude values in numeric format. Must be in the range +#' @param long Longitude values in numeric format. Must be in the range #' \eqn{\left[-180, 180 \right]}. -#' @param address address column name in the output data (default `"address"`). -#' @param return_coords return input coordinates with results if `TRUE`. +#' @param address Address column name in the output data (default `"address"`). +#' @param return_coords Return input coordinates with results if `TRUE`. #' @param custom_query API-specific parameters to be used, passed as a named #' list (ie. `list(zoom = 3)`). See **Details**. #' @@ -74,7 +74,7 @@ #' # With options: zoom to country level #' sev <- reverse_geo_lite( #' lat = c(40.75728, 55.95335), long = c(-73.98586, -3.188375), -#' custom_query = list(zoom = 0, extratags = 1), +#' custom_query = list(zoom = 0, extratags = TRUE), #' verbose = TRUE, full_results = TRUE #' ) #' @@ -201,13 +201,11 @@ reverse_geo_lite_single <- function(lat_cap, - # nocov start if (isFALSE(res)) { message(url, " not reachable.") out <- empty_tbl(tbl_query, address) return(invisible(out)) } - # nocov end result_init <- jsonlite::fromJSON(json, flatten = TRUE) diff --git a/R/reverse_geo_lite_sf.R b/R/reverse_geo_lite_sf.R index 650fe1cb..02387d4f 100644 --- a/R/reverse_geo_lite_sf.R +++ b/R/reverse_geo_lite_sf.R @@ -213,13 +213,11 @@ reverse_geo_lite_sf_single <- function(lat_cap, # Step 2: Read and parse results ---- tbl_query <- dplyr::tibble(lat = lat_cap, lon = long_cap) - # nocov start if (isFALSE(res)) { message(url, " not reachable.") out <- empty_sf(empty_tbl_rev(tbl_query, address)) return(invisible(out)) } - # nocov end # Empty query diff --git a/R/utils.R b/R/utils.R index 35de1689..a428496a 100644 --- a/R/utils.R +++ b/R/utils.R @@ -4,6 +4,14 @@ add_custom_query <- function(custom_query = list(), url) { return(url) } + custom_query <- lapply(custom_query, function(x) { + if (is.logical(x)) { + x <- ifelse(isTRUE(x), 1, 0) + } + x <- paste0(x, collapse = ",") + x + }) + opts <- paste0(names(custom_query), "=", custom_query, collapse = "&") end_url <- paste0(url, "&", opts) diff --git a/data-raw/amenities.csv b/data-raw/amenities.csv deleted file mode 100644 index afcdd534..00000000 --- a/data-raw/amenities.csv +++ /dev/null @@ -1,101 +0,0 @@ -category,amenity -Sustenance,bar -Sustenance,biergarten -Sustenance,cafe -Sustenance,fast_food -Sustenance,food_court -Sustenance,ice_cream -Sustenance,pub -Sustenance,restaurant -Education,college -Education,driving_school -Education,kindergarten -Education,language_school -Education,library -Education,toy_library -Education,music_school -Education,school -Education,university -Transportation,bicycle_parking -Transportation,bicycle_repair_station -Transportation,bicycle_rental -Transportation,boat_rental -Transportation,boat_sharing -Transportation,bus_station -Transportation,car_rental -Transportation,car_sharing -Transportation,car_wash -Transportation,vehicle_inspection -Transportation,charging_station -Transportation,ferry_terminal -Transportation,fuel -Transportation,grit_bin -Transportation,motorcycle_parking -Transportation,parking -Transportation,parking_entrance -Transportation,parking_space -Transportation,taxi -Financial,atm -Financial,bank -Financial,bureau_de_change -Healthcare,baby_hatch -Healthcare,clinic -Healthcare,dentist -Healthcare,doctors -Healthcare,hospital -Healthcare,nursing_home -Healthcare,pharmacy -Healthcare,social_facility -Healthcare,veterinary -Entertainment-Arts-Culture,arts_centre -Entertainment-Arts-Culture,brothel -Entertainment-Arts-Culture,casino -Entertainment-Arts-Culture,cinema -Entertainment-Arts-Culture,community_centre -Entertainment-Arts-Culture,conference_centre -Entertainment-Arts-Culture,events_venue -Entertainment-Arts-Culture,fountain -Entertainment-Arts-Culture,gambling -Entertainment-Arts-Culture,love_hotel -Entertainment-Arts-Culture,nightclub -Entertainment-Arts-Culture,planetarium -Entertainment-Arts-Culture,public_bookcase -Entertainment-Arts-Culture,social_centre -Entertainment-Arts-Culture,stripclub -Entertainment-Arts-Culture,studio -Entertainment-Arts-Culture,swingerclub -Entertainment-Arts-Culture,theatre -Public Service,courthouse -Public Service,embassy -Public Service,fire_station -Public Service,police -Public Service,post_box -Public Service,post_depot -Public Service,post_office -Public Service,prison -Public Service,ranger_station -Public Service,townhall -Facilities,bbq -Facilities,bench -Facilities,dog_toilet -Facilities,drinking_water -Facilities,give_box -Facilities,shelter -Facilities,shower -Facilities,telephone -Facilities,toilets -Facilities,water_point -Facilities,watering_place -Waste Management,sanitary_dump_station -Waste Management,recycling -Waste Management,waste_basket -Waste Management,waste_disposal -Waste Management,waste_transfer_station -Others,animal_boarding -Others,animal_breeding -Others,animal_shelter -Others,baking_oven -Others,childcare -Others,clock -Others,crematorium -Others,dive_centre diff --git a/data-raw/osm_amenities.R b/data-raw/osm_amenities.R index f065b478..39c9abb8 100644 --- a/data-raw/osm_amenities.R +++ b/data-raw/osm_amenities.R @@ -1,8 +1,28 @@ ## code to prepare `osm_amenities` dataset goes here -library(readr) +# https://www.r-bloggers.com/2021/07/politely-scraping-wikipedia-tables-2/ -osm_amenities <- readr::read_csv("./data-raw/amenities.csv") +# To clean data +library(tidyverse) +# To scrape data +library(rvest) +url <- "https://wiki.openstreetmap.org/wiki/Key:amenity" + +osm_amenities <- rvest::read_html(url) %>% # scrape web page + rvest::html_nodes("table.wikitable") %>% # pull out specific table + rvest::html_table() %>% + pluck(1) %>% + as_tibble(.name_repair = "unique") %>% + mutate(Element = ifelse(Element == "", NA, Element)) %>% + fill(Element, .direction = "down") %>% + select(category = Element, amenity = Value, comment = Comment) %>% + mutate( + category = str_trim(category), + amenity = str_trim(amenity), + comment = str_trim(comment) + ) %>% + filter(category != amenity) %>% + as_tibble() usethis::use_data(osm_amenities, overwrite = TRUE) diff --git a/data/osm_amenities.rda b/data/osm_amenities.rda new file mode 100644 index 0000000000000000000000000000000000000000..dc89a2d5efe8b6b673b8b493d233465901f553eb GIT binary patch literal 6116 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 objects. contents: has_concept("spatial") +- title: Datasets + desc: Helper datasets. + contents: + - has_concept("datasets") - title: About the package contents: nominatimlite-package diff --git a/tests/testthat/_snaps/geo_address_lookup.md b/tests/testthat/_snaps/geo_address_lookup.md new file mode 100644 index 00000000..85d3256e --- /dev/null +++ b/tests/testthat/_snaps/geo_address_lookup.md @@ -0,0 +1,8 @@ +# Fail + + Code + several <- geo_address_lookup(vector_ids, vector_type, full_results = TRUE, + nominatim_server = "https://xyz.com/") + Message + https://xyz.com/lookup?osm_ids=R146656,N240109189&format=json&addressdetails=1 not reachable. + diff --git a/tests/testthat/_snaps/geo_address_lookup_sf.md b/tests/testthat/_snaps/geo_address_lookup_sf.md new file mode 100644 index 00000000..5423f23e --- /dev/null +++ b/tests/testthat/_snaps/geo_address_lookup_sf.md @@ -0,0 +1,8 @@ +# Fail + + Code + several <- geo_address_lookup_sf(vector_ids, vector_type, full_results = TRUE, + nominatim_server = "https://xyz.com/") + Message + https://xyz.com/lookup?osm_ids=R146656,N240109189&format=geojson&addressdetails=1 not reachable. + diff --git a/tests/testthat/_snaps/geo_lite.md b/tests/testthat/_snaps/geo_lite.md new file mode 100644 index 00000000..b2867223 --- /dev/null +++ b/tests/testthat/_snaps/geo_lite.md @@ -0,0 +1,7 @@ +# Fail + + Code + several <- geo_lite("Madrid", full_results = TRUE, nominatim_server = "https://xyz.com/") + Message + https://xyz.com/search?q=Madrid&format=json&limit=1&addressdetails=1 not reachable. + diff --git a/tests/testthat/_snaps/geo_lite_sf.md b/tests/testthat/_snaps/geo_lite_sf.md new file mode 100644 index 00000000..c532968c --- /dev/null +++ b/tests/testthat/_snaps/geo_lite_sf.md @@ -0,0 +1,7 @@ +# Fail + + Code + several <- geo_lite_sf("madrid", full_results = TRUE, nominatim_server = "https://xyz.com/") + Message + https://xyz.com/search?q=madrid&format=geojson&limit=1&addressdetails=1 not reachable. + diff --git a/tests/testthat/test-geo_address_lookup.R b/tests/testthat/test-geo_address_lookup.R index f6b4184c..4f02d342 100644 --- a/tests/testthat/test-geo_address_lookup.R +++ b/tests/testthat/test-geo_address_lookup.R @@ -84,7 +84,7 @@ test_that("Checking query", { expect_equal( nrow(geo_address_lookup(34633854, "W", full_results = TRUE, - custom_query = list(extratags = 1) + custom_query = list(extratags = TRUE) )), 1 ) @@ -125,3 +125,20 @@ test_that("Handle several", { expect_identical(as.vector(several$query), paste0(vector_type, vector_ids)[2]) }) + +test_that("Fail", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + # KO + vector_ids <- c(146656, 240109189) + vector_type <- c("R", "N") + expect_snapshot(several <- geo_address_lookup( + vector_ids, vector_type, + full_results = TRUE, + nominatim_server = "https://xyz.com/" + )) + + expect_true(all(is.na(several[, 2:3]))) +}) diff --git a/tests/testthat/test-geo_address_lookup_sf.R b/tests/testthat/test-geo_address_lookup_sf.R index 1b77f847..d952a341 100644 --- a/tests/testthat/test-geo_address_lookup_sf.R +++ b/tests/testthat/test-geo_address_lookup_sf.R @@ -39,7 +39,7 @@ test_that("Checking query", { expect_equal( nrow(geo_address_lookup_sf(34633854, "W", full_results = TRUE, - custom_query = list(extratags = 1) + custom_query = list(extratags = TRUE) )), 1 ) expect_equal( @@ -110,3 +110,20 @@ test_that("Verify names", { # Do I have dups by any chance? expect_false(any(grepl("\\.[0-9]$", names(several)))) }) + +test_that("Fail", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + # KO + vector_ids <- c(146656, 240109189) + vector_type <- c("R", "N") + expect_snapshot(several <- geo_address_lookup_sf( + vector_ids, vector_type, + full_results = TRUE, + nominatim_server = "https://xyz.com/" + )) + + expect_true(all(sf::st_is_empty(several))) +}) diff --git a/tests/testthat/test-geo_lite.R b/tests/testthat/test-geo_lite.R index 790650dc..95142637 100644 --- a/tests/testthat/test-geo_lite.R +++ b/tests/testthat/test-geo_lite.R @@ -102,7 +102,7 @@ test_that("Checking query", { expect_equal( nrow(geo_lite("Madrid", - custom_query = list(extratags = 1) + custom_query = list(extratags = TRUE) )), 1 ) }) @@ -146,3 +146,17 @@ test_that("Progress bar", { # Not expect_silent(aa <- geo_lite(c("Madrid", "Barcelona"), progressbar = FALSE)) }) +test_that("Fail", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + # KO + expect_snapshot(several <- geo_lite( + "Madrid", + full_results = TRUE, + nominatim_server = "https://xyz.com/" + )) + + expect_true(all(is.na(several[, 2:3]))) +}) diff --git a/tests/testthat/test-geo_lite_sf.R b/tests/testthat/test-geo_lite_sf.R index 51461554..9c371f77 100644 --- a/tests/testthat/test-geo_lite_sf.R +++ b/tests/testthat/test-geo_lite_sf.R @@ -109,7 +109,7 @@ test_that("Checking query", { expect_equal( nrow(geo_lite_sf("Madrid", - custom_query = list(extratags = 1) + custom_query = list(extratags = TRUE) )), 1 ) }) @@ -172,3 +172,17 @@ test_that("Progress bar", { aa <- geo_lite_sf(c("Madrid", "Barcelona"), progressbar = FALSE) ) }) +test_that("Fail", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + # KO + expect_snapshot(several <- geo_lite_sf( + "madrid", + full_results = TRUE, + nominatim_server = "https://xyz.com/" + )) + + expect_true(all(sf::st_is_empty(several))) +}) diff --git a/tests/testthat/test-reverse_geo_lite.R b/tests/testthat/test-reverse_geo_lite.R index 7b785f11..3b44927f 100644 --- a/tests/testthat/test-reverse_geo_lite.R +++ b/tests/testthat/test-reverse_geo_lite.R @@ -156,7 +156,7 @@ test_that("Check unnesting", { lat = c(40.75728, 55.95335), long = c(-73.98586, -3.188375), full_results = TRUE, - custom_query = list(extratags = 1) + custom_query = list(extratags = TRUE) ) expect_s3_class(sev, "tbl") diff --git a/tests/testthat/test-reverse_geo_lite_sf.R b/tests/testthat/test-reverse_geo_lite_sf.R index 0af288db..38d99a74 100644 --- a/tests/testthat/test-reverse_geo_lite_sf.R +++ b/tests/testthat/test-reverse_geo_lite_sf.R @@ -186,7 +186,7 @@ test_that("Check unnesting", { lat = c(40.75728, 55.95335), long = c(-73.98586, -3.188375), full_results = TRUE, - custom_query = list(extratags = 1) + custom_query = list(extratags = TRUE) ) expect_s3_class(sev, "tbl") From c397d359656038762a1aa8bfe9d71de03540e87a Mon Sep 17 00:00:00 2001 From: Diego H Date: Wed, 3 Apr 2024 20:24:12 +0200 Subject: [PATCH 2/8] Add structured search and tests --- CITATION.cff | 18 ++- DESCRIPTION | 3 +- R/data.R | 2 +- R/geo_lite_struct.R | 14 +- R/reverse_geo_lite.R | 2 +- codemeta.json | 14 +- data/osm_amenities.rda | Bin 6116 -> 6522 bytes man/figures/README-pizzahut-1.png | Bin 9814 -> 9177 bytes man/osm_amenities.Rd | 2 +- tests/testthat/_snaps/geo_lite_struct.md | 7 + tests/testthat/_snaps/reverse_geo_lite.md | 8 ++ tests/testthat/_snaps/reverse_geo_lite_sf.md | 8 ++ tests/testthat/test-geo_lite_struct.R | 141 +++++++++++++++++++ tests/testthat/test-reverse_geo_lite.R | 14 ++ tests/testthat/test-reverse_geo_lite_sf.R | 14 ++ 15 files changed, 233 insertions(+), 14 deletions(-) create mode 100644 tests/testthat/_snaps/geo_lite_struct.md create mode 100644 tests/testthat/_snaps/reverse_geo_lite.md create mode 100644 tests/testthat/_snaps/reverse_geo_lite_sf.md create mode 100644 tests/testthat/test-geo_lite_struct.R diff --git a/CITATION.cff b/CITATION.cff index e0ef5ed0..0f5289a2 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -105,9 +105,6 @@ references: email: jeroen@berkeley.edu orcid: https://orcid.org/0000-0002-4035-0289 year: '2024' - identifiers: - - type: url - value: https://arxiv.org/abs/1403.2805 version: '>= 1.7.0' - type: software title: lifecycle @@ -266,6 +263,21 @@ references: email: hadley@posit.co year: '2024' version: '>= 3.0.0' +- type: software + title: tibble + abstract: 'tibble: Simple Data Frames' + notes: Suggests + url: https://tibble.tidyverse.org/ + repository: https://CRAN.R-project.org/package=tibble + authors: + - family-names: Müller + given-names: Kirill + email: kirill@cynkra.com + orcid: https://orcid.org/0000-0002-1416-3412 + - family-names: Wickham + given-names: Hadley + email: hadley@rstudio.com + year: '2024' - type: software title: tidygeocoder abstract: 'tidygeocoder: Geocoding Made Easy' diff --git a/DESCRIPTION b/DESCRIPTION index 8aab2196..b1bbc0f9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -33,6 +33,7 @@ Suggests: knitr, rmarkdown, testthat (>= 3.0.0), + tibble, tidygeocoder VignetteBuilder: knitr @@ -42,10 +43,10 @@ Config/testthat/parallel: true Copyright: Data © OpenStreetMap contributors, ODbL 1.0. Encoding: UTF-8 +LazyData: true Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.1 X-schema.org-applicationCategory: cartography X-schema.org-keywords: r, geocoding, openstreetmap, address, nominatim, reverse-geocoding, rstats, shapefile, r-package, spatial, cran, api-wrapper -LazyData: true diff --git a/R/data.R b/R/data.R index 4e368e1f..9a8b1ee4 100644 --- a/R/data.R +++ b/R/data.R @@ -31,5 +31,5 @@ #' #' data("osm_amenities") #' -#' amenities +#' osm_amenities NULL diff --git a/R/geo_lite_struct.R b/R/geo_lite_struct.R index 83d47e1d..dd5e6f4c 100644 --- a/R/geo_lite_struct.R +++ b/R/geo_lite_struct.R @@ -1,6 +1,6 @@ geo_lite_struct <- function( amenity = NULL, street = NULL, city = NULL, county = NULL, state = NULL, - country = NULL, postalcode = NULL, lat = "lat", long = "lon", limit = 50, + country = NULL, postalcode = NULL, lat = "lat", long = "lon", limit = 1, full_results = FALSE, return_addresses = TRUE, verbose = FALSE, nominatim_server = "https://nominatim.openstreetmap.org/", custom_query = list()) { @@ -27,14 +27,11 @@ geo_lite_struct <- function( if (is.null(x)) { return(NA_character_) } - a_char <- as.character(x) - # Replace spaces with + - a_char <- gsub(" ", "+", a_char) a_char }) - tbl_query <- tibble::as_tibble(pars) + tbl_query <- dplyr::as_tibble(pars) names(tbl_query) <- paste0("q_", names(tbl_query)) if (all(is.na(pars))) { @@ -43,6 +40,11 @@ geo_lite_struct <- function( return(invisible(out)) } + # Paste + + pars <- lapply(pars, function(x) { + gsub(" ", "+", x) + }) + # First build the api address. If the passed nominatim_server does not end # with a trailing forward-slash, add one api <- prepare_api_url(nominatim_server, "search?") @@ -84,7 +86,7 @@ geo_lite_struct <- function( # Empty query if (nrow(result) == 0) { - message("No results for query ") + message("No results for query") out <- empty_tbl(tbl_query, lat, long) return(invisible(out)) } diff --git a/R/reverse_geo_lite.R b/R/reverse_geo_lite.R index 1eb0274a..2e6a3d38 100644 --- a/R/reverse_geo_lite.R +++ b/R/reverse_geo_lite.R @@ -203,7 +203,7 @@ reverse_geo_lite_single <- function(lat_cap, if (isFALSE(res)) { message(url, " not reachable.") - out <- empty_tbl(tbl_query, address) + out <- empty_tbl_rev(tbl_query, address) return(invisible(out)) } diff --git a/codemeta.json b/codemeta.json index 6db7e447..5f5c5d3b 100644 --- a/codemeta.json +++ b/codemeta.json @@ -128,6 +128,18 @@ }, "sameAs": "https://CRAN.R-project.org/package=testthat" }, + { + "@type": "SoftwareApplication", + "identifier": "tibble", + "name": "tibble", + "provider": { + "@id": "https://cran.r-project.org", + "@type": "Organization", + "name": "Comprehensive R Archive Network (CRAN)", + "url": "https://cran.r-project.org" + }, + "sameAs": "https://CRAN.R-project.org/package=tibble" + }, { "@type": "SoftwareApplication", "identifier": "tidygeocoder", @@ -208,7 +220,7 @@ }, "applicationCategory": "cartography", "keywords": ["r", "geocoding", "openstreetmap", "address", "nominatim", "reverse-geocoding", "rstats", "shapefile", "r-package", "spatial", "cran", "api-wrapper", "api", "gis"], - "fileSize": "193.155KB", + "fileSize": "209.186KB", "citation": [ { "@type": "SoftwareSourceCode", diff --git a/data/osm_amenities.rda b/data/osm_amenities.rda index dc89a2d5efe8b6b673b8b493d233465901f553eb..e3cb8ebfcaa62b4be0b43d7f7c187b1cdb1de466 100644 GIT binary patch literal 6522 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 literal 9814 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 Date: Fri, 5 Apr 2024 10:10:45 +0000 Subject: [PATCH 3/8] Add structured queries --- CITATION.cff | 3 + NAMESPACE | 2 + NEWS.md | 16 +- R/geo_address_lookup.R | 2 +- R/geo_lite.R | 2 +- R/geo_lite_sf.R | 8 +- R/geo_lite_struct.R | 58 +++- R/geo_lite_struct_sf.R | 177 +++++++++++ R/reverse_geo_lite.R | 2 +- R/utils.R | 6 +- README.md | 3 +- codemeta.json | 4 +- data/osm_amenities.rda | Bin 6522 -> 6116 bytes inst/WORDLIST | 2 + inst/schemaorg.json | 2 +- man/bbox_to_poly.Rd | 1 + man/figures/README-line-object-1.png | Bin 9197 -> 9158 bytes man/figures/README-pizzahut-1.png | Bin 9177 -> 9978 bytes man/figures/README-statue_liberty-1.png | Bin 8941 -> 8965 bytes man/geo_address_lookup.Rd | 4 +- man/geo_address_lookup_sf.Rd | 5 +- man/geo_lite.Rd | 4 +- man/geo_lite_sf.Rd | 13 +- man/geo_lite_struct.Rd | 106 +++++++ man/geo_lite_struct_sf.Rd | 144 +++++++++ man/reverse_geo_lite_sf.Rd | 3 +- tests/testthat/_snaps/geo_address_lookup.md | 2 +- tests/testthat/_snaps/geo_lite.md | 2 +- tests/testthat/_snaps/geo_lite_struct.md | 2 +- tests/testthat/_snaps/geo_lite_struct_sf.md | 7 + tests/testthat/_snaps/reverse_geo_lite.md | 2 +- tests/testthat/test-geo_lite_struct_sf.R | 143 +++++++++ vignettes/nominatimlite.Rmd | 328 ++++++++++---------- 33 files changed, 859 insertions(+), 194 deletions(-) create mode 100644 R/geo_lite_struct_sf.R create mode 100644 man/geo_lite_struct.Rd create mode 100644 man/geo_lite_struct_sf.Rd create mode 100644 tests/testthat/_snaps/geo_lite_struct_sf.md create mode 100644 tests/testthat/test-geo_lite_struct_sf.R diff --git a/CITATION.cff b/CITATION.cff index 0f5289a2..d4a61666 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -105,6 +105,9 @@ references: email: jeroen@berkeley.edu orcid: https://orcid.org/0000-0002-4035-0289 year: '2024' + identifiers: + - type: url + value: https://arxiv.org/abs/1403.2805 version: '>= 1.7.0' - type: software title: lifecycle diff --git a/NAMESPACE b/NAMESPACE index 6c64dc25..a199ffdb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,6 +7,8 @@ export(geo_amenity) export(geo_amenity_sf) export(geo_lite) export(geo_lite_sf) +export(geo_lite_struct) +export(geo_lite_struct_sf) export(nominatim_check_access) export(reverse_geo_lite) export(reverse_geo_lite_sf) diff --git a/NEWS.md b/NEWS.md index b36626e6..a1ab05bc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,14 +1,28 @@ # nominatimlite (development version) +- New functions: + + - `geo_lite_struct()` and `geo_lite_struct_sf()` for performing structured + queries. + - It is possible to use **nominatimlite** with local server thanks to the new argument `nominatim_server` (#42 \@alexwhitedatamine). - Adapt endpoints to **Nominatim v4.4.0** `[Python-only]`. +- `nominatimlite::osm_amenities` data set re-introduced, updated and with + additional description fields. + +- API call for non-spatial function uses now JSONV2 format (`&format=jsonv2`). + This implies the following changes in the output: + + - `class` renamed to `category`. + - additional field `place_rank` with the search rank of the object. + - `custom_query` argument can use vectors and `logical`: ``` r - geo_lite(address = "New York", + geo_lite(address = "New York", custom_query = list(addressdetails = TRUE, viewbox = c(-60, -20, 60, 20)) ) diff --git a/R/geo_address_lookup.R b/R/geo_address_lookup.R index 75e95aa7..f2469648 100644 --- a/R/geo_address_lookup.R +++ b/R/geo_address_lookup.R @@ -64,7 +64,7 @@ geo_address_lookup <- function(osm_ids, nodes <- paste0(type, osm_ids, collapse = ",") # Compose url - url <- paste0(api, "osm_ids=", nodes, "&format=json") + url <- paste0(api, "osm_ids=", nodes, "&format=jsonv2") if (full_results) url <- paste0(url, "&addressdetails=1") diff --git a/R/geo_lite.R b/R/geo_lite.R index a28a8fc9..4e8f6634 100644 --- a/R/geo_lite.R +++ b/R/geo_lite.R @@ -136,7 +136,7 @@ geo_lite_single <- function(address, address2 <- gsub(" ", "+", address) # Compose url - url <- paste0(api, address2, "&format=json&limit=", limit) + url <- paste0(api, address2, "&format=jsonv2&limit=", limit) if (full_results) url <- paste0(url, "&addressdetails=1") diff --git a/R/geo_lite_sf.R b/R/geo_lite_sf.R index 2e91b01e..e7882476 100644 --- a/R/geo_lite_sf.R +++ b/R/geo_lite_sf.R @@ -3,7 +3,7 @@ #' @description #' This function allows you to geocode addresses and return the corresponding #' spatial object. This function returns the spatial object associated with the -#' query using \CRANpkg{sf}, see [geo_lite_sf()] for retrieving the data in +#' query using \CRANpkg{sf}, see [geo_lite()] for retrieving the data in #' [`tibble`][tibble::tibble] format. #' #' This function correspond to the **free-form query** search described in the @@ -75,13 +75,13 @@ #' } #' # Several results #' -#' Madrid <- geo_lite_sf("Comunidad de Madrid, Spain", +#' madrid <- geo_lite_sf("Comunidad de Madrid, Spain", #' limit = 2, #' points_only = FALSE, full_results = TRUE #' ) #' -#' if (any(!sf::st_is_empty(Madrid))) { -#' ggplot(Madrid) + +#' if (any(!sf::st_is_empty(madrid))) { +#' ggplot(madrid) + #' geom_sf(fill = NA) #' } #' } diff --git a/R/geo_lite_struct.R b/R/geo_lite_struct.R index dd5e6f4c..8d8b4772 100644 --- a/R/geo_lite_struct.R +++ b/R/geo_lite_struct.R @@ -1,3 +1,59 @@ +#' Address search API for OSM elements (structured query) +#' +#' @description +#' Geocodes addresses already split into components. This function returns the +#' [`tibble`][tibble::tibble] associated with the query, see +#' [geo_lite_struct_sf()] for retrieving the data as a spatial object +#' ([`sf`][sf::st_sf] format). +#' +#' This function correspond to the **structured query** search described in the +#' [API endpoint](https://nominatim.org/release-docs/develop/api/Search/). For +#' performing a free-form search use [geo_lite()]. +#' +#' @family geocoding +#' +#' @param amenity Name and/or type of POI, see also [geo_amenity]. +#' @param street House number and street name. +#' @param city City. +#' @param county County. +#' @param state State. +#' @param country Country. +#' @param postalcode Postal Code. +#' @inheritParams geo_lite +#' +#' +#' @details +#' +#' The structured form of the search query allows to look up up an address that +#' is already split into its components. Each parameter represents a field of +#' the address. All parameters are optional. You should only use the ones that +#' are relevant for the address you want to geocode. +#' +#' +#' See for additional +#' parameters to be passed to `custom_query`. +#' +#' @return +#' +#' ```{r child = "man/chunks/tibbleout.Rmd"} +#' ``` +#' +#' +#' @seealso +#' [geo_lite_struct_sf()], [tidygeocoder::geo()]. +#' +#' @export +#' +#' @examplesIf nominatim_check_access() +#' \donttest{ +#' pl_mayor <- geo_lite_struct( +#' street = "Plaza Mayor", country = "Spain", +#' limit = 50, full_results = TRUE +#' ) +#' +#' +#' dplyr::glimpse(pl_mayor) +#' } geo_lite_struct <- function( amenity = NULL, street = NULL, city = NULL, county = NULL, state = NULL, country = NULL, postalcode = NULL, lat = "lat", long = "lon", limit = 1, @@ -49,7 +105,7 @@ geo_lite_struct <- function( # with a trailing forward-slash, add one api <- prepare_api_url(nominatim_server, "search?") # Compose url - url <- paste0(api, "format=json&limit=", limit) + url <- paste0(api, "format=jsonv2&limit=", limit) if (full_results) url <- paste0(url, "&addressdetails=1") diff --git a/R/geo_lite_struct_sf.R b/R/geo_lite_struct_sf.R new file mode 100644 index 00000000..ca6cacc5 --- /dev/null +++ b/R/geo_lite_struct_sf.R @@ -0,0 +1,177 @@ +#' Address search API for OSM elements in \CRANpkg{sf} format (structured query) +#' +#' @description +#' Geocodes addresses already split into components and return the corresponding +#' spatial object. This function returns the spatial object associated with the +#' query using \CRANpkg{sf}, see [geo_lite_struct()] for retrieving the data in +#' [`tibble`][tibble::tibble] format. +#' +#' This function correspond to the **structured query** search described in the +#' [API endpoint](https://nominatim.org/release-docs/develop/api/Search/). For +#' performing a free-form search use [geo_lite_sf()]. +#' +#' @family geocoding +#' @family spatial +#' +#' @inheritParams geo_lite_struct +#' @inheritParams geo_lite_sf +#' +#' @details +#' +#' The structured form of the search query allows to look up up an address that +#' is already split into its components. Each parameter represents a field of +#' the address. All parameters are optional. You should only use the ones that +#' are relevant for the address you want to geocode. +#' +#' +#' See for additional +#' parameters to be passed to `custom_query`. +#' +#' @inheritSection geo_lite_sf About Geometry Types +#' +#' @return +#' +#' ```{r child = "man/chunks/sfout.Rmd"} +#' ``` +#' +#' @seealso +#' [geo_lite_struct()]. +#' +#' @export +#' +#' @examplesIf nominatim_check_access() +#' \donttest{ +#' # Map +#' +#' pl_mayor <- geo_lite_struct_sf( +#' street = "Plaza Mayor", +#' county = "Comunidad de Madrid", +#' country = "Spain", limit = 50, +#' full_results = TRUE, verbose = TRUE +#' ) +#' +#' # Outline +#' ccaa <- geo_lite_sf("Comunidad de Madrid, Spain", points_only = FALSE) +#' +#' library(ggplot2) +#' +#' if (any(!sf::st_is_empty(pl_mayor), !sf::st_is_empty(ccaa))) { +#' ggplot(ccaa) + +#' geom_sf() + +#' geom_sf(data = pl_mayor, aes(shape = addresstype, color = addresstype)) +#' } +#' } +geo_lite_struct_sf <- function( + amenity = NULL, street = NULL, city = NULL, county = NULL, state = NULL, + country = NULL, postalcode = NULL, limit = 1, full_results = FALSE, + return_addresses = TRUE, verbose = FALSE, + nominatim_server = "https://nominatim.openstreetmap.org/", + custom_query = list(), points_only = TRUE) { + if (limit > 50) { + message(paste( + "Nominatim provides 50 results as a maximum. ", + "Your query may be incomplete" + )) + limit <- min(50, limit) + } + + # Check params, not vectorized + pars <- list( + amenity = amenity[1], + street = street[1], + city = city[1], + county = county[1], + state = state[1], + country = country[1], + postalcode = postalcode[1] + ) + + pars <- lapply(pars, function(x) { + if (is.null(x)) { + return(NA_character_) + } + a_char <- as.character(x) + a_char + }) + + tbl_query <- dplyr::as_tibble(pars) + names(tbl_query) <- paste0("q_", names(tbl_query)) + + if (all(is.na(pars))) { + message("Nothing to search for.") + out <- empty_sf(tbl_query) + return(invisible(out)) + } + + # Paste + + pars <- lapply(pars, function(x) { + gsub(" ", "+", x) + }) + + # First build the api address. If the passed nominatim_server does not end + # with a trailing forward-slash, add one + api <- prepare_api_url(nominatim_server, "search?") + # Compose url + url <- paste0(api, "format=geojson&limit=", limit) + + if (full_results) url <- paste0(url, "&addressdetails=1") + if (!isTRUE(points_only)) url <- paste0(url, "&polygon_geojson=1") + + # Clean and add options + newopts <- c(pars, custom_query) + + logis <- vapply(newopts, function(x) { + any(is.na(x), is.null(x)) + }, FUN.VALUE = logical(1)) + + + newopts <- newopts[!logis] + url <- add_custom_query(newopts, url) + + # Download to temp file + json <- tempfile(fileext = ".geojson") + res <- api_call(url, json, isFALSE(verbose)) + + # Step 2: Read and parse results ---- + if (isFALSE(res)) { + message(url, " not reachable.") + out <- empty_sf(tbl_query) + return(invisible(out)) + } + + # Read + sfobj <- sf::read_sf(json, stringsAsFactors = FALSE) + + # Empty query + if (length(names(sfobj)) == 1) { + message("No results for query") + out <- empty_sf(tbl_query) + return(invisible(out)) + } + + # Prepare output + + # Unnest address + sfobj <- unnest_sf(sfobj) + + + + # Prepare output + sf_clean <- sfobj + + # Naming order + sf_clean <- dplyr::bind_cols( + sf_clean, + tbl_query[rep(1, nrow(sf_clean)), ] + ) + + # Keep names + result_out <- keep_names(sf_clean, return_addresses, full_results, + colstokeep = names(tbl_query) + ) + + # Attach as tibble + result_out <- sf_to_tbl(result_out) + + result_out +} diff --git a/R/reverse_geo_lite.R b/R/reverse_geo_lite.R index 2e6a3d38..c5a89e2d 100644 --- a/R/reverse_geo_lite.R +++ b/R/reverse_geo_lite.R @@ -181,7 +181,7 @@ reverse_geo_lite_single <- function(lat_cap, api <- prepare_api_url(nominatim_server, "reverse?") # Compose url - url <- paste0(api, "lat=", lat_cap, "&lon=", long_cap, "&format=json") + url <- paste0(api, "lat=", lat_cap, "&lon=", long_cap, "&format=jsonv2") if (isFALSE(full_results)) { url <- paste0(url, "&addressdetails=0") diff --git a/R/utils.R b/R/utils.R index a428496a..849161ed 100644 --- a/R/utils.R +++ b/R/utils.R @@ -38,9 +38,9 @@ is_named <- function(x) { keep_names <- function(x, return_addresses, full_results, colstokeep = "query") { - names(x) <- gsub("address.", "", names(x)) - names(x) <- gsub("namedetails.", "", names(x)) - names(x) <- gsub("display_name", "address", names(x)) + names(x) <- gsub("address.", "", names(x), fixed = TRUE) + names(x) <- gsub("namedetails.", "", names(x), fixed = TRUE) + names(x) <- gsub("display_name", "address", names(x), fixed = TRUE) out_cols <- colstokeep if (return_addresses) out_cols <- c(out_cols, "address") diff --git a/README.md b/README.md index de723649..ff4d3a59 100644 --- a/README.md +++ b/README.md @@ -223,8 +223,7 @@ A BibTeX entry for LaTeX users is ## References -
+
diff --git a/codemeta.json b/codemeta.json index 5f5c5d3b..c3e4775f 100644 --- a/codemeta.json +++ b/codemeta.json @@ -14,7 +14,7 @@ "name": "R", "url": "https://r-project.org" }, - "runtimePlatform": "R version 4.3.3 (2024-02-29 ucrt)", + "runtimePlatform": "R version 4.3.3 (2024-02-29)", "provider": { "@id": "https://cran.r-project.org", "@type": "Organization", @@ -220,7 +220,7 @@ }, "applicationCategory": "cartography", "keywords": ["r", "geocoding", "openstreetmap", "address", "nominatim", "reverse-geocoding", "rstats", "shapefile", "r-package", "spatial", "cran", "api-wrapper", "api", "gis"], - "fileSize": "209.186KB", + "fileSize": "223.845KB", "citation": [ { "@type": "SoftwareSourceCode", diff --git a/data/osm_amenities.rda b/data/osm_amenities.rda index e3cb8ebfcaa62b4be0b43d7f7c187b1cdb1de466..dc89a2d5efe8b6b673b8b493d233465901f553eb 100644 GIT binary patch literal 6116 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 B;2!`0 diff --git a/man/figures/README-pizzahut-1.png b/man/figures/README-pizzahut-1.png index 14f2115781eb4f372238195a69f832bca27d3bfd..aa70f2d259e2d86141be2f3ae5f577cd9e5b5032 100644 GIT binary patch literal 9978 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 literal 9177 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 diff --git a/man/figures/README-statue_liberty-1.png b/man/figures/README-statue_liberty-1.png index 6dc7fe45236c5beaecaf6a7446e2aee5a4560b10..be12c007d4c7625bbb246da768590c7c6ac7382b 100644 GIT binary patch literal 8965 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 literal 8941 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 diff --git a/man/geo_address_lookup.Rd b/man/geo_address_lookup.Rd index 0d8e9164..c0fe4c7a 100644 --- a/man/geo_address_lookup.Rd +++ b/man/geo_address_lookup.Rd @@ -77,7 +77,9 @@ Address Lookup API: Geocoding: \code{\link{geo_address_lookup_sf}()}, \code{\link{geo_lite}()}, -\code{\link{geo_lite_sf}()} +\code{\link{geo_lite_sf}()}, +\code{\link{geo_lite_struct}()}, +\code{\link{geo_lite_struct_sf}()} } \concept{geocoding} \concept{lookup} diff --git a/man/geo_address_lookup_sf.Rd b/man/geo_address_lookup_sf.Rd index 47f2a504..658d5e4e 100644 --- a/man/geo_address_lookup_sf.Rd +++ b/man/geo_address_lookup_sf.Rd @@ -117,11 +117,14 @@ Address Lookup API: Geocoding: \code{\link{geo_address_lookup}()}, \code{\link{geo_lite}()}, -\code{\link{geo_lite_sf}()} +\code{\link{geo_lite_sf}()}, +\code{\link{geo_lite_struct}()}, +\code{\link{geo_lite_struct_sf}()} Get \code{\link[sf:sf]{sf}} objects: \code{\link{bbox_to_poly}()}, \code{\link{geo_lite_sf}()}, +\code{\link{geo_lite_struct_sf}()}, \code{\link{reverse_geo_lite_sf}()} } \concept{geocoding} diff --git a/man/geo_lite.Rd b/man/geo_lite.Rd index 2dd1df67..43f72d01 100644 --- a/man/geo_lite.Rd +++ b/man/geo_lite.Rd @@ -83,6 +83,8 @@ geo_lite(c("Madrid", "Barcelona"), Geocoding: \code{\link{geo_address_lookup}()}, \code{\link{geo_address_lookup_sf}()}, -\code{\link{geo_lite_sf}()} +\code{\link{geo_lite_sf}()}, +\code{\link{geo_lite_struct}()}, +\code{\link{geo_lite_struct_sf}()} } \concept{geocoding} diff --git a/man/geo_lite_sf.Rd b/man/geo_lite_sf.Rd index 03d3fdf0..17ad8367 100644 --- a/man/geo_lite_sf.Rd +++ b/man/geo_lite_sf.Rd @@ -51,7 +51,7 @@ A \code{\link[sf:sf]{sf}} object with the results. \description{ This function allows you to geocode addresses and return the corresponding spatial object. This function returns the spatial object associated with the -query using \CRANpkg{sf}, see \code{\link[=geo_lite_sf]{geo_lite_sf()}} for retrieving the data in +query using \CRANpkg{sf}, see \code{\link[=geo_lite]{geo_lite()}} for retrieving the data in \code{\link[tibble:tibble]{tibble}} format. This function correspond to the \strong{free-form query} search described in the @@ -104,13 +104,13 @@ if (any(!sf::st_is_empty(sol_poly))) { } # Several results -Madrid <- geo_lite_sf("Comunidad de Madrid, Spain", +madrid <- geo_lite_sf("Comunidad de Madrid, Spain", limit = 2, points_only = FALSE, full_results = TRUE ) -if (any(!sf::st_is_empty(Madrid))) { - ggplot(Madrid) + +if (any(!sf::st_is_empty(madrid))) { + ggplot(madrid) + geom_sf(fill = NA) } } @@ -122,11 +122,14 @@ if (any(!sf::st_is_empty(Madrid))) { Geocoding: \code{\link{geo_address_lookup}()}, \code{\link{geo_address_lookup_sf}()}, -\code{\link{geo_lite}()} +\code{\link{geo_lite}()}, +\code{\link{geo_lite_struct}()}, +\code{\link{geo_lite_struct_sf}()} Get \code{\link[sf:sf]{sf}} objects: \code{\link{bbox_to_poly}()}, \code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_lite_struct_sf}()}, \code{\link{reverse_geo_lite_sf}()} } \concept{geocoding} diff --git a/man/geo_lite_struct.Rd b/man/geo_lite_struct.Rd new file mode 100644 index 00000000..5fd02a83 --- /dev/null +++ b/man/geo_lite_struct.Rd @@ -0,0 +1,106 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/geo_lite_struct.R +\name{geo_lite_struct} +\alias{geo_lite_struct} +\title{Address search API for OSM elements (structured query)} +\usage{ +geo_lite_struct( + amenity = NULL, + street = NULL, + city = NULL, + county = NULL, + state = NULL, + country = NULL, + postalcode = NULL, + lat = "lat", + long = "lon", + limit = 1, + full_results = FALSE, + return_addresses = TRUE, + verbose = FALSE, + nominatim_server = "https://nominatim.openstreetmap.org/", + custom_query = list() +) +} +\arguments{ +\item{amenity}{Name and/or type of POI, see also \link{geo_amenity}.} + +\item{street}{House number and street name.} + +\item{city}{City.} + +\item{county}{County.} + +\item{state}{State.} + +\item{country}{Country.} + +\item{postalcode}{Postal Code.} + +\item{lat}{Latitude column name in the output data (default \code{"lat"}).} + +\item{long}{Longitude column name in the output data (default \code{"long"}).} + +\item{limit}{Maximum number of results to return per input address. Note +that each query returns a maximum of 50 results.} + +\item{full_results}{Returns all available data from the API service. +If \code{FALSE} (default) only latitude, longitude and address columns are +returned. See also \code{return_addresses}.} + +\item{return_addresses}{Return input addresses with results if \code{TRUE}.} + +\item{verbose}{If \code{TRUE} then detailed logs are output to the console.} + +\item{nominatim_server}{The URL of the Nominatim server to use. +Defaults to \code{"https://nominatim.openstreetmap.org/"}.} + +\item{custom_query}{A named list with API-specific parameters to be used +(i.e. \code{list(countrycodes = "US")}). See \strong{Details}.} +} +\value{ +A \code{\link[tibble:tibble]{tibble}} with the results found by the query. +} +\description{ +Geocodes addresses already split into components. This function returns the +\code{\link[tibble:tibble]{tibble}} associated with the query, see +\code{\link[=geo_lite_struct_sf]{geo_lite_struct_sf()}} for retrieving the data as a spatial object +(\code{\link[sf:sf]{sf}} format). + +This function correspond to the \strong{structured query} search described in the +\href{https://nominatim.org/release-docs/develop/api/Search/}{API endpoint}. For +performing a free-form search use \code{\link[=geo_lite]{geo_lite()}}. +} +\details{ +The structured form of the search query allows to look up up an address that +is already split into its components. Each parameter represents a field of +the address. All parameters are optional. You should only use the ones that +are relevant for the address you want to geocode. + +See \url{https://nominatim.org/release-docs/latest/api/Search/} for additional +parameters to be passed to \code{custom_query}. +} +\examples{ +\dontshow{if (nominatim_check_access()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\donttest{ +pl_mayor <- geo_lite_struct( + street = "Plaza Mayor", country = "Spain", + limit = 50, full_results = TRUE +) + + +dplyr::glimpse(pl_mayor) +} +\dontshow{\}) # examplesIf} +} +\seealso{ +\code{\link[=geo_lite_struct_sf]{geo_lite_struct_sf()}}, \code{\link[tidygeocoder:geo]{tidygeocoder::geo()}}. + +Geocoding: +\code{\link{geo_address_lookup}()}, +\code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_lite}()}, +\code{\link{geo_lite_sf}()}, +\code{\link{geo_lite_struct_sf}()} +} +\concept{geocoding} diff --git a/man/geo_lite_struct_sf.Rd b/man/geo_lite_struct_sf.Rd new file mode 100644 index 00000000..817c8509 --- /dev/null +++ b/man/geo_lite_struct_sf.Rd @@ -0,0 +1,144 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/geo_lite_struct_sf.R +\name{geo_lite_struct_sf} +\alias{geo_lite_struct_sf} +\title{Address search API for OSM elements in \CRANpkg{sf} format (structured query)} +\usage{ +geo_lite_struct_sf( + amenity = NULL, + street = NULL, + city = NULL, + county = NULL, + state = NULL, + country = NULL, + postalcode = NULL, + limit = 1, + full_results = FALSE, + return_addresses = TRUE, + verbose = FALSE, + nominatim_server = "https://nominatim.openstreetmap.org/", + custom_query = list(), + points_only = TRUE +) +} +\arguments{ +\item{amenity}{Name and/or type of POI, see also \link{geo_amenity}.} + +\item{street}{House number and street name.} + +\item{city}{City.} + +\item{county}{County.} + +\item{state}{State.} + +\item{country}{Country.} + +\item{postalcode}{Postal Code.} + +\item{limit}{Maximum number of results to return per input address. Note +that each query returns a maximum of 50 results.} + +\item{full_results}{Returns all available data from the API service. +If \code{FALSE} (default) only latitude, longitude and address columns are +returned. See also \code{return_addresses}.} + +\item{return_addresses}{Return input addresses with results if \code{TRUE}.} + +\item{verbose}{If \code{TRUE} then detailed logs are output to the console.} + +\item{nominatim_server}{The URL of the Nominatim server to use. +Defaults to \code{"https://nominatim.openstreetmap.org/"}.} + +\item{custom_query}{A named list with API-specific parameters to be used +(i.e. \code{list(countrycodes = "US")}). See \strong{Details}.} + +\item{points_only}{Logical \code{TRUE/FALSE}. Whether to return only spatial +points (\code{TRUE}, which is the default) or potentially other shapes as +provided by the Nominatim API (\code{FALSE}). See \strong{About Geometry Types}.} +} +\value{ +A \code{\link[sf:sf]{sf}} object with the results. +} +\description{ +Geocodes addresses already split into components and return the corresponding +spatial object. This function returns the spatial object associated with the +query using \CRANpkg{sf}, see \code{\link[=geo_lite_struct]{geo_lite_struct()}} for retrieving the data in +\code{\link[tibble:tibble]{tibble}} format. + +This function correspond to the \strong{structured query} search described in the +\href{https://nominatim.org/release-docs/develop/api/Search/}{API endpoint}. For +performing a free-form search use \code{\link[=geo_lite_sf]{geo_lite_sf()}}. +} +\details{ +The structured form of the search query allows to look up up an address that +is already split into its components. Each parameter represents a field of +the address. All parameters are optional. You should only use the ones that +are relevant for the address you want to geocode. + +See \url{https://nominatim.org/release-docs/latest/api/Search/} for additional +parameters to be passed to \code{custom_query}. +} +\section{About Geometry Types}{ + + +The parameter \code{points_only} specifies whether the function results will be +points (all Nominatim results are guaranteed to have at least point +geometry) or possibly other spatial objects. + +Note that the type of geometry returned in case of \code{points_only = FALSE} +will depend on the object being geocoded: +\itemize{ +\item Administrative areas, major buildings and the like will be +returned as polygons. +\item Rivers, roads and their like as lines. +\item Amenities may be points even in case of a \code{points_only = FALSE} call. +} + +The function is vectorized, allowing for multiple addresses to be geocoded; +in case of \code{points_only = FALSE} multiple geometry types may be returned. +} + +\examples{ +\dontshow{if (nominatim_check_access()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\donttest{ +# Map + +pl_mayor <- geo_lite_struct_sf( + street = "Plaza Mayor", + county = "Comunidad de Madrid", + country = "Spain", limit = 50, + full_results = TRUE, verbose = TRUE +) + +# Outline +ccaa <- geo_lite_sf("Comunidad de Madrid, Spain", points_only = FALSE) + +library(ggplot2) + +if (any(!sf::st_is_empty(pl_mayor), !sf::st_is_empty(ccaa))) { + ggplot(ccaa) + + geom_sf() + + geom_sf(data = pl_mayor, aes(shape = addresstype, color = addresstype)) +} +} +\dontshow{\}) # examplesIf} +} +\seealso{ +\code{\link[=geo_lite_struct]{geo_lite_struct()}}. + +Geocoding: +\code{\link{geo_address_lookup}()}, +\code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_lite}()}, +\code{\link{geo_lite_sf}()}, +\code{\link{geo_lite_struct}()} + +Get \code{\link[sf:sf]{sf}} objects: +\code{\link{bbox_to_poly}()}, +\code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_lite_sf}()}, +\code{\link{reverse_geo_lite_sf}()} +} +\concept{geocoding} +\concept{spatial} diff --git a/man/reverse_geo_lite_sf.Rd b/man/reverse_geo_lite_sf.Rd index bafd8a83..2a0f6d1e 100644 --- a/man/reverse_geo_lite_sf.Rd +++ b/man/reverse_geo_lite_sf.Rd @@ -148,7 +148,8 @@ Reverse geocoding coordinates: Get \code{\link[sf:sf]{sf}} objects: \code{\link{bbox_to_poly}()}, \code{\link{geo_address_lookup_sf}()}, -\code{\link{geo_lite_sf}()} +\code{\link{geo_lite_sf}()}, +\code{\link{geo_lite_struct_sf}()} } \concept{reverse} \concept{spatial} diff --git a/tests/testthat/_snaps/geo_address_lookup.md b/tests/testthat/_snaps/geo_address_lookup.md index 85d3256e..461e6b00 100644 --- a/tests/testthat/_snaps/geo_address_lookup.md +++ b/tests/testthat/_snaps/geo_address_lookup.md @@ -4,5 +4,5 @@ several <- geo_address_lookup(vector_ids, vector_type, full_results = TRUE, nominatim_server = "https://xyz.com/") Message - https://xyz.com/lookup?osm_ids=R146656,N240109189&format=json&addressdetails=1 not reachable. + https://xyz.com/lookup?osm_ids=R146656,N240109189&format=jsonv2&addressdetails=1 not reachable. diff --git a/tests/testthat/_snaps/geo_lite.md b/tests/testthat/_snaps/geo_lite.md index b2867223..633ef0a5 100644 --- a/tests/testthat/_snaps/geo_lite.md +++ b/tests/testthat/_snaps/geo_lite.md @@ -3,5 +3,5 @@ Code several <- geo_lite("Madrid", full_results = TRUE, nominatim_server = "https://xyz.com/") Message - https://xyz.com/search?q=Madrid&format=json&limit=1&addressdetails=1 not reachable. + https://xyz.com/search?q=Madrid&format=jsonv2&limit=1&addressdetails=1 not reachable. diff --git a/tests/testthat/_snaps/geo_lite_struct.md b/tests/testthat/_snaps/geo_lite_struct.md index 6268ae30..75314f71 100644 --- a/tests/testthat/_snaps/geo_lite_struct.md +++ b/tests/testthat/_snaps/geo_lite_struct.md @@ -3,5 +3,5 @@ Code several <- geo_lite_struct("Madrid", full_results = TRUE, nominatim_server = "https://xyz.com/") Message - https://xyz.com/search?format=json&limit=1&addressdetails=1&amenity=Madrid not reachable. + https://xyz.com/search?format=jsonv2&limit=1&addressdetails=1&amenity=Madrid not reachable. diff --git a/tests/testthat/_snaps/geo_lite_struct_sf.md b/tests/testthat/_snaps/geo_lite_struct_sf.md new file mode 100644 index 00000000..a33c66e7 --- /dev/null +++ b/tests/testthat/_snaps/geo_lite_struct_sf.md @@ -0,0 +1,7 @@ +# Fail + + Code + several <- geo_lite_struct_sf("madrid", full_results = TRUE, nominatim_server = "https://xyz.com/") + Message + https://xyz.com/search?format=geojson&limit=1&addressdetails=1&amenity=madrid not reachable. + diff --git a/tests/testthat/_snaps/reverse_geo_lite.md b/tests/testthat/_snaps/reverse_geo_lite.md index 8bcc26b2..e0acc520 100644 --- a/tests/testthat/_snaps/reverse_geo_lite.md +++ b/tests/testthat/_snaps/reverse_geo_lite.md @@ -4,5 +4,5 @@ several <- reverse_geo_lite(40.75728, -73.98, full_results = TRUE, nominatim_server = "https://xyz.com/") Message - https://xyz.com/reverse?lat=40.75728&lon=-73.98&format=json&addressdetails=1 not reachable. + https://xyz.com/reverse?lat=40.75728&lon=-73.98&format=jsonv2&addressdetails=1 not reachable. diff --git a/tests/testthat/test-geo_lite_struct_sf.R b/tests/testthat/test-geo_lite_struct_sf.R new file mode 100644 index 00000000..b0ac7fb6 --- /dev/null +++ b/tests/testthat/test-geo_lite_struct_sf.R @@ -0,0 +1,143 @@ +test_that("Returning empty query", { + skip_on_cran() + skip_if_api_server() + + expect_message( + obj <- geo_lite_struct_sf(), + "Nothing to search for" + ) + expect_s3_class(obj, "sf") + expect_true(sf::st_is_empty(obj)) + + + expect_message( + obj <- geo_lite_struct_sf("xbzbzbzoa aiaia"), + "No results for" + ) + expect_s3_class(obj, "sf") + expect_true(sf::st_is_empty(obj)) + + expect_true(nrow(obj) == 1) + expect_true(obj$q_amenity == "xbzbzbzoa aiaia") + expect_s3_class(obj, "sf") + expect_s3_class(obj, "tbl") + expect_true(sf::st_is_empty(obj)) + expect_identical(sf::st_crs(obj), sf::st_crs(4326)) +}) + + +test_that("Data format", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + obj <- geo_lite_struct_sf(city = "Madrid") + + expect_s3_class(obj, "sf") + expect_s3_class(obj, "tbl") + expect_equal(nrow(obj), 1) + expect_identical(as.character(obj$q_city), "Madrid") + expect_true(all(grepl("POINT", sf::st_geometry_type(obj)))) + + + # Polygon + + expect_message( + test <- geo_lite_struct_sf( + city = "Madrid", + points_only = FALSE, limit = 100 + ), + "Nominatim provides 50 results as a maximum" + ) + + + expect_true(any(grepl("POLYGON", sf::st_geometry_type(test)))) + expect_s3_class(test, "sf") + expect_s3_class(test, "tbl") + expect_gt(nrow(test), 2) + expect_identical(unique(as.character(test$q_city)), "Madrid") +}) + +test_that("Checking query", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + + expect_message( + obj <- geo_lite_struct_sf( + city = c("Madrid", "Barcelona"), + limit = 51 + ), "50 results" + ) + + expect_s3_class(obj, "sf") + expect_s3_class(obj, "tbl") + expect_identical(rev(names(obj))[1:2], rev(c("address", "geometry"))) + + obj_old <- obj + obj <- geo_lite_struct_sf("Madrid", + full_results = FALSE, + return_addresses = FALSE + ) + + expect_s3_class(obj, "sf") + expect_s3_class(obj, "tbl") + expect_false("address" %in% names(obj)) + + obj_old1 <- obj + obj <- geo_lite_struct_sf( + city = "Madrid", + full_results = FALSE, + return_addresses = TRUE + ) + + expect_s3_class(obj, "sf") + expect_s3_class(obj, "tbl") + expect_identical(rev(names(obj))[1:2], rev(c("address", "geometry"))) + + obj <- geo_lite_struct_sf( + city = "Madrid", + full_results = TRUE, + return_addresses = FALSE + ) + expect_s3_class(obj, "sf") + expect_s3_class(obj, "tbl") + expect_gt(ncol(obj), 4) + + + expect_gt( + nrow(geo_lite_struct_sf("restaurant", + limit = 50, + custom_query = list(countrycode = "es") + )), 4 + ) + + expect_equal( + nrow(geo_lite_struct_sf("school", + custom_query = list(countrycode = "es") + )), 1 + ) + + expect_equal( + nrow(geo_lite_struct_sf("hospital", + custom_query = list(extratags = TRUE) + )), 1 + ) +}) + + +test_that("Fail", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + # KO + expect_snapshot(several <- geo_lite_struct_sf( + "madrid", + full_results = TRUE, + nominatim_server = "https://xyz.com/" + )) + + expect_true(all(sf::st_is_empty(several))) +}) diff --git a/vignettes/nominatimlite.Rmd b/vignettes/nominatimlite.Rmd index b7f50c9d..f4e64dbe 100644 --- a/vignettes/nominatimlite.Rmd +++ b/vignettes/nominatimlite.Rmd @@ -1,164 +1,164 @@ ---- -title: "Get started with nominatimlite" -output: rmarkdown::html_vignette -desc: > - Quick examples showing what **nominatimlite** can do for you. -vignette: > - %\VignetteIndexEntry{Get started with nominatimlite} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} -bibliography: references.bib -link-citations: true ---- - - - - - -The goal of **nominatimlite** is to provide a light interface for geocoding -addresses, based on the [Nominatim -API](https://nominatim.org/release-docs/latest/). It also allows to load spatial -objects using the **sf** package. - -Full site with examples and vignettes on - - -## What is Nominatim? - -**Nominatim** is a tool to search -[OpenStreetMap](https://www.openstreetmap.org/) data by name and address -([geocoding](https://wiki.openstreetmap.org/wiki/Geocoding "Geocoding")) and to -generate synthetic addresses of OSM points (reverse geocoding). - -## Why **nominatimlite**? - -The main goal of **nominatimlite** is to access the Nominatim API avoiding the -dependency on **curl**. In some situations, **curl** may not be available or -accessible, so **nominatimlite** uses base functions to overcome this -limitation. - -## Recommended packages - -There are other packages much more complete and mature than **nominatimlite**, -that presents similar features: - -- [**tidygeocoder**](https://jessecambon.github.io/tidygeocoder/) - [@R-tidygeocoder]: Allows to interface with Nominatim, Google, TomTom, - Mapbox, etc. for geocoding and reverse geocoding. -- [**osmdata**](https://docs.ropensci.org/osmdata/) [@R-osmdata]: Great for - downloading spatial data from OpenStreetMap, via the [Overpass - API](https://wiki.openstreetmap.org/wiki/Overpass_API). -- [**arcgeocoder**](https://dieghernan.github.io/arcgeocoder/) - [@R-arcgeocoder]: Lite interface for geocoding with the ArcGIS REST API - Service. - -## Usage - -### `sf` objects - -With **nominatimlite** you can extract spatial objects easily: - - -```r -library(nominatimlite) - -# Extract some points - Pizza Hut in California - -CA <- geo_lite_sf("California", points_only = FALSE) - -pizzahut <- geo_lite_sf("Pizza Hut, California", - limit = 50, - custom_query = list(countrycodes = "us") -) - -library(ggplot2) - -ggplot(CA) + - geom_sf() + - geom_sf(data = pizzahut, col = "red") -``` - -![Pizza Hut in California](../man/figures/README-pizzahut-1.png){width="100%"} - -You can also extract polygon and line objects (if available) using the option -`points_only = FALSE`: - - -```r -sol_poly <- geo_lite_sf("Statue of Liberty, NY, USA", points_only = FALSE) - -ggplot(sol_poly) + - geom_sf() -``` - -![The Statue of -Liberty](../man/figures/README-statue_liberty-1.png){width="100%"} - -### Geocoding and reverse geocoding - -*Note: examples adapted from **tidygeocoder** package* - -In this first example we will geocode a few addresses using the `geo_lite()` -function: - - -```r -library(tibble) - -# create a dataframe with addresses -some_addresses <- tribble( - ~name, ~addr, - "White House", "1600 Pennsylvania Ave NW, Washington, DC", - "Transamerica Pyramid", "600 Montgomery St, San Francisco, CA 94111", - "Willis Tower", "233 S Wacker Dr, Chicago, IL 60606" -) - -# geocode the addresses -lat_longs <- geo_lite(some_addresses$addr, lat = "latitude", long = "longitude") -#> | | | 0% | |================= | 33% | |================================= | 67% | |==================================================| 100% -``` - -Only latitude and longitude are returned from the geocoder service in this -example, but `full_results = TRUE` can be used to return all of the data from -the geocoder service. - - - -|query | latitude| longitude|address | -|:------------------------------------------|--------:|----------:|:-----------------------------------------------------------------------------------------------------------------| -|1600 Pennsylvania Ave NW, Washington, DC | 38.89770| -77.03655|White House, 1600, Pennsylvania Avenue Northwest, Ward 2, Washington, District of Columbia, 20500, United States | -|600 Montgomery St, San Francisco, CA 94111 | 37.79520| -122.40279|Transamerica Pyramid, 600, Montgomery Street, Financial District, San Francisco, California, 94111, United States | -|233 S Wacker Dr, Chicago, IL 60606 | 41.87874| -87.63596|Willis Tower, 233, South Wacker Drive, Printer's Row, Loop, Chicago, Cook County, Illinois, 60606, United States | - - - -To perform reverse geocoding (obtaining addresses from geographic coordinates), -we can use the `reverse_geo_lite()` function. The arguments are similar to the -`geo_lite()` function, but now we specify the input data columns with the `lat` -and `long` arguments. The dataset used here is from the geocoder query above. -The single line address is returned in a column named by the `address`. - - -```r -reverse <- reverse_geo_lite( - lat = lat_longs$latitude, long = lat_longs$longitude, - address = "address_found" -) -#> | | | 0% | |================= | 33% | |================================= | 67% | |==================================================| 100% -``` - - - -|address_found | lat| lon| -|:-----------------------------------------------------------------------------------------------------------------|--------:|----------:| -|White House, 1600, Pennsylvania Avenue Northwest, Ward 2, Washington, District of Columbia, 20500, United States | 38.89770| -77.03655| -|Transamerica Pyramid, 600, Montgomery Street, Financial District, San Francisco, California, 94111, United States | 37.79520| -122.40279| -|Willis Tower, 233, South Wacker Drive, Printer's Row, Loop, Chicago, Cook County, Illinois, 60606, United States | 41.87874| -87.63596| - - - -For more advance users, see [Nominatim -docs](https://nominatim.org/release-docs/latest/api/Search/) to check the -parameters available. - -## References +--- +title: "Get started with nominatimlite" +output: rmarkdown::html_vignette +desc: > + Quick examples showing what **nominatimlite** can do for you. +vignette: > + %\VignetteIndexEntry{Get started with nominatimlite} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +bibliography: references.bib +link-citations: true +--- + + + + + +The goal of **nominatimlite** is to provide a light interface for geocoding +addresses, based on the [Nominatim +API](https://nominatim.org/release-docs/latest/). It also allows to load spatial +objects using the **sf** package. + +Full site with examples and vignettes on + + +## What is Nominatim? + +**Nominatim** is a tool to search +[OpenStreetMap](https://www.openstreetmap.org/) data by name and address +([geocoding](https://wiki.openstreetmap.org/wiki/Geocoding "Geocoding")) and to +generate synthetic addresses of OSM points (reverse geocoding). + +## Why **nominatimlite**? + +The main goal of **nominatimlite** is to access the Nominatim API avoiding the +dependency on **curl**. In some situations, **curl** may not be available or +accessible, so **nominatimlite** uses base functions to overcome this +limitation. + +## Recommended packages + +There are other packages much more complete and mature than **nominatimlite**, +that presents similar features: + +- [**tidygeocoder**](https://jessecambon.github.io/tidygeocoder/) + [@R-tidygeocoder]: Allows to interface with Nominatim, Google, TomTom, + Mapbox, etc. for geocoding and reverse geocoding. +- [**osmdata**](https://docs.ropensci.org/osmdata/) [@R-osmdata]: Great for + downloading spatial data from OpenStreetMap, via the [Overpass + API](https://wiki.openstreetmap.org/wiki/Overpass_API). +- [**arcgeocoder**](https://dieghernan.github.io/arcgeocoder/) + [@R-arcgeocoder]: Lite interface for geocoding with the ArcGIS REST API + Service. + +## Usage + +### `sf` objects + +With **nominatimlite** you can extract spatial objects easily: + + +```r +library(nominatimlite) + +# Extract some points - Pizza Hut in California + +CA <- geo_lite_sf("California", points_only = FALSE) + +pizzahut <- geo_lite_sf("Pizza Hut, California", + limit = 50, + custom_query = list(countrycodes = "us") +) + +library(ggplot2) + +ggplot(CA) + + geom_sf() + + geom_sf(data = pizzahut, col = "red") +``` + +![Pizza Hut in California](../man/figures/README-pizzahut-1.png){width="100%"} + +You can also extract polygon and line objects (if available) using the option +`points_only = FALSE`: + + +```r +sol_poly <- geo_lite_sf("Statue of Liberty, NY, USA", points_only = FALSE) + +ggplot(sol_poly) + + geom_sf() +``` + +![The Statue of +Liberty](../man/figures/README-statue_liberty-1.png){width="100%"} + +### Geocoding and reverse geocoding + +*Note: examples adapted from **tidygeocoder** package* + +In this first example we will geocode a few addresses using the `geo_lite()` +function: + + +```r +library(tibble) + +# create a dataframe with addresses +some_addresses <- tribble( + ~name, ~addr, + "White House", "1600 Pennsylvania Ave NW, Washington, DC", + "Transamerica Pyramid", "600 Montgomery St, San Francisco, CA 94111", + "Willis Tower", "233 S Wacker Dr, Chicago, IL 60606" +) + +# geocode the addresses +lat_longs <- geo_lite(some_addresses$addr, lat = "latitude", long = "longitude") +#> | | | 0% | |================= | 33% | |================================= | 67% | |==================================================| 100% +``` + +Only latitude and longitude are returned from the geocoder service in this +example, but `full_results = TRUE` can be used to return all of the data from +the geocoder service. + + + +|query | latitude| longitude|address | +|:------------------------------------------|--------:|----------:|:-----------------------------------------------------------------------------------------------------------------| +|1600 Pennsylvania Ave NW, Washington, DC | 38.89770| -77.03655|White House, 1600, Pennsylvania Avenue Northwest, Ward 2, Washington, District of Columbia, 20500, United States | +|600 Montgomery St, San Francisco, CA 94111 | 37.79520| -122.40279|Transamerica Pyramid, 600, Montgomery Street, Financial District, San Francisco, California, 94111, United States | +|233 S Wacker Dr, Chicago, IL 60606 | 41.87874| -87.63596|Willis Tower, 233, South Wacker Drive, Printer's Row, Loop, Chicago, Cook County, Illinois, 60606, United States | + + + +To perform reverse geocoding (obtaining addresses from geographic coordinates), +we can use the `reverse_geo_lite()` function. The arguments are similar to the +`geo_lite()` function, but now we specify the input data columns with the `lat` +and `long` arguments. The dataset used here is from the geocoder query above. +The single line address is returned in a column named by the `address`. + + +```r +reverse <- reverse_geo_lite( + lat = lat_longs$latitude, long = lat_longs$longitude, + address = "address_found" +) +#> | | | 0% | |================= | 33% | |================================= | 67% | |==================================================| 100% +``` + + + +|address_found | lat| lon| +|:-----------------------------------------------------------------------------------------------------------------|--------:|----------:| +|White House, 1600, Pennsylvania Avenue Northwest, Ward 2, Washington, District of Columbia, 20500, United States | 38.89770| -77.03655| +|Transamerica Pyramid, 600, Montgomery Street, Financial District, San Francisco, California, 94111, United States | 37.79520| -122.40279| +|Willis Tower, 233, South Wacker Drive, Printer's Row, Loop, Chicago, Cook County, Illinois, 60606, United States | 41.87874| -87.63596| + + + +For more advance users, see [Nominatim +docs](https://nominatim.org/release-docs/latest/api/Search/) to check the +parameters available. + +## References From ed3a4aacaab79e2a4c3e6d48a85820dcfec2eb1e Mon Sep 17 00:00:00 2001 From: dieghernan Date: Mon, 8 Apr 2024 10:05:21 +0000 Subject: [PATCH 4/8] Bring back amenities function --- CITATION.cff | 30 +++--- DESCRIPTION | 2 +- NEWS.md | 3 + R/deprecated.R | 66 ------------ R/geo_amenity.R | 144 ++++++++++++++++++++++++++ R/geo_amenity_sf.R | 128 +++++++++++++++++++++++ R/geo_lite_sf.R | 2 +- README.Rmd | 2 +- README.md | 2 +- codemeta.json | 28 ++--- inst/WORDLIST | 3 + man/bbox_to_poly.Rd | 1 + man/figures/README-pizzahut-1.png | Bin 9978 -> 9968 bytes man/geo_address_lookup.Rd | 2 + man/geo_address_lookup_sf.Rd | 3 + man/geo_amenity.Rd | 128 +++++++++++++++++++---- man/geo_amenity_sf.Rd | 147 +++++++++++++++++++++++++++ man/geo_lite.Rd | 2 + man/geo_lite_sf.Rd | 5 +- man/geo_lite_struct.Rd | 2 + man/geo_lite_struct_sf.Rd | 3 + man/osm_amenities.Rd | 5 + man/reverse_geo_lite_sf.Rd | 1 + tests/testthat/_snaps/deprecated.md | 20 ---- tests/testthat/test-deprecated.R | 11 -- tests/testthat/test-geo_amenity.R | 113 ++++++++++++++++++++ tests/testthat/test-geo_amenity_sf.R | 110 ++++++++++++++++++++ 27 files changed, 810 insertions(+), 153 deletions(-) delete mode 100644 R/deprecated.R create mode 100644 R/geo_amenity.R create mode 100644 R/geo_amenity_sf.R create mode 100644 man/geo_amenity_sf.Rd delete mode 100644 tests/testthat/_snaps/deprecated.md delete mode 100644 tests/testthat/test-deprecated.R create mode 100644 tests/testthat/test-geo_amenity.R create mode 100644 tests/testthat/test-geo_amenity_sf.R diff --git a/CITATION.cff b/CITATION.cff index d4a61666..ae31f4c6 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -109,21 +109,6 @@ references: - type: url value: https://arxiv.org/abs/1403.2805 version: '>= 1.7.0' -- type: software - title: lifecycle - abstract: 'lifecycle: Manage the Life Cycle of your Package Functions' - notes: Imports - url: https://lifecycle.r-lib.org/ - repository: https://CRAN.R-project.org/package=lifecycle - authors: - - family-names: Henry - given-names: Lionel - email: lionel@posit.co - - family-names: Wickham - given-names: Hadley - email: hadley@posit.co - orcid: https://orcid.org/0000-0003-4757-117X - year: '2024' - type: software title: sf abstract: 'sf: Simple Features for R' @@ -211,6 +196,21 @@ references: email: xie@yihui.name orcid: https://orcid.org/0000-0003-0645-5666 year: '2024' +- type: software + title: lifecycle + abstract: 'lifecycle: Manage the Life Cycle of your Package Functions' + notes: Suggests + url: https://lifecycle.r-lib.org/ + repository: https://CRAN.R-project.org/package=lifecycle + authors: + - family-names: Henry + given-names: Lionel + email: lionel@posit.co + - family-names: Wickham + given-names: Hadley + email: hadley@posit.co + orcid: https://orcid.org/0000-0003-4757-117X + year: '2024' - type: software title: rmarkdown abstract: 'rmarkdown: Dynamic Documents for R' diff --git a/DESCRIPTION b/DESCRIPTION index b1bbc0f9..949b9bce 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -24,13 +24,13 @@ Depends: Imports: dplyr (>= 1.0.0), jsonlite (>= 1.7.0), - lifecycle, sf (>= 0.9.0), utils Suggests: arcgeocoder, ggplot2 (>= 3.0.0), knitr, + lifecycle, rmarkdown, testthat (>= 3.0.0), tibble, diff --git a/NEWS.md b/NEWS.md index a1ab05bc..3296a5d3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,9 @@ - `geo_lite_struct()` and `geo_lite_struct_sf()` for performing structured queries. + - Bring back `geo_amenity()` and `geo_amenity_sf()` as a wrapper of + `geo_lite_struct()` / `geo_lite_struct_sf(),` so now are more robust and + compatible with **sf** objects. - It is possible to use **nominatimlite** with local server thanks to the new argument `nominatim_server` (#42 \@alexwhitedatamine). diff --git a/R/deprecated.R b/R/deprecated.R deleted file mode 100644 index 63a0b11b..00000000 --- a/R/deprecated.R +++ /dev/null @@ -1,66 +0,0 @@ -#' Geocode amenities -#' -#' @description -#' `r lifecycle::badge("defunct")` -#' -#' This operation is not supported any more. Use -#' [arcgeocoder::arc_geo_categories()] instead. -#' -#' -#' @keywords internal -#' @name geo_amenity -#' @rdname geo_amenity -#' -#' @param bbox,... Deprecated -#' -#' @return An error. -#' -#' -#' @export -#' @examples -#' \donttest{ -#' # Madrid, Spain -#' -#' library(arcgeocoder) -#' library(ggplot2) -#' -#' bbox <- c(-3.888954, 40.311977, -3.517916, 40.643729) -#' -#' # Food -#' rest_pub <- arc_geo_categories( -#' bbox = bbox, category = "Bakery,Bar or Pub", -#' full_results = TRUE, -#' limit = 50 -#' ) -#' -#' rest_pub -#' } -geo_amenity <- function(bbox = NULL, ...) { - if (requireNamespace("lifecycle", quietly = TRUE)) { - lifecycle::deprecate_stop("0.3.0", "geo_amenity()", - with = "arcgeocoder::arc_geo_categories()", - details = paste( - "Operation not supported any", - "more by the Nominatim API." - ) - ) - } -} - -#' -#' @name geo_amenity_sf -#' @rdname geo_amenity -#' -#' @keywords internal -#' @export -geo_amenity_sf <- function(bbox = NULL, ...) { - if (requireNamespace("lifecycle", quietly = TRUE)) { - lifecycle::deprecate_stop("0.3.0", "geo_amenity_sf()", - with = "arcgeocoder::arc_geo_categories()", - details = paste( - "Operation not supported any", - "more by the Nominatim API." - ) - ) - } -} diff --git a/R/geo_amenity.R b/R/geo_amenity.R new file mode 100644 index 00000000..7d05fd82 --- /dev/null +++ b/R/geo_amenity.R @@ -0,0 +1,144 @@ +#' Geocode amenities +#' +#' @description +#' This function search [amenities][osm_amenities] as defined by OpenStreetMap +#' on a restricted area defined by a bounding box in the form +#' `(, , , )`. This function returns the +#' [`tibble`][tibble::tibble] associated with the query, see [geo_amenity_sf()] +#' for retrieving the data as a spatial object ([`sf`][sf::st_sf] format). +#' +#' @family amenity +#' @family geocoding +#' +#' @param bbox The bounding box (viewbox) used to limit the search. It could be: +#' - A numeric vector of **longitude** (`x`) and **latitude** (`y`) +#' `(xmin, ymin, xmax, ymax)`. See **Details**. +#' - A [`sf`][sf::st_sf] or [`sfc`][sf::st_sfc] object. +#' @param amenity A `character` (or a vector of `character`s) with the +#' amenities to be geolocated (i.e. `c("pub", "restaurant")`). See +#' [nominatimlite::osm_amenities]. +#' @param strict Logical `TRUE/FALSE`. Force the results to be included inside +#' the `bbox`. Note that Nominatim default behavior may return results located +#' outside the provided bounding box. +#' @inheritParams geo_lite_struct +#' @inheritParams geo_lite +#' +#' @details +#' +#' Bounding boxes can be located using different online tools, as +#' [Bounding Box Tool](https://boundingbox.klokantech.com/). +#' +#' For a full list of valid amenities see +#' and [osm_amenities]. +#' +#' See for additional +#' parameters to be passed to `custom_query`. +#' +#' +#' @return +#' +#' ```{r child = "man/chunks/tibbleout.Rmd"} +#' ``` +#' +#' @export +#' +#' @examplesIf nominatim_check_access() +#' \donttest{ +#' # Times Square, NY, USA +#' bbox <- c( +#' -73.9894467311, 40.75573629, +#' -73.9830630737, 40.75789245 +#' ) +#' +#' geo_amenity( +#' bbox = bbox, +#' amenity = "restaurant" +#' ) +#' +#' # Several amenities +#' geo_amenity( +#' bbox = bbox, +#' amenity = c("restaurant", "pub") +#' ) +#' +#' # Increase limit and use with strict +#' geo_amenity( +#' bbox = bbox, +#' amenity = c("restaurant", "pub"), +#' limit = 10, +#' strict = TRUE +#' ) +#' } +geo_amenity <- function( + bbox, amenity, lat = "lat", long = "lon", limit = 1, full_results = FALSE, + return_addresses = TRUE, verbose = FALSE, + nominatim_server = "https://nominatim.openstreetmap.org/", + progressbar = TRUE, custom_query = list(), strict = FALSE) { + if (limit > 50) { + message(paste( + "Nominatim provides 50 results as a maximum. ", + "Your query may be incomplete" + )) + limit <- min(50, limit) + } + + # bbox types + if (any(inherits(bbox, "sf"), inherits(bbox, "sfc"))) { + tolonlat <- sf::st_transform(bbox, 4326) + bbox <- as.vector(sf::st_bbox(tolonlat)) + } + bbox <- as.vector(bbox) + + # Overwrite custom query + custom_query <- as.list(custom_query) + custom_query$viewbox <- bbox + custom_query$bounded <- TRUE + + # Dedupe for query + key <- unique(amenity) + + # 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) + + seql <- seq(1, ntot, 1) + + all_res <- lapply(seql, function(x) { + ad <- key[x] + if (progressbar) { + setTxtProgressBar(pb, x) + } + + geo_lite_struct( + amenity = ad, lat = lat, long = long, limit = limit, + full_results = full_results, + return_addresses = return_addresses, verbose = verbose, + nominatim_server = nominatim_server, + custom_query = custom_query + ) + }) + if (progressbar) close(pb) + + all_res <- dplyr::bind_rows(all_res) + + # Clean columns and names + nm <- names(all_res) + nm[nm == "q_amenity"] <- "amenity" + names(all_res) <- nm + all_res <- all_res[, !grepl("^q_", nm)] + + if (strict) { + bbox_sf <- bbox_to_poly(bbox) + all_res_sf <- sf::st_as_sf(all_res, coords = c("lon", "lat"), crs = 4326) + + int <- as.vector(sf::st_intersects(all_res_sf, bbox_sf, sparse = FALSE)) + all_res <- all_res[int, ] + } + + return(all_res) +} diff --git a/R/geo_amenity_sf.R b/R/geo_amenity_sf.R new file mode 100644 index 00000000..a5c2967d --- /dev/null +++ b/R/geo_amenity_sf.R @@ -0,0 +1,128 @@ +#' Geocode amenities in \CRANpkg{sf} format +#' +#' @description +#' This function search [amenities][osm_amenities] as defined by OpenStreetMap +#' on a restricted area defined by a bounding box in the form +#' `(, , , )`. This function returns the spatial +#' object associated with the query using \CRANpkg{sf}, see [geo_amenity()] for +#' retrieving the data in [`tibble`][tibble::tibble] format. +#' +#' @family amenity +#' @family geocoding +#' @family spatial +#' +#' @inheritParams geo_amenity +#' @inheritParams geo_lite_sf +#' +#' @details +#' +#' Bounding boxes can be located using different online tools, as +#' [Bounding Box Tool](https://boundingbox.klokantech.com/). +#' +#' For a full list of valid amenities see +#' and [osm_amenities]. +#' +#' See for additional +#' parameters to be passed to `custom_query`. +#' +#' +#' @inheritSection geo_lite_sf About Geometry Types +#' +#' @return +#' +#' ```{r child = "man/chunks/sfout.Rmd"} +#' ``` +#' +#' @export +#' +#' @examplesIf nominatim_check_access() +#' \donttest{ +#' # Usera, Madrid +#' +#' library(ggplot2) +#' mad <- geo_lite_sf("Usera, Madrid, Spain", points_only = FALSE) +#' +#' +#' # Restaurants, pubs and schools +#' +#' rest_pub <- geo_amenity_sf(mad, c("restaurant", "pub", "school"), +#' limit = 50 +#' ) +#' +#' if (any(!sf::st_is_empty(rest_pub))) { +#' ggplot(mad) + +#' geom_sf() + +#' geom_sf(data = rest_pub, aes(color = amenity, shape = amenity)) +#' } +#' } +geo_amenity_sf <- function( + bbox, amenity, limit = 1, full_results = FALSE, + return_addresses = TRUE, verbose = FALSE, + nominatim_server = "https://nominatim.openstreetmap.org/", + progressbar = TRUE, custom_query = list(), strict = FALSE, + points_only = TRUE) { + if (limit > 50) { + message(paste( + "Nominatim provides 50 results as a maximum. ", + "Your query may be incomplete" + )) + limit <- min(50, limit) + } + + # bbox types + if (any(inherits(bbox, "sf"), inherits(bbox, "sfc"))) { + tolonlat <- sf::st_transform(bbox, 4326) + bbox <- as.vector(sf::st_bbox(tolonlat)) + } + bbox <- as.vector(bbox) + + # Overwrite custom query + custom_query <- as.list(custom_query) + custom_query$viewbox <- bbox + custom_query$bounded <- TRUE + + # Dedupe for query + key <- unique(amenity) + + # 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) + + seql <- seq(1, ntot, 1) + + all_res <- lapply(seql, function(x) { + ad <- key[x] + if (progressbar) { + setTxtProgressBar(pb, x) + } + + geo_lite_struct_sf( + amenity = ad, limit = limit, full_results = full_results, + return_addresses = return_addresses, verbose = verbose, + nominatim_server = nominatim_server, + custom_query = custom_query, points_only = points_only + ) + }) + if (progressbar) close(pb) + + all_res <- dplyr::bind_rows(all_res) + + # Clean columns and names + nm <- names(all_res) + nm[nm == "q_amenity"] <- "amenity" + names(all_res) <- nm + all_res <- all_res[, !grepl("^q_", nm)] + + if (strict) { + bbox_sf <- bbox_to_poly(bbox) + int <- as.vector(sf::st_intersects(all_res, bbox_sf, sparse = FALSE)) + all_res <- all_res[int, ] + } + + return(all_res) +} diff --git a/R/geo_lite_sf.R b/R/geo_lite_sf.R index e7882476..b95bbf0a 100644 --- a/R/geo_lite_sf.R +++ b/R/geo_lite_sf.R @@ -1,7 +1,7 @@ #' Address search API for OSM elements in \CRANpkg{sf} format (free-form query) #' #' @description -#' This function allows you to geocode addresses and return the corresponding +#' This function allows you to geocode addresses and returns the corresponding #' spatial object. This function returns the spatial object associated with the #' query using \CRANpkg{sf}, see [geo_lite()] for retrieving the data in #' [`tibble`][tibble::tibble] format. diff --git a/README.Rmd b/README.Rmd index 71a4b227..0630df2a 100644 --- a/README.Rmd +++ b/README.Rmd @@ -91,7 +91,7 @@ install.packages("nominatimlite") You can install the developing version of **nominatimlite** with: ```{r, eval=FALSE} -devtools::install_github("dieghernan/nominatimlite") +remotes::install_github("dieghernan/nominatimlite") ``` Alternatively, you can install **nominatimlite** using the diff --git a/README.md b/README.md index ff4d3a59..145f89fb 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ install.packages("nominatimlite") You can install the developing version of **nominatimlite** with: ``` r -devtools::install_github("dieghernan/nominatimlite") +remotes::install_github("dieghernan/nominatimlite") ``` Alternatively, you can install **nominatimlite** using the diff --git a/codemeta.json b/codemeta.json index c3e4775f..3ee16255 100644 --- a/codemeta.json +++ b/codemeta.json @@ -103,6 +103,18 @@ }, "sameAs": "https://CRAN.R-project.org/package=knitr" }, + { + "@type": "SoftwareApplication", + "identifier": "lifecycle", + "name": "lifecycle", + "provider": { + "@id": "https://cran.r-project.org", + "@type": "Organization", + "name": "Comprehensive R Archive Network (CRAN)", + "url": "https://cran.r-project.org" + }, + "sameAs": "https://CRAN.R-project.org/package=lifecycle" + }, { "@type": "SoftwareApplication", "identifier": "rmarkdown", @@ -187,18 +199,6 @@ "sameAs": "https://CRAN.R-project.org/package=jsonlite" }, "4": { - "@type": "SoftwareApplication", - "identifier": "lifecycle", - "name": "lifecycle", - "provider": { - "@id": "https://cran.r-project.org", - "@type": "Organization", - "name": "Comprehensive R Archive Network (CRAN)", - "url": "https://cran.r-project.org" - }, - "sameAs": "https://CRAN.R-project.org/package=lifecycle" - }, - "5": { "@type": "SoftwareApplication", "identifier": "sf", "name": "sf", @@ -211,7 +211,7 @@ }, "sameAs": "https://CRAN.R-project.org/package=sf" }, - "6": { + "5": { "@type": "SoftwareApplication", "identifier": "utils", "name": "utils" @@ -220,7 +220,7 @@ }, "applicationCategory": "cartography", "keywords": ["r", "geocoding", "openstreetmap", "address", "nominatim", "reverse-geocoding", "rstats", "shapefile", "r-package", "spatial", "cran", "api-wrapper", "api", "gis"], - "fileSize": "223.845KB", + "fileSize": "242.977KB", "citation": [ { "@type": "SoftwareSourceCode", diff --git a/inst/WORDLIST b/inst/WORDLIST index 48248fab..9f83f9a5 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -29,9 +29,11 @@ geocode geocoded geocoder geocoding +geolocated ie json lon +osm osmdata rlang testthat @@ -39,3 +41,4 @@ tibble tidygeocoder unnesting vectorized +viewbox diff --git a/man/bbox_to_poly.Rd b/man/bbox_to_poly.Rd index 5909c74d..018dae27 100644 --- a/man/bbox_to_poly.Rd +++ b/man/bbox_to_poly.Rd @@ -63,6 +63,7 @@ if (any(!sf::st_is_empty(sfobj))) { Get \code{\link[sf:sf]{sf}} objects: \code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_amenity_sf}()}, \code{\link{geo_lite_sf}()}, \code{\link{geo_lite_struct_sf}()}, \code{\link{reverse_geo_lite_sf}()} diff --git a/man/figures/README-pizzahut-1.png b/man/figures/README-pizzahut-1.png index aa70f2d259e2d86141be2f3ae5f577cd9e5b5032..aadb334069359a329218af95845f8af15a3fad50 100644 GIT binary patch literal 9968 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 qVs7B|?)^oqB`p#JBrYp)C8s4z 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 diff --git a/man/geo_address_lookup.Rd b/man/geo_address_lookup.Rd index c0fe4c7a..e2e6aa25 100644 --- a/man/geo_address_lookup.Rd +++ b/man/geo_address_lookup.Rd @@ -76,6 +76,8 @@ Address Lookup API: Geocoding: \code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_amenity}()}, +\code{\link{geo_amenity_sf}()}, \code{\link{geo_lite}()}, \code{\link{geo_lite_sf}()}, \code{\link{geo_lite_struct}()}, diff --git a/man/geo_address_lookup_sf.Rd b/man/geo_address_lookup_sf.Rd index 658d5e4e..67f6cc9f 100644 --- a/man/geo_address_lookup_sf.Rd +++ b/man/geo_address_lookup_sf.Rd @@ -116,6 +116,8 @@ Address Lookup API: Geocoding: \code{\link{geo_address_lookup}()}, +\code{\link{geo_amenity}()}, +\code{\link{geo_amenity_sf}()}, \code{\link{geo_lite}()}, \code{\link{geo_lite_sf}()}, \code{\link{geo_lite_struct}()}, @@ -123,6 +125,7 @@ Geocoding: Get \code{\link[sf:sf]{sf}} objects: \code{\link{bbox_to_poly}()}, +\code{\link{geo_amenity_sf}()}, \code{\link{geo_lite_sf}()}, \code{\link{geo_lite_struct_sf}()}, \code{\link{reverse_geo_lite_sf}()} diff --git a/man/geo_amenity.Rd b/man/geo_amenity.Rd index f6166ff0..088d7cb5 100644 --- a/man/geo_amenity.Rd +++ b/man/geo_amenity.Rd @@ -1,43 +1,127 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/deprecated.R +% Please edit documentation in R/geo_amenity.R \name{geo_amenity} \alias{geo_amenity} -\alias{geo_amenity_sf} \title{Geocode amenities} \usage{ -geo_amenity(bbox = NULL, ...) - -geo_amenity_sf(bbox = NULL, ...) +geo_amenity( + bbox, + amenity, + lat = "lat", + long = "lon", + limit = 1, + full_results = FALSE, + return_addresses = TRUE, + verbose = FALSE, + nominatim_server = "https://nominatim.openstreetmap.org/", + progressbar = TRUE, + custom_query = list(), + strict = FALSE +) } \arguments{ -\item{bbox, ...}{Deprecated} +\item{bbox}{The bounding box (viewbox) used to limit the search. It could be: +\itemize{ +\item A numeric vector of \strong{longitude} (\code{x}) and \strong{latitude} (\code{y}) +\verb{(xmin, ymin, xmax, ymax)}. See \strong{Details}. +\item A \code{\link[sf:sf]{sf}} or \code{\link[sf:sfc]{sfc}} object. +}} + +\item{amenity}{A \code{character} (or a vector of \code{character}s) with the +amenities to be geolocated (i.e. \code{c("pub", "restaurant")}). See +\link{osm_amenities}.} + +\item{lat}{Latitude column name in the output data (default \code{"lat"}).} + +\item{long}{Longitude column name in the output data (default \code{"long"}).} + +\item{limit}{Maximum number of results to return per input address. Note +that each query returns a maximum of 50 results.} + +\item{full_results}{Returns all available data from the API service. +If \code{FALSE} (default) only latitude, longitude and address columns are +returned. See also \code{return_addresses}.} + +\item{return_addresses}{Return input addresses with results if \code{TRUE}.} + +\item{verbose}{If \code{TRUE} then detailed logs are output to the console.} + +\item{nominatim_server}{The URL of the Nominatim server to use. +Defaults to \code{"https://nominatim.openstreetmap.org/"}.} + +\item{progressbar}{Logical. If \code{TRUE} displays a progress bar to indicate +the progress of the function.} + +\item{custom_query}{A named list with API-specific parameters to be used +(i.e. \code{list(countrycodes = "US")}). See \strong{Details}.} + +\item{strict}{Logical \code{TRUE/FALSE}. Force the results to be included inside +the \code{bbox}. Note that Nominatim default behavior may return results located +outside the provided bounding box.} } \value{ -An error. +A \code{\link[tibble:tibble]{tibble}} with the results found by the query. } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#defunct}{\figure{lifecycle-defunct.svg}{options: alt='[Defunct]'}}}{\strong{[Defunct]}} +This function search \link[=osm_amenities]{amenities} as defined by OpenStreetMap +on a restricted area defined by a bounding box in the form +\verb{(, , , )}. This function returns the +\code{\link[tibble:tibble]{tibble}} associated with the query, see \code{\link[=geo_amenity_sf]{geo_amenity_sf()}} +for retrieving the data as a spatial object (\code{\link[sf:sf]{sf}} format). +} +\details{ +Bounding boxes can be located using different online tools, as +\href{https://boundingbox.klokantech.com/}{Bounding Box Tool}. -This operation is not supported any more. Use -\code{\link[arcgeocoder:arc_geo_categories]{arcgeocoder::arc_geo_categories()}} instead. +For a full list of valid amenities see +\url{https://wiki.openstreetmap.org/wiki/Key:amenity} and \link{osm_amenities}. + +See \url{https://nominatim.org/release-docs/latest/api/Search/} for additional +parameters to be passed to \code{custom_query}. } \examples{ +\dontshow{if (nominatim_check_access()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} \donttest{ -# Madrid, Spain - -library(arcgeocoder) -library(ggplot2) +# Times Square, NY, USA +bbox <- c( + -73.9894467311, 40.75573629, + -73.9830630737, 40.75789245 +) -bbox <- c(-3.888954, 40.311977, -3.517916, 40.643729) +geo_amenity( + bbox = bbox, + amenity = "restaurant" +) -# Food -rest_pub <- arc_geo_categories( - bbox = bbox, category = "Bakery,Bar or Pub", - full_results = TRUE, - limit = 50 +# Several amenities +geo_amenity( + bbox = bbox, + amenity = c("restaurant", "pub") ) -rest_pub +# Increase limit and use with strict +geo_amenity( + bbox = bbox, + amenity = c("restaurant", "pub"), + limit = 10, + strict = TRUE +) } +\dontshow{\}) # examplesIf} +} +\seealso{ +Other amenity: +\code{\link{geo_amenity_sf}()}, +\code{\link{osm_amenities}} + +Geocoding: +\code{\link{geo_address_lookup}()}, +\code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_amenity_sf}()}, +\code{\link{geo_lite}()}, +\code{\link{geo_lite_sf}()}, +\code{\link{geo_lite_struct}()}, +\code{\link{geo_lite_struct_sf}()} } -\keyword{internal} +\concept{amenity} +\concept{geocoding} diff --git a/man/geo_amenity_sf.Rd b/man/geo_amenity_sf.Rd new file mode 100644 index 00000000..fadb99c1 --- /dev/null +++ b/man/geo_amenity_sf.Rd @@ -0,0 +1,147 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/geo_amenity_sf.R +\name{geo_amenity_sf} +\alias{geo_amenity_sf} +\title{Geocode amenities in \CRANpkg{sf} format} +\usage{ +geo_amenity_sf( + bbox, + amenity, + limit = 1, + full_results = FALSE, + return_addresses = TRUE, + verbose = FALSE, + nominatim_server = "https://nominatim.openstreetmap.org/", + progressbar = TRUE, + custom_query = list(), + strict = FALSE, + points_only = TRUE +) +} +\arguments{ +\item{bbox}{The bounding box (viewbox) used to limit the search. It could be: +\itemize{ +\item A numeric vector of \strong{longitude} (\code{x}) and \strong{latitude} (\code{y}) +\verb{(xmin, ymin, xmax, ymax)}. See \strong{Details}. +\item A \code{\link[sf:sf]{sf}} or \code{\link[sf:sfc]{sfc}} object. +}} + +\item{amenity}{A \code{character} (or a vector of \code{character}s) with the +amenities to be geolocated (i.e. \code{c("pub", "restaurant")}). See +\link{osm_amenities}.} + +\item{limit}{Maximum number of results to return per input address. Note +that each query returns a maximum of 50 results.} + +\item{full_results}{Returns all available data from the API service. +If \code{FALSE} (default) only latitude, longitude and address columns are +returned. See also \code{return_addresses}.} + +\item{return_addresses}{Return input addresses with results if \code{TRUE}.} + +\item{verbose}{If \code{TRUE} then detailed logs are output to the console.} + +\item{nominatim_server}{The URL of the Nominatim server to use. +Defaults to \code{"https://nominatim.openstreetmap.org/"}.} + +\item{progressbar}{Logical. If \code{TRUE} displays a progress bar to indicate +the progress of the function.} + +\item{custom_query}{A named list with API-specific parameters to be used +(i.e. \code{list(countrycodes = "US")}). See \strong{Details}.} + +\item{strict}{Logical \code{TRUE/FALSE}. Force the results to be included inside +the \code{bbox}. Note that Nominatim default behavior may return results located +outside the provided bounding box.} + +\item{points_only}{Logical \code{TRUE/FALSE}. Whether to return only spatial +points (\code{TRUE}, which is the default) or potentially other shapes as +provided by the Nominatim API (\code{FALSE}). See \strong{About Geometry Types}.} +} +\value{ +A \code{\link[sf:sf]{sf}} object with the results. +} +\description{ +This function search \link[=osm_amenities]{amenities} as defined by OpenStreetMap +on a restricted area defined by a bounding box in the form +\verb{(, , , )}. This function returns the spatial +object associated with the query using \CRANpkg{sf}, see \code{\link[=geo_amenity]{geo_amenity()}} for +retrieving the data in \code{\link[tibble:tibble]{tibble}} format. +} +\details{ +Bounding boxes can be located using different online tools, as +\href{https://boundingbox.klokantech.com/}{Bounding Box Tool}. + +For a full list of valid amenities see +\url{https://wiki.openstreetmap.org/wiki/Key:amenity} and \link{osm_amenities}. + +See \url{https://nominatim.org/release-docs/latest/api/Search/} for additional +parameters to be passed to \code{custom_query}. +} +\section{About Geometry Types}{ + + +The parameter \code{points_only} specifies whether the function results will be +points (all Nominatim results are guaranteed to have at least point +geometry) or possibly other spatial objects. + +Note that the type of geometry returned in case of \code{points_only = FALSE} +will depend on the object being geocoded: +\itemize{ +\item Administrative areas, major buildings and the like will be +returned as polygons. +\item Rivers, roads and their like as lines. +\item Amenities may be points even in case of a \code{points_only = FALSE} call. +} + +The function is vectorized, allowing for multiple addresses to be geocoded; +in case of \code{points_only = FALSE} multiple geometry types may be returned. +} + +\examples{ +\dontshow{if (nominatim_check_access()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\donttest{ +# Usera, Madrid + +library(ggplot2) +mad <- geo_lite_sf("Usera, Madrid, Spain", points_only = FALSE) + + +# Restaurants, pubs and schools + +rest_pub <- geo_amenity_sf(mad, c("restaurant", "pub", "school"), + limit = 50 +) + +if (any(!sf::st_is_empty(rest_pub))) { + ggplot(mad) + + geom_sf() + + geom_sf(data = rest_pub, aes(color = amenity, shape = amenity)) +} +} +\dontshow{\}) # examplesIf} +} +\seealso{ +Other amenity: +\code{\link{geo_amenity}()}, +\code{\link{osm_amenities}} + +Geocoding: +\code{\link{geo_address_lookup}()}, +\code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_amenity}()}, +\code{\link{geo_lite}()}, +\code{\link{geo_lite_sf}()}, +\code{\link{geo_lite_struct}()}, +\code{\link{geo_lite_struct_sf}()} + +Get \code{\link[sf:sf]{sf}} objects: +\code{\link{bbox_to_poly}()}, +\code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_lite_sf}()}, +\code{\link{geo_lite_struct_sf}()}, +\code{\link{reverse_geo_lite_sf}()} +} +\concept{amenity} +\concept{geocoding} +\concept{spatial} diff --git a/man/geo_lite.Rd b/man/geo_lite.Rd index 43f72d01..0c2f452b 100644 --- a/man/geo_lite.Rd +++ b/man/geo_lite.Rd @@ -83,6 +83,8 @@ geo_lite(c("Madrid", "Barcelona"), Geocoding: \code{\link{geo_address_lookup}()}, \code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_amenity}()}, +\code{\link{geo_amenity_sf}()}, \code{\link{geo_lite_sf}()}, \code{\link{geo_lite_struct}()}, \code{\link{geo_lite_struct_sf}()} diff --git a/man/geo_lite_sf.Rd b/man/geo_lite_sf.Rd index 17ad8367..432f8b43 100644 --- a/man/geo_lite_sf.Rd +++ b/man/geo_lite_sf.Rd @@ -49,7 +49,7 @@ provided by the Nominatim API (\code{FALSE}). See \strong{About Geometry Types}. A \code{\link[sf:sf]{sf}} object with the results. } \description{ -This function allows you to geocode addresses and return the corresponding +This function allows you to geocode addresses and returns the corresponding spatial object. This function returns the spatial object associated with the query using \CRANpkg{sf}, see \code{\link[=geo_lite]{geo_lite()}} for retrieving the data in \code{\link[tibble:tibble]{tibble}} format. @@ -122,6 +122,8 @@ if (any(!sf::st_is_empty(madrid))) { Geocoding: \code{\link{geo_address_lookup}()}, \code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_amenity}()}, +\code{\link{geo_amenity_sf}()}, \code{\link{geo_lite}()}, \code{\link{geo_lite_struct}()}, \code{\link{geo_lite_struct_sf}()} @@ -129,6 +131,7 @@ Geocoding: Get \code{\link[sf:sf]{sf}} objects: \code{\link{bbox_to_poly}()}, \code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_amenity_sf}()}, \code{\link{geo_lite_struct_sf}()}, \code{\link{reverse_geo_lite_sf}()} } diff --git a/man/geo_lite_struct.Rd b/man/geo_lite_struct.Rd index 5fd02a83..e10154d7 100644 --- a/man/geo_lite_struct.Rd +++ b/man/geo_lite_struct.Rd @@ -99,6 +99,8 @@ dplyr::glimpse(pl_mayor) Geocoding: \code{\link{geo_address_lookup}()}, \code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_amenity}()}, +\code{\link{geo_amenity_sf}()}, \code{\link{geo_lite}()}, \code{\link{geo_lite_sf}()}, \code{\link{geo_lite_struct_sf}()} diff --git a/man/geo_lite_struct_sf.Rd b/man/geo_lite_struct_sf.Rd index 817c8509..00423a11 100644 --- a/man/geo_lite_struct_sf.Rd +++ b/man/geo_lite_struct_sf.Rd @@ -130,6 +130,8 @@ if (any(!sf::st_is_empty(pl_mayor), !sf::st_is_empty(ccaa))) { Geocoding: \code{\link{geo_address_lookup}()}, \code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_amenity}()}, +\code{\link{geo_amenity_sf}()}, \code{\link{geo_lite}()}, \code{\link{geo_lite_sf}()}, \code{\link{geo_lite_struct}()} @@ -137,6 +139,7 @@ Geocoding: Get \code{\link[sf:sf]{sf}} objects: \code{\link{bbox_to_poly}()}, \code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_amenity_sf}()}, \code{\link{geo_lite_sf}()}, \code{\link{reverse_geo_lite_sf}()} } diff --git a/man/osm_amenities.Rd b/man/osm_amenities.Rd index 6926f0fb..414261d9 100644 --- a/man/osm_amenities.Rd +++ b/man/osm_amenities.Rd @@ -30,5 +30,10 @@ data("osm_amenities") osm_amenities } +\seealso{ +Other amenity: +\code{\link{geo_amenity}()}, +\code{\link{geo_amenity_sf}()} +} \concept{amenity} \concept{datasets} diff --git a/man/reverse_geo_lite_sf.Rd b/man/reverse_geo_lite_sf.Rd index 2a0f6d1e..a4cf8b4a 100644 --- a/man/reverse_geo_lite_sf.Rd +++ b/man/reverse_geo_lite_sf.Rd @@ -148,6 +148,7 @@ Reverse geocoding coordinates: Get \code{\link[sf:sf]{sf}} objects: \code{\link{bbox_to_poly}()}, \code{\link{geo_address_lookup_sf}()}, +\code{\link{geo_amenity_sf}()}, \code{\link{geo_lite_sf}()}, \code{\link{geo_lite_struct_sf}()} } diff --git a/tests/testthat/_snaps/deprecated.md b/tests/testthat/_snaps/deprecated.md deleted file mode 100644 index 3b55f5a7..00000000 --- a/tests/testthat/_snaps/deprecated.md +++ /dev/null @@ -1,20 +0,0 @@ -# Deprecated geo_amenity_sf - - Code - geo_amenity_sf() - Condition - Error: - ! `geo_amenity_sf()` was deprecated in nominatimlite 0.3.0 and is now defunct. - i Please use `arcgeocoder::arc_geo_categories()` instead. - i Operation not supported any more by the Nominatim API. - -# Deprecated geo_amenity - - Code - geo_amenity() - Condition - Error: - ! `geo_amenity()` was deprecated in nominatimlite 0.3.0 and is now defunct. - i Please use `arcgeocoder::arc_geo_categories()` instead. - i Operation not supported any more by the Nominatim API. - diff --git a/tests/testthat/test-deprecated.R b/tests/testthat/test-deprecated.R deleted file mode 100644 index 3ae58ed0..00000000 --- a/tests/testthat/test-deprecated.R +++ /dev/null @@ -1,11 +0,0 @@ -test_that("Deprecated geo_amenity_sf", { - skip_if_not_installed("lifecycle") - - expect_snapshot(geo_amenity_sf(), error = TRUE) -}) - -test_that("Deprecated geo_amenity", { - skip_if_not_installed("lifecycle") - - expect_snapshot(geo_amenity(), error = TRUE) -}) diff --git a/tests/testthat/test-geo_amenity.R b/tests/testthat/test-geo_amenity.R new file mode 100644 index 00000000..f53393ba --- /dev/null +++ b/tests/testthat/test-geo_amenity.R @@ -0,0 +1,113 @@ +test_that("Progress bar", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + bbox <- c(2.113482, 41.328553, 2.206866, 41.420785) + + # No pbar + expect_silent(geo_amenity(bbox, "school")) + expect_silent(geo_amenity(bbox, "school", progressbar = TRUE)) + + # Get a pbar + expect_output(aa <- geo_amenity(bbox, c("pub", "school"))) + + # Not + expect_silent(aa <- geo_amenity( + bbox, c("pub", "school"), + progressbar = FALSE + )) +}) + +test_that("Checking query", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + expect_message(obj <- geo_amenity( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + c("pub", "restaurant"), + limit = 51 + ), "50 results") + + + expect_identical(names(obj), c("amenity", "lat", "lon", "address")) + + obj <- geo_amenity( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + "pub", + long = "ong", lat = "at", + full_results = FALSE, + return_addresses = FALSE + ) + expect_identical(names(obj), c("amenity", "at", "ong")) + + obj <- geo_amenity( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + "pub", + long = "ong", lat = "at", + full_results = FALSE, + return_addresses = TRUE + ) + + expect_identical(names(obj), c("amenity", "at", "ong", "address")) + + obj <- geo_amenity( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + "pub", + long = "ong", lat = "at", + full_results = TRUE, + return_addresses = FALSE + ) + + expect_identical(names(obj)[1:4], c("amenity", "at", "ong", "address")) + expect_gt(ncol(obj), 4) + + + expect_gt(nrow(geo_amenity( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + "pub", + limit = 10, + custom_query = list(countrycode = "es") + )), 4) + expect_equal(nrow(geo_amenity( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + "pub", + custom_query = list(countrycode = "es") + )), 1) + expect_equal(nrow(geo_amenity( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + "pub", + custom_query = list(extratags = 1) + )), 1) + + expect_lt(nrow(geo_amenity( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + "pub", + limit = 1, + strict = TRUE + )), 2) + + bbox_sfc <- bbox_to_poly(c(-1.1446, 41.5022, -0.4854, 41.8795)) + expect_s3_class(bbox_sfc, "sfc") + + expect_silent(a <- geo_amenity( + bbox = bbox_sfc, + "pub", + limit = 1, + strict = TRUE + )) + + + bbox_sf <- sf::st_sf(x = 1, bbox_sfc) + expect_s3_class(bbox_sf, "sf") + + bbox_sf <- sf::st_transform(bbox_sf, 3857) + + expect_silent(a <- geo_amenity( + bbox = bbox_sf, + "pub", + limit = 1, + strict = TRUE + )) +}) diff --git a/tests/testthat/test-geo_amenity_sf.R b/tests/testthat/test-geo_amenity_sf.R new file mode 100644 index 00000000..39f8105a --- /dev/null +++ b/tests/testthat/test-geo_amenity_sf.R @@ -0,0 +1,110 @@ +test_that("Progress bar", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + bbox <- c(2.113482, 41.328553, 2.206866, 41.420785) + + # No pbar + expect_silent(geo_amenity_sf(bbox, "school")) + expect_silent(geo_amenity_sf(bbox, "school", progressbar = TRUE)) + + # Get a pbar + expect_output(aa <- geo_amenity_sf(bbox, c("pub", "school"))) + + # Not + expect_silent(aa <- geo_amenity_sf( + bbox, c("pub", "school"), + progressbar = FALSE + )) +}) + +test_that("Checking query", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + expect_message(obj <- geo_amenity_sf( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + c("pub", "restaurant"), + limit = 51 + ), "50 results") + + + expect_identical(names(obj), c("amenity", "address", "geometry")) + + obj <- geo_amenity_sf( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + "pub", + full_results = FALSE, + return_addresses = FALSE + ) + expect_identical(names(obj), c("amenity", "geometry")) + + obj <- geo_amenity_sf( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + "pub", + full_results = FALSE, + return_addresses = TRUE + ) + + expect_identical(names(obj), c("amenity", "address", "geometry")) + + obj <- geo_amenity_sf( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + "pub", + full_results = TRUE, + return_addresses = FALSE + ) + + expect_identical(names(obj)[1:2], c("amenity", "address")) + expect_gt(ncol(obj), 3) + + + expect_gt(nrow(geo_amenity_sf( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + "pub", + limit = 10, + custom_query = list(countrycode = "es") + )), 4) + expect_equal(nrow(geo_amenity_sf( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + "pub", + custom_query = list(countrycode = "es") + )), 1) + expect_equal(nrow(geo_amenity_sf( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + "pub", + custom_query = list(extratags = 1) + )), 1) + + expect_lt(nrow(geo_amenity_sf( + bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), + "pub", + limit = 1, + strict = TRUE + )), 2) + + bbox_sfc <- bbox_to_poly(c(-1.1446, 41.5022, -0.4854, 41.8795)) + expect_s3_class(bbox_sfc, "sfc") + + expect_silent(a <- geo_amenity_sf( + bbox = bbox_sfc, + "pub", + limit = 1, + strict = TRUE + )) + + + bbox_sf <- sf::st_sf(x = 1, bbox_sfc) + expect_s3_class(bbox_sf, "sf") + + bbox_sf <- sf::st_transform(bbox_sf, 3857) + + expect_silent(a <- geo_amenity_sf( + bbox = bbox_sf, + "pub", + limit = 1, + strict = TRUE + )) +}) From 1658018165a6b4ad86b743c308549b043937344f Mon Sep 17 00:00:00 2001 From: dieghernan Date: Mon, 8 Apr 2024 14:14:19 +0000 Subject: [PATCH 5/8] Fix problems and improve unnesting --- .Rbuildignore | 1 + DESCRIPTION | 3 +- NEWS.md | 3 + R/geo_amenity.R | 2 +- R/geo_amenity_sf.R | 4 +- R/utils.R | 70 +++++++++- codemeta.json | 2 +- inst/WORDLIST | 5 + tests/testthat/test-geo_amenity.R | 18 +-- tests/testthat/test-geo_amenity_sf.R | 18 +-- vignettes/articles/ex_leaflet.Rmd | 193 +++++++++++++++++++++++++++ 11 files changed, 292 insertions(+), 27 deletions(-) create mode 100644 vignettes/articles/ex_leaflet.Rmd diff --git a/.Rbuildignore b/.Rbuildignore index 4074d72c..f18b3f78 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -29,3 +29,4 @@ ^CODE_OF_CONDUCT\.md$ ^CONTRIBUTING\.md$ ^Rplots\.pdf$ +^vignettes/articles$ diff --git a/DESCRIPTION b/DESCRIPTION index 949b9bce..b881c96b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -37,7 +37,8 @@ Suggests: tidygeocoder VignetteBuilder: knitr -Config/Needs/website: dieghernan/gitdevr, remotes, devtools, tidyverse +Config/Needs/website: dieghernan/gitdevr, remotes, devtools, tidyverse, + leaflet, reactable, crosswalk, tidyr Config/testthat/edition: 3 Config/testthat/parallel: true Copyright: Data © OpenStreetMap contributors, ODbL 1.0. diff --git a/NEWS.md b/NEWS.md index 3296a5d3..dd17ee91 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,6 +8,9 @@ `geo_lite_struct()` / `geo_lite_struct_sf(),` so now are more robust and compatible with **sf** objects. +- Improve unnesting of fields when requiring `extratags`, i.e. + `custom_query = list(extratags = TRUE)`. + - It is possible to use **nominatimlite** with local server thanks to the new argument `nominatim_server` (#42 \@alexwhitedatamine). diff --git a/R/geo_amenity.R b/R/geo_amenity.R index 7d05fd82..0c1e7470 100644 --- a/R/geo_amenity.R +++ b/R/geo_amenity.R @@ -128,7 +128,7 @@ geo_amenity <- function( # Clean columns and names nm <- names(all_res) - nm[nm == "q_amenity"] <- "amenity" + nm[nm == "q_amenity"] <- "query" names(all_res) <- nm all_res <- all_res[, !grepl("^q_", nm)] diff --git a/R/geo_amenity_sf.R b/R/geo_amenity_sf.R index a5c2967d..a5d1a0d7 100644 --- a/R/geo_amenity_sf.R +++ b/R/geo_amenity_sf.R @@ -114,9 +114,10 @@ geo_amenity_sf <- function( # Clean columns and names nm <- names(all_res) - nm[nm == "q_amenity"] <- "amenity" + nm[nm == "q_amenity"] <- "query" names(all_res) <- nm all_res <- all_res[, !grepl("^q_", nm)] + all_res <- sf_to_tbl(all_res) if (strict) { bbox_sf <- bbox_to_poly(bbox) @@ -124,5 +125,6 @@ geo_amenity_sf <- function( all_res <- all_res[int, ] } + return(all_res) } diff --git a/R/utils.R b/R/utils.R index 849161ed..a7826ea9 100644 --- a/R/utils.R +++ b/R/utils.R @@ -38,9 +38,17 @@ is_named <- function(x) { keep_names <- function(x, return_addresses, full_results, colstokeep = "query") { - names(x) <- gsub("address.", "", names(x), fixed = TRUE) - names(x) <- gsub("namedetails.", "", names(x), fixed = TRUE) - names(x) <- gsub("display_name", "address", names(x), fixed = TRUE) + x$address <- x$display_name + if ("boundingbox" %in% names(x)) { + bbun <- lapply(x$boundingbox, function(y) { + unl <- unlist(y) + bb <- dplyr::tibble(boundingbox = list(as.double(unl))) + bb + }) + bbun <- dplyr::bind_rows(bbun) + cln <- x[, names(x) != "boundingbox"] + x <- dplyr::bind_cols(cln, bbun) + } out_cols <- colstokeep if (return_addresses) out_cols <- c(out_cols, "address") @@ -55,8 +63,10 @@ keep_names <- function(x, return_addresses, full_results, keep_names_rev <- function(x, address = "address", return_coords = FALSE, full_results = FALSE, colstokeep = address) { - names(x) <- gsub("display_name", address, names(x)) - + x$xxxyyyzzz <- x$display_name + nm <- names(x) + nm <- gsub("xxxyyyzzz", address, nm, fixed = TRUE) + names(x) <- nm 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)) @@ -110,11 +120,14 @@ unnest_reverse <- function(x) { # OSM address if ("address" %in% names(lngths)) { ad <- dplyr::as_tibble(x$address)[1, ] + names(ad) <- paste0("address.", names(ad)) + endobj <- dplyr::bind_cols(endobj, ad) } if ("extratags" %in% names(lngths)) { xtra <- dplyr::as_tibble(x$extratags)[1, ] + names(xtra) <- paste0("extratags.", names(xtra)) endobj <- dplyr::bind_cols(endobj, xtra) } @@ -153,6 +166,51 @@ sf_to_tbl <- function(x) { unnest_sf <- function(x) { # Unnest + if ("address" %in% names(x)) { + # Need to unnest + add <- as.character(x$address) + newadd <- lapply(add, function(x) { + df <- jsonlite::fromJSON(x, simplifyVector = TRUE) + dplyr::as_tibble(df) + }) + + newadd <- dplyr::bind_rows(newadd) + names(newadd) <- paste0("address.", names(newadd)) + + newsfobj <- x + newsfobj <- x[, setdiff(names(x), "address")] + x <- dplyr::bind_cols(newsfobj, newadd) + } + + if ("extratags" %in% names(x)) { + # Need to unnest + xtra <- as.character(x$extratags) + + newxtra <- lapply(xtra, function(x) { + if (any(is.na(x), is.null(x))) { + return(dplyr::tibble(xxx_empty_remove = NA)) + } + df <- jsonlite::fromJSON(x, simplifyVector = TRUE) + dplyr::as_tibble(df) + }) + + newxtra <- dplyr::bind_rows(newxtra) + names(newxtra) <- paste0("extratags.", names(newxtra)) + + newsfobj <- x + newsfobj <- x[, setdiff(names(x), "extratags")] + x <- dplyr::bind_cols(newsfobj, newxtra) + x <- x[, setdiff(names(x), "extratags.xxx_empty_remove")] + } + + + x <- sf_to_tbl(x) + + x + + # Unnest fields + need_un <- any(c("address", "extratags") %in% names(x)) + if (!("address" %in% names(x))) { return(x) } @@ -186,6 +244,7 @@ unnest_sf_reverse <- function(x) { }) newadd <- dplyr::bind_rows(newadd)[1, ] + names(newadd) <- paste0("address.", names(newadd)) newsfobj <- x newsfobj <- x[, setdiff(names(x), "address")] @@ -202,6 +261,7 @@ unnest_sf_reverse <- function(x) { }) newxtra <- dplyr::bind_rows(newxtra)[1, ] + names(newxtra) <- paste0("extratags.", names(newxtra)) newsfobj <- x newsfobj <- x[, setdiff(names(x), "extratags")] diff --git a/codemeta.json b/codemeta.json index 3ee16255..1c3af936 100644 --- a/codemeta.json +++ b/codemeta.json @@ -220,7 +220,7 @@ }, "applicationCategory": "cartography", "keywords": ["r", "geocoding", "openstreetmap", "address", "nominatim", "reverse-geocoding", "rstats", "shapefile", "r-package", "spatial", "cran", "api-wrapper", "api", "gis"], - "fileSize": "242.977KB", + "fileSize": "244.777KB", "citation": [ { "@type": "SoftwareSourceCode", diff --git a/inst/WORDLIST b/inst/WORDLIST index 9f83f9a5..7d0c4a82 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -4,6 +4,8 @@ CMD Cambon CodeFactor DOI +Flaticon +Freepik Geocode Geocodes Geocoding @@ -21,7 +23,9 @@ Transamerica al api arcgeocoder +browsable codecov +crosstalk de et geo @@ -35,6 +39,7 @@ json lon osm osmdata +reactable rlang testthat tibble diff --git a/tests/testthat/test-geo_amenity.R b/tests/testthat/test-geo_amenity.R index f53393ba..d3d56de4 100644 --- a/tests/testthat/test-geo_amenity.R +++ b/tests/testthat/test-geo_amenity.R @@ -3,18 +3,18 @@ test_that("Progress bar", { skip_if_api_server() skip_if_offline() - bbox <- c(2.113482, 41.328553, 2.206866, 41.420785) + bbox <- c(-73.9894467311, 40.75573629, -73.9830630737, 40.75789245) # No pbar - expect_silent(geo_amenity(bbox, "school")) - expect_silent(geo_amenity(bbox, "school", progressbar = TRUE)) + expect_silent(geo_amenity(bbox, "restaurant")) + expect_silent(geo_amenity(bbox, "restaurant", progressbar = TRUE)) # Get a pbar - expect_output(aa <- geo_amenity(bbox, c("pub", "school"))) + expect_output(aa <- geo_amenity(bbox, c("pub", "restaurant"))) # Not expect_silent(aa <- geo_amenity( - bbox, c("pub", "school"), + bbox, c("pub", "restaurant"), progressbar = FALSE )) }) @@ -31,7 +31,7 @@ test_that("Checking query", { ), "50 results") - expect_identical(names(obj), c("amenity", "lat", "lon", "address")) + expect_identical(names(obj), c("query", "lat", "lon", "address")) obj <- geo_amenity( bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), @@ -40,7 +40,7 @@ test_that("Checking query", { full_results = FALSE, return_addresses = FALSE ) - expect_identical(names(obj), c("amenity", "at", "ong")) + expect_identical(names(obj), c("query", "at", "ong")) obj <- geo_amenity( bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), @@ -50,7 +50,7 @@ test_that("Checking query", { return_addresses = TRUE ) - expect_identical(names(obj), c("amenity", "at", "ong", "address")) + expect_identical(names(obj), c("query", "at", "ong", "address")) obj <- geo_amenity( bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), @@ -60,7 +60,7 @@ test_that("Checking query", { return_addresses = FALSE ) - expect_identical(names(obj)[1:4], c("amenity", "at", "ong", "address")) + expect_identical(names(obj)[1:4], c("query", "at", "ong", "address")) expect_gt(ncol(obj), 4) diff --git a/tests/testthat/test-geo_amenity_sf.R b/tests/testthat/test-geo_amenity_sf.R index 39f8105a..99a0be87 100644 --- a/tests/testthat/test-geo_amenity_sf.R +++ b/tests/testthat/test-geo_amenity_sf.R @@ -3,18 +3,18 @@ test_that("Progress bar", { skip_if_api_server() skip_if_offline() - bbox <- c(2.113482, 41.328553, 2.206866, 41.420785) + bbox <- c(-73.9894467311, 40.75573629, -73.9830630737, 40.75789245) # No pbar - expect_silent(geo_amenity_sf(bbox, "school")) - expect_silent(geo_amenity_sf(bbox, "school", progressbar = TRUE)) + expect_silent(geo_amenity_sf(bbox, "restaurant")) + expect_silent(geo_amenity_sf(bbox, "restaurant", progressbar = TRUE)) # Get a pbar - expect_output(aa <- geo_amenity_sf(bbox, c("pub", "school"))) + expect_output(aa <- geo_amenity_sf(bbox, c("pub", "restaurant"))) # Not expect_silent(aa <- geo_amenity_sf( - bbox, c("pub", "school"), + bbox, c("pub", "restaurant"), progressbar = FALSE )) }) @@ -31,7 +31,7 @@ test_that("Checking query", { ), "50 results") - expect_identical(names(obj), c("amenity", "address", "geometry")) + expect_identical(names(obj), c("query", "address", "geometry")) obj <- geo_amenity_sf( bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), @@ -39,7 +39,7 @@ test_that("Checking query", { full_results = FALSE, return_addresses = FALSE ) - expect_identical(names(obj), c("amenity", "geometry")) + expect_identical(names(obj), c("query", "geometry")) obj <- geo_amenity_sf( bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), @@ -48,7 +48,7 @@ test_that("Checking query", { return_addresses = TRUE ) - expect_identical(names(obj), c("amenity", "address", "geometry")) + expect_identical(names(obj), c("query", "address", "geometry")) obj <- geo_amenity_sf( bbox = c(-1.1446, 41.5022, -0.4854, 41.8795), @@ -57,7 +57,7 @@ test_that("Checking query", { return_addresses = FALSE ) - expect_identical(names(obj)[1:2], c("amenity", "address")) + expect_identical(names(obj)[1:2], c("query", "address")) expect_gt(ncol(obj), 3) diff --git a/vignettes/articles/ex_leaflet.Rmd b/vignettes/articles/ex_leaflet.Rmd new file mode 100644 index 00000000..e1b984aa --- /dev/null +++ b/vignettes/articles/ex_leaflet.Rmd @@ -0,0 +1,193 @@ +--- +title: "Example: nominatimlite and leaflet maps" +subtitle: "Combine nominatimlite and leaflet maps" +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + warning = FALSE, + message = FALSE, + dpi = 300, + tidy = "styler", + dev = "ragg_png", + out.width = "100%" +) +``` + +## Example + +The following example shows how it is possible to create a nice [leaflet +map](https://rstudio.github.io/leaflet/) with data retrieved with +**nominatimlite**. + +This widget is browsable and filterable thanks to **crosstalk** and +**reactable**: + +```{r example} +# Coffee Shops and Restaurants around the Eiffel Tower + + +library(nominatimlite) +library(sf) +library(leaflet) +library(dplyr) +library(tidyr) +library(reactable) +library(crosstalk) + + + +# Step 1: Eiffel Tower +eiffel_tower <- geo_lite_sf("Eiffel Tower, Paris, France", points_only = FALSE) + + + +# Step 2: Coffee Shops and Restaurants nearby + +# Create a buffer of 1km around the Eiffel Tower +buff <- eiffel_tower %>% + st_transform(3857) %>% + st_centroid() %>% + st_buffer(1000) + +cf_bk <- geo_amenity_sf(buff, + amenity = c("cafe", "restaurant"), limit = 50, + full_results = TRUE, + custom_query = list(extratags = TRUE) +) %>% + # Build address with street, house number, suburb and postcode + unite("addr", address.road, address.house_number, address.postcode, + address.suburb, + sep = ", ", na.rm = TRUE + ) + + +# Labels and icons +labs <- paste0("", cf_bk$name, "
", cf_bk$addr) + +# Assign icons +# Base url for icons +icon_url <- paste0( + "https://raw.githubusercontent.com/dieghernan/arcgeocoder/", + "main/vignettes/articles/" +) + +leaf_icons <- icons( + ifelse(cf_bk$type == "cafe", + paste0(icon_url, "coffee-cup.png"), + paste0(icon_url, "restaurant.png") + ), + iconWidth = 20, iconHeight = 20, + iconAnchorX = 10, iconAnchorY = 10 +) + + + +# Step 3: Crosstalk object +cf_bk_data <- cf_bk %>% + select( + Place = name, Type = type, Address = addr, + City = address.city, URL = extratags.website, + Phone = extratags.phone + ) %>% + SharedData$new(group = "Food") + + +# Step 4: Leaflet map with crosstalk +# Init leaflet map +lmend <- leaflet( + data = cf_bk_data, + elementId = "EiffelTower", width = "100%", height = "60vh", + options = leafletOptions(minZoom = 12) +) %>% + addProviderTiles( + provider = "CartoDB.Positron", + group = "CartoDB.Positron" + ) %>% + addTiles(group = "OSM") %>% + addPolygons(data = eiffel_tower) %>% + addMarkers(popup = labs, icon = leaf_icons) %>% + addLayersControl( + baseGroups = c("CartoDB.Positron", "OSM"), + position = "topleft", + options = layersControlOptions(collapsed = FALSE) + ) + + + +# Step 5: Reactable for filtering +tb <- reactable(cf_bk_data, + selection = "multiple", + onClick = "select", + rowStyle = list(cursor = "pointer"), + filterable = TRUE, + searchable = TRUE, + showPageSizeOptions = TRUE, + striped = TRUE, + defaultColDef = colDef(vAlign = "center", minWidth = 150), + paginationType = "jump", + elementId = "coffees", + columns = list( + Place = colDef( + sticky = "left", rowHeader = TRUE, name = "", + cell = function(value) { + htmltools::strong(value) + } + ), + URL = colDef(cell = function(value) { + # Render as a link + if (is.null(value) | is.na(value)) { + return("") + } + htmltools::a(href = value, target = "_blank", as.character(value)) + }), + Phone = colDef(cell = function(value) { + # Render as a link + if (is.null(value) | is.na(value)) { + return("") + } + clearphone <- gsub("-", "", value) + clearphone <- gsub(" ", "", clearphone) + htmltools::a( + href = paste0("tel:", clearphone), target = "_blank", + as.character(value) + ) + }) + ) +) +``` + +## Widget + +```{r widget} +# Last step: Display all +htmltools::browsable( + htmltools::tagList(lmend, tb) +) +``` + +## Attributions + +- [Eiffel tower icons created by Freepik - + Flaticon](https://www.flaticon.com/free-icons/eiffel-tower "eiffel tower icons") +- [Mug icons created by Freepik - + Flaticon](https://www.flaticon.com/free-icons/mug "mug icons") +- [Food icons created by Freepik - + Flaticon](https://www.flaticon.com/free-icons/food "restaurant icons") + +## Session info + +
+ +Details + +```{r session, echo=FALSE} +if (!require("sessioninfo")) { + install.packages("sessioninfo") +} +sessioninfo::session_info() +``` + +
From 074780a389d07c12139465c22d5d35c15f41bcde Mon Sep 17 00:00:00 2001 From: dieghernan Date: Mon, 8 Apr 2024 14:18:00 +0000 Subject: [PATCH 6/8] Linter --- R/utils.R | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/R/utils.R b/R/utils.R index a7826ea9..00468c57 100644 --- a/R/utils.R +++ b/R/utils.R @@ -32,7 +32,7 @@ is_named <- function(x) { return(FALSE) } - return(TRUE) + TRUE } @@ -208,9 +208,6 @@ unnest_sf <- function(x) { x - # Unnest fields - need_un <- any(c("address", "extratags") %in% names(x)) - if (!("address" %in% names(x))) { return(x) } From 4f87460a4a7b836d02d91277227dca4b532fe1ff Mon Sep 17 00:00:00 2001 From: Diego H Date: Mon, 8 Apr 2024 16:29:10 +0200 Subject: [PATCH 7/8] Fix example --- R/geo_amenity_sf.R | 2 +- man/geo_amenity_sf.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/geo_amenity_sf.R b/R/geo_amenity_sf.R index a5d1a0d7..644c29e8 100644 --- a/R/geo_amenity_sf.R +++ b/R/geo_amenity_sf.R @@ -52,7 +52,7 @@ #' if (any(!sf::st_is_empty(rest_pub))) { #' ggplot(mad) + #' geom_sf() + -#' geom_sf(data = rest_pub, aes(color = amenity, shape = amenity)) +#' geom_sf(data = rest_pub, aes(color = type, shape = type)) #' } #' } geo_amenity_sf <- function( diff --git a/man/geo_amenity_sf.Rd b/man/geo_amenity_sf.Rd index fadb99c1..8f037971 100644 --- a/man/geo_amenity_sf.Rd +++ b/man/geo_amenity_sf.Rd @@ -116,7 +116,7 @@ rest_pub <- geo_amenity_sf(mad, c("restaurant", "pub", "school"), if (any(!sf::st_is_empty(rest_pub))) { ggplot(mad) + geom_sf() + - geom_sf(data = rest_pub, aes(color = amenity, shape = amenity)) + geom_sf(data = rest_pub, aes(color = type, shape = type)) } } \dontshow{\}) # examplesIf} From 41d2926a0f366b35dfcf2c83f65a0ccb7cadd743 Mon Sep 17 00:00:00 2001 From: Diego H Date: Mon, 8 Apr 2024 16:44:59 +0200 Subject: [PATCH 8/8] Use query --- R/geo_amenity_sf.R | 2 +- man/geo_amenity_sf.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/geo_amenity_sf.R b/R/geo_amenity_sf.R index 644c29e8..c9215c37 100644 --- a/R/geo_amenity_sf.R +++ b/R/geo_amenity_sf.R @@ -52,7 +52,7 @@ #' if (any(!sf::st_is_empty(rest_pub))) { #' ggplot(mad) + #' geom_sf() + -#' geom_sf(data = rest_pub, aes(color = type, shape = type)) +#' geom_sf(data = rest_pub, aes(color = query, shape = query)) #' } #' } geo_amenity_sf <- function( diff --git a/man/geo_amenity_sf.Rd b/man/geo_amenity_sf.Rd index 8f037971..2c4a45bc 100644 --- a/man/geo_amenity_sf.Rd +++ b/man/geo_amenity_sf.Rd @@ -116,7 +116,7 @@ rest_pub <- geo_amenity_sf(mad, c("restaurant", "pub", "school"), if (any(!sf::st_is_empty(rest_pub))) { ggplot(mad) + geom_sf() + - geom_sf(data = rest_pub, aes(color = type, shape = type)) + geom_sf(data = rest_pub, aes(color = query, shape = query)) } } \dontshow{\}) # examplesIf}