Feature services in ArcGIS Online and ArcGIS Enterprise can store attachments linked to individual features within their layers. This capability is commonly used in Survey123 forms where users upload photos, documents, or other files as part of their data collection. The R-ArcGIS Bridge provides three core functions for managing these attachments: querying metadata, downloading files, and updating attachments.
Understanding Attachment Workflows
Before working with attachments, you need to establish a connection to your feature layer. This requires authentication if you’re accessing private services or plan to modify attachments. The typical workflow involves opening a layer reference, querying to understand what attachments exist, and then downloading or updating those files as needed.
Querying Attachment Metadata
The query_layer_attachments() function retrieves metadata about attachments without downloading the actual files. This lets you inspect what attachments exist, their file names, sizes, and which features they belong to before committing to a download operation.
library(arcgisutils) library(arcgislayers) # Authenticate if working with private data set_arc_token(auth_user()) # Connect to your feature layer furl <- "https://services1.arcgis.com/hLJbHVT9ZrDIzK0I/arcgis/rest/services/v8_Wide_Area_Search_Form_Feature_Layer___a2fe9c/FeatureServer/0" layer <- arc_open(furl) # Query all attachments in the layer att <- query_layer_attachments(layer) # preview the results dplyr::glimpse(att)
#> Rows: 101 #> Columns: 10 #> $ parentGlobalId <chr> "51f34e57-003c-4cb5-b9c6-694c5bc63480", "ca558ac8-3377-… #> $ parentObjectId <int> 13, 14, 19, 21, 22, 27, 34, 35, 36, 40, 43, 49, 52, 53,… #> $ id <int> 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,… #> $ globalId <chr> "1646cbf2-b884-4b85-a8fa-6ce38dfe7474", "71bbbee7-0fd6-… #> $ name <chr> "02d5cd1337d24bc699d6e1e55292c6d9.jpg", "049441602a4048… #> $ contentType <chr> "image/jpeg", "image/jpeg", "image/jpeg", "image/jpeg",… #> $ size <int> 401051, 87400, 179744, 250221, 211540, 152460, 155329, … #> $ keywords <chr> "", "", "", "", "", "", "", "", "", "", "", "", "", "",… #> $ url <chr> "https://services1.arcgis.com/hLJbHVT9ZrDIzK0I/arcgis/r… #> $ exifInfo <list> [<data.frame[4 x 2]>], [<data.frame[3 x 2]>], [<data.f…
The returned data frame contains columns like PARENTOBJECTID (which feature the attachment belongs to), att_name (the file name), and id (the attachment’s unique identifier). This metadata allows you to make informed decisions about which attachments to download.
Filtering Attachments
You can filter attachments in two ways:
- by feature attributes or,
- by attachment properties.
Filtering by feature attributes is useful when you only want attachments from features meeting certain criteria, such as records flagged for follow-up.
The definition_expression argument accepts a standard SQL WHERE clause that is applied to the features in the feature service. This is akin to the where argument in arc_select().
# Filter based on **feature** attributes # Only get attachments where the followup_status field # is set to `"needs_followup" query_layer_attachments( layer, "followup_status = 'needs_followup'" )
#> # A data frame: 24 × 10 #> parentGlobalId parentObjectId id globalId name contentType size #> * <chr> <int> <int> <chr> <chr> <chr> <int> #> 1 498bb7fa-1b64-48b5-8c… 49 13 dd2afd9… 38f8… image/jpeg 272721 #> 2 0b45291f-ec76-4ee1-91… 53 15 146c89e… 3f12… image/jpeg 289968 #> 3 0b45291f-ec76-4ee1-91… 53 16 e929c75… 3f6c… image/jpeg 163008 #> 4 0b45291f-ec76-4ee1-91… 53 17 54f1beb… 401c… image/jpeg 240901 #> 5 edbe0c9f-f2a7-48b1-89… 69 23 482d4f6… 4f6f… image/jpeg 138262 #> 6 e2d7ed7e-092a-4860-8d… 80 30 36fbdb2… 5c8a… image/jpeg 396238 #> 7 5e059ee4-cbc7-4089-a8… 84 32 9e5cb7b… 669a… image/jpeg 411913 #> 8 8823ae01-6799-4b74-ab… 86 33 2e807a4… 69a2… image/jpeg 344543 #> 9 612edf09-92cd-4e39-8b… 91 36 c8981b5… 70ce… image/jpeg 368083 #> 10 612edf09-92cd-4e39-8b… 91 37 1ff5060… 727b… image/jpeg 288994 #> # ℹ 14 more rows #> # ℹ 3 more variables: keywords <chr>, url <chr>, exifInfo <list>
Alternatively, you can filter based on the properties of the attachment itself. To see what properties are available to query based on you can check the attachmentProperties element of the layer object.
layer$attachmentProperties
#> name fieldName isEnabled #> 1 id ATTACHMENTID TRUE #> 2 globalId AttachmentGlobalID TRUE #> 3 name ATT_NAME TRUE #> 4 size DATA_SIZE TRUE #> 5 contentType CONTENT_TYPE TRUE #> 6 keywords Keywords TRUE #> 7 exifInfo ExifInfo TRUE
This shows that we can query based on the fieldNames. To actually query based on these, we use the attachments_definition_expression argument which is applied to the attachment properties.
# Filter based on attachment file names # Only get attachments from a specific date attachments <- query_layer_attachments( layer, attachments_definition_expression = "att_name like '%20221005%'" )
Downloading Attachments
Once you’ve identified the attachments you need, download_attachments() retrieves the actual files to your local system. The function takes the metadata from query_layer_attachments() and a destination folder, then downloads each file while preserving its original name.
# Create a temporary directory for downloads tmp <- file.path(tempdir(), ulid::ulid()) # Create the directory if it doesn't exist dir.create(tmp, recursive = TRUE, showWarnings = FALSE) # Download the queried attachments res <- download_attachments(attachments, tmp, .progress = FALSE) # Verify the downloads downloaded <- list.files(tmp)
The function returns a data frame showing the download results, including file paths and any errors encountered. This makes it easy to verify successful downloads and handle any issues that arose during the transfer.
Updating Attachments
Updating attachments requires write access to the feature service, which typically means you need to own the service or have editing permissions granted by the owner. The update process involves uploading new attachment files and associating them with specific features using the update_attachments() function. This operation requires proper authentication through set_arc_token() before attempting any modifications.
In this example we will prepend the current date to the files. To do this, we will rename the files locally.
# get today's date today <- Sys.Date() # prepend attachments with the date new_filenames <- paste0(today, "-", basename(attachments$name)) # create new file paths new_fps <- file.path(tmp, new_filenames) # the full name of downloaded attachments fps <- file.path(tmp, downloaded) # rename the files file.rename(fps, new_fps)
#> [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE #> [16] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
Now that we have renamed the files, we can use update_attachments() to update the attachments in the feature service.
# update the attachments update_res <- update_attachments( layer, # OID of the feature <> attachment relationship attachments$parentObjectId, # the attachment ID attachments$id, # the path to the attachment on disk new_fps, # turn off for rmarkdown .progress = FALSE ) head(update_res)
#> objectId success #> 1 75 TRUE #> 2 76 TRUE #> 3 77 TRUE #> 4 78 TRUE #> 5 79 TRUE #> 6 80 TRUE
Similarly, we can undo our change just as easily!
# rename the files again file.rename(new_fps, fps)
#> [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE #> [16] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
update_res <- update_attachments( layer, attachments$parentObjectId, attachments$id, # using the previous filenames fps, .progress = FALSE ) head(update_res)
#> objectId success #> 1 75 TRUE #> 2 76 TRUE #> 3 77 TRUE #> 4 78 TRUE #> 5 79 TRUE #> 6 80 TRUE