library(arcgis)
<- auth_code()
token set_auth_token(token)
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:
add_features()
update_features()
delete_features()
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.
#> 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)
<- read_sf(system.file("shape/nc.shp", package = "sf"))
nc_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_sf |>
nc_summary summarise(
1across(
2.cols = c(ends_with("74"), ends_with("79")),
3.fns = mean
),4NAME = "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 thetidyselect
helpers to catch any columns that end with74
or79
. - 3
-
The
.fns
argument specifies which functions will be applied to the columns. In this case, we apply on themean()
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()
.
<- "https://services1.arcgis.com/hLJbHVT9ZrDIzK0I/arcgis/rest/services/North%20Carolina%20SIDS/FeatureServer/0"
nc_url
<- arc_open(nc_url) nc
<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 theFeatureLayer
object that we want to add features to.data
is ansf
object that we want to add to theFeatureLayer
match_on
determines how to match sf columns toFeatureLayer
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_features(nc, nc_summary)
add_res add_res
objectId uniqueId globalId success
1 101 101 NA TRUE
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.
<- refresh_layer(nc) 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 |>
nc_avgs 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_avgs |>
nc_area_perim 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.
<- nc_area_perim |>
to_update 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_features(nc, to_update) update_res
$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_features(nc, object_ids = 101)
delete_res 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_features(nc, where = "BIR74 < 1000")
delete_res 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_features(nc, where = "1 = 1")
delete_res 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
.