Editing Features

Learn to add, update, or delete features

Programmatically, adding, deleting, or updating features using {arcgis} is a deceptively simple process. In this workflow, we illustrate how to add, update, or delete features from an existing hosted Feature Layer or Table.

We will go over the functions:

Pre-requisites

We will use the the North Carolina SIDS dataset we created in the Publishing from R tutorial. To follow along be sure that you have followed that tutorial and have a FeatureLayer that you can modify. If you have not yet configured your environment to authorize with an online portal, start at Connecting to your portal.

Adding features

For this example, we will add a single feature to the North Carolina SIDS dataset that is a summary over the entire state. Before we can begin, we must load the package and authorize ourselves as a user.

library(arcgis)

token <- auth_code()
set_auth_token(token)
#> Token set to environment variable `ARCGIS_TOKEN`

Next, we will create the feature that we want to add using the sf package. We’ll read in the nc.shp file from the sf package.

library(sf)
nc_sf <- read_sf(system.file("shape/nc.shp", package = "sf"))
nc_sf
Simple feature collection with 100 features and 14 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -84.32385 ymin: 33.88199 xmax: -75.45698 ymax: 36.58965
Geodetic CRS:  NAD27
# A tibble: 100 × 15
    AREA PERIMETER CNTY_ CNTY_ID NAME  FIPS  FIPSNO CRESS_ID BIR74 SID74 NWBIR74
   <dbl>     <dbl> <dbl>   <dbl> <chr> <chr>  <dbl>    <int> <dbl> <dbl>   <dbl>
 1 0.114      1.44  1825    1825 Ashe  37009  37009        5  1091     1      10
 2 0.061      1.23  1827    1827 Alle… 37005  37005        3   487     0      10
 3 0.143      1.63  1828    1828 Surry 37171  37171       86  3188     5     208
 4 0.07       2.97  1831    1831 Curr… 37053  37053       27   508     1     123
 5 0.153      2.21  1832    1832 Nort… 37131  37131       66  1421     9    1066
 6 0.097      1.67  1833    1833 Hert… 37091  37091       46  1452     7     954
 7 0.062      1.55  1834    1834 Camd… 37029  37029       15   286     0     115
 8 0.091      1.28  1835    1835 Gates 37073  37073       37   420     0     254
 9 0.118      1.42  1836    1836 Warr… 37185  37185       93   968     4     748
10 0.124      1.43  1837    1837 Stok… 37169  37169       85  1612     1     160
# ℹ 90 more rows
# ℹ 4 more variables: BIR79 <dbl>, SID79 <dbl>, NWBIR79 <dbl>,
#   geometry <MULTIPOLYGON [°]>

Let’s calculate the average birth rate, SIDS rate, and the non-white birth rate and SIDS rate for the entire state. We will add this as a single feature to our existing feature layer. To do so, we will use the R package dplyr for manipulating our data.

library(dplyr)

nc_summary <- nc_sf |>  
  summarise(
1    across(
2      .cols = c(ends_with("74"), ends_with("79")),
3      .fns = mean
    ),
4    NAME = "Total"
  ) 

nc_summary
1
The across() function applies a function to multiple columns at once.
2
We specify the columns we will be apply a function to in .cols. We use the tidyselect helpers to catch any columns that end with 74 or 79.
3
The .fns argument specifies which functions will be applied to the columns. In this case, we apply on the mean() function to calculate the average.
4
The NAME field is set manually to the value of "Total" to indicate that it is not a county.
Simple feature collection with 1 feature and 7 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -84.32385 ymin: 33.88199 xmax: -75.45698 ymax: 36.58965
Geodetic CRS:  NAD27
# A tibble: 1 × 8
  BIR74 SID74 NWBIR74 BIR79 SID79 NWBIR79 NAME                          geometry
  <dbl> <dbl>   <dbl> <dbl> <dbl>   <dbl> <chr>               <MULTIPOLYGON [°]>
