From daa39b9f3c48708bbf563e68cb7fd157abd5c31d Mon Sep 17 00:00:00 2001 From: Diego H Date: Mon, 8 Jan 2024 20:50:04 +0000 Subject: [PATCH] Add reverse geocoding --- .Rbuildignore | 4 + .github/.gitignore | 4 + .github/dependabot.yml | 16 ++ .github/workflows/check-full.yaml | 57 +++++ .github/workflows/lintr.yaml | 71 ++++++ .github/workflows/pkgcheck.yaml | 21 ++ .github/workflows/pkgdown-gh-pages-clean.yaml | 33 +++ .github/workflows/pkgdown-gh-pages.yaml | 38 +++ .github/workflows/revdepcheck.yaml | 52 +++++ .github/workflows/test-coverage.yaml | 51 ++++ .github/workflows/update-docs.yaml | 54 +++++ .github/workflows/wipe-cache.yaml | 11 + CITATION.cff | 119 +++++++++- DESCRIPTION | 22 +- NAMESPACE | 5 + R/arc_reverse_geo.R | 201 ++++++++++++++++ R/arcgeocoder-package.R | 4 + R/arcgeocoder_check_access.R | 110 +++++++++ R/utils.R | 83 +++++++ README.Rmd | 12 + README.md | 19 ++ arcgeocoder.Rproj | 1 + codemeta.json | 85 ++++++- inst/CITATION | 20 ++ inst/schemaorg.json | 57 +++-- man/arc_api_call.Rd | 33 +++ man/arc_reverse_geo.Rd | 75 ++++++ man/arcgeocoder-package.Rd | 5 +- man/arcgeocoder_check_access.Rd | 26 +++ pkgdown/_pkgdown.yml | 20 ++ tests/testthat.R | 12 + tests/testthat/test-arc_reverse_geo.R | 217 ++++++++++++++++++ .../testthat/test-arcgeocoder_check_access.R | 5 + 33 files changed, 1509 insertions(+), 34 deletions(-) create mode 100644 .github/.gitignore create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/check-full.yaml create mode 100644 .github/workflows/lintr.yaml create mode 100644 .github/workflows/pkgcheck.yaml create mode 100644 .github/workflows/pkgdown-gh-pages-clean.yaml create mode 100644 .github/workflows/pkgdown-gh-pages.yaml create mode 100644 .github/workflows/revdepcheck.yaml create mode 100644 .github/workflows/test-coverage.yaml create mode 100644 .github/workflows/update-docs.yaml create mode 100644 .github/workflows/wipe-cache.yaml create mode 100644 R/arc_reverse_geo.R create mode 100644 R/arcgeocoder_check_access.R create mode 100644 R/utils.R create mode 100644 inst/CITATION create mode 100644 man/arc_api_call.Rd create mode 100644 man/arc_reverse_geo.Rd create mode 100644 man/arcgeocoder_check_access.Rd create mode 100644 pkgdown/_pkgdown.yml create mode 100644 tests/testthat.R create mode 100644 tests/testthat/test-arc_reverse_geo.R create mode 100644 tests/testthat/test-arcgeocoder_check_access.R diff --git a/.Rbuildignore b/.Rbuildignore index 15deb68..fd7cd24 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -14,3 +14,7 @@ ^CODE_OF_CONDUCT\.md$ ^CONTRIBUTING\.md$ ^LICENSE\.md$ +^arcgeocoder\.Rcheck$ +^arcgeocoder.*\.tar\.gz$ +^arcgeocoder.*\.tgz$ +^\.github$ diff --git a/.github/.gitignore b/.github/.gitignore new file mode 100644 index 0000000..26c1527 --- /dev/null +++ b/.github/.gitignore @@ -0,0 +1,4 @@ +*.html +R-version +*.Rds +*.rds diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..03b62d6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +# Basic set up for three package managers + +version: 2 +updates: + + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + diff --git a/.github/workflows/check-full.yaml b/.github/workflows/check-full.yaml new file mode 100644 index 0000000..d66dd9f --- /dev/null +++ b/.github/workflows/check-full.yaml @@ -0,0 +1,57 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/master/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +# +# NOTE: This workflow is overkill for most R packages and +# check-standard.yaml is likely a better choice. +# usethis::use_github_action("check-standard") will install it. +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + schedule: + - cron: '30 15 * * 3,6' + +name: R-CMD-check + +jobs: + R-CMD-check: + runs-on: ${{ matrix.config.os }} + + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + + strategy: + fail-fast: false + matrix: + config: + - {os: windows-latest, r: 'devel'} + - {os: windows-latest, r: 'release'} + - {os: windows-latest, r: 'oldrel'} + #- {os: macOS-latest, r: 'devel'} + - {os: macOS-latest, r: 'release'} + #- {os: macOS-latest, r: 'oldrel'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'devel'} + - {os: ubuntu-latest, r: 'oldrel'} + + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.config.r }} + http-user-agent: ${{ matrix.config.http-user-agent }} + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::rcmdcheck + needs: check + + - uses: r-lib/actions/check-r-package@v2 + diff --git a/.github/workflows/lintr.yaml b/.github/workflows/lintr.yaml new file mode 100644 index 0000000..b1d2236 --- /dev/null +++ b/.github/workflows/lintr.yaml @@ -0,0 +1,71 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# lintr provides static code analysis for R. +# It checks for adherence to a given style, +# identifying syntax errors and possible semantic issues, +# then reports them to you so you can take action. +# More details at https://lintr.r-lib.org/ + +name: lintr + +on: + push: + branches: + - main + - master + workflow_dispatch: + pull_request: + # The branches below must be a subset of the branches above + branches: + - main + - master + schedule: + - cron: '56 11 * * 5' + +permissions: + contents: read + +jobs: + lintr: + name: Run lintr scanning + runs-on: ubuntu-latest + permissions: + contents: read # for checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup R + uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - name: Setup lintr + uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: | + local::. + any::lintr + + - name: Run lintr + run: | + # Lintr package + out <- lintr::lint_package() + # Create SARIF report + lintr::sarif_output(out, "lintr-results.sarif") + # Display + out + shell: Rscript {0} + continue-on-error: true + + - name: Upload analysis results to GitHub + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: lintr-results.sarif + wait-for-processing: true + diff --git a/.github/workflows/pkgcheck.yaml b/.github/workflows/pkgcheck.yaml new file mode 100644 index 0000000..e7a5984 --- /dev/null +++ b/.github/workflows/pkgcheck.yaml @@ -0,0 +1,21 @@ +name: pkgcheck + +# This will cancel running jobs once a new run is triggered +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref }} + cancel-in-progress: true + +on: + # Manually trigger the Action under Actions/pkgcheck + workflow_dispatch: + +jobs: + pkgcheck: + runs-on: ubuntu-latest + steps: + - uses: ropensci-review-tools/pkgcheck-action@main + with: + summary-only: false + post-to-issue: true + append-to-issue: true + diff --git a/.github/workflows/pkgdown-gh-pages-clean.yaml b/.github/workflows/pkgdown-gh-pages-clean.yaml new file mode 100644 index 0000000..4a93d6a --- /dev/null +++ b/.github/workflows/pkgdown-gh-pages-clean.yaml @@ -0,0 +1,33 @@ +on: + workflow_dispatch: + +name: Clean pkgdown manually + +jobs: + pkgdown-gh-pages-manual: + runs-on: windows-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + install-r: false + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: | + local::. + any::pkgdown + needs: website + + - name: Deploy package + run: | + git config --local user.name "github-actions[bot]" + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE, clean = TRUE)' + diff --git a/.github/workflows/pkgdown-gh-pages.yaml b/.github/workflows/pkgdown-gh-pages.yaml new file mode 100644 index 0000000..e5ed278 --- /dev/null +++ b/.github/workflows/pkgdown-gh-pages.yaml @@ -0,0 +1,38 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/master/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + workflow_dispatch: + push: + branches: [main, master] + tags: ['*'] + +name: pkgdown-gh-pages + +jobs: + pkgdown-gh-pages: + runs-on: windows-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + install-r: false + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: | + local::. + any::pkgdown + needs: website + + - name: Deploy package + run: | + git config --local user.name "github-actions[bot]" + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' + diff --git a/.github/workflows/revdepcheck.yaml b/.github/workflows/revdepcheck.yaml new file mode 100644 index 0000000..8aeded3 --- /dev/null +++ b/.github/workflows/revdepcheck.yaml @@ -0,0 +1,52 @@ +on: + workflow_dispatch: + +name: revdepcheck + +jobs: + revdepcheck: + runs-on: ${{ matrix.config.os }} + + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + + strategy: + fail-fast: false + matrix: + config: + #- {os: windows-latest, r: 'release'} + - {os: windows-latest, r: 'release'} + + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: | + local::. + r-lib/revdepcheck + needs: check + + - name: revdepcheck + run: | + + revdepcheck::revdep_reset() + revdepcheck::revdep_check(num_workers = 4) + + shell: Rscript {0} + + - name: Commit results + run: | + git config --local user.name "github-actions[bot]" + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git commit -m 'revdepcheck' || echo "No changes to commit" + git push origin || echo "No changes to commit" + diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml new file mode 100644 index 0000000..63af463 --- /dev/null +++ b/.github/workflows/test-coverage.yaml @@ -0,0 +1,51 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/master/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +name: test-coverage + +jobs: + test-coverage: + runs-on: macos-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr + needs: coverage + + - name: Test coverage + run: | + covr::codecov( + quiet = FALSE, + clean = FALSE, + install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") + ) + shell: Rscript {0} + + - name: Show testthat output + if: always() + run: | + ## -------------------------------------------------------------------- + find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v3 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package + diff --git a/.github/workflows/update-docs.yaml b/.github/workflows/update-docs.yaml new file mode 100644 index 0000000..a7c3946 --- /dev/null +++ b/.github/workflows/update-docs.yaml @@ -0,0 +1,54 @@ +on: + workflow_dispatch: + +name: update-docs + +jobs: + update-docs: + runs-on: windows-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: | + local::. + any::cli + any::rcmdcheck + any::pkgdown + dieghernan/pkgdev + + needs: website + + - name: Update docs + run: | + + pkgdev::update_docs() + + shell: Rscript {0} + + - name: Commit results + run: | + git config --local user.name "github-actions[bot]" + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git commit -m 'Update docs with pkgdev' || echo "No changes to commit" + git push origin || echo "No changes to commit" + + - uses: r-lib/actions/check-r-package@v2 + + - name: Deploy package + run: | + git config --local user.name "github-actions[bot]" + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE, run_dont_run = TRUE)' + diff --git a/.github/workflows/wipe-cache.yaml b/.github/workflows/wipe-cache.yaml new file mode 100644 index 0000000..45fbff0 --- /dev/null +++ b/.github/workflows/wipe-cache.yaml @@ -0,0 +1,11 @@ +name: Clear all Github actions caches manually +on: + workflow_dispatch: + +jobs: + cache-clear: + runs-on: ubuntu-latest + + steps: + - uses: easimon/wipe-cache@main + diff --git a/CITATION.cff b/CITATION.cff index 25a3333..06b1584 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -7,18 +7,131 @@ cff-version: 1.2.0 message: 'To cite package "arcgeocoder" in publications use:' type: software license: MIT -title: 'arcgeocoder: What the Package Does (One Line, Title Case)' +title: 'arcgeocoder: Geocoding with the ''ArcGIS'' REST API Service' version: 0.0.0.9000 -abstract: What the package does (one paragraph). +abstract: Lite interface for geocoding and reverse geocoding with the 'ArcGIS' REST + API service . authors: - family-names: Hernangómez given-names: Diego email: diego.hernangomezherrero@gmail.com orcid: https://orcid.org/0000-0001-8457-4658 +preferred-citation: + type: manual + title: 'arcgeocoder: Geocoding with the ArcGIS REST API Service' + authors: + - family-names: Hernangómez + given-names: Diego + email: diego.hernangomezherrero@gmail.com + orcid: https://orcid.org/0000-0001-8457-4658 + year: '2024' + version: 0.0.0.9000 + url: https://dieghernan.github.io/nominatimlite/ + abstract: Lite interface for geocoding and reverse geocoding with the ArcGIS REST + API service . repository-code: https://github.com/dieghernan/arcgeocoder -url: https://github.com/dieghernan/arcgeocoder +url: https://dieghernan.github.io/nominatimlite/ contact: - family-names: Hernangómez given-names: Diego email: diego.hernangomezherrero@gmail.com orcid: https://orcid.org/0000-0001-8457-4658 +keywords: +- r +- geocoding +- arcgis +- address +- reverse-geocoding +- rstats +- r-package +- api-wrapper +references: +- type: software + title: 'R: A Language and Environment for Statistical Computing' + notes: Depends + url: https://www.R-project.org/ + authors: + - name: R Core Team + location: + name: Vienna, Austria + year: '2024' + institution: + name: R Foundation for Statistical Computing + version: '>= 3.6.0' +- type: software + title: dplyr + abstract: 'dplyr: A Grammar of Data Manipulation' + notes: Imports + url: https://dplyr.tidyverse.org + repository: https://CRAN.R-project.org/package=dplyr + authors: + - family-names: Wickham + given-names: Hadley + email: hadley@posit.co + orcid: https://orcid.org/0000-0003-4757-117X + - family-names: François + given-names: Romain + orcid: https://orcid.org/0000-0002-2444-4226 + - family-names: Henry + given-names: Lionel + - family-names: Müller + given-names: Kirill + orcid: https://orcid.org/0000-0002-1416-3412 + - family-names: Vaughan + given-names: Davis + email: davis@posit.co + orcid: https://orcid.org/0000-0003-4777-038X + year: '2024' + version: '>= 1.0.0' +- type: software + title: jsonlite + abstract: 'jsonlite: A Simple and Robust JSON Parser and Generator for R' + notes: Imports + url: https://jeroen.r-universe.dev/jsonlite + repository: https://CRAN.R-project.org/package=jsonlite + authors: + - family-names: Ooms + given-names: Jeroen + 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: testthat + abstract: 'testthat: Unit Testing for R' + notes: Suggests + url: https://testthat.r-lib.org + repository: https://CRAN.R-project.org/package=testthat + authors: + - family-names: Wickham + given-names: Hadley + email: hadley@posit.co + year: '2024' + version: '>= 3.0.0' +- type: software + title: tidygeocoder + abstract: 'tidygeocoder: Geocoding Made Easy' + notes: Suggests + url: https://jessecambon.github.io/tidygeocoder/ + repository: https://CRAN.R-project.org/package=tidygeocoder + authors: + - family-names: Cambon + given-names: Jesse + email: jesse.cambon@gmail.com + orcid: https://orcid.org/0000-0001-6854-1514 + - family-names: Hernangómez + given-names: Diego + email: diego.hernangomezherrero@gmail.com + orcid: https://orcid.org/0000-0001-8457-4658 + - family-names: Belanger + given-names: Christopher + email: christopher.a.belanger@gmail.com + orcid: https://orcid.org/0000-0003-2070-5721 + - family-names: Possenriede + given-names: Daniel + email: possenriede+r@gmail.com + orcid: https://orcid.org/0000-0002-6738-9845 + year: '2024' diff --git a/DESCRIPTION b/DESCRIPTION index c8f3637..0ee4565 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,13 +1,29 @@ Package: arcgeocoder -Title: What the Package Does (One Line, Title Case) +Title: Geocoding with the 'ArcGIS' REST API Service Version: 0.0.0.9000 Authors@R: person("Diego", "Hernangómez", , "diego.hernangomezherrero@gmail.com", role = c("aut", "cre", "cph"), comment = c(ORCID = "0000-0001-8457-4658")) -Description: What the package does (one paragraph). +Description: Lite interface for geocoding and reverse geocoding with the + 'ArcGIS' REST API service + . License: MIT + file LICENSE -URL: https://github.com/dieghernan/arcgeocoder +URL: https://dieghernan.github.io/nominatimlite/, + https://github.com/dieghernan/arcgeocoder BugReports: https://github.com/dieghernan/arcgeocoder/issues +Depends: + R (>= 3.6.0) +Imports: + dplyr (>= 1.0.0), + jsonlite (>= 1.7.0) +Suggests: + testthat (>= 3.0.0), + tidygeocoder +Config/Needs/website: dieghernan/gitdevr +Config/testthat/edition: 3 Encoding: UTF-8 Roxygen: list(markdown = TRUE) RoxygenNote: 7.2.3 +X-schema.org-applicationCategory: cartography +X-schema.org-keywords: r, geocoding, arcgis, address, reverse-geocoding, + rstats, r-package, api-wrapper diff --git a/NAMESPACE b/NAMESPACE index 6ae9268..fa585bc 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,2 +1,7 @@ # Generated by roxygen2: do not edit by hand +export(arc_reverse_geo) +export(arcgeocoder_check_access) +importFrom(utils,download.file) +importFrom(utils,setTxtProgressBar) +importFrom(utils,txtProgressBar) diff --git a/R/arc_reverse_geo.R b/R/arc_reverse_geo.R new file mode 100644 index 0000000..ccb9724 --- /dev/null +++ b/R/arc_reverse_geo.R @@ -0,0 +1,201 @@ +#' Reverse Geocoding using the ArcGIS REST API +#' +#' @description +#' Generates an address from a latitude and longitude. Latitudes must be +#' between `[-90, 90]` and longitudes between `[-180, 180]`. This +#' function returns the \CRANpkg{tibble} associated with the query. +#' +#' @param lat latitude values in numeric format. Must be in the range +#' `[-90, 90]`. +#' @param long longitude values in numeric format. Must be in the range +#' `[-180, 180]`. +#' @param address address column name in the output data (default `"address"`). +#' @param full_results returns all available data from the API service. If +#' `FALSE` (default) only latitude, longitude and address columns are +#' returned. +#' @param return_coords return input coordinates with results if `TRUE`. +#' @param verbose if `TRUE` then detailed logs are output to the console. +#' @param progressbar Logical. If `TRUE` displays a progress bar to indicate +#' the progress of the function. +#' @param custom_query API-specific parameters to be used, passed as a named +#' list (ie. `list(featureTypes = "POI")`). See **Details**. +#' +#' +#' @details +#' See +#' for additional parameters to be passed to `custom_query`. +#' +#' @return A \CRANpkg{tibble} with the results. +#' +#' @examplesIf arcgeocoder_check_access() +#' \donttest{ +#' +#' arc_reverse_geo(lat = 40.75728, long = -73.98586) +#' +#' # Several coordinates +#' arc_reverse_geo(lat = c(40.75728, 55.95335), long = c(-73.98586, -3.188375)) +#' +#' # With options: zoom to country level +#' sev <- arc_reverse_geo( +#' lat = c(40.75728, 55.95335), long = c(-73.98586, -3.188375), +#' custom_query = list(featureTypes = "StreetInt,POI", langCode = "FR"), +#' verbose = TRUE, full_results = TRUE +#' ) +#' +#' dplyr::glimpse(sev) +#' } +#' +#' @export +#' +#' @seealso [tidygeocoder::reverse_geo()] +#' +arc_reverse_geo <- function(lat, + long, + address = "address", + full_results = FALSE, + return_coords = TRUE, + verbose = FALSE, + progressbar = TRUE, + custom_query = list()) { + # Check inputs + if (!is.numeric(lat) || !is.numeric(long)) { + stop("lat and long must be numeric") + } + + if (length(lat) != length(long)) { + stop("lat and long should have the same number of elements") + } + + # Lat + lat_cap <- pmax(pmin(lat, 90), -90) + + if (!identical(lat_cap, lat)) { + message("latitudes have been restricted to [-90, 90]") + } + + # Lon + long_cap <- pmax(pmin(long, 180), -180) + + if (!all(long_cap == long)) { + message("longitudes have been restricted to [-180, 180]") + } + + + # Dedupe for query using data frame + + init_key <- dplyr::tibble( + lat_key_int = lat, long_key_int = long, + lat_cap_int = lat_cap, long_cap_int = long_cap + ) + key <- dplyr::distinct(init_key) + + # Set progress bar + ntot <- nrow(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) + + + all_res <- lapply(seql, function(x) { + if (progressbar) { + setTxtProgressBar(pb, x) + } + rw <- key[x, ] + res_single <- arc_reverse_geo_single( + as.double(rw$lat_cap_int), + as.double(rw$long_cap_int), + address, + full_results, + return_coords, + verbose, + custom_query + ) + + res_single <- dplyr::bind_cols(res_single, rw[, c(1, 2)]) + + res_single + }) + if (progressbar) close(pb) + + all_res <- dplyr::bind_rows(all_res) + all_res <- dplyr::left_join(init_key[, c(1, 2)], all_res, + by = c("lat_key_int", "long_key_int") + ) + + # Final clean + all_res <- all_res[, -c(1, 2)] + return(all_res) +} + +arc_reverse_geo_single <- function(lat_cap, + long_cap, + address = "address", + full_results = FALSE, + return_coords = TRUE, + verbose = TRUE, + custom_query = list()) { + # Step 1: Download ---- + api <- paste0( + "https://geocode.arcgis.com/arcgis/rest/", + "services/World/GeocodeServer/reverseGeocode?" + ) + + # Compose url + url <- paste0(api, "location=", long_cap, ",", lat_cap, "&f=json") + + + # Add options + url <- add_custom_query(custom_query, url) + + # Download to temp file + json <- tempfile(fileext = ".json") + res <- arc_api_call(url, json, isFALSE(verbose)) + + + + # 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_tbl_rev(tbl_query, address) + return(invisible(out)) + } + # nocov end + + result_init <- jsonlite::fromJSON(json, flatten = TRUE) + + # Empty query + if ("error" %in% names(result_init)) { + message( + "No results for query lon=", + long_cap, ", lat=", lat_cap, + "\n", result_init$error$message, "\nDetails: ", result_init$error$details + ) + out <- empty_tbl_rev(tbl_query, address) + return(invisible(out)) + } + + + + # Unnest fields + result <- unnest_reverse(result_init) + + result$lat <- as.double(result$lat) + result$lon <- as.double(result$lon) + + # Keep names + result_out <- keep_names_rev(result, + address = address, + return_coords = return_coords, + full_results = full_results + ) + + return(result_out) +} diff --git a/R/arcgeocoder-package.R b/R/arcgeocoder-package.R index a65cf64..6e5950e 100644 --- a/R/arcgeocoder-package.R +++ b/R/arcgeocoder-package.R @@ -4,3 +4,7 @@ ## usethis namespace: start ## usethis namespace: end NULL + +# import stuffs +#' @importFrom utils download.file txtProgressBar setTxtProgressBar +NULL diff --git a/R/arcgeocoder_check_access.R b/R/arcgeocoder_check_access.R new file mode 100644 index 0000000..724ff78 --- /dev/null +++ b/R/arcgeocoder_check_access.R @@ -0,0 +1,110 @@ +#' Check access to ArcGIS REST +#' +#' @family api_management +#' +#' @description +#' Check if **R** has access to resources at ArcGIS REST API +#' . +#' +#' @return a logical. +#' +#' @examples +#' \donttest{ +#' arcgeocoder_check_access() +#' } +#' @keywords internal +#' @export +arcgeocoder_check_access <- function() { + api <- paste0( + "https://geocode.arcgis.com/arcgis/rest/services/", + "World/GeocodeServer/reverseGeocode?" + ) + + # Compose url + url <- paste0(api, "location=0,0&f=json") + destfile <- tempfile(fileext = ".json") + + api_res <- arc_api_call(url, destfile, TRUE) + + # nocov start + if (isFALSE(api_res)) { + return(FALSE) + } + # nocov end + result_json <- jsonlite::fromJSON(destfile) + + # nocov start + if (result_json$location$x == 0) { + return(TRUE) + } else { + return(FALSE) + } + # nocov end +} + +skip_if_api_server <- function() { + # nocov start + if (arcgeocoder_check_access()) { + return(invisible(TRUE)) + } + + if (requireNamespace("testthat", quietly = TRUE)) { + testthat::skip("ArcGIS REST API not reachable") + } + return(invisible()) + # nocov end +} + + +#' Helper function for centralize API queries +#' +#' @description +#' A wrapper of [utils::download.file()]. On warning on error it will +#' retry the call. +#' +#' +#' @family api_management +#' +#' @inheritParams utils::download.file +#' @return A logical `TRUE/FALSE` +#' +#' @keywords internal +#' +arc_api_call <- function(url, destfile, quiet) { + # nocov start + dwn_res <- + tryCatch( + download.file(url, destfile = destfile, quiet = quiet, mode = "wb"), + warning = function(e) { + return(FALSE) + }, + error = function(e) { + return(FALSE) + } + ) + # nocov end + + # nocov start + if (isFALSE(dwn_res)) { + if (isFALSE(quiet)) message("Retrying query") + Sys.sleep(1) + + dwn_res <- + tryCatch( + download.file(url, destfile = destfile, quiet = quiet, mode = "wb"), + warning = function(e) { + return(FALSE) + }, + error = function(e) { + return(FALSE) + } + ) + } + + if (isFALSE(dwn_res)) { + return(FALSE) + } else { + return(TRUE) + } + # nocov end +} diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 0000000..644eae7 --- /dev/null +++ b/R/utils.R @@ -0,0 +1,83 @@ +# General ---- +add_custom_query <- function(custom_query = list(), url) { + if (any(length(custom_query) == 0, isFALSE(is_named(custom_query)))) { + return(url) + } + + opts <- paste0(names(custom_query), "=", custom_query, collapse = "&") + + end_url <- paste0(url, "&", opts) + + end_url +} + +is_named <- function(x) { + nm <- names(x) + + if (is.null(nm)) { + return(FALSE) + } + if (any(is.na(nm))) { + return(FALSE) + } + if (any(nm == "")) { + return(FALSE) + } + + return(TRUE) +} + +unnest_reverse <- function(x) { + x_add <- x$address + lngths <- vapply(x_add, length, FUN.VALUE = numeric(1)) + endobj <- dplyr::as_tibble(x_add[lngths == 1]) + + x_loc <- x$location + lngths_loc <- vapply(x_loc, length, FUN.VALUE = numeric(1)) + endobj_loc <- dplyr::as_tibble(x_loc[lngths_loc == 1]) + names(endobj_loc) <- c("lon", "lat") + + # ArcGIS address + if ("LongLabel" %in% names(lngths)) { + ad <- dplyr::as_tibble(x_add$LongLabel)[1, ] + names(ad) <- "address" + endobj_loc <- dplyr::bind_cols(endobj_loc, ad) + } + + endobj_loc <- dplyr::bind_cols(endobj_loc, endobj) + + if ("spatialReference" %in% names(lngths_loc)) { + bb <- dplyr::as_tibble(x_loc$spatialReference) + endobj_loc <- dplyr::bind_cols(endobj_loc, bb) + } + return(endobj_loc) +} + + +keep_names_rev <- function(x, address = "address", return_coords = FALSE, + full_results = FALSE, + colstokeep = address) { + names(x) <- gsub("address", address, names(x)) + + out_cols <- colstokeep + if (return_coords) out_cols <- c(out_cols, "lat", "lon") + if (full_results) out_cols <- c(out_cols, "lat", "lon", names(x)) + + out_cols <- unique(out_cols) + out <- x[, out_cols] + + return(out) +} + +empty_tbl_rev <- function(x, address) { + init_nm <- names(x) + x <- dplyr::as_tibble(x) + x$n <- as.character(NA) + + names(x) <- c(init_nm, address) + + # Reorder + x <- x[, c(address, init_nm)] + + x +} diff --git a/README.Rmd b/README.Rmd index ffc29de..ee276e9 100644 --- a/README.Rmd +++ b/README.Rmd @@ -37,3 +37,15 @@ This is a basic example which shows you how to solve a common problem: library(arcgeocoder) ## basic example code ``` + +## Citation + +```{r echo=FALSE, results='asis'} +print(citation("arcgeocoder"), style = "html") +``` + +A BibTeX entry for LaTeX users is + +```{r echo=FALSE, comment=""} +toBibtex(citation("arcgeocoder")) +``` diff --git a/README.md b/README.md index 34800b8..7488e04 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,22 @@ This is a basic example which shows you how to solve a common problem: library(arcgeocoder) ## basic example code ``` + +## Citation + +

