diff --git a/NAMESPACE b/NAMESPACE index c2689a2..57d59c9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -25,6 +25,7 @@ export(clear_query) export(create_feature_server) export(delete_features) export(download_attachments) +export(encode_field_values) export(get_all_layers) export(get_layer) export(get_layer_estimates) @@ -39,8 +40,7 @@ export(publish_layer) export(pull_field_aliases) export(query_layer_attachments) export(refresh_layer) -export(set_layer_coded_values) -export(set_layer_col_names) +export(set_layer_aliases) export(truncate_layer) export(update_features) export(update_params) diff --git a/R/arc-read.R b/R/arc-read.R index df98668..1a875a9 100644 --- a/R/arc-read.R +++ b/R/arc-read.R @@ -18,7 +18,22 @@ #' @param n_max Defaults to `Inf` or an option set with #' `options("arcgislayers.n_max" = )`. Maximum number of records #' to return. -#' @inheritParams set_layer_col_names +#' @param col_names Default `TRUE`. Column names or name handling rule. +#' `col_names` can be `TRUE`, `FALSE`, `NULL`, or a character vector: +#' +#' - If `TRUE`, use existing default column names for the layer or table. +#' If `FALSE` or `NULL`, column names will be generated automatically: X1, X2, +#' X3 etc. +#' - If `col_names` is a character vector, values replace the existing column +#' names. `col_names` can't be length 0 or longer than the number of fields in +#' the returned layer. +#' @param alias Use of field alias values. Default `c("drop", "label", +#' "replace"),`. There are three options: +#' +#' - `"drop"`, field alias values are ignored. +#' - `"label"`: field alias values are assigned as a label attribute for each field. +#' - `"replace"`: field alias values replace existing column names. `col_names` +#' @inheritParams set_layer_aliases #' @param fields Default `NULL`. a character vector of the field names to #' returned. By default all fields are returned. Ignored if `col_names` is #' supplied. @@ -112,85 +127,53 @@ arc_read <- function( ... ) - set_layer_col_names( - .data = layer, - .layer = x, - col_names = col_names, - name_repair = name_repair, - alias = alias - ) -} - -#' Set and repair column names for FeatureLayer or Table data frame -#' -#' [set_layer_col_names()] can replace or label column names based on the the -#' field aliases from a corresponding `Table` or `FeatureLayer` object created -#' with `arc_open()`. Optionally repair names using [vctrs::vec_as_names()]. -#' -#' @param .data A data frame returned by `arc_select() `or `arc_read()`. -#' @param .layer A Table or FeatureLayer object. Required if `alias` is `"label"` or -#' `"replace"`. -#' @param col_names Default `TRUE`. Column names or name handling rule. -#' `col_names` can be `TRUE`, `FALSE`, `NULL`, or a character vector: -#' -#' - If `TRUE`, use existing default column names for the layer or table. -#' If `FALSE` or `NULL`, column names will be generated automatically: X1, X2, -#' X3 etc. -#' - If `col_names` is a character vector, values replace the existing column -#' names. `col_names` can't be length 0 or longer than the number of fields in -#' the returned layer. -#' @param alias Use of field alias values. Default `c("drop", "label", -#' "replace"),`. There are three options: -#' -#' - `"drop"`, field alias values are ignored. -#' - `"label"`: field alias values are assigned as a label attribute for each field. -#' - `"replace"`: field alias values replace existing column names. `col_names` -#' must `TRUE` for this option to be applied. -#' @param name_repair Default `"unique"`. See [vctrs::vec_as_names()] for -#' details. If `name_repair = NULL`, names are set directly. -#' @inheritParams rlang::args_error_context -#' @export -set_layer_col_names <- function( - .data, - .layer = NULL, - col_names = TRUE, - name_repair = NULL, - alias = c("drop", "label", "replace"), - call = rlang::caller_env()) { # check col_names input if (!is.null(col_names) && !rlang::is_logical(col_names) && !is.character(col_names)) { - cli::cli_abort( - "{.arg col_names} must be `TRUE`, `FALSE`, `NULL`, or a character vector.", - call = call - ) + cli::cli_abort("{.arg col_names} must be `TRUE`, `FALSE`, `NULL`, or a character vector.") } - alias <- rlang::arg_match(alias, error_call = call) - - # skip col_names and alias handling if possible - if (rlang::is_true(col_names) && alias == "drop") { - return(repair_layer_names(.data, name_repair = name_repair, call = call)) + if (identical(col_names, "alias")) { + # Set alias to "replace" as name if col_names = "alias" + alias <- "replace" + lifecycle::deprecate_soft( + "deprecated", + what = "arc_read(col_names = \"can't be alias\")", + with = "arc_read(alias = \"replace\")", + ) } - existing_nm <- names(.data) - n_col <- ncol(.data) - sf_column <- attr(.data, "sf_column") + if (identical(alias, "drop") || is.character(col_names) || isFALSE(col_names)) { + layer <- set_col_names( + .data = layer, + col_names = col_names, + name_repair = name_repair + ) - # Use existing names by default - replace_nm <- existing_nm + return(layer) + } - if (alias != "drop" || identical(col_names, "alias")) { - # get alias values and drop names - alias_val <- pull_field_aliases(.layer)[setdiff(existing_nm, sf_column)] - alias_val <- as.character(alias_val) + set_layer_aliases( + .data = layer, + .layer = x, + name_repair = name_repair, + alias = alias + ) +} - if (alias == "replace") { - # NOTE: alias values may not be valid names - replace_nm <- alias_val - } - } +#' Handle col_names +#' @noRd +set_col_names <- function(.data, + col_names = TRUE, + name_repair = NULL, + call = rlang::caller_env() +) { + n_col <- ncol(.data) + nm <- names(.data) - if (is.character(col_names)) { + if (rlang::is_false(col_names)) { + # Use X1, X2, etc. as names if col_names is FALSE + nm <- paste0("X", seq(n_col)) + } else if (is.character(col_names)) { col_names_len <- length(col_names) # Check col_names length @@ -199,55 +182,73 @@ set_layer_col_names <- function( "{.arg col_names} must be length {n_col}{? or shorter}, not {col_names_len}.", call = call ) + } else if ((col_names_len + 1) < n_col) { + # fill missing field names using pattern, X1, X2, etc. + col_names <- c(col_names, paste0("X", seq(length(col_names) + 1, n_col))) } - if (identical(col_names, "alias")) { - # Assign alias values as name if col_names = "alias" - col_names <- alias_val - lifecycle::signal_stage( - "superseded", - what = "arc_read(col_names = \"can't be alias\")", - with = "arc_read(alias = \"replace\")", - ) + # But always keep the default sf column name + if (inherits(.data, "sf")) { + col_names[[n_col]] <- attr(.data, "sf_column") } - replace_nm <- col_names + nm <- col_names } - if (rlang::is_false(col_names) || is.null(col_names)) { - # Use X1, X2, etc. as names if col_names is FALSE - replace_nm <- paste0("X", seq_along(existing_nm)) - } + repair_layer_names(.data, names = nm, name_repair = name_repair, call = call) +} - replace_nm_len <- length(replace_nm) +#' Set column labels or names based FeatureLayer or Table data frame field aliases +#' +#' [set_layer_aliases()] can replace or label column names based on the the +#' field aliases from a corresponding `Table` or `FeatureLayer` object created +#' with `arc_open()`. Optionally repair names using [vctrs::vec_as_names()]. +#' +#' @param .data A data frame returned by `arc_select()` or `arc_read()`. +#' @param .layer A Table or FeatureLayer object. Required. +#' @param alias Use of field alias values. Default `c("label", "replace"),`. +#' There are two options: +#' +#' - `"label"`: field alias values are assigned as a label attribute for each field. +#' - `"replace"`: field alias values replace existing column names. +#' @param name_repair Default `"unique"`. See [vctrs::vec_as_names()] for +#' details. If `name_repair = NULL` and `alias = "replace"` may include +#' invalid names. +#' @inheritParams rlang::args_error_context +#' @export +set_layer_aliases <- function( + .data, + .layer, + name_repair = "unique", + alias = c("label", "replace"), + call = rlang::caller_env()) { + alias <- rlang::arg_match(alias, error_call = call) + nm <- names(.data) + sf_column <- attr(.data, "sf_column") + alias_val <- pull_field_aliases(.layer)[setdiff(nm, sf_column)] - if (replace_nm_len < n_col) { - # fill missing field names using pattern, X1, X2, etc. - replace_nm <- c(replace_nm, paste0("X", seq(replace_nm_len + 1, n_col))) + # NOTE: alias values may not be valid names + if (alias == "replace") { + # get unnamed alias values + nm <- unname(alias_val) - # But keep the default sf column name - if (inherits(.data, "sf")) { - replace_nm[[n_col]] <- sf_column + # geometry columns don't include an alias so keep existing + if (!is.null(sf_column)) { + nm[[ncol(.data)]] <- sf_column } } .data <- repair_layer_names( .data, - names = replace_nm, + names = nm, name_repair = name_repair, call = call ) - if (alias != "label") { + if (alias == "replace") { return(.data) } - # Name alias values with layer names - alias_val <- rlang::set_names( - alias_val, - nm = setdiff(replace_nm, sf_column) - ) - label_layer_fields(.data, values = alias_val) } @@ -297,66 +298,64 @@ label_layer_fields <- function( x } - #' Set coded values for FeatureLayer or Table data frame #' -#' [set_layer_coded_values()] can replace column values based on `codedValue` +#' [encode_field_values()] can replace column values based on `codedValue` #' type field domains from a corresponding `Table` or `FeatureLayer` object #' created with `arc_open()`. #' #' @param .data A data frame returned by `arc_select()` or `arc_read()`. #' @param .layer A Table or FeatureLayer object. Required. -#' @param field Default `NULL`. Field or fields to replace. Fields that are do -#' not have coded values domains are ignored. -#' @param codes Use of field alias values. Default `c("replace"),`. +#' @param field Default `NULL`. Field or fields to replace. Fields that do +#' not have coded value domains are ignored. +#' @param codes Use of field alias values. Defaults to `c("replace", "label"),`. #' There are two options: #' #' - `"replace"`: coded values replace existing column values. #' - `"label"`: coded values are applied as value labels via a `"label"` attribute. #' @inheritParams rlang::args_error_context #' @export -set_layer_coded_values <- function( +encode_field_values <- function( .data, .layer, field = NULL, codes = c("replace", "label"), call = rlang::caller_env()) { - values <- pull_coded_values(.layer, field = field) + values <- pull_coded_values(.layer, field = field, call = call) + + codes <- rlang::arg_match(codes, error_call = call) # Check if coded values is an empty list if (rlang::is_empty(values)) { - if (is.null(field)) { - cli::cli_warn( - "{.arg layer} does not contain any coded values." - ) - } else { - cli::cli_warn( - "{.arg field} does not specific any coded value fields." - ) + message <- "{.arg layer} does not contain any coded values." + + if (!is.null(field)) { + message <- "{.arg field} {.val {field}} do not contain any coded values." } + cli::cli_warn(message) return(.data) } - codes <- rlang::arg_match(codes, error_call = call) - + # Replace column values by default if (codes == "replace") { - # Replace column values by default for (col in names(values)) { .data[[col]] <- values[[col]][.data[[col]]] } - } else { - # Label column values using new_labelled_col helper - for (col in names(values)) { - .data[[col]] <- new_labelled_col( - .data[[col]], - labels = rlang::set_names( - names(values[[col]]), - values[[col]] - ), - call = call - ) - } + + return(.data) + } + + # Label column values using new_labelled_col helper + for (col in names(values)) { + .data[[col]] <- new_labelled_col( + .data[[col]], + labels = rlang::set_names( + names(values[[col]]), + values[[col]] + ), + call = call + ) } .data diff --git a/R/utils.R b/R/utils.R index a999c3c..8a4fa8e 100644 --- a/R/utils.R +++ b/R/utils.R @@ -263,15 +263,24 @@ parse_url_query <- function(url, keep_default = FALSE) { url_elements[["query"]] } +#' List field domains for a layer #' @noRd -list_field_domains <- function(x, field = NULL, keep_null = FALSE) { +list_field_domains <- function(x, + field = NULL, + keep_null = FALSE, + arg = rlang::caller_arg(x), + call = rlang::caller_env()) { fields <- list_fields(x) nm <- fields[["name"]] + if (is.null(nm)) { + cli::cli_abort("{.arg {x}} must have field names.", call = call) + } + domains <- rlang::set_names(fields[["domain"]], nm) if (!is.null(field)) { - field <- rlang::arg_match(nm, multiple = TRUE) + field <- rlang::arg_match(nm, multiple = TRUE, error_call = call) domains <- domains[nm %in% field] } @@ -282,13 +291,23 @@ list_field_domains <- function(x, field = NULL, keep_null = FALSE) { domains[!vapply(domains, is.null, logical(1))] } +#' Pull a named list of codes for fields using codedValue domain type #' @noRd -pull_coded_values <- function(x, field = NULL) { - domains <- list_field_domains(x, field = field, keep_null = FALSE) +pull_coded_values <- function(x, + field = NULL, + arg = rlang::caller_arg(x), + call = rlang::caller_env()) { + domains <- list_field_domains( + x, + field = field, + keep_null = FALSE, + arg = arg, + call = call + ) domains <- lapply( domains, - \(x) { + function(x) { if (x[["type"]] != "codedValue") { return(NULL) } diff --git a/man/arc_read.Rd b/man/arc_read.Rd index fd3dd3b..04aa734 100644 --- a/man/arc_read.Rd +++ b/man/arc_read.Rd @@ -39,7 +39,8 @@ returned. By default, all fields are returned.} to return.} \item{name_repair}{Default \code{"unique"}. See \code{\link[vctrs:vec_as_names]{vctrs::vec_as_names()}} for -details. If \code{name_repair = NULL}, names are set directly.} +details. If \code{name_repair = NULL} and \code{alias = "replace"} may include +invalid names.} \item{crs}{the spatial reference to be returned. If the CRS is different than the CRS for the input \code{FeatureLayer}, a transformation will occur @@ -57,7 +58,6 @@ supplied.} \item \code{"drop"}, field alias values are ignored. \item \code{"label"}: field alias values are assigned as a label attribute for each field. \item \code{"replace"}: field alias values replace existing column names. \code{col_names} -must \code{TRUE} for this option to be applied. }} \item{token}{your authorization token.} diff --git a/man/set_layer_coded_values.Rd b/man/encode_field_values.Rd similarity index 69% rename from man/set_layer_coded_values.Rd rename to man/encode_field_values.Rd index f2dcd76..e4c914b 100644 --- a/man/set_layer_coded_values.Rd +++ b/man/encode_field_values.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/arc-read.R -\name{set_layer_coded_values} -\alias{set_layer_coded_values} +\name{encode_field_values} +\alias{encode_field_values} \title{Set coded values for FeatureLayer or Table data frame} \usage{ -set_layer_coded_values( +encode_field_values( .data, .layer, field = NULL, @@ -17,14 +17,14 @@ set_layer_coded_values( \item{.layer}{A Table or FeatureLayer object. Required.} -\item{field}{Default \code{NULL}. Field or fields to replace. Fields that are do -not have coded values domains are ignored.} +\item{field}{Default \code{NULL}. Field or fields to replace. Fields that do +not have coded value domains are ignored.} -\item{codes}{Use of field alias values. Default \verb{c("replace"),}. +\item{codes}{Use of field alias values. Defaults to \verb{c("replace", "label"),}. There are two options: \itemize{ \item \code{"replace"}: coded values replace existing column values. -\item \code{"label"}: coded values are applied as value labels. +\item \code{"label"}: coded values are applied as value labels via a \code{"label"} attribute. }} \item{call}{The execution environment of a currently @@ -33,7 +33,7 @@ mentioned in error messages as the source of the error. See the \code{call} argument of \code{\link[rlang:abort]{abort()}} for more information.} } \description{ -\code{\link[=set_layer_coded_values]{set_layer_coded_values()}} can replace column values based on \code{codedValue} +\code{\link[=encode_field_values]{encode_field_values()}} can replace column values based on \code{codedValue} type field domains from a corresponding \code{Table} or \code{FeatureLayer} object created with \code{arc_open()}. } diff --git a/man/set_layer_aliases.Rd b/man/set_layer_aliases.Rd new file mode 100644 index 0000000..b27675c --- /dev/null +++ b/man/set_layer_aliases.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/arc-read.R +\name{set_layer_aliases} +\alias{set_layer_aliases} +\title{Set column labels or names based FeatureLayer or Table data frame field aliases} +\usage{ +set_layer_aliases( + .data, + .layer, + name_repair = "unique", + alias = c("label", "replace"), + call = rlang::caller_env() +) +} +\arguments{ +\item{.data}{A data frame returned by \code{arc_select()} or \code{arc_read()}.} + +\item{.layer}{A Table or FeatureLayer object. Required.} + +\item{name_repair}{Default \code{"unique"}. See \code{\link[vctrs:vec_as_names]{vctrs::vec_as_names()}} for +details. If \code{name_repair = NULL} and \code{alias = "replace"} may include +invalid names.} + +\item{alias}{Use of field alias values. Default \verb{c("label", "replace"),}. +There are two options: +\itemize{ +\item \code{"label"}: field alias values are assigned as a label attribute for each field. +\item \code{"replace"}: field alias values replace existing column names. +}} + +\item{call}{The execution environment of a currently +running function, e.g. \code{caller_env()}. The function will be +mentioned in error messages as the source of the error. See the +\code{call} argument of \code{\link[rlang:abort]{abort()}} for more information.} +} +\description{ +\code{\link[=set_layer_aliases]{set_layer_aliases()}} can replace or label column names based on the the +field aliases from a corresponding \code{Table} or \code{FeatureLayer} object created +with \code{arc_open()}. Optionally repair names using \code{\link[vctrs:vec_as_names]{vctrs::vec_as_names()}}. +} diff --git a/man/set_layer_col_names.Rd b/man/set_layer_col_names.Rd deleted file mode 100644 index 72d52b9..0000000 --- a/man/set_layer_col_names.Rd +++ /dev/null @@ -1,53 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/arc-read.R -\name{set_layer_col_names} -\alias{set_layer_col_names} -\title{Set and repair column names for FeatureLayer or Table data frame} -\usage{ -set_layer_col_names( - .data, - .layer = NULL, - col_names = TRUE, - name_repair = NULL, - alias = c("drop", "label", "replace"), - call = rlang::caller_env() -) -} -\arguments{ -\item{.data}{A data frame returned by \code{arc_select() }or \code{arc_read()}.} - -\item{.layer}{A Table or FeatureLayer object. Required if \code{alias} is \code{"label"} or -\code{"replace"}.} - -\item{col_names}{Default \code{TRUE}. Column names or name handling rule. -\code{col_names} can be \code{TRUE}, \code{FALSE}, \code{NULL}, or a character vector: -\itemize{ -\item If \code{TRUE}, use existing default column names for the layer or table. -If \code{FALSE} or \code{NULL}, column names will be generated automatically: X1, X2, -X3 etc. -\item If \code{col_names} is a character vector, values replace the existing column -names. \code{col_names} can't be length 0 or longer than the number of fields in -the returned layer. -}} - -\item{name_repair}{Default \code{"unique"}. See \code{\link[vctrs:vec_as_names]{vctrs::vec_as_names()}} for -details. If \code{name_repair = NULL}, names are set directly.} - -\item{alias}{Use of field alias values. Default \verb{c("drop", "label", "replace"),}. There are three options: -\itemize{ -\item \code{"drop"}, field alias values are ignored. -\item \code{"label"}: field alias values are assigned as a label attribute for each field. -\item \code{"replace"}: field alias values replace existing column names. \code{col_names} -must \code{TRUE} for this option to be applied. -}} - -\item{call}{The execution environment of a currently -running function, e.g. \code{caller_env()}. The function will be -mentioned in error messages as the source of the error. See the -\code{call} argument of \code{\link[rlang:abort]{abort()}} for more information.} -} -\description{ -\code{\link[=set_layer_col_names]{set_layer_col_names()}} can replace or label column names based on the the -field aliases from a corresponding \code{Table} or \code{FeatureLayer} object created -with \code{arc_open()}. Optionally repair names using \code{\link[vctrs:vec_as_names]{vctrs::vec_as_names()}}. -}