Skip to content

Commit

Permalink
version 0.0.2
Browse files Browse the repository at this point in the history
  • Loading branch information
christopherkenny authored and cran-robot committed Jul 6, 2023
0 parents commit 2331cee
Show file tree
Hide file tree
Showing 41 changed files with 1,290 additions and 0 deletions.
30 changes: 30 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Package: feltr
Title: Access the Felt API
Version: 0.0.2
Authors@R:
person(given = "Christopher T.",
family = "Kenny",
role = c("aut", "cre"),
email = "christopherkenny@fas.harvard.edu",
comment = c(ORCID = "0000-0002-9386-6860"))
Description: Upload, download, and edit internet maps with the Felt API
(<https://feltmaps.notion.site/Felt-Public-API-reference-c01e0e6b0d954a678c608131b894e8e1>).
Allows users to create new maps, edit existing maps, and extract data.
Provides tools for working with layers, which represent geographic data, and elements,
which are interactive annotations. Spatial data accessed from the API is
transformed to work with 'sf'.
License: MIT + file LICENSE
Encoding: UTF-8
RoxygenNote: 7.2.3
Imports: cli, curl, fs, geojsonsf, httr2, jsonlite, purrr, rlang, sf,
stringr, tibble
URL: http://christophertkenny.com/feltr/
Suggests: httptest2, testthat (>= 3.0.0)
Config/testthat/edition: 3
NeedsCompilation: no
Packaged: 2023-07-05 18:23:29 UTC; chris
Author: Christopher T. Kenny [aut, cre]
(<https://orcid.org/0000-0002-9386-6860>)
Maintainer: Christopher T. Kenny <christopherkenny@fas.harvard.edu>
Repository: CRAN
Date/Publication: 2023-07-06 14:00:05 UTC
2 changes: 2 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
YEAR: 2023
COPYRIGHT HOLDER: feltr authors
40 changes: 40 additions & 0 deletions MD5
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
a85119eb8b0120e9b55e418813d7da1e *DESCRIPTION
616833dab9c77074aa2657b112f9b006 *LICENSE
49c6d2e68f787871b5180e2581a2a751 *NAMESPACE
fc8c3f4aba17dc5e5349f19f403e602b *NEWS.md
f889f13f4f86101b68895561b001531b *R/add.R
8fcd2188d566f0fb75a6b2a1b6c0bb44 *R/api_url.R
664bc1bbc9a7502133332dac60e4601c *R/create.R
f0e5a27a0faeae409b2a93dcdea14074 *R/delete.R
f278c9a0af3fe63ee62f618ab0fe247e *R/felt_key.R
ce4445d76f667ef40162fc32d37386c8 *R/finish_upload.R
d14cf3cd69beb12710970a876e364e31 *R/helpers.R
237763b046a5380d44a6dc0bfad7a468 *R/map.R
358003bfcd9a3512bb17f92d559bb7dd *R/map_elements.R
ecbdaa8a4d3478635cc95a1b4e2bf789 *R/proc.R
02478d6ea773f7ded0c72e56316e92cf *R/user.R
c8bee16add2c0cd8fb803b73fb55ea2b *README.md
dcafe3a9c7fc22b1830fe8c5bebde3a5 *inst/httptest2/redact.R
47024474bcdf50c357e673031273e3f4 *inst/towns.geojson
a0b9edb7109d774d7368f226d23c6d7c *man/felt_add_map_layers.Rd
65e462d0d60782227fb1d5dbcc4b281b *man/felt_add_map_layers_url.Rd
8a10e9cbd4dec730f65436b1f5b167da *man/felt_create_map.Rd
1be2c7cfda80ada35bfcbf369d93c619 *man/felt_delete_map.Rd
14471f60025ab3f70783abf1e3d688c7 *man/felt_delete_map_layer.Rd
7b6eeb77674fb869dfe52b1e77fe38e3 *man/felt_finish_upload.Rd
76f65dbdd6623811448fbae598d220b8 *man/felt_get_map.Rd
c5efa2eac855c704a6a15e8eb93834bf *man/felt_get_map_elements.Rd
ac6a20b3d829e4dd4ef3040af028a3be *man/felt_get_user.Rd
d0014c782bb08375032c800c376d3d00 *man/figures/logo.png
22e89a3aad1cc54a3f97c521987c2633 *man/key.Rd
06ac6d486f81c291323f4aa8f77916a5 *man/set_felt_key.Rd
e09dfa175bba9ede03319f774ddcae22 *tests/testthat.R
ff3b353b55b6eaa57bec2ed603745880 *tests/testthat/add.R
b013980eae16bb9f943c1ff2f6475354 *tests/testthat/setup.R
121fdc6280a720c2a55c6e84a61d5e97 *tests/testthat/t/create/felt.com/api/v1/maps-b1286e-POST.json
058ce516e115d1b8efb611a80215afdd *tests/testthat/t/get_elements/felt.com/api/v1/maps/TBI8sDkmQjuK2GX9CSiHiUA/elements.R
661ee4e4456fd7998790598934f03259 *tests/testthat/t/user/felt.com/api/v1/user.json
f060f0dae7f8431313ebd4231f9c034d *tests/testthat/test-create.R
f8f9cbf6b4b8f908ef0caa13a05b1b7c *tests/testthat/test-map.R
b3f9a1162626b1a99d09ca500f51c96b *tests/testthat/test-map_elements.R
d3cd84c5695fa3671622c9d9efe6f8f3 *tests/testthat/test-user.R
17 changes: 17 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by roxygen2: do not edit by hand

export(felt_add_map_layers)
export(felt_add_map_layers_url)
export(felt_create_map)
export(felt_delete_map)
export(felt_delete_map_layer)
export(felt_finish_upload)
export(felt_get_map)
export(felt_get_map_elements)
export(felt_get_map_geojson)
export(felt_get_map_layers)
export(felt_get_map_sf)
export(felt_get_user)
export(get_felt_key)
export(has_felt_key)
export(set_felt_key)
8 changes: 8 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# feltr 0.0.2

* Adds path arguments to `felt_set_key()` and `felt_get_map_sf()` to ensure compliance with CRAN policies in writing files.

# feltr 0.0.1

* Added a `NEWS.md` file to track changes to the package.
* Initial package with support for API features as of July 2023.
102 changes: 102 additions & 0 deletions R/add.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#' Add Layers to Existing Map
#'
#' @param map_id map identifier from url, from `https://felt.com/map/Readable-Name-map_id`
#' @param file_names Files to include
#' @param name Name of the layer
#' @param fill_color Color to fill shape with, typically a hexcode. Defaults to `NULL`.v
#' @param stroke_color Color to outline shape with, typically a hexcode. Defaults to `NULL`.
#'
#' @return a string with the id for the created layer
#' @export
#'
#' @examplesIf has_felt_key()
#' felt_add_map_layers(map_id = 'Rockland-2024-Districts-TBI8sDkmQjuK2GX9CSiHiUA',
#' file_names = fs::path_package('feltr', 'towns.geojson'),
#' name = 'Towns')
felt_add_map_layers <- function(map_id, name = NULL, file_names = NULL,
fill_color = NULL, stroke_color = NULL) {
# don't support webhooks for now
webhook_url = NULL

body <- list(
file_names = list(fs::path_file(file_names)),
name = name,
fill_color = fill_color,
stroke_color = stroke_color,
webhook_url = webhook_url
) |>
purrr::discard(is.null)

req <- httr2::request(base_url = api_url()) |>
httr2::req_url_path_append('maps', map_id, 'layers') |>
httr2::req_auth_bearer_token(token = get_felt_key()) |>
httr2::req_body_json(body, auto_unbox = TRUE)

upload_info <- req |>
httr2::req_perform() |>
httr2::resp_body_json()


args <- upload_info |>
purrr::pluck('data', 'attributes', 'presigned_attributes')
upload <- upload_info |>
purrr::pluck('data', 'attributes', 'url') |>
httr2::request() |>
req_injected(!!!args, file = curl::form_file(file_names))

out <- upload |>
httr2::req_perform()

finish_upload <- felt_finish_upload(map_id)

upload_info$data$attributes$layer_id
}

#' Add Layers to Existing Map from URL
#'
#' See [Felt "Upload Anything" documentation](https://feltmaps.notion.site/b26d739e80184127872faa923b55d232?pvs=25#3e37f06bc38c4971b435fbff2f4da6cb)
#' for detailed examples of potential URLs.
#'
#' @param map_id map identifier from url, from `https://felt.com/map/Readable-Name-map_id`
#' @param url Link to layer to include
#' @param name Name of the layer
#'
#' @return a [tibble::tibble] for the created layer
#' @export
#'
#' @examplesIf has_felt_key()
#' # split the URL for length reasons
#' url <- paste0(
#' 'https://www.rocklandgis.com/portal/sharing/rest/',
#' 'content/items/73fc78cb0fb04580b4788937fe5ee697/data'
#' )
#' felt_add_map_layers_url(
#' map_id = 'Rockland-2024-Districts-TBI8sDkmQjuK2GX9CSiHiUA',
#' url = url,
#' name = 'Parks')
felt_add_map_layers_url <- function(map_id, url, name = NULL) {

body <- list(
layer_url = url,
name = name
) |>
purrr::discard(is.null)

req <- httr2::request(base_url = api_url()) |>
httr2::req_url_path_append('maps', map_id, 'layers', 'url_import') |>
httr2::req_auth_bearer_token(token = get_felt_key()) |>
httr2::req_body_json(body, auto_unbox = TRUE)

out <- req |>
httr2::req_perform() |>
httr2::resp_body_json()

tibble::tibble(
name = out$data$attributes$name,
status = out$data$attributes$status,
progress = out$data$attributes$progress,
id = out$data$id,
type = out$data$type,
relationships = out$data$relationships$datasets$data
)
}
3 changes: 3 additions & 0 deletions R/api_url.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
api_url <- function() {
'https://felt.com/api/v1'
}
38 changes: 38 additions & 0 deletions R/create.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#' Create a new map
#'
#' @param title Title to use for the map. Defaults to `NULL`.
#' @param basemap Basemap for the new map. Defaults to `NULL`. Can be a URL or color hex code
#' @param layer_urls vector of URLs to generate layers in map. Defaults to `NULL`.
#' @param lat latitude to center the map. Defaults to `NULL`.
#' @param lon longitude to center the map. Defaults to `NULL`
#' @param zoom zoom level to initialize the map with.
#'
#' @return a [tibble::tibble] for the new map
#' @export
#'
#' @examplesIf has_felt_key()
#' felt_create_map(title = 'feltr example')
felt_create_map <- function(title = NULL, basemap = NULL, layer_urls = NULL,
lat = NULL, lon = NULL, zoom = NULL) {
if (!is.null(layer_urls)) layer_urls <- as.list(layer_urls)

body <- list(
title = title,
basemap = basemap,
layer_urls = layer_urls,
lat = lat,
lon = lon,
zoom = zoom
) |>
purrr::discard(is.null)

req <- httr2::request(base_url = api_url()) |>
httr2::req_url_path_append('maps') |>
httr2::req_auth_bearer_token(token = get_felt_key()) |>
httr2::req_body_json(body, auto_unbox = TRUE)

req |>
httr2::req_perform() |>
httr2::resp_body_json() |>
proc_map()
}
44 changes: 44 additions & 0 deletions R/delete.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#' Delete Layer from an Existing Map
#'
#' @param map_id map identifier from url, from `https://felt.com/map/Readable-Name-map_id`
#' @param layer_id layer identifier, as returned by `felt_get_map_layers()` or `felt_add_map_layers()`
#'
#' @return response code
#' @export
#'
#' @examplesIf has_felt_key()
#' layer <- felt_add_map_layers('TBI8sDkmQjuK2GX9CSiHiUA',
#' 'test', fs::path_package('feltr', 'towns.geojson'))
#' felt_delete_map_layer(map_id = 'Rockland-2024-Districts-TBI8sDkmQjuK2GX9CSiHiUA',
#' layer)
felt_delete_map_layer <- function(map_id, layer_id) {
req <- httr2::request(base_url = api_url()) |>
httr2::req_url_path_append('maps', map_id, 'layers', layer_id) |>
httr2::req_auth_bearer_token(token = get_felt_key()) |>
httr2::req_method(method = 'DELETE')

req |>
httr2::req_perform() |>
httr2::resp_status()
}

#' Delete an existing map
#'
#' @param map_id map identifier from url, from `https://felt.com/map/Readable-Name-map_id`
#'
#' @return response code
#' @export
#'
#' @examplesIf has_felt_key()
#' map <- felt_create_map(title = 'feltr example')
#' felt_delete_map(map_id = map$id)
felt_delete_map <- function(map_id) {
req <- httr2::request(base_url = api_url()) |>
httr2::req_url_path_append('maps', map_id) |>
httr2::req_auth_bearer_token(token = get_felt_key()) |>
httr2::req_method(method = 'DELETE')

req |>
httr2::req_perform() |>
httr2::resp_status()
}
94 changes: 94 additions & 0 deletions R/felt_key.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#' Check or Get Felt API Key
#'
#' @return logical if `has`, key if `get`
#' @export
#'
#' @name key
#'
#' @examples
#' has_felt_key()
has_felt_key <- function() {
Sys.getenv('FELT_KEY') != ''
}

#' @rdname key
#' @export
get_felt_key <- function() {
key <- Sys.getenv('FELT_KEY')

# if (key == '') {
# cli::cli_abort('Must set a key as {.val FELT_KEY}.')
# }
key
}

#' Add Entry to Renviron
#'
#' Adds Felt API key to .Renviron.
#'
#' @param key Character. API key to add to add.
#' @param overwrite Defaults to FALSE. Boolean. Should existing `FELT_KEY` in Renviron be overwritten?
#' @param install Defaults to FALSE. Boolean. Should this be added to an environment file, `r_env`?
#' @param r_env Path to install to if `install` is `TRUE`.
#'
#' @return key, invisibly
#' @export
#'
#' @examples
#' example_env <- tempfile(fileext = '.Renviron')
#' set_felt_key('1234', r_env = example_env)
#' # r_env should likely be: file.path(Sys.getenv('HOME'), '.Renviron')
set_felt_key <- function(key, overwrite = FALSE, install = FALSE,
r_env = NULL) {
if (missing(key)) {
cli::cli_abort('Input {.arg key} cannot be missing.')
}
name <- 'FELT_KEY'

key <- list(key)
names(key) <- name

if (install) {

if (is.null(r_env)) {
r_env <- file.path(Sys.getenv('HOME'), '.Renviron')
if (interactive()) {
utils::askYesNo(paste0('Install to', r_env, '?'))
} else {
cli::cli_abort(c('No path set and not run interactively.',
i = 'Rerun with {.arg r_env} set, possibly to {.file {r_env}}'))
}
}

if (!file.exists(r_env)) {
file.create(r_env)
}

lines <- readLines(r_env)
newline <- paste0(name, "='", key, "'")

exists <- stringr::str_detect(lines, paste0(name, '='))

if (any(exists)) {
if (sum(exists) > 1) {
cli::cli_abort('Multiple entries in .Renviron found.\nEdit manually with {.fn usethis::edit_r_environ}.')
}

if (overwrite) {
lines[exists] <- newline
writeLines(lines, r_env)
do.call(Sys.setenv, key)
} else {
cli::cli_inform('{.arg FELT_KEY} already exists in .Renviron. \nEdit manually with {.fn usethis::edit_r_environ} or set {.code overwrite = TRUE}.')
}
} else {
lines[length(lines) + 1] <- newline
writeLines(lines, r_env)
do.call(Sys.setenv, key)
}
}# else {
# do.call(Sys.setenv, key)
#}

invisible(key)
}

0 comments on commit 2331cee

Please sign in to comment.