1 3300.  6.67   1051. 4224.  8.36   1353. Total (((-75.97629 36.51793, -75.9772…

In order to add this new aggregate feature to the FeatureLayer we must create a reference to the layer using arc_open().

nc_url <- "https://services1.arcgis.com/hLJbHVT9ZrDIzK0I/arcgis/rest/services/North%20Carolina%20SIDS/FeatureServer/0" 

nc <- arc_open(nc_url)
<FeatureLayer>
Name: North Carolina SIDS
Geometry Type: esriGeometryPolygon
CRS: 4267
Capabilities: Create,Delete,Query,Update,Editing

The url you use here will be different than the one you see. Be sure to grab the correct url from the content listing.

Now that we have a FeatureLayer object we can add features to it using add_features(). There are a few key arguments to the function:

  • x is the FeatureLayer object that we want to add features to
  • .data is an sf object that we want to add to the FeatureLayer
  • match_on determines how to match sf columns to FeatureLayer fields

By default, add_features() will compare the column names of the sf object to that of the FeatureLayer. We can find the field names and aliases for a FeatureLayer by using the list_fields() function. Pass the results to tibble::as_tibble() to make them more readable.

Since we know that the column names match those of the FeatureLayer we can pass nc_summary directly to add_feature().

add_res <- add_features(nc, nc_summary)
add_res
  objectId uniqueId globalId success
1      101      101       NA    TRUE
Tip

If you are adding a lot of features at one time, consider changing the value of chunk_size. By default, add_features() will add up to 2000 features at a time and send the requests in parallel. Depending on the geometry type and precision, it may be worthwhile to make that number smaller. If the data are truly massive, consider breaking up the task into smaller manageable chunks.

Once we’ve added the results to the FeatureLayer, we may want to refresh the object to catch any important changes to the metadata.

nc <- refresh_layer(nc)
<FeatureLayer>
Name: North Carolina SIDS
Geometry Type: esriGeometryPolygon
CRS: 4267
Capabilities: Create,Delete,Query,Update,Editing

We can see that the FeatureLayer now has 101 features as opposed to the original 100. To sanity check, we can query nc to see how the value comes back.

nc_avgs <- nc |> 
  filter(NAME == "Total") |> 
  collect()

nc_avgs
Simple feature collection with 1 feature and 15 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -84.32385 ymin: 33.88199 xmax: -75.45698 ymax: 36.58965
Geodetic CRS:  NAD27
  object_id AREA PERIMETER CNTY_ CNTY_ID  NAME FIPS FIPSNO CRESS_ID   BIR74 SID74 NWBIR74   BIR79 SID79 NWBIR79                       geometry
1       101   NA        NA    NA      NA Total   NA     NA       NA 3299.62  6.67 1050.81 4223.92  8.36 1352.81 MULTIPOLYGON (((-75.9248 36...

Updating Features

In the previous section we added a new feature that is the average of our numeric columns and stored the results in the variable nc_totals. When looking at it, we can see that the AREA AND PERIMTER values are missing. These might be helpful at a later point.

In this section we will use the function update_features() to modify these values. First, let’s create a new object called to_update that has the AREA and PERIMETER computed.

nc_area_perim <- nc_avgs |> 
  mutate(
    AREA = st_area(geometry) / 1e10,
    PERIMETER = s2::s2_perimeter(geometry) / 1e5
  )

nc_area_perim
Simple feature collection with 1 feature and 15 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -84.32385 ymin: 33.88199 xmax: -75.45698 ymax: 36.58965
Geodetic CRS:  NAD27
  object_id           AREA PERIMETER CNTY_ CNTY_ID  NAME FIPS FIPSNO CRESS_ID   BIR74 SID74 NWBIR74   BIR79 SID79 NWBIR79                       geometry
1       101 12.70259 [m^2]  33.58819    NA      NA Total   NA     NA       NA 3299.62  6.67 1050.81 4223.92  8.36 1352.81 MULTIPOLYGON (((-75.9248 36...

Like add_features(), we need to be able to match columns to their respective fields. The match_on argument is used to specify if the column names match the field name or field alias.

In the case of update_features() we also need to be able to match the features in the sf dataset to the exact feature in the FeatureLayer. We do this by providing the object ID of the feature. This tells ArcGIS which features we are actually going to update.

When using update_features() we should be aware that every column present in the sf object will be updated including the geometry. For this reason, we should select only those columns which we truly wish to update.

to_update <- nc_area_perim |> 
  st_drop_geometry() |> 
  select(object_id, AREA, PERIMETER)

to_update

Here we use sf::st_drop_geometry()to remove the geometry of our object since we do not want to update the geometry in our FeatureLayer. We also only select the object_id, AREA, and PERIMETER columns so that we do not make an errant updates.

update_res <- update_features(nc, to_update)
$updateResults
  objectId uniqueId globalId success
1      101      101       NA    TRUE

Our update process was successful! We can repeat our previous query to verify this.

 nc |> 
  filter(NAME == "Total") |> 
  collect()
Simple feature collection with 1 feature and 15 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -84.32385 ymin: 33.88199 xmax: -75.45698 ymax: 36.58965
Geodetic CRS:  NAD27
  object_id     AREA PERIMETER CNTY_ CNTY_ID  NAME FIPS FIPSNO CRESS_ID   BIR74 SID74 NWBIR74   BIR79 SID79 NWBIR79                       geometry
1       101 12.70259  33.58819    NA      NA Total   NA     NA       NA 3299.62  6.67 1050.81 4223.92  8.36 1352.81 MULTIPOLYGON (((-75.9248 36...

Deleting Features

While add_features() and update_features() had a very similar syntax, delete_features() has a somewhat different interface. We have 3 different ways in which we can delete features. Here we will explore only two of them.

We can delete features based on object IDs, or a SQL where clause. Let explore deleting features based on object IDs. To do so, we need to pass the FeatureLayer obejct as the first argument to delete_features() and the second argument is a numeric vector of the IDs we want to delete. The ID 101 is the new feature that we created.

delete_res <- delete_features(nc, object_ids = 101)
delete_res
$deleteResults
  objectId uniqueId globalId success
1      101      101       NA    TRUE

We can check to see if the delete worked by refreshing the laye and seeing the count that is printed out.

refresh_layer(nc)
<FeatureLayer>
Name: North Carolina SIDS
Geometry Type: esriGeometryPolygon
CRS: 4267
Capabilities: Create,Delete,Query,Update,Editing

Alternatively, we can delete features based on a where clause. Say we wanted to delete all of the features where the BIR74 value was less than 1000. We can accomplish this using a where clause.

delete_res <- delete_features(nc, where = "BIR74 < 1000")
delete_res
$deleteResults
   objectId uniqueId globalId success
1         2        2       NA    TRUE
2         4        4       NA    TRUE
3         7        7       NA    TRUE
4         8        8       NA    TRUE
5         9        9       NA    TRUE
6        20       20       NA    TRUE
7        21       21       NA    TRUE
8        22       22       NA    TRUE
9        32       32       NA    TRUE
10       35       35       NA    TRUE
11       38       38       NA    TRUE
12       44       44       NA    TRUE
13       45       45       NA    TRUE
14       56       56       NA    TRUE
15       58       58       NA    TRUE
16       59       59       NA    TRUE
17       73       73       NA    TRUE
18       77       77       NA    TRUE
19       78       78       NA    TRUE
20       80       80       NA    TRUE
21       83       83       NA    TRUE
22       87       87       NA    TRUE
23       90       90       NA    TRUE

Successful deletes! Again, we can check to see the new count using refresh_layer().

refresh_layer(nc)
<FeatureLayer>
Name: North Carolina SIDS
Geometry Type: esriGeometryPolygon
CRS: 4267
Capabilities: Create,Delete,Query,Update,Editing

Lastly, if you want to delete every single feature. We can take advantage of the where clause again. If we set where = "1 = 1" that will evaluate TRUE for every single feature.

delete_res <- delete_features(nc, where = "1 = 1")
delete_res
$deleteResults
   objectId uniqueId globalId success
1         1        1       NA    TRUE
2         3        3       NA    TRUE
3         5        5       NA    TRUE
4         6        6       NA    TRUE
5        10       10       NA    TRUE
6        11       11       NA    TRUE
          ... Truncated ...
refresh_layer(nc)
<FeatureLayer>
Name: North Carolina SIDS
Geometry Type: esriGeometryPolygon
CRS: 4267
Capabilities: Create,Delete,Query,Update,Editing

Using delete_features(x, where = "1 = 1") is basically the equivalent of truncate_layer().

Congratulations! You’ve now learned how to add features, update them, and delete them from a hosted FeatureLayer.