+Hernangómez D (2024). arcgeocoder: Geocoding with the ArcGIS REST +API Service. +https://dieghernan.github.io/nominatimlite/. +

+ +A BibTeX entry for LaTeX users is + + @Manual{R-arcgeocoder, + title = {{arcgeocoder}: Geocoding with the {ArcGIS} {REST} {API} Service}, + author = {Diego Hernangómez}, + year = {2024}, + version = {0.0.0.9000}, + url = {https://dieghernan.github.io/nominatimlite/}, + abstract = {Lite interface for geocoding and reverse geocoding with the ArcGIS REST API service .}, + } diff --git a/arcgeocoder.Rproj b/arcgeocoder.Rproj index 7b3621a..e171837 100644 --- a/arcgeocoder.Rproj +++ b/arcgeocoder.Rproj @@ -20,5 +20,6 @@ BuildType: Package PackageUseDevtools: Yes PackageInstallArgs: --no-multiarch --with-keep.source PackageRoxygenize: rd,collate,namespace + MarkdownWrap: Column MarkdownWrapAtColumn: 80 diff --git a/codemeta.json b/codemeta.json index 29ba042..cdfeef5 100644 --- a/codemeta.json +++ b/codemeta.json @@ -2,8 +2,9 @@ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "identifier": "arcgeocoder", - "description": "What the package does (one paragraph).", - "name": "arcgeocoder: What the Package Does (One Line, Title Case)", + "description": "Lite interface for geocoding and reverse geocoding with the 'ArcGIS' REST API service .", + "name": "arcgeocoder: Geocoding with the 'ArcGIS' REST API Service", + "relatedLink": "https://dieghernan.github.io/nominatimlite/", "codeRepository": "https://github.com/dieghernan/arcgeocoder", "issueTracker": "https://github.com/dieghernan/arcgeocoder/issues", "license": "https://spdx.org/licenses/MIT", @@ -13,7 +14,7 @@ "name": "R", "url": "https://r-project.org" }, - "runtimePlatform": "R version 4.3.2 (2023-10-31 ucrt)", + "runtimePlatform": "R version 4.3.2 (2023-10-31)", "author": [ { "@type": "Person", @@ -41,8 +42,84 @@ "@id": "https://orcid.org/0000-0001-8457-4658" } ], + "softwareSuggestions": [ + { + "@type": "SoftwareApplication", + "identifier": "testthat", + "name": "testthat", + "version": ">= 3.0.0", + "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=testthat" + }, + { + "@type": "SoftwareApplication", + "identifier": "tidygeocoder", + "name": "tidygeocoder", + "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=tidygeocoder" + } + ], "softwareRequirements": { + "1": { + "@type": "SoftwareApplication", + "identifier": "R", + "name": "R", + "version": ">= 3.6.0" + }, + "2": { + "@type": "SoftwareApplication", + "identifier": "dplyr", + "name": "dplyr", + "version": ">= 1.0.0", + "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=dplyr" + }, + "3": { + "@type": "SoftwareApplication", + "identifier": "jsonlite", + "name": "jsonlite", + "version": ">= 1.7.0", + "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=jsonlite" + }, "SystemRequirements": null }, - "fileSize": "2.743KB" + "applicationCategory": "cartography", + "keywords": ["r", "geocoding", "arcgis", "address", "reverse-geocoding", "rstats", "r-package", "api-wrapper"], + "fileSize": "24.378KB", + "citation": [ + { + "@type": "SoftwareSourceCode", + "datePublished": "2024", + "author": [ + { + "@type": "Person", + "givenName": "Diego", + "familyName": "Hernangómez" + } + ], + "name": "{arcgeocoder}: Geocoding with the {ArcGIS} {REST} {API} Service", + "url": "https://dieghernan.github.io/nominatimlite/" + } + ] } diff --git a/inst/CITATION b/inst/CITATION new file mode 100644 index 0000000..09460b6 --- /dev/null +++ b/inst/CITATION @@ -0,0 +1,20 @@ +year <- format(Sys.time(), "%Y") +abs <- trimws(gsub("\\s+", " ", meta$Description)) +abs <- gsub("'", "", abs) +title <- gsub("'", "", meta$Title, fixed = TRUE) +title <- gsub("ArcGIS", "{ArcGIS}", title, fixed = TRUE) +title <- gsub("API", "{API}", title, fixed = TRUE) +title <- gsub("REST", "{REST}", title, fixed = TRUE) + + +bibentry( + "Manual", + header = "To cite the 'arcgeocoder' package in publications use:", + title = paste("{arcgeocoder}:", title), + author = person("Diego", "Hernangómez"), + year = year, + key = "R-arcgeocoder", + version = meta$Version, + url = unlist(strsplit(meta$URL, ","))[1], + abstract = abs +) diff --git a/inst/schemaorg.json b/inst/schemaorg.json index b3acf74..7b127da 100644 --- a/inst/schemaorg.json +++ b/inst/schemaorg.json @@ -1,25 +1,38 @@ { "@context": "https://schema.org", - "type": "SoftwareSourceCode", - "author": { - "id": "https://orcid.org/0000-0001-8457-4658" - }, - "codeRepository": "https://github.com/dieghernan/arcgeocoder", - "copyrightHolder": { - "id": "https://orcid.org/0000-0001-8457-4658", - "type": "Person", - "email": "diego.hernangomezherrero@gmail.com", - "familyName": "Hernangómez", - "givenName": "Diego" - }, - "description": "What the package does (one paragraph).", - "license": "https://spdx.org/licenses/MIT", - "name": "arcgeocoder: What the Package Does (One Line, Title Case)", - "programmingLanguage": { - "type": "ComputerLanguage", - "name": "R", - "url": "https://r-project.org" - }, - "runtimePlatform": "R version 4.3.2 (2023-10-31 ucrt)", - "version": "0.0.0.9000" + "@graph": [ + { + "type": "SoftwareSourceCode", + "author": { + "id": "https://orcid.org/0000-0001-8457-4658" + }, + "codeRepository": "https://github.com/dieghernan/arcgeocoder", + "copyrightHolder": { + "id": "https://orcid.org/0000-0001-8457-4658", + "type": "Person", + "email": "diego.hernangomezherrero@gmail.com", + "familyName": "Hernangómez", + "givenName": "Diego" + }, + "description": "Lite interface for geocoding and reverse geocoding with the 'ArcGIS' REST API service .", + "license": "https://spdx.org/licenses/MIT", + "name": "arcgeocoder: Geocoding with the 'ArcGIS' REST API Service", + "programmingLanguage": { + "type": "ComputerLanguage", + "name": "R", + "url": "https://r-project.org" + }, + "runtimePlatform": "R version 4.3.2 (2023-10-31)", + "version": "0.0.0.9000" + }, + { + "type": "SoftwareSourceCode", + "author": { + "type": "Person", + "familyName": "Hernangómez", + "givenName": "Diego" + }, + "name": "{arcgeocoder}: Geocoding with the {ArcGIS} {REST} {API} Service" + } + ] } diff --git a/man/arc_api_call.Rd b/man/arc_api_call.Rd new file mode 100644 index 0000000..fdcc551 --- /dev/null +++ b/man/arc_api_call.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/arcgeocoder_check_access.R +\name{arc_api_call} +\alias{arc_api_call} +\title{Helper function for centralize API queries} +\usage{ +arc_api_call(url, destfile, quiet) +} +\arguments{ +\item{url}{a \code{\link{character}} string (or longer vector + for the \code{"libcurl"} method) naming the URL of a resource to be + downloaded.} + +\item{destfile}{a character string (or vector, see the \code{url} + argument) with the file path where the downloaded file is to be + saved. Tilde-expansion is performed.} + +\item{quiet}{If \code{TRUE}, suppress status messages (if any), and + the progress bar.} +} +\value{ +A logical \code{TRUE/FALSE} +} +\description{ +A wrapper of \code{\link[utils:download.file]{utils::download.file()}}. On warning on error it will +retry the call. +} +\seealso{ +Other api_management: +\code{\link{arcgeocoder_check_access}()} +} +\concept{api_management} +\keyword{internal} diff --git a/man/arc_reverse_geo.Rd b/man/arc_reverse_geo.Rd new file mode 100644 index 0000000..2f4d819 --- /dev/null +++ b/man/arc_reverse_geo.Rd @@ -0,0 +1,75 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/arc_reverse_geo.R +\name{arc_reverse_geo} +\alias{arc_reverse_geo} +\title{Reverse Geocoding using the ArcGIS REST API} +\usage{ +arc_reverse_geo( + lat, + long, + address = "address", + full_results = FALSE, + return_coords = TRUE, + verbose = FALSE, + progressbar = TRUE, + custom_query = list() +) +} +\arguments{ +\item{lat}{latitude values in numeric format. Must be in the range +\verb{[-90, 90]}.} + +\item{long}{longitude values in numeric format. Must be in the range +\verb{[-180, 180]}.} + +\item{address}{address column name in the output data (default \code{"address"}).} + +\item{full_results}{returns all available data from the API service. If +\code{FALSE} (default) only latitude, longitude and address columns are +returned.} + +\item{return_coords}{return input coordinates with results if \code{TRUE}.} + +\item{verbose}{if \code{TRUE} then detailed logs are output to the console.} + +\item{progressbar}{Logical. If \code{TRUE} displays a progress bar to indicate +the progress of the function.} + +\item{custom_query}{API-specific parameters to be used, passed as a named +list (ie. \code{list(featureTypes = "POI")}). See \strong{Details}.} +} +\value{ +A \CRANpkg{tibble} with the results. +} +\description{ +Generates an address from a latitude and longitude. Latitudes must be +between \verb{[-90, 90]} and longitudes between \verb{[-180, 180]}. This +function returns the \CRANpkg{tibble} associated with the query. +} +\details{ +See \url{https://developers.arcgis.com/rest/geocode/api-reference/geocoding-reverse-geocode.htm} +for additional parameters to be passed to \code{custom_query}. +} +\examples{ +\dontshow{if (arcgeocoder_check_access()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +\donttest{ + +arc_reverse_geo(lat = 40.75728, long = -73.98586) + +# Several coordinates +arc_reverse_geo(lat = c(40.75728, 55.95335), long = c(-73.98586, -3.188375)) + +# With options: zoom to country level +sev <- arc_reverse_geo( + lat = c(40.75728, 55.95335), long = c(-73.98586, -3.188375), + custom_query = list(featureTypes = "StreetInt,POI", langCode = "FR"), + verbose = TRUE, full_results = TRUE +) + +dplyr::glimpse(sev) +} +\dontshow{\}) # examplesIf} +} +\seealso{ +\code{\link[tidygeocoder:reverse_geo]{tidygeocoder::reverse_geo()}} +} diff --git a/man/arcgeocoder-package.Rd b/man/arcgeocoder-package.Rd index 225f462..3935eaf 100644 --- a/man/arcgeocoder-package.Rd +++ b/man/arcgeocoder-package.Rd @@ -4,13 +4,14 @@ \name{arcgeocoder-package} \alias{arcgeocoder} \alias{arcgeocoder-package} -\title{arcgeocoder: What the Package Does (One Line, Title Case)} +\title{arcgeocoder: Geocoding with the 'ArcGIS' REST API Service} \description{ -What the package does (one paragraph). +Lite interface for geocoding and reverse geocoding with the 'ArcGIS' REST API service \url{https://developers.arcgis.com/rest/geocode/api-reference/overview-world-geocoding-service.htm}. } \seealso{ Useful links: \itemize{ + \item \url{https://dieghernan.github.io/nominatimlite/} \item \url{https://github.com/dieghernan/arcgeocoder} \item Report bugs at \url{https://github.com/dieghernan/arcgeocoder/issues} } diff --git a/man/arcgeocoder_check_access.Rd b/man/arcgeocoder_check_access.Rd new file mode 100644 index 0000000..acf9074 --- /dev/null +++ b/man/arcgeocoder_check_access.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/arcgeocoder_check_access.R +\name{arcgeocoder_check_access} +\alias{arcgeocoder_check_access} +\title{Check access to ArcGIS REST} +\usage{ +arcgeocoder_check_access() +} +\value{ +a logical. +} +\description{ +Check if \strong{R} has access to resources at ArcGIS REST API +\url{https://developers.arcgis.com/rest/geocode/api-reference/overview-world-geocoding-service.htm}. +} +\examples{ +\donttest{ +arcgeocoder_check_access() +} +} +\seealso{ +Other api_management: +\code{\link{arc_api_call}()} +} +\concept{api_management} +\keyword{internal} diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml new file mode 100644 index 0000000..ab06d41 --- /dev/null +++ b/pkgdown/_pkgdown.yml @@ -0,0 +1,20 @@ +url: 'https://dieghernan.github.io/arcgeocoder' + +template: + bootstrap: 5 + package: gitdevr + opengraph: + twitter: + creator: "@dhernangomez" + card: summary_large_image + +repo: + branch: main + +home: + title: arcgeocoder | Geocoding with the ArcGIS REST API Service + description: Lite interface for geocoding and reverse geocoding with the ArcGIS REST API service. +authors: + Diego Hernangómez: + href: 'https://dieghernan.github.io/' + diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100644 index 0000000..ae20341 --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,12 @@ +# This file is part of the standard setup for testthat. +# It is recommended that you do not modify it. +# +# Where should you do additional test configuration? +# Learn more about the roles of various files in: +# * https://r-pkgs.org/testing-design.html#sec-tests-files-overview +# * https://testthat.r-lib.org/articles/special-files.html + +library(testthat) +library(arcgeocoder) + +test_check("arcgeocoder") diff --git a/tests/testthat/test-arc_reverse_geo.R b/tests/testthat/test-arc_reverse_geo.R new file mode 100644 index 0000000..1dc4981 --- /dev/null +++ b/tests/testthat/test-arc_reverse_geo.R @@ -0,0 +1,217 @@ +test_that("Errors", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + + expect_error( + arc_reverse_geo(0, c(2, 3)), + "lat and long should have the same number" + ) + expect_error( + arc_reverse_geo("a", "a"), + "must be numeric" + ) +}) + +test_that("Messages", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + + expect_message(out <- arc_reverse_geo(0, 200)) + chk <- dplyr::tibble(lat = 0, lon = 180) + expect_identical(out[, c("lat", "lon")], chk) + + expect_message(out <- arc_reverse_geo(200, 0)) + chk <- dplyr::tibble(lat = 90, lon = 0) + expect_identical(out[, c("lat", "lon")], chk) +}) + +test_that("Returning empty query", { + skip_on_cran() + skip_if_api_server() + + expect_message( + obj <- arc_reverse_geo(89.999999, 179.9999, + custom_query = + list(featureTypes = "StreetInt") + ), + "No results for query lon" + ) + + expect_true(nrow(obj) == 1) + expect_true(obj$lat == 89.999999) + expect_true(obj$lon == 179.9999) + expect_s3_class(obj, "tbl") + expect_identical(names(obj), c("address", "lat", "lon")) + expect_true(all( + vapply(obj, class, FUN.VALUE = character(1)) + == c("character", rep("numeric", 2)) + )) + expect_true(is.na(obj$address)) + + expect_message( + obj_renamed <- arc_reverse_geo(89.999999, 179.9999, + address = "adddata", + custom_query = + list(featureTypes = "StreetInt") + ), + "No results for" + ) + + expect_identical(names(obj_renamed), c("adddata", "lat", "lon")) + + names(obj_renamed) <- names(obj) + + expect_identical(obj, obj_renamed) +}) + + +test_that("Data format", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + + obj <- arc_reverse_geo(0, 0) + expect_s3_class(obj, "tbl") +}) + +test_that("Checking query", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + obj <- arc_reverse_geo(40.4207414, -3.6687109) + expect_s3_class(obj, "tbl") + expect_equal(nrow(obj), 1) + + expect_identical(names(obj), c("address", "lat", "lon")) + + # Same with option + obj_zoom <- arc_reverse_geo(40.4207414, -3.6687109, + custom_query = list(featureTypes = "StreetInt") + ) + + + expect_s3_class(obj_zoom, "tbl") + expect_equal(nrow(obj_zoom), 1) + expect_false(identical(obj, obj_zoom)) + + # Several coordinates + sev <- arc_reverse_geo( + lat = c(40.75728, 55.95335), + long = c(-73.98586, -3.188375) + ) + + expect_equal(nrow(sev), 2) + expect_s3_class(sev, "tbl") + + # Check opts + obj <- arc_reverse_geo(40.4207414, -3.6687109, + address = "addrs" + ) + + expect_s3_class(obj, "tbl") + expect_equal(nrow(obj), 1) + + expect_identical(names(obj), c("addrs", "lat", "lon")) + + + # Check opts + obj <- arc_reverse_geo(40.4207414, -3.6687109, + address = "addrs", return_coords = FALSE + ) + + expect_s3_class(obj, "tbl") + expect_identical(names(obj), "addrs") + + obj <- arc_reverse_geo(40.4207414, -3.6687109, + address = "addrs", return_coords = FALSE, + full_results = TRUE + ) + + expect_s3_class(obj, "tbl") + expect_identical(names(obj)[1:3], c("addrs", "lat", "lon")) + expect_gt(ncol(obj), 5) + + obj2 <- arc_reverse_geo(40.4207414, -3.6687109, + address = "addrs", return_coords = TRUE, + full_results = TRUE + ) + + expect_identical(obj, obj2) +}) + + +test_that("Check unnesting", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + + + # Several coordinates + sev <- arc_reverse_geo( + lat = c(40.75728, 55.95335), + long = c(-73.98586, -3.188375), + full_results = TRUE + ) + + expect_s3_class(sev, "tbl") + expect_equal(nrow(sev), 2) + + + # Classes of all cols + + colclass <- vapply(sev, class, FUN.VALUE = character(1)) + + # Rest of columns not list + expect_false(any(grepl("list", colclass["boundingbox" != names(colclass)]))) +}) + +test_that("Dedupe", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + # Dupes + + lats <- rep(c(40.75728, 55.95335), 50) + longs <- rep(c(-73.98586, -3.188375), 50) + + expect_silent(dup <- arc_reverse_geo(lats, longs, progressbar = FALSE)) + + expect_equal(nrow(dup), 100) + + nms <- unique(as.character(dup$address)) + expect_length(nms, 2) + expect_equal(as.character(dup$address), rep(nms, 50)) + + # Check deduping + dedup <- dplyr::distinct(dup) + + expect_equal(nrow(dedup), 2) + expect_equal(as.character(dedup$address), nms) +}) + +test_that("Progress bar", { + skip_on_cran() + skip_if_api_server() + skip_if_offline() + + lat <- c(40.75728, 55.95335) + long <- c(-73.98586, -3.188375) + + # No pbar + expect_silent(arc_reverse_geo(lat[1], long[1])) + expect_silent(arc_reverse_geo(lat[1], long[1], progressbar = TRUE)) + + # Get a pbar + expect_output(aa <- arc_reverse_geo(lat, long), "50") + + # Not + expect_silent(aa <- arc_reverse_geo(lat, long, progressbar = FALSE)) +}) diff --git a/tests/testthat/test-arcgeocoder_check_access.R b/tests/testthat/test-arcgeocoder_check_access.R new file mode 100644 index 0000000..f786855 --- /dev/null +++ b/tests/testthat/test-arcgeocoder_check_access.R @@ -0,0 +1,5 @@ +test_that("Check access", { + t <- expect_silent(arcgeocoder_check_access()) + + expect_type(t, "logical") +})